Merge branch 'production'
authorMatthijs Kooijman <matthijs@stdin.nl>
Sat, 31 Jan 2009 13:27:02 +0000 (14:27 +0100)
committerMatthijs Kooijman <matthijs@stdin.nl>
Sat, 31 Jan 2009 13:27:02 +0000 (14:27 +0100)
* production:
  Reconnect the influence_saved notify handler.

16 files changed:
influences/admin.py
influences/forms.py
influences/models.py
influences/notify.py
influences/views.py
settings.py
templates/influences/character_detail_block.html
templates/influences/email/character_changed.html
templates/influences/email/influence_changed.html
templates/influences/email/influence_comment_added.html
templates/influences/index.html
templates/influences/influence_detail.html
templates/influences/influence_list_block.html
tools/notify.py
tools/templatetags/list.py [new file with mode: 0644]
tools/templatetags/misc.py [new file with mode: 0644]

index 8abe6cc5334ec9c26847747d4b57baf6f5268216..487b237344c0c3c09a0c59936c64df73ea4e27fe 100644 (file)
@@ -18,9 +18,9 @@ class CharacterAdmin(admin.ModelAdmin):
 admin.site.register(Character, CharacterAdmin)
 
 class InfluenceAdmin(admin.ModelAdmin):
-    list_filter=('character', 'status', 'longterm', 'todo')
-    search_fields=('character', 'summary', 'description', 'contact')
-    list_display=('character', 'contact', 'summary', 'longterm', 'status') 
+    list_filter=('initiator', 'status', 'longterm', 'todo')
+    search_fields=('initiator', 'summary', 'description', 'contact')
+    list_display=('initiator', 'summary', 'longterm', 'status') 
 
     class Media:
         js = ('base/js/yahoo-dom-event.js', 'base/js/logger-debug.js')
index 7db751735048eb1affbf7ad7608b4a41606b9ffc..8ced120dcd8d1e854832cf7c43514f7ba4fe5270 100644 (file)
@@ -67,7 +67,7 @@ def _get_influence_comment_form(allow_markup, allow_public, allow_private):
 class InfluenceForm(ContextModelForm):
     class Meta:
         model = Influence
-        fields = ('character', 'contact', 'summary', 'description')
+        fields = ('initiator', 'summary', 'other_characters', 'other_contacts', 'description')
 
 class CharacterForm(ContextModelForm):
     class Meta:
index 6464e683deb20a1277c4cdf2537a4be5661640b8..089e2a37e6a54a7553fd25cedac5ebdd78736b03 100644 (file)
@@ -5,6 +5,7 @@ from django.utils.text import normalize_newlines
 from django.utils.translation import ugettext_lazy as _
 from threadedcomments.models import ThreadedComment
 from xerxes.tools.text import rewrap
+from string import strip
 
 # Create your models here.
 class Character(models.Model):
@@ -12,11 +13,18 @@ class Character(models.Model):
         ('N', _('New')),
         ('A', _('Approved')),
     )
+    TYPE_CHOICES = (
+        ('P', _('Player')),
+        ('N', _('NPC')),
+        ('C', _('Contact')),
+    )
     created     = models.DateField(auto_now_add=1, verbose_name = _("Creation time"))
     modified    = models.DateField(auto_now=1, verbose_name = _("Modification time"))
     name        = models.CharField(max_length=255, verbose_name = _("Name"))
     status      = models.CharField(max_length=2, choices=STATUS_CHOICES, default='N', verbose_name = _("Status"))
     player      = models.ForeignKey(User, verbose_name = _("Player"))
+    contacts    = models.ManyToManyField('self', blank = True)
+    type        = models.CharField(max_length=2, choices=TYPE_CHOICES, verbose_name=_("Type"))
 
     def __unicode__(self):
         return self.name
@@ -38,8 +46,9 @@ class Influence(models.Model):
     created     = models.DateField(auto_now_add=1, verbose_name = _("Creation time"))
     modified    = models.DateField(auto_now=1, verbose_name = _("Modification time"))
     
-    character   = models.ForeignKey(Character, verbose_name = _("Character"))
-    contact     = models.CharField(max_length=255, verbose_name = _("Contact Name"))
+    initiator   = models.ForeignKey(Character, verbose_name = _("Initiator"), related_name='initiated_influences')
+    other_contacts = models.CharField(max_length=255, blank = True, verbose_name = _("Other Contacts"))
+    other_characters = models.ManyToManyField(Character, blank = True, verbose_name = _("Involved characters"), related_name='influences_involved_in')
     summary     = models.CharField(max_length=255, verbose_name = _("Summary"))
     description = models.TextField(verbose_name = _("Description"))
     todo        = models.TextField(blank=True, verbose_name = _("Todo"))
@@ -85,6 +94,31 @@ class Influence(models.Model):
                                            prefix=prefix)
         return comments
 
+    @property 
+    def involved(self):
+        """ Returns the Characters and contacts (strings) involved """
+        chars = list(self.other_characters.all())
+        if (self.other_contacts):
+            chars.extend(map(strip,self.other_contacts.split(',')))
+        return chars
+    
+    @property
+    def related_players(self):
+        """ Returns all players to this Influence (ie, the players of the
+            initiator or involved characters). Returns a dict where the
+            players (User objects) are keys and a list of Character objects
+            for which this player is related is the value.
+        """
+        players = {self.initiator.player : [self.initiator]}
+        for char in self.other_characters.all():
+            # Add this character to the player's list of characters for
+            # this Influence, creating a new list if this is the first
+            # character.
+            chars = players.get(char.player, [])
+            chars.append(char)
+            players[char.player] = chars
+        return players
+
     class Meta:
         verbose_name = _("Influence")
         verbose_name_plural = _("Influences")
index ddcf37cea7b06c5770e98ee5f8be0eec2e03fe07..d53ab36a51a9dd6377ec85c487ac62e68a3a6aac 100644 (file)
@@ -17,9 +17,11 @@ signals.post_save.connect(character_saved, sender=Character)
 def influence_saved(**kwargs):
     instance = kwargs['instance']
     created  = kwargs['created']
+    recipients = ['lextalionis@evolution-events.nl']
+    recipients.extend(instance.related_players.keys())
     if (not settings.DEBUG):
         # TODO: Perhaps only notify when the status is / becomes Done?
-        notify([instance.character.player, 'lextalionis@evolution-events.nl'], 'influences/email/influence_changed.html', {'influence' : instance, 'created' : created})
+        notify(recipients, 'influences/email/influence_changed.html', {'influence' : instance, 'created' : created})
 
 signals.post_save.connect(influence_saved, sender=Influence)
 
@@ -37,7 +39,7 @@ def comment_saved(**kwargs):
     if isinstance(object, Influence):
         recipients = ['lextalionis@evolution-events.nl']
         if comment.is_public:
-            recipients.append(object.character.player)
+            recipients.extend(object.related_players.keys())
 
         notify(
             recipients,
index 7f7b2621f918a588e03e7e356853b0d45514ffb7..ed23bb24d92c7c45560a05a1da94d5f013735c8b 100644 (file)
@@ -24,9 +24,9 @@ def add_influence(request, character_id=None):
     # If a character_id was specified in the url, or there is only one
     # character, preselect it.
     if (character_id):
-        initial['character'] = character_id
+        initial['initiator'] = character_id
     elif (chars.count() == 1):
-        initial['character'] = chars[0].id
+        initial['initiator'] = chars[0].id
 
 
     f = InfluenceForm(request=request, initial=initial)
@@ -34,7 +34,7 @@ def add_influence(request, character_id=None):
     # Only allow characters of the current user. Putting this here also
     # ensures that a form will not validate when any other choice was
     # selected (perhaps through URL crafting).
-    f.fields['character']._set_queryset(chars)
+    f.fields['initiator']._set_queryset(chars)
 
     if (f.is_valid()):
         # The form was submitted, let's save it.
@@ -59,7 +59,7 @@ def add_character(request):
 def index(request):
     # Only show this player's characters and influences
     characters = request.user.character_set.all()
-    influences = Influence.objects.filter(character__player=request.user)
+    influences = Influence.objects.filter(initiator__player=request.user)
     return render_to_response('influences/index.html', {'characters' : characters, 'influences' : influences}, RequestContext(request))
 
 #
@@ -85,9 +85,9 @@ def character_detail(request, object_id):
 
 @login_required
 def influence_list(request):
-    # Only show this player's influences
-    os = Influence.objects.filter(character__player=request.user)
-    return render_to_response('influences/influence_list.html', {'object_list' : os}, RequestContext(request))
+    # Only show the influences related to this player's characters
+    characters = request.user.character_set.all()
+    return render_to_response('influences/influence_list.html', {'characters' : characters}, RequestContext(request))
 
 def influence_comment_preview(request, context_processors, extra_context, **kwargs):
     # Use a custom template
@@ -101,8 +101,8 @@ def influence_detail(request, object_id):
 
     o = Influence.objects.get(pk=object_id)
     # Don't show other player's influences
-    if (not request.user.is_staff and o.character.player != request.user):
-        return HttpResponseForbidden("Forbidden -- Trying to view influences of somebody else's character")
+    if (not request.user.is_staff and not request.user in o.related_players):
+        return HttpResponseForbidden("Forbidden -- Trying to view influences you are not involved in.")
 
     # Show all comments to staff, but only public comments to other
     # users
index 4848651b77d31285b49ebed81d3eb921b538973e..5ed30bfd1e406cd3d2cb0ea62fb3f02263c3669e 100644 (file)
@@ -117,6 +117,8 @@ AUTH_PROFILE_MODULE = 'base.UserProfile'
 # Max length for comments, in characters.
 DEFAULT_MAX_COMMENT_LENGTH = 3000
 
+INTERNAL_IPS = ('127.0.0.1')
+
 # Import local settings, that are specific to this installation. These
 # can override any settings specified here.
 try:
index 6c0a67bbd151f67bd2c0f3bff2b955e955b21f7d..ae325f2486dfe42a151e78e305fdd0c645de8bb7 100644 (file)
@@ -5,11 +5,26 @@
 {% else %}
 <em>{% trans "This character is approved by the SLs" %}</em>
 {% endifequal %}
-<h2>{% blocktrans with object.name as name %}Influences for {{ name }}{% endblocktrans %}</h2>
-<ul>
-{% for influence in object.influence_set.all %}
-       <li><a href="{% url influences_influence_detail influence.pk %}">{{ influence }}</a></li>
-{% endfor %}
-</ul>
+
+{% if object.initiated_influences.all or object.influences_involved_in.all %}
+    {% if object.initiated_influences.all %}
+        <h2>{% blocktrans with object.name as name %}Influences initiated by {{ name }}{% endblocktrans %}</h2>
+        <ul>
+        {% for influence in object.initiated_influences.all %}
+            <li><a href="{% url influences_influence_detail influence.pk %}">{{ influence }}</a></li>
+        {% endfor %}
+        </ul>
+    {% endif %}
+    {% if object.influences_involved_in.all %}
+        <h2>{% blocktrans with object.name as name %}Influences {{ name }} is involved in{% endblocktrans %}</h2>
+        <ul>
+        {% for influence in object.influences_involved_in.all %}
+            <li><a href="{% url influences_influence_detail influence.pk %}">{{ influence }}</a></li>
+        {% endfor %}
+        </ul>
+    {% endif %}
+{% else %}
+    <p>{% trans "No influences yet." %}</p>
+{% endif %}
 
 <a href="{% url influences_add_influence_for_character object.id %}">{% trans "Submit influence" %}</a>
index c858119ad3d1c9a04a4dfe478b3e4d6b5d7e8def..524f64dd112f746a6e3165cee32ce9357df76c74 100644 (file)
@@ -9,8 +9,8 @@ Subject: {% blocktrans %}Character "{{ character }}" created.{% endblocktrans %}
 Subject: {% blocktrans %}Character "{{ character }}" was changed.{% endblocktrans %}
 {% endif%}
 \\
-{% if recipients.0.first_name %}
-{% blocktrans with recipients.0.first_name as name %}Hello {{ name }}{% endblocktrans %},
+{% if first_name %}
+{% blocktrans %}Hello {{ first_name }}{% endblocktrans %},
 {% else %}
 {% trans "L.S." %},
 {% endif %}
index 3c563373e0d847d18da15b17a39fe8112e30e5e9..ac59c93f60db0062846511b0b7a4dd21b56dbae4 100644 (file)
@@ -1,5 +1,6 @@
 {% load gapless %}{% gapless %}
 {% load i18n %}
+{% load list %}
 {% autoescape off %}
 From: Xerxes (Evolution Events)<xerxes@evolution-events.nl>
 X-Mailer: Xerxes
@@ -9,23 +10,25 @@ Subject: {% blocktrans %}Influence "{{ influence }}" submitted.{% endblocktrans
 Subject: {% blocktrans %}Influence "{{ influence }}" was changed.{% endblocktrans %}
 {% endif%}
 \\
-{% if recipients.0.first_name %}
-{% blocktrans with recipients.0.first_name as name %}Hello {{ name }}{% endblocktrans %},
+{% if first_name %}
+{% blocktrans %}Hello {{ first_name }}{% endblocktrans %},
 {% else %}
 {% trans "L.S." %},
 {% endif %}
 \\
 {% filter wordwrap:72 %}
 {% blocktrans with influence.created|date:"j F Y" as creation_date %}
-You have submitted an influence on {{ creation_date }}.
+You are involved in this influence, submitted on {{ creation_date }}.
 {% endblocktrans %}
 {% if not created %}
 {% blocktrans %}The influence has been modified. The current status is{%endblocktrans %}:{% else %}{% blocktrans %}You submitted{% endblocktrans%}:
 {% endif %}
 {% endfilter %}
 \\
-{% filter ljust:20%}{% trans "Character" %}:{%endfilter%}{{ influence.character }}
-{% filter ljust:20%}{% trans "Contact" %}:{%endfilter%}{{ influence.contact }}
+{% filter ljust:20%}{% trans "Iniator" %}:{%endfilter%}{{ influence.initiator }}
+{% if influence.involved %}
+{% filter ljust:20%}{% trans "Involved" %}:{%endfilter%}{{ influence.involved|natural_list }}
+{% endif %}
 {% filter ljust:20%}{% trans "Summary" %}:{%endfilter%}{{ influence.summary }}
 {% filter ljust:20%}{% trans "Status" %}:{%endfilter%}{{ influence.get_status_display }}
 {% if influence.longterm %}
index 7ebe70ca52c98e57656c92e3103283b714b14ca0..656f16346caf48434cf98f1da908f96ccf7b3a6c 100644 (file)
@@ -5,15 +5,15 @@ From: Xerxes (Evolution Events)<xerxes@evolution-events.nl>
 X-Mailer: Xerxes
 Subject: {% blocktrans %}Comment added to influence "{{ influence }}".{% endblocktrans %}
 \\
-{% if recipients.0.first_name %}
-{% blocktrans with recipients.0.first_name as name %}Hello {{ name }}{% endblocktrans %},
+{% if first_name %}
+{% blocktrans %}Hello {{ first_name }}{% endblocktrans %},
 {% else %}
 {% trans "L.S." %},
 {% endif %}
 \\
 {% filter wordwrap:72 %}
 {% blocktrans %}
-{{commenter}} has just commented the following on your influence:
+{{commenter}} has just commented the following on an influence you are involved in:
 {% endblocktrans %}
 \\
 {{comment.comment}}
index 5f53b5ecca4aab7e511db54dbf0ae6d0ac32ed27..a6a3998bab4e75aed8e1e44c3800b3838cd6f103 100644 (file)
@@ -40,9 +40,7 @@ single page, but I'll add that if that would help.</p>
 {% endwith %}
 
 {% if characters %}
-{% with influences as object_list %}
 {% include "influences/influence_list_block.html" %}
-{% endwith %}
 {% endif %}
 
 {% endblock %}
index 61547ea59935c3699326f4d5816c51ce20195c56..5c50b67dce014a8f93495304535d1933d5f155b6 100644 (file)
@@ -1,17 +1,32 @@
 {% extends "base/base.html" %}
 {% load i18n %}
+{% load list %}
+{% load misc %}
 
 {% block content %}
 <h1>{{ object.summary }}</h1>
 <table>
-<tr><td>{% trans "Contact" %}:</td><td>{{ object.contact }}</td></tr>
-<tr><td>{% trans "Character" %}:</td><td><a href="{% url influences_character_detail object.character.id %}">{{ object.character }}</a></td></tr>
+<tr><td>{% trans "Iniator" %}:</td><td><a href="{% url influences_character_detail object.initiator.id %}">{{ object.initiator }}</a></td></tr>
+{% if object.involved %}
+<tr><td>{% trans "Involved" %}:</td><td>
+{{ object.involved|list_or_value }}</td></tr>
+{% endif %}
 {% if object.longterm %}
 <tr><td>{% trans "Long term" %}:</td><td>{{ object.longterm|yesno|capfirst }}</td></tr>
 {% endif %}
 </table>
 <p>{{ object.description }}</p>
-               
+{# Show all related players, except for the current user #}
+{% with object.related_players|remove_item:user as players %}
+    {% if players %}
+    {% trans "Note: This influence (and its comments) can also be viewed by:" %}
+    <ul>
+        {% for player, chars in players.items %}
+            <li>{{ player }} ({% trans "player of" %} {{ chars|natural_list }})</li>
+        {% endfor %}
+    </ul>
+    {% endif %}
+{% endwith %}
 <h2>{% trans "Comments" %}</h2>
 {% block comments %}
 {% include "influences/influence_comments_block.html" %}
index e59216f65f9d848a014e234e2de6352ffece8be8..4d74e401ee36594d1dd5d5c60e927a5be8620de0 100644 (file)
@@ -1,14 +1,36 @@
 {% load i18n %}
 
+{# Note that this template looks quite like character_detail_block, it is #}
+{# still different enough to not try and factor out the common parts #}
+{# currently...  #}
+
 <h1>{% trans "Your influences" %}</h1>
-{% if object_list %}
-       <ul>
-       {% for influence in object_list %}
-        <li><a href="{% url influences_influence_detail influence.id %}">{{influence.contact }} -- {{ influence.summary }}</a></li>
-       {% endfor %}
-       </ul>
+{% if characters %}
+       {% for character in characters %}
+               <h2>{{ character.name }}</h2>
+        {% if character.initiated_influences.all or character.influences_involved_in.all %}
+            {% if character.initiated_influences.all %}
+                <p>{% blocktrans with character.name as name %}Influences initiated by {{ name }}:{% endblocktrans %}</p>
+                <ul>
+                {% for influence in character.initiated_influences.all %}
+                    <li><a href="{% url influences_influence_detail influence.id %}">{{influence.contact }} -- {{ influence.summary }}</a></li>
+                {% endfor %}
+                </ul>
+            {% endif %}
+            {% if character.influences_involved_in.all %}
+                <p>{% blocktrans with character.name as name %}Influences {{ name }} is involved in:{% endblocktrans %}</p>
+                <ul>
+                {% for influence in character.influences_involved_in.all %}
+                    <li><a href="{% url influences_influence_detail influence.id %}">{{influence.contact }} -- {{ influence.summary }}</a></li>
+                {% endfor %}
+                </ul>
+            {% endif %}
+        {% else %}
+            <p>{% trans "No influences yet." %}</p>
+        {% endif %}
+    {% endfor %}
 {% else %}
-       <p>{% trans "No influences yet." %}</p>
+       <p>{% trans "No characters. Add a character first, so you can submit your influences." %}</p>
 {% endif %}
 
 <p><a href="{% url influences_add_influence %}">{% trans "Submit influence" %}</a></p>
index bc0b4d84336d95f835f31b6dc1a8a6cb34b22914..0c8f96909f283c5ee409a0ff5583ee474de19447 100644 (file)
@@ -8,51 +8,69 @@ Notify someone about something.
 """
 def notify(recipients, template, context = {}):
     recipients = make_iter(recipients)
-    addresses = []; 
+    # Keep a dict of address -> (firstname, lastname)
+    to = {}; 
     for r in recipients:
         if (isinstance(r, User)):
-            addresses.append(r.email)
+            to[r.email] = (r.first_name, r.last_name)
         elif (isinstance(r, Group)):
-            addresses += [m.email for m in r.user_set.all()]
+            to.update([(m.email, (m.first_name, m.last_name)) for m in r.user_set.all()])
         else:
             # Assume it is an email address
-            addresses.append(r)
-    # TODO: Make addresses unique
-
-    context['recipients'] = recipients 
-    context['addresses'] = addresses 
-
-    rendered = loader.render_to_string(template, context)
-    (headers, body) = rendered.split('\n\n', 1)
-
-    # Turn the headers into a dict so EmailMessage can turn them into a
-    # string again. Bit pointless, but it works. 
-    # Perhaps we should just use python email stuff directly. OTOH, we
-    # still always need to parse for the From header.
-
-    headers_dict = {}
-    # If no From header is present, let EmailMessage do the default
-    # thing
-    from_email = None
-    for header in headers.split('\n'):
-        (field, value) = header.split(':')
-        if (field == 'From'):
-            from_email = value
-        elif (field == 'Subject'):
-            subject = value
-        else:
-            # Don't put From and Subject in the dict, else they'll be
-            # present twice.
-            headers_dict[field] = value
-
-    msg = EmailMessage(
-        # Only setting the From address through headers won't set the
-        # envelope address right.
-        from_email = from_email,
-        subject    = subject,
-        body       = body, 
-        to         = addresses, 
-        headers    = headers_dict
-    )
-    msg.send()
+            to[r] = None
+
+    for (address, name) in to.items():
+        if name is None:
+            name = (None, None)
+
+        context['first_name'] = name[0]
+        context['last_name']  = name[1]
+
+        rendered = loader.render_to_string(template, context)
+        (headers, body) = rendered.split('\n\n', 1)
+
+        # Turn the headers into a dict so EmailMessage can turn them into a
+        # string again. Bit pointless, but it works. 
+        # Perhaps we should just use python email stuff directly. OTOH, we
+        # still always need to parse for the From header.
+
+        headers_dict = {}
+        # If no From header is present, let EmailMessage do the default
+        # thing
+        from_email = None
+        subject    = ''
+        for header in headers.split('\n'):
+            (field, value) = header.split(':')
+            if (field == 'From'):
+                from_email = value
+            elif (field == 'Subject'):
+                subject = value
+            else:
+                # Don't put From and Subject in the dict, else they'll be
+                # present twice.
+                headers_dict[field] = value
+
+        msg = EmailMessage(
+            # Only setting the From address through headers won't set the
+            # envelope address right.
+            from_email = from_email,
+            subject    = subject,
+            body       = body, 
+            to         = [make_rfc822_recipient(address, name)],
+            headers    = headers_dict
+        )
+        msg.send()
+
+def make_rfc822_recipient(address, name):
+    """
+    Creates a rfc 822 style recipient: Firstname Lastname <address@domain.nl>
+
+    Takes an address and a tuple with firstname and lastname. The tuple can
+    also be None when no name is known.
+    """
+    if name:
+        return "%s %s <%s>" % (name[0], name[1], address)
+    else:
+        return address
+
 # vim: set sts=4 sw=4 expandtab:
diff --git a/tools/templatetags/list.py b/tools/templatetags/list.py
new file mode 100644 (file)
index 0000000..87d7976
--- /dev/null
@@ -0,0 +1,54 @@
+from django import template
+from django.template.defaultfilters import unordered_list
+from django.utils.safestring import mark_safe
+from django.utils.encoding import force_unicode
+from django.utils.translation import ugettext as _
+
+"""
+    Template tags and filters for working with lists.
+"""
+
+register = template.Library()
+
+@register.filter(name='list_or_value')
+def list_or_value(list, autoescape=None):
+    """
+    Turn a list into a simple string or unordered list.
+
+    If the list is empty, returns an empty string.
+    If the list contains one element, returns just that element.
+    If the list contains more elements, return an ordered list with
+    those elements (Just like the builtin unordered_list, but with the
+    <ul> tags).
+    """
+    if len(list) == 0:
+        return ''
+    elif len(list) == 1:
+        return list[0]
+    else:
+        return mark_safe('<ul>' + unordered_list(list, autoescape=autoescape) + '</ul>')
+list_or_value.needs_autoescape = True
+
+@register.filter(name='natural_list')
+def natural_list(list):
+    """
+    Turns the list into a natural list, using comma's and "and" for
+    joining the terms. The result is somewhat localized (but probably
+    insufficient for language that use completely different
+    interpunction for lists).
+    """
+    if len(list) == 0:
+        return ''
+    res = ''
+    for item in list[0:-1]:
+        if res:
+            res += ', '
+        res += force_unicode(item)
+
+    if res:
+        res += ' %s ' % _('and')
+
+    res += force_unicode(list[-1])
+
+    return res 
+natural_list.is_safe = True
diff --git a/tools/templatetags/misc.py b/tools/templatetags/misc.py
new file mode 100644 (file)
index 0000000..2579295
--- /dev/null
@@ -0,0 +1,20 @@
+from django import template
+
+"""
+    Miscellaneous template tags and filters.
+"""
+
+register = template.Library()
+@register.filter(name='remove_item')
+def remove_item(container, item):
+    """
+    Removes the given user from the filtered list or dict.
+    """
+    if (item in container):
+        if isinstance(container, list):
+            container.remove(item)
+        elif isinstance(container, dict):
+            container.pop(item)
+    return container
+
+# vim: set sts=4 sw=4 expandtab: