Add duplicate recipes/classes page

Add page that lists recipes and classes "duplicated" across different
layers.

Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
This commit is contained in:
Paul Eggleton 2013-05-13 10:59:12 +01:00
parent 9af1144928
commit 89f9e6afe8
5 changed files with 156 additions and 25 deletions

2
TODO
View File

@ -20,7 +20,7 @@ Later:
* Return to last page (review/detail) after editing (with success alert)? * Return to last page (review/detail) after editing (with success alert)?
* Cancel button on edit form? * Cancel button on edit form?
* Query backend service? i.e. special URL to query information for external apps/scripts * Query backend service? i.e. special URL to query information for external apps/scripts
* Tool for finding/comparing duplicate recipes? * Add comparison to duplicates page
* Tool for editing SUMMARY/DESCRIPTION? [Paul working on this] * Tool for editing SUMMARY/DESCRIPTION? [Paul working on this]
* Dynamic loading/filtering for recipes list * Dynamic loading/filtering for recipes list
* Some way to notify the user when they search for something that has been renamed / replaced / deprecated? * Some way to notify the user when they search for something that has been renamed / replaced / deprecated?

View File

@ -8,7 +8,7 @@ from django.conf.urls.defaults import *
from django.views.generic import TemplateView, DetailView, ListView from django.views.generic import TemplateView, DetailView, ListView
from django.views.defaults import page_not_found from django.views.defaults import page_not_found
from layerindex.models import LayerItem, Recipe from layerindex.models import LayerItem, Recipe
from layerindex.views import LayerListView, LayerReviewListView, LayerReviewDetailView, RecipeSearchView, MachineSearchView, PlainTextListView, LayerDetailView, edit_layer_view, delete_layer_view, edit_layernote_view, delete_layernote_view, switch_branch_view, HistoryListView, EditProfileFormView from layerindex.views import LayerListView, LayerReviewListView, LayerReviewDetailView, RecipeSearchView, MachineSearchView, PlainTextListView, LayerDetailView, edit_layer_view, delete_layer_view, edit_layernote_view, delete_layernote_view, switch_branch_view, HistoryListView, EditProfileFormView, DuplicatesView
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', url(r'^$',
@ -71,6 +71,10 @@ urlpatterns = patterns('',
HistoryListView.as_view( HistoryListView.as_view(
template_name='layerindex/history.html'), template_name='layerindex/history.html'),
name='history_list'), name='history_list'),
url(r'^duplicates/$',
DuplicatesView.as_view(
template_name='layerindex/duplicates.html'),
name='duplicates'),
url(r'^profile/$', url(r'^profile/$',
EditProfileFormView.as_view( EditProfileFormView.as_view(
template_name='layerindex/profile.html'), template_name='layerindex/profile.html'),

View File

@ -9,14 +9,14 @@ from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidde
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.template import RequestContext from django.template import RequestContext
from layerindex.models import Branch, LayerItem, LayerMaintainer, LayerBranch, LayerDependency, LayerNote, Recipe, Machine from layerindex.models import Branch, LayerItem, LayerMaintainer, LayerBranch, LayerDependency, LayerNote, Recipe, Machine, BBClass
from datetime import datetime from datetime import datetime
from django.views.generic import DetailView, ListView from django.views.generic import TemplateView, DetailView, ListView
from django.views.generic.edit import UpdateView from django.views.generic.edit import UpdateView
from layerindex.forms import EditLayerForm, LayerMaintainerFormSet, EditNoteForm, EditProfileForm from layerindex.forms import EditLayerForm, LayerMaintainerFormSet, EditNoteForm, EditProfileForm
from django.db import transaction from django.db import transaction
from django.contrib.auth.models import User, Permission from django.contrib.auth.models import User, Permission
from django.db.models import Q from django.db.models import Q, Count
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from django.template.loader import get_template from django.template.loader import get_template
from django.template import Context from django.template import Context
@ -256,6 +256,28 @@ class LayerReviewDetailView(LayerDetailView):
raise PermissionDenied raise PermissionDenied
return super(LayerReviewDetailView, self).dispatch(request, *args, **kwargs) return super(LayerReviewDetailView, self).dispatch(request, *args, **kwargs)
def recipes_preferred_count(qs):
# Add extra column so we can show "duplicate" recipes from other layers de-emphasised
# (it's a bit crude having to do this using SQL but I couldn't find a better way...)
return qs.extra(
select={
'preferred_count': """SELECT COUNT(1)
FROM layerindex_recipe AS recipe2
, layerindex_layerbranch as branch2
, layerindex_layeritem as layer1
, layerindex_layeritem as layer2
WHERE branch2.id = recipe2.layerbranch_id
AND layer2.id = branch2.layer_id
AND layer2.layer_type in ('S', 'A')
AND branch2.branch_id = layerindex_layerbranch.branch_id
AND recipe2.pn = layerindex_recipe.pn
AND recipe2.layerbranch_id <> layerindex_recipe.layerbranch_id
AND layer1.id = layerindex_layerbranch.layer_id
AND layer2.index_preference > layer1.index_preference
"""
},
)
class RecipeSearchView(ListView): class RecipeSearchView(ListView):
context_object_name = 'recipe_list' context_object_name = 'recipe_list'
paginate_by = 50 paginate_by = 50
@ -275,32 +297,32 @@ class RecipeSearchView(ListView):
# with no query string) # with no query string)
return Recipe.objects.none() return Recipe.objects.none()
# Add extra column so we can show "duplicate" recipes from other layers de-emphasised return recipes_preferred_count(qs)
# (it's a bit crude having to do this using SQL but I couldn't find a better way...)
return qs.extra(
select={
'preferred_count': """SELECT COUNT(1)
FROM layerindex_recipe AS recipe2
, layerindex_layerbranch as branch2
, layerindex_layeritem as layer1
, layerindex_layeritem as layer2
WHERE branch2.id = recipe2.layerbranch_id
AND layer2.id = branch2.layer_id
AND layer2.layer_type in ('S', 'A')
AND branch2.branch_id = layerindex_layerbranch.branch_id
AND recipe2.pn = layerindex_recipe.pn
AND recipe2.layerbranch_id <> layerindex_recipe.layerbranch_id
AND layer1.id = layerindex_layerbranch.layer_id
AND layer2.index_preference > layer1.index_preference
"""
},
)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(RecipeSearchView, self).get_context_data(**kwargs) context = super(RecipeSearchView, self).get_context_data(**kwargs)
context['search_keyword'] = self.request.GET.get('q', '') context['search_keyword'] = self.request.GET.get('q', '')
return context return context
class DuplicatesView(TemplateView):
def get_recipes(self):
init_qs = Recipe.objects.filter(layerbranch__branch__name=self.request.session.get('branch', 'master'))
dupes = init_qs.values('pn').annotate(Count('layerbranch', distinct=True)).filter(layerbranch__count__gt=1)
qs = init_qs.all().filter(pn__in=[item['pn'] for item in dupes]).order_by('pn', 'layerbranch__layer')
return recipes_preferred_count(qs)
def get_classes(self):
init_qs = BBClass.objects.filter(layerbranch__branch__name=self.request.session.get('branch', 'master'))
dupes = init_qs.values('name').annotate(Count('layerbranch', distinct=True)).filter(layerbranch__count__gt=1)
qs = init_qs.all().filter(name__in=[item['name'] for item in dupes]).order_by('name', 'layerbranch__layer')
return qs
def get_context_data(self, **kwargs):
context = super(DuplicatesView, self).get_context_data(**kwargs)
context['recipes'] = self.get_recipes()
context['classes'] = self.get_classes()
return context
class MachineSearchView(ListView): class MachineSearchView(ListView):
context_object_name = 'machine_list' context_object_name = 'machine_list'
paginate_by = 50 paginate_by = 50

View File

@ -63,6 +63,15 @@
{% endif %} {% endif %}
</a></li> </a></li>
{% endif %} {% endif %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
Tools
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><a href="{% url duplicates %}">Duplicates</a></li>
</ul>
</li>
<li class="divider-vertical"></li> <li class="divider-vertical"></li>
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown">

View File

@ -0,0 +1,96 @@
{% extends "base.html" %}
{% load i18n %}
{% comment %}
layerindex-web - duplicates page template
Copyright (C) 2013 Intel Corporation
Licensed under the MIT license, see COPYING.MIT for details
{% endcomment %}
<!--
{% block title_append %} - duplicates{% endblock %}
-->
{% block content %}
{% autoescape on %}
<div class="row-fluid">
<div class="span9 offset1">
<h2>Duplicate recipes</h2>
{% if recipes %}
<p>Recipes with the same name in different layers:</p>
<table class="table table-striped table-bordered recipestable">
<thead>
<tr>
<th>Recipe name</th>
<th>Version</th>
<th class="span9">Description</th>
<th>Layer</th>
</tr>
</thead>
<tbody>
{% for recipe in recipes %}
<tr {% if recipe.preferred_count > 0 %}class="muted"{% endif %}>
<td><a href="{% url recipe recipe.id %}">{{ recipe.name }}</a></td>
<td>{{ recipe.pv }}</td>
<td>{{ recipe.short_desc }}</td>
<td><a href="{% url layer_item recipe.layerbranch.layer.name %}">{{ recipe.layerbranch.layer.name }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No duplicate recipes in database.</p>
{% endif %}
</div>
</div>
<div class="row-fluid">
<div class="span9 offset1">
<h2>Duplicate classes</h2>
{% if classes %}
<p>Classes with the same name in different layers:</p>
<table class="table table-striped table-bordered recipestable">
<thead>
<tr>
<th>Class name</th>
<th>Layer</th>
</tr>
</thead>
<tbody>
{% for class in classes %}
<tr>
<td><a href="{% url class.vcs_web_url %}">{{ class.name }}</a></td>
<td><a href="{% url layer_item class.layerbranch.layer.name %}">{{ class.layerbranch.layer.name }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No duplicate classes in database.</p>
{% endif %}
</div>
</div>
{% endautoescape %}
{% endblock %}
{% block scripts %}
<script>
$(document).ready(function() {
firstfield = $("#filter-form input:text").first()
if( ! firstfield.val() )
firstfield.focus()
});
</script>
{% endblock %}