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

gis: Merged revisions 6614-6671 via svnmerge from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6672 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2007-11-14 19:13:37 +00:00
parent 1c7ad200c7
commit f66821deae
117 changed files with 6564 additions and 2878 deletions

View File

@ -120,6 +120,7 @@ answer newbie questions, and generally made Django that much better:
Marc Fargas <telenieko@telenieko.com>
Szilveszter Farkas <szilveszter.farkas@gmail.com>
favo@exoweb.net
Dmitri Fedortchenko <zeraien@gmail.com>
Bill Fenner <fenner@gmail.com>
Stefane Fermgier <sf@fermigier.com>
Afonso Fernández Nogueira <fonzzo.django@gmail.com>
@ -298,6 +299,7 @@ answer newbie questions, and generally made Django that much better:
Tyler Tarabula <tyler.tarabula@gmail.com>
Tyson Tate <tyson@fallingbullets.com>
Frank Tegtmeyer <fte@fte.to>
terryh.tp@gmail.com
thebjorn <bp@datakortet.no>
Zach Thompson <zthompson47@gmail.com>
Michael Thornhill

File diff suppressed because it is too large Load Diff

View File

@ -1016,7 +1016,7 @@ msgstr "Escoja %s para modificar"
#: contrib/admin/views/main.py:780
msgid "Database error"
msgstr "Erorr en la base de datos"
msgstr "Error en la base de datos"
#: contrib/auth/forms.py:17
#: contrib/auth/forms.py:138

File diff suppressed because it is too large Load Diff

View File

@ -7,8 +7,8 @@ msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2007-07-18 16:49+0200\n"
"PO-Revision-Date: 2007-03-06 10:30+0100\n"
"Last-Translator: Philip Lindborg <philip.lindborg@gmail.com>\n"
"PO-Revision-Date: 2007-11-03 00:30+0100\n"
"Last-Translator: Dmitri Fedortchenko <zeraien@gmail.com>\n"
"Language-Team: Django I18N <Django-I18N@googlegroups.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -146,7 +146,7 @@ msgid_plural ""
msgstr[0] ""
"Var god och fyll giltiga %(self)s ID-nummer. Värdet %(value)r är ogiltigt."
msgstr[1] ""
"Var god och fyll giltiga %(self)s ID-nummer. Värdena %(value)r är ogiltiga."
"Var god och fyll giltiga %(self)s ID-nummer. Värden %(value)r är ogiltiga."
#: conf/global_settings.py:38
msgid "Arabic"
@ -318,20 +318,20 @@ msgstr "Traditionell Kinesiska"
#: core/validators.py:71
msgid "This value must contain only letters, numbers and underscores."
msgstr "Det här värdet får bara innehålla bokstäver, siffror och understreck."
msgstr "Det här värdet får endast innehålla bokstäver, siffror och understreck."
#: core/validators.py:75
msgid ""
"This value must contain only letters, numbers, underscores, dashes or "
"slashes."
msgstr ""
"Det här värdet får bara innehålla bokstäver, siffror, understreck, "
"Det här värdet får endast innehålla bokstäver, siffror, understreck, "
"bindestreck eller snedstreck."
#: core/validators.py:79
msgid "This value must contain only letters, numbers, underscores or hyphens."
msgstr ""
"Det här värdet får bara innehålla bokstäver, siffror, understreck eller "
"Det här värdet får endast innehålla bokstäver, siffror, understreck eller "
"avstavningstecken."
#: core/validators.py:83
@ -352,7 +352,7 @@ msgstr "Fyll i giltiga e-postadresser avskilda med kommatecken."
#: core/validators.py:110
msgid "Please enter a valid IP address."
msgstr "Var god fyll i en giltigt IP-adress."
msgstr "Var god fyll i en giltig IP-adress."
#: core/validators.py:114
msgid "Empty values are not allowed here."
@ -414,7 +414,7 @@ msgstr ""
#: core/validators.py:204
#, python-format
msgid "The URL %s does not point to a valid QuickTime video."
msgstr "URL:en %s pekar inte en giltig QuickTime-video."
msgstr "URL:en %s pekar inte mot en giltig QuickTime-video."
#: core/validators.py:208
msgid "A valid URL is required."
@ -432,7 +432,7 @@ msgstr ""
#: core/validators.py:229
#, python-format
msgid "Badly formed XML: %s"
msgstr "Missformad XML: %s"
msgstr "Felaktigt XML: %s"
#: core/validators.py:246
#, python-format
@ -480,7 +480,7 @@ msgstr "Det här fältet måste anges om %(field)s inte är %(value)s"
#: core/validators.py:359
msgid "Duplicate values are not allowed."
msgstr "Dubbelvärden är inte tillåtna."
msgstr "Dubletter är inte tillåtna."
#: core/validators.py:374
#, python-format
@ -544,12 +544,12 @@ msgstr "Fyll i ett giltigt decimaltal."
#: core/validators.py:454
#, python-format
msgid "Make sure your uploaded file is at least %s bytes big."
msgstr "Se till att filen du laddade upp är minst %s bytes stor."
msgstr "Se till att filen du laddade upp är minst %s byte stor."
#: core/validators.py:455
#, python-format
msgid "Make sure your uploaded file is at most %s bytes big."
msgstr "Se till att filen du laddade upp är som mest %s bytes stor."
msgstr "Se till att filen du laddade upp är som mest %s byte stor."
#: core/validators.py:472
msgid "The format for this field is wrong."
@ -876,7 +876,7 @@ msgstr "är ett giltigt betyg"
#: contrib/comments/models.py:83 contrib/comments/models.py:169
msgid "date/time submitted"
msgstr "datum/tid skickat"
msgstr "skickat datum/tid"
#: contrib/comments/models.py:84 contrib/comments/models.py:170
msgid "is public"
@ -915,7 +915,7 @@ msgid ""
"\n"
"http://%(domain)s%(url)s"
msgstr ""
"Postat av %(user)s %(date)s\n"
"Inlagt av %(user)s %(date)s\n"
"\n"
"%(comment)s\n"
"\n"
@ -947,7 +947,7 @@ msgstr "poäng"
#: contrib/comments/models.py:234
msgid "score date"
msgstr "poängdatum"
msgstr "poängen tillsatt den"
#: contrib/comments/models.py:237
msgid "karma score"
@ -960,7 +960,7 @@ msgstr "karmapoäng"
#: contrib/comments/models.py:242
#, python-format
msgid "%(score)d rating by %(user)s"
msgstr "Betyget %(score)d av %(user)s"
msgstr "Poäng %(score)d av %(user)s"
#: contrib/comments/models.py:258
#, python-format
@ -996,20 +996,20 @@ msgstr "borttagningsdatum"
#: contrib/comments/models.py:280
msgid "moderator deletion"
msgstr "moderatorborttagning"
msgstr "borttaget av moderator"
#: contrib/comments/models.py:281
msgid "moderator deletions"
msgstr "moderatorborttagningar"
msgstr "borttagna av moderator"
#: contrib/comments/models.py:285
#, python-format
msgid "Moderator deletion by %r"
msgstr "Moderatorborttagning av %r"
msgstr "Borttaget av moderator %r"
#: contrib/comments/views/karma.py:20
msgid "Anonymous users cannot vote"
msgstr "Anonyma användare kan inte rösta"
msgstr "Anonyma användare får inte rösta"
#: contrib/comments/views/karma.py:24
msgid "Invalid comment ID"
@ -1162,7 +1162,7 @@ msgstr "domännamn"
#: contrib/sites/models.py:16
msgid "display name"
msgstr "visat namn"
msgstr "visningsnamn"
#: contrib/sites/models.py:20
msgid "site"
@ -1243,8 +1243,8 @@ msgid ""
"Please enter a correct username and password. Note that both fields are case-"
"sensitive."
msgstr ""
"Var god ange ett korrekt användarnamn och lösenord. Observera att båda "
"fälten är skiftlägeskänsliga."
"Var god ange ett korrekt användarnamn och lösenord. Tänk på att "
"skilja mellan gemener och versaler."
#: contrib/admin/views/decorators.py:24
#: contrib/admin/templates/admin/login.html:25
@ -1474,12 +1474,12 @@ msgstr "Decimaltal"
#: contrib/admin/views/doc.py:299
msgid "E-mail address"
msgstr "E-postadress:"
msgstr "E-postadress"
#: contrib/admin/views/doc.py:300 contrib/admin/views/doc.py:301
#: contrib/admin/views/doc.py:304
msgid "File path"
msgstr "Filsökväg"
msgstr "Sökväg till fil"
#: contrib/admin/views/doc.py:302
msgid "Floating point number"
@ -1697,7 +1697,7 @@ msgstr "Sidan kunde inte hittas"
#: contrib/admin/templates/admin/404.html:10
msgid "We're sorry, but the requested page could not be found."
msgstr "Vi är ledsna, men den efterfrågade sidan kunde inte hittas."
msgstr "Vi beklagar, men den efterfrågade sidan kunde tyvärr inte hittas."
#: contrib/admin/templates/admin/index.html:17
#, python-format
@ -2026,11 +2026,11 @@ msgstr "Python-modellklassnamn"
#: contrib/contenttypes/models.py:40
msgid "content type"
msgstr "innehållstyp"
msgstr "typ av innehåll"
#: contrib/contenttypes/models.py:41
msgid "content types"
msgstr "innehållstyper"
msgstr "typer av innehåll"
#: contrib/auth/views.py:41
msgid "Logged out"
@ -2178,7 +2178,7 @@ msgstr "meddelande"
#: contrib/auth/forms.py:17 contrib/auth/forms.py:138
msgid "The two password fields didn't match."
msgstr "De båda lösenorden stämde inte överens."
msgstr "Lösenorden stämde inte överens."
#: contrib/auth/forms.py:25
msgid "A user with that username already exists."
@ -2218,7 +2218,7 @@ msgstr "Fyll i ett postnummer. Du måste ha mellanslag mellan nummerdelarna."
#: contrib/localflavor/br/forms.py:18
msgid "Enter a zip code in the format XXXXX-XXX."
msgstr "Fyll i zipkod på formatet XXXXX-XXX."
msgstr "Fyll i ett postnummer på formatet XXXXX-XXX."
#: contrib/localflavor/br/forms.py:30
msgid "Phone numbers must be in XX-XXXX-XXXX format."
@ -2246,16 +2246,16 @@ msgstr "Ogiltigt CNPJ-nummer."
#: contrib/localflavor/au/forms.py:18
msgid "Enter a 4 digit post code."
msgstr "Fyll i en fyrsiffrig postkod."
msgstr "Fyll i ett fyrsiffrigt postnummer."
#: contrib/localflavor/fr/forms.py:17 contrib/localflavor/de/forms.py:16
#: contrib/localflavor/fi/forms.py:14
msgid "Enter a zip code in the format XXXXX."
msgstr "Fyll i en zipkod på formatet XXXXX."
msgstr "Fyll i ett postnummer på formatet XXXXX."
#: contrib/localflavor/us/forms.py:18
msgid "Enter a zip code in the format XXXXX or XXXXX-XXXX."
msgstr "Fyll i zipkod på formatet XXXXX eller XXXXX-XXXX."
msgstr "Fyll i ett postnummer på formatet XXXXX eller XXXXX-XXXX."
#: contrib/localflavor/us/forms.py:51
msgid "Enter a valid U.S. Social Security number in XXX-XX-XXXX format."
@ -2335,7 +2335,7 @@ msgstr ""
#: contrib/localflavor/jp/forms.py:21
msgid "Enter a postal code in the format XXXXXXX or XXX-XXXX."
msgstr "Fyll i postkod på formatet XXXXXXX eller XXX-XXXX."
msgstr "Fyll i ett postnummer på formatet XXXXXXX eller XXX-XXXX."
#: contrib/localflavor/jp/jp_prefectures.py:4
msgid "Hokkaido"
@ -2631,7 +2631,7 @@ msgstr "Zürich"
#: contrib/localflavor/ch/forms.py:18 contrib/localflavor/no/forms.py:14
msgid "Enter a zip code in the format XXXX."
msgstr "Fyll i zipkod på formatet XXXX."
msgstr "Fyll i postnummer på formatet XXXX."
#: contrib/localflavor/ch/forms.py:90
msgid ""
@ -2652,7 +2652,7 @@ msgstr "Det isländska personnumret är inte giltigt."
#: contrib/localflavor/it/forms.py:16
msgid "Enter a valid zip code."
msgstr "Fyll i en giltigt zipkod."
msgstr "Fyll i ett giltigt postnummer."
#: contrib/localflavor/it/forms.py:41
msgid "Enter a valid Social Security number."
@ -2739,11 +2739,11 @@ msgstr ""
#: contrib/flatpages/models.py:18
msgid "flat page"
msgstr "flat sida"
msgstr "statisk sida"
#: contrib/flatpages/models.py:19
msgid "flat pages"
msgstr "flata sidor"
msgstr "statiska sidor"
#: utils/dates.py:6
msgid "Monday"
@ -3024,7 +3024,7 @@ msgstr "ja,nej,kanske"
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] "%(size)d byte"
msgstr[1] "%(size)d bytes"
msgstr[1] "%(size)d byte"
#: template/defaultfilters.py:516
#, python-format

View File

@ -58,7 +58,7 @@ msgstr ""
#: contrib/admin/media/js/dateparse.js:33
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
msgstr "Söndag Mondag Tisdag Onsdag Torsdag Fredag Lördag"
msgstr "Söndag Måndag Tisdag Onsdag Torsdag Fredag Lördag"
#: contrib/admin/media/js/calendar.js:25
msgid "S M T W T F S"
@ -96,7 +96,7 @@ msgstr "06.00"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84
msgid "Noon"
msgstr "Mitt på dagen"
msgstr "Middag"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183
@ -118,5 +118,5 @@ msgstr "Igår"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179
msgid "Tomorrow"
msgstr "Imorgon"
msgstr "I morgon"

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,8 @@ certain test -- e.g. being a DateField or ForeignKey.
from django.db import models
from django.utils.encoding import smart_unicode, iri_to_uri
from django.utils.translation import ugettext as _
from django.utils.html import escape
from django.utils.safestring import mark_safe
import datetime
class FilterSpec(object):
@ -39,7 +41,7 @@ class FilterSpec(object):
def output(self, cl):
t = []
if self.has_output():
t.append(_(u'<h3>By %s:</h3>\n<ul>\n') % self.title())
t.append(_(u'<h3>By %s:</h3>\n<ul>\n') % escape(self.title()))
for choice in self.choices(cl):
t.append(u'<li%s><a href="%s">%s</a></li>\n' % \
@ -47,7 +49,7 @@ class FilterSpec(object):
iri_to_uri(choice['query_string']),
choice['display']))
t.append('</ul>\n\n')
return "".join(t)
return mark_safe("".join(t))
class RelatedFilterSpec(FilterSpec):
def __init__(self, f, request, params, model):

View File

@ -3,6 +3,7 @@ from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode
from django.utils.safestring import mark_safe
ADDITION = 1
CHANGE = 2
@ -49,4 +50,4 @@ class LogEntry(models.Model):
Returns the admin URL to edit the object represented by this log entry.
This is relative to the Django admin index page.
"""
return u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id)
return mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id))

View File

@ -1,7 +1,7 @@
{% extends "admin/base.html" %}
{% load i18n %}
{% block title %}{{ title|escape }} | {% trans 'Django site admin' %}{% endblock %}
{% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %}
{% block branding %}
<h1 id="site-name">{% trans 'Django administration' %}</h1>

View File

@ -10,8 +10,8 @@
{% block breadcrumbs %}{% if not is_popup %}
<div class="breadcrumbs">
<a href="../../../">{% trans "Home" %}</a> &rsaquo;
<a href="../">{{ opts.verbose_name_plural|capfirst|escape }}</a> &rsaquo;
{% if add %}{% trans "Add" %} {{ opts.verbose_name|escape }}{% else %}{{ original|truncatewords:"18"|escape }}{% endif %}
<a href="../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
{% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
</div>
{% endif %}{% endblock %}
{% block content %}<div id="content-main">

View File

@ -1,9 +1,9 @@
{% if show %}
<div class="xfull">
<ul class="toplinks">
{% if back %}<li class="date-back"><a href="{{ back.link }}">&lsaquo; {{ back.title|escape }}</a></li>{% endif %}
{% if back %}<li class="date-back"><a href="{{ back.link }}">&lsaquo; {{ back.title }}</a></li>{% endif %}
{% for choice in choices %}
<li> {% if choice.link %}<a href="{{ choice.link }}">{% endif %}{{ choice.title|escape }}{% if choice.link %}</a>{% endif %}</li>
<li> {% if choice.link %}<a href="{{ choice.link }}">{% endif %}{{ choice.title }}{% if choice.link %}</a>{% endif %}</li>
{% endfor %}
</ul><br class="clear" />
</div>

View File

@ -3,7 +3,7 @@
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="../../../../">{% trans "Home" %}</a> &rsaquo;
<a href="../../">{{ opts.verbose_name_plural|capfirst|escape }}</a> &rsaquo;
<a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
<a href="../">{{ object|escape|truncatewords:"18" }}</a> &rsaquo;
{% trans 'Delete' %}
</div>
@ -13,7 +13,7 @@
<p>{% blocktrans with object|escape as escaped_object %}Deleting the {{ object_name }} '{{ escaped_object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p>
<ul>
{% for obj in perms_lacking %}
<li>{{ obj|escape }}</li>
<li>{{ obj }}</li>
{% endfor %}
</ul>
{% else %}

View File

@ -1,7 +1,7 @@
{% load admin_modify %}
<fieldset class="module aligned">
{% for fcw in bound_related_object.form_field_collection_wrappers %}
<h2>{{ bound_related_object.relation.opts.verbose_name|capfirst|escape }}&nbsp;#{{ forloop.counter }}</h2>
<h2>{{ bound_related_object.relation.opts.verbose_name|capfirst }}&nbsp;#{{ forloop.counter }}</h2>
{% if bound_related_object.show_url %}{% if fcw.obj.original %}
<p><a href="/r/{{ fcw.obj.original.content_type_id }}/{{ fcw.obj.original.id }}/">View on site</a></p>
{% endif %}{% endif %}

View File

@ -1,10 +1,10 @@
{% load admin_modify %}
<fieldset class="module">
<h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst|escape }}</h2><table>
<h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst }}</h2><table>
<thead><tr>
{% for fw in bound_related_object.field_wrapper_list %}
{% if fw.needs_header %}
<th{{ fw.header_class_attribute }}>{{ fw.field.verbose_name|capfirst|escape }}</th>
<th{{ fw.header_class_attribute }}>{{ fw.field.verbose_name|capfirst }}</th>
{% endif %}
{% endfor %}
</tr></thead>

View File

@ -19,9 +19,9 @@
{% for model in app.models %}
<tr>
{% if model.perms.change %}
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name|escape }}</a></th>
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
{% else %}
<th scope="row">{{ model.name|escape }}</th>
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.perms.add %}
@ -58,7 +58,7 @@
{% else %}
<ul class="actionlist">
{% for entry in admin_log %}
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{% filter capfirst|escape %}{% trans entry.content_type.name %}{% endfilter %}</span></li>
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %}</span></li>
{% endfor %}
</ul>
{% endif %}

View File

@ -1,7 +1,7 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {{ title|escape }}</div>{% endblock %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {{ title }}</div>{% endblock %}
{% block content %}

View File

@ -1,7 +1,7 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}
<div class="breadcrumbs"><a href="../../../../">{% trans 'Home' %}</a> &rsaquo; <a href="../../">{{ module_name|escape }}</a> &rsaquo; <a href="../">{{ object|escape|truncatewords:"18" }}</a> &rsaquo; {% trans 'History' %}</div>
<div class="breadcrumbs"><a href="../../../../">{% trans 'Home' %}</a> &rsaquo; <a href="../../">{{ module_name }}</a> &rsaquo; <a href="../">{{ object|truncatewords:"18" }}</a> &rsaquo; {% trans 'History' %}</div>
{% endblock %}
{% block content %}
@ -23,8 +23,8 @@
{% for action in action_list %}
<tr>
<th scope="row">{{ action.action_time|date:_("DATE_WITH_TIME_FULL") }}</th>
<td>{{ action.user.username }}{% if action.user.first_name %} ({{ action.user.first_name|escape }} {{ action.user.last_name|escape }}){% endif %}</td>
<td>{{ action.change_message|escape }}</td>
<td>{{ action.user.username }}{% if action.user.first_name %} ({{ action.user.first_name }} {{ action.user.last_name }}){% endif %}</td>
<td>{{ action.change_message }}</td>
</tr>
{% endfor %}
</tbody>

View File

@ -6,6 +6,6 @@
{% paginator_number cl i %}
{% endfor %}
{% endif %}
{{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name|escape }}{% else %}{{ cl.opts.verbose_name_plural|escape }}{% endifequal %}
{{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name|escape }}{% else %}{{ cl.opts.verbose_name_plural }}{% endifequal %}
{% if show_all_url %}&nbsp;&nbsp;<a href="{{ show_all_url }}" class="showall">{% trans 'Show all' %}</a>{% endif %}
</p>

View File

@ -8,16 +8,16 @@
</style>
{% endblock %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; <a href="../">Models</a> &rsaquo; {{ name|escape }}</div>{% endblock %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; <a href="../">Models</a> &rsaquo; {{ name }}</div>{% endblock %}
{% block title %}Model: {{ name|escape }}{% endblock %}
{% block title %}Model: {{ name }}{% endblock %}
{% block content %}
<div id="content-main">
<h1>{{ summary|escape }}</h1>
<h1>{{ summary }}</h1>
{% if description %}
<p>{% filter escape|linebreaksbr %}{% trans description %}{% endfilter %}</p>
<p>{% filter linebreaksbr %}{% trans description %}{% endfilter %}</p>
{% endif %}
<div class="module">

View File

@ -15,6 +15,6 @@
{{ bound_field.original_value }}
{% endif %}
{% if bound_field.raw_id_admin %}
{% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14"|escape }}</strong>{% endif %}
{% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}
{% endif %}
{% endif %}

View File

@ -1,2 +1,2 @@
{% if add %}{% include "widget/foreign.html" %}{% endif %}
{% if change %}{% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14"|escape }}</strong>{% endif %}{% endif %}
{% if change %}{% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}{% endif %}

View File

@ -4,8 +4,9 @@ from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR,
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils import dateformat
from django.utils.html import escape
from django.utils.html import escape, conditional_escape
from django.utils.text import capfirst
from django.utils.safestring import mark_safe
from django.utils.translation import get_date_formats, get_partial_date_formats, ugettext as _
from django.utils.encoding import smart_unicode, smart_str, force_unicode
from django.template import Library
@ -19,9 +20,9 @@ def paginator_number(cl,i):
if i == DOT:
return u'... '
elif i == cl.page_num:
return u'<span class="this-page">%d</span> ' % (i+1)
return mark_safe(u'<span class="this-page">%d</span> ' % (i+1))
else:
return u'<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1)
return mark_safe(u'<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1))
paginator_number = register.simple_tag(paginator_number)
def pagination(cl):
@ -117,7 +118,7 @@ def result_headers(cl):
def _boolean_icon(field_val):
BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
return u'<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
return mark_safe(u'<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val))
def items_for_result(cl, result):
first = True
@ -193,10 +194,10 @@ def items_for_result(cl, result):
# Convert the pk to something that can be used in Javascript.
# Problem cases are long ints (23L) and non-ASCII strings.
result_id = repr(force_unicode(getattr(result, pk)))[1:]
yield (u'<%s%s><a href="%s"%s>%s</a></%s>' % \
(table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), result_repr, table_tag))
yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % \
(table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag))
else:
yield (u'<td%s>%s</td>' % (row_class, result_repr))
yield mark_safe(u'<td%s>%s</td>' % (row_class, conditional_escape(result_repr)))
def results(cl):
for res in cl.result_list:
@ -220,7 +221,7 @@ def date_hierarchy(cl):
day_lookup = cl.params.get(day_field)
year_month_format, month_day_format = get_partial_date_formats()
link = lambda d: cl.get_query_string(d, [field_generic])
link = lambda d: mark_safe(cl.get_query_string(d, [field_generic]))
if year_lookup and month_lookup and day_lookup:
day = datetime.date(int(year_lookup), int(month_lookup), int(day_lookup))

View File

@ -3,6 +3,8 @@ from django.contrib.admin.views.main import AdminBoundField
from django.template import loader
from django.utils.text import capfirst
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe
from django.utils.html import escape
from django.db import models
from django.db.models.fields import Field
from django.db.models.related import BoundRelatedObject
@ -32,7 +34,8 @@ def include_admin_script(script_path):
"""
if not absolute_url_re.match(script_path):
script_path = '%s%s' % (settings.ADMIN_MEDIA_PREFIX, script_path)
return u'<script type="text/javascript" src="%s"></script>' % script_path
return mark_safe(u'<script type="text/javascript" src="%s"></script>'
% script_path)
include_admin_script = register.simple_tag(include_admin_script)
def submit_row(context):
@ -63,8 +66,10 @@ def field_label(bound_field):
class_names.append('inline')
colon = ":"
class_str = class_names and u' class="%s"' % u' '.join(class_names) or u''
return u'<label for="%s"%s>%s%s</label> ' % (bound_field.element_id, class_str, \
force_unicode(capfirst(bound_field.field.verbose_name)), colon)
return mark_safe(u'<label for="%s"%s>%s%s</label> ' %
(bound_field.element_id, class_str,
escape(force_unicode(capfirst(bound_field.field.verbose_name))),
colon))
field_label = register.simple_tag(field_label)
class FieldWidgetNode(template.Node):
@ -193,15 +198,15 @@ def auto_populated_field_script(auto_pop_fields, change = False):
' var e = document.getElementById("id_%s");' \
' if(!e._changed) { e.value = URLify(%s, %s);} }; ' % (
f, field.name, add_values, field.max_length))
return u''.join(t)
return mark_safe(u''.join(t))
auto_populated_field_script = register.simple_tag(auto_populated_field_script)
def filter_interface_script_maybe(bound_field):
f = bound_field.field
if f.rel and isinstance(f.rel, models.ManyToManyRel) and f.rel.filter_interface:
return u'<script type="text/javascript">addEvent(window, "load", function(e) {' \
return mark_safe(u'<script type="text/javascript">addEvent(window, "load", function(e) {' \
' SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % (
f.name, f.verbose_name.replace('"', '\\"'), f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX)
f.name, escape(f.verbose_name.replace('"', '\\"')), f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX))
else:
return ''
filter_interface_script_maybe = register.simple_tag(filter_interface_script_maybe)

View File

