Commits

Patrick Samson committed 690d7c5

Renamed test_urls.py to urls_for_tests.py ; Converted some translations to lazy

Comments (0)

Files changed (10)

 Django Postman changelog
 ========================
 
+Version 3.0.2, October 2013
+---------------------------
+* Rename test_urls.py to urls_for_tests.py, for adjustment with the new test discovery feature of Django 1.6.
+* Fix the need for some translations to become lazy, introduced by the conversion to class-based views.
+* Fix issue #36, BooleanField definition needs an explicit default value for Django 1.6.
+* Fix issue #35, the app can work without the sites framework.
+
 Version 3.0.1, August 2013
 --------------------------
 * Fix issue #32, an IndexError when a Paginator is used and the folder is empty.
 # The short X.Y version.
 version = '3.0'
 # The full version, including alpha/beta/rc tags.
-release = '3.0.1.post1'
+release = '3.0.2'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.

postman/__init__.py

 from __future__ import unicode_literals
 
 # following PEP 386: N.N[.N]+[{a|b|c|rc}N[.N]+][.postN][.devN]
-VERSION = (3, 0, 1)
+VERSION = (3, 0, 2)
 PREREL = ()
-POST = 1
+POST = 0
 DEV = 0
 
 # options

postman/locale/nl/LC_MESSAGES/django.mo

Binary file modified.

postman/locale/nl/LC_MESSAGES/django.po

 # This file is distributed under the same license as the django-postman package.
 # 
 # Translators:
-# Patrick Samson <maxcom@laposte.net>, 2011.
+# Gwildor <gwildorsok@gmail.com>, 2013
+# Patrick Samson <maxcom@laposte.net>, 2011
 msgid ""
 msgstr ""
 "Project-Id-Version: django-postman\n"
 "Report-Msgid-Bugs-To: http://bitbucket.org/psam/django-postman/issues\n"
-"POT-Creation-Date: 2012-12-10 23:13+0100\n"
-"PO-Revision-Date: 2010-12-27 15:10+0000\n"
-"Last-Translator: Patrick Samson <maxcom@laposte.net>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
+"POT-Creation-Date: 2012-12-10 23:00+0100\n"
+"PO-Revision-Date: 2013-10-11 14:53+0000\n"
+"Last-Translator: Gwildor <gwildorsok@gmail.com>\n"
+"Language-Team: Dutch (http://www.transifex.com/projects/p/django-postman/language/nl/)\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
 #: .\models.py:28
 msgid "Accepted"
-msgstr ""
+msgstr "Geaccepteerd"
 
 #: .\models.py:29 .\templates\postman\view.html.py:14
 msgid "Rejected"
 "\n"
 "{sender} wrote:\n"
 "{body}\n"
-msgstr ""
-"\n"
-"\n"
-"{sender} schreef:\n"
-"{body}\n"
+msgstr "\n\n{sender} schreef:\n{body}\n"
 
 #: .\utils.py:63
 msgid "Re: {subject}"
 
 #: .\views.py:146 .\views.py:208
 msgid "Message rejected for at least one recipient."
-msgstr ""
+msgstr "Bericht geweigerd voor ten minste een ontvanger."
 
 #: .\views.py:299
 msgid "Select at least one object."
 
 #: .\templates\admin\postman\pendingmessage\submit_line.html.py:6
 msgid "Accept"
-msgstr ""
+msgstr "Accepteer"
 
 #: .\templates\admin\postman\pendingmessage\submit_line.html.py:7
 msgid "Reject"
 msgid ""
 "Messages in this folder will never be removed. You can use this folder for "
 "long term storage."
-msgstr ""
+msgstr "Berichten in deze map worden nooit verwijderd. Je kunt deze map gebruiken voor langdurige opslag."
 
 #: .\templates\postman\base.html.py:4
 msgid "Messaging"
 #: .\templates\postman\email_user.txt.py:5
 #: .\templates\postman\email_visitor.txt.py:5
 msgid ", for the following reason:"
-msgstr ""
+msgstr ", voor de volgende reden:"
 
 #: .\templates\postman\email_user.txt.py:9
 #: .\templates\postman\email_visitor.txt.py:10
 msgid ""
 "Note: This message is issued by an automated system.\n"
 "Do not reply, this would not be taken into account."
-msgstr ""
+msgstr "NB: Dit bericht is verstuurd door een automatisch systeem.\nReageren helpt niet, dat wordt niet verwerkt."
 
 #: .\templates\postman\email_user_subject.txt.py:1
 #: .\templates\postman\email_visitor_subject.txt.py:1
 msgid ""
 "Messages in this folder can be removed from time to time. For long term "
 "storage, use instead the archive folder."
-msgstr ""
+msgstr "Berichten in deze map kunnen van tijd tot tijd verwijderd worden. Gebruik de archiefmap voor langdurige opslag."
 
 #: .\templates\postman\view.html.py:6
 msgid "Conversation"

postman/test_urls.py

-"""
-URLconf for tests.py usage.
-
-"""
-from __future__ import unicode_literals
-
-from django.conf import settings
-try:
-    from django.conf.urls import patterns, include, url  # django 1.4
-except ImportError:
-    from django.conf.urls.defaults import *  # "patterns, include, url" is enough for django 1.3, "*" for django 1.2
-from django.forms import ValidationError
-from django.views.generic.base import RedirectView
-
-from . import OPTIONS
-from .views import (InboxView, SentView, ArchivesView, TrashView,
-        WriteView, ReplyView, MessageView, ConversationView,
-        ArchiveView, DeleteView, UndeleteView)
-
-
-# user_filter function set
-def user_filter_reason(user):
-    if user.get_username() == 'bar':
-        return 'some reason'
-    return None
-def user_filter_no_reason(user):
-    return ''
-def user_filter_false(user):
-    return False
-def user_filter_exception(user):
-    if user.get_username() == 'bar':
-        raise ValidationError(['first good reason', "anyway, I don't like {0}".format(user.get_username())])
-    return None
-
-# exchange_filter function set
-def exch_filter_reason(sender, recipient, recipients_list):
-    if recipient.get_username() == 'bar':
-        return 'some reason'
-    return None
-def exch_filter_no_reason(sender, recipient, recipients_list):
-    return ''
-def exch_filter_false(sender, recipient, recipients_list):
-    return False
-def exch_filter_exception(sender, recipient, recipients_list):
-    if recipient.get_username() == 'bar':
-        raise ValidationError(['first good reason', "anyway, I don't like {0}".format(recipient.get_username())])
-    return None
-
-# auto-moderation function set
-def moderate_as_51(message):
-    return 51
-def moderate_as_48(message):
-    return (48, "some reason")
-moderate_as_48.default_reason = 'some default reason'
-
-# quote formatters
-def format_subject(subject):
-    return "Re_ " + subject
-def format_body(sender, body):
-    return "{0} _ {1}".format(sender, body)
-
-postman_patterns = patterns('postman.views',
-    # Basic set
-    url(r'^inbox/(?:(?P<option>'+OPTIONS+')/)?$', InboxView.as_view(), name='postman_inbox'),
-    url(r'^sent/(?:(?P<option>'+OPTIONS+')/)?$', SentView.as_view(), name='postman_sent'),
-    url(r'^archives/(?:(?P<option>'+OPTIONS+')/)?$', ArchivesView.as_view(), name='postman_archives'),
-    url(r'^trash/(?:(?P<option>'+OPTIONS+')/)?$', TrashView.as_view(), name='postman_trash'),
-    url(r'^write/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(), name='postman_write'),
-    url(r'^reply/(?P<message_id>[\d]+)/$', ReplyView.as_view(), name='postman_reply'),
-    url(r'^view/(?P<message_id>[\d]+)/$', MessageView.as_view(), name='postman_view'),
-    url(r'^view/t/(?P<thread_id>[\d]+)/$', ConversationView.as_view(), name='postman_view_conversation'),
-    url(r'^archive/$', ArchiveView.as_view(), name='postman_archive'),
-    url(r'^delete/$', DeleteView.as_view(), name='postman_delete'),
-    url(r'^undelete/$', UndeleteView.as_view(), name='postman_undelete'),
-    (r'^$', RedirectView.as_view(url='inbox/')),
-
-    # Customized set
-    # 'success_url'
-    url(r'^write_sent/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(success_url='postman_sent'), name='postman_write_with_success_url_to_sent'),
-    url(r'^reply_sent/(?P<message_id>[\d]+)/$', ReplyView.as_view(success_url='postman_sent'), name='postman_reply_with_success_url_to_sent'),
-    url(r'^archive_arch/$', ArchiveView.as_view(success_url='postman_archives'), name='postman_archive_with_success_url_to_archives'),
-    url(r'^delete_arch/$', DeleteView.as_view(success_url='postman_archives'), name='postman_delete_with_success_url_to_archives'),
-    url(r'^undelete_arch/$', UndeleteView.as_view(success_url='postman_archives'), name='postman_undelete_with_success_url_to_archives'),
-    # 'max'
-    url(r'^write_max/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(max=1), name='postman_write_with_max'),
-    url(r'^reply_max/(?P<message_id>[\d]+)/$', ReplyView.as_view(max=1), name='postman_reply_with_max'),
-    # 'user_filter' on write
-    url(r'^write_user_filter_reason/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(user_filter=user_filter_reason), name='postman_write_with_user_filter_reason'),
-    url(r'^write_user_filter_no_reason/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(user_filter=user_filter_no_reason), name='postman_write_with_user_filter_no_reason'),
-    url(r'^write_user_filter_false/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(user_filter=user_filter_false), name='postman_write_with_user_filter_false'),
-    url(r'^write_user_filter_exception/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(user_filter=user_filter_exception), name='postman_write_with_user_filter_exception'),
-    # 'user_filter' on reply
-    url(r'^reply_user_filter_reason/(?P<message_id>[\d]+)/$', ReplyView.as_view(user_filter=user_filter_reason), name='postman_reply_with_user_filter_reason'),
-    url(r'^reply_user_filter_no_reason/(?P<message_id>[\d]+)/$', ReplyView.as_view(user_filter=user_filter_no_reason), name='postman_reply_with_user_filter_no_reason'),
-    url(r'^reply_user_filter_false/(?P<message_id>[\d]+)/$', ReplyView.as_view(user_filter=user_filter_false), name='postman_reply_with_user_filter_false'),
-    url(r'^reply_user_filter_exception/(?P<message_id>[\d]+)/$', ReplyView.as_view(user_filter=user_filter_exception), name='postman_reply_with_user_filter_exception'),
-    # 'exchange_filter' on write
-    url(r'^write_exch_filter_reason/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(exchange_filter=exch_filter_reason), name='postman_write_with_exch_filter_reason'),
-    url(r'^write_exch_filter_no_reason/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(exchange_filter=exch_filter_no_reason), name='postman_write_with_exch_filter_no_reason'),
-    url(r'^write_exch_filter_false/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(exchange_filter=exch_filter_false), name='postman_write_with_exch_filter_false'),
-    url(r'^write_exch_filter_exception/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(exchange_filter=exch_filter_exception), name='postman_write_with_exch_filter_exception'),
-    # 'exchange_filter' on reply
-    url(r'^reply_exch_filter_reason/(?P<message_id>[\d]+)/$', ReplyView.as_view(exchange_filter=exch_filter_reason), name='postman_reply_with_exch_filter_reason'),
-    url(r'^reply_exch_filter_no_reason/(?P<message_id>[\d]+)/$', ReplyView.as_view(exchange_filter=exch_filter_no_reason), name='postman_reply_with_exch_filter_no_reason'),
-    url(r'^reply_exch_filter_false/(?P<message_id>[\d]+)/$', ReplyView.as_view(exchange_filter=exch_filter_false), name='postman_reply_with_exch_filter_false'),
-    url(r'^reply_exch_filter_exception/(?P<message_id>[\d]+)/$', ReplyView.as_view(exchange_filter=exch_filter_exception), name='postman_reply_with_exch_filter_exception'),
-    # 'auto_moderators'
-    url(r'^write_moderate/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(auto_moderators=(moderate_as_51,moderate_as_48)), name='postman_write_moderate'),
-    url(r'^reply_moderate/(?P<message_id>[\d]+)/$', ReplyView.as_view(auto_moderators=(moderate_as_51,moderate_as_48)), name='postman_reply_moderate'),
-    # 'formatters'
-    url(r'^reply_formatters/(?P<message_id>[\d]+)/$', ReplyView.as_view(formatters=(format_subject, format_body)), name='postman_reply_formatters'),
-    url(r'^view_formatters/(?P<message_id>[\d]+)/$', MessageView.as_view(formatters=(format_subject, format_body)), name='postman_view_formatters'),
-    # auto-complete
-    url(r'^write_ac/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(autocomplete_channels=('postman_multiple_as1-1', None)), name='postman_write_auto_complete'),
-    url(r'^reply_ac/(?P<message_id>[\d]+)/$', ReplyView.as_view(autocomplete_channel='postman_multiple_as1-1'), name='postman_reply_auto_complete'),
-    # 'template_name'
-    url(r'^inbox_template/(?:(?P<option>'+OPTIONS+')/)?$', InboxView.as_view(template_name='postman/fake.html'), name='postman_inbox_template'),
-    url(r'^sent_template/(?:(?P<option>'+OPTIONS+')/)?$', SentView.as_view(template_name='postman/fake.html'), name='postman_sent_template'),
-    url(r'^archives_template/(?:(?P<option>'+OPTIONS+')/)?$', ArchivesView.as_view(template_name='postman/fake.html'), name='postman_archives_template'),
-    url(r'^trash_template/(?:(?P<option>'+OPTIONS+')/)?$', TrashView.as_view(template_name='postman/fake.html'), name='postman_trash_template'),
-    url(r'^write_template/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(template_name='postman/fake.html'), name='postman_write_template'),
-    url(r'^reply_template/(?P<message_id>[\d]+)/$', ReplyView.as_view(template_name='postman/fake.html'), name='postman_reply_template'),
-    url(r'^view_template/(?P<message_id>[\d]+)/$', MessageView.as_view(template_name='postman/fake.html'), name='postman_view_template'),
-    url(r'^view_template/t/(?P<thread_id>[\d]+)/$', ConversationView.as_view(template_name='postman/fake.html'), name='postman_view_conversation_template'),
-)
-
-urlpatterns = patterns('',
-    (r'^accounts/login/$', 'django.contrib.auth.views.login'),  # because of the login_required decorator
-    (r'^messages/', include(postman_patterns)),
-)
-
-# because of fields.py/AutoCompleteWidget/render()/reverse()
-if 'ajax_select' in settings.INSTALLED_APPS:
-    urlpatterns += patterns('',
-        (r'^ajax_select/', include('ajax_select.urls')),  # django-ajax-selects
-    )
-
-# optional
-if 'notification' in settings.INSTALLED_APPS:
-    urlpatterns += patterns('',
-        (r'^notification/', include('notification.urls')),  # django-notification
-    )
     Usual generic tests.
     """
     def test_version(self):
-        self.assertEqual(sys.modules['postman'].__version__, "3.0.1.post1")
+        self.assertEqual(sys.modules['postman'].__version__, "3.0.2")
 
 
 class BaseTest(TestCase):
     """
     Common configuration and helper functions for all tests.
     """
-    urls = 'postman.test_urls'
+    urls = 'postman.urls_for_tests'
 
     def setUp(self):
         deactivate()  # necessary for 1.4 to consider a new settings.LANGUAGE_CODE; 1.3 is fine with or without
             self.check_status(m, sender_deleted_at=True)
         self.assertEqual(len(mail.outbox), 0)
 
+    def check_contrib_messages(self, response, text):
+        if 'messages' in response.context:  # contrib\messages\context_processors.py may be not there
+            messages = response.context['messages']
+            if messages != []:  # contrib\messages\middleware.py may be not there
+                self.assertEqual(len(messages), 1)
+                for message in messages:  # can only be iterated
+                    self.assertEqual(str(message), text)
+
     def check_write_post(self, extra={}, is_anonymous=False):
         "Check message generation, redirection, and mandatory fields."
         url = reverse('postman_write')
         data = {'recipients': self.user2.get_username(), 'subject': 's', 'body': 'b'}
         data.update(extra)
         # default redirect is to the requestor page
-        response = self.client.post(url, data, HTTP_REFERER=url)
+        response = self.client.post(url, data, HTTP_REFERER=url, follow=True)
         self.assertRedirects(response, url)
+        self.check_contrib_messages(response, 'Message successfully sent.')  # no such check for the following posts, one is enough
         m = Message.objects.get()
         pk = m.pk
         self.check_message(m, is_anonymous)
         url = reverse('postman_write')
         data = {'subject': 's', 'body': 'b', 'recipients': self.user2.get_username()}
         self.assertTrue(self.client.login(username='foo', password='pass'))
-        response = self.client.post(reverse('postman_write_moderate'), data, HTTP_REFERER=url)
+        response = self.client.post(reverse('postman_write_moderate'), data, HTTP_REFERER=url, follow=True)
         self.assertRedirects(response, url)
+        self.check_contrib_messages(response, 'Message rejected for at least one recipient.')
         self.check_status(Message.objects.get(), status=STATUS_REJECTED, recipient_deleted_at=True,
             moderation_date=True, moderation_reason="some reason")
 
         # default redirect is to the requestor page
         response = self.client.post(url, data, HTTP_REFERER=url)
         self.assertRedirects(response, url)
+        # the check_contrib_messages() in test_write_post() is enough
         self.check_message(Message.objects.get(pk=pk+1))
         # fallback redirect is to inbox
         response = self.client.post(url, data)
 
         response = self.client.post(reverse('postman_reply_moderate', args=[pk]), data, HTTP_REFERER=url)
         self.assertRedirects(response, url)
+        # the check_contrib_messages() in test_write_post_moderate() is enough
         self.check_status(Message.objects.get(pk=pk+1), status=STATUS_REJECTED, recipient_deleted_at=True,
             parent=m, thread=m,
             moderation_date=True, moderation_reason="some reason")
         response = self.client.get(url)
         self.assertEqual(len(response.context['pm_messages']), 2)
 
-    def check_update(self, view_name, field_bit, pk, field_value=None):
+    def check_update(self, view_name, success_msg, field_bit, pk, field_value=None):
         "Check permission, redirection, field updates, invalid cases."
         url = reverse(view_name)
         url_with_success_url = reverse(view_name + '_with_success_url_to_archives')
         self.assertTrue(self.client.login(username='foo', password='pass'))
         # default redirect is to the requestor page
         redirect_url = reverse('postman_sent')
-        response = self.client.post(url, data, HTTP_REFERER=redirect_url)
+        response = self.client.post(url, data, HTTP_REFERER=redirect_url, follow=True)  # 'follow' to access messages
         self.assertRedirects(response, redirect_url)
+        self.check_contrib_messages(response, success_msg)
         sender_kw = 'sender_{0}'.format(field_bit)
         recipient_kw = 'recipient_{0}'.format(field_bit)
         self.check_status(Message.objects.get(pk=pk),   status=STATUS_ACCEPTED, **{sender_kw: field_value})
         response = self.client.post(url_with_success_url + '?next=' + redirect_url, data, HTTP_REFERER='does not matter')
         self.assertRedirects(response, redirect_url)
         # missing payload
-        response = self.client.post(url)
+        response = self.client.post(url, follow=True)
         self.assertRedirects(response, reverse('postman_inbox'))
+        self.check_contrib_messages(response, 'Select at least one object.')
 
         # not a POST
         response = self.client.get(url, data)
         self.assertTrue(self.client.login(username='foo', password='pass'))
         response = self.client.post(url, data)
         self.assertRedirects(response, reverse('postman_inbox'))
+        # contrib.messages are already tested with check_update()
         sender_kw = 'sender_{0}'.format(field_bit)
         recipient_kw = 'recipient_{0}'.format(field_bit)
         self.check_status(Message.objects.get(pk=pk), status=STATUS_ACCEPTED, is_new=False, is_replied=True, thread=root_msg, **{sender_kw: field_value})
         self.c21()
         self.c12()
         self.c13()
-        self.check_update('postman_archive', 'archived', pk, True)
+        self.check_update('postman_archive', 'Messages or conversations successfully archived.', 'archived', pk, True)
 
     def test_archive_conversation(self):
         "Test archive action on conversations."
         self.c21()
         self.c12()
         self.c13()
-        self.check_update('postman_delete', 'deleted_at', pk, True)
+        self.check_update('postman_delete', 'Messages or conversations successfully deleted.', 'deleted_at', pk, True)
 
     def test_delete_conversation(self):
         "Test delete action on conversations."
         self.c21(recipient_deleted_at=now())
         self.c12(sender_deleted_at=now())
         self.c13()
-        self.check_update('postman_undelete', 'deleted_at', pk)
+        self.check_update('postman_undelete', 'Messages or conversations successfully recovered.', 'deleted_at', pk)
 
     def test_undelete_conversation(self):
         "Test undelete action on conversations."
         ArchiveView, DeleteView, UndeleteView)
 
 
-urlpatterns = patterns('postman.views',
+urlpatterns = patterns('',
     url(r'^inbox/(?:(?P<option>'+OPTIONS+')/)?$', InboxView.as_view(), name='postman_inbox'),
     url(r'^sent/(?:(?P<option>'+OPTIONS+')/)?$', SentView.as_view(), name='postman_sent'),
     url(r'^archives/(?:(?P<option>'+OPTIONS+')/)?$', ArchivesView.as_view(), name='postman_archives'),

postman/urls_for_tests.py

+"""
+URLconf for tests.py usage.
+
+"""
+from __future__ import unicode_literals
+
+from django.conf import settings
+try:
+    from django.conf.urls import patterns, include, url  # django 1.4
+except ImportError:
+    from django.conf.urls.defaults import *  # "patterns, include, url" is enough for django 1.3, "*" for django 1.2
+from django.forms import ValidationError
+from django.views.generic.base import RedirectView
+
+from . import OPTIONS
+from .views import (InboxView, SentView, ArchivesView, TrashView,
+        WriteView, ReplyView, MessageView, ConversationView,
+        ArchiveView, DeleteView, UndeleteView)
+
+
+# user_filter function set
+def user_filter_reason(user):
+    if user.get_username() == 'bar':
+        return 'some reason'
+    return None
+def user_filter_no_reason(user):
+    return ''
+def user_filter_false(user):
+    return False
+def user_filter_exception(user):
+    if user.get_username() == 'bar':
+        raise ValidationError(['first good reason', "anyway, I don't like {0}".format(user.get_username())])
+    return None
+
+# exchange_filter function set
+def exch_filter_reason(sender, recipient, recipients_list):
+    if recipient.get_username() == 'bar':
+        return 'some reason'
+    return None
+def exch_filter_no_reason(sender, recipient, recipients_list):
+    return ''
+def exch_filter_false(sender, recipient, recipients_list):
+    return False
+def exch_filter_exception(sender, recipient, recipients_list):
+    if recipient.get_username() == 'bar':
+        raise ValidationError(['first good reason', "anyway, I don't like {0}".format(recipient.get_username())])
+    return None
+
+# auto-moderation function set
+def moderate_as_51(message):
+    return 51
+def moderate_as_48(message):
+    return (48, "some reason")
+moderate_as_48.default_reason = 'some default reason'
+
+# quote formatters
+def format_subject(subject):
+    return "Re_ " + subject
+def format_body(sender, body):
+    return "{0} _ {1}".format(sender, body)
+
+postman_patterns = patterns('',
+    # Basic set
+    url(r'^inbox/(?:(?P<option>'+OPTIONS+')/)?$', InboxView.as_view(), name='postman_inbox'),
+    url(r'^sent/(?:(?P<option>'+OPTIONS+')/)?$', SentView.as_view(), name='postman_sent'),
+    url(r'^archives/(?:(?P<option>'+OPTIONS+')/)?$', ArchivesView.as_view(), name='postman_archives'),
+    url(r'^trash/(?:(?P<option>'+OPTIONS+')/)?$', TrashView.as_view(), name='postman_trash'),
+    url(r'^write/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(), name='postman_write'),
+    url(r'^reply/(?P<message_id>[\d]+)/$', ReplyView.as_view(), name='postman_reply'),
+    url(r'^view/(?P<message_id>[\d]+)/$', MessageView.as_view(), name='postman_view'),
+    url(r'^view/t/(?P<thread_id>[\d]+)/$', ConversationView.as_view(), name='postman_view_conversation'),
+    url(r'^archive/$', ArchiveView.as_view(), name='postman_archive'),
+    url(r'^delete/$', DeleteView.as_view(), name='postman_delete'),
+    url(r'^undelete/$', UndeleteView.as_view(), name='postman_undelete'),
+    (r'^$', RedirectView.as_view(url='inbox/')),
+
+    # Customized set
+    # 'success_url'
+    url(r'^write_sent/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(success_url='postman_sent'), name='postman_write_with_success_url_to_sent'),
+    url(r'^reply_sent/(?P<message_id>[\d]+)/$', ReplyView.as_view(success_url='postman_sent'), name='postman_reply_with_success_url_to_sent'),
+    url(r'^archive_arch/$', ArchiveView.as_view(success_url='postman_archives'), name='postman_archive_with_success_url_to_archives'),
+    url(r'^delete_arch/$', DeleteView.as_view(success_url='postman_archives'), name='postman_delete_with_success_url_to_archives'),
+    url(r'^undelete_arch/$', UndeleteView.as_view(success_url='postman_archives'), name='postman_undelete_with_success_url_to_archives'),
+    # 'max'
+    url(r'^write_max/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(max=1), name='postman_write_with_max'),
+    url(r'^reply_max/(?P<message_id>[\d]+)/$', ReplyView.as_view(max=1), name='postman_reply_with_max'),
+    # 'user_filter' on write
+    url(r'^write_user_filter_reason/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(user_filter=user_filter_reason), name='postman_write_with_user_filter_reason'),
+    url(r'^write_user_filter_no_reason/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(user_filter=user_filter_no_reason), name='postman_write_with_user_filter_no_reason'),
+    url(r'^write_user_filter_false/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(user_filter=user_filter_false), name='postman_write_with_user_filter_false'),
+    url(r'^write_user_filter_exception/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(user_filter=user_filter_exception), name='postman_write_with_user_filter_exception'),
+    # 'user_filter' on reply
+    url(r'^reply_user_filter_reason/(?P<message_id>[\d]+)/$', ReplyView.as_view(user_filter=user_filter_reason), name='postman_reply_with_user_filter_reason'),
+    url(r'^reply_user_filter_no_reason/(?P<message_id>[\d]+)/$', ReplyView.as_view(user_filter=user_filter_no_reason), name='postman_reply_with_user_filter_no_reason'),
+    url(r'^reply_user_filter_false/(?P<message_id>[\d]+)/$', ReplyView.as_view(user_filter=user_filter_false), name='postman_reply_with_user_filter_false'),
+    url(r'^reply_user_filter_exception/(?P<message_id>[\d]+)/$', ReplyView.as_view(user_filter=user_filter_exception), name='postman_reply_with_user_filter_exception'),
+    # 'exchange_filter' on write
+    url(r'^write_exch_filter_reason/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(exchange_filter=exch_filter_reason), name='postman_write_with_exch_filter_reason'),
+    url(r'^write_exch_filter_no_reason/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(exchange_filter=exch_filter_no_reason), name='postman_write_with_exch_filter_no_reason'),
+    url(r'^write_exch_filter_false/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(exchange_filter=exch_filter_false), name='postman_write_with_exch_filter_false'),
+    url(r'^write_exch_filter_exception/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(exchange_filter=exch_filter_exception), name='postman_write_with_exch_filter_exception'),
+    # 'exchange_filter' on reply
+    url(r'^reply_exch_filter_reason/(?P<message_id>[\d]+)/$', ReplyView.as_view(exchange_filter=exch_filter_reason), name='postman_reply_with_exch_filter_reason'),
+    url(r'^reply_exch_filter_no_reason/(?P<message_id>[\d]+)/$', ReplyView.as_view(exchange_filter=exch_filter_no_reason), name='postman_reply_with_exch_filter_no_reason'),
+    url(r'^reply_exch_filter_false/(?P<message_id>[\d]+)/$', ReplyView.as_view(exchange_filter=exch_filter_false), name='postman_reply_with_exch_filter_false'),
+    url(r'^reply_exch_filter_exception/(?P<message_id>[\d]+)/$', ReplyView.as_view(exchange_filter=exch_filter_exception), name='postman_reply_with_exch_filter_exception'),
+    # 'auto_moderators'
+    url(r'^write_moderate/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(auto_moderators=(moderate_as_51,moderate_as_48)), name='postman_write_moderate'),
+    url(r'^reply_moderate/(?P<message_id>[\d]+)/$', ReplyView.as_view(auto_moderators=(moderate_as_51,moderate_as_48)), name='postman_reply_moderate'),
+    # 'formatters'
+    url(r'^reply_formatters/(?P<message_id>[\d]+)/$', ReplyView.as_view(formatters=(format_subject, format_body)), name='postman_reply_formatters'),
+    url(r'^view_formatters/(?P<message_id>[\d]+)/$', MessageView.as_view(formatters=(format_subject, format_body)), name='postman_view_formatters'),
+    # auto-complete
+    url(r'^write_ac/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(autocomplete_channels=('postman_multiple_as1-1', None)), name='postman_write_auto_complete'),
+    url(r'^reply_ac/(?P<message_id>[\d]+)/$', ReplyView.as_view(autocomplete_channel='postman_multiple_as1-1'), name='postman_reply_auto_complete'),
+    # 'template_name'
+    url(r'^inbox_template/(?:(?P<option>'+OPTIONS+')/)?$', InboxView.as_view(template_name='postman/fake.html'), name='postman_inbox_template'),
+    url(r'^sent_template/(?:(?P<option>'+OPTIONS+')/)?$', SentView.as_view(template_name='postman/fake.html'), name='postman_sent_template'),
+    url(r'^archives_template/(?:(?P<option>'+OPTIONS+')/)?$', ArchivesView.as_view(template_name='postman/fake.html'), name='postman_archives_template'),
+    url(r'^trash_template/(?:(?P<option>'+OPTIONS+')/)?$', TrashView.as_view(template_name='postman/fake.html'), name='postman_trash_template'),
+    url(r'^write_template/(?:(?P<recipients>[\w.@+-:]+)/)?$', WriteView.as_view(template_name='postman/fake.html'), name='postman_write_template'),
+    url(r'^reply_template/(?P<message_id>[\d]+)/$', ReplyView.as_view(template_name='postman/fake.html'), name='postman_reply_template'),
+    url(r'^view_template/(?P<message_id>[\d]+)/$', MessageView.as_view(template_name='postman/fake.html'), name='postman_view_template'),
+    url(r'^view_template/t/(?P<thread_id>[\d]+)/$', ConversationView.as_view(template_name='postman/fake.html'), name='postman_view_conversation_template'),
+)
+
+urlpatterns = patterns('',
+    (r'^accounts/login/$', 'django.contrib.auth.views.login'),  # because of the login_required decorator
+    (r'^messages/', include(postman_patterns)),
+)
+
+# because of fields.py/AutoCompleteWidget/render()/reverse()
+if 'ajax_select' in settings.INSTALLED_APPS:
+    urlpatterns += patterns('',
+        (r'^ajax_select/', include('ajax_select.urls')),  # django-ajax-selects
+    )
+
+# optional
+if 'notification' in settings.INSTALLED_APPS:
+    urlpatterns += patterns('',
+        (r'^notification/', include('notification.urls')),  # django-notification
+    )
 except ImportError:
     from datetime import datetime
     now = datetime.now
-from django.utils.translation import ugettext as _
+from django.utils.translation import ugettext as _, ugettext_lazy as lz_
 from django.views.decorators.csrf import csrf_protect
 from django.views.generic import FormView, TemplateView, View
 
 class ArchiveView(UpdateMessageMixin, View):
     """Mark messages/conversations as archived."""
     field_bit = 'archived'
-    success_msg = _("Messages or conversations successfully archived.")
+    success_msg = lz_("Messages or conversations successfully archived.")
     field_value = True
 
 
 class DeleteView(UpdateMessageMixin, View):
     """Mark messages/conversations as deleted."""
     field_bit = 'deleted_at'
-    success_msg = _("Messages or conversations successfully deleted.")
+    success_msg = lz_("Messages or conversations successfully deleted.")
     field_value = now()
 
 
 class UndeleteView(UpdateMessageMixin, View):
     """Revert messages/conversations from marked as deleted."""
     field_bit = 'deleted_at'
-    success_msg = _("Messages or conversations successfully recovered.")
+    success_msg = lz_("Messages or conversations successfully recovered.")