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 %}

- + +{% 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 %}

+ + {% endif %} + {% if object.influences_involved_in.all %} +

{% blocktrans with object.name as name %}Influences {{ name }} is involved in{% endblocktrans %}

+ + {% 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 }}

- - + +{% if object.involved %} + +{% endif %} {% if object.longterm %} {% endif %}
{% trans "Contact" %}:{{ object.contact }}
{% trans "Character" %}:{{ object.character }}
{% trans "Iniator" %}:{{ object.initiator }}
{% trans "Involved" %}: +{{ object.involved|list_or_value }}
{% trans "Long term" %}:{{ object.longterm|yesno|capfirst }}

{{ 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: