mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +00:00
newforms-admin: Merged to [4502]
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@4503 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
af908e2e8d
commit
649423e81c
17
AUTHORS
17
AUTHORS
@ -1,7 +1,6 @@
|
|||||||
Django was originally created in late 2003 at World Online, the Web division
|
Django was originally created in late 2003 at World Online, the Web division
|
||||||
of the Lawrence Journal-World newspaper in Lawrence, Kansas.
|
of the Lawrence Journal-World newspaper in Lawrence, Kansas.
|
||||||
|
|
||||||
|
|
||||||
The PRIMARY AUTHORS are (and/or have been):
|
The PRIMARY AUTHORS are (and/or have been):
|
||||||
|
|
||||||
Adrian Holovaty <http://www.holovaty.com/>, who originally created Django with
|
Adrian Holovaty <http://www.holovaty.com/>, who originally created Django with
|
||||||
@ -45,6 +44,7 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
adurdin@gmail.com
|
adurdin@gmail.com
|
||||||
akaihola
|
akaihola
|
||||||
Andreas
|
Andreas
|
||||||
|
andy@jadedplanet.net
|
||||||
ant9000@netwise.it
|
ant9000@netwise.it
|
||||||
David Ascher <http://ascher.ca/>
|
David Ascher <http://ascher.ca/>
|
||||||
Arthur <avandorp@gmail.com>
|
Arthur <avandorp@gmail.com>
|
||||||
@ -53,10 +53,12 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Shannon -jj Behrens <http://jjinux.blogspot.com/>
|
Shannon -jj Behrens <http://jjinux.blogspot.com/>
|
||||||
Esdras Beleza <linux@esdrasbeleza.com>
|
Esdras Beleza <linux@esdrasbeleza.com>
|
||||||
James Bennett
|
James Bennett
|
||||||
|
Ben <afternoon@uk2.net>
|
||||||
Paul Bissex <http://e-scribe.com/>
|
Paul Bissex <http://e-scribe.com/>
|
||||||
Simon Blanchard
|
Simon Blanchard
|
||||||
Andrew Brehaut <http://brehaut.net/blog>
|
Andrew Brehaut <http://brehaut.net/blog>
|
||||||
andy@jadedplanet.net
|
brut.alll@gmail.com
|
||||||
|
Jonathan Buchanan <jonathan.buchanan@gmail.com>
|
||||||
Antonio Cavedoni <http://cavedoni.com/>
|
Antonio Cavedoni <http://cavedoni.com/>
|
||||||
C8E
|
C8E
|
||||||
Chris Chamberlin <dja@cdc.msbx.net>
|
Chris Chamberlin <dja@cdc.msbx.net>
|
||||||
@ -66,17 +68,19 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
crankycoder@gmail.com
|
crankycoder@gmail.com
|
||||||
Matt Croydon <http://www.postneo.com/>
|
Matt Croydon <http://www.postneo.com/>
|
||||||
dackze+django@gmail.com
|
dackze+django@gmail.com
|
||||||
|
Dirk Datzert <dummy@habmalnefrage.de>
|
||||||
Jonathan Daugherty (cygnus) <http://www.cprogrammer.org/>
|
Jonathan Daugherty (cygnus) <http://www.cprogrammer.org/>
|
||||||
|
dave@thebarproject.com
|
||||||
Jason Davies (Esaj) <http://www.jasondavies.com/>
|
Jason Davies (Esaj) <http://www.jasondavies.com/>
|
||||||
Alex Dedul
|
Alex Dedul
|
||||||
deric@monowerks.com
|
deric@monowerks.com
|
||||||
dne@mayonnaise.net
|
dne@mayonnaise.net
|
||||||
Maximillian Dornseif <md@hudora.de>
|
Maximillian Dornseif <md@hudora.de>
|
||||||
dummy@habmalnefrage.de
|
|
||||||
Jeremy Dunck <http://dunck.us/>
|
Jeremy Dunck <http://dunck.us/>
|
||||||
Andy Dustman <farcepest@gmail.com>
|
Andy Dustman <farcepest@gmail.com>
|
||||||
Clint Ecker
|
Clint Ecker
|
||||||
Enrico <rico.bl@gmail.com>
|
Enrico <rico.bl@gmail.com>
|
||||||
|
Marc Fargas <telenieko@telenieko.com>
|
||||||
favo@exoweb.net
|
favo@exoweb.net
|
||||||
Eric Floehr <eric@intellovations.com>
|
Eric Floehr <eric@intellovations.com>
|
||||||
gandalf@owca.info
|
gandalf@owca.info
|
||||||
@ -84,15 +88,17 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
martin.glueck@gmail.com
|
martin.glueck@gmail.com
|
||||||
Simon Greenhill <dev@simon.net.nz>
|
Simon Greenhill <dev@simon.net.nz>
|
||||||
Espen Grindhaug <http://grindhaug.org/>
|
Espen Grindhaug <http://grindhaug.org/>
|
||||||
|
Brian Harring <ferringb@gmail.com>
|
||||||
Brant Harris
|
Brant Harris
|
||||||
Hawkeye
|
Hawkeye
|
||||||
heckj@mac.com
|
Joe Heck <http://www.rhonabwy.com/wp/>
|
||||||
Joel Heenan <joelh-django@planetjoel.com>
|
Joel Heenan <joelh-django@planetjoel.com>
|
||||||
hipertracker@gmail.com
|
hipertracker@gmail.com
|
||||||
Ian Holsman <http://feh.holsman.net/>
|
Ian Holsman <http://feh.holsman.net/>
|
||||||
Kieran Holland <http://www.kieranholland.com>
|
Kieran Holland <http://www.kieranholland.com>
|
||||||
Robert Rock Howard <http://djangomojo.com/>
|
Robert Rock Howard <http://djangomojo.com/>
|
||||||
Jason Huggins <http://www.jrandolph.com/blog/>
|
Jason Huggins <http://www.jrandolph.com/blog/>
|
||||||
|
Tom Insam
|
||||||
Baurzhan Ismagulov <ibr@radix50.net>
|
Baurzhan Ismagulov <ibr@radix50.net>
|
||||||
jcrasta@gmail.com
|
jcrasta@gmail.com
|
||||||
Michael Josephson <http://www.sdjournal.com/>
|
Michael Josephson <http://www.sdjournal.com/>
|
||||||
@ -112,6 +118,7 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Jeong-Min Lee <falsetru@gmail.com>
|
Jeong-Min Lee <falsetru@gmail.com>
|
||||||
Christopher Lenz <http://www.cmlenz.net/>
|
Christopher Lenz <http://www.cmlenz.net/>
|
||||||
lerouxb@gmail.com
|
lerouxb@gmail.com
|
||||||
|
Waylan Limberg <waylan@gmail.com>
|
||||||
limodou
|
limodou
|
||||||
mattmcc
|
mattmcc
|
||||||
Martin Maney <http://www.chipy.org/Martin_Maney>
|
Martin Maney <http://www.chipy.org/Martin_Maney>
|
||||||
@ -162,8 +169,8 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Aaron Swartz <http://www.aaronsw.com/>
|
Aaron Swartz <http://www.aaronsw.com/>
|
||||||
Tyson Tate <tyson@fallingbullets.com>
|
Tyson Tate <tyson@fallingbullets.com>
|
||||||
Tom Tobin
|
Tom Tobin
|
||||||
Tom Insam
|
|
||||||
Joe Topjian <http://joe.terrarum.net/geek/code/python/django/>
|
Joe Topjian <http://joe.terrarum.net/geek/code/python/django/>
|
||||||
|
torne-django@wolfpuppy.org.uk
|
||||||
Karen Tracey <graybark@bellsouth.net>
|
Karen Tracey <graybark@bellsouth.net>
|
||||||
Makoto Tsuyuki <mtsuyuki@gmail.com>
|
Makoto Tsuyuki <mtsuyuki@gmail.com>
|
||||||
Amit Upadhyay
|
Amit Upadhyay
|
||||||
|
10
MANIFEST.in
10
MANIFEST.in
@ -1,8 +1,10 @@
|
|||||||
include AUTHORS
|
include AUTHORS
|
||||||
include INSTALL
|
include INSTALL
|
||||||
include LICENSE
|
include LICENSE
|
||||||
|
recursive-include docs *
|
||||||
|
recursive-include scripts *
|
||||||
recursive-include django/conf/locale *
|
recursive-include django/conf/locale *
|
||||||
recursive-include django/contrib/admin/templates
|
recursive-include django/contrib/admin/templates *
|
||||||
recursive-include django/contrib/admin/media
|
recursive-include django/contrib/admin/media *
|
||||||
recursive-include django/contrib/comments/templates
|
recursive-include django/contrib/comments/templates *
|
||||||
recursive-include django/contrib/sitemaps/templates
|
recursive-include django/contrib/sitemaps/templates *
|
||||||
|
@ -7,6 +7,7 @@ a list of all possible variables.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import time # Needed for Windows
|
||||||
from django.conf import global_settings
|
from django.conf import global_settings
|
||||||
|
|
||||||
ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
|
ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
|
||||||
@ -105,7 +106,9 @@ class Settings(object):
|
|||||||
new_installed_apps.append(app)
|
new_installed_apps.append(app)
|
||||||
self.INSTALLED_APPS = new_installed_apps
|
self.INSTALLED_APPS = new_installed_apps
|
||||||
|
|
||||||
# move the time zone info into os.environ
|
if hasattr(time, 'tzset'):
|
||||||
|
# Move the time zone info into os.environ. See ticket #2315 for why
|
||||||
|
# we don't do this unconditionally (breaks Windows).
|
||||||
os.environ['TZ'] = self.TIME_ZONE
|
os.environ['TZ'] = self.TIME_ZONE
|
||||||
|
|
||||||
def get_all_members(self):
|
def get_all_members(self):
|
||||||
|
@ -18,6 +18,8 @@ DATABASE_PORT = '' # Set to empty string for default. Not used with
|
|||||||
|
|
||||||
# Local time zone for this installation. All choices can be found here:
|
# Local time zone for this installation. All choices can be found here:
|
||||||
# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
|
# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
|
||||||
|
# If running in a Windows environment this must be set to the same as your
|
||||||
|
# system time zone.
|
||||||
TIME_ZONE = 'America/Chicago'
|
TIME_ZONE = 'America/Chicago'
|
||||||
|
|
||||||
# Language code for this installation. All choices can be found here:
|
# Language code for this installation. All choices can be found here:
|
||||||
|
@ -4,6 +4,7 @@ from django.contrib.sites.models import Site
|
|||||||
from django.template import Context, loader
|
from django.template import Context, loader
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django import oldforms
|
from django import oldforms
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
class UserCreationForm(oldforms.Manipulator):
|
class UserCreationForm(oldforms.Manipulator):
|
||||||
"A form that creates a user, with no privileges, from the given username and password."
|
"A form that creates a user, with no privileges, from the given username and password."
|
||||||
|
@ -78,6 +78,7 @@ class Feed(object):
|
|||||||
author_link = self.__get_dynamic_attr('author_link', obj),
|
author_link = self.__get_dynamic_attr('author_link', obj),
|
||||||
author_email = self.__get_dynamic_attr('author_email', obj),
|
author_email = self.__get_dynamic_attr('author_email', obj),
|
||||||
categories = self.__get_dynamic_attr('categories', obj),
|
categories = self.__get_dynamic_attr('categories', obj),
|
||||||
|
feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -116,5 +117,6 @@ class Feed(object):
|
|||||||
author_email = author_email,
|
author_email = author_email,
|
||||||
author_link = author_link,
|
author_link = author_link,
|
||||||
categories = self.__get_dynamic_attr('item_categories', item),
|
categories = self.__get_dynamic_attr('item_categories', item),
|
||||||
|
item_copyright = self.__get_dynamic_attr('item_copyright', item),
|
||||||
)
|
)
|
||||||
return feed
|
return feed
|
||||||
|
@ -50,4 +50,9 @@ class LazyDate(object):
|
|||||||
return (datetime.datetime.now() + self.delta).date()
|
return (datetime.datetime.now() + self.delta).date()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
|
if attr == 'delta':
|
||||||
|
# To fix ticket #3377. Note that normal accesses to LazyDate.delta
|
||||||
|
# (after construction) will still work, because they don't go
|
||||||
|
# through __getattr__). This is mainly needed for unpickling.
|
||||||
|
raise AttributeError
|
||||||
return getattr(self.__get_value__(), attr)
|
return getattr(self.__get_value__(), attr)
|
||||||
|
@ -167,17 +167,16 @@ class QuerySet(object):
|
|||||||
|
|
||||||
def iterator(self):
|
def iterator(self):
|
||||||
"Performs the SELECT database lookup of this QuerySet."
|
"Performs the SELECT database lookup of this QuerySet."
|
||||||
# self._select is a dictionary, and dictionaries' key order is
|
|
||||||
# undefined, so we convert it to a list of tuples.
|
|
||||||
extra_select = self._select.items()
|
|
||||||
|
|
||||||
cursor = connection.cursor()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
select, sql, params = self._get_sql_clause()
|
select, sql, params = self._get_sql_clause()
|
||||||
except EmptyResultSet:
|
except EmptyResultSet:
|
||||||
raise StopIteration
|
raise StopIteration
|
||||||
|
|
||||||
|
# self._select is a dictionary, and dictionaries' key order is
|
||||||
|
# undefined, so we convert it to a list of tuples.
|
||||||
|
extra_select = self._select.items()
|
||||||
|
|
||||||
|
cursor = connection.cursor()
|
||||||
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
|
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
|
||||||
fill_cache = self._select_related
|
fill_cache = self._select_related
|
||||||
index_end = len(self.model._meta.fields)
|
index_end = len(self.model._meta.fields)
|
||||||
@ -198,9 +197,12 @@ class QuerySet(object):
|
|||||||
"Performs a SELECT COUNT() and returns the number of records as an integer."
|
"Performs a SELECT COUNT() and returns the number of records as an integer."
|
||||||
counter = self._clone()
|
counter = self._clone()
|
||||||
counter._order_by = ()
|
counter._order_by = ()
|
||||||
|
counter._select_related = False
|
||||||
|
|
||||||
|
offset = counter._offset
|
||||||
|
limit = counter._limit
|
||||||
counter._offset = None
|
counter._offset = None
|
||||||
counter._limit = None
|
counter._limit = None
|
||||||
counter._select_related = False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
select, sql, params = counter._get_sql_clause()
|
select, sql, params = counter._get_sql_clause()
|
||||||
@ -214,7 +216,16 @@ class QuerySet(object):
|
|||||||
cursor.execute("SELECT COUNT(DISTINCT(%s))" % id_col + sql, params)
|
cursor.execute("SELECT COUNT(DISTINCT(%s))" % id_col + sql, params)
|
||||||
else:
|
else:
|
||||||
cursor.execute("SELECT COUNT(*)" + sql, params)
|
cursor.execute("SELECT COUNT(*)" + sql, params)
|
||||||
return cursor.fetchone()[0]
|
count = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
# Apply any offset and limit constraints manually, since using LIMIT or
|
||||||
|
# OFFSET in SQL doesn't change the output of COUNT.
|
||||||
|
if offset:
|
||||||
|
count = max(0, count - offset)
|
||||||
|
if limit:
|
||||||
|
count = min(limit, count)
|
||||||
|
|
||||||
|
return count
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
def get(self, *args, **kwargs):
|
||||||
"Performs the SELECT and returns a single object matching the given keyword arguments."
|
"Performs the SELECT and returns a single object matching the given keyword arguments."
|
||||||
@ -523,11 +534,18 @@ class QuerySet(object):
|
|||||||
return select, " ".join(sql), params
|
return select, " ".join(sql), params
|
||||||
|
|
||||||
class ValuesQuerySet(QuerySet):
|
class ValuesQuerySet(QuerySet):
|
||||||
def iterator(self):
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(ValuesQuerySet, self).__init__(*args, **kwargs)
|
||||||
# select_related and select aren't supported in values().
|
# select_related and select aren't supported in values().
|
||||||
self._select_related = False
|
self._select_related = False
|
||||||
self._select = {}
|
self._select = {}
|
||||||
|
|
||||||
|
def iterator(self):
|
||||||
|
try:
|
||||||
|
select, sql, params = self._get_sql_clause()
|
||||||
|
except EmptyResultSet:
|
||||||
|
raise StopIteration
|
||||||
|
|
||||||
# self._fields is a list of field names to fetch.
|
# self._fields is a list of field names to fetch.
|
||||||
if self._fields:
|
if self._fields:
|
||||||
columns = [self.model._meta.get_field(f, many_to_many=False).column for f in self._fields]
|
columns = [self.model._meta.get_field(f, many_to_many=False).column for f in self._fields]
|
||||||
@ -536,14 +554,8 @@ class ValuesQuerySet(QuerySet):
|
|||||||
columns = [f.column for f in self.model._meta.fields]
|
columns = [f.column for f in self.model._meta.fields]
|
||||||
field_names = [f.attname for f in self.model._meta.fields]
|
field_names = [f.attname for f in self.model._meta.fields]
|
||||||
|
|
||||||
cursor = connection.cursor()
|
|
||||||
|
|
||||||
try:
|
|
||||||
select, sql, params = self._get_sql_clause()
|
|
||||||
except EmptyResultSet:
|
|
||||||
raise StopIteration
|
|
||||||
|
|
||||||
select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns]
|
select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns]
|
||||||
|
cursor = connection.cursor()
|
||||||
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
|
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
|
||||||
while 1:
|
while 1:
|
||||||
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
|
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
|
||||||
@ -592,9 +604,6 @@ class EmptyQuerySet(QuerySet):
|
|||||||
super(EmptyQuerySet, self).__init__(model)
|
super(EmptyQuerySet, self).__init__(model)
|
||||||
self._result_cache = []
|
self._result_cache = []
|
||||||
|
|
||||||
def iterator(self):
|
|
||||||
raise StopIteration
|
|
||||||
|
|
||||||
def count(self):
|
def count(self):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@ -606,6 +615,9 @@ class EmptyQuerySet(QuerySet):
|
|||||||
c._result_cache = []
|
c._result_cache = []
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
def _get_sql_clause(self):
|
||||||
|
raise EmptyResultSet
|
||||||
|
|
||||||
class QOperator(object):
|
class QOperator(object):
|
||||||
"Base class for QAnd and QOr"
|
"Base class for QAnd and QOr"
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
@ -881,7 +893,13 @@ def lookup_inner(path, lookup_type, value, opts, table, column):
|
|||||||
new_opts = field.rel.to._meta
|
new_opts = field.rel.to._meta
|
||||||
new_column = new_opts.pk.column
|
new_column = new_opts.pk.column
|
||||||
join_column = field.column
|
join_column = field.column
|
||||||
|
raise FieldFound
|
||||||
|
elif path:
|
||||||
|
# For regular fields, if there are still items on the path,
|
||||||
|
# an error has been made. We munge "name" so that the error
|
||||||
|
# properly identifies the cause of the problem.
|
||||||
|
name += LOOKUP_SEPARATOR + path[0]
|
||||||
|
else:
|
||||||
raise FieldFound
|
raise FieldFound
|
||||||
|
|
||||||
except FieldFound: # Match found, loop has been shortcut.
|
except FieldFound: # Match found, loop has been shortcut.
|
||||||
|
@ -67,6 +67,9 @@ class RelatedObject(object):
|
|||||||
# A one-to-one relationship, so just return the single related
|
# A one-to-one relationship, so just return the single related
|
||||||
# object
|
# object
|
||||||
return [attr]
|
return [attr]
|
||||||
|
else:
|
||||||
|
if self.field.rel.min_num_in_admin:
|
||||||
|
return [None] * max(self.field.rel.num_in_admin, self.field.rel.min_num_in_admin)
|
||||||
else:
|
else:
|
||||||
return [None] * self.field.rel.num_in_admin
|
return [None] * self.field.rel.num_in_admin
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ class HttpResponse(object):
|
|||||||
self._charset = settings.DEFAULT_CHARSET
|
self._charset = settings.DEFAULT_CHARSET
|
||||||
if not mimetype:
|
if not mimetype:
|
||||||
mimetype = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, settings.DEFAULT_CHARSET)
|
mimetype = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, settings.DEFAULT_CHARSET)
|
||||||
if hasattr(content, '__iter__'):
|
if not isinstance(content, basestring) and hasattr(content, '__iter__'):
|
||||||
self._container = content
|
self._container = content
|
||||||
self._is_string = False
|
self._is_string = False
|
||||||
else:
|
else:
|
||||||
|
@ -356,7 +356,7 @@ class ChoiceField(Field):
|
|||||||
return value
|
return value
|
||||||
valid_values = set([str(k) for k, v in self.choices])
|
valid_values = set([str(k) for k, v in self.choices])
|
||||||
if value not in valid_values:
|
if value not in valid_values:
|
||||||
raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % value)
|
raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.'))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
class MultipleChoiceField(ChoiceField):
|
class MultipleChoiceField(ChoiceField):
|
||||||
|
@ -117,7 +117,13 @@ class TemplateDoesNotExist(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class VariableDoesNotExist(Exception):
|
class VariableDoesNotExist(Exception):
|
||||||
pass
|
|
||||||
|
def __init__(self, msg, params=()):
|
||||||
|
self.msg = msg
|
||||||
|
self.params = params
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.msg % self.params
|
||||||
|
|
||||||
class InvalidTemplateLibrary(Exception):
|
class InvalidTemplateLibrary(Exception):
|
||||||
pass
|
pass
|
||||||
@ -660,7 +666,7 @@ def resolve_variable(path, context):
|
|||||||
try: # list-index lookup
|
try: # list-index lookup
|
||||||
current = current[int(bits[0])]
|
current = current[int(bits[0])]
|
||||||
except (IndexError, ValueError, KeyError):
|
except (IndexError, ValueError, KeyError):
|
||||||
raise VariableDoesNotExist, "Failed lookup for key [%s] in %r" % (bits[0], current) # missing attribute
|
raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bits[0], current)) # missing attribute
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
if getattr(e, 'silent_variable_failure', False):
|
if getattr(e, 'silent_variable_failure', False):
|
||||||
current = settings.TEMPLATE_STRING_IF_INVALID
|
current = settings.TEMPLATE_STRING_IF_INVALID
|
||||||
|
@ -49,6 +49,9 @@ class Context(object):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return self.has_key(key)
|
||||||
|
|
||||||
def get(self, key, otherwise=None):
|
def get(self, key, otherwise=None):
|
||||||
for d in self.dicts:
|
for d in self.dicts:
|
||||||
if d.has_key(key):
|
if d.has_key(key):
|
||||||
|
@ -119,6 +119,21 @@ def truncatewords(value, arg):
|
|||||||
value = str(value)
|
value = str(value)
|
||||||
return truncate_words(value, length)
|
return truncate_words(value, length)
|
||||||
|
|
||||||
|
def truncatewords_html(value, arg):
|
||||||
|
"""
|
||||||
|
Truncates HTML after a certain number of words
|
||||||
|
|
||||||
|
Argument: Number of words to truncate after
|
||||||
|
"""
|
||||||
|
from django.utils.text import truncate_html_words
|
||||||
|
try:
|
||||||
|
length = int(arg)
|
||||||
|
except ValueError: # invalid literal for int()
|
||||||
|
return value # Fail silently.
|
||||||
|
if not isinstance(value, basestring):
|
||||||
|
value = str(value)
|
||||||
|
return truncate_html_words(value, length)
|
||||||
|
|
||||||
def upper(value):
|
def upper(value):
|
||||||
"Converts a string into all uppercase"
|
"Converts a string into all uppercase"
|
||||||
return value.upper()
|
return value.upper()
|
||||||
@ -126,6 +141,8 @@ def upper(value):
|
|||||||
def urlencode(value):
|
def urlencode(value):
|
||||||
"Escapes a value for use in a URL"
|
"Escapes a value for use in a URL"
|
||||||
import urllib
|
import urllib
|
||||||
|
if not isinstance(value, basestring):
|
||||||
|
value = str(value)
|
||||||
return urllib.quote(value)
|
return urllib.quote(value)
|
||||||
|
|
||||||
def urlize(value):
|
def urlize(value):
|
||||||
@ -534,6 +551,7 @@ register.filter(timesince)
|
|||||||
register.filter(timeuntil)
|
register.filter(timeuntil)
|
||||||
register.filter(title)
|
register.filter(title)
|
||||||
register.filter(truncatewords)
|
register.filter(truncatewords)
|
||||||
|
register.filter(truncatewords_html)
|
||||||
register.filter(unordered_list)
|
register.filter(unordered_list)
|
||||||
register.filter(upper)
|
register.filter(upper)
|
||||||
register.filter(urlencode)
|
register.filter(urlencode)
|
||||||
|
@ -315,6 +315,25 @@ class TemplateTagNode(Node):
|
|||||||
def render(self, context):
|
def render(self, context):
|
||||||
return self.mapping.get(self.tagtype, '')
|
return self.mapping.get(self.tagtype, '')
|
||||||
|
|
||||||
|
class URLNode(Node):
|
||||||
|
def __init__(self, view_name, args, kwargs):
|
||||||
|
self.view_name = view_name
|
||||||
|
self.args = args
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||||
|
args = [arg.resolve(context) for arg in self.args]
|
||||||
|
kwargs = dict([(k, 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)
|
||||||
|
except NoReverseMatch:
|
||||||
|
return ''
|
||||||
|
|
||||||
class WidthRatioNode(Node):
|
class WidthRatioNode(Node):
|
||||||
def __init__(self, val_expr, max_expr, max_width):
|
def __init__(self, val_expr, max_expr, max_width):
|
||||||
self.val_expr = val_expr
|
self.val_expr = val_expr
|
||||||
@ -868,6 +887,50 @@ def templatetag(parser, token):
|
|||||||
return TemplateTagNode(tag)
|
return TemplateTagNode(tag)
|
||||||
templatetag = register.tag(templatetag)
|
templatetag = register.tag(templatetag)
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
{% url path.to.some_view arg1,arg2,name1=value1 %}
|
||||||
|
|
||||||
|
The first argument is a path to a view. It can be an absolute python path
|
||||||
|
or just ``app_name.view_name`` without the project name if the view is
|
||||||
|
located inside the project. Other arguments are comma-separated values
|
||||||
|
that will be filled in place of positional and keyword arguments in the
|
||||||
|
URL. All arguments for the URL should be present.
|
||||||
|
|
||||||
|
For example if you have a view ``app_name.client`` taking client's id and
|
||||||
|
the corresponding line in a urlconf looks like this:
|
||||||
|
|
||||||
|
('^client/(\d+)/$', 'app_name.client')
|
||||||
|
|
||||||
|
and this app's urlconf is included into the project's urlconf under some
|
||||||
|
path:
|
||||||
|
|
||||||
|
('^clients/', include('project_name.app_name.urls'))
|
||||||
|
|
||||||
|
then in a template you can create a link for a certain client like this:
|
||||||
|
|
||||||
|
{% url app_name.client client.id %}
|
||||||
|
|
||||||
|
The URL will look like ``/clients/client/123/``.
|
||||||
|
"""
|
||||||
|
bits = token.contents.split(' ', 2)
|
||||||
|
if len(bits) < 2:
|
||||||
|
raise TemplateSyntaxError, "'%s' takes at least one argument (path to a view)" % bits[0]
|
||||||
|
args = []
|
||||||
|
kwargs = {}
|
||||||
|
if len(bits) > 2:
|
||||||
|
for arg in bits[2].split(','):
|
||||||
|
if '=' in arg:
|
||||||
|
k, v = arg.split('=', 1)
|
||||||
|
kwargs[k] = parser.compile_filter(v)
|
||||||
|
else:
|
||||||
|
args.append(parser.compile_filter(arg))
|
||||||
|
return URLNode(bits[1], args, kwargs)
|
||||||
|
url = register.tag(url)
|
||||||
|
|
||||||
#@register.tag
|
#@register.tag
|
||||||
def widthratio(parser, token):
|
def widthratio(parser, token):
|
||||||
"""
|
"""
|
||||||
|
@ -129,7 +129,7 @@ def do_block(parser, token):
|
|||||||
parser.__loaded_blocks.append(block_name)
|
parser.__loaded_blocks.append(block_name)
|
||||||
except AttributeError: # parser.__loaded_blocks isn't a list yet
|
except AttributeError: # parser.__loaded_blocks isn't a list yet
|
||||||
parser.__loaded_blocks = [block_name]
|
parser.__loaded_blocks = [block_name]
|
||||||
nodelist = parser.parse(('endblock',))
|
nodelist = parser.parse(('endblock', 'endblock %s' % block_name))
|
||||||
parser.delete_first_token()
|
parser.delete_first_token()
|
||||||
return BlockNode(block_name, nodelist)
|
return BlockNode(block_name, nodelist)
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
import sys
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
|
from django.conf import settings
|
||||||
from django.core.handlers.base import BaseHandler
|
from django.core.handlers.base import BaseHandler
|
||||||
from django.core.handlers.wsgi import WSGIRequest
|
from django.core.handlers.wsgi import WSGIRequest
|
||||||
|
from django.core.signals import got_request_exception
|
||||||
from django.dispatch import dispatcher
|
from django.dispatch import dispatcher
|
||||||
from django.http import urlencode, SimpleCookie
|
from django.http import urlencode, SimpleCookie
|
||||||
from django.test import signals
|
from django.test import signals
|
||||||
@ -97,7 +100,16 @@ class Client:
|
|||||||
def __init__(self, **defaults):
|
def __init__(self, **defaults):
|
||||||
self.handler = ClientHandler()
|
self.handler = ClientHandler()
|
||||||
self.defaults = defaults
|
self.defaults = defaults
|
||||||
self.cookie = SimpleCookie()
|
self.cookies = SimpleCookie()
|
||||||
|
self.session = {}
|
||||||
|
self.exc_info = None
|
||||||
|
|
||||||
|
def store_exc_info(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Utility method that can be used to store exceptions when they are
|
||||||
|
generated by a view.
|
||||||
|
"""
|
||||||
|
self.exc_info = sys.exc_info()
|
||||||
|
|
||||||
def request(self, **request):
|
def request(self, **request):
|
||||||
"""
|
"""
|
||||||
@ -108,7 +120,7 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
environ = {
|
environ = {
|
||||||
'HTTP_COOKIE': self.cookie,
|
'HTTP_COOKIE': self.cookies,
|
||||||
'PATH_INFO': '/',
|
'PATH_INFO': '/',
|
||||||
'QUERY_STRING': '',
|
'QUERY_STRING': '',
|
||||||
'REQUEST_METHOD': 'GET',
|
'REQUEST_METHOD': 'GET',
|
||||||
@ -126,6 +138,9 @@ class Client:
|
|||||||
on_template_render = curry(store_rendered_templates, data)
|
on_template_render = curry(store_rendered_templates, data)
|
||||||
dispatcher.connect(on_template_render, signal=signals.template_rendered)
|
dispatcher.connect(on_template_render, signal=signals.template_rendered)
|
||||||
|
|
||||||
|
# Capture exceptions created by the handler
|
||||||
|
dispatcher.connect(self.store_exc_info, signal=got_request_exception)
|
||||||
|
|
||||||
response = self.handler(environ)
|
response = self.handler(environ)
|
||||||
|
|
||||||
# Add any rendered template detail to the response
|
# Add any rendered template detail to the response
|
||||||
@ -140,8 +155,19 @@ class Client:
|
|||||||
else:
|
else:
|
||||||
setattr(response, detail, None)
|
setattr(response, detail, None)
|
||||||
|
|
||||||
|
# Look for a signalled exception and reraise it
|
||||||
|
if self.exc_info:
|
||||||
|
raise self.exc_info[1], None, self.exc_info[2]
|
||||||
|
|
||||||
|
# Update persistent cookie and session data
|
||||||
if response.cookies:
|
if response.cookies:
|
||||||
self.cookie.update(response.cookies)
|
self.cookies.update(response.cookies)
|
||||||
|
|
||||||
|
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
||||||
|
from django.contrib.sessions.middleware import SessionWrapper
|
||||||
|
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
|
||||||
|
if cookie:
|
||||||
|
self.session = SessionWrapper(cookie.value)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class SyndicationFeed(object):
|
|||||||
"Base class for all syndication feeds. Subclasses should provide write()"
|
"Base class for all syndication feeds. Subclasses should provide write()"
|
||||||
def __init__(self, title, link, description, language=None, author_email=None,
|
def __init__(self, title, link, description, language=None, author_email=None,
|
||||||
author_name=None, author_link=None, subtitle=None, categories=None,
|
author_name=None, author_link=None, subtitle=None, categories=None,
|
||||||
feed_url=None):
|
feed_url=None, feed_copyright=None):
|
||||||
self.feed = {
|
self.feed = {
|
||||||
'title': title,
|
'title': title,
|
||||||
'link': link,
|
'link': link,
|
||||||
@ -52,12 +52,13 @@ class SyndicationFeed(object):
|
|||||||
'subtitle': subtitle,
|
'subtitle': subtitle,
|
||||||
'categories': categories or (),
|
'categories': categories or (),
|
||||||
'feed_url': feed_url,
|
'feed_url': feed_url,
|
||||||
|
'feed_copyright': feed_copyright,
|
||||||
}
|
}
|
||||||
self.items = []
|
self.items = []
|
||||||
|
|
||||||
def add_item(self, title, link, description, author_email=None,
|
def add_item(self, title, link, description, author_email=None,
|
||||||
author_name=None, author_link=None, pubdate=None, comments=None,
|
author_name=None, author_link=None, pubdate=None, comments=None,
|
||||||
unique_id=None, enclosure=None, categories=()):
|
unique_id=None, enclosure=None, categories=(), item_copyright=None):
|
||||||
"""
|
"""
|
||||||
Adds an item to the feed. All args are expected to be Python Unicode
|
Adds an item to the feed. All args are expected to be Python Unicode
|
||||||
objects except pubdate, which is a datetime.datetime object, and
|
objects except pubdate, which is a datetime.datetime object, and
|
||||||
@ -75,6 +76,7 @@ class SyndicationFeed(object):
|
|||||||
'unique_id': unique_id,
|
'unique_id': unique_id,
|
||||||
'enclosure': enclosure,
|
'enclosure': enclosure,
|
||||||
'categories': categories or (),
|
'categories': categories or (),
|
||||||
|
'item_copyright': item_copyright,
|
||||||
})
|
})
|
||||||
|
|
||||||
def num_items(self):
|
def num_items(self):
|
||||||
@ -128,6 +130,8 @@ class RssFeed(SyndicationFeed):
|
|||||||
handler.addQuickElement(u"language", self.feed['language'])
|
handler.addQuickElement(u"language", self.feed['language'])
|
||||||
for cat in self.feed['categories']:
|
for cat in self.feed['categories']:
|
||||||
handler.addQuickElement(u"category", cat)
|
handler.addQuickElement(u"category", cat)
|
||||||
|
if self.feed['feed_copyright'] is not None:
|
||||||
|
handler.addQuickElement(u"copyright", self.feed['feed_copyright'])
|
||||||
self.write_items(handler)
|
self.write_items(handler)
|
||||||
self.endChannelElement(handler)
|
self.endChannelElement(handler)
|
||||||
handler.endElement(u"rss")
|
handler.endElement(u"rss")
|
||||||
@ -212,6 +216,8 @@ class Atom1Feed(SyndicationFeed):
|
|||||||
handler.addQuickElement(u"subtitle", self.feed['subtitle'])
|
handler.addQuickElement(u"subtitle", self.feed['subtitle'])
|
||||||
for cat in self.feed['categories']:
|
for cat in self.feed['categories']:
|
||||||
handler.addQuickElement(u"category", "", {u"term": cat})
|
handler.addQuickElement(u"category", "", {u"term": cat})
|
||||||
|
if self.feed['feed_copyright'] is not None:
|
||||||
|
handler.addQuickElement(u"rights", self.feed['feed_copyright'])
|
||||||
self.write_items(handler)
|
self.write_items(handler)
|
||||||
handler.endElement(u"feed")
|
handler.endElement(u"feed")
|
||||||
|
|
||||||
@ -252,10 +258,14 @@ class Atom1Feed(SyndicationFeed):
|
|||||||
u"length": item['enclosure'].length,
|
u"length": item['enclosure'].length,
|
||||||
u"type": item['enclosure'].mime_type})
|
u"type": item['enclosure'].mime_type})
|
||||||
|
|
||||||
# Categories:
|
# Categories.
|
||||||
for cat in item['categories']:
|
for cat in item['categories']:
|
||||||
handler.addQuickElement(u"category", u"", {u"term": cat})
|
handler.addQuickElement(u"category", u"", {u"term": cat})
|
||||||
|
|
||||||
|
# Rights.
|
||||||
|
if item['item_copyright'] is not None:
|
||||||
|
handler.addQuickElement(u"rights", item['item_copyright'])
|
||||||
|
|
||||||
handler.endElement(u"entry")
|
handler.endElement(u"entry")
|
||||||
|
|
||||||
# This isolates the decision of what the system default is, so calling code can
|
# This isolates the decision of what the system default is, so calling code can
|
||||||
|
@ -41,6 +41,66 @@ def truncate_words(s, num):
|
|||||||
words.append('...')
|
words.append('...')
|
||||||
return ' '.join(words)
|
return ' '.join(words)
|
||||||
|
|
||||||
|
def truncate_html_words(s, num):
|
||||||
|
"""
|
||||||
|
Truncates html to a certain number of words (not counting tags and comments).
|
||||||
|
Closes opened tags if they were correctly closed in the given html.
|
||||||
|
"""
|
||||||
|
length = int(num)
|
||||||
|
if length <= 0:
|
||||||
|
return ''
|
||||||
|
html4_singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input')
|
||||||
|
# Set up regular expressions
|
||||||
|
re_words = re.compile(r'&.*?;|<.*?>|([A-Za-z0-9][\w-]*)')
|
||||||
|
re_tag = re.compile(r'<(/)?([^ ]+?)(?: (/)| .*?)?>')
|
||||||
|
# Count non-HTML words and keep note of open tags
|
||||||
|
pos = 0
|
||||||
|
ellipsis_pos = 0
|
||||||
|
words = 0
|
||||||
|
open_tags = []
|
||||||
|
while words <= length:
|
||||||
|
m = re_words.search(s, pos)
|
||||||
|
if not m:
|
||||||
|
# Checked through whole string
|
||||||
|
break
|
||||||
|
pos = m.end(0)
|
||||||
|
if m.group(1):
|
||||||
|
# It's an actual non-HTML word
|
||||||
|
words += 1
|
||||||
|
if words == length:
|
||||||
|
ellipsis_pos = pos
|
||||||
|
continue
|
||||||
|
# Check for tag
|
||||||
|
tag = re_tag.match(m.group(0))
|
||||||
|
if not tag or ellipsis_pos:
|
||||||
|
# Don't worry about non tags or tags after our truncate point
|
||||||
|
continue
|
||||||
|
closing_tag, tagname, self_closing = tag.groups()
|
||||||
|
tagname = tagname.lower() # Element names are always case-insensitive
|
||||||
|
if self_closing or tagname in html4_singlets:
|
||||||
|
pass
|
||||||
|
elif closing_tag:
|
||||||
|
# Check for match in open tags list
|
||||||
|
try:
|
||||||
|
i = open_tags.index(tagname)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# SGML: An end tag closes, back to the matching start tag, all unclosed intervening start tags with omitted end tags
|
||||||
|
open_tags = open_tags[i+1:]
|
||||||
|
else:
|
||||||
|
# Add it to the start of the open tags list
|
||||||
|
open_tags.insert(0, tagname)
|
||||||
|
if words <= length:
|
||||||
|
# Don't try to close tags if we don't need to truncate
|
||||||
|
return s
|
||||||
|
out = s[:ellipsis_pos] + ' ...'
|
||||||
|
# Close any tags still open
|
||||||
|
for tag in open_tags:
|
||||||
|
out += '</%s>' % tag
|
||||||
|
# Return string
|
||||||
|
return out
|
||||||
|
|
||||||
def get_valid_filename(s):
|
def get_valid_filename(s):
|
||||||
"""
|
"""
|
||||||
Returns the given string converted to a string that can be used for a clean
|
Returns the given string converted to a string that can be used for a clean
|
||||||
|
@ -484,6 +484,29 @@ Alternatively, you can use a symlink called ``django`` that points to the
|
|||||||
location of the branch's ``django`` package. If you want to switch back, just
|
location of the branch's ``django`` package. If you want to switch back, just
|
||||||
change the symlink to point to the old code.
|
change the symlink to point to the old code.
|
||||||
|
|
||||||
|
A third option is to use a `path file`_ (``<something>.pth``) which should
|
||||||
|
work on all systems (including Windows, which doesn't have symlinks
|
||||||
|
available). First, make sure there are no files, directories or symlinks named
|
||||||
|
``django`` in your ``site-packages`` directory. Then create a text file named
|
||||||
|
``django.pth`` and save it to your ``site-packages`` directory. That file
|
||||||
|
should contain a path to your copy of Django on a single line and optional
|
||||||
|
comments. Here is an example that points to multiple branches. Just uncomment
|
||||||
|
the line for the branch you want to use ('Trunk' in this example) and make
|
||||||
|
sure all other lines are commented::
|
||||||
|
|
||||||
|
# Trunk is a svn checkout of:
|
||||||
|
# http://code.djangoproject.com/svn/django/trunk/
|
||||||
|
#
|
||||||
|
/path/to/trunk
|
||||||
|
|
||||||
|
# <branch> is a svn checkout of:
|
||||||
|
# http://code.djangoproject.com/svn/django/branches/<branch>/
|
||||||
|
#
|
||||||
|
#/path/to/<branch>
|
||||||
|
|
||||||
|
# On windows a path may look like this:
|
||||||
|
# C:/path/to/<branch>
|
||||||
|
|
||||||
If you're using Django 0.95 or earlier and installed it using
|
If you're using Django 0.95 or earlier and installed it using
|
||||||
``python setup.py install``, you'll have a directory called something like
|
``python setup.py install``, you'll have a directory called something like
|
||||||
``Django-0.95-py2.4.egg`` instead of ``django``. In this case, edit the file
|
``Django-0.95-py2.4.egg`` instead of ``django``. In this case, edit the file
|
||||||
@ -491,6 +514,8 @@ If you're using Django 0.95 or earlier and installed it using
|
|||||||
file. Then copy the branch's version of the ``django`` directory into
|
file. Then copy the branch's version of the ``django`` directory into
|
||||||
``site-packages``.
|
``site-packages``.
|
||||||
|
|
||||||
|
.. _path file: http://docs.python.org/lib/module-site.html
|
||||||
|
|
||||||
Official releases
|
Official releases
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
@ -17,7 +17,12 @@ two things for you before delegating to ``django-admin.py``:
|
|||||||
The ``django-admin.py`` script should be on your system path if you installed
|
The ``django-admin.py`` script should be on your system path if you installed
|
||||||
Django via its ``setup.py`` utility. If it's not on your path, you can find it in
|
Django via its ``setup.py`` utility. If it's not on your path, you can find it in
|
||||||
``site-packages/django/bin`` within your Python installation. Consider
|
``site-packages/django/bin`` within your Python installation. Consider
|
||||||
symlinking to it from some place on your path, such as ``/usr/local/bin``.
|
symlinking it from some place on your path, such as ``/usr/local/bin``.
|
||||||
|
|
||||||
|
For Windows users, who do not have symlinking functionality available, you
|
||||||
|
can copy ``django-admin.py`` to a location on your existing path or edit the
|
||||||
|
``PATH`` settings (under ``Settings - Control Panel - System - Advanced - Environment...``)
|
||||||
|
to point to its installed location.
|
||||||
|
|
||||||
Generally, when working on a single Django project, it's easier to use
|
Generally, when working on a single Django project, it's easier to use
|
||||||
``manage.py``. Use ``django-admin.py`` with ``DJANGO_SETTINGS_MODULE``, or the
|
``manage.py``. Use ``django-admin.py`` with ``DJANGO_SETTINGS_MODULE``, or the
|
||||||
|
@ -173,10 +173,10 @@ creation view that takes validation into account::
|
|||||||
|
|
||||||
# Check for validation errors
|
# Check for validation errors
|
||||||
errors = manipulator.get_validation_errors(new_data)
|
errors = manipulator.get_validation_errors(new_data)
|
||||||
|
manipulator.do_html2python(new_data)
|
||||||
if errors:
|
if errors:
|
||||||
return render_to_response('places/errors.html', {'errors': errors})
|
return render_to_response('places/errors.html', {'errors': errors})
|
||||||
else:
|
else:
|
||||||
manipulator.do_html2python(new_data)
|
|
||||||
new_place = manipulator.save(new_data)
|
new_place = manipulator.save(new_data)
|
||||||
return HttpResponse("Place created: %s" % new_place)
|
return HttpResponse("Place created: %s" % new_place)
|
||||||
|
|
||||||
@ -229,10 +229,10 @@ Below is the finished view::
|
|||||||
|
|
||||||
# Check for errors.
|
# Check for errors.
|
||||||
errors = manipulator.get_validation_errors(new_data)
|
errors = manipulator.get_validation_errors(new_data)
|
||||||
|
manipulator.do_html2python(new_data)
|
||||||
|
|
||||||
if not errors:
|
if not errors:
|
||||||
# No errors. This means we can save the data!
|
# No errors. This means we can save the data!
|
||||||
manipulator.do_html2python(new_data)
|
|
||||||
new_place = manipulator.save(new_data)
|
new_place = manipulator.save(new_data)
|
||||||
|
|
||||||
# Redirect to the object's "edit" page. Always use a redirect
|
# Redirect to the object's "edit" page. Always use a redirect
|
||||||
@ -324,8 +324,8 @@ about editing an existing one? It's shockingly similar to creating a new one::
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
new_data = request.POST.copy()
|
new_data = request.POST.copy()
|
||||||
errors = manipulator.get_validation_errors(new_data)
|
errors = manipulator.get_validation_errors(new_data)
|
||||||
if not errors:
|
|
||||||
manipulator.do_html2python(new_data)
|
manipulator.do_html2python(new_data)
|
||||||
|
if not errors:
|
||||||
manipulator.save(new_data)
|
manipulator.save(new_data)
|
||||||
|
|
||||||
# Do a post-after-redirect so that reload works, etc.
|
# Do a post-after-redirect so that reload works, etc.
|
||||||
@ -406,8 +406,8 @@ Here's a simple function that might drive the above form::
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
new_data = request.POST.copy()
|
new_data = request.POST.copy()
|
||||||
errors = manipulator.get_validation_errors(new_data)
|
errors = manipulator.get_validation_errors(new_data)
|
||||||
if not errors:
|
|
||||||
manipulator.do_html2python(new_data)
|
manipulator.do_html2python(new_data)
|
||||||
|
if not errors:
|
||||||
|
|
||||||
# Send e-mail using new_data here...
|
# Send e-mail using new_data here...
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ Test your installation by importing it in the Python interactive interpreter::
|
|||||||
|
|
||||||
If that command doesn't raise any errors, the installation worked.
|
If that command doesn't raise any errors, the installation worked.
|
||||||
|
|
||||||
.. _user guide: http://www.reportlab.org/rsrc/userguide.pdf
|
.. _user guide: http://www.reportlab.com/docs/userguide.pdf
|
||||||
|
|
||||||
Write your view
|
Write your view
|
||||||
===============
|
===============
|
||||||
|
@ -827,6 +827,11 @@ manual configuration option (see below), Django will *not* touch the ``TZ``
|
|||||||
environment variable, and it'll be up to you to ensure your processes are
|
environment variable, and it'll be up to you to ensure your processes are
|
||||||
running in the correct environment.
|
running in the correct environment.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Django cannot reliably use alternate time zones in a Windows environment.
|
||||||
|
If you're running Django on Windows, this variable must be set to match the
|
||||||
|
system timezone.
|
||||||
|
|
||||||
URL_VALIDATOR_USER_AGENT
|
URL_VALIDATOR_USER_AGENT
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
|
@ -478,6 +478,22 @@ This example illustrates all possible attributes and methods for a ``Feed`` clas
|
|||||||
|
|
||||||
categories = ("python", "django") # Hard-coded list of categories.
|
categories = ("python", "django") # Hard-coded list of categories.
|
||||||
|
|
||||||
|
# COPYRIGHT NOTICE -- One of the following three is optional. The
|
||||||
|
# framework looks for them in this order.
|
||||||
|
|
||||||
|
def copyright(self, obj):
|
||||||
|
"""
|
||||||
|
Takes the object returned by get_object() and returns the feed's
|
||||||
|
copyright notice as a normal Python string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def copyright(self):
|
||||||
|
"""
|
||||||
|
Returns the feed's copyright notice as a normal Python string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice.
|
||||||
|
|
||||||
# ITEMS -- One of the following three is required. The framework looks
|
# ITEMS -- One of the following three is required. The framework looks
|
||||||
# for them in this order.
|
# for them in this order.
|
||||||
|
|
||||||
@ -659,6 +675,23 @@ This example illustrates all possible attributes and methods for a ``Feed`` clas
|
|||||||
|
|
||||||
item_categories = ("python", "django") # Hard-coded categories.
|
item_categories = ("python", "django") # Hard-coded categories.
|
||||||
|
|
||||||
|
# ITEM COPYRIGHT NOTICE (only applicable to Atom feeds) -- One of the
|
||||||
|
# following three is optional. The framework looks for them in this
|
||||||
|
# order.
|
||||||
|
|
||||||
|
def item_copyright(self, obj):
|
||||||
|
"""
|
||||||
|
Takes an item, as returned by items(), and returns the item's
|
||||||
|
copyright notice as a normal Python string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def item_copyright(self):
|
||||||
|
"""
|
||||||
|
Returns the copyright notice for every item in the feed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
item_copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice.
|
||||||
|
|
||||||
|
|
||||||
The low-level framework
|
The low-level framework
|
||||||
=======================
|
=======================
|
||||||
|
@ -253,6 +253,16 @@ Here are some tips for working with inheritance:
|
|||||||
if you want to add to the contents of a parent block instead of
|
if you want to add to the contents of a parent block instead of
|
||||||
completely overriding it.
|
completely overriding it.
|
||||||
|
|
||||||
|
* **New in Django development version:** For extra readability, you can
|
||||||
|
optionally give a *name* to your ``{% endblock %}`` tag. For example::
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
...
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
In larger templates, this technique helps you see which ``{% block %}``
|
||||||
|
tags are being closed.
|
||||||
|
|
||||||
Finally, note that you can't define multiple ``{% block %}`` tags with the same
|
Finally, note that you can't define multiple ``{% block %}`` tags with the same
|
||||||
name in the same template. This limitation exists because a block tag works in
|
name in the same template. This limitation exists because a block tag works in
|
||||||
"both" directions. That is, a block tag doesn't just provide a hole to fill --
|
"both" directions. That is, a block tag doesn't just provide a hole to fill --
|
||||||
@ -819,6 +829,40 @@ The argument tells which template bit to output:
|
|||||||
|
|
||||||
Note: ``opencomment`` and ``closecomment`` are new in the Django development version.
|
Note: ``opencomment`` and ``closecomment`` are new in the Django development version.
|
||||||
|
|
||||||
|
url
|
||||||
|
~~~
|
||||||
|
|
||||||
|
**New in Django development version**
|
||||||
|
|
||||||
|
**Note that the syntax for this tag may change in the future, as we make it more robust.**
|
||||||
|
|
||||||
|
Returns an absolute URL (i.e., a URL without the domain name) matching a given
|
||||||
|
view function and optional parameters. This is a way to output links without
|
||||||
|
violating the DRY principle by having to hard-code URLs in your templates::
|
||||||
|
|
||||||
|
{% url path.to.some_view arg1,arg2,name1=value1 %}
|
||||||
|
|
||||||
|
The first argument is a path to a view function in the format
|
||||||
|
``package.package.module.function``. Additional arguments are optional and
|
||||||
|
should be comma-separated values that will be used as positional and keyword
|
||||||
|
arguments in the URL. All arguments required by the URLconf should be present.
|
||||||
|
|
||||||
|
For example, suppose you have a view, ``app_name.client``, whose URLconf takes
|
||||||
|
a client ID. The URLconf line might look like this::
|
||||||
|
|
||||||
|
('^client/(\d+)/$', 'app_name.client')
|
||||||
|
|
||||||
|
If this app's URLconf is included into the project's URLconf under a path
|
||||||
|
such as this::
|
||||||
|
|
||||||
|
('^clients/', include('project_name.app_name.urls'))
|
||||||
|
|
||||||
|
...then, in a template, you can create a link to this view like this::
|
||||||
|
|
||||||
|
{% url app_name.client client.id %}
|
||||||
|
|
||||||
|
The template tag will output the string ``/clients/client/123/``.
|
||||||
|
|
||||||
widthratio
|
widthratio
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
@ -1133,6 +1177,16 @@ Truncates a string after a certain number of words.
|
|||||||
|
|
||||||
**Argument:** Number of words to truncate after
|
**Argument:** Number of words to truncate after
|
||||||
|
|
||||||
|
truncatewords_html
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Similar to ``truncatewords``, except that it is aware of HTML tags. Any tags
|
||||||
|
that are opened in the string and not closed before the truncation point, are
|
||||||
|
closed immediately after the truncation.
|
||||||
|
|
||||||
|
This is less efficient than ``truncatewords``, so should only be used when it
|
||||||
|
is being passed HTML text.
|
||||||
|
|
||||||
unordered_list
|
unordered_list
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -801,6 +801,70 @@ Python 2.4 and above::
|
|||||||
If you leave off the ``name`` argument, as in the second example above, Django
|
If you leave off the ``name`` argument, as in the second example above, Django
|
||||||
will use the function's name as the tag name.
|
will use the function's name as the tag name.
|
||||||
|
|
||||||
|
Passing template variables to the tag
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Although you can pass any number of arguments to a template tag using
|
||||||
|
``token.split_contents()``, the arguments are all unpacked as
|
||||||
|
string literals. A little more work is required in order to dynamic content (a
|
||||||
|
template variable) to a template tag as an argument.
|
||||||
|
|
||||||
|
While the previous examples have formatted the current time into a string and
|
||||||
|
returned the string, suppose you wanted to pass in a ``DateTimeField`` from an
|
||||||
|
object and have the template tag format that date-time::
|
||||||
|
|
||||||
|
<p>This post was last updated at {% format_time blog_entry.date_updated "%Y-%m-%d %I:%M %p" %}.</p>
|
||||||
|
|
||||||
|
Initially, ``token.split_contents()`` will return three values:
|
||||||
|
|
||||||
|
1. The tag name ``format_time``.
|
||||||
|
2. The string "blog_entry.date_updated" (without the surrounding quotes).
|
||||||
|
3. The formatting string "%Y-%m-%d %I:%M %p". The return value from
|
||||||
|
``split_contents()`` will include the leading and trailing quotes for
|
||||||
|
string literals like this.
|
||||||
|
|
||||||
|
Now your tag should begin to look like this::
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
def do_format_time(parser, token):
|
||||||
|
try:
|
||||||
|
# split_contents() knows not to split quoted strings.
|
||||||
|
tag_name, date_to_be_formatted, format_string = token.split_contents()
|
||||||
|
except ValueError:
|
||||||
|
raise template.TemplateSyntaxError, "%r tag requires exactly two arguments" % token.contents[0]
|
||||||
|
if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
|
||||||
|
raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
|
||||||
|
return FormatTimeNode(date_to_be_formatted, format_string[1:-1])
|
||||||
|
|
||||||
|
You also have to change the renderer to retrieve the actual contents of the
|
||||||
|
``date_updated`` property of the ``blog_entry`` object. This can be
|
||||||
|
accomplished by using the ``resolve_variable()`` function in
|
||||||
|
``django.template``. You pass ``resolve_variable()`` the variable name and the
|
||||||
|
current context, available in the ``render`` method::
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
from django.template import resolve_variable
|
||||||
|
import datetime
|
||||||
|
class FormatTimeNode(template.Node):
|
||||||
|
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)
|
||||||
|
return actual_date.strftime(self.format_string)
|
||||||
|
except VariableDoesNotExist:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
``resolve_variable`` will try to resolve ``blog_entry.date_updated`` and then
|
||||||
|
format it accordingly.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The ``resolve_variable()`` function will throw a ``VariableDoesNotExist``
|
||||||
|
exception if it cannot resolve the string passed to it in the current
|
||||||
|
context of the page.
|
||||||
|
|
||||||
Shortcut for simple tags
|
Shortcut for simple tags
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -198,11 +198,6 @@ used as test conditions.
|
|||||||
.. _Twill: http://twill.idyll.org/
|
.. _Twill: http://twill.idyll.org/
|
||||||
.. _Selenium: http://www.openqa.org/selenium/
|
.. _Selenium: http://www.openqa.org/selenium/
|
||||||
|
|
||||||
The Test Client is stateful; if a cookie is returned as part of a response,
|
|
||||||
that cookie is provided as part of the next request issued to that Client
|
|
||||||
instance. Expiry policies for these cookies are not followed; if you want
|
|
||||||
a cookie to expire, either delete it manually from ``client.cookies``, or
|
|
||||||
create a new Client instance (which will effectively delete all cookies).
|
|
||||||
|
|
||||||
Making requests
|
Making requests
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~
|
||||||
@ -296,6 +291,44 @@ for testing purposes:
|
|||||||
|
|
||||||
.. _RFC2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
.. _RFC2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
||||||
|
|
||||||
|
Exceptions
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
If you point the Test Client at a view that raises an exception, that exception
|
||||||
|
will be visible in the test case. You can then use a standard ``try...catch``
|
||||||
|
block, or ``unittest.TestCase.assertRaises()`` to test for exceptions.
|
||||||
|
|
||||||
|
The only exceptions that are not visible in a Test Case are ``Http404``,
|
||||||
|
``PermissionDenied`` and ``SystemExit``. Django catches these exceptions
|
||||||
|
internally and converts them into the appropriate HTTP responses codes.
|
||||||
|
|
||||||
|
Persistent state
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The Test Client is stateful; if a cookie is returned as part of a response,
|
||||||
|
that cookie is provided as part of the next request issued by that Client
|
||||||
|
instance. Expiry policies for these cookies are not followed; if you want
|
||||||
|
a cookie to expire, either delete it manually or create a new Client
|
||||||
|
instance (which will effectively delete all cookies).
|
||||||
|
|
||||||
|
There are two properties of the Test Client which are used to store persistent
|
||||||
|
state information. If necessary, these properties can be interrogated as
|
||||||
|
part of a test condition.
|
||||||
|
|
||||||
|
=============== ==========================================================
|
||||||
|
Property Description
|
||||||
|
=============== ==========================================================
|
||||||
|
``cookies`` A Python ``SimpleCookie`` object, containing the current
|
||||||
|
values of all the client cookies.
|
||||||
|
|
||||||
|
``session`` A dictionary-like object containing session information.
|
||||||
|
See the `session documentation`_ for full details.
|
||||||
|
|
||||||
|
.. _`session documentation`: ../sessions/
|
||||||
|
|
||||||
|
Example
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
The following is a simple unit test using the Test Client::
|
The following is a simple unit test using the Test Client::
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
19
scripts/rpm-install.sh
Normal file
19
scripts/rpm-install.sh
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#! /bin/sh
|
||||||
|
#
|
||||||
|
# this file is *inserted* into the install section of the generated
|
||||||
|
# spec file
|
||||||
|
#
|
||||||
|
|
||||||
|
# this is, what dist.py normally does
|
||||||
|
python setup.py install --root=${RPM_BUILD_ROOT} --record="INSTALLED_FILES"
|
||||||
|
|
||||||
|
for i in `cat INSTALLED_FILES`; do
|
||||||
|
if [ -f ${RPM_BUILD_ROOT}/$i ]; then
|
||||||
|
echo $i >>FILES
|
||||||
|
fi
|
||||||
|
if [ -d ${RPM_BUILD_ROOT}/$i ]; then
|
||||||
|
echo %dir $i >>DIRS
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
cat DIRS FILES >INSTALLED_FILES
|
@ -1,3 +1,4 @@
|
|||||||
[bdist_rpm]
|
[bdist_rpm]
|
||||||
doc_files = docs/*.txt
|
doc_files = docs/*.txt
|
||||||
|
install-script = scripts/rpm-install.sh
|
||||||
|
|
||||||
|
@ -58,6 +58,17 @@ Article 4
|
|||||||
>>> Article.objects.filter(headline__startswith='Blah blah').count()
|
>>> Article.objects.filter(headline__startswith='Blah blah').count()
|
||||||
0L
|
0L
|
||||||
|
|
||||||
|
# count() should respect sliced query sets.
|
||||||
|
>>> articles = Article.objects.all()
|
||||||
|
>>> articles.count()
|
||||||
|
7L
|
||||||
|
>>> articles[:4].count()
|
||||||
|
4
|
||||||
|
>>> articles[1:100].count()
|
||||||
|
6L
|
||||||
|
>>> articles[10:100].count()
|
||||||
|
0
|
||||||
|
|
||||||
# Date and date/time lookups can also be done with strings.
|
# Date and date/time lookups can also be done with strings.
|
||||||
>>> Article.objects.filter(pub_date__exact='2005-07-27 00:00:00').count()
|
>>> Article.objects.filter(pub_date__exact='2005-07-27 00:00:00').count()
|
||||||
3L
|
3L
|
||||||
@ -198,6 +209,8 @@ DoesNotExist: Article matching query does not exist.
|
|||||||
[]
|
[]
|
||||||
>>> Article.objects.none().count()
|
>>> Article.objects.none().count()
|
||||||
0
|
0
|
||||||
|
>>> [article for article in Article.objects.none().iterator()]
|
||||||
|
[]
|
||||||
|
|
||||||
# using __in with an empty list should return an empty query set
|
# using __in with an empty list should return an empty query set
|
||||||
>>> Article.objects.filter(id__in=[])
|
>>> Article.objects.filter(id__in=[])
|
||||||
@ -206,4 +219,15 @@ DoesNotExist: Article matching query does not exist.
|
|||||||
>>> Article.objects.exclude(id__in=[])
|
>>> Article.objects.exclude(id__in=[])
|
||||||
[<Article: Article with \ backslash>, <Article: Article% with percent sign>, <Article: Article_ with underscore>, <Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>]
|
[<Article: Article with \ backslash>, <Article: Article% with percent sign>, <Article: Article_ with underscore>, <Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>]
|
||||||
|
|
||||||
|
# Programming errors are pointed out with nice error messages
|
||||||
|
>>> Article.objects.filter(pub_date_year='2005').count()
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
TypeError: Cannot resolve keyword 'pub_date_year' into field
|
||||||
|
|
||||||
|
>>> Article.objects.filter(headline__starts='Article')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
TypeError: Cannot resolve keyword 'headline__starts' into field
|
||||||
|
|
||||||
"""}
|
"""}
|
||||||
|
@ -99,3 +99,29 @@ class ClientTest(unittest.TestCase):
|
|||||||
|
|
||||||
response = self.client.login('/test_client/login_protected_view/', 'otheruser', 'nopassword')
|
response = self.client.login('/test_client/login_protected_view/', 'otheruser', 'nopassword')
|
||||||
self.assertFalse(response)
|
self.assertFalse(response)
|
||||||
|
|
||||||
|
def test_session_modifying_view(self):
|
||||||
|
"Request a page that modifies the session"
|
||||||
|
# Session value isn't set initially
|
||||||
|
try:
|
||||||
|
self.client.session['tobacconist']
|
||||||
|
self.fail("Shouldn't have a session value")
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
from django.contrib.sessions.models import Session
|
||||||
|
response = self.client.post('/test_client/session_view/')
|
||||||
|
|
||||||
|
# Check that the session was modified
|
||||||
|
self.assertEquals(self.client.session['tobacconist'], 'hovercraft')
|
||||||
|
|
||||||
|
def test_view_with_exception(self):
|
||||||
|
"Request a page that is known to throw an error"
|
||||||
|
self.assertRaises(KeyError, self.client.get, "/test_client/broken_view/")
|
||||||
|
|
||||||
|
#Try the same assertion, a different way
|
||||||
|
try:
|
||||||
|
self.client.get('/test_client/broken_view/')
|
||||||
|
self.fail('Should raise an error')
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
@ -6,4 +6,6 @@ urlpatterns = patterns('',
|
|||||||
(r'^post_view/$', views.post_view),
|
(r'^post_view/$', views.post_view),
|
||||||
(r'^redirect_view/$', views.redirect_view),
|
(r'^redirect_view/$', views.redirect_view),
|
||||||
(r'^login_protected_view/$', views.login_protected_view),
|
(r'^login_protected_view/$', views.login_protected_view),
|
||||||
|
(r'^session_view/$', views.session_view),
|
||||||
|
(r'^broken_view/$', views.broken_view)
|
||||||
)
|
)
|
||||||
|
@ -33,3 +33,16 @@ def login_protected_view(request):
|
|||||||
|
|
||||||
return HttpResponse(t.render(c))
|
return HttpResponse(t.render(c))
|
||||||
login_protected_view = login_required(login_protected_view)
|
login_protected_view = login_required(login_protected_view)
|
||||||
|
|
||||||
|
def session_view(request):
|
||||||
|
"A view that modifies the session"
|
||||||
|
request.session['tobacconist'] = 'hovercraft'
|
||||||
|
|
||||||
|
t = Template('This is a view that modifies the session.',
|
||||||
|
name='Session Modifying View Template')
|
||||||
|
c = Context()
|
||||||
|
return HttpResponse(t.render(c))
|
||||||
|
|
||||||
|
def broken_view(request):
|
||||||
|
"""A view which just raises an exception, simulating a broken view."""
|
||||||
|
raise KeyError("Oops! Looks like you wrote some bad code.")
|
||||||
|
@ -87,6 +87,20 @@ u'\xeb'
|
|||||||
>>> truncatewords('A sentence with a few words in it', 'not a number')
|
>>> truncatewords('A sentence with a few words in it', 'not a number')
|
||||||
'A sentence with a few words in it'
|
'A sentence with a few words in it'
|
||||||
|
|
||||||
|
>>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 0)
|
||||||
|
''
|
||||||
|
|
||||||
|
>>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 2)
|
||||||
|
'<p>one <a href="#">two ...</a></p>'
|
||||||
|
|
||||||
|
>>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 4)
|
||||||
|
'<p>one <a href="#">two - three <br>four ...</a></p>'
|
||||||
|
|
||||||
|
>>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 5)
|
||||||
|
'<p>one <a href="#">two - three <br>four</a> five</p>'
|
||||||
|
|
||||||
|
>>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 100)
|
||||||
|
'<p>one <a href="#">two - three <br>four</a> five</p>'
|
||||||
|
|
||||||
>>> upper('Mixed case input')
|
>>> upper('Mixed case input')
|
||||||
'MIXED CASE INPUT'
|
'MIXED CASE INPUT'
|
||||||
@ -97,6 +111,8 @@ u'\xcb'
|
|||||||
|
|
||||||
>>> urlencode('jack & jill')
|
>>> urlencode('jack & jill')
|
||||||
'jack%20%26%20jill'
|
'jack%20%26%20jill'
|
||||||
|
>>> urlencode(1)
|
||||||
|
'1'
|
||||||
|
|
||||||
|
|
||||||
>>> urlizetrunc('http://short.com/', 20)
|
>>> urlizetrunc('http://short.com/', 20)
|
||||||
|
@ -1493,7 +1493,7 @@ u'1'
|
|||||||
>>> f.clean('3')
|
>>> f.clean('3')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
|
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
|
||||||
|
|
||||||
>>> f = ChoiceField(choices=[('1', '1'), ('2', '2')], required=False)
|
>>> f = ChoiceField(choices=[('1', '1'), ('2', '2')], required=False)
|
||||||
>>> f.clean('')
|
>>> f.clean('')
|
||||||
@ -1507,7 +1507,7 @@ u'1'
|
|||||||
>>> f.clean('3')
|
>>> f.clean('3')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
|
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
|
||||||
|
|
||||||
>>> f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')])
|
>>> f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')])
|
||||||
>>> f.clean('J')
|
>>> f.clean('J')
|
||||||
@ -1515,7 +1515,7 @@ u'J'
|
|||||||
>>> f.clean('John')
|
>>> f.clean('John')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValidationError: [u'Select a valid choice. John is not one of the available choices.']
|
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
|
||||||
|
|
||||||
# NullBooleanField ############################################################
|
# NullBooleanField ############################################################
|
||||||
|
|
||||||
|
@ -390,6 +390,21 @@ class Templates(unittest.TestCase):
|
|||||||
'include03': ('{% include template_name %}', {'template_name': 'basic-syntax02', 'headline': 'Included'}, "Included"),
|
'include03': ('{% include template_name %}', {'template_name': 'basic-syntax02', 'headline': 'Included'}, "Included"),
|
||||||
'include04': ('a{% include "nonexistent" %}b', {}, "ab"),
|
'include04': ('a{% include "nonexistent" %}b', {}, "ab"),
|
||||||
|
|
||||||
|
### NAMED ENDBLOCKS #######################################################
|
||||||
|
|
||||||
|
# Basic test
|
||||||
|
'namedendblocks01': ("1{% block first %}_{% block second %}2{% endblock second %}_{% endblock first %}3", {}, '1_2_3'),
|
||||||
|
|
||||||
|
# Unbalanced blocks
|
||||||
|
'namedendblocks02': ("1{% block first %}_{% block second %}2{% endblock first %}_{% endblock second %}3", {}, template.TemplateSyntaxError),
|
||||||
|
'namedendblocks03': ("1{% block first %}_{% block second %}2{% endblock %}_{% endblock second %}3", {}, template.TemplateSyntaxError),
|
||||||
|
'namedendblocks04': ("1{% block first %}_{% block second %}2{% endblock second %}_{% endblock third %}3", {}, template.TemplateSyntaxError),
|
||||||
|
'namedendblocks05': ("1{% block first %}_{% block second %}2{% endblock first %}", {}, template.TemplateSyntaxError),
|
||||||
|
|
||||||
|
# Mixed named and unnamed endblocks
|
||||||
|
'namedendblocks06': ("1{% block first %}_{% block second %}2{% endblock %}_{% endblock first %}3", {}, '1_2_3'),
|
||||||
|
'namedendblocks07': ("1{% block first %}_{% block second %}2{% endblock second %}_{% endblock %}3", {}, '1_2_3'),
|
||||||
|
|
||||||
### INHERITANCE ###########################################################
|
### INHERITANCE ###########################################################
|
||||||
|
|
||||||
# Standard template with no inheritance
|
# Standard template with no inheritance
|
||||||
@ -630,6 +645,17 @@ class Templates(unittest.TestCase):
|
|||||||
# Compare to a given parameter
|
# Compare to a given parameter
|
||||||
'timeuntil04' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=1), 'b':NOW - timedelta(days=2)}, '1 day'),
|
'timeuntil04' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=1), 'b':NOW - timedelta(days=2)}, '1 day'),
|
||||||
'timeuntil05' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=2), 'b':NOW - timedelta(days=2, minutes=1)}, '1 minute'),
|
'timeuntil05' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=2), 'b':NOW - timedelta(days=2, minutes=1)}, '1 minute'),
|
||||||
|
|
||||||
|
### URL TAG ########################################################
|
||||||
|
# Successes
|
||||||
|
'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
|
||||||
|
'url02' : ('{% url regressiontests.templates.views.client_action client.id,action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
|
||||||
|
'url03' : ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'),
|
||||||
|
|
||||||
|
# Failures
|
||||||
|
'url04' : ('{% url %}', {}, template.TemplateSyntaxError),
|
||||||
|
'url05' : ('{% url no_such_view %}', {}, ''),
|
||||||
|
'url06' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Register our custom template loader.
|
# Register our custom template loader.
|
||||||
|
10
tests/regressiontests/templates/urls.py
Normal file
10
tests/regressiontests/templates/urls.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from django.conf.urls.defaults import *
|
||||||
|
from regressiontests.templates import views
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
|
||||||
|
# Test urls for testing reverse lookups
|
||||||
|
(r'^$', views.index),
|
||||||
|
(r'^client/(\d+)/$', views.client),
|
||||||
|
(r'^client/(\d+)/(?P<action>[^/]+)/$', views.client_action),
|
||||||
|
)
|
10
tests/regressiontests/templates/views.py
Normal file
10
tests/regressiontests/templates/views.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Fake views for testing url reverse lookup
|
||||||
|
|
||||||
|
def index(request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def client(request, id):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def client_action(request, id, action):
|
||||||
|
pass
|
@ -77,13 +77,19 @@ def django_tests(verbosity, tests_to_run):
|
|||||||
old_root_urlconf = settings.ROOT_URLCONF
|
old_root_urlconf = settings.ROOT_URLCONF
|
||||||
old_template_dirs = settings.TEMPLATE_DIRS
|
old_template_dirs = settings.TEMPLATE_DIRS
|
||||||
old_use_i18n = settings.USE_I18N
|
old_use_i18n = settings.USE_I18N
|
||||||
|
old_middleware_classes = settings.MIDDLEWARE_CLASSES
|
||||||
|
|
||||||
# Redirect some settings for the duration of these tests
|
# Redirect some settings for the duration of these tests.
|
||||||
settings.TEST_DATABASE_NAME = TEST_DATABASE_NAME
|
settings.TEST_DATABASE_NAME = TEST_DATABASE_NAME
|
||||||
settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS
|
settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS
|
||||||
settings.ROOT_URLCONF = 'urls'
|
settings.ROOT_URLCONF = 'urls'
|
||||||
settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR),)
|
settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR),)
|
||||||
settings.USE_I18N = True
|
settings.USE_I18N = True
|
||||||
|
settings.MIDDLEWARE_CLASSES = (
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
)
|
||||||
|
|
||||||
# Load all the ALWAYS_INSTALLED_APPS.
|
# Load all the ALWAYS_INSTALLED_APPS.
|
||||||
# (This import statement is intentionally delayed until after we
|
# (This import statement is intentionally delayed until after we
|
||||||
@ -91,7 +97,7 @@ def django_tests(verbosity, tests_to_run):
|
|||||||
from django.db.models.loading import get_apps, load_app
|
from django.db.models.loading import get_apps, load_app
|
||||||
get_apps()
|
get_apps()
|
||||||
|
|
||||||
# Load all the test model apps
|
# Load all the test model apps.
|
||||||
test_models = []
|
test_models = []
|
||||||
for model_dir, model_name in get_test_models():
|
for model_dir, model_name in get_test_models():
|
||||||
model_label = '.'.join([model_dir, model_name])
|
model_label = '.'.join([model_dir, model_name])
|
||||||
@ -109,7 +115,7 @@ def django_tests(verbosity, tests_to_run):
|
|||||||
sys.stderr.write("Error while importing %s:" % model_name + ''.join(traceback.format_exception(*sys.exc_info())[1:]))
|
sys.stderr.write("Error while importing %s:" % model_name + ''.join(traceback.format_exception(*sys.exc_info())[1:]))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Add tests for invalid models
|
# Add tests for invalid models.
|
||||||
extra_tests = []
|
extra_tests = []
|
||||||
for model_dir, model_name in get_invalid_models():
|
for model_dir, model_name in get_invalid_models():
|
||||||
model_label = '.'.join([model_dir, model_name])
|
model_label = '.'.join([model_dir, model_name])
|
||||||
@ -120,12 +126,13 @@ def django_tests(verbosity, tests_to_run):
|
|||||||
from django.test.simple import run_tests
|
from django.test.simple import run_tests
|
||||||
run_tests(test_models, verbosity, extra_tests=extra_tests)
|
run_tests(test_models, verbosity, extra_tests=extra_tests)
|
||||||
|
|
||||||
# Restore the old settings
|
# Restore the old settings.
|
||||||
settings.INSTALLED_APPS = old_installed_apps
|
settings.INSTALLED_APPS = old_installed_apps
|
||||||
settings.TESTS_DATABASE_NAME = old_test_database_name
|
settings.TESTS_DATABASE_NAME = old_test_database_name
|
||||||
settings.ROOT_URLCONF = old_root_urlconf
|
settings.ROOT_URLCONF = old_root_urlconf
|
||||||
settings.TEMPLATE_DIRS = old_template_dirs
|
settings.TEMPLATE_DIRS = old_template_dirs
|
||||||
settings.USE_I18N = old_use_i18n
|
settings.USE_I18N = old_use_i18n
|
||||||
|
settings.MIDDLEWARE_CLASSES = old_middleware_classes
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
@ -7,4 +7,7 @@ urlpatterns = patterns('',
|
|||||||
# Always provide the auth system login and logout views
|
# Always provide the auth system login and logout views
|
||||||
(r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}),
|
(r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}),
|
||||||
(r'^accounts/logout/$', 'django.contrib.auth.views.login'),
|
(r'^accounts/logout/$', 'django.contrib.auth.views.login'),
|
||||||
|
|
||||||
|
# test urlconf for {% url %} template tag
|
||||||
|
(r'^url_tag/', include('regressiontests.templates.urls')),
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user