1
0
mirror of https://github.com/django/django.git synced 2025-07-06 18:59:13 +00:00

[soc2009/model-validation] Merged from trunk up to [12093].

git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/model-validation@12094 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Joseph Kocherhans 2010-01-05 00:39:42 +00:00
parent 6e8d2dd4fb
commit 0b5c67a746
24 changed files with 542 additions and 71 deletions

View File

@ -143,7 +143,7 @@ DATABASES = {
# The default is to use the SMTP backend.
# Third-party backends can be specified by providing a Python path
# to a module that defines an EmailBackend class.
EMAIL_BACKEND = 'django.core.mail.backends.smtp'
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# Host for sending e-mail.
EMAIL_HOST = 'localhost'

View File

@ -44,7 +44,7 @@ var DateTimeShortcuts = {
var shortcuts_span = document.createElement('span');
inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
var now_link = document.createElement('a');
now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().strftime('" + gettext('TIME_INPUT_FORMATS') + "'));");
now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().strftime('" + get_format('TIME_INPUT_FORMATS')[0] + "'));");
now_link.appendChild(document.createTextNode(gettext('Now')));
var clock_link = document.createElement('a');
clock_link.setAttribute('href', 'javascript:DateTimeShortcuts.openClock(' + num + ');');
@ -80,10 +80,11 @@ var DateTimeShortcuts = {
quickElement('h2', clock_box, gettext('Choose a time'));
time_list = quickElement('ul', clock_box, '');
time_list.className = 'timelist';
quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().strftime('" + gettext('TIME_INPUT_FORMATS') + "'));");
quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,0,0,0,0).strftime('" + gettext('TIME_INPUT_FORMATS') + "'));");
quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,6,0,0,0).strftime('" + gettext('TIME_INPUT_FORMATS') + "'));");
quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,12,0,0,0).strftime('" + gettext('TIME_INPUT_FORMATS') + "'));");
time_format = get_format('TIME_INPUT_FORMATS')[0];
quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().strftime('" + time_format + "'));");
quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,0,0,0,0).strftime('" + time_format + "'));");
quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,6,0,0,0).strftime('" + time_format + "'));");
quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,12,0,0,0).strftime('" + time_format + "'));");
cancel_p = quickElement('p', clock_box, '');
cancel_p.className = 'calendar-cancel';
@ -236,7 +237,7 @@ var DateTimeShortcuts = {
DateTimeShortcuts.calendars[num].drawNextMonth();
},
handleCalendarCallback: function(num) {
format = gettext('DATE_INPUT_FORMATS');
format = get_format('DATE_INPUT_FORMATS')[0];
// the format needs to be escaped a little
format = format.replace('\\', '\\\\');
format = format.replace('\r', '\\r');
@ -248,7 +249,7 @@ var DateTimeShortcuts = {
handleCalendarQuickLink: function(num, offset) {
var d = new Date();
d.setDate(d.getDate() + offset)
DateTimeShortcuts.calendarInputs[num].value = d.strftime(gettext('DATE_INPUT_FORMATS'));
DateTimeShortcuts.calendarInputs[num].value = d.strftime(get_format('DATE_INPUT_FORMATS')[0]);
DateTimeShortcuts.dismissCalendar(num);
},
cancelEventPropagation: function(e) {

View File

@ -25,7 +25,7 @@ function quickElement() {
var CalendarNamespace = {
monthsOfYear: gettext('January February March April May June July August September October November December').split(' '),
daysOfWeek: gettext('S M T W T F S').split(' '),
firstDayOfWeek: parseInt(gettext('FIRST_DAY_OF_WEEK')),
firstDayOfWeek: parseInt(get_format('FIRST_DAY_OF_WEEK')),
isLeapYear: function(year) {
return (((year % 4)==0) && ((year % 100)!=0) || ((year % 400)==0));
},
@ -46,6 +46,12 @@ var CalendarNamespace = {
return days;
},
draw: function(month, year, div_id, callback) { // month = 1-12, year = 1-9999
var today = new Date();
var todayDay = today.getDate();
var todayMonth = today.getMonth()+1;
var todayYear = today.getFullYear();
var todayClass = '';
month = parseInt(month);
year = parseInt(year);
var calDiv = document.getElementById(div_id);
@ -76,7 +82,13 @@ var CalendarNamespace = {
if (i%7 == 0 && currentDay != 1) {
tableRow = quickElement('tr', tableBody);
}
var cell = quickElement('td', tableRow, '');
if ((currentDay==todayDay) && (month==todayMonth) && (year==todayYear)) {
todayClass='today';
} else {
todayClass='';
}
var cell = quickElement('td', tableRow, '', 'class', todayClass);
quickElement('a', cell, currentDay, 'href', 'javascript:void(' + callback + '('+year+','+month+','+currentDay+'));');
currentDay++;
}

View File

@ -0,0 +1,10 @@
<dl id="comments">
{% for comment in comment_list %}
<dt id="c{{ comment.id }}">
{{ comment.submit_date }} - {{ comment.name }}
</dt>
<dd>
<p>{{ comment.comment }}</p>
</dd>
{% endfor %}
</dl>

View File

@ -169,6 +169,46 @@ class RenderCommentFormNode(CommentFormNode):
else:
return ''
class RenderCommentListNode(CommentListNode):
"""Render the comment list directly"""
#@classmethod
def handle_token(cls, parser, token):
"""Class method to parse render_comment_list and return a Node."""
tokens = token.contents.split()
if tokens[1] != 'for':
raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0])
# {% render_comment_list for obj %}
if len(tokens) == 3:
return cls(object_expr=parser.compile_filter(tokens[2]))
# {% render_comment_list for app.models pk %}
elif len(tokens) == 4:
return cls(
ctype = BaseCommentNode.lookup_content_type(tokens[2], tokens[0]),
object_pk_expr = parser.compile_filter(tokens[3])
)
handle_token = classmethod(handle_token)
def render(self, context):
ctype, object_pk = self.get_target_ctype_pk(context)
if object_pk:
template_search_list = [
"comments/%s/%s/list.html" % (ctype.app_label, ctype.model),
"comments/%s/list.html" % ctype.app_label,
"comments/list.html"
]
qs = self.get_query_set(context)
context.push()
liststr = render_to_string(template_search_list, {
"comment_list" : self.get_context_value_from_queryset(context, qs)
}, context)
context.pop()
return liststr
else:
return ''
# We could just register each classmethod directly, but then we'd lose out on
# the automagic docstrings-into-admin-docs tricks. So each node gets a cute
# wrapper function that just exists to hold the docstring.
@ -216,6 +256,24 @@ def get_comment_list(parser, token):
"""
return CommentListNode.handle_token(parser, token)
#@register.tag
def render_comment_list(parser, token):
"""
Render the comment list (as returned by ``{% get_comment_list %}``)
through the ``comments/list.html`` template
Syntax::
{% render_comment_list for [object] %}
{% render_comment_list for [app].[model] [object_id] %}
Example usage::
{% render_comment_list for event %}
"""
return RenderCommentListNode.handle_token(parser, token)
#@register.tag
def get_comment_form(parser, token):
"""
@ -248,12 +306,28 @@ def comment_form_target():
Example::
<form action="{% comment_form_target %}" method="POST">
<form action="{% comment_form_target %}" method="post">
"""
return comments.get_form_target()
#@register.simple_tag
def get_comment_permalink(comment, anchor_pattern=None):
"""
Get the permalink for a comment, optionally specifying the format of the
named anchor to be appended to the end of the URL.
Example::
{{ get_comment_permalink comment "#c%(id)s-by-%(user_name)s" }}
"""
if anchor_pattern:
return comment.get_absolute_url(anchor_pattern)
return comment.get_absolute_url()
register.tag(get_comment_count)
register.tag(get_comment_list)
register.tag(get_comment_form)
register.tag(render_comment_form)
register.simple_tag(comment_form_target)
register.simple_tag(get_comment_permalink)
register.tag(render_comment_list)

View File

@ -55,7 +55,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
aliases.add(r)
col_aliases.add(col[1])
else:
result.append(col.as_sql(qn=qn))
result.append(col.as_sql(qn, self.connection))
if hasattr(col, 'alias'):
aliases.add(col.alias)
@ -70,7 +70,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
max_name_length = self.connection.ops.max_name_length()
result.extend([
'%s%s' % (
self.get_extra_select_format(alias) % aggregate.as_sql(qn=qn, connection=self.connection),
self.get_extra_select_format(alias) % aggregate.as_sql(qn, self.connection),
alias is not None
and ' AS %s' % qn(truncate_name(alias, max_name_length))
or ''

View File

@ -23,7 +23,7 @@ class USZipCodeField(RegexField):
class USPhoneNumberField(CharField):
default_error_messages = {
'invalid': u'Phone numbers must be in XXX-XXX-XXXX format.',
'invalid': _('Phone numbers must be in XXX-XXX-XXXX format.'),
}
def clean(self, value):
@ -85,7 +85,7 @@ class USStateField(Field):
abbreviation for the given state.
"""
default_error_messages = {
'invalid': u'Enter a U.S. state or territory.',
'invalid': _('Enter a U.S. state or territory.'),
}
def clean(self, value):

View File

@ -28,16 +28,17 @@ def get_connection(backend=None, fail_silently=False, **kwds):
"""
path = backend or settings.EMAIL_BACKEND
try:
mod = import_module(path)
mod_name, klass_name = path.rsplit('.', 1)
mod = import_module(mod_name)
except ImportError, e:
raise ImproperlyConfigured(('Error importing email backend %s: "%s"'
% (path, e)))
raise ImproperlyConfigured(('Error importing email backend module %s: "%s"'
% (mod_name, e)))
try:
cls = getattr(mod, 'EmailBackend')
klass = getattr(mod, klass_name)
except AttributeError:
raise ImproperlyConfigured(('Module "%s" does not define a '
'"EmailBackend" class' % path))
return cls(fail_silently=fail_silently, **kwds)
'"%s" class' % (mod_name, klass_name)))
return klass(fail_silently=fail_silently, **kwds)
def send_mail(subject, message, from_email, recipient_list,

View File

@ -15,6 +15,7 @@ import stat
import sys
import urllib
from django.core.management.color import color_style
from django.utils.http import http_date
from django.utils._os import safe_join
@ -557,6 +558,7 @@ class WSGIRequestHandler(BaseHTTPRequestHandler):
# We set self.path to avoid crashes in log_message() on unsupported
# requests (like "OPTIONS").
self.path = ''
self.style = color_style()
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
def get_environ(self):
@ -608,7 +610,26 @@ class WSGIRequestHandler(BaseHTTPRequestHandler):
# Don't bother logging requests for admin images or the favicon.
if self.path.startswith(self.admin_media_prefix) or self.path == '/favicon.ico':
return
sys.stderr.write("[%s] %s\n" % (self.log_date_time_string(), format % args))
msg = "[%s] %s\n" % (self.log_date_time_string(), format % args)
# Utilize terminal colors, if available
if args[1][0] == '2':
# Put 2XX first, since it should be the common case
msg = self.style.HTTP_SUCCESS(msg)
elif args[1][0] == '1':
msg = self.style.HTTP_INFO(msg)
elif args[1][0] == '3':
msg = self.style.HTTP_REDIRECT(msg)
elif args[1] == '404':
msg = self.style.HTTP_NOT_FOUND(msg)
elif args[1][0] == '4':
msg = self.style.HTTP_BAD_REQUEST(msg)
else:
# Any 5XX, or any other response
msg = self.style.HTTP_SERVER_ERROR(msg)
sys.stderr.write(msg)
class AdminMediaHandler(object):
"""

View File

@ -43,7 +43,7 @@ def setup_test_environment():
mail.SMTPConnection = locmem.EmailBackend
mail.original_email_backend = settings.EMAIL_BACKEND
settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem'
settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
mail.outbox = []

View File

@ -78,6 +78,12 @@ PALETTES = {
'SQL_COLTYPE': {},
'SQL_KEYWORD': {},
'SQL_TABLE': {},
'HTTP_INFO': {},
'HTTP_SUCCESS': {},
'HTTP_REDIRECT': {},
'HTTP_BAD_REQUEST': {},
'HTTP_NOT_FOUND': {},
'HTTP_SERVER_ERROR': {},
},
DARK_PALETTE: {
'ERROR': { 'fg': 'red', 'opts': ('bold',) },
@ -86,6 +92,12 @@ PALETTES = {
'SQL_COLTYPE': { 'fg': 'green' },
'SQL_KEYWORD': { 'fg': 'yellow' },
'SQL_TABLE': { 'opts': ('bold',) },
'HTTP_INFO': { 'opts': ('bold',) },
'HTTP_SUCCESS': { },
'HTTP_REDIRECT': { 'fg': 'green' },
'HTTP_BAD_REQUEST': { 'fg': 'red', 'opts': ('bold',) },
'HTTP_NOT_FOUND': { 'fg': 'yellow' },
'HTTP_SERVER_ERROR': { 'fg': 'magenta', 'opts': ('bold',) },
},
LIGHT_PALETTE: {
'ERROR': { 'fg': 'red', 'opts': ('bold',) },
@ -94,6 +106,12 @@ PALETTES = {
'SQL_COLTYPE': { 'fg': 'green' },
'SQL_KEYWORD': { 'fg': 'blue' },
'SQL_TABLE': { 'opts': ('bold',) },
'HTTP_INFO': { 'opts': ('bold',) },
'HTTP_SUCCESS': { },
'HTTP_REDIRECT': { 'fg': 'green', 'opts': ('bold',) },
'HTTP_BAD_REQUEST': { 'fg': 'red', 'opts': ('bold',) },
'HTTP_NOT_FOUND': { 'fg': 'red' },
'HTTP_SERVER_ERROR': { 'fg': 'magenta', 'opts': ('bold',) },
}
}
DEFAULT_PALETTE = DARK_PALETTE
@ -117,7 +135,9 @@ def parse_color_setting(config_string):
definition will augment the base palette definition.
Valid roles:
'error', 'notice', 'sql_field', 'sql_coltype', 'sql_keyword', 'sql_table'
'error', 'notice', 'sql_field', 'sql_coltype', 'sql_keyword', 'sql_table',
'http_info', 'http_success', 'http_redirect', 'http_bad_request',
'http_not_found', 'http_server_error'
Valid colors:
'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'

View File

@ -53,7 +53,14 @@ def get_formats():
result[attr] = getattr(module, attr)
except AttributeError:
pass
return result
src = []
for k, v in result.items():
if isinstance(v, (basestring, int)):
src.append("formats['%s'] = '%s';\n" % (javascript_quote(k), javascript_quote(smart_unicode(v))))
elif isinstance(v, (tuple, list)):
v = [javascript_quote(smart_unicode(value)) for value in v]
src.append("formats['%s'] = ['%s'];\n" % (javascript_quote(k), "', '".join(v)))
return ''.join(src)
NullSource = """
/* gettext identity library */
@ -90,6 +97,25 @@ function ngettext(singular, plural, count) {
}
function gettext_noop(msgid) { return msgid; }
"""
LibFormatHead = """
/* formatting library */
var formats = new Array();
"""
LibFormatFoot = """
function get_format(format_type) {
var value = formats[format_type];
if (typeof(value) == 'undefined') {
return msgid;
} else {
return value;
}
}
"""
SimplePlural = """
@ -122,7 +148,8 @@ def null_javascript_catalog(request, domain=None, packages=None):
Returns "identity" versions of the JavaScript i18n functions -- i.e.,
versions that don't actually do anything.
"""
return http.HttpResponse(NullSource + InterPolate, 'text/javascript')
src = [NullSource, InterPolate, LibFormatHead, get_formats(), LibFormatFoot]
return http.HttpResponse(''.join(src), 'text/javascript')
def javascript_catalog(request, domain='djangojs', packages=None):
"""
@ -210,15 +237,12 @@ def javascript_catalog(request, domain='djangojs', packages=None):
csrc.sort()
for k, v in pdict.items():
src.append("catalog['%s'] = [%s];\n" % (javascript_quote(k), ','.join(["''"]*(v+1))))
for k, v in get_formats().items():
if isinstance(v, (basestring, int)):
src.append("catalog['%s'] = '%s';\n" % (javascript_quote(k), javascript_quote(smart_unicode(v))))
elif isinstance(v, (tuple, list)):
v = [javascript_quote(smart_unicode(value)) for value in v]
src.append("catalog['%s'] = ['%s'];\n" % (javascript_quote(k), "', '".join(v)))
src.extend(csrc)
src.append(LibFoot)
src.append(InterPolate)
src.append(LibFormatHead)
src.append(get_formats())
src.append(LibFormatFoot)
src = ''.join(src)
return http.HttpResponse(src, 'text/javascript')

View File

@ -0,0 +1,208 @@
.. _ref-contrib-comments-example:
.. highlightlang:: html+django
===========================================
Example of using the in-built comments app
===========================================
Follow the first three steps of the quick start guide in the
:ref:`documentation <ref-contrib-comments-index>`.
Now suppose, you have an app (``blog``) with a model (``Post``)
to which you want to attach comments. Let us also suppose that
you have a template called ``blog_detail.html`` where you want
to display the comments list and comment form.
Template
========
First, we should load the ``comment`` template tags in the
``blog_detail.html`` so that we can use it's functionality. So
just like all other custom template tag libraries::
{% load comments %}
Next, let us add the number of comments attached to the particular
model instance of ``Post``. For this we assume that a context
variable ``object_pk`` is present which gives the ``id`` of the
instance of ``Post``.
The usage of the :ttag:`get_comment_count` tag is like below::
{% get_comment_count for blog.post object_pk as comment_count %}
<p>{{ comment_count }} comments have been posted.</p>
If you have the instance (say ``entry``) of the model (``Post``)
available in the context, then you can refer to it directly::
{% get_comment_count for entry as comment_count %}
<p>{{ comment_count }} comments have been posted.</p>
Next, we can use the :ttag:`render_comment_list` tag, to render all comments
to the given instance (``entry``) by using the ``comments/list.html`` template.
{% render_comment_list for entry %}
Django will will look for the ``list.html`` under the following directories
(for our example)::
comments/blog/post/list.html
comments/blog/list.html
comments/list.html
To get a list of comments, we make use of the :ttag:`get_comment_list` tag.
This tag's usage is very similar to the :ttag:`get_comment_count` tag. We
need to remember that the :ttag:`get_comment_list` returns a list of comments
and hence we will have to iterate through them to display them::
{% get_comment_list for blog.post object_pk as comment_list %}
{% for comment in comment_list %}
<p>Posted by: {{ comment.user_name }} on {{ comment.submit_date }}</p>
...
<p>Comment: {{ comment.comment }}</p>
...
{% endfor %}
Finally, we display the comment form, enabling users to enter their
comments. There are two ways of doing so. The first is when you want to
display the comments template available under your ``comments/form.html``.
The other method gives you a chance to customize the form.
The first method makes use of the :ttag:`render_comment_form` tag. It's usage
too is similar to the other three tags we have discussed above::
{% render_comment_form for entry %}
It looks for the ``form.html`` under the following directories
(for our example)::
comments/blog/post/form.html
comments/blog/form.html
comments/form.html
Since we customize the form in the second method, we make use of another
tag called :ttag:`comment_form_target`. This tag on rendering gives the URL
where the comment form is posted. Without any :ref:`customization
<ref-contrib-comments-custom>`, :ttag:`comment_form_target` evaluates to
``/comments/post/``. We use this tag in the form's ``action`` attribute.
The :ttag:`get_comment_form` tag renders a ``form`` for a model instance by
creating a context variable. One can iterate over the ``form`` object to
get individual fields. This gives you fine-grain control over the form::
{% for field in form %}
{% ifequal field.name "comment" %}
<!-- Customize the "comment" field, say, make CSS changes -->
...
{% endfor %}
But let's look at a simple example::
{% get_comment_form for entry as form %}
<!-- A context variable called form is created with the necessary hidden
fields, timestamps and security hashes -->
<table>
<form action="{% comment_form_target %}" method="post">
{{ form }}
<tr>
<td></td>
<td><input type="submit" name="preview" class="submit-post" value="Preview"></td>
</tr>
</form>
</table>
Flagging
========
If you want your users to be able to flag comments (say for profanity), you
can just direct them (by placing a link in your comment list) to ``/flag/{{
comment.id }}/``. Similarly, a user with requisite permissions (``"Can
moderate comments"``) can approve and delete comments. This can also be
done through the ``admin`` as you'll see later. You might also want to
customize the following templates:
* ``flag.html``
* ``flagged.html``
* ``approve.html``
* ``approved.html``
* ``delete.html``
* ``deleted.html``
found under the directory structure we saw for ``form.html``.
Feeds
=====
Suppose you want to export a :ref:`feed <ref-contrib-syndication>` of the
latest comments, you can use the in-built :class:`LatestCommentFeed`. Just
enable it in your project's ``urls.py``:
.. code-block:: python
from django.conf.urls.defaults import *
from django.contrib.comments.feeds import LatestCommentFeed
feeds = {
'latest': LatestCommentFeed,
}
urlpatterns = patterns('',
# ...
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
{'feed_dict': feeds}),
# ...
)
Now you should have the latest comment feeds being served off ``/feeds/latest/``.
Moderation
==========
Now that we have the comments framework working, we might want to have some
moderation setup to administer the comments. The comments framework comes
in-built with :ref:`generic comment moderation
<ref-contrib-comments-moderation>`. The comment moderation has the following
features (all of which or only certain can be enabled):
* Enable comments for a particular model instance.
* Close comments after a particular (user-defined) number of days.
* Email new comments to the site-staff.
To enable comment moderation, we subclass the :class:`CommentModerator` and
register it with the moderation features we want. Let us suppose we want to
close comments after 7 days of posting and also send out an email to the
site staff. In ``blog/models.py``, we register a comment moderator in the
following way:
.. code-block:: python
from django.contrib.comments.moderation import CommentModerator, moderator
from django.db import models
class Post(models.Model):
title = models.CharField(max_length = 255)
content = models.TextField()
posted_date = models.DateTimeField()
class PostModerator(CommentModerator):
email_notification = True
auto_close_field = 'posted_date'
# Close the comments after 7 days.
close_after = 7
moderator.register(Post, PostModerator)
The generic comment moderation also has the facility to remove comments.
These comments can then be moderated by any user who has access to the
``admin`` site and the ``Can moderate comments`` permission (can be set
under the ``Users`` page in the ``admin``).
The moderator can ``Flag``, ``Approve`` or ``Remove`` comments using the
``Action`` drop-down in the ``admin`` under the ``Comments`` page.
.. note::
Only a super-user will be able to delete comments from the database.
``Remove Comments`` only sets the ``is_public`` attribute to
``False``.

View File

@ -84,11 +84,34 @@ different ways you can specify which object to attach to:
In the above, ``blog.entry`` is the app label and (lower-cased) model
name of the model class.
.. templatetag:: get_comment_list
Displaying comments
-------------------
To display a list of comments, you can use the template tags
:ttag:`render_comment_list` or :ttag:`get_comment_list`.
.. templatetag:: render_comment_list
Quickly rendering a comment list
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The easiest way to display a list of comments for some object is by using
:ttag:`render_comment_list`::
{% render_comment_list for [object] %}
For example::
{% render_comment_list for event %}
This will render comments using a template named ``comments/list.html``, a
default version of which is included with Django.
.. templatetag:: get_comment_list
Rendering a custom comment list
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To get the list of comments for some object, use :ttag:`get_comment_list`::
{% get_comment_list for [object] as [varname] %}
@ -104,6 +127,44 @@ This returns a list of :class:`~django.contrib.comments.models.Comment` objects;
see :ref:`the comment model documentation <ref-contrib-comments-models>` for
details.
.. templatetag:: get_comment_permalink
Linking to comments
-------------------
To provide a permalink to a specific comment, use :ttag:`get_comment_permalink`::
{% get_comment_permalink comment_obj [format_string] %}
By default, the named anchor that will be appended to the URL will be the letter
'c' followed by the comment id, for example 'c82'. You may specify a custom
format string if you wish to override this behavior::
{% get_comment_permalink comment "#c%(id)s-by-%(user_name)s"%}
The format string is a standard python format string. Valid mapping keys
include any attributes of the comment object.
Regardless of whether you specify a custom anchor pattern, you must supply a
matching named anchor at a suitable place in your template.
For example::
{% for comment in comment_list %}
<a name="c{{ comment.id }}"></a>
<a href="{% get_comment_permalink comment %}">
permalink for comment #{{ forloop.counter }}
</a>
...
{% endfor %}
.. warning::
There's a known bug in Safari/Webkit which causes the named anchor to be
forgotten following a redirect. The practical impact for comments is that
the Safari/webkit browsers will arrive at the correct page but will not
scroll to the named anchor.
.. templatetag:: get_comment_count
Counting comments
@ -157,7 +218,7 @@ you can use in the template::
A complete form might look like::
{% get_comment_form for event as form %}
<form action="{% comment_form_target %}" method="POST">
<form action="{% comment_form_target %}" method="post">
{{ form }}
<tr>
<td></td>
@ -178,7 +239,7 @@ You may have noticed that the above example uses another template tag --
form. This will always return the correct URL that comments should be posted to;
you'll always want to use it like above::
<form action="{% comment_form_target %}" method="POST">
<form action="{% comment_form_target %}" method="post">
Redirecting after the comment post
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -238,3 +299,4 @@ More information
custom
forms
moderation
example

View File

@ -49,7 +49,7 @@ To enable CSRF protection for your views, follow these steps:
2. In any template that uses a POST form, use the :ttag:`csrf_token` tag inside
the ``<form>`` element if the form is for an internal URL, e.g.::
<form action="" method="POST">{% csrf_token %}
<form action="" method="post">{% csrf_token %}
This should not be done for POST forms that target external URLs, since
that would cause the CSRF token to be leaked, leading to a vulnerability.

View File

@ -1026,6 +1026,12 @@ number of roles in which color is used:
* ``sql_coltype`` - The type of a model field in SQL.
* ``sql_keyword`` - A SQL keyword.
* ``sql_table`` - The name of a model in SQL.
* ``http_info`` - A 1XX HTTP Informational server response.
* ``http_success`` - A 2XX HTTP Success server response.
* ``http_redirect`` - A 3XX HTTP Redirect server response.
* ``http_not_found`` - A 404 HTTP Not Found server response.
* ``http_bad_request`` - A 4XX HTTP Bad Request server response other than 404.
* ``http_server_error`` - A 5XX HTTP Server Error response.
Each of these roles can be assigned a specific foreground and
background color, from the following list:

View File

@ -286,7 +286,7 @@ connection with which to send e-mail, you can explicitly request an
SMTP connection::
from django.core.mail import get_connection
connection = get_connection('django.core.mail.backends.smtp')
connection = get_connection('django.core.mail.backends.smtp.EmailBackend')
messages = get_notification_email()
connection.send_messages(messages)
@ -294,7 +294,7 @@ If your call to construct an instance of ``SMTPConnection`` required
additional arguments, those arguments can be passed to the
:meth:`~django.core.mail.get_connection()` call::
connection = get_connection('django.core.mail.backends.smtp', hostname='localhost', port=1234)
connection = get_connection('django.core.mail.backends.smtp.EmailBackend', hostname='localhost', port=1234)
User Messages API
-----------------

View File

@ -408,7 +408,7 @@ settings file.
The SMTP backend is the default configuration inherited by Django. If you
want to specify it explicitly, put the following in your settings::
EMAIL_BACKEND = 'django.core.mail.backends.smtp'
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
.. admonition:: SMTPConnection objects
@ -433,7 +433,7 @@ providing the ``stream`` keyword argument when constructing the connection.
To specify this backend, put the following in your settings::
EMAIL_BACKEND = 'django.core.mail.backends.console'
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
This backend is not intended for use in production -- it is provided as a
convenience that can be used during development.
@ -451,7 +451,7 @@ the ``file_path`` keyword when creating a connection with
To specify this backend, put the following in your settings::
EMAIL_BACKEND = 'django.core.mail.backends.filebased'
EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
EMAIL_FILE_PATH = '/tmp/app-messages' # change this to a proper location
This backend is not intended for use in production -- it is provided as a
@ -470,7 +470,7 @@ be send.
To specify this backend, put the following in your settings::
EMAIL_BACKEND = 'django.core.mail.backends.locmem'
EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
This backend is not intended for use in production -- it is provided as a
convenience that can be used during development and testing.
@ -483,7 +483,7 @@ Dummy backend
As the name suggests the dummy backend does nothing with your messages. To
specify this backend, put the following in your settings::
EMAIL_BACKEND = 'django.core.mail.backends.dummy'
EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
This backend is not intended for use in production -- it is provided as a
convenience that can be used during development.
@ -495,7 +495,7 @@ Defining a custom e-mail backend
If you need to change how e-mails are send you can write your own e-mail
backend. The ``EMAIL_BACKEND`` setting in your settings file is then the
Python import path for your backend.
Python import path for your backend class.
Custom e-mail backends should subclass ``BaseEmailBackend`` that is located in
the ``django.core.mail.backends.base`` module. A custom e-mail backend must
@ -503,7 +503,7 @@ implement the ``send_messages(email_messages)`` method. This method receives a
list of :class:`~django.core.mail.EmailMessage` instances and returns the
number of successfully delivered messages. If your backend has any concept of
a persistent session or connection, you should also implement the ``open()``
and ``close()`` methods. Refer to ``SMTPEmailBackend`` for a reference
and ``close()`` methods. Refer to ``smtp.EmailBackend`` for a reference
implementation.
.. _topics-sending-multiple-emails:

View File

@ -355,7 +355,7 @@ The ``manage_articles.html`` template might look like this:
.. code-block:: html+django
<form method="POST" action="">
<form method="post" action="">
{{ formset.management_form }}
<table>
{% for form in formset.forms %}
@ -369,7 +369,7 @@ with the management form:
.. code-block:: html+django
<form method="POST" action="">
<form method="post" action="">
<table>
{{ formset }}
</table>

View File

@ -172,7 +172,7 @@ Forms are designed to work with the Django template language. In the above
example, we passed our ``ContactForm`` instance to the template using the
context variable ``form``. Here's a simple example template::
<form action="/contact/" method="POST">
<form action="/contact/" method="post">
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
@ -183,7 +183,7 @@ The form only outputs its own fields; it is up to you to provide the surrounding
``form.as_p`` will output the form with each form field and accompanying label
wrapped in a paragraph. Here's the output for our example template::
<form action="/contact/" method="POST">
<form action="/contact/" method="post">
<p><label for="id_subject">Subject:</label>
<input id="id_subject" type="text" name="subject" maxlength="100" /></p>
<p><label for="id_message">Message:</label>
@ -211,7 +211,7 @@ If the default generated HTML is not to your taste, you can completely customize
the way a form is presented using the Django template language. Extending the
above example::
<form action="/contact/" method="POST">
<form action="/contact/" method="post">
<div class="fieldWrapper">
{{ form.subject.errors }}
<label for="id_subject">E-mail subject:</label>
@ -263,7 +263,7 @@ If you're using the same HTML for each of your form fields, you can reduce
duplicate code by looping through each field in turn using a ``{% for %}``
loop::
<form action="/contact/" method="POST">
<form action="/contact/" method="post">
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
@ -322,7 +322,7 @@ and visible fields independently: ``hidden_fields()`` and
``visible_fields()``. Here's a modification of an earlier example that uses
these two methods::
<form action="/contact/" method="POST">
<form action="/contact/" method="post">
{% for field in form.visible_fields %}
<div class="fieldWrapper">
@ -356,7 +356,7 @@ If your site uses the same rendering logic for forms in multiple places, you
can reduce duplication by saving the form's loop in a standalone template and
using the :ttag:`include` tag to reuse it in other templates::
<form action="/contact/" method="POST">
<form action="/contact/" method="post">
{% include "form_snippet.html" %}
<p><input type="submit" value="Send message" /></p>
</form>
@ -373,7 +373,7 @@ using the :ttag:`include` tag to reuse it in other templates::
If the form object passed to a template has a different name within the
context, you can alias it using the :ttag:`with` tag::
<form action="/comments/add/" method="POST">
<form action="/comments/add/" method="post">
{% with comment_form as form %}
{% include "form_snippet.html" %}
{% endwith %}

View File

@ -705,14 +705,14 @@ There are three ways to render a formset in a Django template.
First, you can let the formset do most of the work::
<form method="POST" action="">
<form method="post" action="">
{{ formset }}
</form>
Second, you can manually render the formset, but let the form deal with
itself::
<form method="POST" action="">
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset.forms %}
{{ form }}
@ -725,7 +725,7 @@ form as shown above. See the :ref:`management form documentation
Third, you can manually render each field::
<form method="POST" action="">
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset.forms %}
{% for field in form %}
@ -738,7 +738,7 @@ If you opt to use this third method and you don't iterate over the fields with
a ``{% for %}`` loop, you'll need to render the primary key field. For example,
if you were rendering the ``name`` and ``age`` fields of a model::
<form method="POST" action="">
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset.forms %}
{{ form.id }}

View File

@ -1,5 +1,6 @@
from django.contrib.comments.forms import CommentForm
from django.contrib.comments.models import Comment
from django.contrib.contenttypes.models import ContentType
from django.template import Template, Context
from regressiontests.comment_tests.models import Article, Author
from regressiontests.comment_tests.tests import CommentTestCase
@ -63,3 +64,34 @@ class CommentTemplateTagTests(CommentTestCase):
def testGetCommentListFromObject(self):
self.testGetCommentList("{% get_comment_list for a as cl %}")
def testGetCommentPermalink(self):
self.createSomeComments()
t = "{% load comments %}{% get_comment_list for comment_tests.author author.id as cl %}"
t += "{% get_comment_permalink cl.0 %}"
ct = ContentType.objects.get_for_model(Author)
author = Author.objects.get(pk=1)
ctx, out = self.render(t, author=author)
self.assertEqual(out, "/cr/%s/%s/#c2" % (ct.id, author.id))
def testGetCommentPermalinkFormatted(self):
self.createSomeComments()
t = "{% load comments %}{% get_comment_list for comment_tests.author author.id as cl %}"
t += "{% get_comment_permalink cl.0 '#c%(id)s-by-%(user_name)s' %}"
ct = ContentType.objects.get_for_model(Author)
author = Author.objects.get(pk=1)
ctx, out = self.render(t, author=author)
self.assertEqual(out, "/cr/%s/%s/#c2-by-Joe Somebody" % (ct.id, author.id))
def testRenderCommentList(self, tag=None):
t = "{% load comments %}" + (tag or "{% render_comment_list for comment_tests.article a.id %}")
ctx, out = self.render(t, a=Article.objects.get(pk=1))
self.assert_(out.strip().startswith("<dl id=\"comments\">"))
self.assert_(out.strip().endswith("</dl>"))
def testRenderCommentListFromLiteral(self):
self.testRenderCommentList("{% render_comment_list for comment_tests.article 1 %}")
def testRenderCommentListFromObject(self):
self.testRenderCommentList("{% render_comment_list for a %}")

View File

@ -13,7 +13,7 @@ from django.template import RequestContext, Template
# Response/views used for CsrfResponseMiddleware and CsrfViewMiddleware tests
def post_form_response():
resp = HttpResponse(content="""
<html><body><form method="POST"><input type="text" /></form></body></html>
<html><body><form method="post"><input type="text" /></form></body></html>
""", mimetype="text/html")
return resp

View File

@ -174,7 +174,7 @@ Content
# Test that the console backend can be pointed at an arbitrary stream
>>> s = StringIO()
>>> connection = mail.get_connection('django.core.mail.backends.console', stream=s)
>>> connection = mail.get_connection('django.core.mail.backends.console.EmailBackend', stream=s)
>>> send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection)
1
>>> print s.getvalue()
@ -270,7 +270,7 @@ Content
True
# Test custom backend defined in this suite.
>>> conn = mail.get_connection('regressiontests.mail.custombackend')
>>> conn = mail.get_connection('regressiontests.mail.custombackend.EmailBackend')
>>> hasattr(conn, 'test_outbox')
True
>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
@ -280,23 +280,23 @@ True
1
# Test backend argument of mail.get_connection()
>>> isinstance(mail.get_connection('django.core.mail.backends.smtp'), smtp.EmailBackend)
>>> isinstance(mail.get_connection('django.core.mail.backends.smtp.EmailBackend'), smtp.EmailBackend)
True
>>> isinstance(mail.get_connection('django.core.mail.backends.locmem'), locmem.EmailBackend)
>>> isinstance(mail.get_connection('django.core.mail.backends.locmem.EmailBackend'), locmem.EmailBackend)
True
>>> isinstance(mail.get_connection('django.core.mail.backends.dummy'), dummy.EmailBackend)
>>> isinstance(mail.get_connection('django.core.mail.backends.dummy.EmailBackend'), dummy.EmailBackend)
True
>>> isinstance(mail.get_connection('django.core.mail.backends.console'), console.EmailBackend)
>>> isinstance(mail.get_connection('django.core.mail.backends.console.EmailBackend'), console.EmailBackend)
True
>>> tmp_dir = tempfile.mkdtemp()
>>> isinstance(mail.get_connection('django.core.mail.backends.filebased', file_path=tmp_dir), filebased.EmailBackend)
>>> isinstance(mail.get_connection('django.core.mail.backends.filebased.EmailBackend', file_path=tmp_dir), filebased.EmailBackend)
True
>>> shutil.rmtree(tmp_dir)
>>> isinstance(mail.get_connection(), locmem.EmailBackend)
True
# Test connection argument of send_mail() et al
>>> connection = mail.get_connection('django.core.mail.backends.console')
>>> connection = mail.get_connection('django.core.mail.backends.console.EmailBackend')
>>> send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection)
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0