summaryrefslogtreecommitdiff
path: root/devel/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'devel/views.py')
-rw-r--r--devel/views.py268
1 files changed, 123 insertions, 145 deletions
diff --git a/devel/views.py b/devel/views.py
index 7be3dc17..61c1e568 100644
--- a/devel/views.py
+++ b/devel/views.py
@@ -1,36 +1,34 @@
-from datetime import date, datetime, timedelta
+from datetime import timedelta
import operator
import pytz
-import random
-from string import ascii_letters, digits
import time
-from django import forms
-from django.conf import settings
from django.http import HttpResponseRedirect
from django.contrib.auth.decorators import \
login_required, permission_required, user_passes_test
-from django.contrib.auth.models import User, Group
-from django.contrib.sites.models import Site
-from django.core.mail import send_mail
+from django.contrib.admin.models import LogEntry, ADDITION
+from django.contrib.auth.models import User
+from django.contrib.contenttypes.models import ContentType
from django.db import transaction
-from django.db.models import F
+from django.db.models import F, Count, Max
from django.http import Http404
-from django.shortcuts import get_object_or_404
-from django.template import loader, Context
+from django.shortcuts import get_object_or_404, render
from django.template.defaultfilters import filesizeformat
from django.views.decorators.cache import never_cache
-from django.views.generic.simple import direct_to_template
+from django.utils.encoding import force_unicode
from django.utils.http import http_date
+from django.utils.timezone import now
-from .models import UserProfile
-from main.models import Package, PackageDepend, PackageFile, TodolistPkg
+from .forms import ProfileForm, UserProfileForm, NewUserForm
+from .models import DeveloperKey
+from main.models import Package, PackageFile
from main.models import Arch, Repo
-from main.utils import utc_now
-from packages.models import PackageRelation
+from news.models import News
+from packages.models import PackageRelation, Signoff, FlagRequest, Depend
from packages.utils import get_signoff_groups
+from todolists.models import TodolistPackage
from todolists.utils import get_annotated_todolists
-from .utils import get_annotated_maintainers, UserFinder
+from .utils import get_annotated_maintainers
@login_required
@@ -45,17 +43,28 @@ def index(request):
flagged = Package.objects.normal().filter(
flag_date__isnull=False, pkgbase__in=inner_q).order_by('pkgname')
- todopkgs = TodolistPkg.objects.select_related(
- 'pkg', 'pkg__arch', 'pkg__repo').filter(complete=False)
- todopkgs = todopkgs.filter(pkg__pkgbase__in=inner_q).order_by(
- 'list__name', 'pkg__pkgname')
+ todopkgs = TodolistPackage.objects.select_related(
+ 'todolist', 'pkg', 'arch', 'repo').exclude(
+ status=TodolistPackage.COMPLETE).filter(removed__isnull=True)
+ todopkgs = todopkgs.filter(pkgbase__in=inner_q).order_by(
+ 'todolist__name', 'pkgname')
- todolists = get_annotated_todolists()
- todolists = [todolist for todolist in todolists if todolist.incomplete_count > 0]
+ todolists = get_annotated_todolists(incomplete_only=True)
signoffs = sorted(get_signoff_groups(user=request.user),
key=operator.attrgetter('pkgbase'))
+ arches = Arch.objects.all().annotate(
+ total_ct=Count('packages'), flagged_ct=Count('packages__flag_date'))
+ repos = Repo.objects.all().annotate(
+ total_ct=Count('packages'), flagged_ct=Count('packages__flag_date'))
+ # the join is huge unless we do this separately, so merge the result here
+ repo_maintainers = dict(Repo.objects.order_by().filter(
+ userprofile__user__is_active=True).values_list('id').annotate(
+ Count('userprofile')))
+ for repo in repos:
+ repo.maintainer_ct = repo_maintainers.get(repo.id, 0)
+
maintainers = get_annotated_maintainers()
maintained = PackageRelation.objects.filter(
@@ -72,80 +81,93 @@ def index(request):
page_dict = {
'todos': todolists,
- 'repos': Repo.objects.all(),
- 'arches': Arch.objects.all(),
+ 'arches': arches,
+ 'repos': repos,
'maintainers': maintainers,
'orphan': orphan,
- 'flagged' : flagged,
- 'todopkgs' : todopkgs,
+ 'flagged': flagged,
+ 'todopkgs': todopkgs,
'signoffs': signoffs
}
- return direct_to_template(request, 'devel/index.html', page_dict)
+ return render(request, 'devel/index.html', page_dict)
+
@login_required
def clock(request):
devs = User.objects.filter(is_active=True).order_by(
'first_name', 'last_name').select_related('userprofile')
- now = utc_now()
+ latest_news = dict(News.objects.filter(
+ author__is_active=True).values_list('author').order_by(
+ ).annotate(last_post=Max('postdate')))
+ latest_package = dict(Package.objects.filter(
+ packager__is_active=True).values_list('packager').order_by(
+ ).annotate(last_build=Max('build_date')))
+ latest_signoff = dict(Signoff.objects.filter(
+ user__is_active=True).values_list('user').order_by(
+ ).annotate(last_signoff=Max('created')))
+ # The extra() bit ensures we can use our 'user_id IS NOT NULL' index
+ latest_flagreq = dict(FlagRequest.objects.filter(
+ user__is_active=True).extra(
+ where=['user_id IS NOT NULL']).values_list('user_id').order_by(
+ ).annotate(last_flagrequest=Max('created')))
+ latest_log = dict(LogEntry.objects.filter(
+ user__is_active=True).values_list('user').order_by(
+ ).annotate(last_log=Max('action_time')))
+
+ for dev in devs:
+ dates = [
+ latest_news.get(dev.id, None),
+ latest_package.get(dev.id, None),
+ latest_signoff.get(dev.id, None),
+ latest_flagreq.get(dev.id, None),
+ latest_log.get(dev.id, None),
+ dev.last_login,
+ ]
+ dates = [d for d in dates if d is not None]
+ if dates:
+ dev.last_action = max(dates)
+ else:
+ dev.last_action = None
+
+ current_time = now()
page_dict = {
'developers': devs,
- 'utc_now': now,
+ 'utc_now': current_time,
}
- response = direct_to_template(request, 'devel/clock.html', page_dict)
+ response = render(request, 'devel/clock.html', page_dict)
if not response.has_header('Expires'):
- expire_time = now.replace(second=0, microsecond=0)
+ expire_time = current_time.replace(second=0, microsecond=0)
expire_time += timedelta(minutes=1)
expire_time = time.mktime(expire_time.timetuple())
response['Expires'] = http_date(expire_time)
return response
-class ProfileForm(forms.Form):
- email = forms.EmailField(label='Private email (not shown publicly):',
- help_text="Used for out-of-date notifications, etc.")
- passwd1 = forms.CharField(label='New Password', required=False,
- widget=forms.PasswordInput)
- passwd2 = forms.CharField(label='Confirm Password', required=False,
- widget=forms.PasswordInput)
-
- def clean(self):
- if self.cleaned_data['passwd1'] != self.cleaned_data['passwd2']:
- raise forms.ValidationError('Passwords do not match.')
- return self.cleaned_data
-
-class UserProfileForm(forms.ModelForm):
- def clean_pgp_key(self):
- data = self.cleaned_data['pgp_key']
- # strip 0x prefix if provided; store uppercase
- if data.startswith('0x'):
- data = data[2:]
- return data.upper()
-
- class Meta:
- model = UserProfile
- exclude = ('allowed_repos', 'user', 'latin_name')
@login_required
@never_cache
def change_profile(request):
if request.POST:
form = ProfileForm(request.POST)
- profile_form = UserProfileForm(request.POST, request.FILES, instance=request.user.get_profile())
+ profile_form = UserProfileForm(request.POST, request.FILES,
+ instance=request.user.userprofile)
if form.is_valid() and profile_form.is_valid():
request.user.email = form.cleaned_data['email']
if form.cleaned_data['passwd1']:
request.user.set_password(form.cleaned_data['passwd1'])
- request.user.save()
- profile_form.save()
+ with transaction.commit_on_success():
+ request.user.save()
+ profile_form.save()
return HttpResponseRedirect('/devel/')
else:
form = ProfileForm(initial={'email': request.user.email})
- profile_form = UserProfileForm(instance=request.user.get_profile())
- return direct_to_template(request, 'devel/profile.html',
+ profile_form = UserProfileForm(instance=request.user.userprofile)
+ return render(request, 'devel/profile.html',
{'form': form, 'profile_form': profile_form})
+
@login_required
def report(request, report_name, username=None):
title = 'Developer Report'
@@ -163,12 +185,12 @@ def report(request, report_name, username=None):
if report_name == 'old':
title = 'Packages last built more than one year ago'
- cutoff = utc_now() - timedelta(days=365)
+ cutoff = now() - timedelta(days=365)
packages = packages.filter(
build_date__lt=cutoff).order_by('build_date')
elif report_name == 'long-out-of-date':
title = 'Packages marked out-of-date more than 90 days ago'
- cutoff = utc_now() - timedelta(days=90)
+ cutoff = now() - timedelta(days=90)
packages = packages.filter(
flag_date__lt=cutoff).order_by('flag_date')
elif report_name == 'big':
@@ -199,7 +221,7 @@ def report(request, report_name, username=None):
package.installed_size_pretty = filesizeformat(
package.installed_size)
ratio = package.compressed_size / float(package.installed_size)
- package.ratio = '%.2f' % ratio
+ package.ratio = '%.3f' % ratio
package.compress_type = package.filename.split('.')[-1]
elif report_name == 'uncompressed-man':
title = 'Packages with uncompressed manpages'
@@ -214,7 +236,8 @@ def report(request, report_name, username=None):
if username:
pkg_ids = set(packages.values_list('id', flat=True))
bad_files = bad_files.filter(pkg__in=pkg_ids)
- bad_files = bad_files.values_list('pkg_id', flat=True).distinct()
+ bad_files = bad_files.values_list(
+ 'pkg_id', flat=True).order_by().distinct()
packages = packages.filter(id__in=set(bad_files))
elif report_name == 'uncompressed-info':
title = 'Packages with uncompressed infopages'
@@ -225,12 +248,13 @@ def report(request, report_name, username=None):
if username:
pkg_ids = set(packages.values_list('id', flat=True))
bad_files = bad_files.filter(pkg__in=pkg_ids)
- bad_files = bad_files.values_list('pkg_id', flat=True).distinct()
+ bad_files = bad_files.values_list(
+ 'pkg_id', flat=True).order_by().distinct()
packages = packages.filter(id__in=set(bad_files))
elif report_name == 'unneeded-orphans':
title = 'Orphan packages required by no other packages'
owned = PackageRelation.objects.all().values('pkgbase')
- required = PackageDepend.objects.all().values('depname')
+ required = Depend.objects.all().values('name')
# The two separate calls to exclude is required to do the right thing
packages = packages.exclude(pkgbase__in=owned).exclude(
pkgname__in=required)
@@ -239,98 +263,52 @@ def report(request, report_name, username=None):
names = [ 'Signature Date', 'Signed By', 'Packager' ]
attrs = [ 'sig_date', 'sig_by', 'packager' ]
cutoff = timedelta(hours=24)
- finder = UserFinder()
filtered = []
- packages = packages.filter(pgp_signature__isnull=False)
+ packages = packages.select_related(
+ 'arch', 'repo', 'packager').filter(pgp_signature__isnull=False)
+ known_keys = DeveloperKey.objects.select_related(
+ 'owner').filter(owner__isnull=False)
+ known_keys = {dk.key: dk for dk in known_keys}
for package in packages:
- sig_date = package.signature.datetime.replace(tzinfo=pytz.utc)
+ bad = False
+ sig = package.signature
+ sig_date = sig.creation_time.replace(tzinfo=pytz.utc)
package.sig_date = sig_date.date()
- key_id = package.signature.key_id
- signer = finder.find_by_pgp_key(key_id)
- package.sig_by = signer or key_id
- if signer is None or signer.id != package.packager_id:
- filtered.append(package)
- elif sig_date > package.build_date + cutoff:
+ dev_key = known_keys.get(sig.key_id, None)
+ if dev_key:
+ package.sig_by = dev_key.owner
+ if dev_key.owner_id != package.packager_id:
+ bad = True
+ else:
+ package.sig_by = sig.key_id
+ bad = True
+
+ if sig_date > package.build_date + cutoff:
+ bad = True
+
+ if bad:
filtered.append(package)
packages = filtered
else:
raise Http404
+ arches = {pkg.arch for pkg in packages}
+ repos = {pkg.repo for pkg in packages}
context = {
'all_maintainers': maints,
'title': title,
'maintainer': user,
'packages': packages,
+ 'arches': sorted(arches),
+ 'repos': sorted(repos),
'column_names': names,
'column_attrs': attrs,
}
- return direct_to_template(request, 'devel/packages.html', context)
-
-
-class NewUserForm(forms.ModelForm):
- username = forms.CharField(max_length=30)
- private_email = forms.EmailField()
- first_name = forms.CharField(required=False)
- last_name = forms.CharField(required=False)
- groups = forms.ModelMultipleChoiceField(required=False,
- queryset=Group.objects.all())
-
- class Meta:
- model = UserProfile
- exclude = ('picture', 'user')
-
- def __init__(self, *args, **kwargs):
- super(NewUserForm, self).__init__(*args, **kwargs)
- # Hack ourself so certain fields appear first. self.fields is a
- # SortedDict object where we can manipulate the keyOrder list.
- order = self.fields.keyOrder
- keys = ('username', 'private_email', 'first_name', 'last_name')
- for key in reversed(keys):
- order.remove(key)
- order.insert(0, key)
-
- def clean_username(self):
- username = self.cleaned_data['username']
- if User.objects.filter(username=username).exists():
- raise forms.ValidationError(
- "A user with that username already exists.")
- return username
-
- def save(self, commit=True):
- profile = super(NewUserForm, self).save(False)
- pwletters = ascii_letters + digits
- password = ''.join([random.choice(pwletters) for _ in xrange(8)])
- user = User.objects.create_user(username=self.cleaned_data['username'],
- email=self.cleaned_data['private_email'], password=password)
- user.first_name = self.cleaned_data['first_name']
- user.last_name = self.cleaned_data['last_name']
- user.save()
- # sucks that the MRM.add() method can't take a list directly... we have
- # to resort to dirty * magic.
- user.groups.add(*self.cleaned_data['groups'])
- profile.user = user
- if commit:
- profile.save()
- self.save_m2m()
-
- template = loader.get_template('devel/new_account.txt')
- ctx = Context({
- 'site': Site.objects.get_current(),
- 'user': user,
- 'password': password,
- })
-
- send_mail("Your new "+settings.BRANDING_APPNAME+" account",
- template.render(ctx),
- settings.BRANDING_EMAIL,
- [user.email],
- fail_silently=False)
+ return render(request, 'devel/packages.html', context)
+
def log_addition(request, obj):
"""Cribbed from ModelAdmin.log_addition."""
- from django.contrib.admin.models import LogEntry, ADDITION
- from django.contrib.contenttypes.models import ContentType
- from django.utils.encoding import force_unicode
LogEntry.objects.log_action(
user_id = request.user.pk,
content_type_id = ContentType.objects.get_for_model(obj).pk,
@@ -340,17 +318,16 @@ def log_addition(request, obj):
change_message = "Added via Create New User form."
)
+
@permission_required('auth.add_user')
@never_cache
def new_user_form(request):
if request.POST:
form = NewUserForm(request.POST)
if form.is_valid():
- @transaction.commit_on_success
- def inner_save():
+ with transaction.commit_on_success():
form.save()
log_addition(request, form.instance.user)
- inner_save()
return HttpResponseRedirect('/admin/auth/user/%d/' % \
form.instance.user.id)
else:
@@ -365,7 +342,8 @@ def new_user_form(request):
'title': 'Create User',
'submit_text': 'Create User'
}
- return direct_to_template(request, 'general_form.html', context)
+ return render(request, 'general_form.html', context)
+
@user_passes_test(lambda u: u.is_superuser)
def admin_log(request, username=None):
@@ -376,6 +354,6 @@ def admin_log(request, username=None):
'title': "Admin Action Log",
'log_user': user,
}
- return direct_to_template(request, 'devel/admin_log.html', context)
+ return render(request, 'devel/admin_log.html', context)
# vim: set ts=4 sw=4 et: