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.
This commit is contained in:
Родитель
a9a849cb7e
Коммит
85f3dc1c6a
|
@ -4,7 +4,12 @@ from django.contrib import admin
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.db import models
|
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',
|
UPLOADS_URL = getattr(settings, 'BADGER_UPLOADS_URL',
|
||||||
|
@ -20,16 +25,49 @@ def show_image(obj):
|
||||||
img_url = "%s%s" % (UPLOADS_URL, obj.image)
|
img_url = "%s%s" % (UPLOADS_URL, obj.image)
|
||||||
return ('<a href="%s" target="_new"><img src="%s" width="48" height="48" /></a>' %
|
return ('<a href="%s" target="_new"><img src="%s" width="48" height="48" /></a>' %
|
||||||
(img_url, img_url))
|
(img_url, img_url))
|
||||||
|
|
||||||
show_image.allow_tags = True
|
show_image.allow_tags = True
|
||||||
show_image.short_description = "Image"
|
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 ('<a href="%s">%s %s</a> (<a href="%s">new</a>)' %
|
||||||
|
(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', )
|
filter_horizontal = ('prerequisites', )
|
||||||
|
prepopulated_fields = {"slug": ("title",)}
|
||||||
formfield_overrides = {
|
formfield_overrides = {
|
||||||
models.ManyToManyField: {
|
models.ManyToManyField: {
|
||||||
"widget": forms.widgets.SelectMultiple(attrs={"size": 25})
|
"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 '<a href="%s">%s</a>' % (url, self.badge)
|
||||||
|
|
||||||
|
badge_link.allow_tags = True
|
||||||
|
badge_link.short_description = 'Badge'
|
||||||
|
|
||||||
|
|
||||||
class AwardAdmin(admin.ModelAdmin):
|
class AwardAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (show_unicode, badge_link, show_image, 'user', 'creator',
|
||||||
list_display = (show_unicode, 'badge', 'user', 'creator', show_image, 'created', )
|
'created', )
|
||||||
|
|
||||||
fields = ('badge', 'user', 'creator', )
|
fields = ('badge', 'user', 'creator', )
|
||||||
|
search_fields = ("badge__title", "badge__slug", "badge__description",)
|
||||||
|
|
||||||
|
|
||||||
class ProgressAdmin(admin.ModelAdmin):
|
class ProgressAdmin(admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Badge, BadgerAdmin)
|
def claim_code_link(self):
|
||||||
admin.site.register(Award, AwardAdmin)
|
return '<a href="%s">%s</a>' % (self.get_claim_url(), self.claim_code)
|
||||||
admin.site.register(Progress, ProgressAdmin)
|
|
||||||
|
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)
|
||||||
|
|
|
@ -5,6 +5,7 @@ from django.conf import settings
|
||||||
|
|
||||||
from django.contrib.auth.models import SiteProfileNotAvailable
|
from django.contrib.auth.models import SiteProfileNotAvailable
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.utils.html import conditional_escape
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from commons.urlresolvers import reverse
|
from commons.urlresolvers import reverse
|
||||||
|
@ -61,3 +62,13 @@ def user_awards(user):
|
||||||
@register.function
|
@register.function
|
||||||
def user_badges(user):
|
def user_badges(user):
|
||||||
return Badge.objects.filter(creator=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"""<img class="qrcode" src="%s" width="%s" height="%s" alt="%s" />""" %
|
||||||
|
(url, size, size, alt))
|
||||||
|
|
|
@ -267,21 +267,31 @@ class Badge(models.Model):
|
||||||
"""Representation of a badge"""
|
"""Representation of a badge"""
|
||||||
objects = BadgeManager()
|
objects = BadgeManager()
|
||||||
|
|
||||||
title = models.CharField(max_length=255, blank=False, unique=True)
|
title = models.CharField(max_length=255, blank=False, unique=True,
|
||||||
slug = models.SlugField(blank=False, unique=True)
|
help_text="Short, descriptive title")
|
||||||
description = models.TextField(blank=True)
|
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,
|
image = models.ImageField(blank=True, null=True,
|
||||||
storage=BADGE_UPLOADS_FS,
|
storage=BADGE_UPLOADS_FS, upload_to=mk_upload_to('image','png'),
|
||||||
upload_to=mk_upload_to('image','png'))
|
help_text="Upload an image to represent the badge")
|
||||||
prerequisites = models.ManyToManyField('self', symmetrical=False,
|
prerequisites = models.ManyToManyField('self', symmetrical=False,
|
||||||
blank=True, null=True)
|
blank=True, null=True,
|
||||||
unique = models.BooleanField(default=False)
|
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)
|
creator = models.ForeignKey(User, blank=True, null=True)
|
||||||
created = models.DateTimeField(auto_now_add=True, blank=False)
|
created = models.DateTimeField(auto_now_add=True, blank=False)
|
||||||
modified = models.DateTimeField(auto_now=True, blank=False)
|
modified = models.DateTimeField(auto_now=True, blank=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('title', 'slug')
|
unique_together = ('title', 'slug')
|
||||||
|
ordering = ['-modified', '-created']
|
||||||
|
|
||||||
get_permissions_for = get_permissions_for
|
get_permissions_for = get_permissions_for
|
||||||
|
|
||||||
|
@ -457,6 +467,9 @@ class Award(models.Model):
|
||||||
|
|
||||||
get_permissions_for = get_permissions_for
|
get_permissions_for = get_permissions_for
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['-modified', '-created']
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
by = self.creator and (' by %s' % self.creator) or ''
|
by = self.creator and (' by %s' % self.creator) or ''
|
||||||
return u'Award of %s to %s%s' % (self.badge, self.user, by)
|
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)
|
reusable = models.BooleanField(default=False)
|
||||||
email = models.EmailField(blank=True, null=True, db_index=True)
|
email = models.EmailField(blank=True, null=True, db_index=True)
|
||||||
claim_code = models.CharField(max_length=CLAIM_CODE_LENGTH,
|
claim_code = models.CharField(max_length=CLAIM_CODE_LENGTH,
|
||||||
default=make_random_code, editable=False, unique=True,
|
default=make_random_code, unique=True, db_index=True)
|
||||||
db_index=True)
|
|
||||||
creator = models.ForeignKey(User, blank=True, null=True)
|
creator = models.ForeignKey(User, blank=True, null=True)
|
||||||
created = models.DateTimeField(auto_now_add=True, blank=False)
|
created = models.DateTimeField(auto_now_add=True, blank=False)
|
||||||
modified = models.DateTimeField(auto_now=True, blank=False)
|
modified = models.DateTimeField(auto_now=True, blank=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['-modified', '-created']
|
||||||
|
|
||||||
def get_claim_url(self):
|
def get_claim_url(self):
|
||||||
"""Get the URL to a page where this DeferredAward can be claimed."""
|
"""Get the URL to a page where this DeferredAward can be claimed."""
|
||||||
return reverse('badger.views.claim_deferred_award',
|
return reverse('badger.views.claim_deferred_award',
|
||||||
|
|
|
@ -20,6 +20,8 @@ urlpatterns = patterns('badger.views',
|
||||||
name='badger.award_detail'),
|
name='badger.award_detail'),
|
||||||
url(r'^claim/(?P<claim_code>[^/]+)/?$', 'claim_deferred_award',
|
url(r'^claim/(?P<claim_code>[^/]+)/?$', 'claim_deferred_award',
|
||||||
name='badger.claim_deferred_award'),
|
name='badger.claim_deferred_award'),
|
||||||
|
url(r'^claim/?$', 'claim_deferred_award',
|
||||||
|
name='badger.claim_deferred_award_form'),
|
||||||
url(r'^badge/(?P<slug>[^/]+)/award', 'award_badge',
|
url(r'^badge/(?P<slug>[^/]+)/award', 'award_badge',
|
||||||
name='badger.award_badge'),
|
name='badger.award_badge'),
|
||||||
url(r'^badge/(?P<slug>[^\.]+)\.json$', 'detail',
|
url(r'^badge/(?P<slug>[^\.]+)\.json$', 'detail',
|
||||||
|
|
|
@ -163,8 +163,10 @@ def award_detail(request, slug, id, format="html"):
|
||||||
|
|
||||||
@require_http_methods(['GET', 'POST'])
|
@require_http_methods(['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def claim_deferred_award(request, claim_code):
|
def claim_deferred_award(request, claim_code=None):
|
||||||
"""Deferred award detail view"""
|
"""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)
|
deferred_award = get_object_or_404(DeferredAward, claim_code=claim_code)
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
|
|
Загрузка…
Ссылка в новой задаче