From: Patrick Samson Date: Sun, 9 Dec 2012 20:37:51 +0000 (+0100) Subject: Add a setting: POSTMAN_SHOW_USER_AS X-Git-Tag: 2.1.0~1 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=585325e975f8cb4ee96c2de99f090d85f8eff736;p=django-postman.git Add a setting: POSTMAN_SHOW_USER_AS --- diff --git a/docs/quickstart.rst b/docs/quickstart.rst index e0c8f4f..afdf46a 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -78,7 +78,7 @@ You may specify some additional configuration options in your :file:`settings.py nor to a notifier application (refer to ``POSTMAN_NOTIFIER_APP``) *Defaults to*: False. - + ``POSTMAN_AUTO_MODERATE_AS`` The default moderation status when no auto-moderation functions, if any, were decisive. @@ -93,6 +93,21 @@ You may specify some additional configuration options in your :file:`settings.py * Set this option to True * Do not provide any auto-moderation functions +``POSTMAN_SHOW_USER_AS`` + How to represent a User for display, in message properties: ``obfuscated_recipient`` and ``obfuscated_sender``, + and in the ``or_me`` filter. The value can be specified as: + + * The name of a property of User. For example: 'last_name' + * The name of a method of User. For example: 'get_full_name' + * A function, receiving the User instance as the only parameter. For example: lambda u: u.get_profile().nickname + * ``None`` : the default text representation of the User (username) is used. + + *Defaults to*: None. + + The default behaviour is used as a fallback when: the value is a string and the result is false + (misspelled attribute name, empty result, ...), or the value is a function and an exception is raised + (but any result, even empty, is valid). + ``POSTMAN_NOTIFIER_APP`` A notifier application name, used in preference to the basic emailing, to notify users of their rejected or received messages. @@ -214,27 +229,28 @@ Examples :file:`settings.py`:: INSTALLED_APPS = ( + # 'pagination' # has to be before postman # ... 'postman', # ... - # 'pagination' # 'ajax_select' # 'notification' # 'mailer' ) - # POSTMAN_DISALLOW_ANONYMOUS = True # default is False - # POSTMAN_DISALLOW_MULTIRECIPIENTS = True # default is False - # POSTMAN_DISALLOW_COPIES_ON_REPLY = True # default is False - # POSTMAN_DISABLE_USER_EMAILING = True # default is False - # POSTMAN_AUTO_MODERATE_AS = True # default is None - # POSTMAN_NOTIFIER_APP = None # default is 'notification' - # POSTMAN_MAILER_APP = None # default is 'mailer' + # POSTMAN_DISALLOW_ANONYMOUS = True # default is False + # POSTMAN_DISALLOW_MULTIRECIPIENTS = True # default is False + # POSTMAN_DISALLOW_COPIES_ON_REPLY = True # default is False + # POSTMAN_DISABLE_USER_EMAILING = True # default is False + # POSTMAN_AUTO_MODERATE_AS = True # default is None + # POSTMAN_SHOW_USER_AS = 'get_full_name' # default is None + # POSTMAN_NOTIFIER_APP = None # default is 'notification' + # POSTMAN_MAILER_APP = None # default is 'mailer' # POSTMAN_AUTOCOMPLETER_APP = { - # 'name': '', # default is 'ajax_select' - # 'field': '', # default is 'AutoCompleteField' - # 'arg_name': '', # default is 'channel' - # 'arg_default': 'postman_friends', # no default, mandatory to enable the feature - # } # default is {} + # 'name': '', # default is 'ajax_select' + # 'field': '', # default is 'AutoCompleteField' + # 'arg_name': '', # default is 'channel' + # 'arg_default': 'postman_friends', # no default, mandatory to enable the feature + # } # default is {} :file:`urls.py`:: diff --git a/postman/models.py b/postman/models.py index 5174af5..7decc78 100644 --- a/postman/models.py +++ b/postman/models.py @@ -7,7 +7,7 @@ from django.db import models from django.utils.text import truncate_words from django.utils.translation import ugettext, ugettext_lazy as _ try: - from django.utils.timezone import now # Django 1.4 aware datetimes + from django.utils.timezone import now # Django 1.4 aware datetimes except ImportError: from datetime import datetime now = datetime.now @@ -25,18 +25,19 @@ STATUS_CHOICES = ( (STATUS_REJECTED, _('Rejected')), ) # ordering constants -ORDER_BY_KEY = 'o' # as 'order' +ORDER_BY_KEY = 'o' # as 'order' ORDER_BY_FIELDS = { - 'f': 'sender__username', # as 'from' - 't': 'recipient__username', # as 'to' + 'f': 'sender__username', # as 'from' + 't': 'recipient__username', # as 'to' 's': 'subject', # as 'subject' 'd': 'sent_at', # as 'date' } -ORDER_BY_MAPPER = {'sender': 'f', 'recipient': 't', 'subject': 's', 'date': 'd'} # for templatetags usage +ORDER_BY_MAPPER = {'sender': 'f', 'recipient': 't', 'subject': 's', 'date': 'd'} # for templatetags usage -dbms = settings.DATABASES['default']['ENGINE'].rsplit('.',1)[-1] +dbms = settings.DATABASES['default']['ENGINE'].rsplit('.', 1)[-1] QUOTE_CHAR = '`' if dbms == 'mysql' else '"' + def get_order_by(query_dict): """ Return a field name, optionally prefixed for descending order, or None if not found. @@ -45,20 +46,40 @@ def get_order_by(query_dict): ``query_dict``: a dictionary to look for a key dedicated to ordering purpose >>> get_order_by({}) - + >>> get_order_by({ORDER_BY_KEY: 'f'}) 'sender__username' >>> get_order_by({ORDER_BY_KEY: 'D'}) '-sent_at' """ if ORDER_BY_KEY in query_dict: - code = query_dict[ORDER_BY_KEY] # code may be uppercase or lowercase + code = query_dict[ORDER_BY_KEY] # code may be uppercase or lowercase order_by_field = ORDER_BY_FIELDS.get(code.lower()) if order_by_field: if code.isupper(): order_by_field = '-' + order_by_field return order_by_field + +def get_user_representation(user): + """ + Return a User representation for display, configurable through an optional setting. + """ + show_user_as = getattr(settings, 'POSTMAN_SHOW_USER_AS', None) + if isinstance(show_user_as, (unicode, str)): + attr = getattr(user, show_user_as, None) + if callable(attr): + attr = attr() + if attr: + return unicode(attr) + elif callable(show_user_as): + try: + return unicode(show_user_as(user)) + except: + pass + return unicode(user) # default value, or in case of empty attribute or exception + + class MessageManager(models.Manager): """The manager for Message.""" @@ -76,7 +97,7 @@ class MessageManager(models.Manager): qs = self.all() if order_by: qs = qs.order_by(order_by) - if isinstance(filters, (list,tuple)): + if isinstance(filters, (list, tuple)): lookups = models.Q() for filter in filters: lookups |= models.Q(**filter) @@ -119,7 +140,7 @@ class MessageManager(models.Manager): """ return self.inbox(user, related=False, option=OPTION_MESSAGES).filter(read_at__isnull=True).count() - + def sent(self, user, **kwargs): """ Return all messages sent by a user but not marked as archived or deleted. @@ -137,7 +158,7 @@ class MessageManager(models.Manager): """ Return messages belonging to a user and marked as archived. """ - related = ('sender','recipient') + related = ('sender', 'recipient') filters = ({ 'recipient': user, 'recipient_archived': True, @@ -154,7 +175,7 @@ class MessageManager(models.Manager): """ Return messages belonging to a user and marked as deleted. """ - related = ('sender','recipient') + related = ('sender', 'recipient') filters = ({ 'recipient': user, 'recipient_deleted_at__isnull': False, @@ -169,7 +190,7 @@ class MessageManager(models.Manager): """ Return message/conversation for display. """ - return self.select_related('sender','recipient').filter( + return self.select_related('sender', 'recipient').filter( filter, (models.Q(recipient=user) & models.Q(moderation_status=STATUS_ACCEPTED)) | models.Q(sender=user), ).order_by('sent_at') @@ -184,7 +205,7 @@ class MessageManager(models.Manager): """ Return messages matching a filter AND being visible to a user as the sender. """ - return self.filter(filter, sender=user) # any status is fine + return self.filter(filter, sender=user) # any status is fine def perms(self, user): """ @@ -206,6 +227,7 @@ class MessageManager(models.Manager): read_at__isnull=True, ).update(read_at=now()) + class Message(models.Model): """ A message between a User and another User or an AnonymousUser. @@ -217,7 +239,7 @@ class Message(models.Model): body = models.TextField(_("body"), blank=True) sender = models.ForeignKey(User, related_name='sent_messages', null=True, blank=True, verbose_name=_("sender")) recipient = models.ForeignKey(User, related_name='received_messages', null=True, blank=True, verbose_name=_("recipient")) - email = models.EmailField(_("visitor"), blank=True) # instead of either sender or recipient, for an AnonymousUser + email = models.EmailField(_("visitor"), blank=True) # instead of either sender or recipient, for an AnonymousUser parent = models.ForeignKey('self', related_name='next_messages', null=True, blank=True, verbose_name=_("parent message")) thread = models.ForeignKey('self', related_name='child_messages', null=True, blank=True, verbose_name=_("root message")) sent_at = models.DateTimeField(_("sent at"), default=now) @@ -242,7 +264,7 @@ class Message(models.Model): ordering = ['-sent_at', '-id'] def __unicode__(self): - return u"{0}>{1}:{2}".format(self.obfuscated_sender, self.obfuscated_recipient, truncate_words(self.subject,5)) + return u"{0}>{1}:{2}".format(self.obfuscated_sender, self.obfuscated_recipient, truncate_words(self.subject, 5)) @models.permalink def get_absolute_url(self): @@ -280,12 +302,12 @@ class Message(models.Model): """ email = self.email digest = hashlib.md5(email + settings.SECRET_KEY).hexdigest() - shrunken_digest = '..'.join((digest[:4], digest[-4:])) # 32 characters is too long and is useless + shrunken_digest = '..'.join((digest[:4], digest[-4:])) # 32 characters is too long and is useless bits = email.split('@') - if len(bits) <> 2: + if len(bits) != 2: return u'' domain = bits[1] - return '@'.join((shrunken_digest, domain.rsplit('.',1)[0])) # leave off the TLD to gain some space + return '@'.join((shrunken_digest, domain.rsplit('.', 1)[0])) # leave off the TLD to gain some space def admin_sender(self): """ @@ -307,7 +329,7 @@ class Message(models.Model): def obfuscated_sender(self): """Return the sender either as a username or as an undisclosed email.""" if self.sender: - return unicode(self.sender) + return get_user_representation(self.sender) else: return self._obfuscated_email() @@ -331,7 +353,7 @@ class Message(models.Model): def obfuscated_recipient(self): """Return the recipient either as a username or as an undisclosed email.""" if self.recipient: - return unicode(self.recipient) + return get_user_representation(self.recipient) else: return self._obfuscated_email() @@ -353,7 +375,7 @@ class Message(models.Model): def clean_moderation(self, initial_status, user=None): """Adjust automatically some fields, according to status workflow.""" - if self.moderation_status <> initial_status: + if self.moderation_status != initial_status: self.moderation_date = now() self.moderation_by = user if self.is_rejected(): @@ -385,7 +407,7 @@ class Message(models.Model): def update_parent(self, initial_status): """Update the parent to actualize its response state.""" - if self.moderation_status <> initial_status: + if self.moderation_status != initial_status: parent = self.parent if self.is_accepted(): # keep the very first date; no need to do differently @@ -467,7 +489,7 @@ class Message(models.Model): reasons.append(reason) if auto is None and percents: average = float(sum(percents)) / len(percents) - final_reason = ', '.join([r for i,r in enumerate(reasons) if r and not r.isspace() and percents[i] < 50]) + final_reason = ', '.join([r for i, r in enumerate(reasons) if r and not r.isspace() and percents[i] < 50]) auto = average >= 50 if auto is None: auto = getattr(settings, 'POSTMAN_AUTO_MODERATE_AS', None) @@ -477,6 +499,7 @@ class Message(models.Model): self.moderation_status = STATUS_REJECTED self.moderation_reason = final_reason + class PendingMessageManager(models.Manager): """The manager for PendingMessage.""" @@ -484,6 +507,7 @@ class PendingMessageManager(models.Manager): """Filter to get only pending objects.""" return super(PendingMessageManager, self).get_query_set().filter(moderation_status=STATUS_PENDING) + class PendingMessage(Message): """ A proxy to Message, focused on pending objects to accept or reject. diff --git a/postman/templatetags/postman_tags.py b/postman/templatetags/postman_tags.py index 4b3bad7..639369d 100644 --- a/postman/templatetags/postman_tags.py +++ b/postman/templatetags/postman_tags.py @@ -1,6 +1,7 @@ import datetime from django import VERSION +from django.contrib.auth.models import User from django.http import QueryDict from django.template import Node from django.template import TemplateSyntaxError @@ -8,7 +9,8 @@ from django.template import Library from django.template.defaultfilters import date from django.utils.translation import ugettext_lazy as _ -from postman.models import ORDER_BY_MAPPER, ORDER_BY_KEY, Message +from postman.models import ORDER_BY_KEY, ORDER_BY_MAPPER, Message,\ + get_user_representation register = Library() @@ -25,20 +27,22 @@ def sub(value, arg): return value sub.is_safe = True + @register.filter def or_me(value, arg): """ Replace the value by a fixed pattern, if it equals the argument. - Typical usage: sender|or_me:user + Typical usage: message.obfuscated_sender|or_me:user """ if not isinstance(value, (unicode, str)): - value = unicode(value) + value = (get_user_representation if isinstance(value, User) else unicode)(value) if not isinstance(arg, (unicode, str)): - arg = unicode(arg) + arg = (get_user_representation if isinstance(arg, User) else unicode)(arg) return _('') if value == arg else value + @register.filter(**({'expects_localtime': True, 'is_safe': False} if VERSION >= (1, 4) else {})) def compact_date(value, arg): """ @@ -50,7 +54,7 @@ def compact_date(value, arg): """ bits = arg.split(u',') if len(bits) < 3: - return value # Invalid arg. + return value # Invalid arg. today = datetime.date.today() return date(value, bits[0] if value.date() == today else bits[1] if value.year == today.year else bits[2]) @@ -83,6 +87,7 @@ class OrderByNode(Node): gets[ORDER_BY_KEY] = self.code if self.code <> code else self.code.upper() return '?'+gets.urlencode() if gets else '' + class InboxCountNode(Node): "For use in the postman_unread tag" def __init__(self, asvar=None): @@ -106,6 +111,7 @@ class InboxCountNode(Node): return '' return count + @register.tag def postman_order_by(parser, token): """ @@ -127,6 +133,7 @@ def postman_order_by(parser, token): " Must be one of: {2}".format(field_name, tag_name, ORDER_BY_MAPPER.keys())) return OrderByNode(field_code) + @register.tag def postman_unread(parser, token): """ diff --git a/postman/tests.py b/postman/tests.py index b0449c1..deb2894 100644 --- a/postman/tests.py +++ b/postman/tests.py @@ -46,21 +46,26 @@ from django.db.models import Q from django.http import QueryDict from django.template import Template, Context, TemplateSyntaxError, TemplateDoesNotExist from django.test import TestCase +from django.utils.encoding import force_unicode +from django.utils.formats import localize from django.utils.translation import deactivate try: - from django.utils.timezone import now # Django 1.4 aware datetimes + from django.utils.timezone import now # Django 1.4 aware datetimes except ImportError: from datetime import datetime now = datetime.now +from postman.api import pm_broadcast, pm_write from postman.fields import CommaSeparatedUserField # because of reload()'s, do "from postman.forms import xxForm" just before needs from postman.models import ORDER_BY_KEY, ORDER_BY_MAPPER, Message, PendingMessage,\ - STATUS_PENDING, STATUS_ACCEPTED, STATUS_REJECTED + STATUS_PENDING, STATUS_ACCEPTED, STATUS_REJECTED,\ + get_user_representation from postman.urls import OPTION_MESSAGES # because of reload()'s, do "from postman.utils import notification" just before needs from postman.utils import format_body, format_subject + class GenericTest(TestCase): """ Usual generic tests. @@ -68,6 +73,7 @@ class GenericTest(TestCase): def test_version(self): self.assertEqual(sys.modules['postman'].__version__, "2.1.0a1") + class BaseTest(TestCase): """ Common configuration and helper functions for all tests. @@ -75,7 +81,7 @@ class BaseTest(TestCase): urls = 'postman.test_urls' def setUp(self): - deactivate() # necessary for 1.4 to consider a new settings.LANGUAGE_CODE; 1.3 is fine with or without + deactivate() # necessary for 1.4 to consider a new settings.LANGUAGE_CODE; 1.3 is fine with or without settings.LANGUAGE_CODE = 'en' # do not bother about translation for a in ( 'POSTMAN_DISALLOW_ANONYMOUS', @@ -83,6 +89,7 @@ class BaseTest(TestCase): 'POSTMAN_DISALLOW_COPIES_ON_REPLY', 'POSTMAN_DISABLE_USER_EMAILING', 'POSTMAN_AUTO_MODERATE_AS', + 'POSTMAN_SHOW_USER_AS', ): if hasattr(settings, a): delattr(settings, a) @@ -176,7 +183,8 @@ class BaseTest(TestCase): except KeyError: # happens once at the setUp pass reload(get_resolver(get_urlconf()).urlconf_module) - + + class ViewTest(BaseTest): """ Test the views. @@ -795,6 +803,7 @@ class ViewTest(BaseTest): m1.replied_at = m2.sent_at; m1.save() self.check_update_conversation('postman_undelete', m1, 'deleted_at') + class FieldTest(BaseTest): """ Test the CommaSeparatedUserField. @@ -869,6 +878,7 @@ class FieldTest(BaseTest): self.assertEqual(f.clean('foo'), [self.user1]) self.assertRaises(ValidationError, f.clean, 'foo, bar') + class MessageManagerTest(BaseTest): """ Test the Message manager. @@ -913,13 +923,13 @@ class MessageManagerTest(BaseTest): |<------| x x |------> ------> - ------> x - <------ + ------> x + <------ ...--- x X--- """ - m1 = self.c12(moderation_status=STATUS_PENDING); + m1 = self.c12(moderation_status=STATUS_PENDING); m2 = self.c12(moderation_status=STATUS_REJECTED, recipient_deleted_at=now()) m3 = self.c12() m3.read_at, m3.thread = now(), m3 @@ -980,8 +990,8 @@ class MessageManagerTest(BaseTest): |<------| X X x x |------> X ------> X - ------> X x - X <------ + ------> X x + X <------ ...--- X x X--- X """ @@ -1029,6 +1039,7 @@ class MessageManagerTest(BaseTest): self.check_status(m, status=STATUS_ACCEPTED, is_new=False, recipient_deleted_at=True) self.check_now(m.read_at) + class MessageTest(BaseTest): """ Test the Message model. @@ -1242,7 +1253,7 @@ class MessageTest(BaseTest): self.check_status(r.parent, status=STATUS_ACCEPTED, thread=parent, is_replied=True) # accepted -> rejected: parent is no more replied r.update_parent(STATUS_ACCEPTED) - p = Message.objects.get(pk=parent.pk) + p = Message.objects.get(pk=parent.pk) self.check_status(p, status=STATUS_ACCEPTED, thread=parent) # note: accepted -> rejected, with the existence of another suitable reply # is covered in the accepted -> pending case @@ -1256,7 +1267,7 @@ class MessageTest(BaseTest): # pending -> pending: no change. In real case, parent.replied_at would be from another reply object r.update_parent(STATUS_PENDING) self.check_status(r.parent, status=STATUS_ACCEPTED, thread=parent, is_replied=True) - # rejected -> pending: no change. In real case, parent.replied_at would be from another reply object + # rejected -> pending: no change. In real case, parent.replied_at would be from another reply object r.update_parent(STATUS_REJECTED) self.check_status(r.parent, status=STATUS_ACCEPTED, thread=parent, is_replied=True) # accepted -> pending: parent is still replied but by another object @@ -1273,7 +1284,7 @@ class MessageTest(BaseTest): self.assertEqual(len(mail.outbox), mail_number) if mail_number: self.assertEqual(mail.outbox[0].to, [email]) - from utils import notification + from postman.utils import notification if notification and notice_label: notice = notification.Notice.objects.get() self.assertEqual(notice.notice_type.label, notice_label) @@ -1435,6 +1446,7 @@ class MessageTest(BaseTest): settings.POSTMAN_AUTO_MODERATE_AS = False self.check_auto_moderation(msg, seq, STATUS_REJECTED) + class PendingMessageManagerTest(BaseTest): """ Test the PendingMessage manager. @@ -1446,6 +1458,7 @@ class PendingMessageManagerTest(BaseTest): msg4 = self.create() self.assertQuerysetEqual(PendingMessage.objects.all(), [msg4.pk, msg1.pk], transform=lambda x: x.pk) + class PendingMessageTest(BaseTest): """ Test the PendingMessage model. @@ -1458,8 +1471,7 @@ class PendingMessageTest(BaseTest): m.set_rejected() self.assert_(m.is_rejected()) -from django.utils.encoding import force_unicode -from django.utils.formats import localize + class FiltersTest(BaseTest): """ Test the filters. @@ -1474,21 +1486,28 @@ class FiltersTest(BaseTest): self.check_sub('6', "'X'", '6') self.check_sub("'X'", '2', 'X') - def check_or_me(self, x, value, user=None): + def check_or_me(self, x, value, user=None, m=None): t = Template("{% load postman_tags %}{{ "+x+"|or_me:user }}") # do not load i18n to be able to check the untranslated pattern - self.assertEqual(t.render(Context({'user': user or AnonymousUser()})), value) + self.assertEqual(t.render(Context({'user': user or AnonymousUser(), 'message': m})), value) def test_or_me(self): "Test '|or_me'." self.check_or_me("'foo'", 'foo') self.check_or_me("'foo'", '<me>', self.user1) self.check_or_me("'bar'", 'bar', self.user1) + self.check_or_me("user", '<me>', self.user1) + m = self.c12() + self.check_or_me("message.obfuscated_sender", '<me>', self.user1, m=m) + self.check_or_me("message.obfuscated_recipient", 'bar', self.user1, m=m) + settings.POSTMAN_SHOW_USER_AS = 'email' + self.check_or_me("message.obfuscated_sender", '<me>', self.user1, m=m) + self.check_or_me("message.obfuscated_recipient", 'bar@domain.com', self.user1, m=m) def check_compact_date(self, date, value, format='H:i,d b,d/m/y'): # use 'H', 'd', 'm' instead of 'G', 'j', 'n' because no strftime equivalents t = Template('{% load postman_tags %}{{ date|compact_date:"'+format+'" }}') self.assertEqual(t.render(Context({'date': date})), value) - + def test_compact_date(self): "Test '|compact_date'." dt = now() @@ -1511,6 +1530,7 @@ class FiltersTest(BaseTest): dt = now() - timedelta(days=365) self.check_compact_date(dt, dt.strftime('%d/%m/%y')) + class TagsTest(BaseTest): """ Test the template tags. @@ -1534,7 +1554,7 @@ class TagsTest(BaseTest): self.assertEqual(ctx['var'], 1) self.assertRaises(TemplateSyntaxError, self.check_postman_unread, '', self.user1, 'as var extra') self.assertRaises(TemplateSyntaxError, self.check_postman_unread, '', self.user1, 'As var') - + def check_order_by(self, keyword, value_list, context=None): t = Template("{% load postman_tags %}{% postman_order_by " + keyword +" %}") r = t.render(Context({'gets': QueryDict(context)} if context else {})) @@ -1553,6 +1573,7 @@ class TagsTest(BaseTest): self.assertRaises(TemplateSyntaxError, self.check_order_by, 'subject extra', None) self.assertRaises(TemplateSyntaxError, self.check_order_by, 'unknown', None) + class UtilsTest(BaseTest): """ Test helper functions. @@ -1576,7 +1597,29 @@ class UtilsTest(BaseTest): self.assertEqual(format_subject("Re: foo bar"), "Re: foo bar") self.assertEqual(format_subject("rE: foo bar"), "rE: foo bar") -from postman.api import pm_broadcast, pm_write + def test_get_user_representation(self): + "Test get_user_representation()." + # no setting + self.assertEqual(get_user_representation(self.user1), "foo") + # a wrong setting + settings.POSTMAN_SHOW_USER_AS = 'unknown_attribute' + self.assertEqual(get_user_representation(self.user1), "foo") + # a valid setting but an empty attribute + settings.POSTMAN_SHOW_USER_AS = 'first_name' + self.assertEqual(get_user_representation(self.user1), "foo") + # a property name + settings.POSTMAN_SHOW_USER_AS = 'email' + self.assertEqual(get_user_representation(self.user1), "foo@domain.com") + settings.POSTMAN_SHOW_USER_AS = b'email' + self.assertEqual(get_user_representation(self.user1), "foo@domain.com") + # a method name + settings.POSTMAN_SHOW_USER_AS = 'get_absolute_url' # can't use get_full_name(), an empty string in our case + self.assertEqual(get_user_representation(self.user1), "/users/foo/") + # a function + settings.POSTMAN_SHOW_USER_AS = lambda u: u.get_absolute_url() + self.assertEqual(get_user_representation(self.user1), "/users/foo/") + + class ApiTest(BaseTest): """ Test the API functions.