Mercurial > public > sg101
changeset 204:b4305e18d3af
Resolve ticket #74. Add user badges. Some extra credit was done here: also refactored how pending news, links, and downloads are handled.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Sat, 01 May 2010 21:53:59 +0000 |
parents | 40e5903903e1 |
children | da46e77cd804 |
files | gpp/bio/admin.py gpp/bio/badges.py gpp/bio/models.py gpp/bio/signals.py gpp/bio/views.py gpp/comments/admin.py gpp/core/templatetags/custom_admin_tags.py gpp/downloads/admin.py gpp/downloads/forms.py gpp/downloads/models.py gpp/downloads/views.py gpp/forums/admin.py gpp/gcalendar/admin.py gpp/news/admin.py gpp/news/feeds.py gpp/news/models.py gpp/news/views.py gpp/templates/bio/view_profile.html gpp/templates/core/admin_dashboard.html gpp/templates/forums/display_post.html gpp/templates/news/category_index.html gpp/templates/news/story.html gpp/templates/news/story_summary.html gpp/weblinks/admin.py gpp/weblinks/forms.py gpp/weblinks/models.py gpp/weblinks/views.py media/css/base.css |
diffstat | 28 files changed, 406 insertions(+), 78 deletions(-) [+] |
line wrap: on
line diff
--- a/gpp/bio/admin.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/bio/admin.py Sat May 01 21:53:59 2010 +0000 @@ -4,13 +4,17 @@ import datetime from django.contrib import admin -from bio.models import UserProfile -from bio.models import UserProfileFlag + import bio.models from comments.models import Comment from forums.tools import delete_user_posts +class BadgeOwnerInline(admin.TabularInline): + model = bio.models.BadgeOwnership + extra = 1 + + class UserProfileAdmin(admin.ModelAdmin): search_fields = ('user__username', 'user__first_name', 'user__last_name', 'user__email') @@ -18,6 +22,7 @@ list_display = ('__unicode__', 'get_status_display', 'status_date') list_filter = ('status', ) date_hierarchy = 'status_date' + inlines = (BadgeOwnerInline, ) actions = ( 'mark_active', 'mark_resigned', @@ -93,5 +98,11 @@ list_display = ('__unicode__', 'flag_date', 'get_profile_url') -admin.site.register(UserProfile, UserProfileAdmin) -admin.site.register(UserProfileFlag, UserProfileFlagAdmin) +class BadgeAdmin(admin.ModelAdmin): + list_display = ('name', 'html', 'order', 'numeric_id', 'description') + list_editable = ('order', 'numeric_id') + + +admin.site.register(bio.models.UserProfile, UserProfileAdmin) +admin.site.register(bio.models.UserProfileFlag, UserProfileFlagAdmin) +admin.site.register(bio.models.Badge, BadgeAdmin)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gpp/bio/badges.py Sat May 01 21:53:59 2010 +0000 @@ -0,0 +1,35 @@ +"""This module contains user profile badge-related functionality.""" + +from bio.models import Badge +from bio.models import BadgeOwnership + + +# Numeric ID's for badges that are awarded for user actions: +(CONTRIBUTOR_PIN, CALENDAR_PIN, NEWS_PIN, LINK_PIN, DOWNLOAD_PIN, + SECURITY_PIN) = range(6) + + +def award_badge(badge_id, user): + """This function awards the badge specified by badge_id + to the given user. If the user already has the badge, + the badge count is incremented by one. + """ + import logging + try: + badge = Badge.objects.get(numeric_id=badge_id) + except Badge.DoesNotExist: + logging.error("Can't award badge with numeric_id = %d" % badge_id) + return + + profile = user.get_profile() + + # Does the user already have badges of this type? + try: + bo = BadgeOwnership.objects.get(profile=profile, badge=badge) + except BadgeOwnership.DoesNotExist: + # No badge of this type, yet + bo = BadgeOwnership(profile=profile, badge=badge, count=1) + else: + # Already have this badge + bo.count += 1 + bo.save()
--- a/gpp/bio/models.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/bio/models.py Sat May 01 21:53:59 2010 +0000 @@ -23,6 +23,33 @@ (STA_SPAMMER, "Spammer"), ) + +class Badge(models.Model): + """This model represents badges that users can earn.""" + image = models.ImageField(upload_to='badges') + name = models.CharField(max_length=64) + description = models.TextField(blank=True) + order = models.IntegerField() + numeric_id = models.IntegerField(db_index=True) + + class Meta: + ordering = ('order', ) + + def __unicode__(self): + return self.name + + def get_absolute_url(self): + return self.image.url + + def html(self): + """Returns a HTML img tag representation of the badge.""" + if self.image: + return u'<img src="%s" alt="%s" title="%s" />' % ( + self.get_absolute_url(), self.name, self.name) + return u'' + html.allow_tags = True + + def avatar_file_path_for_user(username, filename): return os.path.join(settings.AVATAR_DIR, 'users', username, filename) @@ -52,6 +79,7 @@ status = models.IntegerField(default=STA_ACTIVE, choices=USER_STATUS_CHOICES) status_date = models.DateTimeField(auto_now_add=True) + badges = models.ManyToManyField(Badge, through="BadgeOwnership") def __unicode__(self): return self.user.username @@ -70,6 +98,13 @@ def get_absolute_url(self): return ('bio-view_profile', (), {'username': self.user.username}) + def badge_ownership(self): + if hasattr(self, '_badges'): + return self._badges + self._badges = BadgeOwnership.objects.filter(profile=self).select_related( + "badge") + return self._badges + class UserProfileFlag(models.Model): """This model represents a user flagging a profile as inappropriate.""" @@ -87,3 +122,27 @@ def get_profile_url(self): return '<a href="%s">Profile</a>' % self.profile.get_absolute_url() get_profile_url.allow_tags = True + + +class BadgeOwnership(models.Model): + """This model represents the ownership of badges by users.""" + profile = models.ForeignKey(UserProfile) + badge = models.ForeignKey(Badge) + count = models.IntegerField(default=1) + + class Meta: + verbose_name_plural = "badge ownership" + ordering = ('badge__order', ) + + def __unicode__(self): + if self.count == 1: + return u"%s owns 1 %s" % (self.profile.user.username, + self.badge.name) + else: + return u"%s owns %d %s badges" % (self.profile.user.username, + self.count, self.badge.name) + + def badge_count_str(self): + if self.count == 1: + return u"1 %s" % self.badge.name + return u"%d %ss" % (self.count, self.badge.name)
--- a/gpp/bio/signals.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/bio/signals.py Sat May 01 21:53:59 2010 +0000 @@ -3,7 +3,14 @@ """ from django.db.models.signals import post_save from django.contrib.auth.models import User + +import bio.badges from bio.models import UserProfile +from donations.models import Donation +from weblinks.models import Link +from downloads.models import Download +from news.models import Story + def on_user_save(sender, **kwargs): """ @@ -14,9 +21,51 @@ created = kwargs['created'] if created: user = kwargs['instance'] - profile = UserProfile() + profile = bio.models.UserProfile() profile.user = user profile.save() +def on_donation_save(sender, **kwargs): + """This function is called after a Donation is saved. + If the Donation was newly created and not anonymous, + award the user a contributor pin. + """ + if kwargs['created']: + donation = kwargs['instance'] + if not donation.is_anonymous and donation.user: + bio.badges.award_badge(bio.badges.CONTRIBUTOR_PIN, donation.user) + + +def on_link_save(sender, **kwargs): + """This function is called after a Link is saved. If the Link was newly + created, award the user a link pin. + """ + if kwargs['created']: + link = kwargs['instance'] + bio.badges.award_badge(bio.badges.LINK_PIN, link.user) + + +def on_download_save(sender, **kwargs): + """This function is called after a Download is saved. If the Download was + newly created, award the user a download pin. + """ + if kwargs['created']: + download = kwargs['instance'] + bio.badges.award_badge(bio.badges.DOWNLOAD_PIN, download.user) + + +def on_story_save(sender, **kwargs): + """This function is called after a Story is saved. If the Story was + newly created, award the user a news pin. + """ + if kwargs['created']: + story = kwargs['instance'] + bio.badges.award_badge(bio.badges.NEWS_PIN, story.submitter) + + post_save.connect(on_user_save, sender=User) +post_save.connect(on_donation_save, sender=Donation) +post_save.connect(on_link_save, sender=Link) +post_save.connect(on_download_save, sender=Download) +post_save.connect(on_story_save, sender=Story)
--- a/gpp/bio/views.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/bio/views.py Sat May 01 21:53:59 2010 +0000 @@ -21,6 +21,7 @@ from bio.models import UserProfile from bio.models import UserProfileFlag +from bio.models import BadgeOwnership from bio.forms import UploadAvatarForm from bio.forms import EditUserForm from bio.forms import EditUserProfileForm @@ -70,12 +71,15 @@ @login_required def my_profile(request): profile = request.user.get_profile() + badge_collection = BadgeOwnership.objects.filter( + profile=profile).select_related("badge") return render_to_response('bio/view_profile.html', { 'subject': request.user, 'profile': profile, 'hide_email': False, 'this_is_me': True, + 'badge_collection': badge_collection, }, context_instance = RequestContext(request)) @@ -89,15 +93,17 @@ return HttpResponseRedirect(reverse('bio.views.my_profile')) profile = user.get_profile() + hide_email = profile.hide_email - # work around MySQL's handling of Boolean - hide_email = bool(profile.hide_email) + badge_collection = BadgeOwnership.objects.filter( + profile=profile).select_related("badge") return render_to_response('bio/view_profile.html', { 'subject': user, 'profile': profile, 'hide_email': hide_email, 'this_is_me': False, + 'badge_collection': badge_collection, }, context_instance = RequestContext(request))
--- a/gpp/comments/admin.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/comments/admin.py Sat May 01 21:53:59 2010 +0000 @@ -4,6 +4,8 @@ from django.contrib import admin from comments.models import Comment from comments.models import CommentFlag +import bio.badges + class CommentAdmin(admin.ModelAdmin): fieldsets = ( @@ -25,8 +27,21 @@ search_fields = ('comment', 'user__username', 'ip_address') raw_id_fields = ('user', 'content_type') + class CommentFlagAdmin(admin.ModelAdmin): list_display = ('__unicode__', 'flag_date', 'get_comment_url') + actions = ('accept_flags', ) + + def accept_flags(self, request, qs): + """This admin action awards a security pin to the user who reported + the comment and then deletes the flagged comment object. + """ + for flag in qs: + bio.badges.award_badge(bio.badges.SECURITY_PIN, flag.user) + flag.delete() + + accept_flags.short_description = "Accept selected comment flags" + admin.site.register(Comment, CommentAdmin) admin.site.register(CommentFlag, CommentFlagAdmin)
--- a/gpp/core/templatetags/custom_admin_tags.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/core/templatetags/custom_admin_tags.py Sat May 01 21:53:59 2010 +0000 @@ -6,11 +6,11 @@ from bio.models import UserProfileFlag from comments.models import CommentFlag -from downloads.models import Download +from downloads.models import PendingDownload from forums.models import FlaggedPost from gcalendar.models import Event from news.models import PendingStory -from weblinks.models import Link, FlaggedLink +from weblinks.models import PendingLink, FlaggedLink from shoutbox.models import ShoutFlag @@ -25,14 +25,14 @@ """ flagged_profiles = UserProfileFlag.objects.count() flagged_comments = CommentFlag.objects.count() - new_downloads = Download.objects.filter(is_public=False).count() + new_downloads = PendingDownload.objects.count() flagged_posts = FlaggedPost.objects.count() event_requests = Event.objects.filter( Q(status=Event.NEW) | Q(status=Event.EDIT_REQ) | Q(status=Event.DEL_REQ)).count() new_stories = PendingStory.objects.count() - new_links = Link.objects.filter(is_public=False).count() + new_links = PendingLink.objects.count() broken_links = FlaggedLink.objects.count() flagged_shouts = ShoutFlag.objects.count()
--- a/gpp/downloads/admin.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/downloads/admin.py Sat May 01 21:53:59 2010 +0000 @@ -1,9 +1,12 @@ """ This file contains the automatic admin site definitions for the downloads models. """ +import datetime + from django.contrib import admin from django.conf import settings +from downloads.models import PendingDownload from downloads.models import Download from downloads.models import Category from downloads.models import AllowedExtension @@ -15,6 +18,39 @@ readonly_fields = ('count', ) +class PendingDownloadAdmin(admin.ModelAdmin): + exclude = ('html', ) + list_display = ('title', 'user', 'category', 'date_added', 'ip_address', 'size') + ordering = ('date_added', ) + raw_id_fields = ('user', ) + + actions = ('approve_downloads', ) + + def approve_downloads(self, request, qs): + for pending_dl in qs: + dl = Download( + title=pending_dl.title, + category=pending_dl.category, + description=pending_dl.description, + html=pending_dl.html, + file=pending_dl.file, + user=pending_dl.user, + date_added=datetime.datetime.now(), + ip_address=pending_dl.ip_address, + hits=0, + average_score=0.0, + total_votes=0, + is_public=True) + dl.save() + + # If we don't do this, the actual file will be deleted when + # the pending download is deleted. + pending_dl.file = None + pending_dl.delete() + + approve_downloads.short_description = "Approve selected downloads" + + class DownloadAdmin(admin.ModelAdmin): exclude = ('html', ) list_display = ('title', 'user', 'category', 'date_added', 'ip_address', @@ -33,6 +69,7 @@ date_hierarchy = 'vote_date' +admin.site.register(PendingDownload, PendingDownloadAdmin) admin.site.register(Download, DownloadAdmin) admin.site.register(Category, CategoryAdmin) admin.site.register(AllowedExtension)
--- a/gpp/downloads/forms.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/downloads/forms.py Sat May 01 21:53:59 2010 +0000 @@ -6,7 +6,7 @@ from django import forms from django.conf import settings -from downloads.models import Download +from downloads.models import PendingDownload from downloads.models import AllowedExtension @@ -34,7 +34,7 @@ raise forms.ValidationError('The file extension "%s" is not allowed.' % ext) class Meta: - model = Download + model = PendingDownload fields = ('title', 'category', 'description', 'file') class Media:
--- a/gpp/downloads/models.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/downloads/models.py Sat May 01 21:53:59 2010 +0000 @@ -45,16 +45,44 @@ is_public=True).select_related() -class Download(models.Model): - """Model to represent a download.""" +class DownloadBase(models.Model): + """Abstract model to collect common download fields.""" title = models.CharField(max_length=128) category = models.ForeignKey(Category) description = models.TextField() html = models.TextField(blank=True) file = models.FileField(upload_to=download_path) user = models.ForeignKey(User) - date_added = models.DateTimeField(auto_now_add=True) + date_added = models.DateTimeField() ip_address = models.IPAddressField('IP Address') + + class Meta: + abstract = True + + def size(self): + return filesizeformat(self.file.size) + + +class PendingDownload(DownloadBase): + """This model represents pending downloads created by users. These pending + downloads must be approved by an admin before they turn into "real" + Downloads and are visible on site. + """ + class Meta: + ordering = ('date_added', ) + + def __unicode__(self): + return self.title + + def save(self, *args, **kwargs): + if not self.pk: + self.date_added = datetime.datetime.now() + self.html = site_markup(self.description) + super(PendingDownload, self).save(*args, **kwargs) + + +class Download(DownloadBase): + """Model to represent a download.""" hits = models.IntegerField(default=0) average_score = models.FloatField(default=0.0) total_votes = models.IntegerField(default=0) @@ -83,9 +111,6 @@ self.average_score = total_score / self.total_votes return self.average_score - def size(self): - return filesizeformat(self.file.size) - class AllowedExtensionManager(models.Manager): def get_extension_list(self):
--- a/gpp/downloads/views.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/downloads/views.py Sat May 01 21:53:59 2010 +0000 @@ -161,7 +161,6 @@ dl = form.save(commit=False) dl.user = request.user dl.ip_address = request.META.get('REMOTE_ADDR', None) - dl.is_public = False dl.save() email_admins('New download for approval', """Hello,
--- a/gpp/forums/admin.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/forums/admin.py Sat May 01 21:53:59 2010 +0000 @@ -9,6 +9,7 @@ from forums.models import Post from forums.models import FlaggedPost from forums.models import TopicLastVisit +import bio.badges class CategoryAdmin(admin.ModelAdmin): @@ -47,6 +48,17 @@ class FlaggedPostAdmin(admin.ModelAdmin): list_display = ('__unicode__', 'flag_date', 'get_post_url') + actions = ('accept_flags', ) + + def accept_flags(self, request, qs): + """This admin action awards a security pin to the user who reported + the post and then deletes the flagged post object. + """ + for flag in qs: + bio.badges.award_badge(bio.badges.SECURITY_PIN, flag.user) + flag.delete() + + accept_flags.short_description = "Accept selected flagged posts" class TopicLastVisitAdmin(admin.ModelAdmin):
--- a/gpp/gcalendar/admin.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/gcalendar/admin.py Sat May 01 21:53:59 2010 +0000 @@ -7,6 +7,7 @@ from gcalendar.models import Event from gcalendar.admin_views import google_sync +import bio.badges class EventAdmin(admin.ModelAdmin): @@ -47,6 +48,9 @@ event.save() count += 1 + if event.status == Event.NEW_APRV: + bio.badges.award_badge(bio.badges.CALENDAR_PIN, event.user) + msg = "1 event was" if count == 1 else "%d events were" % count msg += " approved." self.message_user(request, msg)
--- a/gpp/news/admin.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/news/admin.py Sat May 01 21:53:59 2010 +0000 @@ -1,32 +1,52 @@ """ This file contains the automatic admin site definitions for the News models. """ +import datetime from django.contrib import admin from django.conf import settings +from django.core.cache import cache from news.models import PendingStory from news.models import Story from news.models import Category class PendingStoryAdmin(admin.ModelAdmin): - list_display = ('title', 'date_submitted', 'submitter') - list_filter = ('date_submitted', ) - search_fields = ('title', 'short_text', 'long_text') - date_hierarchy = 'date_submitted' + list_display = ('title', 'date_submitted', 'submitter') + list_filter = ('date_submitted', ) + search_fields = ('title', 'short_text', 'long_text') + date_hierarchy = 'date_submitted' + actions = ('approve_story', ) - class Media: - js = settings.GPP_THIRD_PARTY_JS['tiny_mce'] + def approve_story(self, request, qs): + for pending_story in qs: + story = Story( + title=pending_story.title, + submitter=pending_story.submitter, + category=pending_story.category, + short_text=pending_story.short_text, + long_text=pending_story.long_text, + date_submitted=datetime.datetime.now(), + allow_comments=pending_story.allow_comments, + tags=pending_story.tags) + story.save() + pending_story.delete() + cache.delete('home_news') + + approve_story.short_description = "Approve selected pending stories" + + class Media: + js = settings.GPP_THIRD_PARTY_JS['tiny_mce'] class StoryAdmin(admin.ModelAdmin): - list_display = ('title', 'date_published', 'submitter', 'category') - list_filter = ('date_published', 'category') - search_fields = ('title', 'short_text', 'long_text') - date_hierarchy = 'date_published' + list_display = ('title', 'date_submitted', 'submitter', 'category') + list_filter = ('date_submitted', 'category') + search_fields = ('title', 'short_text', 'long_text') + date_hierarchy = 'date_submitted' - class Media: - js = settings.GPP_THIRD_PARTY_JS['tiny_mce'] + class Media: + js = settings.GPP_THIRD_PARTY_JS['tiny_mce'] admin.site.register(Category)
--- a/gpp/news/feeds.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/news/feeds.py Sat May 01 21:53:59 2010 +0000 @@ -22,7 +22,7 @@ return copyright_str() def items(self): - return Story.objects.order_by('-date_published')[:5] + return Story.objects.order_by('-date_submitted')[:5] def item_title(self, item): return item.title @@ -34,7 +34,7 @@ return get_full_name(item.submitter) def item_pubdate(self, item): - return item.date_published + return item.date_submitted def item_categories(self, item): return (item.category.title, )
--- a/gpp/news/models.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/news/models.py Sat May 01 21:53:59 2010 +0000 @@ -25,32 +25,29 @@ ordering = ('title', ) -class PendingStory(models.Model): - """Stories submitted by users are held pending admin approval""" +class StoryBase(models.Model): + """Abstract model to collect common fields.""" title = models.CharField(max_length=255) submitter = models.ForeignKey(User) category = models.ForeignKey(Category) short_text = models.TextField() long_text = models.TextField(blank=True) - date_submitted = models.DateTimeField(auto_now_add=True, db_index=True) + date_submitted = models.DateTimeField(db_index=True) allow_comments = models.BooleanField(default=True) - approved = models.BooleanField(default=False) tags = TagField() + class Meta: + abstract = True + + +class PendingStory(StoryBase): + """Stories submitted by users are held pending admin approval""" + def save(self, *args, **kwargs): - if self.approved: - Story.objects.create(title=self.title, - submitter=self.submitter, - category=self.category, - short_text=self.short_text, - long_text=self.long_text, - allow_comments=self.allow_comments, - date_published=datetime.datetime.now(), - tags=self.tags) - self.delete() - cache.delete('home_news') - else: - super(PendingStory, self).save(*args, **kwargs) + if not self.pk: + self.date_submitted = datetime.datetime.now() + + super(PendingStory, self).save(*args, **kwargs) def __unicode__(self): return self.title @@ -60,16 +57,8 @@ verbose_name_plural = 'Pending Stories' -class Story(models.Model): +class Story(StoryBase): """Model for news stories""" - title = models.CharField(max_length=255) - submitter = models.ForeignKey(User) - category = models.ForeignKey(Category) - short_text = models.TextField() - long_text = models.TextField(blank=True) - allow_comments = models.BooleanField(default=True) - date_published = models.DateTimeField(db_index=True) - tags = TagField() @models.permalink def get_absolute_url(self): @@ -79,13 +68,13 @@ return self.title class Meta: - ordering = ('-date_published', ) + ordering = ('-date_submitted', ) verbose_name_plural = 'Stories' def can_comment_on(self): now = datetime.datetime.now() - delta = now - self.date_published - return delta.days < 30 + delta = now - self.date_submitted + return self.allow_comments and delta.days < 30 def save(self, *args, **kwargs): super(Story, self).save(*args, **kwargs)
--- a/gpp/news/views.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/news/views.py Sat May 01 21:53:59 2010 +0000 @@ -57,7 +57,7 @@ ####################################################################### def archive_index(request): - dates = Story.objects.dates('date_published', 'month', order='DESC') + dates = Story.objects.dates('date_submitted', 'month', order='DESC') return render_to_response('news/archive_index.html', { 'title': 'News Archive', 'dates': dates, @@ -68,7 +68,7 @@ ####################################################################### def archive(request, year, month, page=1): - stories = Story.objects.filter(date_published__year=year, date_published__month=month) + stories = Story.objects.filter(date_submitted__year=year, date_submitted__month=month) paginator = create_paginator(stories) try: the_page = paginator.page(int(page)) @@ -144,7 +144,7 @@ stories = stories.filter( Q(title__icontains=query_text) | Q(short_text__icontains=query_text) | - Q(long_text__icontains=query_text)).order_by('-date_published') + Q(long_text__icontains=query_text)).order_by('-date_submitted') paginator = create_paginator(stories) try:
--- a/gpp/templates/bio/view_profile.html Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/templates/bio/view_profile.html Sat May 01 21:53:59 2010 +0000 @@ -55,6 +55,16 @@ {% endif %} <tr><th>Elsewhere</th><td>{% elsewhere_links subject %}</td></tr> <tr><th>Time Zone</th><td>{{ profile.time_zone }}</td></tr> + <tr><th>Badges</th><td> + {% if badge_collection %} + <table id="badge_summary"> + <tr><th>Badge</th><th>Qty.</th><th>Name</th><th>Description</th></tr> + {% for bo in badge_collection %} + <tr><td>{{ bo.badge.html|safe }}</td><td>{{ bo.count }}</td><td>{{ bo.badge.name }}</td><td>{{ bo.badge.description }}</td></tr> + {% endfor %} + </table> + {% endif %} + </td></tr> </table> </div> {% if this_is_me %}
--- a/gpp/templates/core/admin_dashboard.html Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/templates/core/admin_dashboard.html Sat May 01 21:53:59 2010 +0000 @@ -20,10 +20,10 @@ <li><a href="/admin/news/pendingstory/">News</a>: {{ new_stories }}</li> {% endif %} {% if new_downloads %} -<li><a href="/admin/downloads/download/">Downloads</a>: {{ new_downloads }}</li> +<li><a href="/admin/downloads/pendingdownload/">Downloads</a>: {{ new_downloads }}</li> {% endif %} {% if new_links %} -<li><a href="/admin/weblinks/link/">New Links</a>: {{ new_links }}</li> +<li><a href="/admin/weblinks/pendinglink/">New Links</a>: {{ new_links }}</li> {% endif %} {% if broken_links %} <li><a href="/admin/weblinks/flaggedlink/">Broken Links</a>: {{ broken_links }}</li>
--- a/gpp/templates/forums/display_post.html Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/templates/forums/display_post.html Sat May 01 21:53:59 2010 +0000 @@ -10,6 +10,9 @@ {% if post.user_profile.location %} Location: {{ post.user_profile.location }}<br /> {% endif %} + {% for bo in post.user_profile.badge_ownership %} + <img src="{{ bo.badge.image.url }}" alt="{{ bo.badge_count_str }}" title="{{ bo.badge_count_str }}" /> + {% endfor %} {% if user.is_authenticated %} <p> <a href="{% url messages-compose_to post.user.username %}">
--- a/gpp/templates/news/category_index.html Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/templates/news/category_index.html Sat May 01 21:53:59 2010 +0000 @@ -20,7 +20,7 @@ <ul> {% for story in story_set %} <li><a href="{{ story.get_absolute_url }}">{{ story.title }}</a> - - {{ story.date_published|date:"F d, Y" }}</li> + - {{ story.date_submitted|date:"F d, Y" }}</li> {% endfor %} </ul> {% else %}
--- a/gpp/templates/news/story.html Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/templates/news/story.html Sat May 01 21:53:59 2010 +0000 @@ -15,7 +15,7 @@ {% block news_content %} <h3>{{ story.title }}</h3> <div class="news-details"> - Submitted by {{ story.submitter.username }} on {{ story.date_published|date:"F d, Y" }}. + Submitted by {{ story.submitter.username }} on {{ story.date_submitted|date:"F d, Y" }}. </div> <hr /> <div class="news-content">
--- a/gpp/templates/news/story_summary.html Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/templates/news/story_summary.html Sat May 01 21:53:59 2010 +0000 @@ -7,7 +7,7 @@ <h4><a href="{{ story.get_absolute_url }}">{{ story.title }}</a></h4> {% endif %} <div class="news-details"> - Submitted by {{ story.submitter.username }} on {{ story.date_published|date:"F d, Y" }}. + Submitted by {{ story.submitter.username }} on {{ story.date_submitted|date:"F d, Y" }}. </div> <a href="{% url news.views.category category=story.category.id page=1 %}"> <img src="{{ story.category.icon.url }}" alt="{{ story.category.title }}" title="{{ story.category.title }}"
--- a/gpp/weblinks/admin.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/weblinks/admin.py Sat May 01 21:53:59 2010 +0000 @@ -1,7 +1,9 @@ """This file contains the automatic admin site definitions for the weblinks models""" +import datetime from django.contrib import admin from weblinks.models import Category +from weblinks.models import PendingLink from weblinks.models import Link from weblinks.models import FlaggedLink @@ -11,6 +13,27 @@ readonly_fields = ('count', ) +class PendingLinkAdmin(admin.ModelAdmin): + list_display = ('title', 'url', 'user', 'category', 'date_added') + raw_id_fields = ('user', ) + actions = ('approve_links', ) + + def approve_links(self, request, qs): + for pending_link in qs: + link = Link(category=pending_link.category, + title=pending_link.title, + url=pending_link.url, + description=pending_link.description, + user=pending_link.user, + date_added=datetime.datetime.now(), + hits=0, + is_public=True) + link.save() + pending_link.delete() + + approve_links.short_description = "Approve selected links" + + class LinkAdmin(admin.ModelAdmin): list_display = ('title', 'url', 'category', 'date_added', 'hits', 'is_public') list_filter = ('date_added', 'is_public', 'category') @@ -27,5 +50,6 @@ raw_id_fields = ('user', ) admin.site.register(Category, CategoryAdmin) +admin.site.register(PendingLink, PendingLinkAdmin) admin.site.register(Link, LinkAdmin) admin.site.register(FlaggedLink, FlaggedLinkAdmin)
--- a/gpp/weblinks/forms.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/weblinks/forms.py Sat May 01 21:53:59 2010 +0000 @@ -3,7 +3,7 @@ """ from django import forms -from weblinks.models import Link +from weblinks.models import PendingLink, Link class SearchForm(forms.Form): '''Weblinks search form''' @@ -26,5 +26,5 @@ raise forms.ValidationError('That link already exists in our database.') class Meta: - model = Link - exclude = ('user', 'date_added', 'hits', 'is_public') + model = PendingLink + exclude = ('user', 'date_added')
--- a/gpp/weblinks/models.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/weblinks/models.py Sat May 01 21:53:59 2010 +0000 @@ -1,6 +1,8 @@ """ This module contains the models for the weblinks application. """ +import datetime + from django.db import models from django.contrib import auth @@ -26,14 +28,21 @@ is_public=True).select_related() -class Link(models.Model): - """Model to represent a web link""" +class LinkBase(models.Model): + """Abstract model to aggregate common fields of a web link.""" category = models.ForeignKey(Category) title = models.CharField(max_length=128) url = models.URLField(verify_exists=False, db_index=True) description = models.TextField(blank=True) user = models.ForeignKey(auth.models.User) - date_added = models.DateField(auto_now_add=True) + date_added = models.DateField() + + class Meta: + abstract = True + + +class Link(LinkBase): + """Model to represent a web link""" hits = models.IntegerField(default=0) is_public = models.BooleanField(default=False, db_index=True) @@ -52,6 +61,22 @@ return ('weblinks-link_detail', [str(self.id)]) +class PendingLink(LinkBase): + """This model represents links that users submit. They must be approved by + an admin before they become visible on the site. + """ + class Meta: + ordering = ('date_added', ) + + def __unicode__(self): + return self.title + + def save(self, *args, **kwargs): + if not self.pk: + self.date_added = datetime.datetime.now() + super(PendingLink, self).save(*args, **kwargs) + + class FlaggedLinkManager(models.Manager): def create(self, link, user):
--- a/gpp/weblinks/views.py Wed Apr 28 03:00:31 2010 +0000 +++ b/gpp/weblinks/views.py Sat May 01 21:53:59 2010 +0000 @@ -73,7 +73,6 @@ if add_form.is_valid(): new_link = add_form.save(commit=False) new_link.user = request.user - new_link.is_public = False new_link.save() email_admins('New link for approval', """Hello,
--- a/media/css/base.css Wed Apr 28 03:00:31 2010 +0000 +++ b/media/css/base.css Sat May 01 21:53:59 2010 +0000 @@ -359,3 +359,9 @@ #forums-post-list dd.even { background-color:#e5ecf9; } +#badge_summary { + border-collapse:collapse; +} +#badge_summary th, #badge_summary td { + border: 1px solid teal; +}