From: Patrick Samson Date: Fri, 24 Dec 2010 17:57:59 +0000 (+0100) Subject: upload docs X-Git-Tag: 1.0.0~3 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=6755ce816f0c56687e9a0905446d61af32b4b454;p=django-postman.git upload docs --- diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..1d7e91a --- /dev/null +++ b/.hgignore @@ -0,0 +1,2 @@ +glob:*.pyc +glob:docs/_* diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..c0d7836 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,89 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-postman.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-postman.qhc" + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..4a9c0eb --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +# +# django-postman documentation build configuration file, created by +# sphinx-quickstart on Fri Nov 26 09:32:49 2010. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'django-postman' +copyright = u'2010, Patrick Samson' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0' +# The full version, including alpha/beta/rc tags. +release = '1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'django-postmandoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'django-postman.tex', u'django-postman Documentation', + u'Patrick Samson', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100644 index 0000000..bab1fe0 --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,11 @@ +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 + \ No newline at end of file diff --git a/docs/features.rst b/docs/features.rst new file mode 100644 index 0000000..112a5c6 --- /dev/null +++ b/docs/features.rst @@ -0,0 +1,271 @@ +Features +======== + +Direct write to +--------------- + +In the pages of your site, you can put links containing the recipient name(s). + +Example:: + + write to {{ username }} + +Separate multiple usernames with a ``:`` character. + +Example:: + + write to admins + +Prefilled fields +---------------- + +You may prefill the contents of some fields by providing a query string in the link. + +Example:: + + + ask for details + + +Recipients Min/Max +------------------ + +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 for knowing how), +in order to keep the features available to anonymous users to a strict minimum. + +Example:: + + urlpatterns = patterns('postman.views', + # ... + url(r'^write/(?:(?P[\w.@+-:]+)/)?$', 'write', + {'max': 3}, name='postman_write'), + # ... + ) + +Advanced usage +~~~~~~~~~~~~~~ +If you define your own custom form, you may specify a ``min`` parameter and a ``max`` parameter +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 ``can_overwrite_limits`` +form attribute 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 :ref:`optional_settings` + +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[\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 inside. + +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[\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: "Writing to some users is not possible: foo, bar." + +* return a string, as a reason for the refusal. + The error message will be: "Writing to 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 inside. + +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 functionality 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 turned 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, given 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[\w.@+-:]+)/)?$', 'write', + {'autocomplete_channels': (None,'anonymous_ac')}, name='postman_write'), + url(r'^reply/(?P[\d]+)/$', 'reply', + {'autocomplete_channel': 'reply_ac'}, name='postman_reply'), + # ... + ) + +Example:: + + urlpatterns = patterns('postman.views', + # ... + url(r'^write/(?:(?P[\w.@+-:]+)/)?$', 'write', + {'autocomplete_channels': 'write_ac'}, name='postman_write'), + # ... + ) + +Advanced usage +~~~~~~~~~~~~~~ + +If you define your own custom form, you may specify an autocomplete channel inside. + +For example:: + + from postman.forms import WriteForm + class MyWriteForm(WriteForm): + recipients = CommaSeparatedUserField(label="Recipients", channel='my_channel') diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..2814047 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,104 @@ +.. django-postman documentation master file, created by + sphinx-quickstart on Fri Nov 26 09:32:49 2010. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to django-postman's documentation! +========================================== + +This is an application for `Django `_-powered websites. + +Basically, the purpose is to allow authenticated users of a site to exchange private **messages** +within the site. In this documentation, the word *user* is to be understood as an instance of a User, +in the django.contrib.auth context. + +So it is mainly for a User-to-User exchange. +But it may be beneficial for a subscriber to receive inquiries from any visitor, ie even if non authenticated. +For instance, a subscriber as a service provider wants an ask-me-details form on a presentation page +to facilitate possible business contacts. +In this case, the visitor is presented a compose message form with an additional field to give +an email address for the reply. The email is obfuscated to the recipient. + +What is a message ? Roughly a piece of text, about a subject, sent by a sender to a recipient. +Each user has access to a collection of messages, stored in folders: + + | ``Inbox`` for incoming messages + | ``Sent`` for sent messages + | ``Archives`` for archived messages + | ``Trash`` for messages marked as deleted + +In folders, messages can be presented in two modes: + +* by **thread**, for a compact view: the original message and its replies are grouped in a set + to constitute one sole entry. + The lastest message (based on the time) is the representative of the set. +* by **message**, for an expanded view: each message is considered by itself. + +Here is a summary of features: + +* A non-User (email is undisclosed) can write to a User and get a reply + (can be disabled by configuration) +* Exchanges can be moderated (with auto-accept and auto-reject plug-ins) +* Optional recipient filter plug-ins +* Optional exchange filtering plug-ins (blacklists) +* Multi-recipient writing is possible (can be disabled by configuration) + with min/max constraints +* Messages are managed in threads +* Messages in folders are sortable by sender|recipient|subject|date +* 'Archives' folder in addition to classic Inbox, Sent and Trash folders +* A Quick-Reply form to only ask for a response text +* A cleanup management command to clear the old deleted messages + +It has support for optional additional applications: + +* Autocomplete recipient field (default is 'django-ajax-selects'), + with multiple recipient management +* New message notification (default is 'django-notification') +* Asynchronous mailer (default is 'django-mailer') + +Moderation +---------- +As an option, messages may need to be validated by a moderator before to be visible +to the recipient. Possible usages are: + +* to control there is no unwanted words in the text fields. +* to make sure that no direct contact informations are exchanged when the site is an intermediary + and delivers services based on subscription fees. + +Messages are first created in a ``pending`` state. A moderator is in charge to change them to +a ``rejected`` or ``accepted`` state. This operation can be done in two ways: + +* By a person, through the Admin site. A specially simplified change view is provided, + with one-click buttons to accept or reject the message. +* Automatically, through one or more auto-moderator functions. + +Filters +------- +As options, custom filters can disallow messages, in two ways: + +* **user filter**: a user is not in a state to act as a recipient +* **exchange filter**: criteria for a message between a specific sender + and a specific recipient are not fulfilled + +---- + +Contents: + +.. toctree:: + :maxdepth: 2 + + quickstart + moderation + views + features + tags-filters + management + faq + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..4b94827 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,113 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +set SPHINXBUILD=sphinx-build +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-postman.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-postman.ghc + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/docs/management.rst b/docs/management.rst new file mode 100644 index 0000000..b7a586c --- /dev/null +++ b/docs/management.rst @@ -0,0 +1,40 @@ +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 member of a thread and the reply chain +must be kept consistent. + +So there are some criteria to fulfill by 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 action of deletion must have been done enough time ago + +A management command is provided for this purpose: + +**django-admin.py postman_cleanup** + +It can be run as a cron job 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 value is 30 days. + +postman_checkup +--------------- + +A management command to run a test suite on the messages presently 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 better as a nightly cron job. diff --git a/docs/moderation.rst b/docs/moderation.rst new file mode 100644 index 0000000..92de994 --- /dev/null +++ b/docs/moderation.rst @@ -0,0 +1,82 @@ +Moderation +========== + +When created, a message is in a *pending* state. It is not delivered to the recipient +immediately. By default, some person must review its contents and must 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 else 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 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 one 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[\w.@+-:]+)/)?$', 'write', + {'auto_moderators': (mod1, mod2)}, name='postman_write'), + url(r'^reply/(?P[\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 the one set 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`` +* 0 or ``False`` +* 100 or ``True`` +* an integer between 1 and 99 + +``reason`` is a string, giving a specific reason for a rejection. +If not provided, a default reason will be taken from the ``default_reason`` attribute +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 among the full set of returned ratings + +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 a comma separated collection of reasons + coming from moderators having returned a rating lesser than 50. + + diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 0000000..5200128 --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,204 @@ +.. _quickstart: + +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 `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: + +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 you 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 you 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 set of working templates is provided with the application. +You may use it as it is with a CSS design of yours, re-use it or extend some parts of it, +or only view it as an example. + +Relations between templates:: + + base.html + |_ base_folder.html + | |_ inbox.html + | |_ sent.html + | |_ archives.html + | |_ trash.html + |_ base_write.html + | |_ write.html + | |_ reply.html + |_ view.html + +If the django-ajax-selects application is used, the following URLs are referenced by this set: + +* {% admin_media_prefix %}js/jquery.min.js +* {{ MEDIA_URL }}js/jquery.autocomplete.min.js +* {{ MEDIA_URL }}css/jquery.autocomplete.css +* {{ MEDIA_URL }}css/indicator.gif + +The ``postman/base.html`` template extends a ``base.html`` site template, +in which some blocks are expected: + +* title: in , at least for a part of the entire title string +* extrahead: in <html><head>, to put some <script> and <link> elements +* content: in <html><body>, to put the page contents +* postman_menu: in <html><body>, to put a navigation menu + +Medias +~~~~~~ +A CSS file is provided with the application, for the Admin site: ``postman/css/admin.css``. +It is not obligatory but makes the display more confortable. + +The file is provided under ``postman/medias/``. It's up to you to make it visible to the URL resolver. + +For example: + +* In a production environment, set /<MEDIA_URL>/postman/ as a symlink to <Postman_module>/medias/postman/ +* In a development environment (django's runserver), you can put in the URLconf, something like:: + + ('^' + settings.MEDIA_URL.strip('/') + r'/(?P<path>postman/.*)$', 'django.views.static.serve', + {'document_root': os.path.join(imp.find_module('postman')[1], 'medias')}), + +See also :ref:`styles` for the stylesheets of views. + +Examples +-------- + +``settings.py``:: + + INSTALLED_APPS = ( + # ... + '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_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``:: + + (r'^messages/', include('postman.urls')), diff --git a/docs/tags-filters.rst b/docs/tags-filters.rst new file mode 100644 index 0000000..440f9c0 --- /dev/null +++ b/docs/tags-filters.rst @@ -0,0 +1,97 @@ +Tags and Filters +================ + +The following tags and filters are available to your templates by loading the library:: + + {% load postman_tags %} + +Here are the other special libraries in the ``postman/templatetags/`` directory, +that are not intended for your site design: + +* ``postman_admin_modify.py``: a library exclusively designed for a customized change_form + template used in the Admin site for the moderation of pending messages. + +* ``pagination_tags_for_tests.py``: a mock of the django-pagination application template tags, + only usable for the test suite in case the real application is not installed. + To rename to ``pagination_tags.py`` during the test session. + +Tags +---- + +postman_unread +~~~~~~~~~~~~~~ + +Gives the number of unread messages for a user. +Returns 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 +~~~~~~~~~~~~~~~~ + +Returns 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 specify the field used for the sort. +Supported values are: + +* sender +* recipient +* subject +* date + +If the list is already sorted 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. + +Example:: + + <a href="{% postman_order_by subject %}">...</a> + +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 cannot be confused with the username of a real user, +because 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 the date compares to the current instant: + +* 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 A,M j,n/j/y") }} + +With a message sent on "5 dec 2010, 09:21:58": + +============ ============== +for the day: the output is: +============ ============== +5 dec 2010 9:21 AM +6 dec 2010 Dec 5 +1 jan 2011 12/5/10 +============ ============== diff --git a/docs/views.rst b/docs/views.rst new file mode 100644 index 0000000..3be4473 --- /dev/null +++ b/docs/views.rst @@ -0,0 +1,102 @@ +Custom views +============ + +.. _styles: + +styles +------ +Here is a sample of some CSS rules, usable for ``postman/views.html``:: + + .pm_message.pm_deleted { text-decoration: line-through; } + .pm_message.pm_deleted .pm_body { display: none; } + .pm_message.pm_archived { font-style: italic; color: grey; } + .pm_message.pm_unread .pm_subject { font-weight: bolder; } + .pm_message.pm_pending .pm_header { background-color: #FFC; } + .pm_message.pm_rejected .pm_header { background-color: #FDD; } + +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 where to return to, after a successful submission. + +The default algorithm is: + +#. Return where you came from +#. If it cannot 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 reply_to_pk %}?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'), + # ... + ) diff --git a/postman/locale/en/LC_MESSAGES/django.po b/postman/locale/en/LC_MESSAGES/django.po new file mode 100644 index 0000000..6a68aa0 --- /dev/null +++ b/postman/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,522 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-12-24 18:38+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: .\admin.py:22 +msgid "Sender and Recipient cannot be both undefined." +msgstr "" + +#: .\admin.py:29 +msgid "Visitor's email is in excess." +msgstr "" + +#: .\admin.py:34 +msgid "Visitor's email is missing." +msgstr "" + +#: .\admin.py:40 +msgid "Reading date must be later to sending date." +msgstr "" + +#: .\admin.py:45 +msgid "Deletion date by sender must be later to sending date." +msgstr "" + +#: .\admin.py:50 +msgid "Deletion date by recipient must be later to sending date." +msgstr "" + +#: .\admin.py:58 +msgid "Response date must be later to sending date." +msgstr "" + +#: .\admin.py:60 +msgid "The message cannot be replied without having been read." +msgstr "" + +#: .\admin.py:62 +msgid "Response date must be later to reading date." +msgstr "" + +#: .\admin.py:64 +msgid "Response date cannot be set without at least one reply." +msgstr "" + +#: .\admin.py:66 +msgid "The message cannot be replied without being in a thread." +msgstr "" + +#: .\admin.py:88 .\admin.py:157 .\templates\postman\view.html.py:5 +msgid "Message" +msgstr "" + +#: .\admin.py:93 +msgid "Dates" +msgstr "" + +#: .\admin.py:98 .\admin.py:161 +msgid "Moderation" +msgstr "" + +#: .\fields.py:22 +msgid "Some usernames are unknown or no more active: {users}." +msgstr "" + +#: .\fields.py:23 +msgid "" +"Ensure this value has at most {limit_value} distinct items (it has " +"{show_value})." +msgstr "" + +#: .\fields.py:24 +msgid "" +"Ensure this value has at least {limit_value} distinct items (it has " +"{show_value})." +msgstr "" + +#: .\fields.py:25 +msgid "Some usernames are rejected: {users}." +msgstr "" + +#: .\fields.py:26 .\forms.py:65 +msgid "{user.username}" +msgstr "" + +#: .\fields.py:27 .\forms.py:66 +msgid "{user.username} ({reason})" +msgstr "" + +#: .\forms.py:64 +msgid "Writing to some users is not possible: {users}." +msgstr "" + +#: .\forms.py:149 .\forms.py:161 +msgid "Recipients" +msgstr "" + +#: .\forms.py:149 .\forms.py:161 .\templates\postman\base_folder.html.py:26 +#: .\templates\postman\reply.html.py:4 +msgid "Recipient" +msgstr "" + +#: .\forms.py:160 +msgid "Email" +msgstr "" + +#: .\forms.py:176 +msgid "Undefined recipient." +msgstr "" + +#: .\forms.py:195 +msgid "Additional recipients" +msgstr "" + +#: .\forms.py:195 +msgid "Additional recipient" +msgstr "" + +#: .\models.py:19 +msgid "Pending" +msgstr "" + +#: .\models.py:20 +msgid "Accepted" +msgstr "" + +#: .\models.py:21 .\templates\postman\view.html.py:13 +msgid "Rejected" +msgstr "" + +#: .\models.py:197 +msgid "subject" +msgstr "" + +#: .\models.py:198 +msgid "body" +msgstr "" + +#: .\models.py:199 .\models.py:282 +msgid "sender" +msgstr "" + +#: .\models.py:200 .\models.py:306 +msgid "recipient" +msgstr "" + +#: .\models.py:202 +msgid "visitor" +msgstr "" + +#: .\models.py:203 +msgid "parent message" +msgstr "" + +#: .\models.py:204 +msgid "root message" +msgstr "" + +#: .\models.py:205 +msgid "sent at" +msgstr "" + +#: .\models.py:206 +msgid "read at" +msgstr "" + +#: .\models.py:207 +msgid "replied at" +msgstr "" + +#: .\models.py:208 +msgid "archived by sender" +msgstr "" + +#: .\models.py:209 +msgid "archived by recipient" +msgstr "" + +#: .\models.py:210 +msgid "deleted by sender at" +msgstr "" + +#: .\models.py:211 +msgid "deleted by recipient at" +msgstr "" + +#: .\models.py:213 +msgid "status" +msgstr "" + +#: .\models.py:215 +msgid "moderator" +msgstr "" + +#: .\models.py:216 +msgid "moderated at" +msgstr "" + +#: .\models.py:217 +msgid "rejection reason" +msgstr "" + +#: .\models.py:222 +msgid "message" +msgstr "" + +#: .\models.py:223 +msgid "messages" +msgstr "" + +#: .\models.py:334 +msgid "Undefined sender." +msgstr "" + +#: .\models.py:478 +msgid "pending message" +msgstr "" + +#: .\models.py:479 +msgid "pending messages" +msgstr "" + +#: .\utils.py:32 +msgid "> " +msgstr "" + +#: .\utils.py:48 +msgid "" +"\n" +"\n" +"{sender} wrote:\n" +"{body}\n" +msgstr "" + +#: .\utils.py:57 +msgid "Re: {subject}" +msgstr "" + +#: .\views.py:129 .\views.py:187 +msgid "Message successfully sent." +msgstr "" + +#: .\views.py:131 .\views.py:189 +msgid "Message rejected for at least one recipient." +msgstr "" + +#: .\views.py:277 +msgid "Select at least one object." +msgstr "" + +#: .\views.py:283 +msgid "Message(s) or thread(s) successfully archived." +msgstr "" + +#: .\views.py:288 +msgid "Message(s) or thread(s) successfully deleted." +msgstr "" + +#: .\views.py:293 +msgid "Message(s) or thread(s) successfully recovered." +msgstr "" + +#: .\management\__init__.py:14 +msgid "Message Rejected" +msgstr "" + +#: .\management\__init__.py:14 +msgid "Your message has been rejected" +msgstr "" + +#: .\management\__init__.py:15 +msgid "Message Received" +msgstr "" + +#: .\management\__init__.py:15 +msgid "You have received a message" +msgstr "" + +#: .\management\__init__.py:16 +msgid "Reply Received" +msgstr "" + +#: .\management\__init__.py:16 +msgid "You have received a reply" +msgstr "" + +#: .\templates\admin\postman\pendingmessage\change_form.html.py:17 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "" +msgstr[1] "" + +#: .\templates\admin\postman\pendingmessage\submit_line.html.py:6 +msgid "Accept" +msgstr "" + +#: .\templates\admin\postman\pendingmessage\submit_line.html.py:7 +msgid "Reject" +msgstr "" + +#: .\templates\postman\archives.html.py:3 +msgid "Archived Messages" +msgstr "" + +#: .\templates\postman\archives.html.py:7 +msgid "" +"Messages in this folder will never be removed. You can use this folder for " +"long term storage." +msgstr "" + +#: .\templates\postman\base.html.py:3 +msgid "Messaging" +msgstr "" + +#: .\templates\postman\base.html.py:6 +msgid "Inbox" +msgstr "" + +#: .\templates\postman\base.html.py:7 .\templates\postman\sent.html.py:3 +msgid "Sent Messages" +msgstr "" + +#: .\templates\postman\base.html.py:8 .\templates\postman\write.html.py:3 +msgid "Write" +msgstr "" + +#: .\templates\postman\base.html.py:9 +msgid "Archives" +msgstr "" + +#: .\templates\postman\base.html.py:10 +msgid "Trash" +msgstr "" + +#: .\templates\postman\base_folder.html.py:9 +msgid "Sorry, this page number is invalid." +msgstr "" + +#: .\templates\postman\base_folder.html.py:12 +msgid "by thread" +msgstr "" + +#: .\templates\postman\base_folder.html.py:13 +msgid "by message" +msgstr "" + +#: .\templates\postman\base_folder.html.py:17 +#: .\templates\postman\view.html.py:22 +msgid "Delete" +msgstr "" + +#: .\templates\postman\base_folder.html.py:18 +#: .\templates\postman\view.html.py:23 +msgid "Archive" +msgstr "" + +#: .\templates\postman\base_folder.html.py:19 +msgid "Undelete" +msgstr "" + +#: .\templates\postman\base_folder.html.py:24 +msgid "Action" +msgstr "" + +#: .\templates\postman\base_folder.html.py:25 +msgid "Sender" +msgstr "" + +#: .\templates\postman\base_folder.html.py:27 +msgid "Subject" +msgstr "" + +#: .\templates\postman\base_folder.html.py:28 +msgid "Date" +msgstr "" + +#: .\templates\postman\base_folder.html.py:43 +msgid "g:i A,M j,n/j/y" +msgstr "" + +#: .\templates\postman\base_folder.html.py:51 +msgid "No messages." +msgstr "" + +#: .\templates\postman\base_write.html.py:20 +msgid "Send" +msgstr "" + +#: .\templates\postman\email_user.txt.py:1 +msgid "Dear user," +msgstr "" + +#: .\templates\postman\email_user.txt.py:3 +#: .\templates\postman\email_visitor.txt.py:3 +#, python-format +msgid "On %(date)s, you asked to send a message to the user '%(recipient)s'." +msgstr "" + +#: .\templates\postman\email_user.txt.py:5 +#: .\templates\postman\email_visitor.txt.py:5 +msgid "Your message has been rejected by the moderator" +msgstr "" + +#: .\templates\postman\email_user.txt.py:5 +#: .\templates\postman\email_visitor.txt.py:5 +msgid ", for the following reason:" +msgstr "" + +#: .\templates\postman\email_user.txt.py:9 +#: .\templates\postman\email_visitor.txt.py:10 +#, python-format +msgid "On %(date)s, you sent a message to the user '%(sender)s'." +msgstr "" + +#: .\templates\postman\email_user.txt.py:10 +msgid "Your correspondent has given you an answer." +msgstr "" + +#: .\templates\postman\email_user.txt.py:11 +#, python-format +msgid "You have received a copy of a response from the user '%(sender)s'." +msgstr "" + +#: .\templates\postman\email_user.txt.py:13 +#, python-format +msgid "You have received a message from the user '%(sender)s'." +msgstr "" + +#: .\templates\postman\email_user.txt.py:16 +#: .\templates\postman\email_visitor.txt.py:14 +msgid "Thank you again for your interest in our services." +msgstr "" + +#: .\templates\postman\email_user.txt.py:17 +#: .\templates\postman\email_visitor.txt.py:16 +msgid "The site administrator" +msgstr "" + +#: .\templates\postman\email_user.txt.py:19 +#: .\templates\postman\email_visitor.txt.py:18 +msgid "" +"Note: This message is issued by an automated system.\n" +"Do not reply, this would not be taken into account." +msgstr "" + +#: .\templates\postman\email_user_subject.txt.py:1 +#: .\templates\postman\email_visitor_subject.txt.py:1 +#, python-format +msgid "Message \"%(subject)s\" on the site %(sitename)s" +msgstr "" + +#: .\templates\postman\email_visitor.txt.py:1 +msgid "Dear visitor," +msgstr "" + +#: .\templates\postman\email_visitor.txt.py:8 +msgid "As a reminder, please find below the content of your message." +msgstr "" + +#: .\templates\postman\email_visitor.txt.py:11 +msgid "Please find below the answer from your correspondent." +msgstr "" + +#: .\templates\postman\email_visitor.txt.py:15 +msgid "For more comfort, we encourage you to open an account on the site." +msgstr "" + +#: .\templates\postman\inbox.html.py:3 +msgid "Received Messages" +msgstr "" + +#: .\templates\postman\inbox.html.py:6 +msgid "Received" +msgstr "" + +#: .\templates\postman\reply.html.py:3 .\templates\postman\view.html.py:25 +#: .\templates\postman\view.html.py:28 .\templates\postman\view.html.py:31 +msgid "Reply" +msgstr "" + +#: .\templates\postman\sent.html.py:6 +msgid "Sent" +msgstr "" + +#: .\templates\postman\trash.html.py:3 +msgid "Deleted Messages" +msgstr "" + +#: .\templates\postman\trash.html.py:10 +msgid "" +"Messages in this folder can be removed from time to time. For long term " +"storage, use instead the archive folder." +msgstr "" + +#: .\templates\postman\view.html.py:5 +msgid "Thread" +msgstr "" + +#: .\templates\postman\view.html.py:13 +msgid ":" +msgstr "" + +#: .\templates\postman\view.html.py:20 +msgid "Back" +msgstr "" + +#: .\templatetags\postman_tags.py:34 +msgid "<me>" +msgstr "" diff --git a/postman/locale/fr/LC_MESSAGES/django.mo b/postman/locale/fr/LC_MESSAGES/django.mo new file mode 100644 index 0000000..7506dda Binary files /dev/null and b/postman/locale/fr/LC_MESSAGES/django.mo differ diff --git a/postman/locale/fr/LC_MESSAGES/django.po b/postman/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 0000000..6f5d51a --- /dev/null +++ b/postman/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,545 @@ +# django-postman French translation. +# Copyright (C) 2010 Patrick Samson +# This file is distributed under the same license as the django-postman package. +# Patrick Samson <maxcom@laposte.net>, 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: django-postman 1.0.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-12-22 22:57+0100\n" +"PO-Revision-Date: 2010-12-15 17:19+0100\n" +"Last-Translator: Patrick Samson <maxcom@laposte.net>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n>1;\n" + +#: .\admin.py:22 +msgid "Sender and Recipient cannot be both undefined." +msgstr "" +"Expéditeur et Destinataire ne peuvent pas être indéfinis tous les deux." + +#: .\admin.py:29 +msgid "Visitor's email is in excess." +msgstr "Le courriel du visiteur est en trop." + +#: .\admin.py:34 +msgid "Visitor's email is missing." +msgstr "Le courriel du visiteur est manquant." + +#: .\admin.py:40 +msgid "Reading date must be later to sending date." +msgstr "La date de lecture doit être postérieure à la date d'envoi." + +#: .\admin.py:45 +msgid "Deletion date by sender must be later to sending date." +msgstr "" +"La date de suppression par l'expéditeur doit être postérieure à la date " +"d'envoi." + +#: .\admin.py:50 +msgid "Deletion date by recipient must be later to sending date." +msgstr "" +"La date de suppression par le destinataire doit être postérieure à la date " +"d'envoi." + +#: .\admin.py:58 +msgid "Response date must be later to sending date." +msgstr "La date de réponse doit être postérieure à la date d'envoi." + +#: .\admin.py:60 +msgid "The message cannot be replied without having been read." +msgstr "Le message ne peut pas être répondu sans avoir été lu." + +#: .\admin.py:62 +msgid "Response date must be later to reading date." +msgstr "La date de réponse doit être postérieure à la date de lecture." + +#: .\admin.py:64 +msgid "Response date cannot be set without at least one reply." +msgstr "" +"La date de réponse ne peut pas être positionnée sans au moins une réponse." + +#: .\admin.py:66 +msgid "The message cannot be replied without being in a thread." +msgstr "Le message ne peut pas être répondu sans être dans une conversation." + +#: .\admin.py:88 .\admin.py:157 .\templates\postman\view.html.py:5 +msgid "Message" +msgstr "Message" + +#: .\admin.py:93 +msgid "Dates" +msgstr "Dates" + +#: .\admin.py:98 .\admin.py:161 +msgid "Moderation" +msgstr "Modération" + +#: .\fields.py:22 +msgid "Some usernames are unknown or no more active: {users}." +msgstr "Des noms d'utilisateur sont inconnus ou ne sont plus actifs : {users}." + +#: .\fields.py:23 +msgid "" +"Ensure this value has at most {limit_value} distinct items (it has " +"{show_value})." +msgstr "" +"Assurez-vous que cette valeur a au plus {limit_value} éléments distincts " +"(elle en a {show_value})." + +#: .\fields.py:24 +msgid "" +"Ensure this value has at least {limit_value} distinct items (it has " +"{show_value})." +msgstr "" +"Assurez-vous que cette valeur a au moins {limit_value} éléments distincts " +"(elle en a {show_value})." + +#: .\fields.py:25 +msgid "Some usernames are rejected: {users}." +msgstr "Des noms d'utilisateur sont rejetés : {users}." + +#: .\fields.py:26 .\forms.py:65 +msgid "{user.username}" +msgstr "" + +#: .\fields.py:27 .\forms.py:66 +msgid "{user.username} ({reason})" +msgstr "" + +#: .\forms.py:64 +msgid "Writing to some users is not possible: {users}." +msgstr "Écrire à certains utilisateurs n'est pas possible : {users}." + +#: .\forms.py:149 .\forms.py:161 +msgid "Recipients" +msgstr "Destinataires" + +#: .\forms.py:149 .\forms.py:161 .\templates\postman\base_folder.html.py:26 +#: .\templates\postman\reply.html.py:4 +msgid "Recipient" +msgstr "Destinataire" + +#: .\forms.py:160 +msgid "Email" +msgstr "Courriel" + +#: .\forms.py:176 +msgid "Undefined recipient." +msgstr "Destinataire indéfini." + +#: .\forms.py:195 +msgid "Additional recipients" +msgstr "Destinataires supplémentaires" + +#: .\forms.py:195 +msgid "Additional recipient" +msgstr "Destinataire supplémentaire" + +#: .\models.py:19 +msgid "Pending" +msgstr "En attente" + +#: .\models.py:20 +msgid "Accepted" +msgstr "Accepté" + +#: .\models.py:21 .\templates\postman\view.html.py:13 +msgid "Rejected" +msgstr "Rejeté" + +#: .\models.py:200 +msgid "subject" +msgstr "objet" + +#: .\models.py:201 +msgid "body" +msgstr "contenu" + +#: .\models.py:202 .\models.py:285 +msgid "sender" +msgstr "expéditeur" + +#: .\models.py:203 .\models.py:309 +msgid "recipient" +msgstr "destinataire" + +#: .\models.py:205 +msgid "visitor" +msgstr "visiteur" + +#: .\models.py:206 +msgid "parent message" +msgstr "message parent" + +#: .\models.py:207 +msgid "root message" +msgstr "message racine" + +#: .\models.py:208 +msgid "sent at" +msgstr "envoyé le" + +#: .\models.py:209 +msgid "read at" +msgstr "lu le" + +#: .\models.py:210 +msgid "replied at" +msgstr "répondu le" + +#: .\models.py:211 +msgid "archived by sender" +msgstr "archivé par l'expéditeur" + +#: .\models.py:212 +msgid "archived by recipient" +msgstr "archivé par le destinataire" + +#: .\models.py:213 +msgid "deleted by sender at" +msgstr "supprimé par l'expéditeur le" + +#: .\models.py:214 +msgid "deleted by recipient at" +msgstr "supprimé par le destinataire le" + +#: .\models.py:216 +msgid "status" +msgstr "état" + +#: .\models.py:218 +msgid "moderator" +msgstr "modérateur" + +#: .\models.py:219 +msgid "moderated at" +msgstr "modéré le" + +#: .\models.py:220 +msgid "rejection reason" +msgstr "motif de rejet" + +#: .\models.py:225 +msgid "message" +msgstr "message" + +#: .\models.py:226 +msgid "messages" +msgstr "messages" + +#: .\models.py:337 +msgid "Undefined sender." +msgstr "Expéditeur indéfini." + +#: .\models.py:481 +msgid "pending message" +msgstr "message en attente" + +#: .\models.py:482 +msgid "pending messages" +msgstr "messages en attente" + +#: .\utils.py:32 +msgid "> " +msgstr "" + +#: .\utils.py:48 +msgid "" +"\n" +"\n" +"{sender} wrote:\n" +"{body}\n" +msgstr "" +"\n" +"\n" +"{sender} a écrit :\n" +"{body}\n" + +#: .\utils.py:57 +msgid "Re: {subject}" +msgstr "" + +#: .\views.py:121 .\views.py:187 +msgid "Message successfully sent." +msgstr "Message envoyé avec succès." + +#: .\views.py:123 .\views.py:189 +msgid "Message rejected for at least one recipient." +msgstr "Message rejeté pour au moins un destinataire." + +#: .\views.py:272 +msgid "Select at least one object." +msgstr "Sélectionner au moins un objet." + +#: .\views.py:278 +msgid "Message(s) or thread(s) successfully archived." +msgstr "Message(s) ou conversation(s) archivé(s) avec succès." + +#: .\views.py:283 +msgid "Message(s) or thread(s) successfully deleted." +msgstr "Message(s) ou conversation(s) supprimé(s) avec succès." + +#: .\views.py:288 +msgid "Message(s) or thread(s) successfully recovered." +msgstr "Message(s) ou conversation(s) restauré(s) avec succès." + +#: .\management\__init__.py:14 +msgid "Message Rejected" +msgstr "Message Rejeté" + +#: .\management\__init__.py:14 +msgid "Your message has been rejected" +msgstr "Votre message a été rejeté" + +#: .\management\__init__.py:15 +msgid "Message Received" +msgstr "Message Reçu" + +#: .\management\__init__.py:15 +msgid "You have received a message" +msgstr "Vous avez reçu un message" + +#: .\management\__init__.py:16 +msgid "Reply Received" +msgstr "Réponse Reçue" + +#: .\management\__init__.py:16 +msgid "You have received a reply" +msgstr "Vous avez reçu une réponse" + +#: .\templates\admin\postman\pendingmessage\change_form.html.py:17 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "" +msgstr[1] "" + +#: .\templates\admin\postman\pendingmessage\submit_line.html.py:6 +msgid "Accept" +msgstr "Accepter" + +#: .\templates\admin\postman\pendingmessage\submit_line.html.py:7 +msgid "Reject" +msgstr "Rejeter" + +#: .\templates\postman\archives.html.py:3 +msgid "Archived Messages" +msgstr "Messages archivés" + +#: .\templates\postman\archives.html.py:7 +msgid "" +"Messages in this folder will never be removed. You can use this folder for " +"long term storage." +msgstr "" +"Les messages dans ce dossier ne seront jamais supprimés. Vous pouvez " +"utiliser ce dossier pour un stockage à long terme." + +#: .\templates\postman\base.html.py:3 +msgid "Messaging" +msgstr "Messagerie" + +#: .\templates\postman\base.html.py:6 +msgid "Inbox" +msgstr "Boîte de réception" + +#: .\templates\postman\base.html.py:7 .\templates\postman\sent.html.py:3 +msgid "Sent Messages" +msgstr "Messages envoyés" + +#: .\templates\postman\base.html.py:8 .\templates\postman\write.html.py:3 +msgid "Write" +msgstr "Écrire" + +#: .\templates\postman\base.html.py:9 +msgid "Archives" +msgstr "Archives" + +#: .\templates\postman\base.html.py:10 +msgid "Trash" +msgstr "Corbeille" + +#: .\templates\postman\base_folder.html.py:9 +msgid "Sorry, this page number is invalid." +msgstr "Désolé, ce numéro de page est invalide." + +#: .\templates\postman\base_folder.html.py:12 +msgid "by thread" +msgstr "par conversation" + +#: .\templates\postman\base_folder.html.py:13 +msgid "by message" +msgstr "par message" + +#: .\templates\postman\base_folder.html.py:17 +#: .\templates\postman\view.html.py:22 +msgid "Delete" +msgstr "Supprimer" + +#: .\templates\postman\base_folder.html.py:18 +#: .\templates\postman\view.html.py:23 +msgid "Archive" +msgstr "Archiver" + +#: .\templates\postman\base_folder.html.py:19 +msgid "Undelete" +msgstr "Restaurer" + +#: .\templates\postman\base_folder.html.py:24 +msgid "Action" +msgstr "Action" + +#: .\templates\postman\base_folder.html.py:25 +msgid "Sender" +msgstr "Expéditeur" + +#: .\templates\postman\base_folder.html.py:27 +msgid "Subject" +msgstr "Objet" + +#: .\templates\postman\base_folder.html.py:28 +msgid "Date" +msgstr "Date" + +#: .\templates\postman\base_folder.html.py:43 +msgid "g:i A,M j,n/j/y" +msgstr "G:i,j b,j/n/y" + +#: .\templates\postman\base_folder.html.py:51 +msgid "No messages." +msgstr "Pas de message." + +#: .\templates\postman\base_write.html.py:20 +msgid "Send" +msgstr "Envoyer" + +#: .\templates\postman\email_user.txt.py:1 +msgid "Dear user," +msgstr "Cher utilisateur," + +#: .\templates\postman\email_user.txt.py:3 +#: .\templates\postman\email_visitor.txt.py:3 +#, python-format +msgid "On %(date)s, you asked to send a message to the user '%(recipient)s'." +msgstr "" +"Le %(date)s, vous avez sollicité l'envoi d'un message à l'utilisateur '%" +"(recipient)s'." + +#: .\templates\postman\email_user.txt.py:5 +#: .\templates\postman\email_visitor.txt.py:5 +msgid "Your message has been rejected by the moderator" +msgstr "Votre message a été rejeté par le modérateur" + +#: .\templates\postman\email_user.txt.py:5 +#: .\templates\postman\email_visitor.txt.py:5 +msgid ", for the following reason:" +msgstr ", pour le motif suivant :" + +#: .\templates\postman\email_user.txt.py:9 +#: .\templates\postman\email_visitor.txt.py:10 +#, python-format +msgid "On %(date)s, you sent a message to the user '%(sender)s'." +msgstr "Le %(date)s, vous avez envoyé un message à l'utilisateur '%(sender)s'." + +#: .\templates\postman\email_user.txt.py:10 +msgid "Your correspondent has given you an answer." +msgstr "Votre correspondant vous a donné une réponse." + +#: .\templates\postman\email_user.txt.py:11 +#, python-format +msgid "You have received a copy of a response from the user '%(sender)s'." +msgstr "Vous avez reçu une copie d'une réponse de l'utilisateur '%(sender)s'." + +#: .\templates\postman\email_user.txt.py:13 +#, python-format +msgid "You have received a message from the user '%(sender)s'." +msgstr "Vous avez reçu un message de l'utilisateur '%(sender)s'." + +#: .\templates\postman\email_user.txt.py:16 +#: .\templates\postman\email_visitor.txt.py:14 +msgid "Thank you again for your interest in our services." +msgstr "Merci encore pour l'intérêt que vous portez à nos services." + +#: .\templates\postman\email_user.txt.py:17 +#: .\templates\postman\email_visitor.txt.py:16 +msgid "The site administrator" +msgstr "L'administrateur du site" + +#: .\templates\postman\email_user.txt.py:19 +#: .\templates\postman\email_visitor.txt.py:18 +msgid "" +"Note: This message is issued by an automated system.\n" +"Do not reply, this would not be taken into account." +msgstr "" +"NB: Ce message est émis par un automate. Ne faites\n" +"pas de réponse, elle ne serait pas prise en compte." + +#: .\templates\postman\email_user_subject.txt.py:1 +#: .\templates\postman\email_visitor_subject.txt.py:1 +#, python-format +msgid "Message \"%(subject)s\" on the site %(sitename)s" +msgstr "Message \"%(subject)s\" sur le site %(sitename)s" + +#: .\templates\postman\email_visitor.txt.py:1 +msgid "Dear visitor," +msgstr "Cher visiteur," + +#: .\templates\postman\email_visitor.txt.py:8 +msgid "As a reminder, please find below the content of your message." +msgstr "Pour rappel, veuillez trouver ci-dessous le contenu de votre message." + +#: .\templates\postman\email_visitor.txt.py:11 +msgid "Please find below the answer from your correspondent." +msgstr "Veuillez trouver ci-dessous la réponse de votre correspondant." + +#: .\templates\postman\email_visitor.txt.py:15 +msgid "For more comfort, we encourage you to open an account on the site." +msgstr "" +"Pour plus de confort, nous vous encourageons à ouvrir un compte sur le site." + +#: .\templates\postman\inbox.html.py:3 +msgid "Received Messages" +msgstr "Messages reçus" + +#: .\templates\postman\inbox.html.py:6 +msgid "Received" +msgstr "Reçu" + +#: .\templates\postman\reply.html.py:3 .\templates\postman\view.html.py:25 +#: .\templates\postman\view.html.py:28 .\templates\postman\view.html.py:31 +msgid "Reply" +msgstr "Répondre" + +#: .\templates\postman\sent.html.py:6 +msgid "Sent" +msgstr "Envoyé" + +#: .\templates\postman\trash.html.py:3 +msgid "Deleted Messages" +msgstr "Messages effacés" + +#: .\templates\postman\trash.html.py:10 +msgid "" +"Messages in this folder can be removed from time to time. For long term " +"storage, use instead the archive folder." +msgstr "" +"Les messages dans ce dossier peuvent être retirés de temps en temps. Pour un " +"stockage à long terme, utilisez plutôt le dossier d'archivage." + +#: .\templates\postman\view.html.py:5 +msgid "Thread" +msgstr "Conversation" + +#: .\templates\postman\view.html.py:13 +msgid ":" +msgstr " :" + +#: .\templates\postman\view.html.py:20 +msgid "Back" +msgstr "Retour" + +#: .\templatetags\postman_tags.py:34 +msgid "<me>" +msgstr "<moi>"