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')
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:
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):
('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
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"))
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")
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)
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,
# 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)
# 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.
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))
#
@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
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
# 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:
{% 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>
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 %}
{% load gapless %}{% gapless %}
{% load i18n %}
+{% load list %}
{% autoescape off %}
From: Xerxes (Evolution Events)<xerxes@evolution-events.nl>
X-Mailer: Xerxes
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 %}
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}}
{% endwith %}
{% if characters %}
-{% with influences as object_list %}
{% include "influences/influence_list_block.html" %}
-{% endwith %}
{% endif %}
{% endblock %}
{% 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" %}
{% 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>
"""
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:
--- /dev/null
+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
--- /dev/null
+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: