layerindex-web/layerindex/views.py
Paul Eggleton cb656bdd31 Sort higher preference layers first in layer list
On the main layer index page we want openembedded-core to appear before
meta-oe - naturally openembedded-core is the layer everyone starts from.
We already have an "index_preference" column that we use for search
results, so just sort by that here before sorting by name.

Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
2017-01-16 14:27:06 +13:00

897 lines
37 KiB
Python

# layerindex-web - view definitions
#
# Copyright (C) 2013-2016 Intel Corporation
#
# Licensed under the MIT license, see COPYING.MIT for details
import sys
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404
from django.core.urlresolvers import reverse, reverse_lazy, resolve
from django.core.exceptions import PermissionDenied
from django.template import RequestContext
from layerindex.models import Branch, LayerItem, LayerMaintainer, LayerBranch, LayerDependency, LayerNote, Update, LayerUpdate, Recipe, Machine, Distro, BBClass, BBAppend, RecipeChange, RecipeChangeset, ClassicRecipe
from datetime import datetime
from django.views.generic import TemplateView, DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic.base import RedirectView
from layerindex.forms import EditLayerForm, LayerMaintainerFormSet, EditNoteForm, EditProfileForm, RecipeChangesetForm, AdvancedRecipeSearchForm, BulkChangeEditFormSet, ClassicRecipeForm, ClassicRecipeSearchForm
from django.db import transaction
from django.contrib.auth.models import User, Permission
from django.db.models import Q, Count, Sum
from django.core.mail import EmailMessage
from django.template.loader import get_template
from django.template import Context
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from reversion.models import Revision
from . import utils
from . import simplesearch
import settings
from django.dispatch import receiver
import reversion
def edit_layernote_view(request, template_name, slug, pk=None):
layeritem = get_object_or_404(LayerItem, name=slug)
if layeritem.classic:
raise Http404
if not (request.user.is_authenticated() and (request.user.has_perm('layerindex.publish_layer') or layeritem.user_can_edit(request.user))):
raise PermissionDenied
if pk:
# Edit mode
layernote = get_object_or_404(LayerNote, pk=pk)
else:
# Add mode
layernote = LayerNote()
layernote.layer = layeritem
if request.method == 'POST':
form = EditNoteForm(request.POST, instance=layernote)
if form.is_valid():
form.save()
return HttpResponseRedirect(layeritem.get_absolute_url())
else:
form = EditNoteForm(instance=layernote)
return render(request, template_name, {
'form': form,
})
def delete_layernote_view(request, template_name, slug, pk):
layeritem = get_object_or_404(LayerItem, name=slug)
if layeritem.classic:
raise Http404
if not (request.user.is_authenticated() and (request.user.has_perm('layerindex.publish_layer') or layeritem.user_can_edit(request.user))):
raise PermissionDenied
layernote = get_object_or_404(LayerNote, pk=pk)
if request.method == 'POST':
layernote.delete()
return HttpResponseRedirect(layeritem.get_absolute_url())
else:
return render(request, template_name, {
'object': layernote,
'object_type': layernote._meta.verbose_name,
'cancel_url': layeritem.get_absolute_url()
})
def delete_layer_view(request, template_name, slug):
layeritem = get_object_or_404(LayerItem, name=slug)
if layeritem.classic:
raise Http404
if not (request.user.is_authenticated() and request.user.has_perm('layerindex.publish_layer') and layeritem.status == 'N'):
raise PermissionDenied
if request.method == 'POST':
layeritem.delete()
return HttpResponseRedirect(reverse('layer_list', args=('master',)))
else:
return render(request, template_name, {
'object': layeritem,
'object_type': layeritem._meta.verbose_name,
'cancel_url': layeritem.get_absolute_url()
})
def edit_layer_view(request, template_name, branch='master', slug=None):
return_url = None
branchobj = Branch.objects.filter(name=branch)[:1].get()
if slug:
# Edit mode
layeritem = get_object_or_404(LayerItem, name=slug)
if layeritem.classic:
raise Http404
if not (request.user.is_authenticated() and (request.user.has_perm('layerindex.publish_layer') or layeritem.user_can_edit(request.user))):
raise PermissionDenied
layerbranch = get_object_or_404(LayerBranch, layer=layeritem, branch=branchobj)
deplistlayers = LayerItem.objects.exclude(id=layeritem.id).order_by('name')
returnto = request.GET.get('returnto', 'layer_item')
if returnto:
if returnto == 'layer_review':
return_url = reverse_lazy(returnto, args=(layeritem.name,))
else:
return_url = reverse_lazy(returnto, args=(branch, layeritem.name))
else:
# Submit mode
layeritem = LayerItem()
layerbranch = LayerBranch(layer=layeritem, branch=branchobj)
deplistlayers = LayerItem.objects.filter(classic=False).order_by('name')
if request.method == 'POST':
last_vcs_url = layeritem.vcs_url
form = EditLayerForm(request.user, layerbranch, request.POST, instance=layeritem)
maintainerformset = LayerMaintainerFormSet(request.POST, instance=layerbranch)
if form.is_valid() and maintainerformset.is_valid():
with transaction.atomic():
reset_last_rev = False
form.save()
layerbranch.layer = layeritem
new_subdir = form.cleaned_data['vcs_subdir']
if layerbranch.vcs_subdir != new_subdir:
layerbranch.vcs_subdir = new_subdir
reset_last_rev = True
layerbranch.save()
maintainerformset.save()
if slug:
new_deps = form.cleaned_data['deps']
existing_deps = [deprec.dependency for deprec in layerbranch.dependencies_set.all()]
reset_last_rev = False
for dep in new_deps:
if dep not in existing_deps:
deprec = LayerDependency()
deprec.layerbranch = layerbranch
deprec.dependency = dep
deprec.save()
reset_last_rev = True
for dep in existing_deps:
if dep not in new_deps:
layerbranch.dependencies_set.filter(dependency=dep).delete()
reset_last_rev = True
if layeritem.vcs_url != last_vcs_url:
reset_last_rev = True
if reset_last_rev:
layerbranch.vcs_last_rev = ''
layerbranch.save()
else:
# Save dependencies
for dep in form.cleaned_data['deps']:
deprec = LayerDependency()
deprec.layerbranch = layerbranch
deprec.dependency = dep
deprec.save()
# Send email
plaintext = get_template('layerindex/submitemail.txt')
perm = Permission.objects.get(codename='publish_layer')
users = User.objects.filter(Q(groups__permissions=perm) | Q(user_permissions=perm) ).distinct()
for user in users:
if user.first_name:
user_name = user.first_name
else:
user_name = user.username
d = Context({
'user_name': user_name,
'layer_name': layeritem.name,
'layer_url': request.build_absolute_uri(reverse('layer_review', args=(layeritem.name,))),
})
subject = '%s - %s' % (settings.SUBMIT_EMAIL_SUBJECT, layeritem.name)
from_email = settings.SUBMIT_EMAIL_FROM
to_email = user.email
text_content = plaintext.render(d)
msg = EmailMessage(subject, text_content, from_email, [to_email])
msg.send()
return HttpResponseRedirect(reverse('submit_layer_thanks'))
messages.success(request, 'Layer %s saved successfully.' % layeritem.name)
if return_url:
return HttpResponseRedirect(return_url)
else:
form = EditLayerForm(request.user, layerbranch, instance=layeritem)
maintainerformset = LayerMaintainerFormSet(instance=layerbranch)
return render(request, template_name, {
'form': form,
'maintainerformset': maintainerformset,
'deplistlayers': deplistlayers,
'return_url': return_url,
})
def bulk_change_edit_view(request, template_name, pk):
changeset = get_object_or_404(RecipeChangeset, pk=pk)
if request.method == 'POST':
formset = BulkChangeEditFormSet(request.POST, queryset=changeset.recipechange_set.all())
if formset.is_valid():
formset.save()
return HttpResponseRedirect(reverse('bulk_change_review', args=(changeset.id,)))
else:
formset = BulkChangeEditFormSet(queryset=changeset.recipechange_set.all())
return render(request, template_name, {
'formset': formset,
})
def bulk_change_patch_view(request, pk):
import os
import os.path
changeset = get_object_or_404(RecipeChangeset, pk=pk)
# FIXME this couples the web server and machine running the update script together,
# but given that it's a separate script the way is open to decouple them in future
try:
ret = utils.runcmd('%s bulkchange.py %d %s' % (sys.executable, int(pk), settings.TEMP_BASE_DIR), os.path.dirname(__file__))
if ret:
fn = ret.splitlines()[-1].decode('utf-8')
if os.path.exists(fn):
if fn.endswith('.tar.gz'):
mimetype = 'application/x-gzip'
else:
mimetype = 'text/x-diff'
response = HttpResponse(mimetype=mimetype)
response['Content-Disposition'] = 'attachment; filename="%s"' % os.path.basename(fn)
with open(fn, "rb") as f:
data = f.read()
response.write(data)
os.remove(fn)
return response
return HttpResponse('No patch data generated', content_type='text/plain')
except Exception as e:
output = getattr(e, 'output', None)
if output:
output = output.decode('utf-8', errors="ignore")
if 'timeout' in output:
return HttpResponse('Failed to generate patches: timed out waiting for lock. Please try again shortly.', content_type='text/plain')
else:
return HttpResponse('Failed to generate patches: %s' % output, content_type='text/plain')
return HttpResponse('Failed to generate patches: %s' % e, content_type='text/plain')
# FIXME better error handling
def _check_url_branch(kwargs):
branchname = kwargs['branch']
if branchname:
if branchname == 'oe-classic':
raise Http404
branch = get_object_or_404(Branch, name=branchname)
def publish(request, name):
if not (request.user.is_authenticated() and request.user.has_perm('layerindex.publish_layer')):
raise PermissionDenied
return _statuschange(request, name, 'P')
def _statuschange(request, name, newstatus):
w = get_object_or_404(LayerItem, name=name)
if w.classic:
raise Http404
if w.status != newstatus:
w.change_status(newstatus, request.user.username)
w.save()
return HttpResponseRedirect(w.get_absolute_url())
class RedirectParamsView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
redirect_name = kwargs.pop('redirect_name')
return reverse_lazy(redirect_name, args=args, kwargs=kwargs)
class LayerListView(ListView):
context_object_name = 'layerbranch_list'
def get_queryset(self):
_check_url_branch(self.kwargs)
return LayerBranch.objects.filter(branch__name=self.kwargs['branch']).filter(layer__status='P').order_by('layer__layer_type', '-layer__index_preference', 'layer__name')
def get_context_data(self, **kwargs):
context = super(LayerListView, self).get_context_data(**kwargs)
context['url_branch'] = self.kwargs['branch']
context['this_url_name'] = resolve(self.request.path_info).url_name
context['layer_type_choices'] = LayerItem.LAYER_TYPE_CHOICES
return context
class LayerReviewListView(ListView):
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
if not request.user.has_perm('layerindex.publish_layer'):
raise PermissionDenied
return super(LayerReviewListView, self).dispatch(request, *args, **kwargs)
def get_queryset(self):
return LayerBranch.objects.filter(branch__name='master').filter(layer__status='N').order_by('layer__name')
class LayerDetailView(DetailView):
model = LayerItem
slug_field = 'name'
# This is a bit of a mess. Surely there has to be a better way to handle this...
def dispatch(self, request, *args, **kwargs):
self.user = request.user
res = super(LayerDetailView, self).dispatch(request, *args, **kwargs)
l = self.get_object()
if l:
if l.classic:
raise Http404
if l.status == 'N':
if not (request.user.is_authenticated() and request.user.has_perm('layerindex.publish_layer')):
raise PermissionDenied
return res
def get_context_data(self, **kwargs):
_check_url_branch(self.kwargs)
context = super(LayerDetailView, self).get_context_data(**kwargs)
layer = context['layeritem']
context['useredit'] = layer.user_can_edit(self.user)
layerbranch = layer.get_layerbranch(self.kwargs['branch'])
if layerbranch:
context['layerbranch'] = layerbranch
context['machines'] = layerbranch.machine_set.order_by('name')
context['distros'] = layerbranch.distro_set.order_by('name')
context['appends'] = layerbranch.bbappend_set.order_by('filename')
context['classes'] = layerbranch.bbclass_set.order_by('name')
context['updates'] = layerbranch.layerupdate_set.order_by('-started')
context['url_branch'] = self.kwargs['branch']
context['this_url_name'] = resolve(self.request.path_info).url_name
return context
class LayerReviewDetailView(LayerDetailView):
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
if not request.user.has_perm('layerindex.publish_layer'):
raise PermissionDenied
return super(LayerReviewDetailView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
self.kwargs['branch'] = 'master'
context = super(LayerReviewDetailView, self).get_context_data(**kwargs)
return context
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):
context_object_name = 'recipe_list'
paginate_by = 50
def get_queryset(self):
_check_url_branch(self.kwargs)
query_string = self.request.GET.get('q', '')
init_qs = Recipe.objects.filter(layerbranch__branch__name=self.kwargs['branch'])
# Support slightly crude search on inherits field
query_items = query_string.split()
inherits = []
query_terms = []
for item in query_items:
if item.startswith('inherits:'):
inherits.append(item.split(':')[1])
else:
query_terms.append(item)
if inherits:
# FIXME This is a bit ugly, perhaps we should consider having this as a one-many relationship instead
for inherit in inherits:
init_qs = init_qs.filter(Q(inherits=inherit) | Q(inherits__startswith=inherit + ' ') | Q(inherits__endswith=' ' + inherit) | Q(inherits__contains=' %s ' % inherit))
query_string = ' '.join(query_terms)
if query_string.strip():
order_by = ('pn', 'layerbranch__layer')
qs0 = init_qs.filter(pn=query_string).order_by(*order_by)
qs0 = recipes_preferred_count(qs0)
entry_query = simplesearch.get_query(query_string, ['pn'])
qs1 = init_qs.filter(entry_query).order_by(*order_by)
qs1 = recipes_preferred_count(qs1)
entry_query = simplesearch.get_query(query_string, ['description', 'summary'])
qs2 = init_qs.filter(entry_query).order_by(*order_by)
qs2 = recipes_preferred_count(qs2)
qs = list(utils.chain_unique(qs0, qs1, qs2))
else:
if 'q' in self.request.GET:
qs = init_qs.order_by('pn', 'layerbranch__layer')
qs = list(recipes_preferred_count(qs))
else:
# It's a bit too slow to return all records by default, and most people
# won't actually want that (if they do they can just hit the search button
# with no query string)
return Recipe.objects.none()
return qs
def get_context_data(self, **kwargs):
context = super(RecipeSearchView, self).get_context_data(**kwargs)
searchval = self.request.GET.get('q', '')
context['search_keyword'] = searchval
context['url_branch'] = self.kwargs['branch']
context['this_url_name'] = resolve(self.request.path_info).url_name
if searchval:
context['extra_url_param'] = '?q=%s' % searchval
return context
class DuplicatesView(TemplateView):
def get_recipes(self, layer_ids):
init_qs = Recipe.objects.filter(layerbranch__branch__name=self.kwargs['branch'])
if layer_ids:
init_qs = init_qs.filter(layerbranch__layer__in=layer_ids)
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', '-pv')
return recipes_preferred_count(qs)
def get_classes(self, layer_ids):
init_qs = BBClass.objects.filter(layerbranch__branch__name=self.kwargs['branch'])
if layer_ids:
init_qs = init_qs.filter(layerbranch__layer__in=layer_ids)
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):
layer_ids = [int(i) for i in self.request.GET.getlist('l')]
context = super(DuplicatesView, self).get_context_data(**kwargs)
context['recipes'] = self.get_recipes(layer_ids)
context['classes'] = self.get_classes(layer_ids)
context['url_branch'] = self.kwargs['branch']
context['this_url_name'] = resolve(self.request.path_info).url_name
context['layers'] = LayerBranch.objects.filter(branch__name=self.kwargs['branch']).filter(layer__status='P').order_by( 'layer__name')
context['showlayers'] = layer_ids
return context
class AdvancedRecipeSearchView(ListView):
context_object_name = 'recipe_list'
paginate_by = 50
def get_queryset(self):
field = self.request.GET.get('field', '')
if field:
search_form = AdvancedRecipeSearchForm(self.request.GET)
if not search_form.is_valid():
return Recipe.objects.none()
match_type = self.request.GET.get('match_type', '')
if match_type == 'B':
value = ''
else:
value = self.request.GET.get('value', '')
if value or match_type == 'B':
if match_type == 'C' or match_type == 'N':
query = Q(**{"%s__icontains" % field: value})
else:
query = Q(**{"%s" % field: value})
queryset = Recipe.objects.filter(layerbranch__branch__name='master')
layer = self.request.GET.get('layer', '')
if layer:
queryset = queryset.filter(layerbranch__layer=layer)
if match_type == 'N':
# Exclude blank as well
queryset = queryset.exclude(Q(**{"%s" % field: ''})).exclude(query)
else:
queryset = queryset.filter(query)
return queryset.order_by('pn', 'layerbranch__layer')
return Recipe.objects.none()
def get_context_data(self, **kwargs):
context = super(AdvancedRecipeSearchView, self).get_context_data(**kwargs)
if self.request.GET.get('field', ''):
searched = True
search_form = AdvancedRecipeSearchForm(self.request.GET)
else:
searched = False
search_form = AdvancedRecipeSearchForm()
context['search_form'] = search_form
context['searched'] = searched
return context
class BulkChangeView(CreateView):
model = RecipeChangeset
form_class = RecipeChangesetForm
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(BulkChangeView, self).dispatch(request, *args, **kwargs)
def form_valid(self, form):
if not self.request.user.is_authenticated():
raise PermissionDenied
obj = form.save(commit=False)
obj.user = self.request.user
obj.save()
return HttpResponseRedirect(reverse('bulk_change_search', args=(obj.id,)))
def get_context_data(self, **kwargs):
context = super(BulkChangeView, self).get_context_data(**kwargs)
context['changesets'] = RecipeChangeset.objects.filter(user=self.request.user)
return context
class BulkChangeSearchView(AdvancedRecipeSearchView):
def get(self, request, *args, **kwargs):
self.changeset = get_object_or_404(RecipeChangeset, pk=kwargs['pk'])
if self.changeset.user != request.user:
raise PermissionDenied
return super(BulkChangeSearchView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated():
raise PermissionDenied
changeset = get_object_or_404(RecipeChangeset, pk=kwargs['pk'])
if changeset.user != request.user:
raise PermissionDenied
def add_recipes(recipes):
for recipe in recipes:
if not changeset.recipechange_set.filter(recipe=recipe):
change = RecipeChange()
change.changeset = changeset
change.recipe = recipe
change.reset_fields()
change.save()
if 'add_selected' in request.POST:
id_list = request.POST.getlist('selecteditems')
id_list = [int(i) for i in id_list if i.isdigit()]
recipes = Recipe.objects.filter(id__in=id_list)
add_recipes(recipes)
elif 'add_all' in request.POST:
add_recipes(self.get_queryset())
elif 'remove_all' in request.POST:
changeset.recipechange_set.all().delete()
return self.get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(BulkChangeSearchView, self).get_context_data(**kwargs)
context['changeset'] = self.changeset
context['current_branch'] = 'master'
return context
class BaseDeleteView(DeleteView):
def get_context_data(self, **kwargs):
context = super(BaseDeleteView, self).get_context_data(**kwargs)
obj = context.get('object', None)
if obj:
context['object_type'] = obj._meta.verbose_name
cancel = self.request.GET.get('cancel', '')
if cancel:
context['cancel_url'] = reverse_lazy(cancel, args=(obj.pk,))
return context
class BulkChangeDeleteView(BaseDeleteView):
model = RecipeChangeset
success_url = reverse_lazy('bulk_change')
def get_queryset(self):
qs = super(BulkChangeDeleteView, self).get_queryset()
return qs.filter(user=self.request.user)
class MachineSearchView(ListView):
context_object_name = 'machine_list'
paginate_by = 50
def get_queryset(self):
_check_url_branch(self.kwargs)
query_string = self.request.GET.get('q', '')
init_qs = Machine.objects.filter(layerbranch__branch__name=self.kwargs['branch'])
if query_string.strip():
entry_query = simplesearch.get_query(query_string, ['name', 'description'])
return init_qs.filter(entry_query).order_by('name', 'layerbranch__layer')
else:
if 'q' in self.request.GET:
return init_qs.order_by('name', 'layerbranch__layer')
else:
# Be consistent with RecipeSearchView
return Machine.objects.none()
def get_context_data(self, **kwargs):
context = super(MachineSearchView, self).get_context_data(**kwargs)
context['search_keyword'] = self.request.GET.get('q', '')
context['url_branch'] = self.kwargs['branch']
context['this_url_name'] = resolve(self.request.path_info).url_name
return context
class UpdateListView(ListView):
context_object_name = "updates"
paginate_by = 50
def get_queryset(self):
return Update.objects.all().order_by('-started').annotate(errors=Sum('layerupdate__errors'), warnings=Sum('layerupdate__warnings'))
class UpdateDetailView(DetailView):
model = Update
def get_context_data(self, **kwargs):
context = super(UpdateDetailView, self).get_context_data(**kwargs)
update = self.get_object()
if update:
context['layerupdates'] = update.layerupdate_set.exclude(log__isnull=True).exclude(log__exact='')
return context
class LayerUpdateDetailView(DetailView):
model = LayerUpdate
class DistroSearchView(ListView):
context_object_name = 'distro_list'
paginate_by = 50
def get_queryset(self):
_check_url_branch(self.kwargs)
query_string = self.request.GET.get('q', '')
init_qs = Distro.objects.filter(layerbranch__branch__name=self.kwargs['branch'])
if query_string.strip():
entry_query = simplesearch.get_query(query_string, ['name', 'description'])
return init_qs.filter(entry_query).order_by('name', 'layerbranch__layer')
if 'q' in self.request.GET:
return init_qs.order_by('name', 'layerbranch__layer')
# Be consistent with RecipeSearchView
return Distro.objects.none()
def get_context_data(self, **kwargs):
context = super(DistroSearchView, self).get_context_data(**kwargs)
context['search_keyword'] = self.request.GET.get('q', '')
context['url_branch'] = self.kwargs['branch']
context['this_url_name'] = resolve(self.request.path_info).url_name
return context
class PlainTextListView(ListView):
def render_to_response(self, context):
"Returns a plain text response rendering of the template"
template = get_template(self.template_name)
return HttpResponse(template.render(Context(context)),
content_type='text/plain')
class HistoryListView(ListView):
context_object_name = "revisions"
paginate_by = 50
def get_queryset(self):
return Revision.objects.all().order_by('-date_created')
class EditProfileFormView(UpdateView):
form_class = EditProfileForm
def dispatch(self, request, *args, **kwargs):
self.user = request.user
return super(EditProfileFormView, self).dispatch(request, *args, **kwargs)
def get_object(self, queryset=None):
return self.user
def get_success_url(self):
return reverse('frontpage')
@receiver(reversion.pre_revision_commit)
def annotate_revision(sender, **kwargs):
ignorefields = ['vcs_last_rev', 'vcs_last_fetch', 'vcs_last_commit', 'updated']
versions = kwargs.pop('versions')
instances = kwargs.pop('instances')
changelist = []
for ver, inst in zip(versions, instances):
currentVersion = ver.field_dict
modelmeta = ver.content_type.model_class()._meta
#FIXME modern django-reversion dropped the type field (argh!)
#if ver.type == reversion.models.VERSION_DELETE:
# changelist.append("Deleted %s: %s" % (modelmeta.verbose_name.lower(), ver.object_repr))
#else:
pastver = reversion.get_for_object(inst)
if pastver:# and ver.type != reversion.models.VERSION_ADD:
pastVersion = pastver[0].field_dict
changes = set(currentVersion.items()) - set(pastVersion.items())
changedVars = [var[0] for var in changes]
fieldchanges = []
for field in changedVars:
if field not in ignorefields:
modelfield = modelmeta.get_field(field)
newvalue = currentVersion[field]
if modelfield.choices:
for v in modelfield.choices:
if v[0] == newvalue:
newvalue = v[1]
break
fieldchanges.append("%s to '%s'" % (modelfield.verbose_name.lower(), newvalue))
if fieldchanges:
changelist.append("Changed %s %s %s" % (modelmeta.verbose_name.lower(), ver.object_repr, ", ".join(fieldchanges)))
else:
changelist.append("Added %s: %s" % (modelmeta.verbose_name.lower(), ver.object_repr))
comment = '\n'.join(changelist)
if not comment:
comment = 'No changes'
revision = kwargs.pop('revision')
revision.comment = comment
revision.save()
kwargs['revision'] = revision
class RecipeDetailView(DetailView):
model = Recipe
def get_context_data(self, **kwargs):
context = super(RecipeDetailView, self).get_context_data(**kwargs)
recipe = self.get_object()
if recipe:
verappendprefix = recipe.filename.split('.bb')[0]
appendprefix = verappendprefix.split('_')[0]
#context['verappends'] = BBAppend.objects.filter(layerbranch__branch=recipe.layerbranch.branch).filter(filename='%s.bbappend' % verappendprefix)
context['appends'] = BBAppend.objects.filter(layerbranch__branch=recipe.layerbranch.branch).filter(filename__regex=r'^%s(_[^_]*)?\.bbappend' % appendprefix)
verappends = []
for append in context['appends']:
if append.matches_recipe(recipe):
verappends.append(append)
context['verappends'] = verappends
return context
class ClassicRecipeSearchView(RecipeSearchView):
def get_queryset(self):
self.kwargs['branch'] = 'oe-classic'
query_string = self.request.GET.get('q', '')
cover_status = self.request.GET.get('cover_status', None)
cover_verified = self.request.GET.get('cover_verified', None)
category = self.request.GET.get('category', None)
init_qs = ClassicRecipe.objects.filter(layerbranch__branch__name='oe-classic')
if cover_status:
if cover_status == '!':
init_qs = init_qs.filter(cover_status__in=['U', 'N'])
else:
init_qs = init_qs.filter(cover_status=cover_status)
if cover_verified:
init_qs = init_qs.filter(cover_verified=(cover_verified=='1'))
if category:
init_qs = init_qs.filter(classic_category__icontains=category)
if query_string.strip():
order_by = ('pn', 'layerbranch__layer')
qs0 = init_qs.filter(pn=query_string).order_by(*order_by)
entry_query = simplesearch.get_query(query_string, ['pn'])
qs1 = init_qs.filter(entry_query).order_by(*order_by)
entry_query = simplesearch.get_query(query_string, ['summary', 'description'])
qs2 = init_qs.filter(entry_query).order_by(*order_by)
qs = list(utils.chain_unique(qs0, qs1, qs2))
else:
if 'q' in self.request.GET:
qs = init_qs.order_by('pn', 'layerbranch__layer')
else:
# It's a bit too slow to return all records by default, and most people
# won't actually want that (if they do they can just hit the search button
# with no query string)
return Recipe.objects.none()
return qs
def get_context_data(self, **kwargs):
context = super(ClassicRecipeSearchView, self).get_context_data(**kwargs)
context['multi_classic_layers'] = LayerItem.objects.filter(classic=True).count() > 1
if 'q' in self.request.GET:
searched = True
search_form = ClassicRecipeSearchForm(self.request.GET)
else:
searched = False
search_form = ClassicRecipeSearchForm()
context['search_form'] = search_form
context['searched'] = searched
return context
class ClassicRecipeDetailView(UpdateView):
model = ClassicRecipe
form_class = ClassicRecipeForm
context_object_name = 'recipe'
def _can_edit(self):
if self.request.user.is_authenticated():
if not self.request.user.has_perm('layerindex.edit_classic'):
user_email = self.request.user.email.strip().lower()
if not LayerMaintainer.objects.filter(email__iexact=user_email):
return False
else:
return False
return True
def post(self, request, *args, **kwargs):
if not self._can_edit():
raise PermissionDenied
return super(ClassicRecipeDetailView, self).post(request, *args, **kwargs)
def get_success_url(self):
return reverse_lazy('classic_recipe_search')
def get_context_data(self, **kwargs):
context = super(ClassicRecipeDetailView, self).get_context_data(**kwargs)
context['can_edit'] = self._can_edit()
return context
class ClassicRecipeStatsView(TemplateView):
def get_context_data(self, **kwargs):
context = super(ClassicRecipeStatsView, self).get_context_data(**kwargs)
# *** Cover status chart ***
statuses = []
status_counts = {}
for choice, desc in ClassicRecipe.COVER_STATUS_CHOICES:
statuses.append(desc)
status_counts[desc] = ClassicRecipe.objects.filter(cover_status=choice).count()
statuses = sorted(statuses, key=lambda status: status_counts[status], reverse=True)
chartdata = {'x': statuses, 'y': [status_counts[k] for k in statuses]}
context['charttype_status'] = 'pieChart'
context['chartdata_status'] = chartdata
context['extra_status'] = {
'x_is_date': False,
'x_axis_format': '',
'tag_script_js': True,
'jquery_on_ready': False,
}
# *** Categories chart ***
categories = ['obsoletedir', 'nonworkingdir']
uniquevals = ClassicRecipe.objects.exclude(classic_category='').values_list('classic_category', flat=True).distinct()
for value in uniquevals:
cats = value.split()
for cat in cats:
if not cat in categories:
categories.append(cat)
categories.append('none')
catcounts = dict.fromkeys(categories, 0)
unmigrated = ClassicRecipe.objects.filter(cover_status='U')
catcounts['none'] = unmigrated.filter(classic_category='').count()
values = unmigrated.exclude(classic_category='').values_list('classic_category', flat=True)
# We gather data this way because an item might be in more than one category, thus
# the categories list must be in priority order
for value in values:
recipecats = value.split()
foundcat = 'none'
for cat in categories:
if cat in recipecats:
foundcat = cat
break
catcounts[foundcat] += 1
# Eliminate categories with zero count
categories = [cat for cat in categories if catcounts[cat] > 0]
categories = sorted(categories, key=lambda cat: catcounts[cat], reverse=True)
chartdata_category = {'x': categories, 'y': [catcounts[k] for k in categories]}
context['charttype_category'] = 'pieChart'
context['chartdata_category'] = chartdata_category
context['extra_category'] = {
'x_is_date': False,
'x_axis_format': '',
'tag_script_js': True,
'jquery_on_ready': False,
}
return context