mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +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:
parent
1c7ad200c7
commit
f66821deae
2
AUTHORS
2
AUTHORS
@ -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
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -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
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -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 på 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
|
||||
|
Binary file not shown.
@ -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"
|
||||
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -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):
|
||||
|
@ -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))
|
||||
|
@ -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>
|
||||
|
@ -10,8 +10,8 @@
|
||||
{% block breadcrumbs %}{% if not is_popup %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="../../../">{% trans "Home" %}</a> ›
|
||||
<a href="../">{{ opts.verbose_name_plural|capfirst|escape }}</a> ›
|
||||
{% if add %}{% trans "Add" %} {{ opts.verbose_name|escape }}{% else %}{{ original|truncatewords:"18"|escape }}{% endif %}
|
||||
<a href="../">{{ opts.verbose_name_plural|capfirst }}</a> ›
|
||||
{% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
|
||||
</div>
|
||||
{% endif %}{% endblock %}
|
||||
{% block content %}<div id="content-main">
|
||||
|
@ -1,9 +1,9 @@
|
||||
{% if show %}
|
||||
<div class="xfull">
|
||||
<ul class="toplinks">
|
||||
{% if back %}<li class="date-back"><a href="{{ back.link }}">‹ {{ back.title|escape }}</a></li>{% endif %}
|
||||
{% if back %}<li class="date-back"><a href="{{ back.link }}">‹ {{ 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>
|
||||
|
@ -3,7 +3,7 @@
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="../../../../">{% trans "Home" %}</a> ›
|
||||
<a href="../../">{{ opts.verbose_name_plural|capfirst|escape }}</a> ›
|
||||
<a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> ›
|
||||
<a href="../">{{ object|escape|truncatewords:"18" }}</a> ›
|
||||
{% 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 %}
|
||||
|
@ -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 }} #{{ forloop.counter }}</h2>
|
||||
<h2>{{ bound_related_object.relation.opts.verbose_name|capfirst }} #{{ 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 %}
|
||||
|
@ -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>
|
||||
|
@ -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 %}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> › {{ title|escape }}</div>{% endblock %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> › {{ title }}</div>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs"><a href="../../../../">{% trans 'Home' %}</a> › <a href="../../">{{ module_name|escape }}</a> › <a href="../">{{ object|escape|truncatewords:"18" }}</a> › {% trans 'History' %}</div>
|
||||
<div class="breadcrumbs"><a href="../../../../">{% trans 'Home' %}</a> › <a href="../../">{{ module_name }}</a> › <a href="../">{{ object|truncatewords:"18" }}</a> › {% 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>
|
||||
|
@ -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 %} <a href="{{ show_all_url }}" class="showall">{% trans 'Show all' %}</a>{% endif %}
|
||||
</p>
|
||||
|
@ -8,16 +8,16 @@
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> › <a href="../../">Documentation</a> › <a href="../">Models</a> › {{ name|escape }}</div>{% endblock %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> › <a href="../../">Documentation</a> › <a href="../">Models</a> › {{ 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">
|
||||
|
@ -15,6 +15,6 @@
|
||||
{{ bound_field.original_value }}
|
||||
{% endif %}
|
||||
{% if bound_field.raw_id_admin %}
|
||||
{% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14"|escape }}</strong>{% endif %}
|
||||
{% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
@ -1,2 +1,2 @@
|
||||
{% if add %}{% include "widget/foreign.html" %}{% endif %}
|
||||
{% if change %}{% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14"|escape }}</strong>{% endif %}{% endif %}
|
||||
{% if change %}{% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}{% endif %}
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
})
|
||||
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 '?' + '&'.join([u'%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')
|
||||
return mark_safe('?' + '&'.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)
|
||||
|
@ -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)
|
||||
|
@ -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."
|
||||
|
@ -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)
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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 “quotes” and <code>code</code></p>""")
|
||||
else:
|
||||
self.assertEqual(rendered, textile_content)
|
||||
self.assertEqual(rendered, escape(textile_content))
|
||||
|
||||
def test_markdown(self):
|
||||
try:
|
||||
|
@ -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,
|
||||
|
@ -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 %}
|
||||
|
@ -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 %}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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__()
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
53
django/db/models/fields/subclassing.py
Normal file
53
django/db/models/fields/subclassing.py
Normal 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
|
||||
|
@ -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
34
django/http/utils.py
Normal 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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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."
|
||||
|
@ -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
|
||||
f.save_form_data(instance, cleaned_data[f.name])
|
||||
# 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
|
||||
# We're not committing. Add a method to the form to allow deferred
|
||||
# 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)
|
||||
return type(opts.object_name + 'Form', (form,),
|
||||
{'base_fields': base_fields, '_model': model, 'save': make_model_save(model, fields, 'created')})
|
||||
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')})
|
||||
|
||||
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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
@ -637,7 +648,7 @@ def resolve_variable(path, context):
|
||||
"""
|
||||
Returns the resolved variable, which may contain attribute syntax, within
|
||||
the given context.
|
||||
|
||||
|
||||
Deprecated; use the Variable class instead.
|
||||
"""
|
||||
return Variable(path).resolve(context)
|
||||
@ -647,7 +658,7 @@ class Variable(object):
|
||||
A template variable, resolvable against a given context. The variable may be
|
||||
a hard-coded string (if it begins and ends with single or double quote
|
||||
marks)::
|
||||
|
||||
|
||||
>>> c = {'article': {'section':'News'}}
|
||||
>>> Variable('article.section').resolve(c)
|
||||
u'News'
|
||||
@ -662,25 +673,25 @@ class Variable(object):
|
||||
|
||||
(The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, var):
|
||||
self.var = var
|
||||
self.literal = None
|
||||
self.lookups = None
|
||||
|
||||
|
||||
try:
|
||||
# First try to treat this variable as a number.
|
||||
#
|
||||
# Note that this could cause an OverflowError here that we're not
|
||||
# Note that this could cause an OverflowError here that we're not
|
||||
# catching. Since this should only happen at compile time, that's
|
||||
# probably OK.
|
||||
self.literal = float(var)
|
||||
|
||||
|
||||
# So it's a float... is it an int? If the original value contained a
|
||||
# dot or an "e" then it was a float, not an int.
|
||||
if '.' not in var and 'e' not in var.lower():
|
||||
self.literal = int(self.literal)
|
||||
|
||||
|
||||
# "2." is invalid
|
||||
if var.endswith('.'):
|
||||
raise ValueError
|
||||
@ -691,12 +702,12 @@ class Variable(object):
|
||||
# we're also dealing with a literal.
|
||||
if var[0] in "\"'" and var[0] == var[-1]:
|
||||
self.literal = var[1:-1]
|
||||
|
||||
|
||||
else:
|
||||
# Otherwise we'll set self.lookups so that resolve() knows we're
|
||||
# dealing with a bonafide variable
|
||||
self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
|
||||
|
||||
|
||||
def resolve(self, context):
|
||||
"""Resolve this variable against a given context."""
|
||||
if self.lookups is not None:
|
||||
@ -705,18 +716,18 @@ class Variable(object):
|
||||
else:
|
||||
# We're dealing with a literal, so it's already been "resolved"
|
||||
return self.literal
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %r>" % (self.__class__.__name__, self.var)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.var
|
||||
|
||||
def _resolve_lookup(self, context):
|
||||
"""
|
||||
Performs resolution of a real variable (i.e. not a literal) against the
|
||||
given context.
|
||||
|
||||
given context.
|
||||
|
||||
As indicated by the method's name, this method is an implementation
|
||||
detail and shouldn't be called by external code. Use Variable.resolve()
|
||||
instead.
|
||||
@ -757,14 +768,7 @@ class Variable(object):
|
||||
current = settings.TEMPLATE_STRING_IF_INVALID
|
||||
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__
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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 ``&`` entities"
|
||||
"""Replaces ampersands with ``&`` 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.
|
||||
@ -416,7 +519,7 @@ def unordered_list(value):
|
||||
sublist = ''
|
||||
sublist_item = None
|
||||
if isinstance(title, (list, tuple)):
|
||||
sublist_item = title
|
||||
sublist_item = title
|
||||
title = ''
|
||||
elif i < list_length - 1:
|
||||
next_item = list_[i+1]
|
||||
@ -424,25 +527,28 @@ def unordered_list(value):
|
||||
# The next item is a sub-list.
|
||||
sublist_item = next_item
|
||||
# We've processed the next item now too.
|
||||
i += 1
|
||||
i += 1
|
||||
if sublist_item:
|
||||
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)
|
||||
value, converted = convert_old_style_list(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)
|
||||
|
@ -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::
|
||||
@ -417,14 +455,14 @@ def cycle(parser, token):
|
||||
<tr class="{% cycle rowcolors %}">...</tr>
|
||||
|
||||
You can use any number of values, seperated by spaces. Commas can also
|
||||
be used to separate values; if a comma is used, the cycle values are
|
||||
be used to separate values; if a comma is used, the cycle values are
|
||||
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``::
|
||||
|
||||
@ -545,9 +589,9 @@ def do_for(parser, token):
|
||||
|
||||
You can loop over a list in reverse by using
|
||||
``{% for obj in list reversed %}``.
|
||||
|
||||
|
||||
You can also unpack multiple values from a two-dimensional array::
|
||||
|
||||
|
||||
{% for key,value in dict.items %}
|
||||
{{ key }}: {{ value }}
|
||||
{% endfor %}
|
||||
@ -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',))
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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'):
|
||||
|
@ -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,16 +28,28 @@ del x # Temporary variable
|
||||
|
||||
def escape(html):
|
||||
"Return the given HTML with ampersands, quotes and carets encoded."
|
||||
return force_unicode(html).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''')
|
||||
return mark_safe(force_unicode(html).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", '''))
|
||||
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)
|
||||
linebreaks = allow_lazy(linebreaks, unicode)
|
||||
|
||||
def strip_tags(value):
|
||||
"Return the given HTML with all tags stripped."
|
||||
@ -58,7 +71,7 @@ def fix_ampersands(value):
|
||||
return unencoded_ampersands_re.sub('&', 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'))):
|
||||
|
@ -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
124
django/utils/safestring.py
Normal 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))
|
||||
|
@ -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 %}
|
||||
{{ frame.lineno }}. {{ frame.context_line|escape }}<br/>
|
||||
{{ frame.lineno }}. {{ frame.context_line }}<br/>
|
||||
{% endif %}
|
||||
{% endfor %}<br/>
|
||||
{{ 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>
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 %}
|
||||
|
567
docs/custom_model_fields.txt
Normal file
567
docs/custom_model_fields.txt
Normal 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.
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
============
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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:
|
||||
|
||||
|
@ -47,14 +47,14 @@ This is useful if you want to serialize data directly to a file-like object
|
||||
Subset of fields
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
If you only want a subset of fields to be serialized, you can
|
||||
If you only want a subset of fields to be serialized, you can
|
||||
specify a ``fields`` argument to the serializer::
|
||||
|
||||
from django.core import serializers
|
||||
data = serializers.serialize('xml', SomeModel.objects.all(), fields=('name','size'))
|
||||
|
||||
In this example, only the ``name`` and ``size`` attributes of each model will
|
||||
be serialized.
|
||||
be serialized.
|
||||
|
||||
.. note::
|
||||
|
||||
@ -111,9 +111,9 @@ Django "ships" with a few included serializers:
|
||||
``python`` Translates to and from "simple" Python objects (lists, dicts,
|
||||
strings, etc.). Not really all that useful on its own, but
|
||||
used as a base for other serializers.
|
||||
|
||||
``yaml`` Serializes to YAML (Yet Another Markup Lanuage). This
|
||||
serializer is only available if PyYAML_ is installed.
|
||||
|
||||
``yaml`` Serializes to YAML (Yet Another Markup Lanuage). This
|
||||
serializer is only available if PyYAML_ is installed.
|
||||
========== ==============================================================
|
||||
|
||||
.. _json: http://json.org/
|
||||
@ -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
|
||||
``````````````````````````
|
||||
|
||||
|
@ -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})
|
||||
|
||||
|
@ -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: <a>
|
||||
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 ``"&"``
|
||||
@ -1084,6 +1204,16 @@ Escapes a string's HTML. Specifically, it makes these replacements:
|
||||
* ``'"'`` (double quote) to ``'"'``
|
||||
* ``"'"`` (single quote) to ``'''``
|
||||
|
||||
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
|
||||
~~~~~
|
||||
|
||||
|
@ -219,13 +219,13 @@ be replaced with the name of the invalid variable.
|
||||
|
||||
While ``TEMPLATE_STRING_IF_INVALID`` can be a useful debugging tool,
|
||||
it is a bad idea to turn it on as a 'development default'.
|
||||
|
||||
|
||||
Many templates, including those in the Admin site, rely upon the
|
||||
silence of the template system when a non-existent variable is
|
||||
encountered. If you assign a value other than ``''`` to
|
||||
``TEMPLATE_STRING_IF_INVALID``, you will experience rendering
|
||||
problems with these templates and sites.
|
||||
|
||||
|
||||
Generally, ``TEMPLATE_STRING_IF_INVALID`` should only be enabled
|
||||
in order to debug a specific template problem, then cleared
|
||||
once debugging is complete.
|
||||
@ -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
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -917,7 +1043,7 @@ current context, available in the ``render`` method::
|
||||
def __init__(self, date_to_be_formatted, format_string):
|
||||
self.date_to_be_formatted = date_to_be_formatted
|
||||
self.format_string = format_string
|
||||
|
||||
|
||||
def render(self, context):
|
||||
try:
|
||||
actual_date = resolve_variable(self.date_to_be_formatted, context)
|
||||
@ -934,26 +1060,26 @@ format it accordingly.
|
||||
``template.resolve_variable()`` is still available, but has been deprecated
|
||||
in favor of a new ``template.Variable`` class. Using this class will usually
|
||||
be more efficient than calling ``template.resolve_variable``
|
||||
|
||||
|
||||
To use the ``Variable`` class, simply instantiate it with the name of the
|
||||
variable to be resolved, and then call ``variable.resolve(context)``. So,
|
||||
in the development version, the above example would be more correctly
|
||||
written as:
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
|
||||
class FormatTimeNode(template.Node):
|
||||
def __init__(self, date_to_be_formatted, format_string):
|
||||
self.date_to_be_formatted = **Variable(date_to_be_formatted)**
|
||||
self.format_string = format_string
|
||||
|
||||
|
||||
def render(self, context):
|
||||
try:
|
||||
actual_date = **self.date_to_be_formatted.resolve(context)**
|
||||
return actual_date.strftime(self.format_string)
|
||||
except template.VariableDoesNotExist:
|
||||
return ''
|
||||
|
||||
|
||||
Changes are highlighted in bold.
|
||||
|
||||
Variable resolution will throw a ``VariableDoesNotExist`` exception if it cannot
|
||||
|
@ -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):
|
||||
|
0
tests/modeltests/field_subclassing/__init__.py
Normal file
0
tests/modeltests/field_subclassing/__init__.py
Normal file
106
tests/modeltests/field_subclassing/models.py
Normal file
106
tests/modeltests/field_subclassing/models.py
Normal 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
Loading…
x
Reference in New Issue
Block a user