@ -1,6 +1,7 @@
from django import template
from django.db.models import get_models
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe
register = template.Library()
@ -38,7 +39,7 @@ class AdminApplistNode(template.Node):
if True in perms.values():
model_list.append({
'name': force_unicode(capfirst(m._meta.verbose_name_plural)),
'admin_url': u'%s/%s/' % (force_unicode(app_label), m.__name__.lower()),
'admin_url': mark_safe(u'%s/%s/' % (force_unicode(app_label), m.__name__.lower())),
'perms': perms,
})

View File

@ -3,6 +3,7 @@
import re
from email.Parser import HeaderParser
from email.Errors import HeaderParseError
from django.utils.safestring import mark_safe
try:
import docutils.core
import docutils.nodes
@ -66,7 +67,7 @@ def parse_rst(text, default_reference_context, thing_being_parsed=None, link_bas
parts = docutils.core.publish_parts(text, source_path=thing_being_parsed,
destination_path=None, writer_name='html',
settings_overrides=overrides)
return parts['fragment']
return mark_safe(parts['fragment'])
#
# reST roles

View File

@ -4,6 +4,7 @@ from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login
from django.shortcuts import render_to_response
from django.utils.translation import ugettext_lazy, ugettext as _
from django.utils.safestring import mark_safe
import base64, datetime, md5
import cPickle as pickle
@ -22,7 +23,7 @@ def _display_login_form(request, error_message=''):
post_data = _encode_post_data({})
return render_to_response('admin/login.html', {
'title': _('Log in'),
'app_path': request.path,
'app_path': mark_safe(request.path),
'post_data': post_data,
'error_message': error_message
}, context_instance=template.RequestContext(request))

View File

@ -10,6 +10,7 @@ from django.core import urlresolvers
from django.contrib.admin import utils
from django.contrib.sites.models import Site
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
import inspect, os, re
# Exclude methods starting with these strings from documentation
@ -29,7 +30,7 @@ def bookmarklets(request):
# Hack! This couples this view to the URL it lives at.
admin_root = request.path[:-len('doc/bookmarklets/')]
return render_to_response('admin_doc/bookmarklets.html', {
'admin_url': "%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root),
'admin_url': mark_safe("%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root)),
}, context_instance=RequestContext(request))
bookmarklets = staff_member_required(bookmarklets)

View File

@ -14,6 +14,7 @@ from django.utils.html import escape
from django.utils.text import capfirst, get_text_list
from django.utils.encoding import force_unicode, smart_str
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
import operator
try:
@ -136,7 +137,9 @@ class AdminBoundField(object):
self._repr_filled = False
if field.rel:
self.related_url = u'../../../%s/%s/' % (field.rel.to._meta.app_label, field.rel.to._meta.object_name.lower())
self.related_url = mark_safe(u'../../../%s/%s/'
% (field.rel.to._meta.app_label,
field.rel.to._meta.object_name.lower()))
def original_value(self):
if self.original:
@ -216,7 +219,7 @@ def render_change_form(model, manipulator, context, add=False, change=False, for
'javascript_imports': get_javascript_imports(opts, auto_populated_fields, field_sets),
'ordered_objects': ordered_objects,
'inline_related_objects': inline_related_objects,
'form_url': form_url,
'form_url': mark_safe(form_url),
'opts': opts,
'content_type_id': ContentType.objects.get_for_model(model).id,
}
@ -436,12 +439,14 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
if related.field.rel.edit_inline or not related.opts.admin:
# Don't display link to edit, because it either has no
# admin or is edited inline.
nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj), []])
nh(deleted_objects, current_depth, [mark_safe(u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj)), []])
else:
# Display a link to the admin page.
nh(deleted_objects, current_depth, [u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
(force_unicode(capfirst(related.opts.verbose_name)), related.opts.app_label, related.opts.object_name.lower(),
sub_obj._get_pk_val(), sub_obj), []])
nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="../../../../%s/%s/%s/">%s</a>' %
(escape(force_unicode(capfirst(related.opts.verbose_name))),
related.opts.app_label,
related.opts.object_name.lower(),
sub_obj._get_pk_val(), sub_obj)), []])
_get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
else:
has_related_objs = False
@ -453,8 +458,8 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), escape(sub_obj)), []])
else:
# Display a link to the admin page.
nh(deleted_objects, current_depth, [u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
(force_unicode(capfirst(related.opts.verbose_name)), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(sub_obj)), []])
nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
(escape(force_unicode(capfirst(related.opts.verbose_name))), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(sub_obj))), []])
_get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
# If there were related objects, and the user doesn't have
# permission to delete them, add the missing perm to perms_needed.
@ -485,9 +490,9 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
else:
# Display a link to the admin page.
nh(deleted_objects, current_depth, [
(_('One or more %(fieldname)s in %(name)s:') % {'fieldname': force_unicode(related.field.verbose_name), 'name': force_unicode(related.opts.verbose_name)}) + \
mark_safe((_('One or more %(fieldname)s in %(name)s:') % {'fieldname': escape(force_unicode(related.field.verbose_name)), 'name': escape(force_unicode(related.opts.verbose_name))}) + \
(u' <a href="../../../../%s/%s/%s/">%s</a>' % \
(related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(sub_obj))), []])
(related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(sub_obj)))), []])
# If there were related objects, and the user doesn't have
# permission to change them, add the missing perm to perms_needed.
if related.opts.admin and has_related_objs:
@ -507,7 +512,7 @@ def delete_stage(request, app_label, model_name, object_id):
# Populate deleted_objects, a data structure of all related objects that
# will also be deleted.
deleted_objects = [u'%s: <a href="../../%s/">%s</a>' % (force_unicode(capfirst(opts.verbose_name)), force_unicode(object_id), escape(obj)), []]
deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), force_unicode(object_id), escape(obj))), []]
perms_needed = set()
_get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1)
@ -604,7 +609,7 @@ class ChangeList(object):
del p[k]
elif v is not None:
p[k] = v
return '?' + '&amp;'.join([u'%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')
return mark_safe('?' + '&amp;'.join([u'%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20'))
def get_results(self, request):
paginator = ObjectPaginator(self.query_set, self.lookup_opts.admin.list_per_page)

View File

@ -8,19 +8,9 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE
redirecting to the log-in page if necessary. The test should be a callable
that takes the user object and returns True if the user passes.
"""
if not login_url:
from django.conf import settings
login_url = settings.LOGIN_URL
def _dec(view_func):
def _checklogin(request, *args, **kwargs):
if test_func(request.user):
return view_func(request, *args, **kwargs)
return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, urlquote(request.get_full_path())))
_checklogin.__doc__ = view_func.__doc__
_checklogin.__dict__ = view_func.__dict__
return _checklogin
return _dec
def decorate(view_func):
return _CheckLogin(view_func, test_func, login_url, redirect_field_name)
return decorate
def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME):
"""
@ -42,3 +32,34 @@ def permission_required(perm, login_url=None):
"""
return user_passes_test(lambda u: u.has_perm(perm), login_url=login_url)
class _CheckLogin(object):
"""
Class that checks that the user passes the given test, redirecting to
the log-in page if necessary. If the test is passed, the view function
is invoked. The test should be a callable that takes the user object
and returns True if the user passes.
We use a class here so that we can define __get__. This way, when a
_CheckLogin object is used as a method decorator, the view function
is properly bound to its instance.
"""
def __init__(self, view_func, test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
if not login_url:
from django.conf import settings
login_url = settings.LOGIN_URL
self.view_func = view_func
self.test_func = test_func
self.login_url = login_url
self.redirect_field_name = redirect_field_name
self.__name__ = view_func.__name__
def __get__(self, obj, cls=None):
view_func = self.view_func.__get__(obj, cls)
return _CheckLogin(view_func, self.test_func, self.login_url, self.redirect_field_name)
def __call__(self, request, *args, **kwargs):
if self.test_func(request.user):
return self.view_func(request, *args, **kwargs)
path = urlquote(request.get_full_path())
tup = self.login_url, self.redirect_field_name, path
return HttpResponseRedirect('%s?%s=%s' % tup)

View File

@ -104,7 +104,7 @@ class PasswordResetForm(oldforms.Manipulator):
'site_name': site_name,
'user': user,
}
send_mail('Password reset on %s' % site_name, t.render(Context(c)), None, [user.email])
send_mail(_('Password reset on %s') % site_name, t.render(Context(c)), None, [user.email])
class PasswordChangeForm(oldforms.Manipulator):
"A form that lets a user change his password."

View File

@ -7,11 +7,12 @@ against request forgeries from other sites.
"""
from django.conf import settings
from django.http import HttpResponseForbidden
from django.utils.safestring import mark_safe
import md5
import re
import itertools
_ERROR_MSG = '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>'
_ERROR_MSG = mark_safe('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>')
_POST_FORM_RE = \
re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
@ -82,10 +83,10 @@ class CsrfMiddleware(object):
itertools.repeat(''))
def add_csrf_field(match):
"""Returns the matched <form> tag plus the added <input> element"""
return match.group() + "<div style='display:none;'>" + \
return mark_safe(match.group() + "<div style='display:none;'>" + \
"<input type='hidden' " + idattributes.next() + \
" name='csrfmiddlewaretoken' value='" + csrf_token + \
"' /></div>"
"' /></div>")
# Modify any POST forms
response.content = _POST_FORM_RE.sub(add_csrf_field, response.content)

View File

@ -8,6 +8,7 @@ from django.utils import dateformat
from django.utils.text import capfirst
from django.utils.translation import get_date_formats
from django.utils.encoding import smart_unicode, smart_str, iri_to_uri
from django.utils.safestring import mark_safe
from django.db.models.query import QuerySet
EMPTY_VALUE = '(None)'
@ -28,7 +29,7 @@ class EasyModel(object):
return self.site.registry[self.model]
def url(self):
return '%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name)
return mark_safe('%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name))
def objects(self, **kwargs):
return self.get_query_set().filter(**kwargs)
@ -68,9 +69,9 @@ class EasyField(object):
def url(self):
if self.field.choices:
return '%s%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name)
return mark_safe('%s%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name))
elif self.field.rel:
return '%s%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name)
return mark_safe('%s%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name))
class EasyChoice(object):
def __init__(self, easy_model, field, value, label):
@ -81,7 +82,7 @@ class EasyChoice(object):
return smart_str(u'<EasyChoice for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
def url(self):
return '%s%s/%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.field.name, iri_to_uri(self.value))
return mark_safe('%s%s/%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.field.name, iri_to_uri(self.value)))
class EasyInstance(object):
def __init__(self, easy_model, instance):
@ -184,14 +185,14 @@ class EasyInstanceField(object):
if self.field.rel.to in self.model.model_list:
lst = []
for value in self.values():
url = '%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val()))
url = mark_safe('%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val())))
lst.append((smart_unicode(value), url))
else:
lst = [(value, None) for value in self.values()]
elif self.field.choices:
lst = []
for value in self.values():
url = '%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value))
url = mark_safe('%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value)))
lst.append((value, url))
elif isinstance(self.field, models.URLField):
val = self.values()[0]

View File

@ -5,8 +5,9 @@ from django.contrib.databrowse.sites import DatabrowsePlugin
from django.shortcuts import render_to_response
from django.utils.text import capfirst
from django.utils.translation import get_date_formats
from django.views.generic import date_based
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe
from django.views.generic import date_based
import datetime
import time
@ -29,16 +30,17 @@ class CalendarPlugin(DatabrowsePlugin):
fields = self.field_dict(model)
if not fields:
return u''
return u'<p class="filter"><strong>View calendar by:</strong> %s</p>' % \
u', '.join(['<a href="calendars/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])
return mark_safe(u'<p class="filter"><strong>View calendar by:</strong> %s</p>' % \
u', '.join(['<a href="calendars/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()]))
def urls(self, plugin_name, easy_instance_field):
if isinstance(easy_instance_field.field, models.DateField):
return [u'%s%s/%s/%s/%s/%s/' % (easy_instance_field.model.url(),
return [mark_safe(u'%s%s/%s/%s/%s/%s/' % (
easy_instance_field.model.url(),
plugin_name, easy_instance_field.field.name,
easy_instance_field.raw_value.year,
easy_instance_field.raw_value.strftime('%b').lower(),
easy_instance_field.raw_value.day)]
easy_instance_field.raw_value.day))]
def model_view(self, request, model_databrowse, url):
self.model, self.site = model_databrowse.model, model_databrowse.site

View File

@ -5,6 +5,7 @@ from django.contrib.databrowse.sites import DatabrowsePlugin
from django.shortcuts import render_to_response
from django.utils.text import capfirst
from django.utils.encoding import smart_str, force_unicode
from django.utils.safestring import mark_safe
from django.views.generic import date_based
import datetime
import time
@ -32,15 +33,16 @@ class FieldChoicePlugin(DatabrowsePlugin):
fields = self.field_dict(model)
if not fields:
return u''
return u'<p class="filter"><strong>View by:</strong> %s</p>' % \
u', '.join(['<a href="fields/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])
return mark_safe(u'<p class="filter"><strong>View by:</strong> %s</p>' % \
u', '.join(['<a href="fields/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()]))
def urls(self, plugin_name, easy_instance_field):
if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values():
field_value = smart_str(easy_instance_field.raw_value)
return [u'%s%s/%s/%s/' % (easy_instance_field.model.url(),
return [mark_safe(u'%s%s/%s/%s/' % (
easy_instance_field.model.url(),
plugin_name, easy_instance_field.field.name,
urllib.quote(field_value, safe=''))]
urllib.quote(field_value, safe='')))]
def model_view(self, request, model_databrowse, url):
self.model, self.site = model_databrowse.model, model_databrowse.site

View File

@ -2,6 +2,7 @@ from django import http
from django.db import models
from django.contrib.databrowse.datastructures import EasyModel, EasyChoice
from django.shortcuts import render_to_response
from django.utils.safestring import mark_safe
class AlreadyRegistered(Exception):
pass
@ -60,7 +61,7 @@ class ModelDatabrowse(object):
def main_view(self, request):
easy_model = EasyModel(self.site, self.model)
html_snippets = u'\n'.join([p.model_index_html(request, self.model, self.site) for p in self.plugins.values()])
html_snippets = mark_safe(u'\n'.join([p.model_index_html(request, self.model, self.site) for p in self.plugins.values()]))
return render_to_response('databrowse/model_detail.html', {
'model': easy_model,
'root_url': self.site.root_url,

View File

@ -4,6 +4,7 @@ from django.shortcuts import get_object_or_404
from django.http import HttpResponse
from django.conf import settings
from django.core.xheaders import populate_xheaders
from django.utils.safestring import mark_safe
DEFAULT_TEMPLATE = 'flatpages/default.html'
@ -30,6 +31,13 @@ def flatpage(request, url):
t = loader.select_template((f.template_name, DEFAULT_TEMPLATE))
else:
t = loader.get_template(DEFAULT_TEMPLATE)
# To avoid having to always use the "|safe" filter in flatpage templates,
# mark the title and content as already safe (since they are raw HTML
# content in the first place).
f.title = mark_safe(f.title)
f.content = mark_safe(f.content)
c = RequestContext(request, {
'flatpage': f,
})

View File

@ -21,6 +21,7 @@ def ordinal(value):
if value % 100 in (11, 12, 13): # special case
return u"%d%s" % (value, t[0])
return u'%d%s' % (value, t[value % 10])
ordinal.is_safe = True
register.filter(ordinal)
def intcomma(value):
@ -34,6 +35,7 @@ def intcomma(value):
return new
else:
return intcomma(new)
intcomma.is_safe = True
register.filter(intcomma)
def intword(value):
@ -55,6 +57,7 @@ def intword(value):
new_value = value / 1000000000000.0
return ungettext('%(value).1f trillion', '%(value).1f trillion', new_value) % {'value': new_value}
return value
intword.is_safe = False
register.filter(intword)
def apnumber(value):
@ -69,6 +72,7 @@ def apnumber(value):
if not 0 < value < 10:
return value
return (_('one'), _('two'), _('three'), _('four'), _('five'), _('six'), _('seven'), _('eight'), _('nine'))[value-1]
apnumber.is_safe = True
register.filter(apnumber)
def naturalday(value, arg=None):

View File

@ -17,6 +17,7 @@ silently fail and return the un-marked-up text.
from django import template
from django.conf import settings
from django.utils.encoding import smart_str, force_unicode
from django.utils.safestring import mark_safe
register = template.Library()
@ -28,7 +29,8 @@ def textile(value):
raise template.TemplateSyntaxError, "Error in {% textile %} filter: The Python textile library isn't installed."
return force_unicode(value)
else:
return force_unicode(textile.textile(smart_str(value), encoding='utf-8', output='utf-8'))
return mark_safe(force_unicode(textile.textile(smart_str(value), encoding='utf-8', output='utf-8')))
textile.is_safe = True
def markdown(value):
try:
@ -38,7 +40,8 @@ def markdown(value):
raise template.TemplateSyntaxError, "Error in {% markdown %} filter: The Python markdown library isn't installed."
return force_unicode(value)
else:
return force_unicode(markdown.markdown(smart_str(value)))
return mark_safe(force_unicode(markdown.markdown(smart_str(value))))
markdown.is_safe = True
def restructuredtext(value):
try:
@ -50,7 +53,8 @@ def restructuredtext(value):
else:
docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {})
parts = publish_parts(source=smart_str(value), writer_name="html4css1", settings_overrides=docutils_settings)
return force_unicode(parts["fragment"])
return mark_safe(force_unicode(parts["fragment"]))
restructuredtext.is_safe = True
register.filter(textile)
register.filter(markdown)

View File

@ -1,9 +1,11 @@
# Quick tests for the markup templatetags (django.contrib.markup)
from django.template import Template, Context, add_to_builtins
import re
import unittest
from django.template import Template, Context, add_to_builtins
from django.utils.html import escape
add_to_builtins('django.contrib.markup.templatetags.markup')
class Templates(unittest.TestCase):
@ -24,7 +26,7 @@ Paragraph 2 with "quotes" and @code@"""
<p>Paragraph 2 with &#8220;quotes&#8221; and <code>code</code></p>""")
else:
self.assertEqual(rendered, textile_content)
self.assertEqual(rendered, escape(textile_content))
def test_markdown(self):
try:

View File

@ -1,8 +1,8 @@
import time
from django.conf import settings
from django.utils.cache import patch_vary_headers
from email.Utils import formatdate
import datetime
import time
from django.utils.http import cookie_date
TEST_COOKIE_NAME = 'testcookie'
TEST_COOKIE_VALUE = 'worked'
@ -10,8 +10,9 @@ TEST_COOKIE_VALUE = 'worked'
class SessionMiddleware(object):
def process_request(self, request):
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
request.session = engine.SessionStore(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None))
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
request.session = engine.SessionStore(session_key)
def process_response(self, request, response):
# If request.session was modified, or if response.session was set, save
@ -30,13 +31,8 @@ class SessionMiddleware(object):
expires = None
else:
max_age = settings.SESSION_COOKIE_AGE
rfcdate = formatdate(time.time() + settings.SESSION_COOKIE_AGE)
# Fixed length date must have '-' separation in the format
# DD-MMM-YYYY for compliance with Netscape cookie standard
expires = datetime.datetime.strftime(datetime.datetime.utcnow() + \
datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
expires_time = time.time() + settings.SESSION_COOKIE_AGE
expires = cookie_date(expires_time)
# Save the seesion data and refresh the client cookie.
request.session.save()
response.set_cookie(settings.SESSION_COOKIE_NAME,

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
{% autoescape off %}<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% spaceless %}
{% for url in urlset %}
@ -11,3 +11,4 @@
{% endfor %}
{% endspaceless %}
</urlset>
{% endautoescape %}

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
{% autoescape off %}<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% for location in sitemaps %}<sitemap><loc>{{ location|escape }}</loc></sitemap>{% endfor %}
</sitemapindex>
{% endautoescape %}

View File

@ -4,6 +4,10 @@ from django import http
import sys
class BaseHandler(object):
# Changes that are always applied to a response (in this order).
response_fixes = [http.fix_location_header,
http.conditional_content_removal]
def __init__(self):
self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None
@ -50,10 +54,6 @@ class BaseHandler(object):
def get_response(self, request):
"Returns an HttpResponse object for the given HttpRequest"
response = self._real_get_response(request)
return fix_location_header(request, response)
def _real_get_response(self, request):
from django.core import exceptions, urlresolvers
from django.core.mail import mail_admins
from django.conf import settings
@ -134,15 +134,13 @@ class BaseHandler(object):
import traceback
return '\n'.join(traceback.format_exception(*(exc_info or sys.exc_info())))
def fix_location_header(request, response):
"""
Ensure that we always use an absolute URI in any location header in the
response. This is required by RFC 2616, section 14.30.
Code constructing response objects is free to insert relative paths and
this function converts them to absolute paths.
"""
if 'Location' in response and request.get_host():
response['Location'] = request.build_absolute_uri(response['Location'])
return response
def apply_response_fixes(self, request, response):
"""
Applies each of the functions in self.response_fixes to the request and
response, modifying the response in the process. Returns the new
response.
"""
for func in self.response_fixes:
response = func(request, response)
return response

View File

@ -162,6 +162,7 @@ class ModPythonHandler(BaseHandler):
# Apply response middleware
for middleware_method in self._response_middleware:
response = middleware_method(request, response)
response = self.apply_response_fixes(request, response)
finally:
dispatcher.send(signal=signals.request_finished)

View File

@ -207,6 +207,7 @@ class WSGIHandler(BaseHandler):
# Apply response middleware
for middleware_method in self._response_middleware:
response = middleware_method(request, response)
response = self.apply_response_fixes(request, response)
finally:
dispatcher.send(signal=signals.request_finished)
@ -220,3 +221,4 @@ class WSGIHandler(BaseHandler):
response_headers.append(('Set-Cookie', str(c.output(header=''))))
start_response(status, response_headers)
return response

View File

@ -242,6 +242,8 @@ def setup_environ(settings_mod):
"""
Configures the runtime environment. This can also be used by external
scripts wanting to set up a similar environment to manage.py.
Returns the project directory (assuming the passed settings module is
directly in the project directory).
"""
# Add this project to sys.path so that it's importable in the conventional
# way. For example, if this file (manage.py) lives in a directory

View File

@ -1,8 +1,10 @@
from django.core.management.base import copy_helper, CommandError, LabelCommand
import os
from django.core.management.base import copy_helper, CommandError, LabelCommand
class Command(LabelCommand):
help = "Creates a Django app directory structure for the given app name in the current directory."
help = ("Creates a Django app directory structure for the given app name"
" in the current directory.")
args = "[appname]"
label = 'application name'
@ -14,17 +16,18 @@ class Command(LabelCommand):
def handle_label(self, app_name, directory=None, **options):
if directory is None:
directory = os.getcwd()
# Determine the project_name a bit naively -- by looking at the name of
# the parent directory.
project_dir = os.path.normpath(os.path.join(directory, os.pardir))
parent_dir = os.path.basename(project_dir)
# Determine the project_name by using the basename of directory,
# which should be the full path of the project directory (or the
# current directory if no directory was passed).
project_name = os.path.basename(directory)
if app_name == project_name:
raise CommandError("You cannot create an app with the same name (%r) as your project." % app_name)
copy_helper(self.style, 'app', app_name, directory, parent_dir)
raise CommandError("You cannot create an app with the same name"
" (%r) as your project." % app_name)
copy_helper(self.style, 'app', app_name, directory, project_name)
class ProjectCommand(Command):
help = "Creates a Django app directory structure for the given app name in this project's directory."
help = ("Creates a Django app directory structure for the given app name"
" in this project's directory.")
def __init__(self, project_directory):
super(ProjectCommand, self).__init__()

View File

@ -252,6 +252,7 @@ def sql_model_create(model, style, known_models=set()):
table_output = []
pending_references = {}
qn = connection.ops.quote_name
inline_references = connection.features.inline_fk_references
for f in opts.fields:
col_type = f.db_type()
tablespace = f.db_tablespace or opts.db_tablespace
@ -272,7 +273,7 @@ def sql_model_create(model, style, known_models=set()):
# won't be generating a CREATE INDEX statement for this field.
field_output.append(connection.ops.tablespace_sql(tablespace, inline=True))
if f.rel:
if f.rel.to in known_models:
if inline_references and f.rel.to in known_models:
field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \
style.SQL_TABLE(qn(f.rel.to._meta.db_table)) + ' (' + \
style.SQL_FIELD(qn(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')' +
@ -341,10 +342,12 @@ def sql_for_pending_references(model, style, pending_references):
def many_to_many_sql_for_model(model, style):
from django.db import connection, models
from django.contrib.contenttypes import generic
from django.db.backends.util import truncate_name
opts = model._meta
final_output = []
qn = connection.ops.quote_name
inline_references = connection.features.inline_fk_references
for f in opts.many_to_many:
if not isinstance(f.rel, generic.GenericRel):
tablespace = f.db_tablespace or opts.db_tablespace
@ -354,26 +357,43 @@ def many_to_many_sql_for_model(model, style):
tablespace_sql = ''
table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
style.SQL_TABLE(qn(f.m2m_db_table())) + ' (']
table_output.append(' %s %s %s%s,' % \
table_output.append(' %s %s %s%s,' %
(style.SQL_FIELD(qn('id')),
style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()),
style.SQL_KEYWORD('NOT NULL PRIMARY KEY'),
tablespace_sql))
table_output.append(' %s %s %s %s (%s)%s,' % \
(style.SQL_FIELD(qn(f.m2m_column_name())),
style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
style.SQL_KEYWORD('NOT NULL REFERENCES'),
style.SQL_TABLE(qn(opts.db_table)),
style.SQL_FIELD(qn(opts.pk.column)),
connection.ops.deferrable_sql()))
table_output.append(' %s %s %s %s (%s)%s,' % \
(style.SQL_FIELD(qn(f.m2m_reverse_name())),
style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()),
style.SQL_KEYWORD('NOT NULL REFERENCES'),
style.SQL_TABLE(qn(f.rel.to._meta.db_table)),
style.SQL_FIELD(qn(f.rel.to._meta.pk.column)),
connection.ops.deferrable_sql()))
table_output.append(' %s (%s, %s)%s' % \
if inline_references:
deferred = []
table_output.append(' %s %s %s %s (%s)%s,' %
(style.SQL_FIELD(qn(f.m2m_column_name())),
style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
style.SQL_KEYWORD('NOT NULL REFERENCES'),
style.SQL_TABLE(qn(opts.db_table)),
style.SQL_FIELD(qn(opts.pk.column)),
connection.ops.deferrable_sql()))
table_output.append(' %s %s %s %s (%s)%s,' %
(style.SQL_FIELD(qn(f.m2m_reverse_name())),
style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()),
style.SQL_KEYWORD('NOT NULL REFERENCES'),
style.SQL_TABLE(qn(f.rel.to._meta.db_table)),
style.SQL_FIELD(qn(f.rel.to._meta.pk.column)),
connection.ops.deferrable_sql()))
else:
table_output.append(' %s %s %s,' %
(style.SQL_FIELD(qn(f.m2m_column_name())),
style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
style.SQL_KEYWORD('NOT NULL')))
table_output.append(' %s %s %s,' %
(style.SQL_FIELD(qn(f.m2m_reverse_name())),
style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()),
style.SQL_KEYWORD('NOT NULL')))
deferred = [
(f.m2m_db_table(), f.m2m_column_name(), opts.db_table,
opts.pk.column),
( f.m2m_db_table(), f.m2m_reverse_name(),
f.rel.to._meta.db_table, f.rel.to._meta.pk.column)
]
table_output.append(' %s (%s, %s)%s' %
(style.SQL_KEYWORD('UNIQUE'),
style.SQL_FIELD(qn(f.m2m_column_name())),
style.SQL_FIELD(qn(f.m2m_reverse_name())),
@ -385,6 +405,15 @@ def many_to_many_sql_for_model(model, style):
table_output.append(';')
final_output.append('\n'.join(table_output))
for r_table, r_col, table, col in deferred:
r_name = '%s_refs_%s_%x' % (r_col, col,
abs(hash((r_table, table))))
final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' %
(qn(r_table),
truncate_name(r_name, connection.ops.max_name_length()),
qn(r_col), qn(table), qn(col),
connection.ops.deferrable_sql()))
# Add any extra SQL needed to support auto-incrementing PKs
autoinc_sql = connection.ops.autoinc_sql(f.m2m_db_table(), 'id')
if autoinc_sql:

View File

@ -9,14 +9,14 @@ been reviewed for security issues. Don't use it for production use.
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from types import ListType, StringType
from email.Utils import formatdate
import mimetypes
import os
import re
import sys
import time
import urllib
from django.utils.http import http_date
__version__ = "0.1"
__all__ = ['WSGIServer','WSGIRequestHandler','demo_app']
@ -376,7 +376,7 @@ class ServerHandler(object):
self._write('HTTP/%s %s\r\n' % (self.http_version,self.status))
if 'Date' not in self.headers:
self._write(
'Date: %s\r\n' % (formatdate()[:26] + "GMT")
'Date: %s\r\n' % http_date()
)
if self.server_software and 'Server' not in self.headers:
self._write('Server: %s\r\n' % self.server_software)

View File

@ -43,6 +43,7 @@ class BaseDatabaseFeatures(object):
allows_group_by_ordinal = True
allows_unique_and_pk = True
autoindexes_primary_keys = True
inline_fk_references = True
needs_datetime_string_cast = True
needs_upper_for_iops = False
supports_constraints = True

View File

@ -61,6 +61,7 @@ server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
class DatabaseFeatures(BaseDatabaseFeatures):
autoindexes_primary_keys = False
inline_fk_references = False
class DatabaseOperations(BaseDatabaseOperations):
def date_extract_sql(self, lookup_type, field_name):

View File

@ -65,6 +65,7 @@ class MysqlDebugWrapper:
class DatabaseFeatures(BaseDatabaseFeatures):
autoindexes_primary_keys = False
inline_fk_references = False
class DatabaseOperations(BaseDatabaseOperations):
def date_extract_sql(self, lookup_type, field_name):

View File

@ -7,6 +7,7 @@ from django.db.models.query import Q
from django.db.models.manager import Manager
from django.db.models.base import Model, AdminOptions
from django.db.models.fields import *
from django.db.models.fields.subclassing import SubfieldBase
from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED
from django.db.models import signals
from django.utils.functional import curry

View File

@ -147,6 +147,8 @@ class Field(object):
# exactly which wacky database column type you want to use.
data_types = get_creation_module().DATA_TYPES
internal_type = self.get_internal_type()
if internal_type not in data_types:
return None
return data_types[internal_type] % self.__dict__
def validate_full(self, field_data, all_data):

View File

@ -0,0 +1,53 @@
"""
Convenience routines for creating non-trivial Field subclasses.
Add SubfieldBase as the __metaclass__ for your Field subclass, implement
to_python() and the other necessary methods and everything will work seamlessly.
"""
from django.utils.maxlength import LegacyMaxlength
class SubfieldBase(LegacyMaxlength):
"""
A metaclass for custom Field subclasses. This ensures the model's attribute
has the descriptor protocol attached to it.
"""
def __new__(cls, base, name, attrs):
new_class = super(SubfieldBase, cls).__new__(cls, base, name, attrs)
new_class.contribute_to_class = make_contrib(
attrs.get('contribute_to_class'))
return new_class
class Creator(object):
"""
A placeholder class that provides a way to set the attribute on the model.
"""
def __init__(self, field):
self.field = field
def __get__(self, obj, type=None):
if obj is None:
raise AttributeError('Can only be accessed via an instance.')
return self.value
def __set__(self, obj, value):
self.value = self.field.to_python(value)
def make_contrib(func=None):
"""
Returns a suitable contribute_to_class() method for the Field subclass.
If 'func' is passed in, it is the existing contribute_to_class() method on
the subclass and it is called before anything else. It is assumed in this
case that the existing contribute_to_class() calls all the necessary
superclass methods.
"""
def contribute_to_class(self, cls, name):
if func:
func(self, cls, name)
else:
super(self.__class__, self).contribute_to_class(cls, name)
setattr(cls, self.name, Creator(self))
return contribute_to_class

View File

@ -5,6 +5,7 @@ from urllib import urlencode
from urlparse import urljoin
from django.utils.datastructures import MultiValueDict, FileDict
from django.utils.encoding import smart_str, iri_to_uri, force_unicode
from utils import *
RESERVED_CHARS="!*'();:@&=+$,/?%#[]"

34
django/http/utils.py Normal file
View File

@ -0,0 +1,34 @@
"""
Functions that modify an HTTP request or response in some way.
"""
# This group of functions are run as part of the response handling, after
# everything else, including all response middleware. Think of them as
# "compulsory response middleware". Be careful about what goes here, because
# it's a little fiddly to override this behaviour, so they should be truly
# universally applicable.
def fix_location_header(request, response):
"""
Ensures that we always use an absolute URI in any location header in the
response. This is required by RFC 2616, section 14.30.
Code constructing response objects is free to insert relative paths and
this function converts them to absolute paths.
"""
if 'Location' in response and request.get_host():
response['Location'] = request.build_absolute_uri(response['Location'])
return response
def conditional_content_removal(request, response):
"""
Removes the content of responses for HEAD requests, 1xx, 204 and 304
responses. Ensures compliance with RFC 2616, section 4.3.
"""
if 100 <= response.status_code < 200 or response.status_code in (204, 304):
response.content = ''
response['Content-Length'] = 0
if request.method == 'HEAD':
response.content = ''
return response

View File

@ -1,4 +1,4 @@
from email.Utils import formatdate
from django.utils.http import http_date
class ConditionalGetMiddleware(object):
"""
@ -6,32 +6,27 @@ class ConditionalGetMiddleware(object):
Last-Modified header, and the request has If-None-Match or
If-Modified-Since, the response is replaced by an HttpNotModified.
Removes the content from any response to a HEAD request.
Also sets the Date and Content-Length response-headers.
"""
def process_response(self, request, response):
response['Date'] = formatdate()[:26] + "GMT"
response['Date'] = http_date()
if not response.has_header('Content-Length'):
response['Content-Length'] = str(len(response.content))
if response.has_header('ETag'):
if_none_match = request.META.get('HTTP_IF_NONE_MATCH', None)
if if_none_match == response['ETag']:
response.status_code = 304
response.content = ''
response['Content-Length'] = '0'
# Setting the status is enough here. The response handling path
# automatically removes content for this status code (in
# http.conditional_content_removal()).
response.status = 304
if response.has_header('Last-Modified'):
last_mod = response['Last-Modified']
if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None)
if if_modified_since == response['Last-Modified']:
response.status_code = 304
response.content = ''
response['Content-Length'] = '0'
if request.method == 'HEAD':
response.content = ''
# Setting the status code is enough here (same reasons as
# above).
response.status = 304
return response

View File

@ -1,30 +1,35 @@
"""
Field classes
Field classes.
"""
import copy
import datetime
import re
import time
# Python 2.3 fallbacks
try:
from decimal import Decimal, DecimalException
except ImportError:
from django.utils._decimal import Decimal, DecimalException
try:
set
except NameError:
from sets import Set as set
from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import StrAndUnicode, smart_unicode
from util import ErrorList, ValidationError
from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
try:
from decimal import Decimal, DecimalException
except ImportError:
from django.utils._decimal import Decimal, DecimalException
__all__ = (
'Field', 'CharField', 'IntegerField',
'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'BooleanField',
'ChoiceField', 'NullBooleanField', 'MultipleChoiceField',
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
'SplitDateTimeField', 'IPAddressField',
)
@ -32,24 +37,20 @@ __all__ = (
# These values, if given to to_python(), will trigger the self.required check.
EMPTY_VALUES = (None, '')
try:
set
except NameError:
from sets import Set as set # Python 2.3 fallback
try:
from decimal import Decimal
except ImportError:
from django.utils._decimal import Decimal # Python 2.3 fallback
class Field(object):
widget = TextInput # Default widget to use when rendering this type of Field.
hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
default_error_messages = {
'required': _(u'This field is required.'),
'invalid': _(u'Enter a valid value.'),
}
# Tracks each time a Field instance is created. Used to retain order.
creation_counter = 0
def __init__(self, required=True, widget=None, label=None, initial=None, help_text=None):
def __init__(self, required=True, widget=None, label=None, initial=None,
help_text=None, error_messages=None):
# required -- Boolean that specifies whether the field is required.
# True by default.
# widget -- A Widget class, or instance of a Widget class, that should
@ -82,6 +83,22 @@ class Field(object):
self.creation_counter = Field.creation_counter
Field.creation_counter += 1
self.error_messages = self._build_error_messages(error_messages)
def _build_error_messages(self, extra_error_messages):
error_messages = {}
def get_default_error_messages(klass):
for base_class in klass.__bases__:
get_default_error_messages(base_class)
if hasattr(klass, 'default_error_messages'):
error_messages.update(klass.default_error_messages)
get_default_error_messages(self.__class__)
if extra_error_messages:
error_messages.update(extra_error_messages)
return error_messages
def clean(self, value):
"""
Validates the given value and returns its "cleaned" value as an
@ -90,7 +107,7 @@ class Field(object):
Raises ValidationError for any errors.
"""
if self.required and value in EMPTY_VALUES:
raise ValidationError(ugettext(u'This field is required.'))
raise ValidationError(self.error_messages['required'])
return value
def widget_attrs(self, widget):
@ -108,6 +125,11 @@ class Field(object):
return result
class CharField(Field):
default_error_messages = {
'max_length': _(u'Ensure this value has at most %(max)d characters (it has %(length)d).'),
'min_length': _(u'Ensure this value has at least %(min)d characters (it has %(length)d).'),
}
def __init__(self, max_length=None, min_length=None, *args, **kwargs):
self.max_length, self.min_length = max_length, min_length
super(CharField, self).__init__(*args, **kwargs)
@ -120,9 +142,9 @@ class CharField(Field):
value = smart_unicode(value)
value_length = len(value)
if self.max_length is not None and value_length > self.max_length:
raise ValidationError(ugettext(u'Ensure this value has at most %(max)d characters (it has %(length)d).') % {'max': self.max_length, 'length': value_length})
raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length})
if self.min_length is not None and value_length < self.min_length:
raise ValidationError(ugettext(u'Ensure this value has at least %(min)d characters (it has %(length)d).') % {'min': self.min_length, 'length': value_length})
raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length})
return value
def widget_attrs(self, widget):
@ -131,6 +153,12 @@ class CharField(Field):
return {'maxlength': str(self.max_length)}
class IntegerField(Field):
default_error_messages = {
'invalid': _(u'Enter a whole number.'),
'max_value': _(u'Ensure this value is less than or equal to %s.'),
'min_value': _(u'Ensure this value is greater than or equal to %s.'),
}
def __init__(self, max_value=None, min_value=None, *args, **kwargs):
self.max_value, self.min_value = max_value, min_value
super(IntegerField, self).__init__(*args, **kwargs)
@ -146,14 +174,20 @@ class IntegerField(Field):
try:
value = int(str(value))
except (ValueError, TypeError):
raise ValidationError(ugettext(u'Enter a whole number.'))
raise ValidationError(self.error_messages['invalid'])
if self.max_value is not None and value > self.max_value:
raise ValidationError(ugettext(u'Ensure this value is less than or equal to %s.') % self.max_value)
raise ValidationError(self.error_messages['max_value'] % self.max_value)
if self.min_value is not None and value < self.min_value:
raise ValidationError(ugettext(u'Ensure this value is greater than or equal to %s.') % self.min_value)
raise ValidationError(self.error_messages['min_value'] % self.min_value)
return value
class FloatField(Field):
default_error_messages = {
'invalid': _(u'Enter a number.'),
'max_value': _(u'Ensure this value is less than or equal to %s.'),
'min_value': _(u'Ensure this value is greater than or equal to %s.'),
}
def __init__(self, max_value=None, min_value=None, *args, **kwargs):
self.max_value, self.min_value = max_value, min_value
Field.__init__(self, *args, **kwargs)
@ -169,14 +203,23 @@ class FloatField(Field):
try:
value = float(value)
except (ValueError, TypeError):
raise ValidationError(ugettext('Enter a number.'))
raise ValidationError(self.error_messages['invalid'])
if self.max_value is not None and value > self.max_value:
raise ValidationError(ugettext('Ensure this value is less than or equal to %s.') % self.max_value)
raise ValidationError(self.error_messages['max_value'] % self.max_value)
if self.min_value is not None and value < self.min_value:
raise ValidationError(ugettext('Ensure this value is greater than or equal to %s.') % self.min_value)
raise ValidationError(self.error_messages['min_value'] % self.min_value)
return value
class DecimalField(Field):
default_error_messages = {
'invalid': _(u'Enter a number.'),
'max_value': _(u'Ensure this value is less than or equal to %s.'),
'min_value': _(u'Ensure this value is greater than or equal to %s.'),
'max_digits': _('Ensure that there are no more than %s digits in total.'),
'max_decimal_places': _('Ensure that there are no more than %s decimal places.'),
'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.')
}
def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs):
self.max_value, self.min_value = max_value, min_value
self.max_digits, self.decimal_places = max_digits, decimal_places
@ -196,20 +239,20 @@ class DecimalField(Field):
try:
value = Decimal(value)
except DecimalException:
raise ValidationError(ugettext('Enter a number.'))
raise ValidationError(self.error_messages['invalid'])
pieces = str(value).lstrip("-").split('.')
decimals = (len(pieces) == 2) and len(pieces[1]) or 0
digits = len(pieces[0])
if self.max_value is not None and value > self.max_value:
raise ValidationError(ugettext('Ensure this value is less than or equal to %s.') % self.max_value)
raise ValidationError(self.error_messages['max_value'] % self.max_value)
if self.min_value is not None and value < self.min_value:
raise ValidationError(ugettext('Ensure this value is greater than or equal to %s.') % self.min_value)
raise ValidationError(self.error_messages['min_value'] % self.min_value)
if self.max_digits is not None and (digits + decimals) > self.max_digits:
raise ValidationError(ugettext('Ensure that there are no more than %s digits in total.') % self.max_digits)
raise ValidationError(self.error_messages['max_digits'] % self.max_digits)
if self.decimal_places is not None and decimals > self.decimal_places:
raise ValidationError(ugettext('Ensure that there are no more than %s decimal places.') % self.decimal_places)
raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places)
if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places):
raise ValidationError(ugettext('Ensure that there are no more than %s digits before the decimal point.') % (self.max_digits - self.decimal_places))
raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places))
return value
DEFAULT_DATE_INPUT_FORMATS = (
@ -221,6 +264,10 @@ DEFAULT_DATE_INPUT_FORMATS = (
)
class DateField(Field):
default_error_messages = {
'invalid': _(u'Enter a valid date.'),
}
def __init__(self, input_formats=None, *args, **kwargs):
super(DateField, self).__init__(*args, **kwargs)
self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
@ -242,7 +289,7 @@ class DateField(Field):
return datetime.date(*time.strptime(value, format)[:3])
except ValueError:
continue
raise ValidationError(ugettext(u'Enter a valid date.'))
raise ValidationError(self.error_messages['invalid'])
DEFAULT_TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
@ -250,6 +297,10 @@ DEFAULT_TIME_INPUT_FORMATS = (
)
class TimeField(Field):
default_error_messages = {
'invalid': _(u'Enter a valid time.')
}
def __init__(self, input_formats=None, *args, **kwargs):
super(TimeField, self).__init__(*args, **kwargs)
self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
@ -269,7 +320,7 @@ class TimeField(Field):
return datetime.time(*time.strptime(value, format)[3:6])
except ValueError:
continue
raise ValidationError(ugettext(u'Enter a valid time.'))
raise ValidationError(self.error_messages['invalid'])
DEFAULT_DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
@ -285,6 +336,9 @@ DEFAULT_DATETIME_INPUT_FORMATS = (
class DateTimeField(Field):
widget = DateTimeInput
default_error_messages = {
'invalid': _(u'Enter a valid date/time.'),
}
def __init__(self, input_formats=None, *args, **kwargs):
super(DateTimeField, self).__init__(*args, **kwargs)
@ -306,14 +360,14 @@ class DateTimeField(Field):
# Input comes from a SplitDateTimeWidget, for example. So, it's two
# components: date and time.
if len(value) != 2:
raise ValidationError(ugettext(u'Enter a valid date/time.'))
raise ValidationError(self.error_messages['invalid'])
value = '%s %s' % tuple(value)
for format in self.input_formats:
try:
return datetime.datetime(*time.strptime(value, format)[:6])
except ValueError:
continue
raise ValidationError(ugettext(u'Enter a valid date/time.'))
raise ValidationError(self.error_messages['invalid'])
class RegexField(CharField):
def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
@ -322,11 +376,15 @@ class RegexField(CharField):
error_message is an optional error message to use, if
'Enter a valid value' is too generic for you.
"""
# error_message is just kept for backwards compatibility:
if error_message:
error_messages = kwargs.get('error_messages') or {}
error_messages['invalid'] = error_message
kwargs['error_messages'] = error_messages
super(RegexField, self).__init__(max_length, min_length, *args, **kwargs)
if isinstance(regex, basestring):
regex = re.compile(regex)
self.regex = regex
self.error_message = error_message or ugettext(u'Enter a valid value.')
def clean(self, value):
"""
@ -337,7 +395,7 @@ class RegexField(CharField):
if value == u'':
return value
if not self.regex.search(value):
raise ValidationError(self.error_message)
raise ValidationError(self.error_messages['invalid'])
return value
email_re = re.compile(
@ -346,9 +404,13 @@ email_re = re.compile(
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
class EmailField(RegexField):
default_error_messages = {
'invalid': _(u'Enter a valid e-mail address.'),
}
def __init__(self, max_length=None, min_length=None, *args, **kwargs):
RegexField.__init__(self, email_re, max_length, min_length,
ugettext(u'Enter a valid e-mail address.'), *args, **kwargs)
RegexField.__init__(self, email_re, max_length, min_length, *args,
**kwargs)
try:
from django.conf import settings
@ -372,6 +434,12 @@ class UploadedFile(StrAndUnicode):
class FileField(Field):
widget = FileInput
default_error_messages = {
'invalid': _(u"No file was submitted. Check the encoding type on the form."),
'missing': _(u"No file was submitted."),
'empty': _(u"The submitted file is empty."),
}
def __init__(self, *args, **kwargs):
super(FileField, self).__init__(*args, **kwargs)
@ -382,14 +450,18 @@ class FileField(Field):
try:
f = UploadedFile(data['filename'], data['content'])
except TypeError:
raise ValidationError(ugettext(u"No file was submitted. Check the encoding type on the form."))
raise ValidationError(self.error_messages['invalid'])
except KeyError:
raise ValidationError(ugettext(u"No file was submitted."))
raise ValidationError(self.error_messages['missing'])
if not f.content:
raise ValidationError(ugettext(u"The submitted file is empty."))
raise ValidationError(self.error_messages['empty'])
return f
class ImageField(FileField):
default_error_messages = {
'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
}
def clean(self, data):
"""
Checks that the file-upload field data contains a valid image (GIF, JPG,
@ -410,7 +482,7 @@ class ImageField(FileField):
trial_image = Image.open(StringIO(f.content))
trial_image.verify()
except Exception: # Python Imaging Library doesn't recognize it as an image
raise ValidationError(ugettext(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."))
raise ValidationError(self.error_messages['invalid_image'])
return f
url_re = re.compile(
@ -422,9 +494,15 @@ url_re = re.compile(
r'(?:/?|/\S+)$', re.IGNORECASE)
class URLField(RegexField):
default_error_messages = {
'invalid': _(u'Enter a valid URL.'),
'invalid_link': _(u'This URL appears to be a broken link.'),
}
def __init__(self, max_length=None, min_length=None, verify_exists=False,
validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs):
super(URLField, self).__init__(url_re, max_length, min_length, ugettext(u'Enter a valid URL.'), *args, **kwargs)
super(URLField, self).__init__(url_re, max_length, min_length, *args,
**kwargs)
self.verify_exists = verify_exists
self.user_agent = validator_user_agent
@ -449,9 +527,9 @@ class URLField(RegexField):
req = urllib2.Request(value, None, headers)
u = urllib2.urlopen(req)
except ValueError:
raise ValidationError(ugettext(u'Enter a valid URL.'))
raise ValidationError(self.error_messages['invalid'])
except: # urllib2.URLError, httplib.InvalidURL, etc.
raise ValidationError(ugettext(u'This URL appears to be a broken link.'))
raise ValidationError(self.error_messages['invalid_link'])
return value
class BooleanField(Field):
@ -478,9 +556,14 @@ class NullBooleanField(BooleanField):
class ChoiceField(Field):
widget = Select
default_error_messages = {
'invalid_choice': _(u'Select a valid choice. That choice is not one of the available choices.'),
}
def __init__(self, choices=(), required=True, widget=None, label=None, initial=None, help_text=None):
super(ChoiceField, self).__init__(required, widget, label, initial, help_text)
def __init__(self, choices=(), required=True, widget=None, label=None,
initial=None, help_text=None, *args, **kwargs):
super(ChoiceField, self).__init__(required, widget, label, initial,
help_text, *args, **kwargs)
self.choices = choices
def _get_choices(self):
@ -506,29 +589,33 @@ class ChoiceField(Field):
return value
valid_values = set([smart_unicode(k) for k, v in self.choices])
if value not in valid_values:
raise ValidationError(ugettext(u'Select a valid choice. That choice is not one of the available choices.'))
raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
return value
class MultipleChoiceField(ChoiceField):
hidden_widget = MultipleHiddenInput
widget = SelectMultiple
default_error_messages = {
'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'),
'invalid_list': _(u'Enter a list of values.'),
}
def clean(self, value):
"""
Validates that the input is a list or tuple.
"""
if self.required and not value:
raise ValidationError(ugettext(u'This field is required.'))
raise ValidationError(self.error_messages['required'])
elif not self.required and not value:
return []
if not isinstance(value, (list, tuple)):
raise ValidationError(ugettext(u'Enter a list of values.'))
raise ValidationError(self.error_messages['invalid_list'])
new_value = [smart_unicode(val) for val in value]
# Validate that each value in the value list is in self.choices.
valid_values = set([smart_unicode(k) for k, v in self.choices])
for val in new_value:
if val not in valid_values:
raise ValidationError(ugettext(u'Select a valid choice. %s is not one of the available choices.') % val)
raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
return new_value
class ComboField(Field):
@ -571,6 +658,10 @@ class MultiValueField(Field):
You'll probably want to use this with MultiWidget.
"""
default_error_messages = {
'invalid': _(u'Enter a list of values.'),
}
def __init__(self, fields=(), *args, **kwargs):
super(MultiValueField, self).__init__(*args, **kwargs)
# Set 'required' to False on the individual fields, because the
@ -594,18 +685,18 @@ class MultiValueField(Field):
if not value or isinstance(value, (list, tuple)):
if not value or not [v for v in value if v not in EMPTY_VALUES]:
if self.required:
raise ValidationError(ugettext(u'This field is required.'))
raise ValidationError(self.error_messages['required'])
else:
return self.compress([])
else:
raise ValidationError(ugettext(u'Enter a list of values.'))
raise ValidationError(self.error_messages['invalid'])
for i, field in enumerate(self.fields):
try:
field_value = value[i]
except IndexError:
field_value = None
if self.required and field_value in EMPTY_VALUES:
raise ValidationError(ugettext(u'This field is required.'))
raise ValidationError(self.error_messages['required'])
try:
clean_data.append(field.clean(field_value))
except ValidationError, e:
@ -629,8 +720,19 @@ class MultiValueField(Field):
raise NotImplementedError('Subclasses must implement this method.')
class SplitDateTimeField(MultiValueField):
default_error_messages = {
'invalid_date': _(u'Enter a valid date.'),
'invalid_time': _(u'Enter a valid time.'),
}
def __init__(self, *args, **kwargs):
fields = (DateField(), TimeField())
errors = self.default_error_messages.copy()
if 'error_messages' in kwargs:
errors.update(kwargs['error_messages'])
fields = (
DateField(error_messages={'invalid': errors['invalid_date']}),
TimeField(error_messages={'invalid': errors['invalid_time']}),
)
super(SplitDateTimeField, self).__init__(fields, *args, **kwargs)
def compress(self, data_list):
@ -638,16 +740,18 @@ class SplitDateTimeField(MultiValueField):
# Raise a validation error if time or date is empty
# (possible if SplitDateTimeField has required=False).
if data_list[0] in EMPTY_VALUES:
raise ValidationError(ugettext(u'Enter a valid date.'))
raise ValidationError(self.error_messages['invalid_date'])
if data_list[1] in EMPTY_VALUES:
raise ValidationError(ugettext(u'Enter a valid time.'))
raise ValidationError(self.error_messages['invalid_time'])
return datetime.datetime.combine(*data_list)
return None
ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
class IPAddressField(RegexField):
default_error_messages = {
'invalid': _(u'Enter a valid IPv4 address.'),
}
def __init__(self, *args, **kwargs):
RegexField.__init__(self, ipv4_re,
error_message=ugettext(u'Enter a valid IPv4 address.'),
*args, **kwargs)
super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs)

View File

@ -2,11 +2,12 @@
Form classes
"""
import copy
from copy import deepcopy
from django.utils.datastructures import SortedDict
from django.utils.html import escape
from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
from django.utils.safestring import mark_safe
from fields import Field
from widgets import TextInput, Textarea
@ -21,18 +22,6 @@ def pretty_name(name):
name = name[0].upper() + name[1:]
return name.replace('_', ' ')
class SortedDictFromList(SortedDict):
"A dictionary that keeps its keys in the order in which they're inserted."
# This is different than django.utils.datastructures.SortedDict, because
# this takes a list/tuple as the argument to __init__().
def __init__(self, data=None):
if data is None: data = []
self.keyOrder = [d[0] for d in data]
dict.__init__(self, dict(data))
def copy(self):
return SortedDictFromList([(k, copy.deepcopy(v)) for k, v in self.items()])
class DeclarativeFieldsMetaclass(type):
"""
Metaclass that converts Field attributes to a dictionary called
@ -49,7 +38,7 @@ class DeclarativeFieldsMetaclass(type):
if hasattr(base, 'base_fields'):
fields = base.base_fields.items() + fields
attrs['base_fields'] = SortedDictFromList(fields)
attrs['base_fields'] = SortedDict(fields)
return type.__new__(cls, name, bases, attrs)
class BaseForm(StrAndUnicode):
@ -74,7 +63,7 @@ class BaseForm(StrAndUnicode):
# alter self.fields, we create self.fields here by copying base_fields.
# Instances should always modify self.fields; they should not modify
# self.base_fields.
self.fields = self.base_fields.copy()
self.fields = deepcopy(self.base_fields)
def __unicode__(self):
return self.as_table()
@ -130,7 +119,8 @@ class BaseForm(StrAndUnicode):
output.append(error_row % force_unicode(bf_errors))
if bf.label:
label = escape(force_unicode(bf.label))
# Only add the suffix if the label does not end in punctuation.
# Only add the suffix if the label does not end in
# punctuation.
if self.label_suffix:
if label[-1] not in ':?.!':
label += self.label_suffix
@ -148,11 +138,14 @@ class BaseForm(StrAndUnicode):
str_hidden = u''.join(hidden_fields)
if output:
last_row = output[-1]
# Chop off the trailing row_ender (e.g. '</td></tr>') and insert the hidden fields.
# Chop off the trailing row_ender (e.g. '</td></tr>') and
# insert the hidden fields.
output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
else: # If there aren't any rows in the output, just append the hidden fields.
else:
# If there aren't any rows in the output, just append the
# hidden fields.
output.append(str_hidden)
return u'\n'.join(output)
return mark_safe(u'\n'.join(output))
def as_table(self):
"Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
@ -315,7 +308,7 @@ class BoundField(StrAndUnicode):
if id_:
attrs = attrs and flatatt(attrs) or ''
contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents)
return contents
return mark_safe(contents)
def _is_hidden(self):
"Returns True if this BoundField's widget is hidden."

View File

@ -5,10 +5,10 @@ and database field objects.
from django.utils.translation import ugettext
from django.utils.encoding import smart_unicode
from django.utils.datastructures import SortedDict
from util import ValidationError
from forms import BaseForm, SortedDictFromList
from forms import BaseForm
from fields import Field, ChoiceField
from widgets import Select, SelectMultiple, MultipleHiddenInput
@ -17,7 +17,8 @@ __all__ = (
'ModelChoiceField', 'ModelMultipleChoiceField'
)
def save_instance(form, instance, fields=None, fail_message='saved', commit=True):
def save_instance(form, instance, fields=None, fail_message='saved',
commit=True):
"""
Saves bound Form ``form``'s cleaned_data into model instance ``instance``.
@ -27,15 +28,17 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True
from django.db import models
opts = instance.__class__._meta
if form.errors:
raise ValueError("The %s could not be %s because the data didn't validate." % (opts.object_name, fail_message))
raise ValueError("The %s could not be %s because the data didn't"
" validate." % (opts.object_name, fail_message))
cleaned_data = form.cleaned_data
for f in opts.fields:
if not f.editable or isinstance(f, models.AutoField) or not f.name in cleaned_data:
if not f.editable or isinstance(f, models.AutoField) \
or not f.name in cleaned_data:
continue
if fields and f.name not in fields:
continue
f.save_form_data(instance, cleaned_data[f.name])
# Wrap up the saving of m2m data as a function
# Wrap up the saving of m2m data as a function.
def save_m2m():
opts = instance.__class__._meta
cleaned_data = form.cleaned_data
@ -45,28 +48,29 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True
if f.name in cleaned_data:
f.save_form_data(instance, cleaned_data[f.name])
if commit:
# If we are committing, save the instance and the m2m data immediately
# If we are committing, save the instance and the m2m data immediately.
instance.save()
save_m2m()
else:
# We're not committing. Add a method to the form to allow deferred
# saving of m2m data
# saving of m2m data.
form.save_m2m = save_m2m
return instance
def make_model_save(model, fields, fail_message):
"Returns the save() method for a Form."
"""Returns the save() method for a Form."""
def save(self, commit=True):
return save_instance(self, model(), fields, fail_message, commit)
return save
def make_instance_save(instance, fields, fail_message):
"Returns the save() method for a Form."
"""Returns the save() method for a Form."""
def save(self, commit=True):
return save_instance(self, instance, fields, fail_message, commit)
return save
def form_for_model(model, form=BaseForm, fields=None, formfield_callback=lambda f: f.formfield()):
def form_for_model(model, form=BaseForm, fields=None,
formfield_callback=lambda f: f.formfield()):
"""
Returns a Form class for the given Django model class.
@ -86,11 +90,13 @@ def form_for_model(model, form=BaseForm, fields=None, formfield_callback=lambda
formfield = formfield_callback(f)
if formfield:
field_list.append((f.name, formfield))
base_fields = SortedDictFromList(field_list)
base_fields = SortedDict(field_list)
return type(opts.object_name + 'Form', (form,),
{'base_fields': base_fields, '_model': model, 'save': make_model_save(model, fields, 'created')})
{'base_fields': base_fields, '_model': model,
'save': make_model_save(model, fields, 'created')})
def form_for_instance(instance, form=BaseForm, fields=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
def form_for_instance(instance, form=BaseForm, fields=None,
formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
"""
Returns a Form class for the given Django model instance.
@ -113,18 +119,24 @@ def form_for_instance(instance, form=BaseForm, fields=None, formfield_callback=l
formfield = formfield_callback(f, initial=current_value)
if formfield:
field_list.append((f.name, formfield))
base_fields = SortedDictFromList(field_list)
base_fields = SortedDict(field_list)
return type(opts.object_name + 'InstanceForm', (form,),
{'base_fields': base_fields, '_model': model, 'save': make_instance_save(instance, fields, 'changed')})
{'base_fields': base_fields, '_model': model,
'save': make_instance_save(instance, fields, 'changed')})
def form_for_fields(field_list):
"Returns a Form class for the given list of Django database field instances."
fields = SortedDictFromList([(f.name, f.formfield()) for f in field_list if f.editable])
"""
Returns a Form class for the given list of Django database field instances.
"""
fields = SortedDict([(f.name, f.formfield())
for f in field_list if f.editable])
return type('FormForFields', (BaseForm,), {'base_fields': fields})
class QuerySetIterator(object):
def __init__(self, queryset, empty_label, cache_choices):
self.queryset, self.empty_label, self.cache_choices = queryset, empty_label, cache_choices
self.queryset = queryset
self.empty_label = empty_label
self.cache_choices = cache_choices
def __iter__(self):
if self.empty_label is not None:
@ -136,19 +148,29 @@ class QuerySetIterator(object):
self.queryset._result_cache = None
class ModelChoiceField(ChoiceField):
"A ChoiceField whose choices are a model QuerySet."
"""A ChoiceField whose choices are a model QuerySet."""
# This class is a subclass of ChoiceField for purity, but it doesn't
# actually use any of ChoiceField's implementation.
def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
required=True, widget=Select, label=None, initial=None, help_text=None):
self.queryset = queryset
required=True, widget=Select, label=None, initial=None,
help_text=None):
self.empty_label = empty_label
self.cache_choices = cache_choices
# Call Field instead of ChoiceField __init__() because we don't need
# ChoiceField.__init__().
Field.__init__(self, required, widget, label, initial, help_text)
self.queryset = queryset
def _get_queryset(self):
return self._queryset
def _set_queryset(self, queryset):
self._queryset = queryset
self.widget.choices = self.choices
queryset = property(_get_queryset, _set_queryset)
def _get_choices(self):
# If self._choices is set, then somebody must have manually set
# the property self.choices. In this case, just return self._choices.
@ -160,7 +182,8 @@ class ModelChoiceField(ChoiceField):
# *each* time _get_choices() is called (and, thus, each time
# self.choices is accessed) so that we can ensure the QuerySet has not
# been consumed.
return QuerySetIterator(self.queryset, self.empty_label, self.cache_choices)
return QuerySetIterator(self.queryset, self.empty_label,
self.cache_choices)
def _set_choices(self, value):
# This method is copied from ChoiceField._set_choices(). It's necessary
@ -175,18 +198,22 @@ class ModelChoiceField(ChoiceField):
if value in ('', None):
return None
try:
value = self.queryset.model._default_manager.get(pk=value)
value = self.queryset.get(pk=value)
except self.queryset.model.DoesNotExist:
raise ValidationError(ugettext(u'Select a valid choice. That choice is not one of the available choices.'))
raise ValidationError(ugettext(u'Select a valid choice. That'
u' choice is not one of the'
u' available choices.'))
return value
class ModelMultipleChoiceField(ModelChoiceField):
"A MultipleChoiceField whose choices are a model QuerySet."
"""A MultipleChoiceField whose choices are a model QuerySet."""
hidden_widget = MultipleHiddenInput
def __init__(self, queryset, cache_choices=False, required=True,
widget=SelectMultiple, label=None, initial=None, help_text=None):
super(ModelMultipleChoiceField, self).__init__(queryset, None, cache_choices,
required, widget, label, initial, help_text)
widget=SelectMultiple, label=None, initial=None,
help_text=None):
super(ModelMultipleChoiceField, self).__init__(queryset, None,
cache_choices, required, widget, label, initial, help_text)
def clean(self, value):
if self.required and not value:
@ -198,9 +225,11 @@ class ModelMultipleChoiceField(ModelChoiceField):
final_values = []
for val in value:
try:
obj = self.queryset.model._default_manager.get(pk=val)
obj = self.queryset.get(pk=val)
except self.queryset.model.DoesNotExist:
raise ValidationError(ugettext(u'Select a valid choice. %s is not one of the available choices.') % val)
raise ValidationError(ugettext(u'Select a valid choice. %s is'
u' not one of the available'
u' choices.') % val)
else:
final_values.append(obj)
return final_values

View File

@ -1,6 +1,7 @@
from django.utils.html import escape
from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
from django.utils.functional import Promise
from django.utils.safestring import mark_safe
def flatatt(attrs):
"""
@ -22,7 +23,9 @@ class ErrorDict(dict, StrAndUnicode):
def as_ul(self):
if not self: return u''
return u'<ul class="errorlist">%s</ul>' % ''.join([u'<li>%s%s</li>' % (k, force_unicode(v)) for k, v in self.items()])
return mark_safe(u'<ul class="errorlist">%s</ul>'
% ''.join([u'<li>%s%s</li>' % (k, force_unicode(v))
for k, v in self.items()]))
def as_text(self):
return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u' * %s' % force_unicode(i) for i in v])) for k, v in self.items()])
@ -36,19 +39,25 @@ class ErrorList(list, StrAndUnicode):
def as_ul(self):
if not self: return u''
return u'<ul class="errorlist">%s</ul>' % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self])
return mark_safe(u'<ul class="errorlist">%s</ul>'
% ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
def as_text(self):
if not self: return u''
return u'\n'.join([u'* %s' % force_unicode(e) for e in self])
def __repr__(self):
return repr([force_unicode(e) for e in self])
class ValidationError(Exception):
def __init__(self, message):
"ValidationError can be passed a string or a list."
"""
ValidationError can be passed any object that can be printed (usually
a string) or a list of objects.
"""
if isinstance(message, list):
self.messages = ErrorList([smart_unicode(msg) for msg in message])
else:
assert isinstance(message, (basestring, Promise)), ("%s should be a basestring or lazy translation" % repr(message))
message = smart_unicode(message)
self.messages = ErrorList([message])
@ -58,4 +67,3 @@ class ValidationError(Exception):
# AttributeError: ValidationError instance has no attribute 'args'
# See http://www.python.org/doc/current/tut/node10.html#handling
return repr(self.messages)

View File

@ -14,6 +14,7 @@ from django.utils.datastructures import MultiValueDict
from django.utils.html import escape
from django.utils.translation import ugettext
from django.utils.encoding import StrAndUnicode, force_unicode
from django.utils.safestring import mark_safe
from util import flatatt
__all__ = (
@ -86,8 +87,10 @@ class Input(Widget):
def render(self, name, value, attrs=None):
if value is None: value = ''
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
if value != '': final_attrs['value'] = force_unicode(value) # Only add the 'value' attribute if a value is non-empty.
return u'<input%s />' % flatatt(final_attrs)
if value != '':
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_unicode(value)
return mark_safe(u'<input%s />' % flatatt(final_attrs))
class TextInput(Input):
input_type = 'text'
@ -120,7 +123,9 @@ class MultipleHiddenInput(HiddenInput):
def render(self, name, value, attrs=None, choices=()):
if value is None: value = []
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
return u'\n'.join([(u'<input%s />' % flatatt(dict(value=force_unicode(v), **final_attrs))) for v in value])
return mark_safe(u'\n'.join([(u'<input%s />' %
flatatt(dict(value=force_unicode(v), **final_attrs)))
for v in value]))
def value_from_datadict(self, data, files, name):
if isinstance(data, MultiValueDict):
@ -149,7 +154,8 @@ class Textarea(Widget):
if value is None: value = ''
value = force_unicode(value)
final_attrs = self.build_attrs(attrs, name=name)
return u'<textarea%s>%s</textarea>' % (flatatt(final_attrs), escape(value))
return mark_safe(u'<textarea%s>%s</textarea>' % (flatatt(final_attrs),
escape(value)))
class DateTimeInput(Input):
input_type = 'text'
@ -183,8 +189,9 @@ class CheckboxInput(Widget):
if result:
final_attrs['checked'] = 'checked'
if value not in ('', True, False, None):
final_attrs['value'] = force_unicode(value) # Only add the 'value' attribute if a value is non-empty.
return u'<input%s />' % flatatt(final_attrs)
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_unicode(value)
return mark_safe(u'<input%s />' % flatatt(final_attrs))
def value_from_datadict(self, data, files, name):
if name not in data:
@ -205,13 +212,14 @@ class Select(Widget):
if value is None: value = ''
final_attrs = self.build_attrs(attrs, name=name)
output = [u'<select%s>' % flatatt(final_attrs)]
str_value = force_unicode(value) # Normalize to string.
# Normalize to string.
str_value = force_unicode(value)
for option_value, option_label in chain(self.choices, choices):
option_value = force_unicode(option_value)
selected_html = (option_value == str_value) and u' selected="selected"' or ''
output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(force_unicode(option_label))))
output.append(u'</select>')
return u'\n'.join(output)
return mark_safe(u'\n'.join(output))
class NullBooleanSelect(Select):
"""
@ -248,7 +256,7 @@ class SelectMultiple(Widget):
selected_html = (option_value in str_values) and ' selected="selected"' or ''
output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(force_unicode(option_label))))
output.append(u'</select>')
return u'\n'.join(output)
return mark_safe(u'\n'.join(output))
def value_from_datadict(self, data, files, name):
if isinstance(data, MultiValueDict):
@ -269,7 +277,8 @@ class RadioInput(StrAndUnicode):
self.index = index
def __unicode__(self):
return u'<label>%s %s</label>' % (self.tag(), self.choice_label)
return mark_safe(u'<label>%s %s</label>' % (self.tag(),
self.choice_label))
def is_checked(self):
return self.value == self.choice_value
@ -280,7 +289,7 @@ class RadioInput(StrAndUnicode):
final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
if self.is_checked():
final_attrs['checked'] = 'checked'
return u'<input%s />' % flatatt(final_attrs)
return mark_safe(u'<input%s />' % flatatt(final_attrs))
class RadioFieldRenderer(StrAndUnicode):
"""
@ -304,7 +313,8 @@ class RadioFieldRenderer(StrAndUnicode):
def render(self):
"""Outputs a <ul> for this set of radio fields."""
return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % force_unicode(w) for w in self])
return mark_safe(u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>'
% force_unicode(w) for w in self]))
class RadioSelect(Select):
@ -341,7 +351,8 @@ class CheckboxSelectMultiple(SelectMultiple):
has_id = attrs and 'id' in attrs
final_attrs = self.build_attrs(attrs, name=name)
output = [u'<ul>']
str_values = set([force_unicode(v) for v in value]) # Normalize to strings.
# Normalize to strings
str_values = set([force_unicode(v) for v in value])
for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
# If an ID attribute was given, add a numeric index as a suffix,
# so that the checkboxes don't all have the same ID attribute.
@ -352,7 +363,7 @@ class CheckboxSelectMultiple(SelectMultiple):
rendered_cb = cb.render(name, option_value)
output.append(u'<li><label>%s %s</label></li>' % (rendered_cb, escape(force_unicode(option_label))))
output.append(u'</ul>')
return u'\n'.join(output)
return mark_safe(u'\n'.join(output))
def id_for_label(self, id_):
# See the comment for RadioSelect.id_for_label()
@ -450,3 +461,4 @@ class SplitDateTimeWidget(MultiWidget):
if value:
return [value.date(), value.time().replace(microsecond=0)]
return [None, None]

