From 85f3dc1c6ab2189db9c76e732951c1e89f3522ad Mon Sep 17 00:00:00 2001 From: Les Orchard Date: Sat, 7 Apr 2012 12:51:36 -0400 Subject: [PATCH] Improvements to admin, claim codes, login * Fix to ?next parameter in post-login redirect * Simple claim code entry field in the header * Quick hack to stick a QR code on the DeferredAward detail page. * Admin improvements to better handle relations between badges, awards, and deferred awards. --- badger/admin.py | 85 +++++++++++++++++++++++++++++++++++++++++------ badger/helpers.py | 11 ++++++ badger/models.py | 33 +++++++++++++----- badger/urls.py | 2 ++ badger/views.py | 4 ++- 5 files changed, 115 insertions(+), 20 deletions(-) diff --git a/badger/admin.py b/badger/admin.py index 4ed3eb7..bb49805 100644 --- a/badger/admin.py +++ b/badger/admin.py @@ -4,7 +4,12 @@ from django.contrib import admin from django import forms from django.db import models -from .models import (Badge, Award, Progress) +try: + from funfactory.urlresolvers import reverse +except ImportError, e: + from django.core.urlresolvers import reverse + +from .models import (Badge, Award, Progress, DeferredAward) UPLOADS_URL = getattr(settings, 'BADGER_UPLOADS_URL', @@ -20,16 +25,49 @@ def show_image(obj): img_url = "%s%s" % (UPLOADS_URL, obj.image) return ('' % (img_url, img_url)) + show_image.allow_tags = True show_image.short_description = "Image" -class BadgerAdmin(admin.ModelAdmin): +def build_related_link(self, model_name, name_single, name_plural, qs): + link = '%s?%s' % ( + reverse('admin:badger_%s_changelist' % model_name, args=[]), + 'badge__exact=%s' % (self.id) + ) + new_link = '%s?%s' % ( + reverse('admin:badger_%s_add' % model_name, args=[]), + 'badge=%s' % (self.id) + ) + count = qs.count() + what = (count == 1) and name_single or name_plural + return ('%s %s (new)' % + (link, count, what, new_link)) - list_display = ("title", "slug", "unique", "creator", show_image, "created", ) +def related_deferredawards_link(self): + return build_related_link(self, 'deferredaward', 'deferred', 'deferred', + self.deferredaward_set) + +related_deferredawards_link.allow_tags = True +related_deferredawards_link.short_description = "Deferred Awards" + + +def related_awards_link(self): + return build_related_link(self, 'award', 'award', 'awards', + self.award_set) + +related_awards_link.allow_tags = True +related_awards_link.short_description = "Awards" + + +class BadgeAdmin(admin.ModelAdmin): + list_display = ("id", "title", show_image, "slug", "unique", "creator", + related_awards_link, related_deferredawards_link, "created",) + list_display_links = ('id', 'title',) + search_fields = ("title", "slug", "image", "description",) filter_horizontal = ('prerequisites', ) - + prepopulated_fields = {"slug": ("title",)} formfield_overrides = { models.ManyToManyField: { "widget": forms.widgets.SelectMultiple(attrs={"size": 25}) @@ -37,17 +75,44 @@ class BadgerAdmin(admin.ModelAdmin): } +def badge_link(self): + url = reverse('admin:badger_badge_change', args=[self.badge.id]) + return '%s' % (url, self.badge) + +badge_link.allow_tags = True +badge_link.short_description = 'Badge' + + class AwardAdmin(admin.ModelAdmin): - - list_display = (show_unicode, 'badge', 'user', 'creator', show_image, 'created', ) - + list_display = (show_unicode, badge_link, show_image, 'user', 'creator', + 'created', ) fields = ('badge', 'user', 'creator', ) + search_fields = ("badge__title", "badge__slug", "badge__description",) class ProgressAdmin(admin.ModelAdmin): pass -admin.site.register(Badge, BadgerAdmin) -admin.site.register(Award, AwardAdmin) -admin.site.register(Progress, ProgressAdmin) +def claim_code_link(self): + return '%s' % (self.get_claim_url(), self.claim_code) + +claim_code_link.allow_tags = True +claim_code_link.short_description = "Claim Code" + + +class DeferredAwardAdmin(admin.ModelAdmin): + list_display = ('id', claim_code_link, badge_link, 'email', 'reusable', + 'creator', 'created', 'modified',) + list_display_links = ('id',) + list_filter = ('reusable', ) + fields = ('badge', 'claim_code', 'email', 'reusable', 'description',) + readonly_fields = ('created', 'modified') + search_fields = ("badge__title", "badge__slug", "badge__description",) + + +for x in ((Badge, BadgeAdmin), + (Award, AwardAdmin), + (Progress, ProgressAdmin), + (DeferredAward, DeferredAwardAdmin),): + admin.site.register(*x) diff --git a/badger/helpers.py b/badger/helpers.py index 6e359c2..b46afe7 100644 --- a/badger/helpers.py +++ b/badger/helpers.py @@ -5,6 +5,7 @@ from django.conf import settings from django.contrib.auth.models import SiteProfileNotAvailable from django.core.exceptions import ObjectDoesNotExist +from django.utils.html import conditional_escape try: from commons.urlresolvers import reverse @@ -61,3 +62,13 @@ def user_awards(user): @register.function def user_badges(user): return Badge.objects.filter(creator=user) + + +@register.function +def qr_code_image(value, alt=None, size=150): + url = conditional_escape("http://chart.apis.google.com/chart?%s" % \ + urllib.urlencode({'chs':'%sx%s' % (size, size), 'cht':'qr', 'chl':value, 'choe':'UTF-8'})) + alt = conditional_escape(alt or value) + + return Markup(u"""%s""" % + (url, size, size, alt)) diff --git a/badger/models.py b/badger/models.py index cc77624..008bf62 100644 --- a/badger/models.py +++ b/badger/models.py @@ -267,21 +267,31 @@ class Badge(models.Model): """Representation of a badge""" objects = BadgeManager() - title = models.CharField(max_length=255, blank=False, unique=True) - slug = models.SlugField(blank=False, unique=True) - description = models.TextField(blank=True) + title = models.CharField(max_length=255, blank=False, unique=True, + help_text="Short, descriptive title") + slug = models.SlugField(blank=False, unique=True, + help_text="Very short name, for use in URLs and links") + description = models.TextField(blank=True, + help_text="Longer description of the badge and its criteria") image = models.ImageField(blank=True, null=True, - storage=BADGE_UPLOADS_FS, - upload_to=mk_upload_to('image','png')) + storage=BADGE_UPLOADS_FS, upload_to=mk_upload_to('image','png'), + help_text="Upload an image to represent the badge") prerequisites = models.ManyToManyField('self', symmetrical=False, - blank=True, null=True) - unique = models.BooleanField(default=False) + blank=True, null=True, + help_text="When all of the selected badges have been awarded, this " + "badge will be automatically awarded.") + # TODO: Rename? Eventually we'll want a globally-unique badge. That is, one + # unique award for one person for the whole site. + unique = models.BooleanField(default=False, + help_text="Should only one award of this badge be allowed per " + "person?") creator = models.ForeignKey(User, blank=True, null=True) created = models.DateTimeField(auto_now_add=True, blank=False) modified = models.DateTimeField(auto_now=True, blank=False) class Meta: unique_together = ('title', 'slug') + ordering = ['-modified', '-created'] get_permissions_for = get_permissions_for @@ -457,6 +467,9 @@ class Award(models.Model): get_permissions_for = get_permissions_for + class Meta: + ordering = ['-modified', '-created'] + def __unicode__(self): by = self.creator and (' by %s' % self.creator) or '' return u'Award of %s to %s%s' % (self.badge, self.user, by) @@ -682,12 +695,14 @@ class DeferredAward(models.Model): reusable = models.BooleanField(default=False) email = models.EmailField(blank=True, null=True, db_index=True) claim_code = models.CharField(max_length=CLAIM_CODE_LENGTH, - default=make_random_code, editable=False, unique=True, - db_index=True) + default=make_random_code, unique=True, db_index=True) creator = models.ForeignKey(User, blank=True, null=True) created = models.DateTimeField(auto_now_add=True, blank=False) modified = models.DateTimeField(auto_now=True, blank=False) + class Meta: + ordering = ['-modified', '-created'] + def get_claim_url(self): """Get the URL to a page where this DeferredAward can be claimed.""" return reverse('badger.views.claim_deferred_award', diff --git a/badger/urls.py b/badger/urls.py index cc1a30f..21f1881 100644 --- a/badger/urls.py +++ b/badger/urls.py @@ -20,6 +20,8 @@ urlpatterns = patterns('badger.views', name='badger.award_detail'), url(r'^claim/(?P[^/]+)/?$', 'claim_deferred_award', name='badger.claim_deferred_award'), + url(r'^claim/?$', 'claim_deferred_award', + name='badger.claim_deferred_award_form'), url(r'^badge/(?P[^/]+)/award', 'award_badge', name='badger.award_badge'), url(r'^badge/(?P[^\.]+)\.json$', 'detail', diff --git a/badger/views.py b/badger/views.py index 91b4bf7..351b82a 100644 --- a/badger/views.py +++ b/badger/views.py @@ -163,8 +163,10 @@ def award_detail(request, slug, id, format="html"): @require_http_methods(['GET', 'POST']) @login_required -def claim_deferred_award(request, claim_code): +def claim_deferred_award(request, claim_code=None): """Deferred award detail view""" + if not claim_code: + claim_code = request.GET.get('code', '').strip() deferred_award = get_object_or_404(DeferredAward, claim_code=claim_code) if request.method == "POST":