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

sqlalchemy: Merged revisions 3770 to 3831 from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/sqlalchemy@3832 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Robin Munn 2006-09-25 15:39:20 +00:00
parent 8afc419b12
commit 1bb4fa2cb6
60 changed files with 3585 additions and 1001 deletions

View File

@ -42,6 +42,7 @@ And here is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS --
people who have submitted patches, reported bugs, added translations, helped people who have submitted patches, reported bugs, added translations, helped
answer newbie questions, and generally made Django that much better: answer newbie questions, and generally made Django that much better:
adurdin@gmail.com
akaihola akaihola
Andreas Andreas
ant9000@netwise.it ant9000@netwise.it
@ -68,15 +69,19 @@ answer newbie questions, and generally made Django that much better:
Alex Dedul Alex Dedul
deric@monowerks.com deric@monowerks.com
dne@mayonnaise.net dne@mayonnaise.net
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
favo@exoweb.net
gandalf@owca.info gandalf@owca.info
Baishampayan Ghose Baishampayan Ghose
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/>
Brant Harris Brant Harris
heckj@mac.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>
@ -96,6 +101,7 @@ answer newbie questions, and generally made Django that much better:
lakin.wecker@gmail.com lakin.wecker@gmail.com
Stuart Langridge <http://www.kryogenix.org/> Stuart Langridge <http://www.kryogenix.org/>
Eugene Lazutkin <http://lazutkin.com/blog/> Eugene Lazutkin <http://lazutkin.com/blog/>
Jeong-Min Lee
Christopher Lenz <http://www.cmlenz.net/> Christopher Lenz <http://www.cmlenz.net/>
limodou limodou
Martin Maney <http://www.chipy.org/Martin_Maney> Martin Maney <http://www.chipy.org/Martin_Maney>
@ -122,6 +128,7 @@ answer newbie questions, and generally made Django that much better:
Daniel Poelzleithner <http://poelzi.org/> Daniel Poelzleithner <http://poelzi.org/>
J. Rademaker J. Rademaker
Michael Radziej <mir@noris.de> Michael Radziej <mir@noris.de>
ramiro
Brian Ray <http://brianray.chipy.org/> Brian Ray <http://brianray.chipy.org/>
rhettg@gmail.com rhettg@gmail.com
Oliver Rutherfurd <http://rutherfurd.net/> Oliver Rutherfurd <http://rutherfurd.net/>
@ -144,8 +151,10 @@ answer newbie questions, and generally made Django that much better:
Milton Waddams Milton Waddams
Dan Watson <http://theidioteque.net/> Dan Watson <http://theidioteque.net/>
Rachel Willmer <http://www.willmer.com/kb/> Rachel Willmer <http://www.willmer.com/kb/>
Gary Wilson <gary.wilson@gmail.com>
wojtek wojtek
ye7cakf02@sneakemail.com ye7cakf02@sneakemail.com
ymasuda@ethercube.com
Cheng Zhang Cheng Zhang
A big THANK YOU goes to: A big THANK YOU goes to:

4
README
View File

@ -25,10 +25,10 @@ http://code.djangoproject.com/newticket
To get more help: To get more help:
* Join the #django channel on irc.freenode.net. Lots of helpful people * Join the #django channel on irc.freenode.net. Lots of helpful people
hang out there. Read the archives at http://loglibrary.com/179 . hang out there. Read the archives at http://simon.bofh.ms/logger/django/ .
* Join the django-users mailing list, or read the archives, at * Join the django-users mailing list, or read the archives, at
http://groups-beta.google.com/group/django-users. http://groups.google.com/group/django-users.
To contribute to Django: To contribute to Django:

View File

@ -64,6 +64,7 @@ LANGUAGES = (
('sr', gettext_noop('Serbian')), ('sr', gettext_noop('Serbian')),
('sv', gettext_noop('Swedish')), ('sv', gettext_noop('Swedish')),
('ta', gettext_noop('Tamil')), ('ta', gettext_noop('Tamil')),
('tr', gettext_noop('Turkish')),
('uk', gettext_noop('Ukrainian')), ('uk', gettext_noop('Ukrainian')),
('zh-cn', gettext_noop('Simplified Chinese')), ('zh-cn', gettext_noop('Simplified Chinese')),
('zh-tw', gettext_noop('Traditional Chinese')), ('zh-tw', gettext_noop('Traditional Chinese')),
@ -272,6 +273,10 @@ CACHE_MIDDLEWARE_KEY_PREFIX = ''
COMMENTS_ALLOW_PROFANITIES = False COMMENTS_ALLOW_PROFANITIES = False
# The profanities that will trigger a validation error in the
# 'hasNoProfanities' validator. All of these should be in lower-case.
PROFANITIES_LIST = ['asshat', 'asshead', 'asshole', 'cunt', 'fuck', 'gook', 'nigger', 'shit']
# The group ID that designates which users are banned. # The group ID that designates which users are banned.
# Set to None if you're not using it. # Set to None if you're not using it.
COMMENTS_BANNED_USERS_GROUP = None COMMENTS_BANNED_USERS_GROUP = None

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: django v1.0\n" "Project-Id-Version: django v1.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2006-05-16 10:10+0200\n" "POT-Creation-Date: 2006-05-16 10:10+0200\n"
"PO-Revision-Date: 2006-05-17 13:47+0800\n" "PO-Revision-Date: 2006-09-01 22:05+0800\n"
"Last-Translator: limodou <limodou@gmail.com>\n" "Last-Translator: limodou <limodou@gmail.com>\n"
"Language-Team: Simplified Chinese <limodou@gmail.com>\n" "Language-Team: Simplified Chinese <limodou@gmail.com>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -1167,7 +1167,7 @@ msgstr "个人信息"
#: contrib/auth/models.py:77 #: contrib/auth/models.py:77
msgid "Permissions" msgid "Permissions"
msgstr "许可" msgstr "权限"
#: contrib/auth/models.py:78 #: contrib/auth/models.py:78
msgid "Important dates" msgid "Important dates"

View File

@ -3,22 +3,21 @@
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
#, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: Django 0.95\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2006-03-21 18:43+0800\n" "POT-Creation-Date: 2006-03-21 18:43+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2006-09-25 08:35+0800\n"
"Last-Translator: FULL NAME <max@exoweb.net>\n" "Last-Translator: limodou <limodou@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: limodou <limodou@gmail.com>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: contrib/admin/media/js/SelectFilter2.js:33 #: contrib/admin/media/js/SelectFilter2.js:33
msgid "Available %s" msgid "Available %s"
msgstr "可 %s" msgstr "可 %s"
#: contrib/admin/media/js/SelectFilter2.js:41 #: contrib/admin/media/js/SelectFilter2.js:41
msgid "Choose all" msgid "Choose all"
@ -30,34 +29,32 @@ msgstr "增加"
#: contrib/admin/media/js/SelectFilter2.js:48 #: contrib/admin/media/js/SelectFilter2.js:48
msgid "Remove" msgid "Remove"
msgstr "移出" msgstr "删除"
#: contrib/admin/media/js/SelectFilter2.js:53 #: contrib/admin/media/js/SelectFilter2.js:53
msgid "Chosen %s" msgid "Chosen %s"
msgstr "选 %s" msgstr "选中的 %s"
#: contrib/admin/media/js/SelectFilter2.js:54 #: contrib/admin/media/js/SelectFilter2.js:54
msgid "Select your choice(s) and click " msgid "Select your choice(s) and click "
msgstr "挑选你的选择并点击 " msgstr "选择并点击 "
#: contrib/admin/media/js/SelectFilter2.js:59 #: contrib/admin/media/js/SelectFilter2.js:59
msgid "Clear all" msgid "Clear all"
msgstr "清除所有" msgstr "清除全部"
#: contrib/admin/media/js/dateparse.js:32 #: contrib/admin/media/js/dateparse.js:32
#: contrib/admin/media/js/calendar.js:24 #: contrib/admin/media/js/calendar.js:24
msgid "" msgid "January February March April May June July August September October November December"
"January February March April May June July August September October November "
"December"
msgstr "一月 二月 三月 四月 五月 六月 六月 七月 八月 九月 十月 十一月 十二月" msgstr "一月 二月 三月 四月 五月 六月 六月 七月 八月 九月 十月 十一月 十二月"
#: contrib/admin/media/js/dateparse.js:33 #: contrib/admin/media/js/dateparse.js:33
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
msgstr "星期 星期一 星期二 星期三 星期四 星期五 星期六" msgstr "星期 星期一 星期二 星期三 星期四 星期五 星期六"
#: contrib/admin/media/js/calendar.js:25 #: contrib/admin/media/js/calendar.js:25
msgid "S M T W T F S" msgid "S M T W T F S"
msgstr "日 月 火 水 木 金 土" msgstr "日 一 二 三 四 五 六"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:45
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:80
@ -105,3 +102,4 @@ msgstr "昨天"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:164
msgid "Tomorrow" msgid "Tomorrow"
msgstr "明天" msgstr "明天"

View File

@ -160,8 +160,10 @@ class EditInlineNode(template.Node):
context.push() context.push()
if relation.field.rel.edit_inline == models.TABULAR: if relation.field.rel.edit_inline == models.TABULAR:
bound_related_object_class = TabularBoundRelatedObject bound_related_object_class = TabularBoundRelatedObject
else: elif relation.field.rel.edit_inline == models.STACKED:
bound_related_object_class = StackedBoundRelatedObject bound_related_object_class = StackedBoundRelatedObject
else:
bound_related_object_class = relation.field.rel.edit_inline
original = context.get('original', None) original = context.get('original', None)
bound_related_object = relation.bind(context['form'], original, bound_related_object_class) bound_related_object = relation.bind(context['form'], original, bound_related_object_class)
context['bound_related_object'] = bound_related_object context['bound_related_object'] = bound_related_object

View File

@ -727,6 +727,8 @@ class ChangeList(object):
for bit in self.query.split(): for bit in self.query.split():
or_queries = [models.Q(**{construct_search(field_name): bit}) for field_name in self.lookup_opts.admin.search_fields] or_queries = [models.Q(**{construct_search(field_name): bit}) for field_name in self.lookup_opts.admin.search_fields]
other_qs = QuerySet(self.model) other_qs = QuerySet(self.model)
if qs._select_related:
other_qs = other_qs.select_related()
other_qs = other_qs.filter(reduce(operator.or_, or_queries)) other_qs = other_qs.filter(reduce(operator.or_, or_queries))
qs = qs & other_qs qs = qs & other_qs

View File

@ -26,3 +26,11 @@ login_required.__doc__ = (
to the log-in page if necessary. to the log-in page if necessary.
""" """
) )
def permission_required(perm, login_url=LOGIN_URL):
"""
Decorator for views that checks if a user has a particular permission
enabled, redirectiing to the log-in page if necessary.
"""
return user_passes_test(lambda u: u.has_perm(perm), login_url=login_url)

View File

@ -81,7 +81,7 @@ class PasswordResetForm(forms.Manipulator):
try: try:
self.user_cache = User.objects.get(email__iexact=new_data) self.user_cache = User.objects.get(email__iexact=new_data)
except User.DoesNotExist: except User.DoesNotExist:
raise validators.ValidationError, "That e-mail address doesn't have an associated user acount. Are you sure you've registered?" raise validators.ValidationError, _("That e-mail address doesn't have an associated user acount. Are you sure you've registered?")
def save(self, domain_override=None, email_template_name='registration/password_reset_email.html'): def save(self, domain_override=None, email_template_name='registration/password_reset_email.html'):
"Calculates a new password randomly and sends it to the user" "Calculates a new password randomly and sends it to the user"
@ -113,14 +113,14 @@ class PasswordChangeForm(forms.Manipulator):
forms.PasswordField(field_name="old_password", length=30, maxlength=30, is_required=True, forms.PasswordField(field_name="old_password", length=30, maxlength=30, is_required=True,
validator_list=[self.isValidOldPassword]), validator_list=[self.isValidOldPassword]),
forms.PasswordField(field_name="new_password1", length=30, maxlength=30, is_required=True, forms.PasswordField(field_name="new_password1", length=30, maxlength=30, is_required=True,
validator_list=[validators.AlwaysMatchesOtherField('new_password2', "The two 'new password' fields didn't match.")]), validator_list=[validators.AlwaysMatchesOtherField('new_password2', _("The two 'new password' fields didn't match."))]),
forms.PasswordField(field_name="new_password2", length=30, maxlength=30, is_required=True), forms.PasswordField(field_name="new_password2", length=30, maxlength=30, is_required=True),
) )
def isValidOldPassword(self, new_data, all_data): def isValidOldPassword(self, new_data, all_data):
"Validates that the old_password field is correct." "Validates that the old_password field is correct."
if not self.user.check_password(new_data): if not self.user.check_password(new_data):
raise validators.ValidationError, "Your old password was entered incorrectly. Please enter it again." raise validators.ValidationError, _("Your old password was entered incorrectly. Please enter it again.")
def save(self, new_data): def save(self, new_data):
"Saves the new password." "Saves the new password."

View File

@ -22,6 +22,8 @@ def authenhandler(req, **kwargs):
os.environ['DJANGO_SETTINGS_MODULE'] = settings_module os.environ['DJANGO_SETTINGS_MODULE'] = settings_module
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django import db
db.reset_queries()
# check that the username is valid # check that the username is valid
kwargs = {'username': req.user, 'is_active': True} kwargs = {'username': req.user, 'is_active': True}
@ -29,6 +31,7 @@ def authenhandler(req, **kwargs):
kwargs['is_staff'] = True kwargs['is_staff'] = True
if superuser_only: if superuser_only:
kwargs['is_superuser'] = True kwargs['is_superuser'] = True
try:
try: try:
user = User.objects.get(**kwargs) user = User.objects.get(**kwargs)
except User.DoesNotExist: except User.DoesNotExist:
@ -45,3 +48,5 @@ def authenhandler(req, **kwargs):
return apache.OK return apache.OK
else: else:
return apache.HTTP_UNAUTHORIZED return apache.HTTP_UNAUTHORIZED
finally:
db.connection.close()

View File

@ -51,15 +51,19 @@ def request(request):
class PermLookupDict(object): class PermLookupDict(object):
def __init__(self, user, module_name): def __init__(self, user, module_name):
self.user, self.module_name = user, module_name self.user, self.module_name = user, module_name
def __repr__(self): def __repr__(self):
return str(self.user.get_permission_list()) return str(self.user.get_all_permissions())
def __getitem__(self, perm_name): def __getitem__(self, perm_name):
return self.user.has_perm("%s.%s" % (self.module_name, perm_name)) return self.user.has_perm("%s.%s" % (self.module_name, perm_name))
def __nonzero__(self): def __nonzero__(self):
return self.user.has_module_perms(self.module_name) return self.user.has_module_perms(self.module_name)
class PermWrapper(object): class PermWrapper(object):
def __init__(self, user): def __init__(self, user):
self.user = user self.user = user
def __getitem__(self, module_name): def __getitem__(self, module_name):
return PermLookupDict(self.user, module_name) return PermLookupDict(self.user, module_name)

View File

@ -16,9 +16,26 @@ class ModPythonRequest(http.HttpRequest):
self.path = req.uri self.path = req.uri
def __repr__(self): def __repr__(self):
# Since this is called as part of error handling, we need to be very
# robust against potentially malformed input.
try:
get = pformat(self.GET)
except:
get = '<could not parse>'
try:
post = pformat(self.POST)
except:
post = '<could not parse>'
try:
cookies = pformat(self.COOKIES)
except:
cookies = '<could not parse>'
try:
meta = pformat(self.META)
except:
meta = '<could not parse>'
return '<ModPythonRequest\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \ return '<ModPythonRequest\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
(self.path, pformat(self.GET), pformat(self.POST), pformat(self.COOKIES), (self.path, get, post, cookies, meta)
pformat(self.META))
def get_full_path(self): def get_full_path(self):
return '%s%s' % (self.path, self._req.args and ('?' + self._req.args) or '') return '%s%s' % (self.path, self._req.args and ('?' + self._req.args) or '')
@ -155,8 +172,11 @@ def populate_apache_request(http_response, mod_python_req):
for c in http_response.cookies.values(): for c in http_response.cookies.values():
mod_python_req.headers_out.add('Set-Cookie', c.output(header='')) mod_python_req.headers_out.add('Set-Cookie', c.output(header=''))
mod_python_req.status = http_response.status_code mod_python_req.status = http_response.status_code
for chunk in http_response.iterator: try:
for chunk in http_response:
mod_python_req.write(chunk) mod_python_req.write(chunk)
finally:
http_response.close()
def handler(req): def handler(req):
# mod_python hooks into this function. # mod_python hooks into this function.

View File

@ -4,6 +4,11 @@ from django.dispatch import dispatcher
from django.utils import datastructures from django.utils import datastructures
from django import http from django import http
from pprint import pformat from pprint import pformat
from shutil import copyfileobj
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
STATUS_CODE_TEXT = { STATUS_CODE_TEXT = {
@ -50,6 +55,21 @@ STATUS_CODE_TEXT = {
505: 'HTTP VERSION NOT SUPPORTED', 505: 'HTTP VERSION NOT SUPPORTED',
} }
def safe_copyfileobj(fsrc, fdst, length=16*1024, size=0):
"""
A version of shutil.copyfileobj that will not read more than 'size' bytes.
This makes it safe from clients sending more than CONTENT_LENGTH bytes of
data in the body.
"""
if not size:
return copyfileobj(fsrc, fdst, length)
while size > 0:
buf = fsrc.read(min(length, size))
if not buf:
break
fdst.write(buf)
size -= len(buf)
class WSGIRequest(http.HttpRequest): class WSGIRequest(http.HttpRequest):
def __init__(self, environ): def __init__(self, environ):
self.environ = environ self.environ = environ
@ -58,9 +78,26 @@ class WSGIRequest(http.HttpRequest):
self.method = environ['REQUEST_METHOD'].upper() self.method = environ['REQUEST_METHOD'].upper()
def __repr__(self): def __repr__(self):
# Since this is called as part of error handling, we need to be very
# robust against potentially malformed input.
try:
get = pformat(self.GET)
except:
get = '<could not parse>'
try:
post = pformat(self.POST)
except:
post = '<could not parse>'
try:
cookies = pformat(self.COOKIES)
except:
cookies = '<could not parse>'
try:
meta = pformat(self.META)
except:
meta = '<could not parse>'
return '<WSGIRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \ return '<WSGIRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
(pformat(self.GET), pformat(self.POST), pformat(self.COOKIES), (get, post, cookies, meta)
pformat(self.META))
def get_full_path(self): def get_full_path(self):
return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '') return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '')
@ -119,7 +156,11 @@ class WSGIRequest(http.HttpRequest):
try: try:
return self._raw_post_data return self._raw_post_data
except AttributeError: except AttributeError:
self._raw_post_data = self.environ['wsgi.input'].read(int(self.environ["CONTENT_LENGTH"])) buf = StringIO()
content_length = int(self.environ['CONTENT_LENGTH'])
safe_copyfileobj(self.environ['wsgi.input'], buf, size=content_length)
self._raw_post_data = buf.getvalue()
buf.close()
return self._raw_post_data return self._raw_post_data
GET = property(_get_get, _set_get) GET = property(_get_get, _set_get)
@ -163,4 +204,4 @@ class WSGIHandler(BaseHandler):
for c in response.cookies.values(): for c in response.cookies.values():
response_headers.append(('Set-Cookie', c.output(header=''))) response_headers.append(('Set-Cookie', c.output(header='')))
start_response(status, response_headers) start_response(status, response_headers)
return response.iterator return response

View File

@ -347,7 +347,7 @@ def get_sql_initial_data_for_model(model):
os.path.join(app_dir, "%s.sql" % opts.object_name.lower())] os.path.join(app_dir, "%s.sql" % opts.object_name.lower())]
for sql_file in sql_files: for sql_file in sql_files:
if os.path.exists(sql_file): if os.path.exists(sql_file):
fp = open(sql_file) fp = open(sql_file, 'U')
for statement in statements.split(fp.read()): for statement in statements.split(fp.read()):
if statement.strip(): if statement.strip():
output.append(statement + ";") output.append(statement + ";")
@ -958,7 +958,8 @@ def get_validation_errors(outfile, app=None):
try: try:
f = opts.get_field(fn) f = opts.get_field(fn)
except models.FieldDoesNotExist: except models.FieldDoesNotExist:
e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn) if not hasattr(cls, fn):
e.add(opts, '"admin.list_display_links" refers to %r, which isn\'t an attribute, method or property.' % fn)
if fn not in opts.admin.list_display: if fn not in opts.admin.list_display:
e.add(opts, '"admin.list_display_links" refers to %r, which is not defined in "admin.list_display".' % fn) e.add(opts, '"admin.list_display_links" refers to %r, which is not defined in "admin.list_display".' % fn)
# list_filter # list_filter
@ -1016,10 +1017,12 @@ def get_validation_errors(outfile, app=None):
return len(e.errors) return len(e.errors)
def validate(outfile=sys.stdout): def validate(outfile=sys.stdout, silent_success=False):
"Validates all installed models." "Validates all installed models."
try: try:
num_errors = get_validation_errors(outfile) num_errors = get_validation_errors(outfile)
if silent_success and num_errors == 0:
return
outfile.write('%s error%s found.\n' % (num_errors, num_errors != 1 and 's' or '')) outfile.write('%s error%s found.\n' % (num_errors, num_errors != 1 and 's' or ''))
except ImproperlyConfigured: except ImproperlyConfigured:
outfile.write("Skipping validation because things aren't configured properly.") outfile.write("Skipping validation because things aren't configured properly.")
@ -1042,7 +1045,7 @@ def _check_for_validation_errors(app=None):
sys.stderr.write(s.read()) sys.stderr.write(s.read())
sys.exit(1) sys.exit(1)
def runserver(addr, port, use_reloader=True): def runserver(addr, port, use_reloader=True, admin_media_dir=''):
"Starts a lightweight Web server for development." "Starts a lightweight Web server for development."
from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException
from django.core.handlers.wsgi import WSGIHandler from django.core.handlers.wsgi import WSGIHandler
@ -1060,7 +1063,10 @@ def runserver(addr, port, use_reloader=True):
print "Development server is running at http://%s:%s/" % (addr, port) print "Development server is running at http://%s:%s/" % (addr, port)
print "Quit the server with %s." % quit_command print "Quit the server with %s." % quit_command
try: try:
run(addr, int(port), AdminMediaHandler(WSGIHandler())) import django
path = admin_media_dir or django.__path__[0] + '/contrib/admin/media'
handler = AdminMediaHandler(WSGIHandler(), path)
run(addr, int(port), handler)
except WSGIServerException, e: except WSGIServerException, e:
# Use helpful error messages instead of ugly tracebacks. # Use helpful error messages instead of ugly tracebacks.
ERRORS = { ERRORS = {
@ -1081,7 +1087,7 @@ def runserver(addr, port, use_reloader=True):
autoreload.main(inner_run) autoreload.main(inner_run)
else: else:
inner_run() inner_run()
runserver.args = '[--noreload] [optional port number, or ipaddr:port]' runserver.args = '[--noreload] [--adminmedia=ADMIN_MEDIA_PATH] [optional port number, or ipaddr:port]'
def createcachetable(tablename): def createcachetable(tablename):
"Creates the table needed to use the SQL cache backend" "Creates the table needed to use the SQL cache backend"
@ -1267,7 +1273,8 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None):
help='Tells Django to NOT use the auto-reloader when running the development server.') help='Tells Django to NOT use the auto-reloader when running the development server.')
parser.add_option('--verbosity', action='store', dest='verbosity', default='2', parser.add_option('--verbosity', action='store', dest='verbosity', default='2',
type='choice', choices=['0', '1', '2'], type='choice', choices=['0', '1', '2'],
help='Verbosity level; 0=minimal output, 1=normal output, 2=all output') help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
parser.add_option('--adminmedia', dest='admin_media_path', default='', help='Lets you manually specify the directory to serve admin media from when running the development server.'),
options, args = parser.parse_args(argv[1:]) options, args = parser.parse_args(argv[1:])
@ -1331,11 +1338,12 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None):
addr, port = args[1].split(':') addr, port = args[1].split(':')
except ValueError: except ValueError:
addr, port = '', args[1] addr, port = '', args[1]
action_mapping[action](addr, port, options.use_reloader) action_mapping[action](addr, port, options.use_reloader, options.admin_media_path)
elif action == 'runfcgi': elif action == 'runfcgi':
action_mapping[action](args[1:]) action_mapping[action](args[1:])
else: else:
from django.db import models from django.db import models
validate(silent_success=True)
try: try:
mod_list = [models.get_app(app_label) for app_label in args[1:]] mod_list = [models.get_app(app_label) for app_label in args[1:]]
except ImportError, e: except ImportError, e:

View File

@ -16,7 +16,7 @@ class Serializer(PythonSerializer):
Convert a queryset to JSON. Convert a queryset to JSON.
""" """
def end_serialization(self): def end_serialization(self):
simplejson.dump(self.objects, self.stream, cls=DateTimeAwareJSONEncoder) simplejson.dump(self.objects, self.stream, cls=DateTimeAwareJSONEncoder, **self.options)
def getvalue(self): def getvalue(self):
return self.stream.getvalue() return self.stream.getvalue()

View File

@ -594,11 +594,14 @@ class AdminMediaHandler(object):
Use this ONLY LOCALLY, for development! This hasn't been tested for Use this ONLY LOCALLY, for development! This hasn't been tested for
security and is not super efficient. security and is not super efficient.
""" """
def __init__(self, application): def __init__(self, application, media_dir = None):
from django.conf import settings from django.conf import settings
import django
self.application = application self.application = application
if not media_dir:
import django
self.media_dir = django.__path__[0] + '/contrib/admin/media' self.media_dir = django.__path__[0] + '/contrib/admin/media'
else:
self.media_dir = media_dir
self.media_url = settings.ADMIN_MEDIA_PREFIX self.media_url = settings.ADMIN_MEDIA_PREFIX
def __call__(self, environ, start_response): def __call__(self, environ, start_response):

View File

@ -74,7 +74,7 @@ def fastcgi_help(message=None):
print message print message
return False return False
def runfastcgi(argset, **kwargs): def runfastcgi(argset=[], **kwargs):
options = FASTCGI_OPTIONS.copy() options = FASTCGI_OPTIONS.copy()
options.update(kwargs) options.update(kwargs)
for x in argset: for x in argset:

View File

@ -13,7 +13,7 @@ from django.utils.translation import gettext, gettext_lazy, ngettext
from django.utils.functional import Promise, lazy from django.utils.functional import Promise, lazy
import re import re
_datere = r'(19|2\d)\d{2}-((?:0?[1-9])|(?:1[0-2]))-((?:0?[1-9])|(?:[12][0-9])|(?:3[0-1]))' _datere = r'\d{4}-\d{1,2}-\d{1,2}'
_timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?' _timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?'
alnum_re = re.compile(r'^\w+$') alnum_re = re.compile(r'^\w+$')
alnumurl_re = re.compile(r'^[-\w/]+$') alnumurl_re = re.compile(r'^[-\w/]+$')
@ -122,9 +122,29 @@ def isOnlyLetters(field_data, all_data):
if not field_data.isalpha(): if not field_data.isalpha():
raise ValidationError, gettext("Only alphabetical characters are allowed here.") raise ValidationError, gettext("Only alphabetical characters are allowed here.")
def _isValidDate(date_string):
"""
A helper function used by isValidANSIDate and isValidANSIDatetime to
check if the date is valid. The date string is assumed to already be in
YYYY-MM-DD format.
"""
from datetime import date
# Could use time.strptime here and catch errors, but datetime.date below
# produces much friendlier error messages.
year, month, day = map(int, date_string.split('-'))
# This check is needed because strftime is used when saving the date
# value to the database, and strftime requires that the year be >=1900.
if year < 1900:
raise ValidationError, gettext('Year must be 1900 or later.')
try:
date(year, month, day)
except ValueError, e:
raise ValidationError, gettext('Invalid date: %s.' % e)
def isValidANSIDate(field_data, all_data): def isValidANSIDate(field_data, all_data):
if not ansi_date_re.search(field_data): if not ansi_date_re.search(field_data):
raise ValidationError, gettext('Enter a valid date in YYYY-MM-DD format.') raise ValidationError, gettext('Enter a valid date in YYYY-MM-DD format.')
_isValidDate(field_data)
def isValidANSITime(field_data, all_data): def isValidANSITime(field_data, all_data):
if not ansi_time_re.search(field_data): if not ansi_time_re.search(field_data):
@ -133,6 +153,7 @@ def isValidANSITime(field_data, all_data):
def isValidANSIDatetime(field_data, all_data): def isValidANSIDatetime(field_data, all_data):
if not ansi_datetime_re.search(field_data): if not ansi_datetime_re.search(field_data):
raise ValidationError, gettext('Enter a valid date/time in YYYY-MM-DD HH:MM format.') raise ValidationError, gettext('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
_isValidDate(field_data.split()[0])
def isValidEmail(field_data, all_data): def isValidEmail(field_data, all_data):
if not email_re.search(field_data): if not email_re.search(field_data):
@ -227,9 +248,8 @@ def hasNoProfanities(field_data, all_data):
catch 'motherfucker' as well. Raises a ValidationError such as: catch 'motherfucker' as well. Raises a ValidationError such as:
Watch your mouth! The words "f--k" and "s--t" are not allowed here. Watch your mouth! The words "f--k" and "s--t" are not allowed here.
""" """
bad_words = ['asshat', 'asshead', 'asshole', 'cunt', 'fuck', 'gook', 'nigger', 'shit'] # all in lower case
field_data = field_data.lower() # normalize field_data = field_data.lower() # normalize
words_seen = [w for w in bad_words if field_data.find(w) > -1] words_seen = [w for w in settings.PROFANITIES_LIST if field_data.find(w) > -1]
if words_seen: if words_seen:
from django.utils.text import get_text_list from django.utils.text import get_text_list
plural = len(words_seen) > 1 plural = len(words_seen) > 1
@ -352,10 +372,12 @@ class IsValidFloat(object):
float(data) float(data)
except ValueError: except ValueError:
raise ValidationError, gettext("Please enter a valid decimal number.") raise ValidationError, gettext("Please enter a valid decimal number.")
if len(data) > (self.max_digits + 1): # Negative floats require more space to input.
max_allowed_length = data.startswith('-') and (self.max_digits + 2) or (self.max_digits + 1)
if len(data) > max_allowed_length:
raise ValidationError, ngettext("Please enter a valid decimal number with at most %s total digit.", raise ValidationError, ngettext("Please enter a valid decimal number with at most %s total digit.",
"Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits
if (not '.' in data and len(data) > (self.max_digits - self.decimal_places)) or ('.' in data and len(data) > (self.max_digits - (self.decimal_places - len(data.split('.')[1])) + 1)): if (not '.' in data and len(data) > (max_allowed_length - self.decimal_places)) or ('.' in data and len(data) > (self.max_digits - (self.decimal_places - len(data.split('.')[1])) + 1)):
raise ValidationError, ngettext( "Please enter a valid decimal number with a whole part of at most %s digit.", raise ValidationError, ngettext( "Please enter a valid decimal number with a whole part of at most %s digit.",
"Please enter a valid decimal number with a whole part of at most %s digits.", str(self.max_digits-self.decimal_places)) % str(self.max_digits-self.decimal_places) "Please enter a valid decimal number with a whole part of at most %s digits.", str(self.max_digits-self.decimal_places)) % str(self.max_digits-self.decimal_places)
if '.' in data and len(data.split('.')[1]) > self.decimal_places: if '.' in data and len(data.split('.')[1]) > self.decimal_places:

View File

@ -13,9 +13,10 @@ def populate_xheaders(request, response, model, object_id):
""" """
Adds the "X-Object-Type" and "X-Object-Id" headers to the given Adds the "X-Object-Type" and "X-Object-Id" headers to the given
HttpResponse according to the given model and object_id -- but only if the HttpResponse according to the given model and object_id -- but only if the
given HttpRequest object has an IP address within the INTERNAL_IPS setting. given HttpRequest object has an IP address within the INTERNAL_IPS setting
or if the request is from a logged in staff member.
""" """
from django.conf import settings from django.conf import settings
if request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS: if request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS or (request.user.is_authenticated() and request.user.is_staff):
response['X-Object-Type'] = "%s.%s" % (model._meta.app_label, model._meta.object_name.lower()) response['X-Object-Type'] = "%s.%s" % (model._meta.app_label, model._meta.object_name.lower())
response['X-Object-Id'] = str(object_id) response['X-Object-Id'] = str(object_id)

View File

@ -4,10 +4,18 @@ SQLite3 backend for django. Requires pysqlite2 (http://pysqlite.org/).
from django.db.backends import util from django.db.backends import util
try: try:
try:
from sqlite3 import dbapi2 as Database
except ImportError:
from pysqlite2 import dbapi2 as Database from pysqlite2 import dbapi2 as Database
except ImportError, e: except ImportError, e:
import sys
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured, "Error loading pysqlite2 module: %s" % e if sys.version_info < (2, 5, 0):
module = 'pysqlite2'
else:
module = 'sqlite3'
raise ImproperlyConfigured, "Error loading %s module: %s" % (module, e)
DatabaseError = Database.DatabaseError DatabaseError = Database.DatabaseError

View File

@ -110,9 +110,11 @@ def dictfetchone(cursor):
def dictfetchmany(cursor, number): def dictfetchmany(cursor, number):
"Returns a certain number of rows from a cursor as a dict" "Returns a certain number of rows from a cursor as a dict"
desc = cursor.description desc = cursor.description
return [_dict_helper(desc, row) for row in cursor.fetchmany(number)] for row in cursor.fetchmany(number):
yield _dict_helper(desc, row)
def dictfetchall(cursor): def dictfetchall(cursor):
"Returns all rows from a cursor as a dict" "Returns all rows from a cursor as a dict"
desc = cursor.description desc = cursor.description
return [_dict_helper(desc, row) for row in cursor.fetchall()] for row in cursor.fetchall():
yield _dict_helper(desc, row)

View File

@ -117,7 +117,7 @@ class GenericRelation(RelatedField, Field):
return self.object_id_field_name return self.object_id_field_name
def m2m_reverse_name(self): def m2m_reverse_name(self):
return self.model._meta.pk.attname return self.object_id_field_name
def contribute_to_class(self, cls, name): def contribute_to_class(self, cls, name):
super(GenericRelation, self).contribute_to_class(cls, name) super(GenericRelation, self).contribute_to_class(cls, name)

View File

@ -215,8 +215,8 @@ class AutomaticManipulator(forms.Manipulator):
# Save many-to-many objects. # Save many-to-many objects.
for f in related.opts.many_to_many: for f in related.opts.many_to_many:
if child_follow.get(f.name, None) and not f.rel.edit_inline: if child_follow.get(f.name, None) and not f.rel.edit_inline:
was_changed = getattr(new_rel_obj, 'set_%s' % f.name)(rel_new_data[f.attname]) setattr(new_rel_obj, f.name, f.rel.to.objects.filter(pk__in=rel_new_data[f.attname]))
if self.change and was_changed: if self.change:
self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj)) self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
# If, in the change stage, all of the core fields were blank and # If, in the change stage, all of the core fields were blank and

View File

@ -712,13 +712,9 @@ def parse_lookup(kwarg_items, opts):
# Extract the last elements of the kwarg. # Extract the last elements of the kwarg.
# The very-last is the lookup_type (equals, like, etc). # The very-last is the lookup_type (equals, like, etc).
# The second-last is the table column on which the lookup_type is # The second-last is the table column on which the lookup_type is
# to be performed. # to be performed. If this name is 'pk', it will be substituted with
# The exceptions to this are: # the name of the primary key.
# 1) "pk", which is an implicit id__exact; # If there is only one part, or the last part is not a query
# if we find "pk", make the lookup_type "exact', and insert
# a dummy name of None, which we will replace when
# we know which table column to grab as the primary key.
# 2) If there is only one part, or the last part is not a query
# term, assume that the query is an __exact # term, assume that the query is an __exact
lookup_type = path.pop() lookup_type = path.pop()
if lookup_type == 'pk': if lookup_type == 'pk':
@ -766,7 +762,7 @@ def lookup_inner(path, lookup_type, value, opts, table, column):
name = path.pop(0) name = path.pop(0)
# Has the primary key been requested? If so, expand it out # Has the primary key been requested? If so, expand it out
# to be the name of the current class' primary key # to be the name of the current class' primary key
if name is None: if name is None or name == 'pk':
name = current_opts.pk.name name = current_opts.pk.name
# Try to find the name in the fields associated with the current class # Try to find the name in the fields associated with the current class

View File

@ -2,7 +2,7 @@ from django.core import validators
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.utils.html import escape from django.utils.html import escape
from django.conf import settings from django.conf import settings
from django.utils.translation import gettext, gettext_lazy, ngettext from django.utils.translation import gettext, ngettext
FORM_FIELD_ID_PREFIX = 'id_' FORM_FIELD_ID_PREFIX = 'id_'
@ -343,7 +343,7 @@ class FormField(object):
def get_validation_errors(self, new_data): def get_validation_errors(self, new_data):
errors = {} errors = {}
if self.is_required and not new_data.get(self.field_name, False): if self.is_required and not new_data.get(self.field_name, False):
errors.setdefault(self.field_name, []).append(gettext_lazy('This field is required.')) errors.setdefault(self.field_name, []).append(gettext('This field is required.'))
return errors return errors
try: try:
for validator in self.validator_list: for validator in self.validator_list:
@ -434,11 +434,11 @@ class HiddenField(FormField):
(self.get_id(), self.field_name, escape(data)) (self.get_id(), self.field_name, escape(data))
class CheckboxField(FormField): class CheckboxField(FormField):
def __init__(self, field_name, checked_by_default=False, validator_list=None): def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False):
if validator_list is None: validator_list = [] if validator_list is None: validator_list = []
self.field_name = field_name self.field_name = field_name
self.checked_by_default = checked_by_default self.checked_by_default = checked_by_default
self.is_required = False # because the validator looks for these self.is_required = is_required
self.validator_list = validator_list[:] self.validator_list = validator_list[:]
def render(self, data): def render(self, data):
@ -639,8 +639,8 @@ class CheckboxSelectMultipleField(SelectMultipleField):
checked_html = ' checked="checked"' checked_html = ' checked="checked"'
field_name = '%s%s' % (self.field_name, value) field_name = '%s%s' % (self.field_name, value)
output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s /> <label for="%s">%s</label></li>' % \ output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s /> <label for="%s">%s</label></li>' % \
(self.get_id() + value , self.__class__.__name__, field_name, checked_html, (self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html,
self.get_id() + value, choice)) self.get_id() + escape(value), choice))
output.append('</ul>') output.append('</ul>')
return '\n'.join(output) return '\n'.join(output)
@ -743,7 +743,7 @@ class FloatField(TextField):
if validator_list is None: validator_list = [] if validator_list is None: validator_list = []
self.max_digits, self.decimal_places = max_digits, decimal_places self.max_digits, self.decimal_places = max_digits, decimal_places
validator_list = [self.isValidFloat] + validator_list validator_list = [self.isValidFloat] + validator_list
TextField.__init__(self, field_name, max_digits+1, max_digits+1, is_required, validator_list) TextField.__init__(self, field_name, max_digits+2, max_digits+2, is_required, validator_list)
def isValidFloat(self, field_data, all_data): def isValidFloat(self, field_data, all_data):
v = validators.IsValidFloat(self.max_digits, self.decimal_places) v = validators.IsValidFloat(self.max_digits, self.decimal_places)
@ -952,10 +952,7 @@ class USStateField(TextField):
raise validators.CriticalValidationError, e.messages raise validators.CriticalValidationError, e.messages
def html2python(data): def html2python(data):
if data:
return data.upper() # Should always be stored in upper case return data.upper() # Should always be stored in upper case
else:
return None
html2python = staticmethod(html2python) html2python = staticmethod(html2python)
class CommaSeparatedIntegerField(TextField): class CommaSeparatedIntegerField(TextField):
@ -972,9 +969,19 @@ class CommaSeparatedIntegerField(TextField):
except validators.ValidationError, e: except validators.ValidationError, e:
raise validators.CriticalValidationError, e.messages raise validators.CriticalValidationError, e.messages
def render(self, data):
if data is None:
data = ''
elif isinstance(data, (list, tuple)):
data = ','.join(data)
return super(CommaSeparatedIntegerField, self).render(data)
class RawIdAdminField(CommaSeparatedIntegerField): class RawIdAdminField(CommaSeparatedIntegerField):
def html2python(data): def html2python(data):
if data:
return data.split(',') return data.split(',')
else:
return []
html2python = staticmethod(html2python) html2python = staticmethod(html2python)
class XMLLargeTextField(LargeTextField): class XMLLargeTextField(LargeTextField):

View File

@ -161,10 +161,10 @@ class HttpResponse(object):
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 hasattr(content, '__iter__'):
self._iterator = content self._container = content
self._is_string = False self._is_string = False
else: else:
self._iterator = [content] self._container = [content]
self._is_string = True self._is_string = True
self.headers = {'Content-Type': mimetype} self.headers = {'Content-Type': mimetype}
self.cookies = SimpleCookie() self.cookies = SimpleCookie()
@ -213,32 +213,37 @@ class HttpResponse(object):
self.cookies[key]['max-age'] = 0 self.cookies[key]['max-age'] = 0
def _get_content(self): def _get_content(self):
content = ''.join(self._iterator) content = ''.join(self._container)
if isinstance(content, unicode): if isinstance(content, unicode):
content = content.encode(self._charset) content = content.encode(self._charset)
return content return content
def _set_content(self, value): def _set_content(self, value):
self._iterator = [value] self._container = [value]
self._is_string = True self._is_string = True
content = property(_get_content, _set_content) content = property(_get_content, _set_content)
def _get_iterator(self): def __iter__(self):
"Output iterator. Converts data into client charset if necessary." self._iterator = self._container.__iter__()
for chunk in self._iterator: return self
def next(self):
chunk = self._iterator.next()
if isinstance(chunk, unicode): if isinstance(chunk, unicode):
chunk = chunk.encode(self._charset) chunk = chunk.encode(self._charset)
yield chunk return chunk
iterator = property(_get_iterator) def close(self):
if hasattr(self._container, 'close'):
self._container.close()
# The remaining methods partially implement the file-like object interface. # The remaining methods partially implement the file-like object interface.
# See http://docs.python.org/lib/bltin-file-objects.html # See http://docs.python.org/lib/bltin-file-objects.html
def write(self, content): def write(self, content):
if not self._is_string: if not self._is_string:
raise Exception, "This %s instance is not writable" % self.__class__ raise Exception, "This %s instance is not writable" % self.__class__
self._iterator.append(content) self._container.append(content)
def flush(self): def flush(self):
pass pass
@ -246,7 +251,7 @@ class HttpResponse(object):
def tell(self): def tell(self):
if not self._is_string: if not self._is_string:
raise Exception, "This %s instance cannot tell its position" % self.__class__ raise Exception, "This %s instance cannot tell its position" % self.__class__
return sum([len(chunk) for chunk in self._iterator]) return sum([len(chunk) for chunk in self._container])
class HttpResponseRedirect(HttpResponse): class HttpResponseRedirect(HttpResponse):
def __init__(self, redirect_to): def __init__(self, redirect_to):

View File

@ -64,8 +64,9 @@ class CommonMiddleware(object):
is_internal = referer and (domain in referer) is_internal = referer and (domain in referer)
path = request.get_full_path() path = request.get_full_path()
if referer and not _is_ignorable_404(path) and (is_internal or '?' not in referer): if referer and not _is_ignorable_404(path) and (is_internal or '?' not in referer):
ua = request.META.get('HTTP_USER_AGENT','<none>')
mail_managers("Broken %slink on %s" % ((is_internal and 'INTERNAL ' or ''), domain), mail_managers("Broken %slink on %s" % ((is_internal and 'INTERNAL ' or ''), domain),
"Referrer: %s\nRequested URL: %s\n" % (referer, request.get_full_path())) "Referrer: %s\nRequested URL: %s\nUser Agent: %s\n" % (referer, request.get_full_path(), ua))
return response return response
# Use ETags, if requested. # Use ETags, if requested.

View File

@ -7,11 +7,12 @@ class XViewMiddleware(object):
""" """
def process_view(self, request, view_func, view_args, view_kwargs): def process_view(self, request, view_func, view_args, view_kwargs):
""" """
If the request method is HEAD and the IP is internal, quickly return If the request method is HEAD and either the IP is internal or the
with an x-header indicating the view function. This is used by the user is a logged-in staff member, quickly return with an x-header
documentation module to lookup the view function for an arbitrary page. indicating the view function. This is used by the documentation module
to lookup the view function for an arbitrary page.
""" """
if request.method == 'HEAD' and request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS: if request.method == 'HEAD' and (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS or (request.user.is_authenticated() and request.user.is_staff)):
response = http.HttpResponse() response = http.HttpResponse()
response['X-View'] = "%s.%s" % (view_func.__module__, view_func.__name__) response['X-View'] = "%s.%s" % (view_func.__module__, view_func.__name__)
return response return response

View File

@ -15,7 +15,7 @@ register = Library()
def addslashes(value): 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("'", "\\'") return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
def capfirst(value): def capfirst(value):
"Capitalizes the first character of the value" "Capitalizes the first character of the value"

View File

@ -13,14 +13,18 @@ class CommentNode(Node):
return '' return ''
class CycleNode(Node): class CycleNode(Node):
def __init__(self, cyclevars): def __init__(self, cyclevars, variable_name=None):
self.cyclevars = cyclevars self.cyclevars = cyclevars
self.cyclevars_len = len(cyclevars) self.cyclevars_len = len(cyclevars)
self.counter = -1 self.counter = -1
self.variable_name = variable_name
def render(self, context): def render(self, context):
self.counter += 1 self.counter += 1
return self.cyclevars[self.counter % self.cyclevars_len] value = self.cyclevars[self.counter % self.cyclevars_len]
if self.variable_name:
context[self.variable_name] = value
return value
class DebugNode(Node): class DebugNode(Node):
def render(self, context): def render(self, context):
@ -125,6 +129,8 @@ class IfChangedNode(Node):
self._last_seen = None self._last_seen = None
def render(self, context): def render(self, context):
if context.has_key('forloop') and context['forloop']['first']:
self._last_seen = None
content = self.nodelist.render(context) content = self.nodelist.render(context)
if content != self._last_seen: if content != self._last_seen:
firstloop = (self._last_seen == None) firstloop = (self._last_seen == None)
@ -385,7 +391,7 @@ def cycle(parser, token):
raise TemplateSyntaxError("Second 'cycle' argument must be 'as'") raise TemplateSyntaxError("Second 'cycle' argument must be 'as'")
cyclevars = [v for v in args[1].split(",") if v] # split and kill blanks cyclevars = [v for v in args[1].split(",") if v] # split and kill blanks
name = args[3] name = args[3]
node = CycleNode(cyclevars) node = CycleNode(cyclevars, name)
if not hasattr(parser, '_namedCycleNodes'): if not hasattr(parser, '_namedCycleNodes'):
parser._namedCycleNodes = {} parser._namedCycleNodes = {}

View File

@ -1319,13 +1319,16 @@ class DocTestRunner:
__LINECACHE_FILENAME_RE = re.compile(r'<doctest ' __LINECACHE_FILENAME_RE = re.compile(r'<doctest '
r'(?P<name>[\w\.]+)' r'(?P<name>[\w\.]+)'
r'\[(?P<examplenum>\d+)\]>$') r'\[(?P<examplenum>\d+)\]>$')
def __patched_linecache_getlines(self, filename): def __patched_linecache_getlines(self, filename, module_globals=None):
m = self.__LINECACHE_FILENAME_RE.match(filename) m = self.__LINECACHE_FILENAME_RE.match(filename)
if m and m.group('name') == self.test.name: if m and m.group('name') == self.test.name:
example = self.test.examples[int(m.group('examplenum'))] example = self.test.examples[int(m.group('examplenum'))]
return example.source.splitlines(True) return example.source.splitlines(True)
else: else:
if sys.version_info < (2, 5, 0):
return self.save_linecache_getlines(filename) return self.save_linecache_getlines(filename)
else:
return self.save_linecache_getlines(filename, module_globals)
def run(self, test, compileflags=None, out=None, clear_globs=True): def run(self, test, compileflags=None, out=None, clear_globs=True):
""" """

View File

@ -14,6 +14,9 @@ class MergeDict(object):
pass pass
raise KeyError raise KeyError
def __contains__(self, key):
return self.has_key(key)
def get(self, key, default): def get(self, key, default):
try: try:
return self[key] return self[key]

View File

@ -3,12 +3,11 @@ Iterator based sre token scanner
""" """
import sre_parse, sre_compile, sre_constants import sre_parse, sre_compile, sre_constants
from sre_constants import BRANCH, SUBPATTERN from sre_constants import BRANCH, SUBPATTERN
from sre import VERBOSE, MULTILINE, DOTALL
import re import re
__all__ = ['Scanner', 'pattern'] __all__ = ['Scanner', 'pattern']
FLAGS = (VERBOSE | MULTILINE | DOTALL) FLAGS = (re.VERBOSE | re.MULTILINE | re.DOTALL)
class Scanner(object): class Scanner(object):
def __init__(self, lexicon, flags=FLAGS): def __init__(self, lexicon, flags=FLAGS):
self.actions = [None] self.actions = [None]

View File

@ -94,7 +94,8 @@ def compress_string(s):
return zbuf.getvalue() return zbuf.getvalue()
ustring_re = re.compile(u"([\u0080-\uffff])") ustring_re = re.compile(u"([\u0080-\uffff])")
def javascript_quote(s):
def javascript_quote(s, quote_double_quotes=False):
def fix(match): def fix(match):
return r"\u%04x" % ord(match.group(1)) return r"\u%04x" % ord(match.group(1))
@ -104,9 +105,12 @@ def javascript_quote(s):
elif type(s) != unicode: elif type(s) != unicode:
raise TypeError, s raise TypeError, s
s = s.replace('\\', '\\\\') s = s.replace('\\', '\\\\')
s = s.replace('\r', '\\r')
s = s.replace('\n', '\\n') s = s.replace('\n', '\\n')
s = s.replace('\t', '\\t') s = s.replace('\t', '\\t')
s = s.replace("'", "\\'") s = s.replace("'", "\\'")
if quote_double_quotes:
s = s.replace('"', '&quot;')
return str(ustring_re.sub(fix, s)) return str(ustring_re.sub(fix, s))
smart_split_re = re.compile('("(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'|[^\\s]+)') smart_split_re = re.compile('("(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'|[^\\s]+)')

View File

@ -4,7 +4,7 @@ from django.utils.html import escape
from django.http import HttpResponseServerError, HttpResponseNotFound from django.http import HttpResponseServerError, HttpResponseNotFound
import os, re import os, re
HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD') HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST')
def linebreak_iter(template_source): def linebreak_iter(template_source):
yield 0 yield 0

View File

@ -456,6 +456,10 @@ As a shortcut, you can use the convenient ``user_passes_test`` decorator::
# ... # ...
my_view = user_passes_test(lambda u: u.has_perm('polls.can_vote'))(my_view) my_view = user_passes_test(lambda u: u.has_perm('polls.can_vote'))(my_view)
We are using this particular test as a relatively simple example, however be
aware that if you just want to test if a permission is available to a user,
you can use the ``permission_required()`` decorator described below.
Here's the same thing, using Python 2.4's decorator syntax:: Here's the same thing, using Python 2.4's decorator syntax::
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
@ -488,6 +492,24 @@ Example in Python 2.4 syntax::
def my_view(request): def my_view(request):
# ... # ...
The permission_required decorator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Since checking whether a user has a particular permission available to them is a
relatively common operation, Django provides a shortcut for that particular
case: the ``permission_required()`` decorator. Using this decorator, the
earlier example can be written as::
from django.contrib.auth.decorators import permission_required
def my_view(request):
# ...
my_view = permission_required('polls.can_vote')(my_view)
Note that ``permission_required()`` also takes an optional ``login_url``
parameter.
Limiting access to generic views Limiting access to generic views
-------------------------------- --------------------------------
@ -677,7 +699,7 @@ timestamps.
Messages are used by the Django admin after successful actions. For example, Messages are used by the Django admin after successful actions. For example,
``"The poll Foo was created successfully."`` is a message. ``"The poll Foo was created successfully."`` is a message.
The API is simple:: The API is simple:
* To create a new message, use * To create a new message, use
``user_obj.message_set.create(message='message_text')``. ``user_obj.message_set.create(message='message_text')``.

View File

@ -247,18 +247,23 @@ Django tarball. It's our policy to make sure all tests pass at all times.
The tests cover: The tests cover:
* Models and the database API (``tests/testapp/models``). * Models and the database API (``tests/modeltests/``).
* The cache system (``tests/otherthests/cache.py``). * The cache system (``tests/regressiontests/cache.py``).
* The ``django.utils.dateformat`` module (``tests/othertests/dateformat.py``). * The ``django.utils.dateformat`` module (``tests/regressiontests/dateformat/``).
* Database typecasts (``tests/othertests/db_typecasts.py``). * Database typecasts (``tests/regressiontests/db_typecasts/``).
* The template system (``tests/othertests/templates.py`` and * The template system (``tests/regressiontests/templates/`` and
``tests/othertests/defaultfilters.py``). ``tests/regressiontests/defaultfilters/``).
* ``QueryDict`` objects (``tests/othertests/httpwrappers.py``). * ``QueryDict`` objects (``tests/regressiontests/httpwrappers/``).
* Markup template tags (``tests/othertests/markup.py``). * Markup template tags (``tests/regressiontests/markup/``).
* The ``django.utils.timesince`` module (``tests/othertests/timesince.py``).
We appreciate any and all contributions to the test suite! We appreciate any and all contributions to the test suite!
The Django tests all use the testing infrastructure that ships with Django for
testing applications. See `Testing Django Applications`_ for an explanation of
how to write new tests.
.. _Testing Django Applications: http://www.djangoproject.com/documentation/testing/
Running the unit tests Running the unit tests
---------------------- ----------------------
@ -268,10 +273,14 @@ To run the tests, ``cd`` to the ``tests/`` directory and type::
Yes, the unit tests need a settings module, but only for database connection Yes, the unit tests need a settings module, but only for database connection
info -- the ``DATABASE_ENGINE``, ``DATABASE_USER`` and ``DATABASE_PASSWORD``. info -- the ``DATABASE_ENGINE``, ``DATABASE_USER`` and ``DATABASE_PASSWORD``.
You will also need a ``ROOT_URLCONF`` setting (it's value is ignored; it just
needs to be present) and a ``SITE_ID`` setting (any integer value will do) in
order for all the tests to pass.
The unit tests will not touch your database; they create a new database, called The unit tests will not touch your existing databases; they create a new
``django_test_db``, which is deleted when the tests are finished. This means database, called ``django_test_db``, which is deleted when the tests are
your user account needs permission to execute ``CREATE DATABASE``. finished. This means your user account needs permission to execute ``CREATE
DATABASE``.
Requesting features Requesting features
=================== ===================

View File

@ -1140,7 +1140,7 @@ The pk lookup shortcut
---------------------- ----------------------
For convenience, Django provides a ``pk`` lookup type, which stands for For convenience, Django provides a ``pk`` lookup type, which stands for
"primary_key". This is shorthand for "an exact lookup on the primary-key." "primary_key".
In the example ``Blog`` model, the primary key is the ``id`` field, so these In the example ``Blog`` model, the primary key is the ``id`` field, so these
three statements are equivalent:: three statements are equivalent::
@ -1149,6 +1149,14 @@ three statements are equivalent::
Blog.objects.get(id=14) # __exact is implied Blog.objects.get(id=14) # __exact is implied
Blog.objects.get(pk=14) # pk implies id__exact Blog.objects.get(pk=14) # pk implies id__exact
The use of ``pk`` isn't limited to ``__exact`` queries -- any query term
can be combined with ``pk`` to perform a query on the primary key of a model::
# Get blogs entries with id 1, 4 and 7
Blog.objects.filter(pk__in=[1,4,7])
# Get all blog entries with id > 14
Blog.objects.filter(pk__gt=14)
``pk`` lookups also work across joins. For example, these three statements are ``pk`` lookups also work across joins. For example, these three statements are
equivalent:: equivalent::
@ -1511,7 +1519,7 @@ Many-to-many relationships
-------------------------- --------------------------
Both ends of a many-to-many relationship get automatic API access to the other Both ends of a many-to-many relationship get automatic API access to the other
end. The API works just as a "backward" one-to-many relationship. See _Backward end. The API works just as a "backward" one-to-many relationship. See Backward_
above. above.
The only difference is in the attribute naming: The model that defines the The only difference is in the attribute naming: The model that defines the

View File

@ -352,8 +352,9 @@ options.
**New in Django development version** **New in Django development version**
Inform django-admin that the user should NOT be prompted for any input. Useful if Inform django-admin that the user should NOT be prompted for any input. Useful
the django-admin script will be executed as an unattended, automated script. if the django-admin script will be executed as an unattended, automated
script.
--noreload --noreload
---------- ----------
@ -383,6 +384,19 @@ Verbosity determines the amount of notification and debug information that
will be printed to the console. '0' is no output, '1' is normal output, will be printed to the console. '0' is no output, '1' is normal output,
and `2` is verbose output. and `2` is verbose output.
--adminmedia
------------
**New in Django development version**
Example usage::
django-admin.py manage.py --adminmedia=/tmp/new-admin-style/
Tell Django where to find the various stylesheets and Javascript files for the
admin interface when running the development server. Normally these files are
served out of the Django source tree, but since some designers change these
files for their site, this option allows you to test against custom versions.
Extra niceties Extra niceties
============== ==============

View File

@ -136,7 +136,7 @@ template::
{% endblock %} {% endblock %}
Before we get back to the problems with these naive set of views, let's go over Before we get back to the problems with these naive set of views, let's go over
some salient points of the above template:: some salient points of the above template:
* Field "widgets" are handled for you: ``{{ form.field }}`` automatically * Field "widgets" are handled for you: ``{{ form.field }}`` automatically
creates the "right" type of widget for the form, as you can see with the creates the "right" type of widget for the form, as you can see with the
@ -148,8 +148,8 @@ some salient points of the above template::
If you must use tables, use tables. If you're a semantic purist, you can If you must use tables, use tables. If you're a semantic purist, you can
probably find better HTML than in the above template. probably find better HTML than in the above template.
* To avoid name conflicts, the ``id``s of form elements take the form * To avoid name conflicts, the ``id`` values of form elements take the
"id_*fieldname*". form "id_*fieldname*".
By creating a creation form we've solved problem number 3 above, but we still By creating a creation form we've solved problem number 3 above, but we still
don't have any validation. Let's revise the validation issue by writing a new don't have any validation. Let's revise the validation issue by writing a new
@ -481,6 +481,33 @@ the data being validated.
Also, because consistency in user interfaces is important, we strongly urge you Also, because consistency in user interfaces is important, we strongly urge you
to put punctuation at the end of your validation messages. to put punctuation at the end of your validation messages.
When Are Validators Called?
---------------------------
After a form has been submitted, Django first checks to see that all the
required fields are present and non-empty. For each field that passes that
test *and if the form submission contained data* for that field, all the
validators for that field are called in turn. The emphasised portion in the
last sentence is important: if a form field is not submitted (because it
contains no data -- which is normal HTML behaviour), the validators are not
run against the field.
This feature is particularly important for models using
``models.BooleanField`` or custom manipulators using things like
``forms.CheckBoxField``. If the checkbox is not selected, it will not
contribute to the form submission.
If you would like your validator to *always* run, regardless of whether the
field it is attached to contains any data, set the ``always_test`` attribute
on the validator function. For example::
def my_custom_validator(field_data, all_data):
# ...
my_custom_validator.always_test = True
This validator will always be executed for any field it is attached to.
Ready-made Validators Ready-made Validators
--------------------- ---------------------

View File

@ -543,7 +543,9 @@ The default value for the field.
``editable`` ``editable``
~~~~~~~~~~~~ ~~~~~~~~~~~~
If ``False``, the field will not be editable in the admin. Default is ``True``. If ``False``, the field will not be editable in the admin or via form
processing using the object's ``AddManipulator`` or ``ChangeManipulator``
classes. Default is ``True``.
``help_text`` ``help_text``
~~~~~~~~~~~~~ ~~~~~~~~~~~~~

View File

@ -96,6 +96,21 @@ Django "ships" with a few included serializers:
.. _json: http://json.org/ .. _json: http://json.org/
.. _simplejson: http://undefined.org/python/#simplejson .. _simplejson: http://undefined.org/python/#simplejson
Notes For Specific Serialization Formats
----------------------------------------
json
~~~~
If you are using UTF-8 (or any other non-ASCII encoding) data with the JSON
serializer, you must pass ``ensure_ascii=False`` as a parameter to the
``serialize()`` call. Otherwise the output will not be encoded correctly.
For example::
json_serializer = serializers.get_serializer("json")
json_serializer.serialize(queryset, ensure_ascii=False, stream=response)
Writing custom serializers Writing custom serializers
`````````````````````````` ``````````````````````````

View File

@ -596,6 +596,12 @@ Whether to prepend the "www." subdomain to URLs that don't have it. This is
only used if ``CommonMiddleware`` is installed (see the `middleware docs`_). only used if ``CommonMiddleware`` is installed (see the `middleware docs`_).
See also ``APPEND_SLASH``. See also ``APPEND_SLASH``.
PROFANITIES_LIST
----------------
A list of profanities that will trigger a validation error when the
``hasNoProfanities`` validator is called.
ROOT_URLCONF ROOT_URLCONF
------------ ------------

View File

@ -763,17 +763,17 @@ will use the function's name as the tag name.
Shortcut for simple tags Shortcut for simple tags
~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~
Many template tags take a single argument -- a string or a template variable Many template tags take a number of arguments -- strings or a template variables
reference -- and return a string after doing some processing based solely on -- and return a string after doing some processing based solely on
the input argument and some external information. For example, the the input argument and some external information. For example, the
``current_time`` tag we wrote above is of this variety: we give it a format ``current_time`` tag we wrote above is of this variety: we give it a format
string, it returns the time as a string. string, it returns the time as a string.
To ease the creation of the types of tags, Django provides a helper function, To ease the creation of the types of tags, Django provides a helper function,
``simple_tag``. This function, which is a method of ``simple_tag``. This function, which is a method of
``django.template.Library``, takes a function that accepts one argument, wraps ``django.template.Library``, takes a function that accepts any number of
it in a ``render`` function and the other necessary bits mentioned above and arguments, wraps it in a ``render`` function and the other necessary bits
registers it with the template system. mentioned above and registers it with the template system.
Our earlier ``current_time`` function could thus be written like this:: Our earlier ``current_time`` function could thus be written like this::
@ -789,11 +789,16 @@ In Python 2.4, the decorator syntax also works::
... ...
A couple of things to note about the ``simple_tag`` helper function: A couple of things to note about the ``simple_tag`` helper function:
* Only the (single) argument is passed into our function.
* Checking for the required number of arguments, etc, has already been * Checking for the required number of arguments, etc, has already been
done by the time our function is called, so we don't need to do that. done by the time our function is called, so we don't need to do that.
* The quotes around the argument (if any) have already been stripped away, * The quotes around the argument (if any) have already been stripped away,
so we just receive a plain string. so we just receive a plain string.
* If the argument was a template variable, our function is passed the
current value of the variable, not the variable itself.
When your template tag does not need access to the current context, writing a
function to work with the input values and using the ``simple_tag`` helper is
the easiest way to create a new tag.
Inclusion tags Inclusion tags
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~

View File

@ -26,6 +26,7 @@ setup(
'media/js/*.js', 'media/js/*.js',
'media/js/admin/*js'], 'media/js/admin/*js'],
'django.contrib.comments': ['templates/comments/*.html'], 'django.contrib.comments': ['templates/comments/*.html'],
'django.contrib.sitemaps': ['templates/*.xml'],
}, },
scripts = ['django/bin/django-admin.py'], scripts = ['django/bin/django-admin.py'],
zip_safe = False, zip_safe = False,

View File

@ -86,6 +86,10 @@ DoesNotExist: Article matching query does not exist.
>>> Article.objects.get(pk=1) >>> Article.objects.get(pk=1)
<Article: Area woman programs in Python> <Article: Area woman programs in Python>
# pk can be used as a shortcut for the primary key name in any query
>>> Article.objects.filter(pk__in=[1])
[<Article: Area woman programs in Python>]
# Model instances of the same type and same ID are considered equal. # Model instances of the same type and same ID are considered equal.
>>> a = Article.objects.get(pk=1) >>> a = Article.objects.get(pk=1)
>>> b = Article.objects.get(pk=1) >>> b = Article.objects.get(pk=1)

View File

@ -51,6 +51,10 @@ DoesNotExist: Employee matching query does not exist.
>>> Employee.objects.get(employee_code__exact='ABC123') >>> Employee.objects.get(employee_code__exact='ABC123')
<Employee: Dan Jones> <Employee: Dan Jones>
# pk can be used as a substitute for the primary key.
>>> Employee.objects.filter(pk__in=['ABC123','XYZ456'])
[<Employee: Fran Bones>, <Employee: Dan Jones>]
# Fran got married and changed her last name. # Fran got married and changed her last name.
>>> fran = Employee.objects.get(pk='XYZ456') >>> fran = Employee.objects.get(pk='XYZ456')
>>> fran.last_name = 'Jones' >>> fran.last_name = 'Jones'

View File

@ -15,6 +15,9 @@ r"""
>>> addslashes('"double quotes" and \'single quotes\'') >>> addslashes('"double quotes" and \'single quotes\'')
'\\"double quotes\\" and \\\'single quotes\\\'' '\\"double quotes\\" and \\\'single quotes\\\''
>>> addslashes(r'\ : backslashes, too')
'\\\\ : backslashes, too'
>>> capfirst('hello world') >>> capfirst('hello world')
'Hello world' 'Hello world'

View File

@ -4,4 +4,5 @@ INSERT INTO initial_sql_regress_simple (name) VALUES ('Ringo');
INSERT INTO initial_sql_regress_simple (name) VALUES ('George'); INSERT INTO initial_sql_regress_simple (name) VALUES ('George');
INSERT INTO initial_sql_regress_simple (name) VALUES ('Miles O''Brien'); INSERT INTO initial_sql_regress_simple (name) VALUES ('Miles O''Brien');
INSERT INTO initial_sql_regress_simple (name) VALUES ('Semicolon;Man'); INSERT INTO initial_sql_regress_simple (name) VALUES ('Semicolon;Man');
INSERT INTO initial_sql_regress_simple (name) VALUES ('This line has a Windows line ending');

View File

@ -187,6 +187,7 @@ class Templates(unittest.TestCase):
'cycle05': ('{% cycle %}', {}, template.TemplateSyntaxError), 'cycle05': ('{% cycle %}', {}, template.TemplateSyntaxError),
'cycle06': ('{% cycle a %}', {}, template.TemplateSyntaxError), 'cycle06': ('{% cycle a %}', {}, template.TemplateSyntaxError),
'cycle07': ('{% cycle a,b,c as foo %}{% cycle bar %}', {}, template.TemplateSyntaxError), 'cycle07': ('{% cycle a,b,c as foo %}{% cycle bar %}', {}, template.TemplateSyntaxError),
'cycle08': ('{% cycle a,b,c as foo %}{% cycle foo %}{{ foo }}{{ foo }}{% cycle foo %}{{ foo }}', {}, 'abbbcc'),
### EXCEPTIONS ############################################################ ### EXCEPTIONS ############################################################
@ -304,6 +305,10 @@ class Templates(unittest.TestCase):
'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,2,3) }, '123'), 'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,2,3) }, '123'),
'ifchanged02': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,3) }, '13'), 'ifchanged02': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,3) }, '13'),
'ifchanged03': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,1) }, '1'), 'ifchanged03': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,1) }, '1'),
'ifchanged04': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 2, 3), 'numx': (2, 2, 2)}, '122232'),
'ifchanged05': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (1, 2, 3)}, '1123123123'),
'ifchanged06': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2)}, '1222'),
'ifchanged07': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% for y in numy %}{% ifchanged %}{{ y }}{% endifchanged %}{% endfor %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2), 'numy': (3, 3, 3)}, '1233323332333'),
### IFEQUAL TAG ########################################################### ### IFEQUAL TAG ###########################################################
'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""), 'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""),