View File

@ -1,6 +1,7 @@
from django.core import validators
from django.core.exceptions import PermissionDenied
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.conf import settings
from django.utils.translation import ugettext, ungettext
from django.utils.encoding import smart_unicode, force_unicode
@ -189,9 +190,9 @@ class FormFieldWrapper(object):
def html_error_list(self):
if self.errors():
return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])
return mark_safe('<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()]))
else:
return ''
return mark_safe('')
def get_id(self):
return self.formfield.get_id()
@ -226,7 +227,7 @@ class FormFieldCollection(FormFieldWrapper):
return bool(len(self.errors()))
def html_combined_error_list(self):
return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])
return mark_safe(''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')]))
class InlineObjectCollection(object):
"An object that acts like a sparse list of form field collections."
@ -418,9 +419,9 @@ class TextField(FormField):
max_length = u''
if self.max_length:
max_length = u'maxlength="%s" ' % self.max_length
return u'<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
return mark_safe(u'<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
(self.input_type, self.get_id(), self.__class__.__name__, self.is_required and u' required' or '',
self.field_name, self.length, escape(data), max_length)
self.field_name, self.length, escape(data), max_length))
def html2python(data):
return data
@ -442,9 +443,9 @@ class LargeTextField(TextField):
def render(self, data):
if data is None:
data = ''
return u'<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
return mark_safe(u'<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
(self.get_id(), self.__class__.__name__, self.is_required and u' required' or u'',
self.field_name, self.rows, self.cols, escape(data))
self.field_name, self.rows, self.cols, escape(data)))
class HiddenField(FormField):
def __init__(self, field_name, is_required=False, validator_list=None, max_length=None):
@ -453,8 +454,8 @@ class HiddenField(FormField):
self.validator_list = validator_list[:]
def render(self, data):
return u'<input type="hidden" id="%s" name="%s" value="%s" />' % \
(self.get_id(), self.field_name, escape(data))
return mark_safe(u'<input type="hidden" id="%s" name="%s" value="%s" />' % \
(self.get_id(), self.field_name, escape(data)))
class CheckboxField(FormField):
def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False):
@ -468,9 +469,9 @@ class CheckboxField(FormField):
checked_html = ''
if data or (data is '' and self.checked_by_default):
checked_html = ' checked="checked"'
return u'<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
return mark_safe(u'<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
(self.get_id(), self.__class__.__name__,
self.field_name, checked_html)
self.field_name, checked_html))
def html2python(data):
"Convert value from browser ('on' or '') to a Python boolean"
@ -502,7 +503,7 @@ class SelectField(FormField):
selected_html = u' selected="selected"'
output.append(u' <option value="%s"%s>%s</option>' % (escape(value), selected_html, force_unicode(escape(display_name))))
output.append(u' </select>')
return u'\n'.join(output)
return mark_safe(u'\n'.join(output))
def isValidChoice(self, data, form):
str_data = smart_unicode(data)
@ -556,7 +557,7 @@ class RadioSelectField(FormField):
output = [u'<ul%s>' % (self.ul_class and u' class="%s"' % self.ul_class or u'')]
output.extend([u'<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist])
output.append(u'</ul>')
return u''.join(output)
return mark_safe(u''.join(output))
def __iter__(self):
for d in self.datalist:
yield d
@ -571,11 +572,11 @@ class RadioSelectField(FormField):
datalist.append({
'value': value,
'name': display_name,
'field': u'<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
(self.get_id() + u'_' + unicode(i), self.field_name, value, selected_html),
'label': u'<label for="%s">%s</label>' % \
'field': mark_safe(u'<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
(self.get_id() + u'_' + unicode(i), self.field_name, value, selected_html)),
'label': mark_safe(u'<label for="%s">%s</label>' % \
(self.get_id() + u'_' + unicode(i), display_name),
})
)})
return RadioFieldRenderer(datalist, self.ul_class)
def isValidChoice(self, data, form):
@ -614,7 +615,7 @@ class SelectMultipleField(SelectField):
selected_html = u' selected="selected"'
output.append(u' <option value="%s"%s>%s</option>' % (escape(value), selected_html, force_unicode(escape(choice))))
output.append(u' </select>')
return u'\n'.join(output)
return mark_safe(u'\n'.join(output))
def isValidChoice(self, field_data, all_data):
# data is something like ['1', '2', '3']
@ -667,7 +668,7 @@ class CheckboxSelectMultipleField(SelectMultipleField):
(self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html,
self.get_id() + escape(value), choice))
output.append(u'</ul>')
return u'\n'.join(output)
return mark_safe(u'\n'.join(output))
####################
# FILE UPLOADS #
@ -688,8 +689,8 @@ class FileUploadField(FormField):
raise validators.CriticalValidationError, ugettext("The submitted file is empty.")
def render(self, data):
return u'<input type="file" id="%s" class="v%s" name="%s" />' % \
(self.get_id(), self.__class__.__name__, self.field_name)
return mark_safe(u'<input type="file" id="%s" class="v%s" name="%s" />' % \
(self.get_id(), self.__class__.__name__, self.field_name))
def html2python(data):
if data is None:

View File

@ -57,6 +57,8 @@ from django.utils.functional import curry, Promise
from django.utils.text import smart_split
from django.utils.encoding import smart_unicode, force_unicode
from django.utils.translation import ugettext as _
from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
from django.utils.html import escape
__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
@ -595,7 +597,16 @@ class FilterExpression(object):
arg_vals.append(arg)
else:
arg_vals.append(arg.resolve(context))
obj = func(obj, *arg_vals)
if getattr(func, 'needs_autoescape', False):
new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
else:
new_obj = func(obj, *arg_vals)
if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
obj = mark_safe(new_obj)
elif isinstance(obj, EscapeData):
obj = mark_for_escaping(new_obj)
else:
obj = new_obj
return obj
def args_check(name, func, provided):
@ -758,13 +769,6 @@ class Variable(object):
else:
raise
if isinstance(current, (basestring, Promise)):
try:
current = force_unicode(current)
except UnicodeDecodeError:
# Failing to convert to unicode can happen sometimes (e.g. debug
# tracebacks). So we allow it in this particular instance.
pass
return current
class Node(object):
@ -838,16 +842,31 @@ class VariableNode(Node):
return "<Variable Node: %s>" % self.filter_expression
def render(self, context):
return self.filter_expression.resolve(context)
try:
output = force_unicode(self.filter_expression.resolve(context))
except UnicodeDecodeError:
# Unicode conversion can fail sometimes for reasons out of our
# control (e.g. exception rendering). In that case, we fail quietly.
return ''
if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
return force_unicode(escape(output))
else:
return force_unicode(output)
class DebugVariableNode(VariableNode):
def render(self, context):
try:
return self.filter_expression.resolve(context)
output = force_unicode(self.filter_expression.resolve(context))
except TemplateSyntaxError, e:
if not hasattr(e, 'source'):
e.source = self.source
raise
except UnicodeDecodeError:
return ''
if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
return escape(output)
else:
return output
def generic_tag_compiler(params, defaults, name, node_class, parser, token):
"Returns a template.Node subclass."
@ -961,7 +980,8 @@ class Library(object):
else:
t = get_template(file_name)
self.nodelist = t.nodelist
return self.nodelist.render(context_class(dict))
return self.nodelist.render(context_class(dict,
autoescape=context.autoescape))
compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
compile_func.__doc__ = func.__doc__

View File

@ -9,9 +9,11 @@ class ContextPopException(Exception):
class Context(object):
"A stack container for variable context"
def __init__(self, dict_=None):
def __init__(self, dict_=None, autoescape=True):
dict_ = dict_ or {}
self.dicts = [dict_]
self.autoescape = autoescape
def __repr__(self):
return repr(self.dicts)
@ -97,3 +99,4 @@ class RequestContext(Context):
processors = tuple(processors)
for processor in get_standard_processors() + processors:
self.update(processor(request))

View File

@ -1,11 +1,13 @@
"Default variable filters"
"""Default variable filters."""
import re
import random as random_module
from django.template import Variable, Library
from django.conf import settings
from django.utils.translation import ugettext, ungettext
from django.utils.encoding import force_unicode, smart_str, iri_to_uri
import re
import random as random_module
from django.utils.encoding import force_unicode, iri_to_uri
from django.utils.safestring import mark_safe, SafeData
register = Library()
@ -28,6 +30,9 @@ def stringfilter(func):
# Include a reference to the real function (used to check original
# arguments by the template parser).
_dec._decorated_function = getattr(func, '_decorated_function', func)
for attr in ('is_safe', 'needs_autoescape'):
if hasattr(func, attr):
setattr(_dec, attr, getattr(func, attr))
return _dec
###################
@ -36,39 +41,51 @@ def stringfilter(func):
def addslashes(value):
"Adds slashes - useful for passing strings to JavaScript, for example."
"""Adds slashes - useful for passing strings to JavaScript, for example."""
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
addslashes.is_safe = True
addslashes = stringfilter(addslashes)
def capfirst(value):
"Capitalizes the first character of the value"
"""Capitalizes the first character of the value."""
return value and value[0].upper() + value[1:]
capfirst.is_safe=True
capfirst = stringfilter(capfirst)
def fix_ampersands(value):
"Replaces ampersands with ``&amp;`` entities"
"""Replaces ampersands with ``&amp;`` entities."""
from django.utils.html import fix_ampersands
return fix_ampersands(value)
fix_ampersands.is_safe=True
fix_ampersands = stringfilter(fix_ampersands)
def floatformat(text, arg=-1):
"""
If called without an argument, displays a floating point
number as 34.2 -- but only if there's a point to be displayed.
With a positive numeric argument, it displays that many decimal places
always.
With a negative numeric argument, it will display that many decimal
places -- but only if there's places to be displayed.
Examples:
Displays a float to a specified number of decimal places.
If called without an argument, it displays the floating point number with
one decimal place -- but only if there's a decimal place to be displayed:
* num1 = 34.23234
* num2 = 34.00000
* num1|floatformat results in 34.2
* num2|floatformat is 34
* num1|floatformat:3 is 34.232
* num2|floatformat:3 is 34.000
* num1|floatformat:-3 is 34.232
* num2|floatformat:-3 is 34
* num3 = 34.26000
* {{ num1|floatformat }} displays "34.2"
* {{ num2|floatformat }} displays "34"
* {{ num3|floatformat }} displays "34.3"
If arg is positive, it will always display exactly arg number of decimal
places:
* {{ num1|floatformat:3 }} displays "34.232"
* {{ num2|floatformat:3 }} displays "34.000"
* {{ num3|floatformat:3 }} displays "34.260"
If arg is negative, it will display arg number of decimal places -- but
only if there are places to be displayed:
* {{ num1|floatformat:"-3" }} displays "34.232"
* {{ num2|floatformat:"-3" }} displays "34"
* {{ num3|floatformat:"-3" }} displays "34.260"
"""
try:
f = float(text)
@ -80,54 +97,68 @@ def floatformat(text, arg=-1):
return force_unicode(f)
m = f - int(f)
if not m and d < 0:
return u'%d' % int(f)
return mark_safe(u'%d' % int(f))
else:
formatstr = u'%%.%df' % abs(d)
return formatstr % f
return mark_safe(formatstr % f)
floatformat.is_safe = True
def iriencode(value):
"Escapes an IRI value for use in a URL"
"""Escapes an IRI value for use in a URL."""
return force_unicode(iri_to_uri(value))
iriencode = stringfilter(iriencode)
def linenumbers(value):
"Displays text with line numbers"
def linenumbers(value, autoescape=None):
"""Displays text with line numbers."""
from django.utils.html import escape
lines = value.split(u'\n')
# Find the maximum width of the line count, for use with zero padding string format command
# Find the maximum width of the line count, for use with zero padding
# string format command
width = unicode(len(unicode(len(lines))))
for i, line in enumerate(lines):
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line))
return u'\n'.join(lines)
if not autoescape or isinstance(value, SafeData):
for i, line in enumerate(lines):
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, line)
else:
for i, line in enumerate(lines):
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line))
return mark_safe(u'\n'.join(lines))
linenumbers.is_safe = True
linenumbers.needs_autoescape = True
linenumbers = stringfilter(linenumbers)
def lower(value):
"Converts a string into all lowercase"
"""Converts a string into all lowercase."""
return value.lower()
lower.is_safe = True
lower = stringfilter(lower)
def make_list(value):
"""
Returns the value turned into a list. For an integer, it's a list of
digits. For a string, it's a list of characters.
Returns the value turned into a list.
For an integer, it's a list of digits.
For a string, it's a list of characters.
"""
return list(value)
make_list.is_safe = False
make_list = stringfilter(make_list)
def slugify(value):
"""
Normalizes string, converts to lowercase, removes non-alpha chars and
converts spaces to hyphens.
Normalizes string, converts to lowercase, removes non-alpha characters,
and converts spaces to hyphens.
"""
import unicodedata
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
return re.sub('[-\s]+', '-', value)
return mark_safe(re.sub('[-\s]+', '-', value))
slugify.is_safe = True
slugify = stringfilter(slugify)
def stringformat(value, arg):
"""
Formats the variable according to the argument, a string formatting specifier.
Formats the variable according to the arg, a string formatting specifier.
This specifier uses Python string formating syntax, with the exception that
the leading "%" is dropped.
@ -138,31 +169,34 @@ def stringformat(value, arg):
return (u"%" + unicode(arg)) % value
except (ValueError, TypeError):
return u""
stringformat.is_safe = True
def title(value):
"Converts a string into titlecase"
"""Converts a string into titlecase."""
return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
title.is_safe = True
title = stringfilter(title)
def truncatewords(value, arg):
"""
Truncates a string after a certain number of words
Truncates a string after a certain number of words.
Argument: Number of words to truncate after
Argument: Number of words to truncate after.
"""
from django.utils.text import truncate_words
try:
length = int(arg)
except ValueError: # invalid literal for int()
except ValueError: # Invalid literal for int().
return value # Fail silently.
return truncate_words(value, length)
truncatewords.is_safe = True
truncatewords = stringfilter(truncatewords)
def truncatewords_html(value, arg):
"""
Truncates HTML after a certain number of words
Truncates HTML after a certain number of words.
Argument: Number of words to truncate after
Argument: Number of words to truncate after.
"""
from django.utils.text import truncate_html_words
try:
@ -170,77 +204,94 @@ def truncatewords_html(value, arg):
except ValueError: # invalid literal for int()
return value # Fail silently.
return truncate_html_words(value, length)
truncatewords_html.is_safe = True
truncatewords_html = stringfilter(truncatewords_html)
def upper(value):
"Converts a string into all uppercase"
"""Converts a string into all uppercase."""
return value.upper()
upper.is_safe = False
upper = stringfilter(upper)
def urlencode(value):
"Escapes a value for use in a URL"
"""Escapes a value for use in a URL."""
from django.utils.http import urlquote
return urlquote(value)
urlencode.is_safe = False
urlencode = stringfilter(urlencode)
def urlize(value):
"Converts URLs in plain text into clickable links"
def urlize(value, autoescape=None):
"""Converts URLs in plain text into clickable links."""
from django.utils.html import urlize
return urlize(value, nofollow=True)
return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))
urlize.is_safe=True
urlize.needs_autoescape = True
urlize = stringfilter(urlize)
def urlizetrunc(value, limit):
"""
Converts URLs into clickable links, truncating URLs to the given character limit,
and adding 'rel=nofollow' attribute to discourage spamming.
Converts URLs into clickable links, truncating URLs to the given character
limit, and adding 'rel=nofollow' attribute to discourage spamming.
Argument: Length to truncate URLs to.
"""
from django.utils.html import urlize
return urlize(value, trim_url_limit=int(limit), nofollow=True)
return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True))
urlizetrunc.is_safe = True
urlizetrunc = stringfilter(urlizetrunc)
def wordcount(value):
"Returns the number of words"
"""Returns the number of words."""
return len(value.split())
wordcount.is_safe = False
wordcount = stringfilter(wordcount)
def wordwrap(value, arg):
"""
Wraps words at specified line length
Wraps words at specified line length.
Argument: number of characters to wrap the text at.
"""
from django.utils.text import wrap
return wrap(value, int(arg))
wordwrap.is_safe = True
wordwrap = stringfilter(wordwrap)
def ljust(value, arg):
"""
Left-aligns the value in a field of a given width
Left-aligns the value in a field of a given width.
Argument: field size
Argument: field size.
"""
return value.ljust(int(arg))
ljust.is_safe = True
ljust = stringfilter(ljust)
def rjust(value, arg):
"""
Right-aligns the value in a field of a given width
Right-aligns the value in a field of a given width.
Argument: field size
Argument: field size.
"""
return value.rjust(int(arg))
rjust.is_safe = True
rjust = stringfilter(rjust)
def center(value, arg):
"Centers the value in a field of a given width"
"""Centers the value in a field of a given width."""
return value.center(int(arg))
center.is_safe = True
center = stringfilter(center)
def cut(value, arg):
"Removes all values of arg from the given string"
return value.replace(arg, u'')
"""
Removes all values of arg from the given string.
"""
safe = isinstance(value, SafeData)
value = value.replace(arg, u'')
if safe and arg != ';':
return mark_safe(value)
return value
cut = stringfilter(cut)
###################
@ -248,31 +299,62 @@ cut = stringfilter(cut)
###################
def escape(value):
"Escapes a string's HTML"
from django.utils.html import escape
return escape(value)
"""
Marks the value as a string that should not be auto-escaped.
"""
from django.utils.safestring import mark_for_escaping
return mark_for_escaping(value)
escape.is_safe = True
escape = stringfilter(escape)
def linebreaks(value):
def force_escape(value):
"""
Escapes a string's HTML. This returns a new string containing the escaped
characters (as opposed to "escape", which marks the content for later
possible escaping).
"""
from django.utils.html import escape
return mark_safe(escape(value))
escape = stringfilter(escape)
force_escape.is_safe = True
def linebreaks(value, autoescape=None):
"""
Replaces line breaks in plain text with appropriate HTML; a single
newline becomes an HTML line break (``<br />``) and a new line
followed by a blank line becomes a paragraph break (``</p>``).
"""
from django.utils.html import linebreaks
return linebreaks(value)
autoescape = autoescape and not isinstance(value, SafeData)
return mark_safe(linebreaks(value, autoescape))
linebreaks.is_safe = True
linebreaks.needs_autoescape = True
linebreaks = stringfilter(linebreaks)
def linebreaksbr(value):
def linebreaksbr(value, autoescape=None):
"""
Converts all newlines in a piece of plain text to HTML line breaks
(``<br />``).
"""
return value.replace('\n', '<br />')
if autoescape and not isinstance(value, SafeData):
from django.utils.html import escape
value = escape(value)
return mark_safe(value.replace('\n', '<br />'))
linebreaksbr.is_safe = True
linebreaksbr.needs_autoescape = True
linebreaksbr = stringfilter(linebreaksbr)
def safe(value):
"""
Marks the value as a string that should not be auto-escaped.
"""
from django.utils.safestring import mark_safe
return mark_safe(value)
safe.is_safe = True
safe = stringfilter(safe)
def removetags(value, tags):
"Removes a space separated list of [X]HTML tags from the output"
"""Removes a space separated list of [X]HTML tags from the output."""
tags = [re.escape(tag) for tag in tags.split()]
tags_re = u'(%s)' % u'|'.join(tags)
starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
@ -280,12 +362,14 @@ def removetags(value, tags):
value = starttag_re.sub(u'', value)
value = endtag_re.sub(u'', value)
return value
removetags.is_safe = True
removetags = stringfilter(removetags)
def striptags(value):
"Strips all [X]HTML tags"
"""Strips all [X]HTML tags."""
from django.utils.html import strip_tags
return strip_tags(value)
striptags.is_safe = True
striptags = stringfilter(striptags)
###################
@ -301,6 +385,7 @@ def dictsort(value, arg):
decorated = [(var_resolve(item), item) for item in value]
decorated.sort()
return [item[1] for item in decorated]
dictsort.is_safe = False
def dictsortreversed(value, arg):
"""
@ -312,32 +397,44 @@ def dictsortreversed(value, arg):
decorated.sort()
decorated.reverse()
return [item[1] for item in decorated]
dictsortreversed.is_safe = False
def first(value):
"Returns the first item in a list"
"""Returns the first item in a list."""
try:
return value[0]
except IndexError:
return u''
first.is_safe = True
def join(value, arg):
"Joins a list with a string, like Python's ``str.join(list)``"
"""Joins a list with a string, like Python's ``str.join(list)``."""
try:
return arg.join(map(force_unicode, value))
data = arg.join(map(force_unicode, value))
except AttributeError: # fail silently but nicely
return value
safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData),
value, True)
if safe_args:
return mark_safe(data)
else:
return data
join.is_safe = True
def length(value):
"Returns the length of the value - useful for lists"
"""Returns the length of the value - useful for lists."""
return len(value)
length.is_safe = True
def length_is(value, arg):
"Returns a boolean of whether the value's length is the argument"
"""Returns a boolean of whether the value's length is the argument."""
return len(value) == int(arg)
length_is.is_safe = True
def random(value):
"Returns a random item from the list"
"""Returns a random item from the list."""
return random_module.choice(value)
random.is_safe = True
def slice_(value, arg):
"""
@ -358,8 +455,9 @@ def slice_(value, arg):
except (ValueError, TypeError):
return value # Fail silently.
slice_.is_safe = True
def unordered_list(value):
def unordered_list(value, autoescape=None):
"""
Recursively takes a self-nested list and returns an HTML unordered list --
WITHOUT opening and closing <ul> tags.
@ -380,6 +478,11 @@ def unordered_list(value):
</ul>
</li>
"""
if autoescape:
from django.utils.html import conditional_escape
escaper = conditional_escape
else:
escaper = lambda x: x
def convert_old_style_list(list_):
"""
Converts old style lists to the new easier to understand format.
@ -429,20 +532,23 @@ def unordered_list(value):
sublist = _helper(sublist_item, tabs+1)
sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
indent, indent)
output.append('%s<li>%s%s</li>' % (indent, force_unicode(title),
sublist))
output.append('%s<li>%s%s</li>' % (indent,
escaper(force_unicode(title)), sublist))
i += 1
return '\n'.join(output)
value, converted = convert_old_style_list(value)
return _helper(value)
return mark_safe(_helper(value))
unordered_list.is_safe = True
unordered_list.needs_autoescape = True
###################
# INTEGERS #
###################
def add(value, arg):
"Adds the arg to the value"
"""Adds the arg to the value."""
return int(value) + int(arg)
add.is_safe = False
def get_digit(value, arg):
"""
@ -462,40 +568,44 @@ def get_digit(value, arg):
return int(str(value)[-arg])
except IndexError:
return 0
get_digit.is_safe = False
###################
# DATES #
###################
def date(value, arg=None):
"Formats a date according to the given format"
"""Formats a date according to the given format."""
from django.utils.dateformat import format
if not value:
return u''
if arg is None:
arg = settings.DATE_FORMAT
return format(value, arg)
date.is_safe = False
def time(value, arg=None):
"Formats a time according to the given format"
"""Formats a time according to the given format."""
from django.utils.dateformat import time_format
if value in (None, u''):
return u''
if arg is None:
arg = settings.TIME_FORMAT
return time_format(value, arg)
time.is_safe = False
def timesince(value, arg=None):
'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
"""Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
from django.utils.timesince import timesince
if not value:
return u''
if arg:
return timesince(arg, value)
return timesince(value)
timesince.is_safe = False
def timeuntil(value, arg=None):
'Formats a date as the time until that date (i.e. "4 days, 6 hours")'
"""Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
from django.utils.timesince import timesince
from datetime import datetime
if not value:
@ -503,24 +613,28 @@ def timeuntil(value, arg=None):
if arg:
return timesince(arg, value)
return timesince(datetime.now(), value)
timeuntil.is_safe = False
###################
# LOGIC #
###################
def default(value, arg):
"If value is unavailable, use given default"
"""If value is unavailable, use given default."""
return value or arg
default.is_safe = False
def default_if_none(value, arg):
"If value is None, use given default"
"""If value is None, use given default."""
if value is None:
return arg
return value
default_if_none.is_safe = False
def divisibleby(value, arg):
"Returns true if the value is devisible by the argument"
"""Returns True if the value is devisible by the argument."""
return int(value) % int(arg) == 0
divisibleby.is_safe = False
def yesno(value, arg=None):
"""
@ -544,13 +658,15 @@ def yesno(value, arg=None):
return value # Invalid arg.
try:
yes, no, maybe = bits
except ValueError: # unpack list of wrong size (no "maybe" value provided)
except ValueError:
# Unpack list of wrong size (no "maybe" value provided).
yes, no, maybe = bits[0], bits[1], bits[1]
if value is None:
return maybe
if value:
return yes
return no
yesno.is_safe = False
###################
# MISC #
@ -558,8 +674,8 @@ def yesno(value, arg=None):
def filesizeformat(bytes):
"""
Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
bytes, etc).
Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
102 bytes, etc).
"""
try:
bytes = float(bytes)
@ -573,13 +689,30 @@ def filesizeformat(bytes):
if bytes < 1024 * 1024 * 1024:
return ugettext("%.1f MB") % (bytes / (1024 * 1024))
return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
filesizeformat.is_safe = True
def pluralize(value, arg=u's'):
"""
Returns a plural suffix if the value is not 1, for '1 vote' vs. '2 votes'
By default, 's' is used as a suffix; if an argument is provided, that string
is used instead. If the provided argument contains a comma, the text before
the comma is used for the singular case.
Returns a plural suffix if the value is not 1. By default, 's' is used as
the suffix:
* If value is 0, vote{{ value|pluralize }} displays "0 votes".
* If value is 1, vote{{ value|pluralize }} displays "1 vote".
* If value is 2, vote{{ value|pluralize }} displays "2 votes".
If an argument is provided, that string is used instead:
* If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
* If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
* If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
If the provided argument contains a comma, the text before the comma is
used for the singular case and the text after the comma is used for the
plural case:
* If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
* If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
* If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
"""
if not u',' in arg:
arg = u',' + arg
@ -591,28 +724,31 @@ def pluralize(value, arg=u's'):
try:
if int(value) != 1:
return plural_suffix
except ValueError: # invalid string that's not a number
except ValueError: # Invalid string that's not a number.
pass
except TypeError: # value isn't a string or a number; maybe it's a list?
except TypeError: # Value isn't a string or a number; maybe it's a list?
try:
if len(value) != 1:
return plural_suffix
except TypeError: # len() of unsized object
except TypeError: # len() of unsized object.
pass
return singular_suffix
pluralize.is_safe = False
def phone2numeric(value):
"Takes a phone number and converts it in to its numerical equivalent"
"""Takes a phone number and converts it in to its numerical equivalent."""
from django.utils.text import phone2numeric
return phone2numeric(value)
phone2numeric.is_safe = True
def pprint(value):
"A wrapper around pprint.pprint -- for debugging, really"
"""A wrapper around pprint.pprint -- for debugging, really."""
from pprint import pformat
try:
return pformat(value)
except Exception, e:
return u"Error in formatting: %s" % force_unicode(e, errors="replace")
pprint.is_safe = True
# Syntax: register.filter(name of filter, callback)
register.filter(add)
@ -631,6 +767,7 @@ register.filter(filesizeformat)
register.filter(first)
register.filter(fix_ampersands)
register.filter(floatformat)
register.filter(force_escape)
register.filter(get_digit)
register.filter(iriencode)
register.filter(join)
@ -648,6 +785,7 @@ register.filter(pprint)
register.filter(removetags)
register.filter(random)
register.filter(rjust)
register.filter(safe)
register.filter('slice', slice_)
register.filter(slugify)
register.filter(stringformat)

View File

