Patrick Samson avatar Patrick Samson committed 31c63e1

copy of pages from docs/

Comments (0)

Files changed (8)

+== Features ==
+
+=== Direct write to ===
+
+In your site pages, you can put links to initiate the recipient name(s).
+
+Example:
+{{{
+    <a href="{% url postman_write username %}">write to {{ username }}</a>
+}}}
+Separate multiple usernames with a {{{:}}} character.
+
+Example:
+{{{
+    <a href="{% url postman_write 'adm1:adm2:adm3' %}">write to admins</a>
+}}}
+
+=== Prefilled fields ===
+
+You may initiate some field contents by providing a query string in the link.
+
+Example:
+{{{
+    <a href="{% url postman_write %}?subject=details request&body=give me details about ...">ask for details</a>
+}}}
+
+=== Min/Max recipients ===
+
+If you need to constraint the maximum number of recipients in the forms, you can pass the optional
+{{{max}}} parameter to the view.
+There is no parameter for a minimum number, but you can code a custom form and pass a {{{min}}} parameter
+to the recipient field (see Advanced Usage below for details).
+
+Views supporting the parameter are: {{{write}}}, {{{reply}}}.
+
+But this parameter does not apply to the default {{{AnonymousWriteForm}}} for visitors:
+The maximum is enforced to 1 (see Advanced Usage below to know how),
+as we want the features available to anonymous users kept to the strict minimum.
+
+Example:
+{{{
+    urlpatterns = patterns('postman.views',
+        # ...
+        url(r'^write/(?:(?P<recipients>[\w.@+-:]+)/)?$', 'write',
+            {'max': 3}, name='postman_write'),
+        # ...
+    )
+}}}
+
+==== Advanced usage ====
+
+If you define your own custom form, you may specify {{{min}}} and {{{max}}} parameters to the recipients field.
+
+For example:
+{{{
+    from postman.forms import WriteForm
+    class MyWriteForm(WriteForm):
+        recipients = CommaSeparatedUserField(label="Recipients", min=2, max=5)
+}}}
+
+If you do not want the fixed {{{max}}} parameter of the recipients field in your custom form to be superseded
+by the parameter passed to the view, set the form atttribute {{{can_overwrite_limits}}} to False.
+
+For example:
+{{{
+    class MyThreeAnonymousWriteForm(MyBaseAnonymousWriteForm):
+        can_overwrite_limits = False
+        recipients = CommaSeparatedUserField(label="Recipients", max=3)
+}}}
+
+See also:
+
+* the {{{POSTMAN_DISALLOW_MULTIRECIPIENTS}}} setting in [[Quick Start Guide]]
+
+=== User filter ===
+
+If there are some situations where a user should not be a recipient, you can write a filter
+and pass it to the view.
+
+Views supporting a user filter are: {{{write}}}, {{{reply}}}.
+
+Example:
+{{{
+    def my_user_filter(user):
+        if user.get_profile().is_absent:
+            return "is away"
+        return None
+
+    urlpatterns = patterns('postman.views',
+        # ...
+        url(r'^write/(?:(?P<recipients>[\w.@+-:]+)/)?$', 'write',
+            {'user_filter': my_user_filter}, name='postman_write'),
+        # ...
+    )
+}}}
+
+The filter will be called for each recipient, for validation.
+
+//Input//:
+
+* {{{user}}}: a User instance, as the recipient of the message
+
+//Output//:
+
+If the recipient is allowed, just return {{{None}}}.
+
+To forbid the message, use one of these means:
+
+* return {{{False}}} or {{{''}}}, if you do not want to give a reason for the refusal.
+  The error message will be: "Some usernames are rejected: foo, bar."
+
+* return a string, as a reason for the refusal.
+  The error message will be: "Some usernames are rejected: foo (reason), bar (reason)."
+
+* raise a {{{ValidationError}}} with an error message to your liking.
+
+==== Advanced usage ====
+
+If you define your own custom form, you may specify a user filter into it.
+
+For example:
+{{{
+    def my_user_filter(user):
+        # ...
+        return None
+
+    from postman.forms import WriteForm
+    class MyWriteForm(WriteForm):
+        recipients = CommaSeparatedUserField(label="Recipients", user_filter=my_user_filter)
+}}}
+
+=== Exchange filter ===
+
+If there are some situations where an exchange should not take place, you can write a filter
+and pass it to the view.
+Typical usages would be: blacklists, users that do not want solicitation from visitors.
+
+Views supporting an exchange filter are: {{{write}}}, {{{reply}}}.
+
+An example, with the django-relationships application:
+{{{
+    def my_exchange_filter(sender, recipient, recipients_list):
+        if recipient.relationships.exists(sender, RelationshipStatus.objects.blocking()):
+            return "has blacklisted you"
+        return None
+
+    urlpatterns = patterns('postman.views',
+        # ...
+        url(r'^write/(?:(?P<recipients>[\w.@+-:]+)/)?$', 'write',
+            {'exchange_filter': my_exchange_filter}, name='postman_write'),
+        # ...
+    )
+}}}
+
+The filter will be called for each couple to validate that the exchange is possible.
+
+//Inputs//:
+
+* {{{sender}}}: a User instance, as the sender of the message, or None if the writer is not authenticated
+* {{{recipient}}}: a User instance, as the recipient of the message
+* {{{recipients_list}}}: the full list of recipients.
+  Provided as a convenient additional element of decision.
+
+//Output//:
+
+If the exchange is allowed, just return {{{None}}}.
+
+To forbid the exchange, use one of these means:
+
+* return {{{False}}} or {{{''}}}, if you do not want to give a reason for the refusal.
+  The error message will be: "Exchanging with some users is not possible: foo, bar."
+
+* return a string, as a reason for the refusal.
+  The error message will be: "Exchanging with some users is not possible: foo (reason), bar (reason)."
+
+* raise a {{{ValidationError}}} with an error message to your liking.
+
+==== Advanced usage ====
+
+If you define your own custom form, you may specify an exchange filter into it.
+
+For example::
+{{{
+    def my_exchange_filter(sender, recipient, recipients_list):
+        # ...
+        return None
+
+    from postman.forms import WriteForm
+    class MyWriteForm(WriteForm):
+        exchange_filter = staticmethod(my_exchange_filter)
+}}}
+
+== Auto-complete field ===
+
+An auto-complete fonctionality may be useful on the recipients field.
+
+To activate the option, set at least the {{{arg_default}}} key in the {{{POSTMAN_AUTOCOMPLETER_APP}}} dictionary.
+If the default ajax_select application is used, define a matching entry in the {{{AJAX_LOOKUP_CHANNELS}}} dictionary.
+
+Example:
+{{{
+    AJAX_LOOKUP_CHANNELS = {
+        'postman_users': dict(model='auth.user', search_field='username'),
+    }
+    POSTMAN_AUTOCOMPLETER_APP = {
+        'arg_default': 'postman_users',
+    }
+}}}
+
+Support for multiple recipients is not turn on by default by django-ajax-selects.
+To allow this capability, you have to pass the option {{{multiple: true}}}.
+
+Make your own templates, based on these two files, provided as implementation examples:
+
+* postman/templates/autocomplete_postman_multiple.html
+* postman/templates/autocomplete_postman_single.html
+
+These examples include a correction necessary for the support of the 'multiple' option
+(in version 1.1.4 of django-ajax-selects).
+
+==== Customization ====
+
+You may attach a specific channel, different from the default one, to a particular view.
+
+Views supporting an auto-complete parameter are: {{{write}}}, {{{reply}}}.
+
+For the {{{write}}} view, the parameter is named {{{autocomplete_channels}}} (note the plural).
+It supports two variations:
+
+* a 2-tuple of channels names: the first one for authenticated users, the second for visitors.
+  Specify {{{None}}} if you let the default channel name for one of the tuple parts.
+* a single channel name: the same for users and visitors
+
+For the {{{reply}}} view, the parameter is named {{{autocomplete_channel}}} (note the singular).
+The value is the channel name.
+
+Example:
+{{{
+    urlpatterns = patterns('postman.views',
+        # ...
+        url(r'^write/(?:(?P<recipients>[\w.@+-:]+)/)?$', 'write',
+            {'autocomplete_channels': (None,'anonymous_ac')}, name='postman_write'),
+        url(r'^reply/(?P<message_id>[\d]+)/$', 'reply',
+            {'autocomplete_channel': 'reply_ac'}, name='postman_reply'),
+        # ...
+    )
+}}}
+
+Example:
+{{{
+    urlpatterns = patterns('postman.views',
+        # ...
+        url(r'^write/(?:(?P<recipients>[\w.@+-:]+)/)?$', 'write',
+            {'autocomplete_channels': 'write_ac'}, name='postman_write'),
+        # ...
+    )
+}}}
+
+==== Advanced usage ====
+
+If you define your own custom form, you may specify an autocomplete channel into it.
+
+For example:
+{{{
+    from postman.forms import WriteForm
+    class MyWriteForm(WriteForm):
+        recipients = CommaSeparatedUserField(label="Recipients", channel='my_channel')
+}}}

Frequently_Asked_Questions.wiki

+== Frequently-asked questions ==
+
+=== General ===
+
+**I don't want to bother with the moderation feature, how to bypass it?**
+
+    Set the configuration option:
+
+    {{{POSTMAN_AUTO_MODERATE_AS = True}}}
 Each user has access to a collection of messages, stored in folders:
 
     * **{{{Inbox   }}}** for incoming messages
-    * **{{{Sent    }}}** for sent messages
+    * **{{{Sent&nbsp;}}}** for sent messages
     * **{{{Archives}}}** for archived messages
     * **{{{Trash   }}}** for messages marked as deleted
 
 
 == Contents ==
 
-   * [[quickstart]]
-   * [[features]]
-   * [[tags-filters]]
-   * [[management]]
-   * [[faq]]
-
-
+   * [[Quick Start Guide]]
+   * [[Moderation]]
+   * [[Views]]
+   * [[Features]]
+   * [[Tags and Filters]]
+   * [[Management Commands]]
+   * [[Frequently Asked Questions]]

Management_Commands.wiki

+== Management Commands ==
+
+=== postman_cleanup ===
+
+When a user deletes a message, the object is not deleted from the database right away, it is moved to a //trash// folder.
+One reason is to allow a message to be undeleted if the user wants to retrieve it.
+Another reason is that there is only one copy of a message for both the sender and the recipient, so the message must
+be marked for deletion by the two parties before to be considered for a withdraw.
+An additional constraint is that a message may be a part of a thread and the reply chain must be kept consistent.
+
+So there are some criteria to fulfill for a record to be really deleted from the database:
+
+* both the sender and the recipient must have marked the message as deleted
+* if the message is in a thread, all the messages of the thread must be marked for deletion
+* the delete action must be old enough
+
+A management command is provided for this purpose:
+
+**django-admin.py postman_cleanup**
+
+It can be run as a cronjob or directly.
+
+The {{{--days}}} option can be used to specify the minimal number of days a message/thread must have been marked for deletion.
+Default is 30 days.
+
+=== postman_checkup ===
+
+A management command to run a test suite on the messages currently in the database.
+It checks messages and threads for possible inconsistencies, in a read-only mode. No change is made on the data.
+
+**django-admin.py postman_checkup**
+
+It can be run directly or more profitably as a nightly cronjob.
+== Moderation ==
+
+When created, a message is in a //pending// state. It is not delivered to the recipient immediately.
+By default, some human must review its contents and either accept or reject the message.
+
+Moderation is done through the Admin site. To ease the action, a special message type is available:
+PendingMessage. It's nothing but the classic Message type but:
+
+* It is intended to collect only messages in the pending state
+* A dedicated simplified change view is available, with two main buttons: Accept and Reject
+
+The moderator can give a reason in case of the rejection of the message.
+If provided, this piece of information will be reported in the notification to the sender.
+
+=== Auto moderators ===
+
+You may automate the moderation by giving zero, one, or many auto-moderator functions to the views.
+The value of the parameter can be a single function or a sequence of functions as a tuple or a list.
+
+Views supporting an {{{auto-moderators}}} parameter are: {{{write}}}, {{{reply}}}.
+
+Example:
+{{{
+    def mod1(message):
+        # ...
+        return None
+
+    def mod2(message):
+        # ...
+        return None
+    mod2.default_reason = 'mod2 default reason'
+
+    urlpatterns = patterns('postman.views',
+        # ...
+        url(r'^write/(?:(?P<recipients>[\w.@+-:]+)/)?$', 'write',
+            {'auto_moderators': (mod1, mod2)}, name='postman_write'),
+        url(r'^reply/(?P<message_id>[\d]+)/$', 'reply',
+            {'auto_moderators': mod1}, name='postman_reply'),
+        # ...
+    )
+}}}
+
+Each auto-moderator function will be called for the message to moderate,
+in the same order as defined in the parameter.
+
+//Input//:
+
+* {{{message}}}: a Message instance
+
+//Output//:
+
+The structure of the output is either a {{{rating}}} or a tuple {{{(rating, reason)}}}.
+
+{{{rating}}} may take the following values:
+
+* {{{None}}}
+* {{{True}}}, interpreted as 100
+* {{{False}}}, interpreted as 0
+* an integer between 0 and 100
+
+{{{reason}}} is a string, giving a specific reason for a rejection.
+If not provided, a default reason will be taken from the attribute {{{default_reason}}} of the function,
+if any. Otherwise, there will be no reason.
+
+The processing of the chain of auto-moderators is managed by these rules:
+
+# If return is {{{None}}} or outside the range 0..100, the auto-moderator is neutral
+# If return is 0, no other function is processed, the message is rejected
+# If return is 100, no other function is processed, the message is accepted
+# Otherwise, the rating will count for an average amongst the other returns
+
+At the end of the loop, if the decision is not final, the sequence is:
+
+# If there was no valid rating at all, then the {{{POSTMAN_AUTO_MODERATE_AS}}} setting applies.
+# An average rating is computed: if greater or equal to 50, the message is accepted.
+# The message is rejected. The final reason is the comma separated collection of reasons
+   related to moderators having returned a rating lesser than 50.
+
+

Quick_Start_Guide.wiki

+== Quick start guide ==
+
+=== Requisites and dependances ===
+
+Python version >= 2.6
+
+Some reasons:
+
+* use of {{{str.format()}}}
+
+Django version >= 1.2.2
+
+Some reasons:
+
+* use of {{{self.stdout}}} in management commands
+
+=== Installation ===
+
+Get the code from the repository, which is hosted at [[http://bitbucket.org/|Bitbucket]].
+
+You have two main ways to obtain the latest code and documentation:
+
+With the version control software Mercurial installed, get a local copy by typing:
+{{{
+    hg clone http://bitbucket.org/psam/django-postman/
+}}}
+Or download a copy of the package, which is available in several compressed formats,
+either from the {{{Download}}} tab or from the {{{get source}}} menu option.
+
+In both case, make sure the directory is accessible from the Python import path.
+
+=== Configuration ===
+
+==== Required settings ====
+
+Add {{{postman}}} to the {{{INSTALLED_APPS}}} setting of your project.
+
+Run a {{{manage.py syncdb}}}.
+
+Include the URLconf {{{postman.urls}}} in your project's root URL configuration.
+
+==== Optional settings ====
+
+If you want to make use of a {{{postman_unread_count}}} context variable in your templates,
+add {{{postman.context_processors.inbox}}} to the {{{TEMPLATE_CONTEXT_PROCESSORS}}} setting of your project.
+
+You may specify some additional configuration options in your {{{settings.py}}}:
+
+{{{POSTMAN_DISALLOW_ANONYMOUS}}}
+    Set it to True if you do not allow visitors to write to users. That way, messaging is restricted to a User-to-User exchange.
+
+    //Defaults to//: False.
+
+{{{POSTMAN_DISALLOW_MULTIRECIPIENTS}}}
+    Set it to True if you do not allow more than one username in the recipient field.
+
+    //Defaults to//: False.
+
+{{{POSTMAN_DISALLOW_COPIES_ON_REPLY}}}
+    Set it to True if you do not allow additional recipients when replying.
+
+    //Defaults to//: False.
+    
+{{{POSTMAN_AUTO_MODERATE_AS}}}
+    The default moderation status when no auto-moderation functions, if any, were decisive.
+
+    * {{{True}}} to accept messages.
+    * {{{False}}} to reject messages.
+    * {{{None}}} to leave messages to a moderator review.
+
+    //Defaults to//: None.
+
+    To disable the moderation feature (no control, no filter):
+    * Set this option to True
+    * Do not provide any auto-moderation functions
+
+{{{POSTMAN_NOTIFIER_APP}}}
+    A notifier application name, used in preference to the basic emailing, to notify users of their rejected or received messages.
+
+    //Defaults to//: 'notification', as in django-notification.
+
+    If you already have a notifier application with the default name in the installed applications
+    but do not want it to be used by this application, set the option to None.
+
+{{{POSTMAN_MAILER_APP}}}
+    An email application name, used in preference to the basic django.core.mail, to send emails.
+
+    //Defaults to//: 'mailer', as in django-mailer.
+
+    If you already have a mailer application with the default name in the installed applications
+    but do not want it to be used by this application, set the option to None.
+
+{{{POSTMAN_AUTOCOMPLETER_APP}}}
+    An auto completer application specification, useful for recipient fields.
+    To enable the feature, define a dictionary with these keys:
+
+    * 'name'
+        The name of the auto completer application.
+        Defaults to 'ajax_select'
+    * 'field'
+        The model class name.
+        Defaults to 'AutoCompleteField'
+    * 'arg_name'
+        The name of the argument
+        Defaults to 'channel'
+    * 'arg_default'
+        No default value. This is a mandatory default value, but you may supersede it in the field definition of a custom form or
+        pass it in the url pattern definitions.
+
+    //Defaults to//: an empty dictionary.
+
+==== Templates ====
+
+A complete working set of templates is provided with the application.
+You may use it as it is with a CSS design of your own, reuse or extend some parts of it, or only view it as an example.
+
+==== Medias ====
+A CSS file is provided with the application, for the Admin site. It is not mandatory.
+
+=== Examples ===
+
+{{{settings.py}}}:
+{{{
+    INSTALLED_APPS = (
+        # ...
+        'postman',
+        # ...
+    )
+    # POSTMAN_DISALLOW_ANONYMOUS = True # default is False
+    # POSTMAN_DISALLOW_MULTIRECIPIENTS = True # default is False
+    # POSTMAN_DISALLOW_COPIES_ON_REPLY = 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_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 {}
+}}}
+
+{{{urls.py}}}:
+{{{
+    url(r'^messages/', include('postman.urls')),
+}}}

Tags_and_Filters.wiki

+== Tags and Filters ==
+
+=== Tags ===
+
+==== postman_unread ====
+
+Give the number of unread messages for a user.
+Return nothing (an empty string) for anonymous users.
+
+Storing the count in a variable for further processing is advised, such as:
+{{{
+    {% postman_unread as unread_count %}
+    ...
+    {% if unread_count %}
+        You have <strong>{{ unread_count }}</strong> unread messages.
+    {% endif %}
+}}}
+
+==== postman_order_by ====
+
+Return a formatted GET query string, usable to have the messages list presented in a specific order.
+This string must be put in the href attribute of a <a> HTML tag.
+
+One argument is required: a keyword to tell the field used for the order. Supported values are:
+
+* sender
+* recipient
+* subject
+* date
+
+If the list is already ordered by the keyword, the returned value will specify the reversed order.
+If there are other existing parameters, such as a page number, they are preserved in the resulting output.
+
+=== Filters ===
+
+==== or_me ====
+
+If the value is equal to the argument, replace it with the constant string '<me>'.
+
+For example, if we have:
+{{{
+    {{ message.obfuscated_sender|or_me:user }}
+}}}
+and the sender is the currently logged-in user, the output is compacted to show only the simple pattern '<me>'.
+
+Note that this pattern can not be confused with the username of a real user, as the brackets are not in the valid character set
+for a username.
+
+==== compact_date ====
+
+Output a date as short as possible. The argument must provide three date format patterns.
+The pattern used depends on how far the date is from the current datetime:
+
+* pattern 1 if in the same day
+* pattern 2 if in the same year
+* pattern 3 otherwise
+
+For example::
+{{{
+    {{ message.sent_at|compact_date:_("G:i,j b,j/n/y") }}
+}}}
+With a message sent on "5 dec 2010, 09:21:58":
+
+|=for the day: |=the output is:
+|5 dec 2010    |9:21
+|6 dec 2010    |5 dec
+|1 jan 2011    |5/12/10
+== Custom views ==
+
+=== forms ===
+
+You can replace the default forms in views.
+
+Examples:
+{{{
+    urlpatterns = patterns('postman.views',
+        # ...
+        url(r'^write/(?:(?P<recipients>[\w.@+-:]+)/)?$', 'write',
+            {'form_classes': (MyCustomWriteForm, MyCustomAnonymousWriteForm)}, name='postman_write'),
+        url(r'^reply/(?P<message_id>[\d]+)/$', 'reply',
+            {'form_class': MyCustomFullReplyForm}, name='postman_reply'),
+        url(r'^view/(?P<message_id>[\d]+)/$', 'view',
+            {'form_class': MyCustomQuickReplyForm}, name='postman_view'),
+        # ...
+    )
+}}}
+
+=== templates ===
+
+You can replace the default template name in all views.
+
+Example:
+{{{
+    urlpatterns = patterns('postman.views',
+        # ...
+        url(r'^view/(?P<message_id>[\d]+)/$', 'view',
+            {'template_name': 'my_custom_view.html'}, name='postman_view'),
+        # ...
+    )
+}}}
+
+=== after submission ===
+
+You can supersede the default view to return to after a successful submission.
+
+The default algorithm is:
+
+# Return where you came from
+# If it can not be known, fall back to the inbox view
+# But if the submission view has a {{{success_url}}} parameter, use it preferably
+# In all cases, a {{{next}}} parameter in the query string has higher precedence
+
+The parameter {{{success_url}}} is available to these views:
+
+* {{{write}}}
+* {{{reply}}}
+* {{{archive}}}
+* {{{delete}}}
+* {{{undelete}}}
+
+Example:
+{{{
+    urlpatterns = patterns('postman.views',
+        # ...
+        url(r'^reply/(?P<message_id>[\d]+)/$', 'reply',
+            {'success_url': 'postman_inbox'}, name='postman_reply'),
+        # ...
+    )
+}}}
+
+Example:
+{{{
+    <a href="{% url postman_reply message.id %}?next={{ next_url|urlencode }}">Reply</a>
+}}}
+
+=== reply formatters ===
+
+You can replace the default formatters used for replying.
+
+Examples:
+{{{
+    def format_subject(subject):
+        return "Re_ " + subject
+
+    def format_body(sender, body):
+        return "{0} _ {1}".format(sender, body)
+
+    urlpatterns = patterns('postman.views',
+        # ...
+        url(r'^reply/(?P<message_id>[\d]+)/$', 'reply',
+            {'formatters': (format_subject,format_body)}, name='postman_reply'),
+        url(r'^view/(?P<message_id>[\d]+)/$', 'view',
+            {'formatters': (format_subject,format_body)}, name='postman_view'),
+        # ...
+    )
+}}}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.