From 36f8649c36c0fa6af02247e0a796b79df8b1eb2b Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 29 Mar 2011 13:53:55 -0500 Subject: Show important fields first on new user form Signed-off-by: Dan McGee --- devel/views.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'devel') diff --git a/devel/views.py b/devel/views.py index 5b8965d8..b774e0e2 100644 --- a/devel/views.py +++ b/devel/views.py @@ -119,14 +119,25 @@ def change_profile(request): {'form': form, 'profile_form': profile_form}) class NewUserForm(forms.ModelForm): - class Meta: - model = UserProfile - exclude = ('picture', 'user') username = forms.CharField(max_length=30) - email = forms.EmailField() + private_email = forms.EmailField() first_name = forms.CharField(required=False) last_name = forms.CharField(required=False) + 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(): @@ -139,7 +150,7 @@ class NewUserForm(forms.ModelForm): pwletters = ascii_letters + digits password = ''.join([random.choice(pwletters) for i in xrange(8)]) user = User.objects.create_user(username=self.cleaned_data['username'], - email=self.cleaned_data['email'], password=password) + 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() -- cgit v1.2.3-2-g168b From 1ce650d3c3850020c6ba54766cb70ad049c6e0bd Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 29 Mar 2011 14:19:51 -0500 Subject: Log user additions via new user form Signed-off-by: Dan McGee --- devel/views.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'devel') diff --git a/devel/views.py b/devel/views.py index b774e0e2..a6a51f22 100644 --- a/devel/views.py +++ b/devel/views.py @@ -170,6 +170,20 @@ class NewUserForm(forms.ModelForm): [user.email], fail_silently=False) +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, + object_id = obj.pk, + object_repr = force_unicode(obj), + action_flag = ADDITION, + change_message = "Added via Create New User form." + ) + @permission_required('auth.add_user') @never_cache def new_user_form(request): @@ -177,6 +191,7 @@ def new_user_form(request): form = NewUserForm(request.POST) if form.is_valid(): form.save() + log_addition(request, form.instance.user) return HttpResponseRedirect('/admin/auth/user/%d/' % \ form.instance.user.id) else: -- cgit v1.2.3-2-g168b From 325b9d63d07cff73117258507e7cde592d2f824c Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 29 Mar 2011 14:46:25 -0500 Subject: Work out kinks in create new user view We allowed repo selection before, but never actually called save_m2m() so selections would have to be repeated on the next page. Add in group selection as well to the form, and ensure we do all of our database operations in one transaction so it is a lot easier to test and roll back if things go wrong. Signed-off-by: Dan McGee --- devel/views.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) (limited to 'devel') diff --git a/devel/views.py b/devel/views.py index a6a51f22..a83c3bbc 100644 --- a/devel/views.py +++ b/devel/views.py @@ -2,9 +2,10 @@ from django import forms 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 +from django.contrib.auth.models import User, Group from django.contrib.sites.models import Site from django.core.mail import send_mail +from django.db import transaction from django.shortcuts import get_object_or_404 from django.template import loader, Context from django.views.decorators.cache import never_cache @@ -123,6 +124,8 @@ class NewUserForm(forms.ModelForm): 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 @@ -145,8 +148,8 @@ class NewUserForm(forms.ModelForm): "A user with that username already exists.") return username - def save(self): - profile = forms.ModelForm.save(self, False) + def save(self, commit=True): + profile = super(NewUserForm, self).save(False) pwletters = ascii_letters + digits password = ''.join([random.choice(pwletters) for i in xrange(8)]) user = User.objects.create_user(username=self.cleaned_data['username'], @@ -154,8 +157,13 @@ class NewUserForm(forms.ModelForm): 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 - profile.save() + if commit: + profile.save() + self.save_m2m() t = loader.get_template('devel/new_account.txt') c = Context({ @@ -190,8 +198,11 @@ def new_user_form(request): if request.POST: form = NewUserForm(request.POST) if form.is_valid(): - form.save() - log_addition(request, form.instance.user) + @transaction.commit_on_success + def inner_save(): + form.save() + log_addition(request, form.instance.user) + inner_save() return HttpResponseRedirect('/admin/auth/user/%d/' % \ form.instance.user.id) else: -- cgit v1.2.3-2-g168b From 01db07bad844e17e084f650b6732647f77a91c5c Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 7 Apr 2011 15:39:53 -0500 Subject: Use UTC datetime objects everywhere Rather than the twisted mix of local times and UTC times we currently have. Signed-off-by: Dan McGee --- devel/management/commands/reporead.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'devel') diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py index e26bb800..a8875c7e 100644 --- a/devel/management/commands/reporead.py +++ b/devel/management/commands/reporead.py @@ -315,7 +315,7 @@ def populate_files(dbpkg, repopkg, force=False): directory=dirname + '/', filename=filename) pkgfile.save(force_insert=True) - dbpkg.files_last_update = datetime.now() + dbpkg.files_last_update = datetime.utcnow() dbpkg.save() @transaction.commit_on_success @@ -374,7 +374,7 @@ def db_update(archname, reponame, pkgs, options): for p in [x for x in pkgs if x.name in in_sync_not_db]: logger.info("Adding package %s", p.name) pkg = Package(pkgname = p.name, arch = architecture, repo = repository) - populate_pkg(pkg, p, timestamp=datetime.now()) + populate_pkg(pkg, p, timestamp=datetime.utcnow()) # packages in database and not in syncdb (remove from database) in_db_not_sync = dbset - syncset @@ -398,7 +398,7 @@ def db_update(archname, reponame, pkgs, options): if not force: continue else: - timestamp = datetime.now() + timestamp = datetime.utcnow() if filesonly: logger.debug("Checking files for package %s in database", p.name) populate_files(dbp, p, force=force) -- cgit v1.2.3-2-g168b From 842f59d018947ca696cf116e9d1591f2ad83f8a7 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 9 Apr 2011 16:29:02 -0500 Subject: Rename local variables for clarity Signed-off-by: Dan McGee --- devel/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'devel') diff --git a/devel/views.py b/devel/views.py index a83c3bbc..cb0ff056 100644 --- a/devel/views.py +++ b/devel/views.py @@ -165,15 +165,15 @@ class NewUserForm(forms.ModelForm): profile.save() self.save_m2m() - t = loader.get_template('devel/new_account.txt') - c = Context({ + template = loader.get_template('devel/new_account.txt') + ctx = Context({ 'site': Site.objects.get_current(), 'user': user, 'password': password, }) send_mail("Your new archweb account", - t.render(c), + template.render(ctx), 'Arch Website Notification ', [user.email], fail_silently=False) -- cgit v1.2.3-2-g168b From 064813560c20f53f9fd759d0c4e0f0a6729c8ba6 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 9 Apr 2011 16:29:38 -0500 Subject: Show more info about todolists on developer dashboard Signed-off-by: Dan McGee --- devel/views.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'devel') diff --git a/devel/views.py b/devel/views.py index cb0ff056..b61e605f 100644 --- a/devel/views.py +++ b/devel/views.py @@ -11,10 +11,11 @@ from django.template import loader, Context from django.views.decorators.cache import never_cache from django.views.generic.simple import direct_to_template -from main.models import Package, Todolist, TodolistPkg +from main.models import Package, TodolistPkg from main.models import Arch, Repo from main.models import UserProfile from packages.models import PackageRelation +from todolists.utils import get_annotated_todolists from .utils import get_annotated_maintainers import datetime @@ -35,6 +36,9 @@ def index(request): todopkgs = todopkgs.filter(pkg__pkgbase__in=inner_q).order_by( 'list__name', 'pkg__pkgname') + todolists = get_annotated_todolists() + todolists = [todolist for todolist in todolists if todolist.incomplete_count > 0] + maintainers = get_annotated_maintainers() maintained = PackageRelation.objects.filter( @@ -48,7 +52,7 @@ def index(request): } page_dict = { - 'todos': Todolist.objects.incomplete().order_by('-date_added'), + 'todos': todolists, 'repos': Repo.objects.all(), 'arches': Arch.objects.all(), 'maintainers': maintainers, -- cgit v1.2.3-2-g168b From 381e0a787205af530ae11bac1b1a17e567eecc84 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 25 Apr 2011 18:09:39 -0500 Subject: Developer reports This commit adds four initial developer reports that are hopefully useful to developers and packages in checking up on the state of things. They include: * big : the 100 biggest packages in the repos * old : packages built > 2 years ago * uncompressed-man : self-explanatory * uncompressed-info : self-explanatory There should obviously be some sort of index page to access all of these, so that will be coming soon. Signed-off-by: Dan McGee --- devel/urls.py | 9 +++++---- devel/views.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 9 deletions(-) (limited to 'devel') diff --git a/devel/urls.py b/devel/urls.py index 41be2b31..9bf50f45 100644 --- a/devel/urls.py +++ b/devel/urls.py @@ -1,12 +1,13 @@ from django.conf.urls.defaults import patterns urlpatterns = patterns('devel.views', - (r'^$', 'index'), + (r'^admin_log/$','admin_log'), + (r'^admin_log/(?P.*)/$','admin_log'), (r'^clock/$', 'clock'), - (r'^profile/$', 'change_profile'), + (r'^$', 'index'), (r'^newuser/$', 'new_user_form'), - (r'^admin_log/(?P.*)/$','admin_log'), - (r'^admin_log/$','admin_log'), + (r'^profile/$', 'change_profile'), + (r'^reports/(?P.*)/$', 'report'), ) # vim: set ts=4 sw=4 et: diff --git a/devel/views.py b/devel/views.py index b61e605f..01d54e6f 100644 --- a/devel/views.py +++ b/devel/views.py @@ -6,19 +6,22 @@ from django.contrib.auth.models import User, Group from django.contrib.sites.models import Site from django.core.mail import send_mail from django.db import transaction +from django.db.models import Q +from django.http import Http404 from django.shortcuts import get_object_or_404 from django.template import loader, Context from django.views.decorators.cache import never_cache from django.views.generic.simple import direct_to_template -from main.models import Package, TodolistPkg +from main.models import Package, PackageFile, TodolistPkg from main.models import Arch, Repo from main.models import UserProfile from packages.models import PackageRelation from todolists.utils import get_annotated_todolists from .utils import get_annotated_maintainers -import datetime +from datetime import datetime, timedelta +import operator import pytz import random from string import ascii_letters, digits @@ -26,7 +29,7 @@ from string import ascii_letters, digits @login_required @never_cache def index(request): - '''the Developer dashboard''' + '''the developer dashboard''' inner_q = PackageRelation.objects.filter(user=request.user).values('pkgbase') flagged = Package.objects.select_related('arch', 'repo').filter( flag_date__isnull=False, pkgbase__in=inner_q).order_by('pkgname') @@ -70,8 +73,8 @@ def clock(request): 'username').select_related('userprofile') # now annotate each dev object with their current time - now = datetime.datetime.now() - utc_now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc) + now = datetime.now() + utc_now = datetime.utcnow().replace(tzinfo=pytz.utc) for dev in devs: # Work around https://bugs.launchpad.net/pytz/+bug/718673 timezone = str(dev.userprofile.time_zone) @@ -123,6 +126,47 @@ def change_profile(request): return direct_to_template(request, 'devel/profile.html', {'form': form, 'profile_form': profile_form}) +@login_required +def report(request, report): + title = "Developer Report" + packages = Package.objects.select_related('arch', 'repo') + names = attrs = None + if report == "old": + title = "Packages last built more than two years ago" + cutoff = datetime.now() - timedelta(days=730) + packages = packages.filter(build_date__lt=cutoff).order_by('build_date') + elif report == "big": + title = "100 largest compressed packages" + packages = packages.order_by('-compressed_size')[:100] + names = [ 'Compressed Size', 'Installed Size' ] + attrs = [ 'compressed_size', 'installed_size' ] + elif report == "uncompressed-man": + title = "Packages with uncompressed manpages" + # magic going on here! Checking for all '.1'...'.9' extensions + invalid_endings = [Q(filename__endswith='.%d' % n) for n in range(1,10)] + invalid_endings.append(Q(filename__endswith='.n')) + bad_files = PackageFile.objects.filter(Q(directory__contains='man') & ( + reduce(operator.or_, invalid_endings)) + ).values_list('pkg_id', flat=True).distinct() + packages = packages.filter(id__in=set(bad_files)) + elif report == "uncompressed-info": + title = "Packages with uncompressed infopages" + bad_files = PackageFile.objects.filter(directory__contains='/info', + filename__endswith='.info').values_list( + 'pkg_id', flat=True).distinct() + packages = packages.filter(id__in=set(bad_files)) + else: + raise Http404 + + context = { + 'title': title, + 'packages': packages, + '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() -- cgit v1.2.3-2-g168b From 2d1cfc24232945b2ad2c749cc3b3443a89ee880f Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 29 Apr 2011 17:58:31 -0500 Subject: Use single quotes only in dev report view --- devel/views.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'devel') diff --git a/devel/views.py b/devel/views.py index 01d54e6f..cf24f0d9 100644 --- a/devel/views.py +++ b/devel/views.py @@ -128,20 +128,20 @@ def change_profile(request): @login_required def report(request, report): - title = "Developer Report" + title = 'Developer Report' packages = Package.objects.select_related('arch', 'repo') names = attrs = None - if report == "old": - title = "Packages last built more than two years ago" + if report == 'old': + title = 'Packages last built more than two years ago' cutoff = datetime.now() - timedelta(days=730) packages = packages.filter(build_date__lt=cutoff).order_by('build_date') - elif report == "big": - title = "100 largest compressed packages" + elif report == 'big': + title = '100 largest compressed packages' packages = packages.order_by('-compressed_size')[:100] names = [ 'Compressed Size', 'Installed Size' ] attrs = [ 'compressed_size', 'installed_size' ] - elif report == "uncompressed-man": - title = "Packages with uncompressed manpages" + elif report == 'uncompressed-man': + title = 'Packages with uncompressed manpages' # magic going on here! Checking for all '.1'...'.9' extensions invalid_endings = [Q(filename__endswith='.%d' % n) for n in range(1,10)] invalid_endings.append(Q(filename__endswith='.n')) @@ -149,8 +149,8 @@ def report(request, report): reduce(operator.or_, invalid_endings)) ).values_list('pkg_id', flat=True).distinct() packages = packages.filter(id__in=set(bad_files)) - elif report == "uncompressed-info": - title = "Packages with uncompressed infopages" + elif report == 'uncompressed-info': + title = 'Packages with uncompressed infopages' bad_files = PackageFile.objects.filter(directory__contains='/info', filename__endswith='.info').values_list( 'pkg_id', flat=True).distinct() -- cgit v1.2.3-2-g168b From e58eb76a1ab4d6f1293b717e21da68f5aa3e5c45 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 29 Apr 2011 18:21:10 -0500 Subject: Change big packages report Signed-off-by: Dan McGee --- devel/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'devel') diff --git a/devel/views.py b/devel/views.py index cf24f0d9..a013b329 100644 --- a/devel/views.py +++ b/devel/views.py @@ -136,8 +136,9 @@ def report(request, report): cutoff = datetime.now() - timedelta(days=730) packages = packages.filter(build_date__lt=cutoff).order_by('build_date') elif report == 'big': - title = '100 largest compressed packages' - packages = packages.order_by('-compressed_size')[:100] + title = 'Packages with compressed size > 50 MiB' + cutoff = 50 * 1024 * 1024 + packages = packages.filter(compressed_size__gte=cutoff).order_by('-compressed_size') names = [ 'Compressed Size', 'Installed Size' ] attrs = [ 'compressed_size', 'installed_size' ] elif report == 'uncompressed-man': -- cgit v1.2.3-2-g168b From 5379348c9337a4abe27e807fef7956e11eebed30 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 29 Apr 2011 18:21:43 -0500 Subject: Add unneeded orphans report Signed-off-by: Dan McGee --- devel/views.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'devel') diff --git a/devel/views.py b/devel/views.py index a013b329..6907de24 100644 --- a/devel/views.py +++ b/devel/views.py @@ -13,7 +13,7 @@ from django.template import loader, Context from django.views.decorators.cache import never_cache from django.views.generic.simple import direct_to_template -from main.models import Package, PackageFile, TodolistPkg +from main.models import Package, PackageDepend, PackageFile, TodolistPkg from main.models import Arch, Repo from main.models import UserProfile from packages.models import PackageRelation @@ -156,6 +156,13 @@ def report(request, report): filename__endswith='.info').values_list( 'pkg_id', flat=True).distinct() packages = packages.filter(id__in=set(bad_files)) + elif report == 'unneeded-orphans': + title = 'Orphan packages required by no other packages' + owned = PackageRelation.objects.all().values('pkgbase') + required = PackageDepend.objects.all().values('depname') + # The two separate calls to exclude is required to do the right thing + packages = packages.exclude(pkgbase__in=owned).exclude( + pkgname__in=required) else: raise Http404 -- cgit v1.2.3-2-g168b From 8de1bd0639a8b6117bc35dfe0ad1e6a1ac34f715 Mon Sep 17 00:00:00 2001 From: Evangelos Foutras Date: Sat, 30 Apr 2011 15:15:38 +0300 Subject: Add filesizeformat filter to sizes in reports/big We also add a new 'filesize' tablesorter parser that handles all the suffixes found in django's filesizeformat filter. Signed-off-by: Dan McGee --- devel/views.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'devel') diff --git a/devel/views.py b/devel/views.py index 6907de24..9c523f0a 100644 --- a/devel/views.py +++ b/devel/views.py @@ -10,6 +10,7 @@ from django.db.models import Q from django.http import Http404 from django.shortcuts import get_object_or_404 from django.template import loader, Context +from django.template.defaultfilters import filesizeformat from django.views.decorators.cache import never_cache from django.views.generic.simple import direct_to_template @@ -140,7 +141,13 @@ def report(request, report): cutoff = 50 * 1024 * 1024 packages = packages.filter(compressed_size__gte=cutoff).order_by('-compressed_size') names = [ 'Compressed Size', 'Installed Size' ] - attrs = [ 'compressed_size', 'installed_size' ] + attrs = [ 'compressed_size_pretty', 'installed_size_pretty' ] + # Format the compressed and installed sizes with MB/GB/etc suffixes + for package in packages: + package.compressed_size_pretty = filesizeformat( + package.compressed_size) + package.installed_size_pretty = filesizeformat( + package.installed_size) elif report == 'uncompressed-man': title = 'Packages with uncompressed manpages' # magic going on here! Checking for all '.1'...'.9' extensions -- cgit v1.2.3-2-g168b From 3fdc79b01066b91348ab655be890a0aa59aac8b8 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 4 May 2011 12:53:49 -0500 Subject: Make uncompressed info query slightly easier on the database Signed-off-by: Dan McGee --- devel/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'devel') diff --git a/devel/views.py b/devel/views.py index 9c523f0a..555c7cc5 100644 --- a/devel/views.py +++ b/devel/views.py @@ -159,7 +159,9 @@ def report(request, report): packages = packages.filter(id__in=set(bad_files)) elif report == 'uncompressed-info': title = 'Packages with uncompressed infopages' - bad_files = PackageFile.objects.filter(directory__contains='/info', + # we don't worry abut looking for '*.info-1', etc., given that an + # uncompressed root page probably exists in the package anyway + bad_files = PackageFile.objects.filter(directory__endswith='/info/', filename__endswith='.info').values_list( 'pkg_id', flat=True).distinct() packages = packages.filter(id__in=set(bad_files)) -- cgit v1.2.3-2-g168b