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 @@ def save(self): 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-54-g00ecf 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 @@ def save(self): [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-54-g00ecf 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.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 @@ def clean_username(self): "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 @@ def save(self): 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-54-g00ecf 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 +++--- feeds.py | 22 ++++------------------ main/models.py | 13 +++++++++++-- news/models.py | 2 +- packages/views.py | 2 +- 5 files changed, 20 insertions(+), 25 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) diff --git a/feeds.py b/feeds.py index 7a2f2e40..0be12531 100644 --- a/feeds.py +++ b/feeds.py @@ -1,5 +1,4 @@ -import datetime -from decimal import Decimal, ROUND_HALF_DOWN +import pytz from django.contrib.sites.models import Site from django.contrib.syndication.views import Feed @@ -10,7 +9,7 @@ from django.views.decorators.http import condition from main.models import Arch, Repo, Package -from main.utils import CACHE_TIMEOUT, INVALIDATE_TIMEOUT +from main.utils import CACHE_TIMEOUT from main.utils import CACHE_PACKAGE_KEY, CACHE_NEWS_KEY from news.models import News @@ -32,17 +31,6 @@ def write_items(self, handler): super(GuidNotPermalinkFeed, self).write_items(handler) -def utc_offset(): - '''Calculate the UTC offset from local time. Useful for converting values - stored in local time to things like cache last modifed headers.''' - timediff = datetime.datetime.utcnow() - datetime.datetime.now() - secs = timediff.days * 86400 + timediff.seconds - # round to nearest minute - mins = Decimal(secs) / Decimal(60) - mins = mins.quantize(Decimal('0'), rounding=ROUND_HALF_DOWN) - return datetime.timedelta(minutes=int(mins)) - - def retrieve_package_latest(): # we could break this down based on the request url, but it would probably # cost us more in query time to do so. @@ -52,7 +40,6 @@ def retrieve_package_latest(): try: latest = Package.objects.values('last_update').latest( 'last_update')['last_update'] - latest = latest + utc_offset() # Using add means "don't overwrite anything in there". What could be in # there is an explicit None value that our refresh signal set, which # means we want to avoid race condition possibilities for a bit. @@ -132,7 +119,7 @@ def item_guid(self, item): date.strftime('%Y%m%d%H%M')) def item_pubdate(self, item): - return item.last_update + return item.last_update.replace(tzinfo=pytz.utc) def item_categories(self, item): return (item.repo.name, item.arch.name) @@ -145,7 +132,6 @@ def retrieve_news_latest(): try: latest = News.objects.values('last_modified').latest( 'last_modified')['last_modified'] - latest = latest + utc_offset() # same thoughts apply as in retrieve_package_latest cache.add(CACHE_NEWS_KEY, latest, CACHE_TIMEOUT) return latest @@ -184,7 +170,7 @@ def item_guid(self, item): return item.guid def item_pubdate(self, item): - return item.postdate + return item.postdate.replace(tzinfo=pytz.utc) def item_author_name(self, item): return item.author.get_full_name() diff --git a/main/models.py b/main/models.py index 4370fa24..8d34731f 100644 --- a/main/models.py +++ b/main/models.py @@ -5,6 +5,7 @@ from main.utils import cache_function, make_choice from packages.models import PackageRelation +from datetime import datetime from itertools import groupby import pytz @@ -351,7 +352,7 @@ class Todolist(models.Model): creator = models.ForeignKey(User) name = models.CharField(max_length=255) description = models.TextField() - date_added = models.DateTimeField(auto_now_add=True, db_index=True) + date_added = models.DateTimeField(db_index=True) objects = TodolistManager() def __unicode__(self): @@ -383,10 +384,18 @@ class Meta: db_table = 'todolist_pkgs' unique_together = (('list','pkg'),) +def set_todolist_fields(sender, **kwargs): + todolist = kwargs['instance'] + if not todolist.date_added: + todolist.date_added = datetime.utcnow() + # connect signals needed to keep cache in line with reality from main.utils import refresh_package_latest -from django.db.models.signals import post_save +from django.db.models.signals import pre_save, post_save + post_save.connect(refresh_package_latest, sender=Package, dispatch_uid="main.models") +pre_save.connect(set_todolist_fields, sender=Todolist, + dispatch_uid="main.models") # vim: set ts=4 sw=4 et: diff --git a/news/models.py b/news/models.py index c4fb136a..17d51de9 100644 --- a/news/models.py +++ b/news/models.py @@ -27,7 +27,7 @@ class Meta: def set_news_fields(sender, **kwargs): news = kwargs['instance'] - now = datetime.now() + now = datetime.utcnow() news.last_modified = now if not news.postdate: news.postdate = now diff --git a/packages/views.py b/packages/views.py index 2f614e34..1587563b 100644 --- a/packages/views.py +++ b/packages/views.py @@ -371,7 +371,7 @@ def flag(request, name, repo, arch): # find all packages from (hopefully) the same PKGBUILD pkgs = Package.objects.filter( pkgbase=pkg.pkgbase, repo__testing=pkg.repo.testing) - pkgs.update(flag_date=datetime.now()) + pkgs.update(flag_date=datetime.utcnow()) maints = pkg.maintainers if not maints: -- cgit v1.2.3-54-g00ecf 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 +++--- todolists/views.py | 12 ++++++------ 2 files changed, 9 insertions(+), 9 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 @@ def save(self, commit=True): 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) diff --git a/todolists/views.py b/todolists/views.py index a2029be0..d3ed7818 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -35,7 +35,7 @@ class Meta: @permission_required('main.change_todolistpkg') @never_cache def flag(request, listid, pkgid): - list = get_object_or_404(Todolist, id=listid) + todolist = get_object_or_404(Todolist, id=listid) pkg = get_object_or_404(TodolistPkg, id=pkgid) pkg.complete = not pkg.complete pkg.save() @@ -48,8 +48,8 @@ def flag(request, listid, pkgid): @login_required @never_cache def view(request, listid): - list = get_object_or_404(Todolist, id=listid) - return direct_to_template(request, 'todolists/view.html', {'list': list}) + todolist = get_object_or_404(Todolist, id=listid) + return direct_to_template(request, 'todolists/view.html', {'list': todolist}) @login_required @never_cache @@ -161,13 +161,13 @@ def send_todolist_emails(todo_list, new_packages): maint_packages.setdefault(maint, []).append(todo_package) for maint, packages in maint_packages.iteritems(): - c = Context({ + ctx = Context({ 'todo_packages': sorted(packages), 'todolist': todo_list, }) - t = loader.get_template('todolists/email_notification.txt') + template = loader.get_template('todolists/email_notification.txt') send_mail('Packages added to todo list \'%s\'' % todo_list.name, - t.render(c), + template.render(ctx), 'Arch Website Notification ', [maint], fail_silently=True) -- cgit v1.2.3-54-g00ecf 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 ++++++-- templates/devel/index.html | 21 ++++++++++++++------- todolists/urls.py | 2 +- todolists/utils.py | 19 +++++++++++++++++++ todolists/views.py | 17 +++-------------- 5 files changed, 43 insertions(+), 24 deletions(-) create mode 100644 todolists/utils.py (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.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, diff --git a/templates/devel/index.html b/templates/devel/index.html index af2e5d28..92332c7a 100644 --- a/templates/devel/index.html +++ b/templates/devel/index.html @@ -72,19 +72,26 @@

Package Todo Lists

Name Creation Date + Creator Description + Package Count + Incomplete Count + {% for todo in todos %} - - {{ todo.name }} - {{ todo.date_added|date }} - {{ todo.description|urlize }} - + + {{ todo.name }} + {{ todo.date_added|date }} + {{ todo.creator.get_full_name }} + {{ todo.description|urlize }} + {{ todo.pkg_count }} + {{ todo.incomplete_count }} + {% empty %} - No package todo lists to display + No package todo lists to display {% endfor %} diff --git a/todolists/urls.py b/todolists/urls.py index 8814d65e..2612a52e 100644 --- a/todolists/urls.py +++ b/todolists/urls.py @@ -4,7 +4,7 @@ from .views import DeleteTodolist urlpatterns = patterns('todolists.views', - (r'^$', 'list'), + (r'^$', 'todolist_list'), (r'^(\d+)/$', 'view'), (r'^add/$', 'add'), (r'^edit/(?P\d+)/$', 'edit'), diff --git a/todolists/utils.py b/todolists/utils.py new file mode 100644 index 00000000..894f3f1d --- /dev/null +++ b/todolists/utils.py @@ -0,0 +1,19 @@ +from django.db.models import Count + +from main.models import Todolist + +def get_annotated_todolists(): + qs = Todolist.objects.all() + lists = qs.select_related('creator').annotate( + pkg_count=Count('todolistpkg')).order_by('-date_added') + incomplete = qs.filter(todolistpkg__complete=False).annotate( + Count('todolistpkg')).values_list('id', 'todolistpkg__count') + + # tag each list with an incomplete package count + lookup = dict(incomplete) + for todolist in lists: + todolist.incomplete_count = lookup.get(todolist.id, 0) + + return lists + +# vim: set ts=4 sw=4 et: diff --git a/todolists/views.py b/todolists/views.py index d3ed7818..ffe4c32f 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -5,7 +5,6 @@ from django.shortcuts import get_object_or_404, redirect from django.contrib.auth.decorators import login_required, permission_required from django.db import transaction -from django.db.models import Count from django.views.decorators.cache import never_cache from django.views.generic import DeleteView from django.views.generic.simple import direct_to_template @@ -13,6 +12,7 @@ from django.utils import simplejson from main.models import Todolist, TodolistPkg, Package +from .utils import get_annotated_todolists class TodoListForm(forms.ModelForm): packages = forms.CharField(required=False, @@ -53,19 +53,8 @@ def view(request, listid): @login_required @never_cache -def list(request): - lists = Todolist.objects.select_related('creator').annotate( - pkg_count=Count('todolistpkg')).order_by('-date_added') - incomplete = Todolist.objects.filter(todolistpkg__complete=False).annotate( - Count('todolistpkg')).values_list('id', 'todolistpkg__count') - - # tag each list with an incomplete package count - lookup = {} - for k, v in incomplete: - lookup[k] = v - for l in lists: - l.incomplete_count = lookup.get(l.id, 0) - +def todolist_list(request): + lists = get_annotated_todolists() return direct_to_template(request, 'todolists/list.html', {'lists': lists}) @permission_required('main.add_todolist') -- cgit v1.2.3-54-g00ecf 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 +++++++++++++++++++++++++++++++++++---- main/templatetags/attributes.py | 21 ++++++++++++++++ templates/devel/packages.html | 56 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 main/templatetags/attributes.py create mode 100644 templates/devel/packages.html (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.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 @@ @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() diff --git a/main/templatetags/attributes.py b/main/templatetags/attributes.py new file mode 100644 index 00000000..bd4ccf3d --- /dev/null +++ b/main/templatetags/attributes.py @@ -0,0 +1,21 @@ +import re +from django import template +from django.conf import settings + +numeric_test = re.compile("^\d+$") +register = template.Library() + +def attribute(value, arg): + """Gets an attribute of an object dynamically from a string name""" + if hasattr(value, str(arg)): + return getattr(value, arg) + elif hasattr(value, 'has_key') and value.has_key(arg): + return value[arg] + elif numeric_test.match(str(arg)) and len(value) > int(arg): + return value[int(arg)] + else: + return settings.TEMPLATE_STRING_IF_INVALID + +register.filter('attribute', attribute) + +# vim: set ts=4 sw=4 et: diff --git a/templates/devel/packages.html b/templates/devel/packages.html new file mode 100644 index 00000000..3b511c98 --- /dev/null +++ b/templates/devel/packages.html @@ -0,0 +1,56 @@ +{% extends "base.html" %} +{% load attributes %} + +{% block title %}Arch Linux - {{ title }}{% endblock %} + +{% block content %} +
+

{{ title }}

+ + + + + + + + + + + + {% for name in column_names %} + + {% endfor %} + + + + {% for pkg in packages %} + + + + + {% if pkg.flag_date %} + + {% else %} + + {% endif %} + + + + + {% for attr in column_attrs %} + + {% endfor %} + + {% endfor %} + +
ArchRepoNameVersionDescriptionLast UpdatedBuild DateFlag Date{{ name }}
{{ pkg.arch.name }}{{ pkg.repo.name|capfirst }}{{ pkg.pkgname }}{{ pkg.full_version }}{{ pkg.full_version }}{{ pkg.pkgdesc }}{{ pkg.last_update|date }}{{ pkg.build_date|date }}{{ pkg.flag_date|date }}{{ pkg|attribute:attr }}
+
+{% load cdn %}{% jquery %} + + +{% endblock %} -- cgit v1.2.3-54-g00ecf 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-54-g00ecf 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 +++-- templates/devel/index.html | 2 +- 2 files changed, 4 insertions(+), 3 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': diff --git a/templates/devel/index.html b/templates/devel/index.html index 601de752..8bf8c910 100644 --- a/templates/devel/index.html +++ b/templates/devel/index.html @@ -100,7 +100,7 @@

Package Todo Lists

Developer Reports

    -
  • Big: 100 largest compressed packages in the repositories
  • +
  • Big: All packages with compressed size > 50 MiB
  • Old: Packages last built more than two years ago
  • Uncompressed Manpages: Self-explanatory
  • Uncompressed Info Pages: Self-explanatory
  • -- cgit v1.2.3-54-g00ecf 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 ++++++++- templates/devel/index.html | 3 +++ templates/devel/packages.html | 1 + 3 files changed, 12 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.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 diff --git a/templates/devel/index.html b/templates/devel/index.html index 8bf8c910..29b98bdf 100644 --- a/templates/devel/index.html +++ b/templates/devel/index.html @@ -104,6 +104,9 @@

    Developer Reports

  • Old: Packages last built more than two years ago
  • Uncompressed Manpages: Self-explanatory
  • Uncompressed Info Pages: Self-explanatory
  • +
  • Unneeded Orphans: Packages + that have no maintainer and are not required by any other package in + any repository
diff --git a/templates/devel/packages.html b/templates/devel/packages.html index 3b511c98..32cdf217 100644 --- a/templates/devel/packages.html +++ b/templates/devel/packages.html @@ -6,6 +6,7 @@ {% block content %}

{{ title }}

+

{{ packages|length }} package{{ packages|pluralize }} found.

-- cgit v1.2.3-54-g00ecf 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 ++++++++- media/archweb.js | 27 +++++++++++++++++++++++++++ templates/devel/packages.html | 1 + 3 files changed, 36 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.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 diff --git a/media/archweb.js b/media/archweb.js index 03358fa9..78b15670 100644 --- a/media/archweb.js +++ b/media/archweb.js @@ -67,6 +67,33 @@ if (typeof $.tablesorter !== 'undefined') { }, type: 'numeric' }); + $.tablesorter.addParser({ + id: 'filesize', + re: /^(\d+(?:\.\d+)?) (bytes?|KB|MB|GB|TB)$/, + is: function(s) { + return this.re.test(s); + }, + format: function(s) { + var matches = this.re.exec(s); + var size = parseFloat(matches[1]); + var suffix = matches[2]; + + switch(suffix) { + case 'byte': + case 'bytes': + return size; + case 'KB': + return size * 1024; + case 'MB': + return size * 1024 * 1024; + case 'GB': + return size * 1024 * 1024 * 1024; + case 'TB': + return size * 1024 * 1024 * 1024 * 1024; + } + }, + type: 'numeric' + }); } /* news/add.html */ diff --git a/templates/devel/packages.html b/templates/devel/packages.html index 32cdf217..e0988c03 100644 --- a/templates/devel/packages.html +++ b/templates/devel/packages.html @@ -49,6 +49,7 @@

{{ title }}

{% load cdn %}{% jquery %} +