Skip to content
Browse files

Tweaked front page layout; showing top rated badges on badge index pa…

…ge; add badge rating on thumb element; notifications on badge and award comments; TODO tweaks
  • Loading branch information...
1 parent f977ed8 commit f46e58e1af432d6c5aec8259b359cdd7b2c93aae @lmorchard committed Oct 5, 2010
View
65 TODO.md
@@ -7,15 +7,23 @@
## v0.1
-* Standardize thumb/full includes for Badges and Profiles
- * Possibly add rollover popups with further details on each
+* Nomination by claim code
+ * Single-use: Changes the nomination's awardee to the code bearer
+ * Multi-use: Generates a cloned nomination with code bearer as awardee
-* Enable notifications on badge and badge award comments
+* Standardize thumb/full includes for Badges and Profiles
+ * Even on front page recent awards list
* Show only recent awards on badge detail page, link to paginated award list for more
* Accept flags / alerts to spam / inapproriate content
+* Delegation of nomination approval
+ * Allow badge creator to flag others as co-creators / nomination approvers
+
+* Allow transfer of badge ownership
+ * Another user becomes the new creator
+
* Trophy case: Flag awards for highlighting on profile and social apps
* Social sharing
@@ -30,35 +38,9 @@
* L10N for badge content... later?
* Delaying it could result in duplicate badges with the same intent in different locales
-* Nomination by claim code
- * Single-use: Changes the nomination's awardee to the code bearer
- * Multi-use: Generates a cloned nomination with code bearer as awardee
-
-* Aspirations
- * Badge creator can build a short questionaire of information necessary for aspiration
- * Human readable (eg. how many widgets did you frob?) or machine readable (eg. bugzilla email addr)
- * User registers interest in claiming a badge, creates aspiration
- * Fills out questionaire, if necessary
- * Aspiration may be made public or private by aspirant
- * Badge creator is notified
- * Scripts and API
- * API offers a list of aspirants for a badge, with questionaire data
- * API accepts and offers a private JSON block up to 1k for per-aspirant record keeping
- * Badge creator and delegates can see, but not the aspirant
- * Useful for tracking scores, progress, etc detected by scanning script
- * API accepts aspirant-only human-readable progress update message
- * API offers option to notify aspirant on update
-
* Meta-badges for auto-nomination
* Attach a list of badges to a badge
* Any user who has all of them gets auto-nominated for the meta-badge
- * Anyone who registers an aspiration for the meta-badge is automatically registered as an aspirant to all the subordinate badges
-
-* Delegation of nomination approval
- * Allow badge creator to flag others as co-creators / nomination approvers
-
-* Allow transfer of badge ownership
- * Another user becomes the new creator
* HTML / markdown descriptions using jsocol's bleach
@@ -68,9 +50,6 @@
* List of badges authored by auth'd user in API
-* Register interest in claiming a badge
- * Produces a list in the API for scripts looking to track achievement progress
-
* API using valet keys and selective permissions
* Valet key = HTTP basic auth user/pass
* Simpler than OAuth
@@ -79,14 +58,30 @@
* Per-valet key logging and reports?
* Should allow for simple external services that track conditions and trigger nominations
-* Reduce SQL queries
-
-* Invitation-only at launch?
+* Invitation-only mode at launch?
* A badge award constitutes an invite
* auto-complete on emails somehow too?
## v0.2
+* Reduce SQL queries
+
+* Aspirations
+ * Badge creator can build a short questionaire of information necessary for aspiration
+ * Human readable (eg. how many widgets did you frob?) or machine readable (eg. bugzilla email addr)
+ * User registers interest in claiming a badge, creates aspiration
+ * Fills out questionaire, if necessary
+ * Aspiration may be made public or private by aspirant
+ * Badge creator is notified
+ * Scripts and API
+ * API offers a list of aspirants for a badge, with questionaire data
+ * API accepts and offers a private JSON block up to 1k for per-aspirant record keeping
+ * Badge creator and delegates can see, but not the aspirant
+ * Useful for tracking scores, progress, etc detected by scanning script
+ * API accepts aspirant-only human-readable progress update message
+ * API offers option to notify aspirant on update
+ * Anyone who registers an aspiration for a meta-badge is automatically registered as an aspirant to all the subordinate badges
+
* AJAXification
* Sprinkle in more AJAX / hidden iframe / facebox magic for in-place submissions
* Lightweight interactions
View
12 apps/badges/management.py
@@ -61,5 +61,17 @@ def create_notice_types(app, created_models, verbosity, **kwargs):
_("a badge award has been ignored")
)
+ notification.create_notice_type(
+ "badge_comment",
+ _("Badge Comment"),
+ _("a comment has been posted to a badge")
+ )
+
+ notification.create_notice_type(
+ "badge_award_comment",
+ _("Badge Award Comment"),
+ _("a comment has been posted to a badge award")
+ )
+
signals.post_syncdb.connect(create_notice_types, sender=notification)
View
37 apps/badges/models.py
@@ -20,6 +20,8 @@
from tagging.fields import TagField
from tagging.models import Tag
+from threadedcomments.models import ThreadedComment
+
from notification import models as notification
from mailer import send_mail
@@ -560,16 +562,27 @@ def show(self):
self.hidden = False
self.save()
+
+# Fire off notifications on posting of new comments.
+def new_comment(sender, instance, **kwargs):
+ obj = instance.content_object
+ if isinstance(obj, Badge):
+ if notification:
+ recipients = dict((x.username, x) for x in reversed((
+ # TODO: Should send this to more people?
+ obj.creator,
+ ))).values()
+ notification.send(recipients, "badge_comment",
+ {"commenter": instance.user, "badge": obj, "comment": instance})
+ if isinstance(obj, BadgeAward):
+ if notification:
+ recipients = dict((x.username, x) for x in reversed((
+ # TODO: Should send this to more people?
+ obj.nomination.nominator,
+ obj.awardee.user,
+ obj.nomination.approved_by
+ ))).values()
+ notification.send(recipients, "badge_award_comment",
+ {"commenter": instance.user, "award": obj, "comment": instance})
-# handle notification of new comments
-# from threadedcomments.models import ThreadedComment
-#
-#
-# def new_comment(sender, instance, **kwargs):
-# post = instance.content_object
-# if isinstance(post, Badge):
-# if notification:
-# notification.send([post.author], "badge_comment",
-# {"user": instance.user, "post": post, "comment": instance})
-#
-# models.signals.post_save.connect(new_comment, sender=ThreadedComment)
+models.signals.post_save.connect(new_comment, sender=ThreadedComment)
View
3 apps/badges/templates/badges/badge_detail.html
@@ -10,6 +10,7 @@
{% load humanize i18n %}
{% load timezone_filters %}
{% load comments_tag %}
+{% load flag_tags %}
{% block extra_head %}
<link rel="alternate" type="application/atom+xml" title="{% trans "Recently Claimed Badges" %}" href="{% url badge_feed_badgeawards badge.slug %}" />
@@ -106,7 +107,7 @@
</div>
{% endif %}
- <div class="section comments">
+ <div id="comments" class="section comments">
<h3>Comments</h3>
<div>{% comments badge %}</div>
</div>
View
43 apps/badges/templates/badges/badge_index.html
@@ -0,0 +1,43 @@
+{% extends "site_base.html" %}
+
+{% load i18n %}
+{% load badge_tags %}
+{% load tagging_tags %}
+
+{% block head_title %}{% trans "Badges" %}{% endblock %}
+
+{% block body %}
+ <div class="badge_list">
+
+ <h1>{% trans "Badges" %}</h1>
+
+ <ul class="actions">
+ <li><a class="create" href="{% url badge_browse %}">Browse all badges</a></li>
+ <li><a class="create" href="{% url create_badge %}">Create a new badge</a></li>
+ </ul>
+
+ {% tag_cloud_for_model badges.Badge as badge_tags with steps=9 min_count=1 distribution=log %}
+ {% if badge_tags %}
+ <div>
+ <h2>Tags</h2>
+ <ul class="tags tag-cloud clearfix">
+ {% for tag in badge_tags|dictsortreversed:"count" %}
+ <li class="size-{{ tag.font_size }}">
+ <a href="{% url badge_tag tag=tag.name %}" title="{{ tag.count }}">{{ tag.name }}</a>
+ </li>
+ {% endfor %}
+ </ul>
+ </div>
+ {% endif %}
+
+ <div>
+ <h2>Top rated badges</h2>
+ <ul class="badges">
+ {% for badge, score in badge_list %}
+ <li>{% include "badges/elements/badge_display_thumb.html" %}</li>
+ {% endfor %}
+ </ul>
+ </div>
+
+ </div>
+{% endblock %}
View
22 apps/badges/templates/badges/badge_list.html
@@ -8,7 +8,7 @@
{% if tag %}
{% blocktrans %}Badges tagged "{{ tag }}"{% endblocktrans %}
{% else %}
- {% trans "Badges" %}
+ {% trans "All Badges" %}
{% endif %}
{% endblock %}
@@ -17,24 +17,8 @@
{% if tag %}
<h1>{% blocktrans %}Badges tagged "{{ tag }}"{% endblocktrans %}</h1>
{% else %}
-
- <h1>{% trans "Badges" %}</h1>
-
- <p><a class="create" href="{% url create_badge %}">Create a new badge</a></p>
-
- {% tag_cloud_for_model badges.Badge as badge_tags with steps=9 min_count=1 distribution=log %}
-
- {% if badge_tags %}
- <ul class="tags tag-cloud clearfix">
- {% for tag in badge_tags|dictsortreversed:"count" %}
- <li class="size-{{ tag.font_size }}">
- <a href="{% url badge_tag tag=tag.name %}" title="{{ tag.count }}">{{ tag.name }}</a>
- </li>
- {% endfor %}
- </ul>
- {% endif %}
-
- {% endif %}
+ <h1>{% trans "Browse All Badges" %}</h1>
+ {% endif %}
<ul class="badges">
{% for badge in badge_list %}
View
5 apps/badges/templates/badges/elements/badge_display_full.html
@@ -25,7 +25,7 @@
{% vote_by_user user on badge as vote %}
<form action="{% url badge_vote slug=badge.slug direction="down" %}" method="POST">
{% csrf_token %}
- <input class="button" type="submit" value="{% trans "--" %}"
+ <input class="button" type="submit" value="{% trans "-1" %}"
{% if vote and vote.is_downvote %}disabled="disabled"{% endif %} />
</form>
<form action="{% url badge_vote slug=badge.slug direction="clear" %}" method="POST">
@@ -35,7 +35,7 @@
</form>
<form action="{% url badge_vote slug=badge.slug direction="up" %}" method="POST">
{% csrf_token %}
- <input class="button" type="submit" value="{% trans "++" %}"
+ <input class="button" type="submit" value="{% trans "+1" %}"
{% if vote and vote.is_upvote %}disabled="disabled"{% endif %} />
</form>
</span>
@@ -77,3 +77,4 @@
</div>
</div>
+
View
9 apps/badges/templates/badges/elements/badge_display_thumb.html
@@ -1,8 +1,13 @@
{% load i18n %}
{% load badge_tags %}
-<li class="badge_thumb">
+{% load voting_tags %}
+<div class="badge_thumb">
<a href="{{badge.get_absolute_url}}"
class="name">{{badge.title}}</a>
<a href="{{badge.get_absolute_url}}"
class="icon">{% badge_image badge 80 %}</a>
-</li>
+ <div class="score">
+ {% score_for_object badge as score %}
+ <span>{{ score.score }}<!-- / {{ score.num_votes }} --></span>
+ </div>
+</div>
View
5 apps/badges/templates/notification/badge_award_comment/full.txt
@@ -0,0 +1,5 @@
+{% load i18n %}{% load account_tags %}{% load badge_tags %}{% user_display commenter as commenter_display %}{% awardee_display award.awardee as awardee_display %}{% url profile_detail username=commenter.username as commenter_url %}{% blocktrans with award.awardee.get_absolute_url as awardee_url and award.awardee.display as awardee_display and award.get_absolute_url as award_url and award.badge.title as award_title and comment.comment as comment_title %}{{ commenter_display }} has posted a comment to an award of the badge "{{ award_title }}" claimed by {{ awardee_display }}:
+
+{{ comment_title }}
+
+http://{{ current_site }}{{ award_url }}#comments{% endblocktrans %}
View
6 apps/badges/templates/notification/badge_award_comment/notice.html
@@ -0,0 +1,6 @@
+{% load i18n %}{% load account_tags %}{% load badge_tags %}
+{% user_display commenter as commenter_display %}
+{% awardee_display award.awardee as awardee_display %}
+{% url profile_detail username=commenter.username as commenter_url %}
+{% blocktrans with award.awardee.get_absolute_url as awardee_url and award.awardee.display as awardee_display and award.get_absolute_url as award_url and award.badge.title as award_title %}<a href="{{ commenter_url }}">{{ commenter_display }}</a> has posted <a href="{{ award_url }}#comments">a comment</a> to an award of the badge <a href="{{ award_url }}#comments">{{ award_title }}</a> claimed by <a href="{{ awardee_url }}">{{ awardee_display }}</a>.{% endblocktrans %}
+
View
5 apps/badges/templates/notification/badge_comment/full.txt
@@ -0,0 +1,5 @@
+{% load i18n %}{% load account_tags %}{% load badge_tags %}{% user_display user as profile_display %}{% url profile_detail username=user.username as profile_url %}{% blocktrans with badge.get_absolute_url as badge_url and badge.title as badge_title and comment.comment as comment_body%}{{ profile_display }} has posted a comment to the badge "{{ badge_title }}":
+
+{{ comment_body }}
+
+http://{{ current_site }}{{ badge_url }}#comments{% endblocktrans %}
View
4 apps/badges/templates/notification/badge_comment/notice.html
@@ -0,0 +1,4 @@
+{% load i18n %}{% load account_tags %}{% load badge_tags %}
+{% user_display commenter as commenter_display %}
+{% url profile_detail username=commenter.username as commenter_url %}
+{% blocktrans with badge.get_absolute_url as badge_url and badge.title as badge_title %}<a href="{{ commenter_url }}">{{ commenter_display }}</a> has posted <a href="{{ badge_url }}#comments">a comment</a> to the badge <a href="{{ badge_url }}#comments">{{ badge_title }}</a>.{% endblocktrans %}
View
6 apps/badges/urls.py
@@ -14,11 +14,13 @@
urlpatterns = patterns("badges.views",
- url(r'^$', object_list,
+ url(r'^$', 'index', name='badge_index'),
+
+ url(r'^all/$', object_list,
dict(queryset=Badge.objects.all(), template_object_name='badge',
template_name='badges/badge_list.html', paginate_by=25,
allow_empty=True),
- name='badge_index'),
+ name='badge_browse'),
url(r'^tag/(?P<tag>[^/]+)/$', tagged_object_list,
dict(queryset_or_model=Badge, paginate_by=25, allow_empty=True,
View
11 apps/badges/views.py
@@ -14,11 +14,14 @@
from django.core import validators
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User
+
from badges.models import Badge, BadgeNomination
from badges.models import BadgeAward, BadgeAwardee
from badges.models import badge_file_path
+
from badges.forms import BadgeForm, BadgeNominationForm
from badges.forms import BadgeNominationDecisionForm
+
from notification import models as notification
from django.core.exceptions import ObjectDoesNotExist
@@ -28,13 +31,15 @@
from avatar.templatetags.avatar_tags import avatar_url
from badges.templatetags.badge_tags import badge_url
+from voting.models import Vote
def index(request):
"""Browse badges"""
- badges = Badge.objects.all()
+ # TODO: This needs some heavy caching:
+ badge_list = Vote.objects.get_top(Badge, 25)
- return render_to_response('badges/index.html', {
- 'badges': badges
+ return render_to_response('badges/badge_index.html', {
+ 'badge_list': badge_list
}, context_instance=RequestContext(request))
#import pinax.apps.profiles.views
View
29 media/css/main.css
@@ -142,9 +142,9 @@ a.button:hover {
border: 2px solid #d0d0d0;
background: #d8d8d8;
}
-.badge .metadata table { width: 100%; margin: 1em 0 1em 0 }
-.badge .metadata table th { text-align: right; padding: 0 0.5em 0.75em 0 }
-.badge .metadata table td { text-align: left; padding: 0 0 0.75em 0 }
+.badge .metadata table { width: 100%; margin: 1em 1em 1em 1em }
+.badge .metadata table th { text-align: right; vertical-align: top; white-space: nowrap; padding: 0 0.5em 0.75em 0 }
+.badge .metadata table td { text-align: left; padding: 0 1em 0.75em 0 }
.badge .edit_badge { position: absolute; right: 5px; top: 2px; background: #fff; padding: 2px 5px; font-size: 0.9em; display: block }
@@ -185,20 +185,25 @@ a.button:hover {
.home .recent_awards { list-style-type: none; margin: 0; padding: 0;}
.home .recent_awards li { position: relative; text-align: center; margin: 0.5em; border: 2px solid #ccc; border-radius: 8px; -moz-border-radius: 8px; float: left; background: #fff;}
-.home .recent_awards li .when { display: block; background: #ccc; color: #000; font-size: 80%; text-align: center; font-weight: normal; }
+.home .recent_awards li .when { display: block; background: #ccc; color: #000; font-size: 90%; text-align: center; font-weight: bold; padding: 2px 0 2px 0 }
.home .recent_awards li .avatar { float: left; text-align: right; margin: 0.5em; }
.home .recent_awards li .avatar a { display: inline; }
-.home .recent_awards li .action { float: left; text-align: center; font-size: 115%; font-weight: bold; line-height: 64px; padding: 0 0.5em 0 0.5em; }
+.home .recent_awards li .action { float: left; text-align: center; font-size: 100%; font-weight: bold; margin: 1em 0 1em 0; padding: 0 0.5em 0 0.5em; }
.home .recent_awards li .badge { float: left; text-align: left; margin: 0.5em; }
.home .recent_awards li .badge a { display: inline; }
.badge_detail .pending_awards li { margin-bottom: 1.5em; }
.badge_list .badges { margin: 1em 0 0 0; padding: 0; text-align: center; }
-.badge_list .badges li { float: left; margin: 0.75em; list-style:none; border: 4px solid #ccc; text-align: center; -moz-border-radius: 8px; width: 125px }
-.badge_list .badges li .icon { display: block; height: 90px; padding: 0.75em 0.25em; background: #fff; }
-.badge_list .badges li .icon img { height: 80px; }
-.badge_list .badges li .name { display: block; font-weight: bold; padding: 0.25em; background: #ccc; height: 2.75em; overflow: hidden }
+
+.badge_list .badges li { float: left; list-style: none; }
+
+.badge_thumb { margin: 0.75em; border: 4px solid #ccc; text-align: center; -moz-border-radius: 8px; width: 110px }
+.badge_thumb .icon { display: block; padding: 5px; background: #fff; }
+.badge_thumb .icon img { height: 80px; }
+.badge_thumb .name { display: block; font-size: 85%; font-weight: bold; padding: 0.25em; background: #ccc; height: 2.75em; overflow: hidden }
+.badge_thumb .score { display: block; background: #ccc; overflow: hidden; padding: 4px 20% 2px 20%; }
+.badge_thumb .score span { display: block; background: #fff; font-size: 90%; border-radius: 8px; -moz-border-radius: 8px; -webkit-border-radius: 8px }
.pagination { clear: both }
@@ -207,7 +212,7 @@ a.button:hover {
.tag-cloud {
list-style: none;
margin: 0.75em;
- padding: 0.5em;
+ padding: 0.75em;
text-indent: 0;
border: 1px solid #ccc;
border-radius: 4px;
@@ -216,8 +221,8 @@ a.button:hover {
}
.tag-cloud li { display: inline; padding: 0 0.25em 0 0; white-space: nowrap; }
.tag-cloud li a { }
-.tag-cloud li.size-1 a { font-size: 90%; }
-.tag-cloud li.size-2 a { font-size: 100%; }
+.tag-cloud li.size-1 a { font-size: 100%; }
+.tag-cloud li.size-2 a { font-size: 110%; }
.tag-cloud li.size-3 a { font-size: 120%; }
.tag-cloud li.size-4 a { font-size: 130%; }
.tag-cloud li.size-5 a { font-size: 140%; }
View
1 settings.py
@@ -195,6 +195,7 @@
"debug_toolbar",
"tagging_ext",
"voting",
+ "flag",
"piston",
# Pinax
View
4 templates/homepage.html
@@ -53,7 +53,6 @@
<ul class="recent_awards">
{% recent_badge_awards as recent_awards %}
{% for award in recent_awards %}
- {% cache 500 homepage_recent_award award.id %}
<li class="clearfix">
<div class="when"><div>
@@ -64,14 +63,13 @@
<a href="{{ award.claimed_by.get_absolute_url }}" class="photo" title="{{ award.claimed_by }}"><img src="{% avatar_url award.claimed_by 64 %}" alt="Photo of {{ award.claimed_by }}" class="photo" /></a>
</div>
- <div class="action"><a href="{{ award.get_absolute_url }}">claimed</a></div>
+ <div class="action">{{ award.claimed_by }}<br /><a href="{{ award.get_absolute_url }}">claimed the badge</a><br />{{ award.badge.title }}</div>
<div class="badge">
<a href="{{ award.badge.get_absolute_url }}" class="icon" title="{{ award.badge.title }}">{% badge_image award.badge 64 %}</a>
</div>
</li>
- {% endcache %}
{% empty %}
<p>None, yet.</p>
{% endfor %}
View
2 templates/site_base.html
@@ -69,7 +69,7 @@
<a href="http://decafbad.com/2010/07/badger-article/">What's the big idea?</a> |
<a href="http://github.com/lmorchard/badger/issues">Report an issue.</a> |
<a href="http://github.com/lmorchard/badger">Fork me.</a> |
- <a href="http://www.weebls-stuff.com/songs/badgers/">MUSHROOM MUSHROOM.</a>
+ <a href="http://www.weebls-stuff.com/songs/badgers/" title="Yes, I went there.">MUSHROOM MUSHROOM.</a>
{% if False %}
{% trans "&copy; 2010 Badgers" %}
- <a href="{% url about %}">{% trans "About" %}</a>

0 comments on commit f46e58e

Please sign in to comment.
Something went wrong with that request. Please try again.