@ -1,4 +1,12 @@
"Default tags used by the template system, available to all templates."
"""Default tags used by the template system, available to all templates."""
import sys
import re
from itertools import cycle as itertools_cycle
try:
reversed
except NameError:
from django.utils.itercompat import reversed # Python 2.3 fallback
from django.template import Node, NodeList, Template, Context, Variable
from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
@ -6,30 +14,36 @@ from django.template import get_library, Library, InvalidTemplateLibrary
from django.conf import settings
from django.utils.encoding import smart_str, smart_unicode
from django.utils.itercompat import groupby
import sys
import re
try:
reversed
except NameError:
from django.utils.itercompat import reversed # Python 2.3 fallback
from django.utils.safestring import mark_safe
register = Library()
class AutoEscapeControlNode(Node):
"""Implements the actions of the autoescape tag."""
def __init__(self, setting, nodelist):
self.setting, self.nodelist = setting, nodelist
def render(self, context):
old_setting = context.autoescape
context.autoescape = self.setting
output = self.nodelist.render(context)
context.autoescape = old_setting
if self.setting:
return mark_safe(output)
else:
return output
class CommentNode(Node):
def render(self, context):
return ''
class CycleNode(Node):
def __init__(self, cyclevars, variable_name=None):
self.cyclevars = cyclevars
self.cyclevars_len = len(cyclevars)
self.counter = -1
self.cycle_iter = itertools_cycle(cyclevars)
self.variable_name = variable_name
def render(self, context):
self.counter += 1
value = self.cyclevars[self.counter % self.cyclevars_len]
value = self.cycle_iter.next()
value = Variable(value).resolve(context)
if self.variable_name:
context[self.variable_name] = value
@ -49,7 +63,7 @@ class FilterNode(Node):
def render(self, context):
output = self.nodelist.render(context)
# apply filters
# Apply filters.
context.update({'var': output})
filtered = self.filter_expr.resolve(context)
context.pop()
@ -81,7 +95,8 @@ class ForNode(Node):
else:
reversed = ''
return "<For Node: for %s in %s, tail_len: %d%s>" % \
(', '.join( self.loopvars ), self.sequence, len(self.nodelist_loop), reversed)
(', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
reversed)
def __iter__(self):
for node in self.nodelist_loop:
@ -115,19 +130,20 @@ class ForNode(Node):
unpack = len(self.loopvars) > 1
for i, item in enumerate(values):
context['forloop'] = {
# shortcuts for current loop iteration number
# Shortcuts for current loop iteration number.
'counter0': i,
'counter': i+1,
# reverse counter iteration numbers
# Reverse counter iteration numbers.
'revcounter': len_values - i,
'revcounter0': len_values - i - 1,
# boolean values designating first and last times through loop
# Boolean values designating first and last times through loop.
'first': (i == 0),
'last': (i == len_values - 1),
'parentloop': parentloop,
}
if unpack:
# If there are multiple loop variables, unpack the item into them.
# If there are multiple loop variables, unpack the item into
# them.
context.update(dict(zip(self.loopvars, item)))
else:
context[self.loopvars[0]] = item
@ -154,8 +170,8 @@ class IfChangedNode(Node):
self._last_seen = None
try:
if self._varlist:
# Consider multiple parameters.
# This automatically behaves like a OR evaluation of the multiple variables.
# Consider multiple parameters. This automatically behaves
# like an OR evaluation of the multiple variables.
compare_to = [var.resolve(context) for var in self._varlist]
else:
compare_to = self.nodelist.render(context)
@ -249,13 +265,17 @@ class RegroupNode(Node):
def render(self, context):
obj_list = self.target.resolve(context, True)
if obj_list == None: # target_var wasn't found in context; fail silently
if obj_list == None:
# target variable wasn't found in context; fail silently.
context[self.var_name] = []
return ''
# List of dictionaries in the format
# List of dictionaries in the format:
# {'grouper': 'key', 'list': [list of contents]}.
context[self.var_name] = [{'grouper':key, 'list':list(val)} for key, val in
groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))]
context[self.var_name] = [
{'grouper': key, 'list': list(val)}
for key, val in
groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
]
return ''
def include_is_allowed(filepath):
@ -339,13 +359,15 @@ class URLNode(Node):
def render(self, context):
from django.core.urlresolvers import reverse, NoReverseMatch
args = [arg.resolve(context) for arg in self.args]
kwargs = dict([(smart_str(k,'ascii'), v.resolve(context)) for k, v in self.kwargs.items()])
kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
for k, v in self.kwargs.items()])
try:
return reverse(self.view_name, args=args, kwargs=kwargs)
except NoReverseMatch:
try:
project_name = settings.SETTINGS_MODULE.split('.')[0]
return reverse(project_name + '.' + self.view_name, args=args, kwargs=kwargs)
return reverse(project_name + '.' + self.view_name,
args=args, kwargs=kwargs)
except NoReverseMatch:
return ''
@ -386,10 +408,26 @@ class WithNode(Node):
context.pop()
return output
#@register.tag
def autoescape(parser, token):
"""
Force autoescape behaviour for this block.
"""
args = token.contents.split()
if len(args) != 2:
raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.")
arg = args[1]
if arg not in (u'on', u'off'):
raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
nodelist = parser.parse(('endautoescape',))
parser.delete_first_token()
return AutoEscapeControlNode((arg == 'on'), nodelist)
autoescape = register.tag(autoescape)
#@register.tag
def comment(parser, token):
"""
Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
"""
parser.skip_past('endcomment')
return CommentNode()
@ -398,7 +436,7 @@ comment = register.tag(comment)
#@register.tag
def cycle(parser, token):
"""
Cycle among the given strings each time this tag is encountered
Cycles among the given strings each time this tag is encountered.
Within a loop, cycles among the given strings each time through
the loop::
@ -421,10 +459,10 @@ def cycle(parser, token):
interpreted as literal strings.
"""
# Note: This returns the exact same node on each {% cycle name %} call; that
# is, the node object returned from {% cycle a b c as name %} and the one
# returned from {% cycle name %} are the exact same object. This shouldn't
# cause problems (heh), but if it does, now you know.
# Note: This returns the exact same node on each {% cycle name %} call;
# that is, the node object returned from {% cycle a b c as name %} and the
# one returned from {% cycle name %} are the exact same object. This
# shouldn't cause problems (heh), but if it does, now you know.
#
# Ugly hack warning: this stuffs the named template dict into parser so
# that names are only unique within each template (as opposed to using
@ -442,10 +480,11 @@ def cycle(parser, token):
args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
if len(args) == 2:
# {% cycle foo %} case
# {% cycle foo %} case.
name = args[1]
if not hasattr(parser, '_namedCycleNodes'):
raise TemplateSyntaxError("No named cycles in template: '%s' is not defined" % name)
raise TemplateSyntaxError("No named cycles in template."
" '%s' is not defined" % name)
if not name in parser._namedCycleNodes:
raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
return parser._namedCycleNodes[name]
@ -463,7 +502,8 @@ cycle = register.tag(cycle)
def debug(parser, token):
"""
Output a whole load of debugging information, including the current context and imported modules.
Outputs a whole load of debugging information, including the current
context and imported modules.
Sample usage::
@ -477,19 +517,22 @@ debug = register.tag(debug)
#@register.tag(name="filter")
def do_filter(parser, token):
"""
Filter the contents of the blog through variable filters.
Filters the contents of the blog through variable filters.
Filters can also be piped through each other, and they can have
arguments -- just like in variable syntax.
Sample usage::
{% filter escape|lower %}
{% filter force_escape|lower %}
This text will be HTML-escaped, and will appear in lowercase.
{% endfilter %}
"""
_, rest = token.contents.split(None, 1)
filter_expr = parser.compile_filter("var|%s" % (rest))
for func, unused in filter_expr.filters:
if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__)
nodelist = parser.parse(('endfilter',))
parser.delete_first_token()
return FilterNode(filter_expr, nodelist)
@ -526,14 +569,15 @@ def firstof(parser, token):
"""
bits = token.split_contents()[1:]
if len(bits) < 1:
raise TemplateSyntaxError, "'firstof' statement requires at least one argument"
raise TemplateSyntaxError("'firstof' statement requires at least one"
" argument")
return FirstOfNode(bits)
firstof = register.tag(firstof)
#@register.tag(name="for")
def do_for(parser, token):
"""
Loop over each item in an array.
Loops over each item in an array.
For example, to display a list of athletes given ``athlete_list``::
@ -572,17 +616,20 @@ def do_for(parser, token):
"""
bits = token.contents.split()
if len(bits) < 4:
raise TemplateSyntaxError, "'for' statements should have at least four words: %s" % token.contents
raise TemplateSyntaxError("'for' statements should have at least four"
" words: %s" % token.contents)
reversed = bits[-1] == 'reversed'
in_index = reversed and -3 or -2
if bits[in_index] != 'in':
raise TemplateSyntaxError, "'for' statements should use the format 'for x in y': %s" % token.contents
raise TemplateSyntaxError("'for' statements should use the format"
" 'for x in y': %s" % token.contents)
loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
for var in loopvars:
if not var or ' ' in var:
raise TemplateSyntaxError, "'for' tag received an invalid argument: %s" % token.contents
raise TemplateSyntaxError("'for' tag received an invalid argument:"
" %s" % token.contents)
sequence = parser.compile_filter(bits[in_index+1])
nodelist_loop = parser.parse(('endfor',))
@ -607,7 +654,7 @@ def do_ifequal(parser, token, negate):
#@register.tag
def ifequal(parser, token):
"""
Output the contents of the block if the two arguments equal each other.
Outputs the contents of the block if the two arguments equal each other.
Examples::
@ -626,7 +673,10 @@ ifequal = register.tag(ifequal)
#@register.tag
def ifnotequal(parser, token):
"""Output the contents of the block if the two arguments are not equal. See ifequal."""
"""
Outputs the contents of the block if the two arguments are not equal.
See ifequal.
"""
return do_ifequal(parser, token, True)
ifnotequal = register.tag(ifnotequal)
@ -635,9 +685,7 @@ def do_if(parser, token):
"""
The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
(i.e. exists, is not empty, and is not a false boolean value) the contents
of the block are output:
::
of the block are output::
{% if athlete_list %}
Number of athletes: {{ athlete_list|count }}
@ -648,8 +696,8 @@ def do_if(parser, token):
In the above, if ``athlete_list`` is not empty, the number of athletes will
be displayed by the ``{{ athlete_list|count }}`` variable.
As you can see, the ``if`` tag can take an option ``{% else %}`` clause that
will be displayed if the test fails.
As you can see, the ``if`` tag can take an option ``{% else %}`` clause
that will be displayed if the test fails.
``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
variables or to negate a given variable::
@ -674,9 +722,9 @@ def do_if(parser, token):
There are some athletes and absolutely no coaches.
{% endif %}
``if`` tags do not allow ``and`` and ``or`` clauses with the same
tag, because the order of logic would be ambigous. For example,
this is invalid::
``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
because the order of logic would be ambigous. For example, this is
invalid::
{% if athlete_list and coach_list or cheerleader_list %}
@ -692,8 +740,8 @@ def do_if(parser, token):
bits = token.contents.split()
del bits[0]
if not bits:
raise TemplateSyntaxError, "'if' statement requires at least one argument"
# bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
raise TemplateSyntaxError("'if' statement requires at least one argument")
# Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
bitstr = ' '.join(bits)
boolpairs = bitstr.split(' and ')
boolvars = []
@ -728,13 +776,13 @@ do_if = register.tag("if", do_if)
#@register.tag
def ifchanged(parser, token):
"""
Check if a value has changed from the last iteration of a loop.
Checks if a value has changed from the last iteration of a loop.
The 'ifchanged' block tag is used within a loop. It has two possible uses.
1. Checks its own rendered contents against its previous state and only
displays the content if it has changed. For example, this displays a list of
days, only displaying the month if it changes::
displays the content if it has changed. For example, this displays a
list of days, only displaying the month if it changes::
<h1>Archive for {{ year }}</h1>
@ -743,9 +791,9 @@ def ifchanged(parser, token):
<a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
{% endfor %}
2. If given a variable, check whether that variable has changed. For example, the
following shows the date every time it changes, but only shows the hour if both
the hour and the date have changed::
2. If given a variable, check whether that variable has changed.
For example, the following shows the date every time it changes, but
only shows the hour if both the hour and the date have changed::
{% for date in days %}
{% ifchanged date.date %} {{ date.date }} {% endifchanged %}
@ -763,7 +811,7 @@ ifchanged = register.tag(ifchanged)
#@register.tag
def ssi(parser, token):
"""
Output the contents of a given file into the page.
Outputs the contents of a given file into the page.
Like a simple "include" tag, the ``ssi`` tag includes the contents
of another file -- which must be specified using an absolute page --
@ -779,21 +827,24 @@ def ssi(parser, token):
bits = token.contents.split()
parsed = False
if len(bits) not in (2, 3):
raise TemplateSyntaxError, "'ssi' tag takes one argument: the path to the file to be included"
raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
" the file to be included")
if len(bits) == 3:
if bits[2] == 'parsed':
parsed = True
else:
raise TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0]
raise TemplateSyntaxError("Second (optional) argument to %s tag"
" must be 'parsed'" % bits[0])
return SsiNode(bits[1], parsed)
ssi = register.tag(ssi)
#@register.tag
def load(parser, token):
"""
Load a custom template tag set.
Loads a custom template tag set.
For example, to load the template tags in ``django/templatetags/news/photos.py``::
For example, to load the template tags in
``django/templatetags/news/photos.py``::
{% load news.photos %}
"""
@ -804,14 +855,15 @@ def load(parser, token):
lib = get_library("django.templatetags.%s" % taglib)
parser.add_library(lib)
except InvalidTemplateLibrary, e:
raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e)
raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
(taglib, e))
return LoadNode()
load = register.tag(load)
#@register.tag
def now(parser, token):
"""
Display the date, formatted according to the given string.
Displays the date, formatted according to the given string.
Uses the same format as PHP's ``date()`` function; see http://php.net/date
for all the possible values.
@ -830,7 +882,7 @@ now = register.tag(now)
#@register.tag
def regroup(parser, token):
"""
Regroup a list of alike objects by a common attribute.
Regroups a list of alike objects by a common attribute.
This complex tag is best illustrated by use of an example: say that
``people`` is a list of ``Person`` objects that have ``first_name``,
@ -868,8 +920,8 @@ def regroup(parser, token):
Note that `{% regroup %}`` does not work when the list to be grouped is not
sorted by the key you are grouping by! This means that if your list of
people was not sorted by gender, you'd need to make sure it is sorted before
using it, i.e.::
people was not sorted by gender, you'd need to make sure it is sorted
before using it, i.e.::
{% regroup people|dictsort:"gender" by gender as grouped %}
@ -879,10 +931,11 @@ def regroup(parser, token):
raise TemplateSyntaxError, "'regroup' tag takes five arguments"
target = parser.compile_filter(firstbits[1])
if firstbits[2] != 'by':
raise TemplateSyntaxError, "second argument to 'regroup' tag must be 'by'"
raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
lastbits_reversed = firstbits[3][::-1].split(None, 2)
if lastbits_reversed[1][::-1] != 'as':
raise TemplateSyntaxError, "next-to-last argument to 'regroup' tag must be 'as'"
raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
" be 'as'")
expression = parser.compile_filter(lastbits_reversed[2][::-1])
@ -892,8 +945,7 @@ regroup = register.tag(regroup)
def spaceless(parser, token):
"""
Removes whitespace between HTML tags. This includes tab
characters and newlines.
Removes whitespace between HTML tags, including tab and newline characters.
Example usage::
@ -907,8 +959,8 @@ def spaceless(parser, token):
<p><a href="foo/">Foo</a></p>
Only space between *tags* is normalized -- not space between tags and text. In
this example, the space around ``Hello`` won't be stripped::
Only space between *tags* is normalized -- not space between tags and text.
In this example, the space around ``Hello`` won't be stripped::
{% spaceless %}
<strong>
@ -924,7 +976,7 @@ spaceless = register.tag(spaceless)
#@register.tag
def templatetag(parser, token):
"""
Output one of the bits used to compose template tags.
Outputs one of the bits used to compose template tags.
Since the template system has no concept of "escaping", to display one of
the bits used in template tags, you must use the ``{% templatetag %}`` tag.
@ -949,8 +1001,9 @@ def templatetag(parser, token):
raise TemplateSyntaxError, "'templatetag' statement takes one argument"
tag = bits[1]
if tag not in TemplateTagNode.mapping:
raise TemplateSyntaxError, "Invalid templatetag argument: '%s'. Must be one of: %s" % \
(tag, TemplateTagNode.mapping.keys())
raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
" Must be one of: %s" %
(tag, TemplateTagNode.mapping.keys()))
return TemplateTagNode(tag)
templatetag = register.tag(templatetag)
@ -958,7 +1011,8 @@ def url(parser, token):
"""
Returns an absolute URL matching given view with its parameters.
This is a way to define links that aren't tied to a particular URL configuration::
This is a way to define links that aren't tied to a particular URL
configuration::
{% url path.to.some_view arg1,arg2,name1=value1 %}
@ -986,7 +1040,8 @@ def url(parser, token):
"""
bits = token.contents.split(' ', 2)
if len(bits) < 2:
raise TemplateSyntaxError, "'%s' takes at least one argument (path to a view)" % bits[0]
raise TemplateSyntaxError("'%s' takes at least one argument"
" (path to a view)" % bits[0])
args = []
kwargs = {}
if len(bits) > 2:
@ -1011,8 +1066,8 @@ def widthratio(parser, token):
<img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
the above example will be 88 pixels wide (because 175/200 = .875; .875 *
100 = 87.5 which is rounded up to 88).
the above example will be 88 pixels wide (because 175/200 = .875;
.875 * 100 = 87.5 which is rounded up to 88).
"""
bits = token.contents.split()
if len(bits) != 4:
@ -1029,7 +1084,7 @@ widthratio = register.tag(widthratio)
#@register.tag
def do_with(parser, token):
"""
Add a value to the context (inside of this block) for caching and easy
Adds a value to the context (inside of this block) for caching and easy
access.
For example::
@ -1040,7 +1095,8 @@ def do_with(parser, token):
"""
bits = list(token.split_contents())
if len(bits) != 4 or bits[2] != "as":
raise TemplateSyntaxError, "%r expected format is 'value as name'" % bits[0]
raise TemplateSyntaxError("%r expected format is 'value as name'" %
bits[0])
var = parser.compile_filter(bits[1])
name = bits[3]
nodelist = parser.parse(('endwith',))

View File

@ -42,7 +42,7 @@ class ClientHandler(BaseHandler):
# Apply response middleware
for middleware_method in self._response_middleware:
response = middleware_method(request, response)
response = self.apply_response_fixes(request, response)
finally:
dispatcher.send(signal=signals.request_finished)

View File

@ -1,6 +1,6 @@
import re
import unittest
from urlparse import urlsplit
from urlparse import urlsplit, urlunsplit
from django.http import QueryDict
from django.db import transaction
@ -74,7 +74,7 @@ class TestCase(unittest.TestCase):
super(TestCase, self).__call__(result)
def assertRedirects(self, response, expected_url, status_code=302,
target_status_code=200):
target_status_code=200, host=None):
"""Asserts that a response redirected to a specific URL, and that the
redirect URL can be loaded.
@ -86,6 +86,10 @@ class TestCase(unittest.TestCase):
" (expected %d)" % (response.status_code, status_code)))
url = response['Location']
scheme, netloc, path, query, fragment = urlsplit(url)
e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url)
if not (e_scheme or e_netloc):
expected_url = urlunsplit(('http', host or 'testserver', e_path,
e_query, e_fragment))
self.assertEqual(url, expected_url,
"Response redirected to '%s', expected '%s'" % (url, expected_url))
@ -146,7 +150,7 @@ class TestCase(unittest.TestCase):
" context %d does not contain the"
" error '%s' (actual errors: %s)" %
(field, form, i, err,
list(field_errors)))
repr(field_errors)))
elif field in context[form].fields:
self.fail("The field '%s' on form '%s' in context %d"
" contains no errors" % (field, form, i))

View File

@ -13,17 +13,18 @@ into account when building its cache key. Requests with the same path but
different header content for headers named in "Vary" need to get different
cache keys to prevent delivery of wrong content.
A example: i18n middleware would need to distinguish caches by the
An example: i18n middleware would need to distinguish caches by the
"Accept-language" header.
"""
import md5
import re
import time
from email.Utils import formatdate
from django.conf import settings
from django.core.cache import cache
from django.utils.encoding import smart_str, iri_to_uri
from django.utils.http import http_date
cc_delim_re = re.compile(r'\s*,\s*')
@ -40,7 +41,7 @@ def patch_cache_control(response, **kwargs):
str() to it.
"""
def dictitem(s):
t = s.split('=',1)
t = s.split('=', 1)
if len(t) > 1:
return (t[0].lower(), t[1])
else:
@ -64,7 +65,7 @@ def patch_cache_control(response, **kwargs):
if 'max-age' in cc and 'max_age' in kwargs:
kwargs['max_age'] = min(cc['max-age'], kwargs['max_age'])
for (k,v) in kwargs.items():
for (k, v) in kwargs.items():
cc[k.replace('_', '-')] = v
cc = ', '.join([dictvalue(el) for el in cc.items()])
response['Cache-Control'] = cc
@ -88,15 +89,14 @@ def patch_response_headers(response, cache_timeout=None):
if not response.has_header('ETag'):
response['ETag'] = md5.new(response.content).hexdigest()
if not response.has_header('Last-Modified'):
response['Last-Modified'] = formatdate()[:26] + "GMT"
response['Last-Modified'] = http_date()
if not response.has_header('Expires'):
response['Expires'] = formatdate(time.time() + cache_timeout)[:26] + "GMT"
response['Expires'] = http_date(time.time() + cache_timeout)
patch_cache_control(response, max_age=cache_timeout)
def add_never_cache_headers(response):
"""
Add headers to a response to indicate that
a page should never be cached.
Adds headers to a response to indicate that a page should never be cached.
"""
patch_response_headers(response, cache_timeout=-1)
@ -119,13 +119,14 @@ def patch_vary_headers(response, newheaders):
response['Vary'] = ', '.join(vary)
def _generate_cache_key(request, headerlist, key_prefix):
"Returns a cache key from the headers given in the header list."
"""Returns a cache key from the headers given in the header list."""
ctx = md5.new()
for header in headerlist:
value = request.META.get(header, None)
if value is not None:
ctx.update(value)
return 'views.decorators.cache.cache_page.%s.%s.%s' % (key_prefix, iri_to_uri(request.path), ctx.hexdigest())
return 'views.decorators.cache.cache_page.%s.%s.%s' % (
key_prefix, iri_to_uri(request.path), ctx.hexdigest())
def get_cache_key(request, key_prefix=None):
"""
@ -139,7 +140,8 @@ def get_cache_key(request, key_prefix=None):
"""
if key_prefix is None:
key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, iri_to_uri(request.path))
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
key_prefix, iri_to_uri(request.path))
headerlist = cache.get(cache_key, None)
if headerlist is not None:
return _generate_cache_key(request, headerlist, key_prefix)
@ -163,9 +165,11 @@ def learn_cache_key(request, response, cache_timeout=None, key_prefix=None):
key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
if cache_timeout is None:
cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, iri_to_uri(request.path))
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
key_prefix, iri_to_uri(request.path))
if response.has_header('Vary'):
headerlist = ['HTTP_'+header.upper().replace('-', '_') for header in vary_delim_re.split(response['Vary'])]
headerlist = ['HTTP_'+header.upper().replace('-', '_')
for header in vary_delim_re.split(response['Vary'])]
cache.set(cache_key, headerlist, cache_timeout)
return _generate_cache_key(request, headerlist, key_prefix)
else:

View File

@ -62,12 +62,10 @@ class SortedDict(dict):
else:
self.keyOrder = [key for key, value in data]
def __deepcopy__(self,memo):
def __deepcopy__(self, memo):
from copy import deepcopy
obj = self.__class__()
for k, v in self.items():
obj[k] = deepcopy(v, memo)
return obj
return self.__class__([(key, deepcopy(value, memo))
for key, value in self.iteritems()])
def __setitem__(self, key, value):
dict.__setitem__(self, key, value)

View File

@ -170,7 +170,7 @@ class DateFormat(TimeFormat):
return u"%+03d%02d" % (seconds // 3600, (seconds // 60) % 60)
def r(self):
"RFC 822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'"
"RFC 2822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'"
return self.format('D, j M Y H:i:s O')
def S(self):

View File

@ -1,7 +1,19 @@
import types
import urllib
import datetime
from django.utils.functional import Promise
from django.utils.safestring import SafeData, mark_safe
class DjangoUnicodeDecodeError(UnicodeDecodeError):
def __init__(self, obj, *args):
self.obj = obj
UnicodeDecodeError.__init__(self, *args)
def __str__(self):
original = UnicodeDecodeError.__str__(self)
return '%s. You passed in %r (%s)' % (original, self.obj,
type(self.obj))
class StrAndUnicode(object):
"""
@ -33,13 +45,19 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
"""
if strings_only and isinstance(s, (types.NoneType, int, long, datetime.datetime, datetime.date, datetime.time, float)):
return s
if not isinstance(s, basestring,):
if hasattr(s, '__unicode__'):
s = unicode(s)
else:
s = unicode(str(s), encoding, errors)
elif not isinstance(s, unicode):
s = unicode(s, encoding, errors)
try:
if not isinstance(s, basestring,):
if hasattr(s, '__unicode__'):
s = unicode(s)
else:
s = unicode(str(s), encoding, errors)
elif not isinstance(s, unicode):
# Note: We use .decode() here, instead of unicode(s, encoding,
# errors), so that if s is a SafeString, it ends up being a
# SafeUnicode at the end.
s = s.decode(encoding, errors)
except UnicodeDecodeError, e:
raise DjangoUnicodeDecodeError(s, *e.args)
return s
def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):

View File

@ -3,6 +3,7 @@
import re
import string
from django.utils.safestring import SafeData, mark_safe
from django.utils.encoding import force_unicode
from django.utils.functional import allow_lazy
@ -27,14 +28,26 @@ del x # Temporary variable
def escape(html):
"Return the given HTML with ampersands, quotes and carets encoded."
return force_unicode(html).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;')
return mark_safe(force_unicode(html).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;'))
escape = allow_lazy(escape, unicode)
def linebreaks(value):
"Convert newlines into <p> and <br />s."
def conditional_escape(html):
"""
Similar to escape(), except that it doesn't operate on pre-escaped strings.
"""
if isinstance(html, SafeData):
return html
else:
return escape(html)
def linebreaks(value, autoescape=False):
"Converts newlines into <p> and <br />s"
value = re.sub(r'\r\n|\r|\n', '\n', force_unicode(value)) # normalize newlines
paras = re.split('\n{2,}', value)
paras = [u'<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
if autoescape:
paras = [u'<p>%s</p>' % escape(p.strip()).replace('\n', '<br />') for p in paras]
else:
paras = [u'<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
return u'\n\n'.join(paras)
linebreaks = allow_lazy(linebreaks, unicode)
@ -58,7 +71,7 @@ def fix_ampersands(value):
return unencoded_ampersands_re.sub('&amp;', force_unicode(value))
fix_ampersands = allow_lazy(fix_ampersands, unicode)
def urlize(text, trim_url_limit=None, nofollow=False):
def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
"""
Convert any URLs in text into clickable links.
@ -72,13 +85,19 @@ def urlize(text, trim_url_limit=None, nofollow=False):
If nofollow is True, the URLs in link text will get a rel="nofollow"
attribute.
"""
trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
if autoescape:
trim_url = lambda x, limit=trim_url_limit: conditional_escape(limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x)
else:
trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
safe_input = isinstance(text, SafeData)
words = word_split_re.split(force_unicode(text))
nofollow_attr = nofollow and ' rel="nofollow"' or ''
for i, word in enumerate(words):
match = punctuation_re.match(word)
if match:
lead, middle, trail = match.groups()
if safe_input:
middle = mark_safe(middle)
if middle.startswith('www.') or ('@' not in middle and not middle.startswith('http://') and \
len(middle) > 0 and middle[0] in string.letters + string.digits and \
(middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))):

View File

@ -1,4 +1,6 @@
import urllib
from email.Utils import formatdate
from django.utils.encoding import smart_str, force_unicode
from django.utils.functional import allow_lazy
@ -37,3 +39,29 @@ def urlencode(query, doseq=0):
for k, v in query],
doseq)
def cookie_date(epoch_seconds=None):
"""
Formats the time to ensure compatibility with Netscape's cookie standard.
Accepts a floating point number expressed in seconds since the epoch, in
UTC - such as that outputted by time.time(). If set to None, defaults to
the current time.
Outputs a string in the format 'Wdy, DD-Mon-YYYY HH:MM:SS GMT'.
"""
rfcdate = formatdate(epoch_seconds)
return '%s-%s-%s GMT' % (rfcdate[:7], rfcdate[8:11], rfcdate[12:25])
def http_date(epoch_seconds=None):
"""
Formats the time to match the RFC1123 date format as specified by HTTP
RFC2616 section 3.3.1.
Accepts a floating point number expressed in seconds since the epoch, in
UTC - such as that outputted by time.time(). If set to None, defaults to
the current time.
Outputs a string in the format 'Wdy, DD Mon YYYY HH:MM:SS GMT'.
"""
rfcdate = formatdate(epoch_seconds)
return '%s GMT' % rfcdate[:25]

124
django/utils/safestring.py Normal file
View File

@ -0,0 +1,124 @@
"""
Functions for working with "safe strings": strings that can be displayed safely
without further escaping in HTML. Marking something as a "safe string" means
that the producer of the string has already turned characters that should not
be interpreted by the HTML engine (e.g. '<') into the appropriate entities.
"""
from django.utils.functional import curry, Promise
class EscapeData(object):
pass
class EscapeString(str, EscapeData):
"""
A string that should be HTML-escaped when output.
"""
pass
class EscapeUnicode(unicode, EscapeData):
"""
A unicode object that should be HTML-escaped when output.
"""
pass
class SafeData(object):
pass
class SafeString(str, SafeData):
"""
A string subclass that has been specifically marked as "safe" (requires no
further escaping) for HTML output purposes.
"""
def __add__(self, rhs):
"""
Concatenating a safe string with another safe string or safe unicode
object is safe. Otherwise, the result is no longer safe.
"""
if isinstance(rhs, SafeUnicode):
return SafeUnicode(self + rhs)
elif isinstance(rhs, SafeString):
return SafeString(self + rhs)
else:
return super(SafeString, self).__add__(rhs)
def __str__(self):
return self
def _proxy_method(self, *args, **kwargs):
"""
Wrap a call to a normal unicode method up so that we return safe
results. The method that is being wrapped is passed in the 'method'
argument.
"""
method = kwargs.pop('method')
data = method(self, *args, **kwargs)
if isinstance(data, str):
return SafeString(data)
else:
return SafeUnicode(data)
encode = curry(_proxy_method, method = str.encode)
decode = curry(_proxy_method, method = str.decode)
class SafeUnicode(unicode, SafeData):
"""
A unicode subclass that has been specifically marked as "safe" for HTML
output purposes.
"""
def __add__(self, rhs):
"""
Concatenating a safe unicode object with another safe string or safe
unicode object is safe. Otherwise, the result is no longer safe.
"""
if isinstance(rhs, SafeData):
return SafeUnicode(self + rhs)
else:
return super(SafeUnicode, self).__add__(rhs)
def _proxy_method(self, *args, **kwargs):
"""
Wrap a call to a normal unicode method up so that we return safe
results. The method that is being wrapped is passed in the 'method'
argument.
"""
method = kwargs.pop('method')
data = method(self, *args, **kwargs)
if isinstance(data, str):
return SafeString(data)
else:
return SafeUnicode(data)
encode = curry(_proxy_method, method = unicode.encode)
decode = curry(_proxy_method, method = unicode.decode)
def mark_safe(s):
"""
Explicitly mark a string as safe for (HTML) output purposes. The returned
object can be used everywhere a string or unicode object is appropriate.
Can be called multiple times on a single string.
"""
if isinstance(s, SafeData):
return s
if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str):
return SafeString(s)
if isinstance(s, (unicode, Promise)):
return SafeUnicode(s)
return SafeString(str(s))
def mark_for_escaping(s):
"""
Explicitly mark a string as requiring HTML escaping upon output. Has no
effect on SafeData subclasses.
Can be called multiple times on a single string (the resulting escaping is
only applied once).
"""
if isinstance(s, (SafeData, EscapeData)):
return s
if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str):
return EscapeString(s)
if isinstance(s, (unicode, Promise)):
return EscapeUnicode(s)
return EscapeString(str(s))

View File

@ -333,7 +333,6 @@ TECHNICAL_500_TEMPLATE = """
</script>
</head>
<body>
<div id="summary">
<h1>{{ exception_type }} at {{ request.path|escape }}</h1>
<h2>{{ exception_value|escape }}</h2>
@ -395,7 +394,7 @@ TECHNICAL_500_TEMPLATE = """
<div id="template">
<h2>Template error</h2>
<p>In template <code>{{ template_info.name }}</code>, error at line <strong>{{ template_info.line }}</strong></p>
<h3>{{ template_info.message|escape }}</h3>
<h3>{{ template_info.message }}</h3>
<table class="source{% if template_info.top %} cut-top{% endif %}{% ifnotequal template_info.bottom template_info.total %} cut-bottom{% endifnotequal %}">
{% for source_line in template_info.source_lines %}
{% ifequal source_line.0 template_info.line %}
@ -413,6 +412,7 @@ TECHNICAL_500_TEMPLATE = """
<h2>Traceback <span>(innermost last)</span></h2>
<div class="commands"><a href="#" onclick="return switchPastebinFriendly(this);">Switch to copy-and-paste view</a></div>
<br/>
{% autoescape off %}
<div id="browserTraceback">
<ul class="traceback">
{% for frame in frames %}
@ -422,11 +422,11 @@ TECHNICAL_500_TEMPLATE = """
{% if frame.context_line %}
<div class="context" id="c{{ frame.id }}">
{% if frame.pre_context %}
<ol start="{{ frame.pre_context_lineno }}" class="pre-context" id="pre{{ frame.id }}">{% for line in frame.pre_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line|escape }}</li>{% endfor %}</ol>
<ol start="{{ frame.pre_context_lineno }}" class="pre-context" id="pre{{ frame.id }}">{% for line in frame.pre_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line }}</li>{% endfor %}</ol>
{% endif %}
<ol start="{{ frame.lineno }}" class="context-line"><li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ frame.context_line|escape }} <span>...</span></li></ol>
<ol start="{{ frame.lineno }}" class="context-line"><li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ frame.context_line }} <span>...</span></li></ol>
{% if frame.post_context %}
<ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}">{% for line in frame.post_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line|escape }}</li>{% endfor %}</ol>
<ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}">{% for line in frame.post_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line }}</li>{% endfor %}</ol>
{% endif %}
</div>
{% endif %}
@ -446,7 +446,7 @@ TECHNICAL_500_TEMPLATE = """
{% for var in frame.vars|dictsort:"0" %}
<tr>
<td>{{ var.0 }}</td>
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
<td class="code"><div>{{ var.1|pprint }}</div></td>
</tr>
{% endfor %}
</tbody>
@ -466,7 +466,7 @@ Traceback (most recent call last):<br/>
{% for frame in frames %}
File "{{ frame.filename }}" in {{ frame.function }}<br/>
{% if frame.context_line %}
&nbsp;&nbsp;{{ frame.lineno }}. {{ frame.context_line|escape }}<br/>
&nbsp;&nbsp;{{ frame.lineno }}. {{ frame.context_line }}<br/>
{% endif %}
{% endfor %}<br/>
&nbsp;&nbsp;{{ exception_type }} at {{ request.path|escape }}<br/>
@ -476,6 +476,7 @@ Traceback (most recent call last):<br/>
</tbody>
</table>
</div>
{% endautoescape %}
</div>
<div id="requestinfo">
@ -494,7 +495,7 @@ Traceback (most recent call last):<br/>
{% for var in request.GET.items %}
<tr>
<td>{{ var.0 }}</td>
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
<td class="code"><div>{{ var.1|pprint }}</div></td>
</tr>
{% endfor %}
</tbody>
@ -516,7 +517,7 @@ Traceback (most recent call last):<br/>
{% for var in request.POST.items %}
<tr>
<td>{{ var.0 }}</td>
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
<td class="code"><div>{{ var.1|pprint }}</div></td>
</tr>
{% endfor %}
</tbody>
@ -538,7 +539,7 @@ Traceback (most recent call last):<br/>
{% for var in request.COOKIES.items %}
<tr>
<td>{{ var.0 }}</td>
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
<td class="code"><div>{{ var.1|pprint }}</div></td>
</tr>
{% endfor %}
</tbody>
@ -559,7 +560,7 @@ Traceback (most recent call last):<br/>
{% for var in request.META.items|dictsort:"0" %}
<tr>
<td>{{ var.0 }}</td>
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
<td class="code"><div>{{ var.1|pprint }}</div></td>
</tr>
{% endfor %}
</tbody>
@ -578,7 +579,7 @@ Traceback (most recent call last):<br/>
{% for var in settings.items|dictsort:"0" %}
<tr>
<td>{{ var.0 }}</td>
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
<td class="code"><div>{{ var.1|pprint }}</div></td>
</tr>
{% endfor %}
</tbody>
@ -593,7 +594,6 @@ Traceback (most recent call last):<br/>
display a standard 500 page.
</p>
</div>
</body>
</html>
"""
@ -645,12 +645,12 @@ TECHNICAL_404_TEMPLATE = """
</p>
<ol>
{% for pattern in urlpatterns %}
<li>{{ pattern|escape }}</li>
<li>{{ pattern }}</li>
{% endfor %}
</ol>
<p>The current URL, <code>{{ request_path|escape }}</code>, didn't match any of these.</p>
{% else %}
<p>{{ reason|escape }}</p>
<p>{{ reason }}</p>
{% endif %}
</div>

View File

@ -7,13 +7,14 @@ import mimetypes
import os
import posixpath
import re
import rfc822
import stat
import urllib
from email.Utils import parsedate_tz, mktime_tz
from django.template import loader
from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
from django.template import Template, Context, TemplateDoesNotExist
from django.utils.http import http_date
def serve(request, path, document_root=None, show_indexes=False):
"""
@ -60,7 +61,7 @@ def serve(request, path, document_root=None, show_indexes=False):
mimetype = mimetypes.guess_type(fullpath)[0]
contents = open(fullpath, 'rb').read()
response = HttpResponse(contents, mimetype=mimetype)
response["Last-Modified"] = rfc822.formatdate(statobj[stat.ST_MTIME])
response["Last-Modified"] = http_date(statobj[stat.ST_MTIME])
return response
DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
@ -119,8 +120,7 @@ def was_modified_since(header=None, mtime=0, size=0):
raise ValueError
matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
re.IGNORECASE)
header_mtime = rfc822.mktime_tz(rfc822.parsedate_tz(
matches.group(1)))
header_mtime = mktime_tz(parsedate_tz(matches.group(1)))
header_len = matches.group(3)
if header_len and int(header_len) != size:
raise ValueError

View File

@ -291,13 +291,15 @@ minutes.
Template fragment caching
=========================
**New in development version**.
If you're after even more control, you can also cache template fragments using
the ``cache`` template tag. To give your template access to this tag, put ``{%
load cache %}`` near the top of your template.
the ``cache`` template tag. To give your template access to this tag, put
``{% load cache %}`` near the top of your template.
The ``{% cache %}`` template tag caches the contents of the block for a given
amount of time. It takes at least two arguments: the cache timeout, in
seconds, and the name to give the cache fragment. For example::
amount of time. It takes at least two arguments: the cache timeout, in seconds,
and the name to give the cache fragment. For example::
{% load cache %}
{% cache 500 sidebar %}

View File

@ -0,0 +1,567 @@
===================
Custom Model Fields
===================
**New in Django development version**
Introduction
============
The `model reference`_ documentation explains how to use Django's standard
field classes. For many purposes, those classes are all you'll need. Sometimes,
though, the Django version won't meet your precise requirements, or you'll want
to use a field that is entirely different from those shipped with Django.
Django's built-in field types don't cover every possible database column type --
only the common types, such as ``VARCHAR`` and ``INTEGER``. For more obscure
column types, such as geographic polygons or even user-created types such as
`PostgreSQL custom types`_, you can define your own Django ``Field`` subclasses.
Alternatively, you may have a complex Python object that can somehow be
serialized to fit into a standard database column type. This is another case
where a ``Field`` subclass will help you use your object with your models.
Our example object
------------------
Creating custom fields requires a bit of attention to detail. To make things
easier to follow, we'll use a consistent example throughout this document.
Suppose you have a Python object representing the deal of cards in a hand of
Bridge_. It doesn't matter if you don't know how to play Bridge. You only need
to know that 52 cards are dealt out equally to four players, who are
traditionally called *north*, *east*, *south* and *west*. Our class looks
something like this::
class Hand(object):
def __init__(self, north, east, south, west):
# Input parameters are lists of cards ('Ah', '9s', etc)
self.north = north
self.east = east
self.south = south
self.west = west
# ... (other possibly useful methods omitted) ...
This is just an ordinary Python class, nothing Django-specific about it. We
would like to be able to things like this in our models (we assume the
``hand`` attribute on the model is an instance of ``Hand``)::
example = MyModel.objects.get(pk=1)
print example.hand.north
new_hand = Hand(north, east, south, west)
example.hand = new_hand
example.save()
We assign to and retrieve from the ``hand`` attribute in our model just like
any other Python class. The trick is to tell Django how to handle saving and
loading such an object
In order to use the ``Hand`` class in our models, we **do not** have to change
this class at all. This is ideal, because it means you can easily write
model support for existing classes where you cannot change the source code.
.. note::
You might only be wanting to take advantage of custom database column
types and deal with the data as standard Python types in your models;
strings, or floats, for example. This case is similar to our ``Hand``
example and we'll note any differences as we go along.
.. _model reference: ../model_api/
.. _PostgreSQL custom types: http://www.postgresql.org/docs/8.2/interactive/sql-createtype.html
.. _Bridge: http://en.wikipedia.org/wiki/Contract_bridge
Background Theory
=================
Database storage
----------------
The simplest way to think of a model field is that it provides a way to take a
normal Python object -- string, boolean, ``datetime``, or something more
complex like ``Hand`` -- and convert it to and from a format that is useful
when dealing with the database (and serialization, but, as we'll see later,
that falls out fairly naturally once you have the database side under control).
Fields in a model must somehow be converted to fit into an existing database
column type. Different databases provide different sets of valid column types,
but the rule is still the same: those are the only types you have to work
with. Anything you want to store in the database must fit into one of
those types.
Normally, you're either writing a Django field to match a particular database
column type, or there's a fairly straightforward way to convert your data to,
say, a string.
For our ``Hand`` example, we could convert the card data to a string of 104
characters by concatenating all the cards together in a pre-determined order.
Say, all the *north* cards first, then the *east*, *south* and *west* cards, in
that order. So ``Hand`` objects can be saved to text or character columns in
the database
What does a field class do?
---------------------------
All of Django's fields (and when we say *fields* in this document, we always
mean model fields and not `form fields`_) are subclasses of
``django.db.models.Field``. Most of the information that Django records about a
field is common to all fields -- name, help text, validator lists, uniqueness
and so forth. Storing all that information is handled by ``Field``. We'll get
into the precise details of what ``Field`` can do later on; for now, suffice it
to say that everything descends from ``Field`` and then customises key pieces
of the class behaviour.
.. _form fields: ../newforms/#fields
It's important to realise that a Django field class is not what is stored in
your model attributes. The model attributes contain normal Python objects. The
field classes you define in a model are actually stored in the ``Meta`` class
when the model class is created (the precise details of how this is done are
unimportant here). This is because the field classes aren't necessary when
you're just creating and modifying attributes. Instead, they provide the
machinery for converting between the attribute value and what is stored in the
database or sent to the serializer.
Keep this in mind when creating your own custom fields. The Django ``Field``
subclass you write provides the machinery for converting between your Python
instances and the database/serializer values in various ways (there are
differences between storing a value and using a value for lookups, for
example). If this sounds a bit tricky, don't worry. It will hopefully become
clearer in the examples below. Just remember that you will often end up
creating two classes when you want a custom field. The first class is the
Python object that your users will manipulate. They will assign it to the model
attribute, they will read from it for displaying purposes, things like that.
This is the ``Hand`` class in our example. The second class is the ``Field``
subclass. This is the class that knows how to convert your first class back and
forth between its permanent storage form and the Python form.
Writing a ``Field`` subclass
=============================
When you are planning your ``Field`` subclass, first give some thought to
which existing field your new field is most similar to. Can you subclass an
existing Django field and save yourself some work? If not, you should subclass the ``Field`` class, from which everything is descended.
Initialising your new field is a matter of separating out any arguments that
are specific to your case from the common arguments and passing the latter to
the ``__init__()`` method of ``Field`` (or your parent class).
In our example, the Django field we create is going to be called
``HandField``. It's not a bad idea to use a similar naming scheme to Django's
fields so that our new class is identifiable and yet clearly related to the
``Hand`` class it is wrapping. It doesn't behave like any existing field, so
we'll subclass directly from ``Field``::
from django.db import models
class HandField(models.Field):
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 104
super(HandField, self).__init__(*args, **kwargs)
Our ``HandField`` will accept most of the standard field options (see the list
below), but we ensure it has a fixed length, since it only needs to hold 52
card values plus their suits; 104 characters in total.
.. note::
Many of Django's model fields accept options that they don't do anything
with. For example, you can pass both ``editable`` and ``auto_now`` to a
``DateField`` and it will simply ignore the ``editable`` parameter
(``auto_now`` being set implies ``editable=False``). No error is raised in
this case.
This behaviour simplifies the field classes, because they don't need to
check for options that aren't necessary. They just pass all the options to
the parent class and then don't use them later on. It is up to you whether
you want your fields to be more strict about the options they select, or
to use the simpler, more permissive behaviour of the current fields.
The ``Field.__init__()`` method takes the following parameters, in this
order:
- ``verbose_name``
- ``name``
- ``primary_key``
- ``max_length``
- ``unique``
- ``blank``
- ``null``
- ``db_index``
- ``core``
- ``rel``: Used for related fields (like ``ForeignKey``). For advanced use
only.
- ``default``
- ``editable``
- ``serialize``: If ``False``, the field will not be serialized when the
model is passed to Django's serializers_. Defaults to ``True``.
- ``prepopulate_from``
- ``unique_for_date``
- ``unique_for_month``
- ``unique_for_year``
- ``validator_list``
- ``choices``
- ``radio_admin``
- ``help_text``
- ``db_column``
- ``db_tablespace``: Currently only used with the Oracle backend and only
for index creation. You can usually ignore this option.
All of the options without an explanation in the above list have the same
meaning they do for normal Django fields. See the `model documentation`_ for
examples and details.
.. _serializers: ../serialization/
.. _model documentation: ../model-api/
The ``SubfieldBase`` metaclass
------------------------------
As we indicated in the introduction_, field subclasses are often needed for
two reasons. Either to take advantage of a custom database column type, or to
handle complex Python types. A combination of the two is obviously also
possible. If you are only working with custom database column types and your
model fields appear in Python as standard Python types direct from the
database backend, you don't need to worry about this section.
If you are handling custom Python types, such as our ``Hand`` class, we need
to make sure that when Django initialises an instance of our model and assigns
a database value to our custom field attribute we convert that value into the
appropriate Python object. The details of how this happens internally are a
little complex. For the field writer, though, things are fairly simple. Make
sure your field subclass uses ``django.db.models.SubfieldBase`` as its
metaclass. This ensures that the ``to_python()`` method, documented below_,
will always be called when the attribute is initialised.
Our ``HandleField`` class now looks like this::
class HandleField(models.Field):
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
# ...
.. _below: #to-python-self-value
Useful methods
--------------
Once you've created your ``Field`` subclass and setup up the
``__metaclass__``, if necessary, there are a few standard methods you need to
consider overriding. Which of these you need to implement will depend on you
particular field behaviour. The list below is in approximately decreasing
order of importance, so start from the top.
``db_type(self)``
~~~~~~~~~~~~~~~~~
Returns the database column data type for the ``Field``, taking into account
the current ``DATABASE_ENGINE`` setting.
Say you've created a PostgreSQL custom type called ``mytype``. You can use this
field with Django by subclassing ``Field`` and implementing the ``db_type()``
method, like so::
from django.db import models
class MytypeField(models.Field):
def db_type(self):
return 'mytype'
Once you have ``MytypeField``, you can use it in any model, just like any other
``Field`` type::
class Person(models.Model):
name = models.CharField(max_length=80)
gender = models.CharField(max_length=1)
something_else = MytypeField()
If you aim to build a database-agnostic application, you should account for
differences in database column types. For example, the date/time column type
in PostgreSQL is called ``timestamp``, while the same column in MySQL is called
``datetime``. The simplest way to handle this in a ``db_type()`` method is to
import the Django settings module and check the ``DATABASE_ENGINE`` setting.
For example::
class MyDateField(models.Field):
def db_type(self):
from django.conf import settings
if settings.DATABASE_ENGINE == 'mysql':
return 'datetime'
else:
return 'timestamp'
The ``db_type()`` method is only called by Django when the framework constructs
the ``CREATE TABLE`` statements for your application -- that is, when you first
create your tables. It's not called at any other time, so it can afford to
execute slightly complex code, such as the ``DATABASE_ENGINE`` check in the
above example.
Some database column types accept parameters, such as ``CHAR(25)``, where the
parameter ``25`` represents the maximum column length. In cases like these,
it's more flexible if the parameter is specified in the model rather than being
hard-coded in the ``db_type()`` method. For example, it wouldn't make much
sense to have a ``CharMaxlength25Field``, shown here::
# This is a silly example of hard-coded parameters.
class CharMaxlength25Field(models.Field):
def db_type(self):
return 'char(25)'
# In the model:
class MyModel(models.Model):
# ...
my_field = CharMaxlength25Field()
The better way of doing this would be to make the parameter specifiable at run
time -- i.e., when the class is instantiated. To do that, just implement
``__init__()``, like so::
# This is a much more flexible example.
class BetterCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super(BetterCharField, self).__init__(*args, **kwargs)
def db_type(self):
return 'char(%s)' % self.max_length
# In the model:
class MyModel(models.Model):
# ...
my_field = BetterCharField(25)
Finally, if your column requires truly complex SQL setup, return ``None`` from
``db_type()``. This will cause Django's SQL creation code to skip over this
field. You are then responsible for creating the column in the right table in
some other way, of course, but this gives you a way to tell Django to get out
of the way.
``to_python(self, value)``
~~~~~~~~~~~~~~~~~~~~~~~~~~
Converts between all the ways your field can receive its initial value and the
Python object you want to end up with. The default version just returns
``value``, so is useful is the database backend returns the data already in
the correct form (a Python string, for example).
Normally, you will need to override this method. As a general rule, be
prepared to accept an instance of the right type (e.g. ``Hand`` in our ongoing
example), a string (from a deserializer, for example), and whatever the
database wrapper returns for the column type you are using.
In our ``HandField`` class, we are storing the data in a character field in
the database, so we need to be able to process strings and ``Hand`` instances
in ``to_python()``::
class HandField(models.Field):
# ...
def to_python(self, value):
if isinstance(value, Hand):
return value
# The string case
p1 = re.compile('.{26}')
p2 = re.compile('..')
args = [p2.findall(x) for x in p1.findall(value)]
return Hand(*args)
Notice that we always return a ``Hand`` instance from this method. That is the
Python object we want to store in the model's attribute.
``get_db_prep_save(self, value)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is the reverse of ``to_python()`` when working with the database backends
(as opposed to serialization). The ``value`` parameter is the current value of
the model's attribute (a field has no reference to its containing model, so it
cannot retrieve the value itself) and the method should return data in a
format that can be used as a parameter in a query for the database backend.
For example::
class HandField(models.Field):
# ...
def get_db_prep_save(self, value):
return ''.join([''.join(l) for l in (self.north,
self.east, self.south, self.west)])
``pre_save(self, model_instance, add)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This method is called just prior to ``get_db_prep_save()`` and should return
the value of the appropriate attribute from ``model_instance`` for this field.
The attribute name is in ``self.attname`` (this is set up by ``Field``). If
the model is being saved to the database for the first time, the ``add``
parameter will be ``True``, otherwise it will be ``False``.
Often you won't need to override this method. However, at times it can be very
useful. For example, the Django ``DateTimeField`` uses this method to set the
attribute to the correct value before returning it in the cases when
``auto_now`` or ``auto_now_add`` are set on the field.
If you do override this method, you must return the value of the attribute at
the end. You should also update the model's attribute if you make any changes
to the value so that code holding references to the model will always see the
correct value.
``get_db_prep_lookup(self, lookup_type, value)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Prepares the ``value`` for passing to the database when used in a lookup (a
``WHERE`` constraint in SQL). The ``lookup_type`` will be one of the valid
Django filter lookups: ``exact``, ``iexact``, ``contains``, ``icontains``,
``gt``, ``gte``, ``lt``, ``lte``, ``in``, ``startswith``, ``istartswith``,
``endswith``, ``iendswith``, ``range``, ``year``, ``month``, ``day``,
``isnull``, ``search``, ``regex``, and ``iregex``.
Your method must be prepared to handle all of these ``lookup_type`` values and
should raise either a ``ValueError`` if the ``value`` is of the wrong sort (a
list when you were expecting an object, for example) or a ``TypeError`` if
your field does not support that type of lookup. For many fields, you can get
by with handling the lookup types that need special handling for your field
and pass the rest of the ``get_db_prep_lookup()`` method of the parent class.
If you needed to implement ``get_db_prep_save()``, you will usually need to
implement ``get_db_prep_lookup()``. The usual reason is because of the
``range`` and ``in`` lookups. In these case, you will passed a list of
objects (presumably of the right type) and will need to convert them to a list
of things of the right type for passing to the database. Sometimes you can
reuse ``get_db_prep_save()``, or at least factor out some common pieces from
both methods into a help function.
For example::
class HandField(models.Field):
# ...
def get_db_prep_lookup(self, lookup_type, value):
# We only handle 'exact' and 'in'. All others are errors.
if lookup_type == 'exact':
return self.get_db_prep_save(value)
elif lookup_type == 'in':
return [self.get_db_prep_save(v) for v in value]
else:
raise TypeError('Lookup type %r not supported.' % lookup_type)
``formfield(self, form_class=forms.CharField, **kwargs)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns the default form field to use when this field is displayed
in a model. This method is called by the `helper functions`_
``form_for_model()`` and ``form_for_instance()``.
All of the ``kwargs`` dictionary is passed directly to the form field's
``__init__()`` method. Normally, all you need to do is set up a good default
for the ``form_class`` argument and then delegate further handling to the
parent class. This might require you to write a custom form field (and even a
form widget). See the `forms documentation`_ for information about this. Also
have a look at ``django.contrib.localflavor`` for some examples of custom
widgets.
Continuing our ongoing example, we can write the ``formfield()`` method as::
class HandField(models.Field):
# ...
def formfield(self, **kwargs):
# This is a fairly standard way to set up some defaults
# whilst letting the caller override them.
defaults = {'form_class': MyFormField}
defaults.update(kwargs)
return super(HandField, self).formfield(**defaults)
This assumes we have some ``MyFormField`` field class (which has its own
default widget) imported. This document doesn't cover the details of writing
custom form fields.
.. _helper functions: ../newforms/#generating-forms-for-models
.. _forms documentation: ../newforms/
``get_internal_type(self)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns a string giving the name of the ``Field`` subclass we are emulating at
the database level. This is used to determine the type of database column for
simple cases.
If you have created a ``db_type()`` method, you do not need to worry about
``get_internal_type()`` -- it won't be used much. Sometimes, though, your
database storage is similar in type to some other field, so you can use that
other field's logic to create the right column.
For example::
class HandField(models.Field):
# ...
def get_internal_type(self):
return 'CharField'
No matter which database backend we are using, this will mean that ``syncdb``
and other SQL commands create the right column type for storing a string.
If ``get_internal_type()`` returns a string that is not known to Django for
the database backend you are using -- that is, it doesn't appear in
``django.db.backends.<db_name>.creation.DATA_TYPES`` -- the string will still
be used by the serializer, but the default ``db_type()`` method will return
``None``. See the documentation of ``db_type()`` above_ for reasons why this
might be useful. Putting a descriptive string in as the type of the field for
the serializer is a useful idea if you are ever going to be using the
serializer output in some other place, outside of Django.
.. _above: #db-type-self
``flatten_data(self, follow, obj=None)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. admonition:: Subject to change
Although implementing this method is necessary to allow field
serialization, the API might change in the future.
Returns a dictionary, mapping the field's attribute name to a flattened string
version of the data. This method has some internal uses that aren't of
interest to use here (mostly having to do with manipulators). For our
purposes, it is sufficient to return a one item dictionary that maps the
attribute name to a string.
This method is used by the serializers to convert the field into a string for
output. You can ignore the input parameters for serialization purposes,
although calling ``Field._get_val_from_obj(obj)`` is the best way to get the
value to serialize.
For example, since our ``HandField`` uses strings for its data storage anyway,
we can reuse some existing conversion code::
class HandField(models.Field):
# ...
def flatten_data(self, follow, obj=None):
value = self._get_val_from_obj(obj)
return {self.attname: self.get_db_prep_save(value)}
Some general advice
--------------------
Writing a custom field can be a tricky process sometime, particularly if you
are doing complex conversions between your Python types and your database and
serialization formats. A couple of tips to make things go more smoothly:
1. Look at the existing Django fields (in
``django/db/models/fields/__init__.py``) for inspiration. Try to find a field
that is already close to what you want and extend it a little bit, in
preference to creating an entirely new field from scratch.
2. Put a ``__str__()`` or ``__unicode__()`` method on the class you are
wrapping up as a field. There are a lot of places where the default behaviour
of the field code is to call ``force_unicode()`` on the value (in our
examples in this document, ``value`` would be a ``Hand`` instance, not a
``HandField``). So if your ``__unicode__()`` method automatically converts to
the string form of your Python object, you can save yourself a lot of work.

View File

@ -275,7 +275,7 @@ The class has the following methods:
There are two ways to call ``attach()``:
* You can pass it a single argument that is an
``email.MIMBase.MIMEBase`` instance. This will be inserted directly
``email.MIMEBase.MIMEBase`` instance. This will be inserted directly
into the resulting message.
* Alternatively, you can pass ``attach()`` three arguments:

View File

@ -45,7 +45,7 @@ How to use ``FormPreview``
2. Create a ``FormPreview`` subclass that overrides the ``done()`` method::
from django.contrib.formtools import FormPreview
from django.contrib.formtools.preview import FormPreview
from myapp.models import SomeModel
class SomeModelFormPreview(FormPreview):

View File

@ -1013,111 +1013,12 @@ See the `One-to-one relationship model example`_ for a full example.
Custom field types
------------------
**New in Django development version**
If one of the existing model fields cannot be used to fit your purposes, or if
you wish to take advantage of some less common database column types, you can
create your own field class. Full coverage of creating your own fields is
provided in the `Custom Model Fields`_ documentation.
Django's built-in field types don't cover every possible database column type --
only the common types, such as ``VARCHAR`` and ``INTEGER``. For more obscure
column types, such as geographic polygons or even user-created types such as
`PostgreSQL custom types`_, you can define your own Django ``Field`` subclasses.
.. _PostgreSQL custom types: http://www.postgresql.org/docs/8.2/interactive/sql-createtype.html
.. admonition:: Experimental territory
This is an area of Django that traditionally has not been documented, but
we're starting to include bits of documentation, one feature at a time.
Please forgive the sparseness of this section.
If you like living on the edge and are comfortable with the risk of
unstable, undocumented APIs, see the code for the core ``Field`` class
in ``django/db/models/fields/__init__.py`` -- but if/when the innards
change, don't say we didn't warn you.
To create a custom field type, simply subclass ``django.db.models.Field``.
Here is an incomplete list of the methods you should implement:
``db_type()``
~~~~~~~~~~~~~
Returns the database column data type for the ``Field``, taking into account
the current ``DATABASE_ENGINE`` setting.
Say you've created a PostgreSQL custom type called ``mytype``. You can use this
field with Django by subclassing ``Field`` and implementing the ``db_type()``
method, like so::
from django.db import models
class MytypeField(models.Field):
def db_type(self):
return 'mytype'
Once you have ``MytypeField``, you can use it in any model, just like any other
``Field`` type::
class Person(models.Model):
name = models.CharField(max_length=80)
gender = models.CharField(max_length=1)
something_else = MytypeField()
If you aim to build a database-agnostic application, you should account for
differences in database column types. For example, the date/time column type
in PostgreSQL is called ``timestamp``, while the same column in MySQL is called
``datetime``. The simplest way to handle this in a ``db_type()`` method is to
import the Django settings module and check the ``DATABASE_ENGINE`` setting.
For example::
class MyDateField(models.Field):
def db_type(self):
from django.conf import settings
if settings.DATABASE_ENGINE == 'mysql':
return 'datetime'
else:
return 'timestamp'
The ``db_type()`` method is only called by Django when the framework constructs
the ``CREATE TABLE`` statements for your application -- that is, when you first
create your tables. It's not called at any other time, so it can afford to
execute slightly complex code, such as the ``DATABASE_ENGINE`` check in the
above example.
Some database column types accept parameters, such as ``CHAR(25)``, where the
parameter ``25`` represents the maximum column length. In cases like these,
it's more flexible if the parameter is specified in the model rather than being
hard-coded in the ``db_type()`` method. For example, it wouldn't make much
sense to have a ``CharMaxlength25Field``, shown here::
# This is a silly example of hard-coded parameters.
class CharMaxlength25Field(models.Field):
def db_type(self):
return 'char(25)'
# In the model:
class MyModel(models.Model):
# ...
my_field = CharMaxlength25Field()
The better way of doing this would be to make the parameter specifiable at run
time -- i.e., when the class is instantiated. To do that, just implement
``__init__()``, like so::
# This is a much more flexible example.
class BetterCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super(BetterCharField, self).__init__(*args, **kwargs)
def db_type(self):
return 'char(%s)' % self.max_length
# In the model:
class MyModel(models.Model):
# ...
my_field = BetterCharField(25)
Note that if you implement ``__init__()`` on a ``Field`` subclass, it's
important to call ``Field.__init__()`` -- i.e., the parent class'
``__init__()`` method.
.. _Custom Model Fields: ../custom_model_fields/
Meta options
============

View File

@ -150,7 +150,7 @@ mess things up. Use the ``PythonInterpreter`` directive to give different
<Location "/otherthing">
SetEnv DJANGO_SETTINGS_MODULE mysite.other_settings
PythonInterpreter mysite_other
PythonInterpreter othersite
</Location>
</VirtualHost>

View File

@ -1078,6 +1078,30 @@ fields. We've specified ``auto_id=False`` to simplify the output::
<p>Sender: <input type="text" name="sender" /> A valid e-mail address, please.</p>
<p>Cc myself: <input type="checkbox" name="cc_myself" /></p>
``error_messages``
~~~~~~~~~~~~~~~~~~
**New in Django development version**
The ``error_messages`` argument lets you override the default messages which the
field will raise. Pass in a dictionary with keys matching the error messages you
want to override. For example::
>>> generic = forms.CharField()
>>> generic.clean('')
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> name = forms.CharField(error_messages={'required': 'Please enter your name'})
>>> name.clean('')
Traceback (most recent call last):
...
ValidationError: [u'Please enter your name']
In the `built-in Field classes`_ section below, each Field defines the error
message keys it uses.
Dynamic initial values
----------------------
@ -1143,6 +1167,7 @@ For each field, we describe the default widget used if you don't specify
* Normalizes to: A Python ``True`` or ``False`` value.
* Validates that the check box is checked (i.e. the value is ``True``) if
the field has ``required=True``.
* Error message keys: ``required``
**New in Django development version:** The empty value for a ``CheckboxInput``
(and hence the standard ``BooleanField``) has changed to return ``False``
@ -1162,6 +1187,7 @@ instead of ``None`` in the development version.
* Normalizes to: A Unicode object.
* Validates ``max_length`` or ``min_length``, if they are provided.
Otherwise, all inputs are valid.
* Error message keys: ``required``, ``max_length``, ``min_length``
Has two optional arguments for validation, ``max_length`` and ``min_length``.
If provided, these arguments ensure that the string is at most or at least the
@ -1174,6 +1200,7 @@ given length.
* Empty value: ``''`` (an empty string)
* Normalizes to: A Unicode object.
* Validates that the given value exists in the list of choices.
* Error message keys: ``required``, ``invalid_choice``
Takes one extra argument, ``choices``, which is an iterable (e.g., a list or
tuple) of 2-tuples to use as choices for this field.
@ -1186,6 +1213,7 @@ tuple) of 2-tuples to use as choices for this field.
* Normalizes to: A Python ``datetime.date`` object.
* Validates that the given value is either a ``datetime.date``,
``datetime.datetime`` or string formatted in a particular date format.
* Error message keys: ``required``, ``invalid``
Takes one optional argument, ``input_formats``, which is a list of formats used
to attempt to convert a string to a valid ``datetime.date`` object.
@ -1206,6 +1234,7 @@ If no ``input_formats`` argument is provided, the default input formats are::
* Normalizes to: A Python ``datetime.datetime`` object.
* Validates that the given value is either a ``datetime.datetime``,
``datetime.date`` or string formatted in a particular datetime format.
* Error message keys: ``required``, ``invalid``
Takes one optional argument, ``input_formats``, which is a list of formats used
to attempt to convert a string to a valid ``datetime.datetime`` object.
@ -1235,6 +1264,9 @@ If no ``input_formats`` argument is provided, the default input formats are::
* Normalizes to: A Python ``decimal``.
* Validates that the given value is a decimal. Leading and trailing
whitespace is ignored.
* Error message keys: ``required``, ``invalid``, ``max_value``,
``min_value``, ``max_digits``, ``max_decimal_places``,
``max_whole_digits``
Takes four optional arguments: ``max_value``, ``min_value``, ``max_digits``,
and ``decimal_places``. The first two define the limits for the fields value.
@ -1251,6 +1283,7 @@ decimal places permitted.
* Normalizes to: A Unicode object.
* Validates that the given value is a valid e-mail address, using a
moderately complex regular expression.
* Error message keys: ``required``, ``invalid``
Has two optional arguments for validation, ``max_length`` and ``min_length``.
If provided, these arguments ensure that the string is at most or at least the
@ -1266,6 +1299,7 @@ given length.
* Normalizes to: An ``UploadedFile`` object that wraps the file content
and file name into a single object.
* Validates that non-empty file data has been bound to the form.
* Error message keys: ``required``, ``invalid``, ``missing``, ``empty``
An ``UploadedFile`` object has two attributes:
@ -1296,6 +1330,8 @@ When you use a ``FileField`` on a form, you must also remember to
and file name into a single object.
* Validates that file data has been bound to the form, and that the
file is of an image format understood by PIL.
* Error message keys: ``required``, ``invalid``, ``missing``, ``empty``,
``invalid_image``
Using an ImageField requires that the `Python Imaging Library`_ is installed.
@ -1312,6 +1348,8 @@ When you use a ``FileField`` on a form, you must also remember to
* Normalizes to: A Python integer or long integer.
* Validates that the given value is an integer. Leading and trailing
whitespace is allowed, as in Python's ``int()`` function.
* Error message keys: ``required``, ``invalid``, ``max_value``,
``min_value``
Takes two optional arguments for validation, ``max_value`` and ``min_value``.
These control the range of values permitted in the field.
@ -1324,6 +1362,7 @@ These control the range of values permitted in the field.
* Normalizes to: A Unicode object.
* Validates that the given value is a valid IPv4 address, using a regular
expression.
* Error message keys: ``required``, ``invalid``
``MultipleChoiceField``
~~~~~~~~~~~~~~~~~~~~~~~
@ -1333,6 +1372,7 @@ These control the range of values permitted in the field.
* Normalizes to: A list of Unicode objects.
* Validates that every value in the given list of values exists in the list
of choices.
* Error message keys: ``required``, ``invalid_choice``, ``invalid_list``
Takes one extra argument, ``choices``, which is an iterable (e.g., a list or
tuple) of 2-tuples to use as choices for this field.
@ -1353,6 +1393,7 @@ tuple) of 2-tuples to use as choices for this field.
* Normalizes to: A Unicode object.
* Validates that the given value matches against a certain regular
expression.
* Error message keys: ``required``, ``invalid``
Takes one required argument, ``regex``, which is a regular expression specified
either as a string or a compiled regular expression object.
@ -1364,11 +1405,13 @@ Also takes the following optional arguments:
====================== =====================================================
``max_length`` Ensures the string has at most this many characters.
``min_length`` Ensures the string has at least this many characters.
``error_message`` Error message to return for failed validation. If no
message is provided, a generic error message will be
used.
====================== =====================================================
The optional argument ``error_message`` is also accepted for backwards
compatibility. The preferred way to provide an error message is to use the
``error_messages`` argument, passing a dictionary with ``'invalid'`` as a key
and the error message as the value.
``TimeField``
~~~~~~~~~~~~~
@ -1377,6 +1420,7 @@ Also takes the following optional arguments:
* Normalizes to: A Python ``datetime.time`` object.
* Validates that the given value is either a ``datetime.time`` or string
formatted in a particular time format.
* Error message keys: ``required``, ``invalid``
Takes one optional argument, ``input_formats``, which is a list of formats used
to attempt to convert a string to a valid ``datetime.time`` object.
@ -1393,6 +1437,7 @@ If no ``input_formats`` argument is provided, the default input formats are::
* Empty value: ``''`` (an empty string)
* Normalizes to: A Unicode object.
* Validates that the given value is a valid URL.
* Error message keys: ``required``, ``invalid``, ``invalid_link``
Takes the following optional arguments:

View File

@ -135,6 +135,23 @@ For example::
json_serializer = serializers.get_serializer("json")()
json_serializer.serialize(queryset, ensure_ascii=False, stream=response)
Django ships with a copy of simplejson_ in the source. Be aware, that if
you're using that for serializing directly that not all Django output can be
passed unmodified to simplejson. In particular, `lazy translation objects`_
need a `special encoder`_ written for them. Something like this will work::
from django.utils.functional import Promise
from django.utils.encoding import force_unicode
class LazyEncoder(simplejson.JSONEncoder):
def default(self, obj):
if isinstance(obj, Promise):
return force_unicode(obj)
return obj
.. _lazy translation objects: ../i18n/#lazy-translation
.. _special encoder: http://svn.red-bean.com/bob/simplejson/tags/simplejson-1.7/docs/index.html
Writing custom serializers
``````````````````````````

View File

@ -47,7 +47,7 @@ Initialization
==============
To activate sitemap generation on your Django site, add this line to your
URLconf_:
URLconf_::
(r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps})

View File

@ -299,6 +299,104 @@ it also defines the content that fills the hole in the *parent*. If there were
two similarly-named ``{% block %}`` tags in a template, that template's parent
wouldn't know which one of the blocks' content to use.
Automatic HTML escaping
=======================
**New in Django development version**
A very real problem when creating HTML (and other) output using templates and
variable substitution is the possibility of accidently inserting some variable
value that affects the resulting HTML. For example, a template fragment such as
::
Hello, {{ name }}.
seems like a harmless way to display the user's name. However, if you are
displaying data that the user entered directly and they had entered their name as ::
<script>alert('hello')</script>
this would always display a Javascript alert box when the page was loaded.
Similarly, if you were displaying some data generated by another process and it
contained a '<' symbol, you couldn't just dump this straight into your HTML,
because it would be treated as the start of an element. The effects of these
sorts of problems can vary from merely annoying to allowing exploits via `Cross
Site Scripting`_ (XSS) attacks.
.. _Cross Site Scripting: http://en.wikipedia.org/wiki/Cross-site_scripting
In order to provide some protection against these problems, Django
provides automatic (but controllable) HTML escaping for data coming from
tempate variables. Inside this tag, any data that comes from template
variables is examined to see if it contains one of the five HTML characters
(<, >, ', " and &) that often need escaping and those characters are converted
to their respective HTML entities. It causes no harm if a character is
converted to an entity when it doesn't need to be, so all five characters are
always converted.
Since some variables will contain data that is *intended* to be rendered
as HTML, template tag and filter writers can mark their output strings as
requiring no further escaping. For example, the ``unordered_list`` filter is
designed to return raw HTML and we want the template processor to simply
display the results as returned, without applying any escaping. That is taken
care of by the filter. The template author need do nothing special in that
case.
By default, automatic HTML escaping is always applied. However, sometimes you
will not want this to occur (for example, if you're using the templating
system to create an email). To control automatic escaping inside your template,
wrap the affected content in the ``autoescape`` tag, like so::
{% autoescape off %}
Hello {{ name }}
{% endautoescape %}
The auto-escaping tag passes its effect onto templates that extend the
current one as well as templates included via the ``include`` tag, just like
all block tags.
The ``autoescape`` tag takes either ``on`` or ``off`` as its argument. At times, you might want to force auto-escaping when it would otherwise be disabled. For example::
Auto-escaping is on by default. Hello {{ name }}
{% autoescape off %}
This will not be auto-escaped: {{ data }}.
Nor this: {{ other_data }}
{% autoescape on %}
Auto-escaping applies again, {{ name }}
{% endautoescape %}
{% endautoescape %}
For individual variables, the ``safe`` filter can also be used to indicate
that the contents should not be automatically escaped::
This will be escaped: {{ data }}
This will not be escaped: {{ data|safe }}
Think of *safe* as shorthand for *safe from further escaping* or *can be
safely interpreted as HTML*. In this example, if ``data`` contains ``'<a>'``,
the output will be::
This will be escaped: &lt;a&gt;
This will not be escaped: <a>
Generally, you won't need to worry about auto-escaping very much. View
developers and custom filter authors need to think about when their data
shouldn't be escaped and mark it appropriately. They are in a better position
to know when that should happen than the template author, so it is their
responsibility. By default, all output is escaped unless the template
processor is explicitly told otherwise.
You should also note that if you are trying to write a template that might be
used in situations where automatic escaping is enabled or disabled and you
don't know which (such as when your template is included in other templates),
you can safely write as if you were in an ``{% autoescape off %}`` situation.
Scatter ``escape`` filters around for any variables that need escaping. When
auto-escaping is on, these extra filters won't change the output -- any
variables that use the ``escape`` filter do not have further automatic
escaping applied to them.
Using the built-in reference
============================
@ -374,6 +472,24 @@ available, and what they do.
Built-in tag reference
----------------------
autoescape
~~~~~~~~~~
**New in Django development version**
Control the current auto-escaping behaviour. This tag takes either ``on`` or
``off`` as an argument and that determines whether auto-escaping is in effect
inside the block.
When auto-escaping is in effect, all variable content has HTML escaping applied
to it before placing the result into the output (but after any filters have
been applied). This is equivalent to manually applying the ``escape`` filter
attached to each variable.
The only exceptions are variables that are already marked as 'safe' from
escaping, either by the code that populated the variable, or because it has
the ``safe`` or ``escape`` filters applied.
block
~~~~~
@ -452,7 +568,7 @@ just like in variable syntax.
Sample usage::
{% filter escape|lower %}
{% filter force_escape|lower %}
This text will be HTML-escaped, and will appear in all lowercase.
{% endfilter %}
@ -740,7 +856,7 @@ Available format strings:
if they're zero and the special-case
strings 'midnight' and 'noon' if
appropriate. Proprietary extension.
r RFC 822 formatted date. ``'Thu, 21 Dec 2000 16:01:07 +0200'``
r RFC 2822 formatted date. ``'Thu, 21 Dec 2000 16:01:07 +0200'``
s Seconds, 2 digits with leading zeros. ``'00'`` to ``'59'``
S English ordinal suffix for day of the ``'st'``, ``'nd'``, ``'rd'`` or ``'th'``
month, 2 characters.
@ -1076,6 +1192,10 @@ Returns true if the value is divisible by the argument.
escape
~~~~~~
**New in Django development version:** The behaviour of this filter has
changed slightly in the development version (the affects are only applied
once, after all other filters).
Escapes a string's HTML. Specifically, it makes these replacements:
* ``"&"`` to ``"&amp;"``
@ -1084,6 +1204,16 @@ Escapes a string's HTML. Specifically, it makes these replacements:
* ``'"'`` (double quote) to ``'&quot;'``
* ``"'"`` (single quote) to ``'&#39;'``
The escaping is only applied when the string is output, so it does not matter
where in a chained sequence of filters you put ``escape``: it will always be
applied as though it were the last filter. If you want escaping to be applied
immediately, use the ``force_escape`` filter.
Applying ``escape`` to a variable that would normally have auto-escaping
applied to the result will only result in one round of escaping being done. So
it is safe to use this function even in auto-escaping environments. If you want
multiple escaping passes to be applied, use the ``force_escape`` filter.
filesizeformat
~~~~~~~~~~~~~~
@ -1106,25 +1236,50 @@ floatformat
When used without an argument, rounds a floating-point number to one decimal
place -- but only if there's a decimal part to be displayed. For example:
* ``36.123`` gets converted to ``36.1``
* ``36.15`` gets converted to ``36.2``
* ``36`` gets converted to ``36``
======== ======================= ======
value Template Output
======== ======================= ======
34.23234 {{ value|floatformat }} 34.2
34.00000 {{ value|floatformat }} 34
34.26000 {{ value|floatformat }} 34.3
======== ======================= ======
If used with a numeric integer argument, ``floatformat`` rounds a number to that
many decimal places. For example:
If used with a numeric integer argument, ``floatformat`` rounds a number to
that many decimal places. For example:
* ``36.1234`` with floatformat:3 gets converted to ``36.123``
* ``36`` with floatformat:4 gets converted to ``36.0000``
======== ========================= ======
value Template Output
======== ========================= ======
34.23234 {{ value|floatformat:3 }} 34.232
34.00000 {{ value|floatformat:3 }} 34.000
34.26000 {{ value|floatformat:3 }} 34.260
======== ========================= ======
If the argument passed to ``floatformat`` is negative, it will round a number to
that many decimal places -- but only if there's a decimal part to be displayed.
For example:
If the argument passed to ``floatformat`` is negative, it will round a number
to that many decimal places -- but only if there's a decimal part to be
displayed. For example:
* ``36.1234`` with floatformat:-3 gets converted to ``36.123``
* ``36`` with floatformat:-4 gets converted to ``36``
======== ============================ ======
value Template Output
======== ============================ ======
34.23234 {{ value|floatformat:"-3" }} 34.232
34.00000 {{ value|floatformat:"-3" }} 34
34.26000 {{ value|floatformat:"-3" }} 34.260
======== ============================ ======
Using ``floatformat`` with no argument is equivalent to using ``floatformat`` with
an argument of ``-1``.
Using ``floatformat`` with no argument is equivalent to using ``floatformat``
with an argument of ``-1``.
force_escape
~~~~~~~~~~~~
**New in Django development version**
Applies HTML escaping to a string (see the ``escape`` filter for details).
This filter is applied *immediately* and returns a new, escaped string. This
is useful in the rare cases where you need multiple escaping or want to apply
other filters to the escaped results. Normally, you want to use the ``escape``
filter.
get_digit
~~~~~~~~~
@ -1250,6 +1405,12 @@ Right-aligns the value in a field of a given width.
**Argument:** field size
safe
~~~~
Marks a string as not requiring further HTML escaping prior to output. When
autoescaping is off, this filter has no effect.
slice
~~~~~

View File

@ -722,6 +722,95 @@ decorator instead::
If you leave off the ``name`` argument, as in the second example above, Django
will use the function's name as the filter name.
Filters and auto-escaping
~~~~~~~~~~~~~~~~~~~~~~~~~
**New in Django development version**
When you are writing a custom filter, you need to give some thought to how
this filter will interact with Django's auto-escaping behaviour. Firstly, you
should realise that there are three types of strings that can be passed around
inside the template code:
* raw strings are the native Python ``str`` or ``unicode`` types. On
output, they are escaped if auto-escaping is in effect and presented
unchanged, otherwise.
* "safe" strings are strings that are safe from further escaping at output
time. Any necessary escaping has already been done. They are commonly used
for output that contains raw HTML that is intended to be intrepreted on the
client side.
Internally, these strings are of type ``SafeString`` or ``SafeUnicode``,
although they share a common base class in ``SafeData``, so you can test
for them using code like::
if isinstance(value, SafeData):
# Do something with the "safe" string.
* strings which are marked as "needing escaping" are *always* escaped on
output, regardless of whether they are in an ``autoescape`` block or not.
These strings are only escaped once, however, even if auto-escaping
applies. This type of string is internally represented by the types
``EscapeString`` and ``EscapeUnicode``. You will not normally need to worry
about these; they exist for the implementation of the ``escape`` filter.
Inside your filter, you will need to think about three areas in order to be
auto-escaping compliant:
1. If your filter returns a string that is ready for direct output (it should
be considered a "safe" string), you should call
``django.utils.safestring.mark_safe()`` on the result prior to returning.
This will turn the result into the appropriate ``SafeData`` type. This is
often the case when you are returning raw HTML, for example.
2. If your filter is given a "safe" string, is it guaranteed to return a
"safe" string? If so, set the ``is_safe`` attribute on the function to be
``True``. For example, a filter that replaced a word consisting only of
digits with the number spelt out in words is going to be
safe-string-preserving, since it cannot introduce any of the five dangerous
characters: <, >, ", ' or &. We can write::
@register.filter
def convert_to_words(value):
# ... implementation here ...
return result
convert_to_words.is_safe = True
Note that this filter does not return a universally safe result (it does
not return ``mark_safe(result)``) because if it is handed a raw string such
as '<a>', this will need further escaping in an auto-escape environment.
The ``is_safe`` attribute only talks about the the result when a safe
string is passed into the filter.
3. Will your filter behave differently depending upon whether auto-escaping
is currently in effect or not? This is normally a concern when you are
returning mixed content (HTML elements mixed with user-supplied content).
For example, the ``ordered_list`` filter that ships with Django needs to
know whether to escape its content or not. It will always return a safe
string. Since it returns raw HTML, we cannot apply escaping to the
result -- it needs to be done in-situ.
For these cases, the filter function needs to be told what the current
auto-escaping setting is. Set the ``needs_autoescape`` attribute on the
filter to ``True`` and have your function take an extra argument called
``autoescape`` with a default value of ``None``. When the filter is called,
the ``autoescape`` keyword argument will be ``True`` if auto-escaping is in
effect. For example, the ``unordered_list`` filter is written as::
def unordered_list(value, autoescape=None):
# ... lots of code here ...
return mark_safe(...)
unordered_list.is_safe = True
unordered_list.needs_autoescape = True
By default, both the ``is_safe`` and ``needs_autoescape`` attributes are
``False``. You do not need to specify them if ``False`` is an acceptable
value.
Writing custom template tags
----------------------------
@ -840,6 +929,43 @@ Ultimately, this decoupling of compilation and rendering results in an
efficient template system, because a template can render multiple context
without having to be parsed multiple times.
Auto-escaping considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The output from template tags is not automatically run through the
auto-escaping filters. However, there are still a couple of things you should
keep in mind when writing a template tag:
If the ``render()`` function of your template stores the result in a context
variable (rather than returning the result in a string), it should take care
to call ``mark_safe()`` if appropriate. When the variable is ultimately
rendered, it will be affected by the auto-escape setting in effect at the
time, so content that should be safe from further escaping needs to be marked
as such.
Also, if your template tag creates a new context for performing some
sub-rendering, you should be careful to set the auto-escape attribute to the
current context's value. The ``__init__`` method for the ``Context`` class
takes a parameter called ``autoescape`` that you can use for this purpose. For
example::
def render(self, context):
# ...
new_context = Context({'var': obj}, autoescape=context.autoescape)
# ... Do something with new_context ...
This is not a very common situation, but it is sometimes useful, particularly
if you are rendering a template yourself. For example::
def render(self, context):
t = template.load_template('small_fragment.html')
return t.render(Context({'var': obj}, autoescape=context.autoescape))
If we had neglected to pass in the current ``context.autoescape`` value to our
new ``Context`` in this example, the results would have *always* been
automatically escaped, which may not be the desired behaviour if the template
tag is used inside a ``{% autoescape off %}`` block.
Registering the tag
~~~~~~~~~~~~~~~~~~~

View File

@ -721,7 +721,6 @@ This means, instead of instantiating a ``Client`` in each test::
...you can just refer to ``self.client``, like so::
from django.test import TestCase
from django.test.client import Client
class SimpleTest(TestCase):
def test_details(self):

View File

@ -0,0 +1,106 @@
"""
Tests for field subclassing.
"""
from django.db import models
from django.utils.encoding import force_unicode
from django.core import serializers
class Small(object):
"""
A simple class to show that non-trivial Python objects can be used as
attributes.
"""
def __init__(self, first, second):
self.first, self.second = first, second
def __unicode__(self):
return u'%s%s' % (force_unicode(self.first), force_unicode(self.second))
def __str__(self):
return unicode(self).encode('utf-8')
class SmallField(models.Field):
"""
Turns the "Small" class into a Django field. Because of the similarities
with normal character fields and the fact that Small.__unicode__ does
something sensible, we don't need to implement a lot here.
"""
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 2
super(SmallField, self).__init__(*args, **kwargs)
def get_internal_type(self):
return 'CharField'
def to_python(self, value):
if isinstance(value, Small):
return value
return Small(value[0], value[1])
def get_db_prep_save(self, value):
return unicode(value)
def get_db_prep_lookup(self, lookup_type, value):
if lookup_type == 'exact':
return force_unicode(value)
if lookup_type == 'in':
return [force_unicode(v) for v in value]
if lookup_type == 'isnull':
return []
raise TypeError('Invalid lookup type: %r' % lookup_type)
def flatten_data(self, follow, obj=None):
return {self.attname: force_unicode(self._get_val_from_obj(obj))}
class MyModel(models.Model):
name = models.CharField(max_length=10)
data = SmallField('small field')
def __unicode__(self):
return force_unicode(self.name)
__test__ = {'API_TESTS': ur"""
# Creating a model with custom fields is done as per normal.
>>> s = Small(1, 2)
>>> print s
12
>>> m = MyModel(name='m', data=s)
>>> m.save()
# Custom fields still have normal field's attributes.
>>> m._meta.get_field('data').verbose_name
'small field'
# The m.data attribute has been initialised correctly. It's a Small object.
>>> m.data.first, m.data.second
(1, 2)
# The data loads back from the database correctly and 'data' has the right type.
>>> m1 = MyModel.objects.get(pk=m.pk)
>>> isinstance(m1.data, Small)
True
>>> print m1.data
12
# We can do normal filtering on the custom field (and will get an error when we
# use a lookup type that does not make sense).
>>> s1 = Small(1, 3)
>>> s2 = Small('a', 'b')
>>> MyModel.objects.filter(data__in=[s, s1, s2])
[<MyModel: m>]
>>> MyModel.objects.filter(data__lt=s)
Traceback (most recent call last):
...
TypeError: Invalid lookup type: 'lt'
# Serialization works, too.
>>> stream = serializers.serialize("json", MyModel.objects.all())
>>> stream
'[{"pk": 1, "model": "field_subclassing.mymodel", "fields": {"data": "12", "name": "m"}}]'
>>> obj = list(serializers.deserialize("json", stream))[0]
>>> obj.object == m
True
"""}

Some files were not shown because too many files have changed in this diff Show More