From: Matthijs Kooijman
Date: Sat, 31 Jan 2009 13:27:02 +0000 (+0100)
Subject: Merge branch 'production'
X-Git-Url: https://git.stderr.nl/gitweb?p=matthijs%2Fprojects%2Fxerxes.git;a=commitdiff_plain;h=d0284ad0db06a4e81bc340619122d6d59abefed7;hp=89667723f59df28360a5e12dd969a8b190caa8d9
Merge branch 'production'
* production:
Reconnect the influence_saved notify handler.
---
diff --git a/influences/admin.py b/influences/admin.py
index 8abe6cc..487b237 100644
--- a/influences/admin.py
+++ b/influences/admin.py
@@ -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')
diff --git a/influences/forms.py b/influences/forms.py
index 7db7517..8ced120 100644
--- a/influences/forms.py
+++ b/influences/forms.py
@@ -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:
diff --git a/influences/models.py b/influences/models.py
index 6464e68..089e2a3 100644
--- a/influences/models.py
+++ b/influences/models.py
@@ -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")
diff --git a/influences/notify.py b/influences/notify.py
index ddcf37c..d53ab36 100644
--- a/influences/notify.py
+++ b/influences/notify.py
@@ -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,
diff --git a/influences/views.py b/influences/views.py
index 7f7b262..ed23bb2 100644
--- a/influences/views.py
+++ b/influences/views.py
@@ -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
diff --git a/settings.py b/settings.py
index 4848651..5ed30bf 100644
--- a/settings.py
+++ b/settings.py
@@ -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:
diff --git a/templates/influences/character_detail_block.html b/templates/influences/character_detail_block.html
index 6c0a67b..ae325f2 100644
--- a/templates/influences/character_detail_block.html
+++ b/templates/influences/character_detail_block.html
@@ -5,11 +5,26 @@
{% else %}
{% trans "This character is approved by the SLs" %}
{% endifequal %}
-{% blocktrans with object.name as name %}Influences for {{ name }}{% endblocktrans %}
-
-{% for influence in object.influence_set.all %}
- - {{ influence }}
-{% endfor %}
-
+
+{% if object.initiated_influences.all or object.influences_involved_in.all %}
+ {% if object.initiated_influences.all %}
+ {% blocktrans with object.name as name %}Influences initiated by {{ name }}{% endblocktrans %}
+
+ {% for influence in object.initiated_influences.all %}
+ - {{ influence }}
+ {% endfor %}
+
+ {% endif %}
+ {% if object.influences_involved_in.all %}
+ {% blocktrans with object.name as name %}Influences {{ name }} is involved in{% endblocktrans %}
+
+ {% for influence in object.influences_involved_in.all %}
+ - {{ influence }}
+ {% endfor %}
+
+ {% endif %}
+{% else %}
+ {% trans "No influences yet." %}
+{% endif %}
{% trans "Submit influence" %}
diff --git a/templates/influences/email/character_changed.html b/templates/influences/email/character_changed.html
index c858119..524f64d 100644
--- a/templates/influences/email/character_changed.html
+++ b/templates/influences/email/character_changed.html
@@ -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 %}
diff --git a/templates/influences/email/influence_changed.html b/templates/influences/email/influence_changed.html
index 3c56337..ac59c93 100644
--- a/templates/influences/email/influence_changed.html
+++ b/templates/influences/email/influence_changed.html
@@ -1,5 +1,6 @@
{% load gapless %}{% gapless %}
{% load i18n %}
+{% load list %}
{% autoescape off %}
From: Xerxes (Evolution Events)
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 %}
diff --git a/templates/influences/email/influence_comment_added.html b/templates/influences/email/influence_comment_added.html
index 7ebe70c..656f163 100644
--- a/templates/influences/email/influence_comment_added.html
+++ b/templates/influences/email/influence_comment_added.html
@@ -5,15 +5,15 @@ From: Xerxes (Evolution Events)
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}}
diff --git a/templates/influences/index.html b/templates/influences/index.html
index 5f53b5e..a6a3998 100644
--- a/templates/influences/index.html
+++ b/templates/influences/index.html
@@ -40,9 +40,7 @@ single page, but I'll add that if that would help.
{% endwith %}
{% if characters %}
-{% with influences as object_list %}
{% include "influences/influence_list_block.html" %}
-{% endwith %}
{% endif %}
{% endblock %}
diff --git a/templates/influences/influence_detail.html b/templates/influences/influence_detail.html
index 61547ea..5c50b67 100644
--- a/templates/influences/influence_detail.html
+++ b/templates/influences/influence_detail.html
@@ -1,17 +1,32 @@
{% extends "base/base.html" %}
{% load i18n %}
+{% load list %}
+{% load misc %}
{% block content %}
{{ object.summary }}
-{% trans "Contact" %}: | {{ object.contact }} |
-{% trans "Character" %}: | {{ object.character }} |
+{% trans "Iniator" %}: | {{ object.initiator }} |
+{% if object.involved %}
+{% trans "Involved" %}: |
+{{ object.involved|list_or_value }} |
+{% endif %}
{% if object.longterm %}
{% trans "Long term" %}: | {{ object.longterm|yesno|capfirst }} |
{% endif %}
{{ object.description }}
-
+{# 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:" %}
+
+ {% for player, chars in players.items %}
+ - {{ player }} ({% trans "player of" %} {{ chars|natural_list }})
+ {% endfor %}
+
+ {% endif %}
+{% endwith %}
{% trans "Comments" %}
{% block comments %}
{% include "influences/influence_comments_block.html" %}
diff --git a/templates/influences/influence_list_block.html b/templates/influences/influence_list_block.html
index e59216f..4d74e40 100644
--- a/templates/influences/influence_list_block.html
+++ b/templates/influences/influence_list_block.html
@@ -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... #}
+
{% trans "Your influences" %}
-{% if object_list %}
-
+{% if characters %}
+ {% for character in characters %}
+ {{ character.name }}
+ {% if character.initiated_influences.all or character.influences_involved_in.all %}
+ {% if character.initiated_influences.all %}
+ {% blocktrans with character.name as name %}Influences initiated by {{ name }}:{% endblocktrans %}
+
+ {% endif %}
+ {% if character.influences_involved_in.all %}
+ {% blocktrans with character.name as name %}Influences {{ name }} is involved in:{% endblocktrans %}
+
+ {% endif %}
+ {% else %}
+ {% trans "No influences yet." %}
+ {% endif %}
+ {% endfor %}
{% else %}
- {% trans "No influences yet." %}
+ {% trans "No characters. Add a character first, so you can submit your influences." %}
{% endif %}
{% trans "Submit influence" %}
diff --git a/tools/notify.py b/tools/notify.py
index bc0b4d8..0c8f969 100644
--- a/tools/notify.py
+++ b/tools/notify.py
@@ -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
+
+ 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
index 0000000..87d7976
--- /dev/null
+++ b/tools/templatetags/list.py
@@ -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
+ tags).
+ """
+ if len(list) == 0:
+ return ''
+ elif len(list) == 1:
+ return list[0]
+ else:
+ return mark_safe('' + unordered_list(list, autoescape=autoescape) + '
')
+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
index 0000000..2579295
--- /dev/null
+++ b/tools/templatetags/misc.py
@@ -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: