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

newforms-admin: Merged from trunk up to [6670]. This is just before auto-escaping was checked in.

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@6761 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Joseph Kocherhans 2007-11-30 04:32:25 +00:00
parent 8b3d65e517
commit 4d52c80dd1
29 changed files with 3598 additions and 2093 deletions

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -58,7 +58,7 @@ 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 "Söndag Mondag Tisdag Onsdag Torsdag Fredag Lördag" msgstr "Söndag Måndag Tisdag Onsdag Torsdag Fredag Lördag"
#: contrib/admin/media/js/calendar.js:25 #: contrib/admin/media/js/calendar.js:25
msgid "S M T W T F S" msgid "S M T W T F S"
@ -96,7 +96,7 @@ msgstr "06.00"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:84
msgid "Noon" msgid "Noon"
msgstr "Mitt på dagen" msgstr "Middag"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:88
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:183
@ -118,5 +118,5 @@ msgstr "Igår"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:179
msgid "Tomorrow" msgid "Tomorrow"
msgstr "Imorgon" msgstr "I morgon"

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

@ -6,8 +6,6 @@ class ConditionalGetMiddleware(object):
Last-Modified header, and the request has If-None-Match or Last-Modified header, and the request has If-None-Match or
If-Modified-Since, the response is replaced by an HttpNotModified. If-Modified-Since, the response is replaced by an HttpNotModified.
Removes the content from any response to a HEAD request.
Also sets the Date and Content-Length response-headers. Also sets the Date and Content-Length response-headers.
""" """
def process_response(self, request, response): def process_response(self, request, response):
@ -18,19 +16,17 @@ class ConditionalGetMiddleware(object):
if response.has_header('ETag'): if response.has_header('ETag'):
if_none_match = request.META.get('HTTP_IF_NONE_MATCH', None) if_none_match = request.META.get('HTTP_IF_NONE_MATCH', None)
if if_none_match == response['ETag']: if if_none_match == response['ETag']:
response.status_code = 304 # Setting the status is enough here. The response handling path
response.content = '' # automatically removes content for this status code (in
response['Content-Length'] = '0' # http.conditional_content_removal()).
response.status = 304
if response.has_header('Last-Modified'): if response.has_header('Last-Modified'):
if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None) if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None)
if if_modified_since == response['Last-Modified']: if if_modified_since == response['Last-Modified']:
response.status_code = 304 # Setting the status code is enough here (same reasons as
response.content = '' # above).
response['Content-Length'] = '0' response.status = 304
if request.method == 'HEAD':
response.content = ''
return response return response

View File

@ -2,7 +2,7 @@
Form classes Form classes
""" """
import copy from copy import deepcopy
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from django.utils.html import escape from django.utils.html import escape
@ -21,18 +21,6 @@ def pretty_name(name):
name = name[0].upper() + name[1:] name = name[0].upper() + name[1:]
return name.replace('_', ' ') return name.replace('_', ' ')
class SortedDictFromList(SortedDict):
"A dictionary that keeps its keys in the order in which they're inserted."
# This is different than django.utils.datastructures.SortedDict, because
# this takes a list/tuple as the argument to __init__().
def __init__(self, data=None):
if data is None: data = []
self.keyOrder = [d[0] for d in data]
dict.__init__(self, dict(data))
def copy(self):
return SortedDictFromList([(k, copy.deepcopy(v)) for k, v in self.items()])
class DeclarativeFieldsMetaclass(type): class DeclarativeFieldsMetaclass(type):
""" """
Metaclass that converts Field attributes to a dictionary called Metaclass that converts Field attributes to a dictionary called
@ -50,7 +38,7 @@ class DeclarativeFieldsMetaclass(type):
if hasattr(base, 'base_fields'): if hasattr(base, 'base_fields'):
fields = base.base_fields.items() + fields fields = base.base_fields.items() + fields
attrs['base_fields'] = SortedDictFromList(fields) attrs['base_fields'] = SortedDict(fields)
new_class = type.__new__(cls, name, bases, attrs) new_class = type.__new__(cls, name, bases, attrs)
if 'media' not in attrs: if 'media' not in attrs:
@ -79,7 +67,7 @@ class BaseForm(StrAndUnicode):
# alter self.fields, we create self.fields here by copying base_fields. # alter self.fields, we create self.fields here by copying base_fields.
# Instances should always modify self.fields; they should not modify # Instances should always modify self.fields; they should not modify
# self.base_fields. # self.base_fields.
self.fields = self.base_fields.copy() self.fields = deepcopy(self.base_fields)
def __unicode__(self): def __unicode__(self):
return self.as_table() return self.as_table()

View File

@ -5,9 +5,10 @@ and database field objects.
from django.utils.translation import ugettext from django.utils.translation import ugettext
from django.utils.encoding import smart_unicode from django.utils.encoding import smart_unicode
from django.utils.datastructures import SortedDict
from util import ValidationError from util import ValidationError
from forms import BaseForm, SortedDictFromList from forms import BaseForm
from fields import Field, ChoiceField, IntegerField from fields import Field, ChoiceField, IntegerField
from formsets import BaseFormSet, formset_for_form, DELETION_FIELD_NAME from formsets import BaseFormSet, formset_for_form, DELETION_FIELD_NAME
from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
@ -91,7 +92,7 @@ def form_for_model(model, form=BaseForm, fields=None,
formfield = formfield_callback(f) formfield = formfield_callback(f)
if formfield: if formfield:
field_list.append((f.name, formfield)) field_list.append((f.name, formfield))
base_fields = SortedDictFromList(field_list) base_fields = SortedDict(field_list)
return type(opts.object_name + 'Form', (form,), return type(opts.object_name + 'Form', (form,),
{'base_fields': base_fields, '_model': model, {'base_fields': base_fields, '_model': model,
'save': make_model_save(model, fields, 'created')}) 'save': make_model_save(model, fields, 'created')})
@ -120,7 +121,7 @@ def form_for_instance(instance, form=BaseForm, fields=None,
formfield = formfield_callback(f, initial=current_value) formfield = formfield_callback(f, initial=current_value)
if formfield: if formfield:
field_list.append((f.name, formfield)) field_list.append((f.name, formfield))
base_fields = SortedDictFromList(field_list) base_fields = SortedDict(field_list)
return type(opts.object_name + 'InstanceForm', (form,), return type(opts.object_name + 'InstanceForm', (form,),
{'base_fields': base_fields, '_model': model, {'base_fields': base_fields, '_model': model,
'save': make_instance_save(instance, fields, 'changed')}) 'save': make_instance_save(instance, fields, 'changed')})
@ -129,8 +130,8 @@ def form_for_fields(field_list):
""" """
Returns a Form class for the given list of Django database field instances. Returns a Form class for the given list of Django database field instances.
""" """
fields = SortedDictFromList([(f.name, f.formfield()) fields = SortedDict([(f.name, f.formfield())
for f in field_list if f.editable]) for f in field_list if f.editable])
return type('FormForFields', (BaseForm,), {'base_fields': fields}) return type('FormForFields', (BaseForm,), {'base_fields': fields})
class QuerySetIterator(object): class QuerySetIterator(object):
@ -156,14 +157,22 @@ class ModelChoiceField(ChoiceField):
def __init__(self, queryset, empty_label=u"---------", cache_choices=False, def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
required=True, widget=Select, label=None, initial=None, required=True, widget=Select, label=None, initial=None,
help_text=None): help_text=None):
self.queryset = queryset
self.empty_label = empty_label self.empty_label = empty_label
self.cache_choices = cache_choices self.cache_choices = cache_choices
# Call Field instead of ChoiceField __init__() because we don't need # Call Field instead of ChoiceField __init__() because we don't need
# ChoiceField.__init__(). # ChoiceField.__init__().
Field.__init__(self, required, widget, label, initial, help_text) Field.__init__(self, required, widget, label, initial, help_text)
self.queryset = queryset
def _get_queryset(self):
return self._queryset
def _set_queryset(self, queryset):
self._queryset = queryset
self.widget.choices = self.choices self.widget.choices = self.choices
queryset = property(_get_queryset, _set_queryset)
def _get_choices(self): def _get_choices(self):
# If self._choices is set, then somebody must have manually set # If self._choices is set, then somebody must have manually set
# the property self.choices. In this case, just return self._choices. # the property self.choices. In this case, just return self._choices.
@ -191,7 +200,7 @@ class ModelChoiceField(ChoiceField):
if value in ('', None): if value in ('', None):
return None return None
try: try:
value = self.queryset.model._default_manager.get(pk=value) value = self.queryset.get(pk=value)
except self.queryset.model.DoesNotExist: except self.queryset.model.DoesNotExist:
raise ValidationError(ugettext(u'Select a valid choice. That' raise ValidationError(ugettext(u'Select a valid choice. That'
u' choice is not one of the' u' choice is not one of the'
@ -218,7 +227,7 @@ class ModelMultipleChoiceField(ModelChoiceField):
final_values = [] final_values = []
for val in value: for val in value:
try: try:
obj = self.queryset.model._default_manager.get(pk=val) obj = self.queryset.get(pk=val)
except self.queryset.model.DoesNotExist: except self.queryset.model.DoesNotExist:
raise ValidationError(ugettext(u'Select a valid choice. %s is' raise ValidationError(ugettext(u'Select a valid choice. %s is'
u' not one of the available' u' not one of the available'

View File

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

View File

@ -1,6 +1,6 @@
import re import re
import unittest import unittest
from urlparse import urlsplit from urlparse import urlsplit, urlunsplit
from django.http import QueryDict from django.http import QueryDict
from django.db import transaction from django.db import transaction
@ -74,7 +74,7 @@ class TestCase(unittest.TestCase):
super(TestCase, self).__call__(result) super(TestCase, self).__call__(result)
def assertRedirects(self, response, expected_url, status_code=302, def assertRedirects(self, response, expected_url, status_code=302,
target_status_code=200): target_status_code=200, host=None):
"""Asserts that a response redirected to a specific URL, and that the """Asserts that a response redirected to a specific URL, and that the
redirect URL can be loaded. redirect URL can be loaded.
@ -86,6 +86,10 @@ class TestCase(unittest.TestCase):
" (expected %d)" % (response.status_code, status_code))) " (expected %d)" % (response.status_code, status_code)))
url = response['Location'] url = response['Location']
scheme, netloc, path, query, fragment = urlsplit(url) scheme, netloc, path, query, fragment = urlsplit(url)
e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url)
if not (e_scheme or e_netloc):
expected_url = urlunsplit(('http', host or 'testserver', e_path,
e_query, e_fragment))
self.assertEqual(url, expected_url, self.assertEqual(url, expected_url,
"Response redirected to '%s', expected '%s'" % (url, expected_url)) "Response redirected to '%s', expected '%s'" % (url, expected_url))

View File

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

View File

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

View File

@ -440,6 +440,8 @@ the data in the database when the form is instantiated.
>>> from django.newforms import ModelChoiceField, ModelMultipleChoiceField >>> from django.newforms import ModelChoiceField, ModelMultipleChoiceField
>>> f = ModelChoiceField(Category.objects.all()) >>> f = ModelChoiceField(Category.objects.all())
>>> list(f.choices)
[(u'', u'---------'), (1, u'Entertainment'), (2, u"It's a test"), (3, u'Third'), (4, u'Fourth')]
>>> f.clean('') >>> f.clean('')
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -485,9 +487,23 @@ Traceback (most recent call last):
... ...
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
# queryset can be changed after the field is created.
>>> f.queryset = Category.objects.exclude(name='Fourth')
>>> list(f.choices)
[(u'', u'---------'), (1, u'Entertainment'), (2, u"It's a test"), (3, u'Third')]
>>> f.clean(3)
<Category: Third>
>>> f.clean(4)
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
# ModelMultipleChoiceField #################################################### # ModelMultipleChoiceField ####################################################
>>> f = ModelMultipleChoiceField(Category.objects.all()) >>> f = ModelMultipleChoiceField(Category.objects.all())
>>> list(f.choices)
[(1, u'Entertainment'), (2, u"It's a test"), (3, u'Third'), (4, u'Fourth')]
>>> f.clean(None) >>> f.clean(None)
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -517,7 +533,7 @@ Traceback (most recent call last):
... ...
ValidationError: [u'Enter a list of values.'] ValidationError: [u'Enter a list of values.']
# Add a Category object *after* the ModelChoiceField has already been # Add a Category object *after* the ModelMultipleChoiceField has already been
# instantiated. This proves clean() checks the database during clean() rather # instantiated. This proves clean() checks the database during clean() rather
# than caching it at time of instantiation. # than caching it at time of instantiation.
>>> Category.objects.create(id=6, name='Sixth', url='6th') >>> Category.objects.create(id=6, name='Sixth', url='6th')
@ -525,7 +541,7 @@ ValidationError: [u'Enter a list of values.']
>>> f.clean([6]) >>> f.clean([6])
[<Category: Sixth>] [<Category: Sixth>]
# Delete a Category object *after* the ModelChoiceField has already been # Delete a Category object *after* the ModelMultipleChoiceField has already been
# instantiated. This proves clean() checks the database during clean() rather # instantiated. This proves clean() checks the database during clean() rather
# than caching it at time of instantiation. # than caching it at time of instantiation.
>>> Category.objects.get(url='6th').delete() >>> Category.objects.get(url='6th').delete()
@ -552,6 +568,22 @@ Traceback (most recent call last):
... ...
ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] ValidationError: [u'Select a valid choice. 10 is not one of the available choices.']
# queryset can be changed after the field is created.
>>> f.queryset = Category.objects.exclude(name='Fourth')
>>> list(f.choices)
[(1, u'Entertainment'), (2, u"It's a test"), (3, u'Third')]
>>> f.clean([3])
[<Category: Third>]
>>> f.clean([4])
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. 4 is not one of the available choices.']
>>> f.clean(['3', '4'])
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. 4 is not one of the available choices.']
# PhoneNumberField ############################################################ # PhoneNumberField ############################################################
>>> PhoneNumberForm = form_for_model(PhoneNumber) >>> PhoneNumberForm = form_for_model(PhoneNumber)

View File

@ -83,13 +83,16 @@ class ClientTest(TestCase):
def test_redirect(self): def test_redirect(self):
"GET a URL that redirects elsewhere" "GET a URL that redirects elsewhere"
response = self.client.get('/test_client/redirect_view/') response = self.client.get('/test_client/redirect_view/')
# Check that the response was a 302 (redirect) # Check that the response was a 302 (redirect) and that
self.assertRedirects(response, 'http://testserver/test_client/get_view/') # assertRedirect() understands to put an implicit http://testserver/ in
# front of non-absolute URLs.
self.assertRedirects(response, '/test_client/get_view/')
client_providing_host = Client(HTTP_HOST='django.testserver') host = 'django.testserver'
client_providing_host = Client(HTTP_HOST=host)
response = client_providing_host.get('/test_client/redirect_view/') response = client_providing_host.get('/test_client/redirect_view/')
# Check that the response was a 302 (redirect) with absolute URI # Check that the response was a 302 (redirect) with absolute URI
self.assertRedirects(response, 'http://django.testserver/test_client/get_view/') self.assertRedirects(response, '/test_client/get_view/', host=host)
def test_redirect_with_query(self): def test_redirect_with_query(self):
"GET a URL that redirects with given GET parameters" "GET a URL that redirects with given GET parameters"
@ -250,6 +253,22 @@ class ClientTest(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['user'].username, 'testclient') self.assertEqual(response.context['user'].username, 'testclient')
def test_view_with_method_login(self):
"Request a page that is protected with a @login_required method"
# Get the page without logging in. Should result in 302.
response = self.client.get('/test_client/login_protected_method_view/')
self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/login_protected_method_view/')
# Log in
login = self.client.login(username='testclient', password='password')
self.failUnless(login, 'Could not log in')
# Request a page that requires a login
response = self.client.get('/test_client/login_protected_method_view/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['user'].username, 'testclient')
def test_view_with_login_and_custom_redirect(self): def test_view_with_login_and_custom_redirect(self):
"Request a page that is protected with @login_required(redirect_field_name='redirect_to')" "Request a page that is protected with @login_required(redirect_field_name='redirect_to')"
@ -295,6 +314,40 @@ class ClientTest(TestCase):
response = self.client.get('/test_client/login_protected_view/') response = self.client.get('/test_client/login_protected_view/')
self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/login_protected_view/') self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/login_protected_view/')
def test_view_with_permissions(self):
"Request a page that is protected with @permission_required"
# Get the page without logging in. Should result in 302.
response = self.client.get('/test_client/permission_protected_view/')
self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/permission_protected_view/')
# Log in
login = self.client.login(username='testclient', password='password')
self.failUnless(login, 'Could not log in')
# Log in with wrong permissions. Should result in 302.
response = self.client.get('/test_client/permission_protected_view/')
self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/permission_protected_view/')
# TODO: Log in with right permissions and request the page again
def test_view_with_method_permissions(self):
"Request a page that is protected with a @permission_required method"
# Get the page without logging in. Should result in 302.
response = self.client.get('/test_client/permission_protected_method_view/')
self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/permission_protected_method_view/')
# Log in
login = self.client.login(username='testclient', password='password')
self.failUnless(login, 'Could not log in')
# Log in with wrong permissions. Should result in 302.
response = self.client.get('/test_client/permission_protected_method_view/')
self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/permission_protected_method_view/')
# TODO: Log in with right permissions and request the page again
def test_session_modifying_view(self): def test_session_modifying_view(self):
"Request a page that modifies the session" "Request a page that modifies the session"
# Session value isn't set initially # Session value isn't set initially

View File

@ -13,7 +13,10 @@ urlpatterns = patterns('',
(r'^form_view/$', views.form_view), (r'^form_view/$', views.form_view),
(r'^form_view_with_template/$', views.form_view_with_template), (r'^form_view_with_template/$', views.form_view_with_template),
(r'^login_protected_view/$', views.login_protected_view), (r'^login_protected_view/$', views.login_protected_view),
(r'^login_protected_method_view/$', views.login_protected_method_view),
(r'^login_protected_view_custom_redirect/$', views.login_protected_view_changed_redirect), (r'^login_protected_view_custom_redirect/$', views.login_protected_view_changed_redirect),
(r'^permission_protected_view/$', views.permission_protected_view),
(r'^permission_protected_method_view/$', views.permission_protected_method_view),
(r'^session_view/$', views.session_view), (r'^session_view/$', views.session_view),
(r'^broken_view/$', views.broken_view), (r'^broken_view/$', views.broken_view),
(r'^mail_sending_view/$', views.mail_sending_view), (r'^mail_sending_view/$', views.mail_sending_view),

View File

@ -3,7 +3,7 @@ from xml.dom.minidom import parseString
from django.core.mail import EmailMessage, SMTPConnection from django.core.mail import EmailMessage, SMTPConnection
from django.template import Context, Template from django.template import Context, Template
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required, permission_required
from django.newforms.forms import Form from django.newforms.forms import Form
from django.newforms import fields from django.newforms import fields
from django.shortcuts import render_to_response from django.shortcuts import render_to_response
@ -130,6 +130,38 @@ def login_protected_view_changed_redirect(request):
return HttpResponse(t.render(c)) return HttpResponse(t.render(c))
login_protected_view_changed_redirect = login_required(redirect_field_name="redirect_to")(login_protected_view_changed_redirect) login_protected_view_changed_redirect = login_required(redirect_field_name="redirect_to")(login_protected_view_changed_redirect)
def permission_protected_view(request):
"A simple view that is permission protected."
t = Template('This is a permission protected test. '
'Username is {{ user.username }}. '
'Permissions are {{ user.get_all_permissions }}.' ,
name='Permissions Template')
c = Context({'user': request.user})
return HttpResponse(t.render(c))
permission_protected_view = permission_required('modeltests.test_perm')(permission_protected_view)
class _ViewManager(object):
def login_protected_view(self, request):
t = Template('This is a login protected test using a method. '
'Username is {{ user.username }}.',
name='Login Method Template')
c = Context({'user': request.user})
return HttpResponse(t.render(c))
login_protected_view = login_required(login_protected_view)
def permission_protected_view(self, request):
t = Template('This is a permission protected test using a method. '
'Username is {{ user.username }}. '
'Permissions are {{ user.get_all_permissions }}.' ,
name='Permissions Template')
c = Context({'user': request.user})
return HttpResponse(t.render(c))
permission_protected_view = permission_required('modeltests.test_perm')(permission_protected_view)
_view_manager = _ViewManager()
login_protected_method_view = _view_manager.login_protected_view
permission_protected_method_view = _view_manager.permission_protected_view
def session_view(request): def session_view(request):
"A view that modifies the session" "A view that modifies the session"
request.session['tobacconist'] = 'hovercraft' request.session['tobacconist'] = 'hovercraft'

View File

@ -119,7 +119,7 @@ class AssertRedirectsTests(TestCase):
try: try:
self.assertRedirects(response, '/test_client/get_view/') self.assertRedirects(response, '/test_client/get_view/')
except AssertionError, e: except AssertionError, e:
self.assertEquals(str(e), "Response redirected to 'http://testserver/test_client/get_view/?var=value', expected '/test_client/get_view/'") self.assertEquals(str(e), "Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'")
def test_incorrect_target(self): def test_incorrect_target(self):
"An assertion is raised if the response redirects to another target" "An assertion is raised if the response redirects to another target"