Browse files

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

  • Loading branch information...
1 parent 6e8d2dd commit 0b5c67a7465f354ceb531489d2124921f832db27 @jkocherhans jkocherhans committed Jan 5, 2010
View
2 django/conf/global_settings.py
@@ -143,7 +143,7 @@
# 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
15 django/contrib/admin/media/js/admin/DateTimeShortcuts.js
@@ -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
16 django/contrib/admin/media/js/calendar.js
@@ -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
10 django/contrib/comments/templates/comments/list.html
@@ -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
80 django/contrib/comments/templatetags/comments.py
@@ -81,10 +81,10 @@ def get_query_set(self, context):
object_pk = smart_unicode(object_pk),
site__pk = settings.SITE_ID,
)
-
+
# The is_public and is_removed fields are implementation details of the
# built-in comment model's spam filtering system, so they might not
- # be present on a custom comment model subclass. If they exist, we
+ # be present on a custom comment model subclass. If they exist, we
# should filter on them.
field_names = [f.name for f in self.comment_model._meta.fields]
if 'is_public' in field_names:
@@ -169,6 +169,46 @@ def render(self, context):
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.
@@ -217,6 +257,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):
"""
Get a (new) form object to post a new comment.
@@ -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
6 django/contrib/gis/db/models/sql/compiler.py
@@ -55,8 +55,8 @@ def get_columns(self, with_aliases=False):
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)
col_aliases.add(col.alias)
@@ -70,7 +70,7 @@ def get_columns(self, with_aliases=False):
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
4 django/contrib/localflavor/us/forms.py
@@ -23,7 +23,7 @@ def __init__(self, *args, **kwargs):
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
13 django/core/mail/__init__.py
@@ -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
23 django/core/servers/basehttp.py
@@ -15,6 +15,7 @@
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 @@ def __init__(self, *args, **kwargs):
# 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 @@ def log_message(self, format, *args):
# 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
2 django/test/utils.py
@@ -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
22 django/utils/termcolors.py
@@ -78,6 +78,12 @@ def make_style(opts=(), **kwargs):
'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 @@ def make_style(opts=(), **kwargs):
'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 @@ def make_style(opts=(), **kwargs):
'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
40 django/views/i18n.py
@@ -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 @@ def get_formats():
}
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
208 docs/ref/contrib/comments/example.txt
@@ -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
70 docs/ref/contrib/comments/index.txt
@@ -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
2 docs/ref/contrib/csrf.txt
@@ -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
6 docs/ref/django-admin.txt
@@ -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
4 docs/releases/1.2.txt
@@ -286,15 +286,15 @@ 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)
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
14 docs/topics/email.txt
@@ -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,15 +495,15 @@ 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
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
4 docs/topics/forms/formsets.txt
@@ -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
14 docs/topics/forms/index.txt
@@ -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
8 docs/topics/forms/modelforms.txt
@@ -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
32 tests/regressiontests/comment_tests/tests/templatetag_tests.py
@@ -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 @@ def testGetCommentListFromLiteral(self):
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
2 tests/regressiontests/csrf_tests/tests.py
@@ -13,7 +13,7 @@
# 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
16 tests/regressiontests/mail/tests.py
@@ -174,7 +174,7 @@
# 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 @@
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 @@
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

0 comments on commit 0b5c67a

Please sign in to comment.