Merge branch 'production'
authorMatthijs Kooijman <matthijs@stdin.nl>
Fri, 16 Jan 2009 21:27:55 +0000 (22:27 +0100)
committerMatthijs Kooijman <matthijs@stdin.nl>
Fri, 16 Jan 2009 21:27:55 +0000 (22:27 +0100)
* production:
  Allow Influence.todo to be blank.

influences/admin.py
influences/forms.py
influences/models.py
influences/views.py
settings.py
templates/influences/influence_detail.html
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..8f243f5b49de90f250e0949b7a2e98f958f513ff 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')
+    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 7f7b2621f918a588e03e7e356853b0d45514ffb7..e741605e9acad8e94efb4f96552c1ea2daa85c0f 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))
 
 #
@@ -86,7 +86,7 @@ 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)
+    os = Influence.objects.filter(initiator__player=request.user)
     return render_to_response('influences/influence_list.html', {'object_list' : os}, RequestContext(request))
 
 def influence_comment_preview(request, context_processors, extra_context, **kwargs):
@@ -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 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" %}
diff --git a/tools/templatetags/list.py b/tools/templatetags/list.py
new file mode 100644 (file)
index 0000000..70ce860
--- /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 += item.__unicode__()
+
+    if res:
+        res += ' %s ' % _('and')
+
+    res += list[-1].__unicode__()
+
+    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: