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:
parent
8b3d65e517
commit
4d52c80dd1
2
AUTHORS
2
AUTHORS
@ -121,6 +121,7 @@ answer newbie questions, and generally made Django that much better:
|
||||
Marc Fargas <telenieko@telenieko.com>
|
||||
Szilveszter Farkas <szilveszter.farkas@gmail.com>
|
||||
favo@exoweb.net
|
||||
Dmitri Fedortchenko <zeraien@gmail.com>
|
||||
Bill Fenner <fenner@gmail.com>
|
||||
Stefane Fermgier <sf@fermigier.com>
|
||||
Afonso Fernández Nogueira <fonzzo.django@gmail.com>
|
||||
@ -304,6 +305,7 @@ answer newbie questions, and generally made Django that much better:
|
||||
Tyler Tarabula <tyler.tarabula@gmail.com>
|
||||
Tyson Tate <tyson@fallingbullets.com>
|
||||
Frank Tegtmeyer <fte@fte.to>
|
||||
terryh.tp@gmail.com
|
||||
thebjorn <bp@datakortet.no>
|
||||
Zach Thompson <zthompson47@gmail.com>
|
||||
Michael Thornhill
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -7,8 +7,8 @@ msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2007-07-18 16:49+0200\n"
|
||||
"PO-Revision-Date: 2007-03-06 10:30+0100\n"
|
||||
"Last-Translator: Philip Lindborg <philip.lindborg@gmail.com>\n"
|
||||
"PO-Revision-Date: 2007-11-03 00:30+0100\n"
|
||||
"Last-Translator: Dmitri Fedortchenko <zeraien@gmail.com>\n"
|
||||
"Language-Team: Django I18N <Django-I18N@googlegroups.com>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -146,7 +146,7 @@ msgid_plural ""
|
||||
msgstr[0] ""
|
||||
"Var god och fyll giltiga %(self)s ID-nummer. Värdet %(value)r är ogiltigt."
|
||||
msgstr[1] ""
|
||||
"Var god och fyll giltiga %(self)s ID-nummer. Värdena %(value)r är ogiltiga."
|
||||
"Var god och fyll giltiga %(self)s ID-nummer. Värden %(value)r är ogiltiga."
|
||||
|
||||
#: conf/global_settings.py:38
|
||||
msgid "Arabic"
|
||||
@ -318,20 +318,20 @@ msgstr "Traditionell Kinesiska"
|
||||
|
||||
#: core/validators.py:71
|
||||
msgid "This value must contain only letters, numbers and underscores."
|
||||
msgstr "Det här värdet får bara innehålla bokstäver, siffror och understreck."
|
||||
msgstr "Det här värdet får endast innehålla bokstäver, siffror och understreck."
|
||||
|
||||
#: core/validators.py:75
|
||||
msgid ""
|
||||
"This value must contain only letters, numbers, underscores, dashes or "
|
||||
"slashes."
|
||||
msgstr ""
|
||||
"Det här värdet får bara innehålla bokstäver, siffror, understreck, "
|
||||
"Det här värdet får endast innehålla bokstäver, siffror, understreck, "
|
||||
"bindestreck eller snedstreck."
|
||||
|
||||
#: core/validators.py:79
|
||||
msgid "This value must contain only letters, numbers, underscores or hyphens."
|
||||
msgstr ""
|
||||
"Det här värdet får bara innehålla bokstäver, siffror, understreck eller "
|
||||
"Det här värdet får endast innehålla bokstäver, siffror, understreck eller "
|
||||
"avstavningstecken."
|
||||
|
||||
#: core/validators.py:83
|
||||
@ -352,7 +352,7 @@ msgstr "Fyll i giltiga e-postadresser avskilda med kommatecken."
|
||||
|
||||
#: core/validators.py:110
|
||||
msgid "Please enter a valid IP address."
|
||||
msgstr "Var god fyll i en giltigt IP-adress."
|
||||
msgstr "Var god fyll i en giltig IP-adress."
|
||||
|
||||
#: core/validators.py:114
|
||||
msgid "Empty values are not allowed here."
|
||||
@ -414,7 +414,7 @@ msgstr ""
|
||||
#: core/validators.py:204
|
||||
#, python-format
|
||||
msgid "The URL %s does not point to a valid QuickTime video."
|
||||
msgstr "URL:en %s pekar inte på en giltig QuickTime-video."
|
||||
msgstr "URL:en %s pekar inte mot en giltig QuickTime-video."
|
||||
|
||||
#: core/validators.py:208
|
||||
msgid "A valid URL is required."
|
||||
@ -432,7 +432,7 @@ msgstr ""
|
||||
#: core/validators.py:229
|
||||
#, python-format
|
||||
msgid "Badly formed XML: %s"
|
||||
msgstr "Missformad XML: %s"
|
||||
msgstr "Felaktigt XML: %s"
|
||||
|
||||
#: core/validators.py:246
|
||||
#, python-format
|
||||
@ -480,7 +480,7 @@ msgstr "Det här fältet måste anges om %(field)s inte är %(value)s"
|
||||
|
||||
#: core/validators.py:359
|
||||
msgid "Duplicate values are not allowed."
|
||||
msgstr "Dubbelvärden är inte tillåtna."
|
||||
msgstr "Dubletter är inte tillåtna."
|
||||
|
||||
#: core/validators.py:374
|
||||
#, python-format
|
||||
@ -544,12 +544,12 @@ msgstr "Fyll i ett giltigt decimaltal."
|
||||
#: core/validators.py:454
|
||||
#, python-format
|
||||
msgid "Make sure your uploaded file is at least %s bytes big."
|
||||
msgstr "Se till att filen du laddade upp är minst %s bytes stor."
|
||||
msgstr "Se till att filen du laddade upp är minst %s byte stor."
|
||||
|
||||
#: core/validators.py:455
|
||||
#, python-format
|
||||
msgid "Make sure your uploaded file is at most %s bytes big."
|
||||
msgstr "Se till att filen du laddade upp är som mest %s bytes stor."
|
||||
msgstr "Se till att filen du laddade upp är som mest %s byte stor."
|
||||
|
||||
#: core/validators.py:472
|
||||
msgid "The format for this field is wrong."
|
||||
@ -876,7 +876,7 @@ msgstr "är ett giltigt betyg"
|
||||
|
||||
#: contrib/comments/models.py:83 contrib/comments/models.py:169
|
||||
msgid "date/time submitted"
|
||||
msgstr "datum/tid skickat"
|
||||
msgstr "skickat datum/tid"
|
||||
|
||||
#: contrib/comments/models.py:84 contrib/comments/models.py:170
|
||||
msgid "is public"
|
||||
@ -915,7 +915,7 @@ msgid ""
|
||||
"\n"
|
||||
"http://%(domain)s%(url)s"
|
||||
msgstr ""
|
||||
"Postat av %(user)s %(date)s\n"
|
||||
"Inlagt av %(user)s %(date)s\n"
|
||||
"\n"
|
||||
"%(comment)s\n"
|
||||
"\n"
|
||||
@ -947,7 +947,7 @@ msgstr "poäng"
|
||||
|
||||
#: contrib/comments/models.py:234
|
||||
msgid "score date"
|
||||
msgstr "poängdatum"
|
||||
msgstr "poängen tillsatt den"
|
||||
|
||||
#: contrib/comments/models.py:237
|
||||
msgid "karma score"
|
||||
@ -960,7 +960,7 @@ msgstr "karmapoäng"
|
||||
#: contrib/comments/models.py:242
|
||||
#, python-format
|
||||
msgid "%(score)d rating by %(user)s"
|
||||
msgstr "Betyget %(score)d av %(user)s"
|
||||
msgstr "Poäng %(score)d av %(user)s"
|
||||
|
||||
#: contrib/comments/models.py:258
|
||||
#, python-format
|
||||
@ -996,20 +996,20 @@ msgstr "borttagningsdatum"
|
||||
|
||||
#: contrib/comments/models.py:280
|
||||
msgid "moderator deletion"
|
||||
msgstr "moderatorborttagning"
|
||||
msgstr "borttaget av moderator"
|
||||
|
||||
#: contrib/comments/models.py:281
|
||||
msgid "moderator deletions"
|
||||
msgstr "moderatorborttagningar"
|
||||
msgstr "borttagna av moderator"
|
||||
|
||||
#: contrib/comments/models.py:285
|
||||
#, python-format
|
||||
msgid "Moderator deletion by %r"
|
||||
msgstr "Moderatorborttagning av %r"
|
||||
msgstr "Borttaget av moderator %r"
|
||||
|
||||
#: contrib/comments/views/karma.py:20
|
||||
msgid "Anonymous users cannot vote"
|
||||
msgstr "Anonyma användare kan inte rösta"
|
||||
msgstr "Anonyma användare får inte rösta"
|
||||
|
||||
#: contrib/comments/views/karma.py:24
|
||||
msgid "Invalid comment ID"
|
||||
@ -1162,7 +1162,7 @@ msgstr "domännamn"
|
||||
|
||||
#: contrib/sites/models.py:16
|
||||
msgid "display name"
|
||||
msgstr "visat namn"
|
||||
msgstr "visningsnamn"
|
||||
|
||||
#: contrib/sites/models.py:20
|
||||
msgid "site"
|
||||
@ -1243,8 +1243,8 @@ msgid ""
|
||||
"Please enter a correct username and password. Note that both fields are case-"
|
||||
"sensitive."
|
||||
msgstr ""
|
||||
"Var god ange ett korrekt användarnamn och lösenord. Observera att båda "
|
||||
"fälten är skiftlägeskänsliga."
|
||||
"Var god ange ett korrekt användarnamn och lösenord. Tänk på att "
|
||||
"skilja mellan gemener och versaler."
|
||||
|
||||
#: contrib/admin/views/decorators.py:24
|
||||
#: contrib/admin/templates/admin/login.html:25
|
||||
@ -1474,12 +1474,12 @@ msgstr "Decimaltal"
|
||||
|
||||
#: contrib/admin/views/doc.py:299
|
||||
msgid "E-mail address"
|
||||
msgstr "E-postadress:"
|
||||
msgstr "E-postadress"
|
||||
|
||||
#: contrib/admin/views/doc.py:300 contrib/admin/views/doc.py:301
|
||||
#: contrib/admin/views/doc.py:304
|
||||
msgid "File path"
|
||||
msgstr "Filsökväg"
|
||||
msgstr "Sökväg till fil"
|
||||
|
||||
#: contrib/admin/views/doc.py:302
|
||||
msgid "Floating point number"
|
||||
@ -1697,7 +1697,7 @@ msgstr "Sidan kunde inte hittas"
|
||||
|
||||
#: contrib/admin/templates/admin/404.html:10
|
||||
msgid "We're sorry, but the requested page could not be found."
|
||||
msgstr "Vi är ledsna, men den efterfrågade sidan kunde inte hittas."
|
||||
msgstr "Vi beklagar, men den efterfrågade sidan kunde tyvärr inte hittas."
|
||||
|
||||
#: contrib/admin/templates/admin/index.html:17
|
||||
#, python-format
|
||||
@ -2026,11 +2026,11 @@ msgstr "Python-modellklassnamn"
|
||||
|
||||
#: contrib/contenttypes/models.py:40
|
||||
msgid "content type"
|
||||
msgstr "innehållstyp"
|
||||
msgstr "typ av innehåll"
|
||||
|
||||
#: contrib/contenttypes/models.py:41
|
||||
msgid "content types"
|
||||
msgstr "innehållstyper"
|
||||
msgstr "typer av innehåll"
|
||||
|
||||
#: contrib/auth/views.py:41
|
||||
msgid "Logged out"
|
||||
@ -2178,7 +2178,7 @@ msgstr "meddelande"
|
||||
|
||||
#: contrib/auth/forms.py:17 contrib/auth/forms.py:138
|
||||
msgid "The two password fields didn't match."
|
||||
msgstr "De båda lösenorden stämde inte överens."
|
||||
msgstr "Lösenorden stämde inte överens."
|
||||
|
||||
#: contrib/auth/forms.py:25
|
||||
msgid "A user with that username already exists."
|
||||
@ -2218,7 +2218,7 @@ msgstr "Fyll i ett postnummer. Du måste ha mellanslag mellan nummerdelarna."
|
||||
|
||||
#: contrib/localflavor/br/forms.py:18
|
||||
msgid "Enter a zip code in the format XXXXX-XXX."
|
||||
msgstr "Fyll i zipkod på formatet XXXXX-XXX."
|
||||
msgstr "Fyll i ett postnummer på formatet XXXXX-XXX."
|
||||
|
||||
#: contrib/localflavor/br/forms.py:30
|
||||
msgid "Phone numbers must be in XX-XXXX-XXXX format."
|
||||
@ -2246,16 +2246,16 @@ msgstr "Ogiltigt CNPJ-nummer."
|
||||
|
||||
#: contrib/localflavor/au/forms.py:18
|
||||
msgid "Enter a 4 digit post code."
|
||||
msgstr "Fyll i en fyrsiffrig postkod."
|
||||
msgstr "Fyll i ett fyrsiffrigt postnummer."
|
||||
|
||||
#: contrib/localflavor/fr/forms.py:17 contrib/localflavor/de/forms.py:16
|
||||
#: contrib/localflavor/fi/forms.py:14
|
||||
msgid "Enter a zip code in the format XXXXX."
|
||||
msgstr "Fyll i en zipkod på formatet XXXXX."
|
||||
msgstr "Fyll i ett postnummer på formatet XXXXX."
|
||||
|
||||
#: contrib/localflavor/us/forms.py:18
|
||||
msgid "Enter a zip code in the format XXXXX or XXXXX-XXXX."
|
||||
msgstr "Fyll i zipkod på formatet XXXXX eller XXXXX-XXXX."
|
||||
msgstr "Fyll i ett postnummer på formatet XXXXX eller XXXXX-XXXX."
|
||||
|
||||
#: contrib/localflavor/us/forms.py:51
|
||||
msgid "Enter a valid U.S. Social Security number in XXX-XX-XXXX format."
|
||||
@ -2335,7 +2335,7 @@ msgstr ""
|
||||
|
||||
#: contrib/localflavor/jp/forms.py:21
|
||||
msgid "Enter a postal code in the format XXXXXXX or XXX-XXXX."
|
||||
msgstr "Fyll i postkod på formatet XXXXXXX eller XXX-XXXX."
|
||||
msgstr "Fyll i ett postnummer på formatet XXXXXXX eller XXX-XXXX."
|
||||
|
||||
#: contrib/localflavor/jp/jp_prefectures.py:4
|
||||
msgid "Hokkaido"
|
||||
@ -2631,7 +2631,7 @@ msgstr "Zürich"
|
||||
|
||||
#: contrib/localflavor/ch/forms.py:18 contrib/localflavor/no/forms.py:14
|
||||
msgid "Enter a zip code in the format XXXX."
|
||||
msgstr "Fyll i zipkod på formatet XXXX."
|
||||
msgstr "Fyll i postnummer på formatet XXXX."
|
||||
|
||||
#: contrib/localflavor/ch/forms.py:90
|
||||
msgid ""
|
||||
@ -2652,7 +2652,7 @@ msgstr "Det isländska personnumret är inte giltigt."
|
||||
|
||||
#: contrib/localflavor/it/forms.py:16
|
||||
msgid "Enter a valid zip code."
|
||||
msgstr "Fyll i en giltigt zipkod."
|
||||
msgstr "Fyll i ett giltigt postnummer."
|
||||
|
||||
#: contrib/localflavor/it/forms.py:41
|
||||
msgid "Enter a valid Social Security number."
|
||||
@ -2739,11 +2739,11 @@ msgstr ""
|
||||
|
||||
#: contrib/flatpages/models.py:18
|
||||
msgid "flat page"
|
||||
msgstr "flat sida"
|
||||
msgstr "statisk sida"
|
||||
|
||||
#: contrib/flatpages/models.py:19
|
||||
msgid "flat pages"
|
||||
msgstr "flata sidor"
|
||||
msgstr "statiska sidor"
|
||||
|
||||
#: utils/dates.py:6
|
||||
msgid "Monday"
|
||||
@ -3024,7 +3024,7 @@ msgstr "ja,nej,kanske"
|
||||
msgid "%(size)d byte"
|
||||
msgid_plural "%(size)d bytes"
|
||||
msgstr[0] "%(size)d byte"
|
||||
msgstr[1] "%(size)d bytes"
|
||||
msgstr[1] "%(size)d byte"
|
||||
|
||||
#: template/defaultfilters.py:516
|
||||
#, python-format
|
||||
|
Binary file not shown.
@ -58,7 +58,7 @@ msgstr ""
|
||||
|
||||
#: contrib/admin/media/js/dateparse.js:33
|
||||
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
|
||||
msgstr "Söndag Mondag Tisdag Onsdag Torsdag Fredag Lördag"
|
||||
msgstr "Söndag Måndag Tisdag Onsdag Torsdag Fredag Lördag"
|
||||
|
||||
#: contrib/admin/media/js/calendar.js:25
|
||||
msgid "S M T W T F S"
|
||||
@ -96,7 +96,7 @@ msgstr "06.00"
|
||||
|
||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84
|
||||
msgid "Noon"
|
||||
msgstr "Mitt på dagen"
|
||||
msgstr "Middag"
|
||||
|
||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88
|
||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183
|
||||
@ -118,5 +118,5 @@ msgstr "Igår"
|
||||
|
||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179
|
||||
msgid "Tomorrow"
|
||||
msgstr "Imorgon"
|
||||
msgstr "I morgon"
|
||||
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -8,19 +8,9 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE
|
||||
redirecting to the log-in page if necessary. The test should be a callable
|
||||
that takes the user object and returns True if the user passes.
|
||||
"""
|
||||
if not login_url:
|
||||
from django.conf import settings
|
||||
login_url = settings.LOGIN_URL
|
||||
def _dec(view_func):
|
||||
def _checklogin(request, *args, **kwargs):
|
||||
if test_func(request.user):
|
||||
return view_func(request, *args, **kwargs)
|
||||
return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, urlquote(request.get_full_path())))
|
||||
_checklogin.__doc__ = view_func.__doc__
|
||||
_checklogin.__dict__ = view_func.__dict__
|
||||
|
||||
return _checklogin
|
||||
return _dec
|
||||
def decorate(view_func):
|
||||
return _CheckLogin(view_func, test_func, login_url, redirect_field_name)
|
||||
return decorate
|
||||
|
||||
def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME):
|
||||
"""
|
||||
@ -42,3 +32,34 @@ def permission_required(perm, login_url=None):
|
||||
"""
|
||||
return user_passes_test(lambda u: u.has_perm(perm), login_url=login_url)
|
||||
|
||||
class _CheckLogin(object):
|
||||
"""
|
||||
Class that checks that the user passes the given test, redirecting to
|
||||
the log-in page if necessary. If the test is passed, the view function
|
||||
is invoked. The test should be a callable that takes the user object
|
||||
and returns True if the user passes.
|
||||
|
||||
We use a class here so that we can define __get__. This way, when a
|
||||
_CheckLogin object is used as a method decorator, the view function
|
||||
is properly bound to its instance.
|
||||
"""
|
||||
def __init__(self, view_func, test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
|
||||
if not login_url:
|
||||
from django.conf import settings
|
||||
login_url = settings.LOGIN_URL
|
||||
self.view_func = view_func
|
||||
self.test_func = test_func
|
||||
self.login_url = login_url
|
||||
self.redirect_field_name = redirect_field_name
|
||||
self.__name__ = view_func.__name__
|
||||
|
||||
def __get__(self, obj, cls=None):
|
||||
view_func = self.view_func.__get__(obj, cls)
|
||||
return _CheckLogin(view_func, self.test_func, self.login_url, self.redirect_field_name)
|
||||
|
||||
def __call__(self, request, *args, **kwargs):
|
||||
if self.test_func(request.user):
|
||||
return self.view_func(request, *args, **kwargs)
|
||||
path = urlquote(request.get_full_path())
|
||||
tup = self.login_url, self.redirect_field_name, path
|
||||
return HttpResponseRedirect('%s?%s=%s' % tup)
|
||||
|
@ -4,6 +4,10 @@ from django import http
|
||||
import sys
|
||||
|
||||
class BaseHandler(object):
|
||||
# Changes that are always applied to a response (in this order).
|
||||
response_fixes = [http.fix_location_header,
|
||||
http.conditional_content_removal]
|
||||
|
||||
def __init__(self):
|
||||
self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None
|
||||
|
||||
@ -50,10 +54,6 @@ class BaseHandler(object):
|
||||
|
||||
def get_response(self, request):
|
||||
"Returns an HttpResponse object for the given HttpRequest"
|
||||
response = self._real_get_response(request)
|
||||
return fix_location_header(request, response)
|
||||
|
||||
def _real_get_response(self, request):
|
||||
from django.core import exceptions, urlresolvers
|
||||
from django.core.mail import mail_admins
|
||||
from django.conf import settings
|
||||
@ -134,15 +134,13 @@ class BaseHandler(object):
|
||||
import traceback
|
||||
return '\n'.join(traceback.format_exception(*(exc_info or sys.exc_info())))
|
||||
|
||||
def fix_location_header(request, response):
|
||||
"""
|
||||
Ensure that we always use an absolute URI in any location header in the
|
||||
response. This is required by RFC 2616, section 14.30.
|
||||
|
||||
Code constructing response objects is free to insert relative paths and
|
||||
this function converts them to absolute paths.
|
||||
"""
|
||||
if 'Location' in response and request.get_host():
|
||||
response['Location'] = request.build_absolute_uri(response['Location'])
|
||||
return response
|
||||
def apply_response_fixes(self, request, response):
|
||||
"""
|
||||
Applies each of the functions in self.response_fixes to the request and
|
||||
response, modifying the response in the process. Returns the new
|
||||
response.
|
||||
"""
|
||||
for func in self.response_fixes:
|
||||
response = func(request, response)
|
||||
return response
|
||||
|
||||
|
@ -162,6 +162,7 @@ class ModPythonHandler(BaseHandler):
|
||||
# Apply response middleware
|
||||
for middleware_method in self._response_middleware:
|
||||
response = middleware_method(request, response)
|
||||
response = self.apply_response_fixes(request, response)
|
||||
finally:
|
||||
dispatcher.send(signal=signals.request_finished)
|
||||
|
||||
|
@ -207,6 +207,7 @@ class WSGIHandler(BaseHandler):
|
||||
# Apply response middleware
|
||||
for middleware_method in self._response_middleware:
|
||||
response = middleware_method(request, response)
|
||||
response = self.apply_response_fixes(request, response)
|
||||
finally:
|
||||
dispatcher.send(signal=signals.request_finished)
|
||||
|
||||
@ -220,3 +221,4 @@ class WSGIHandler(BaseHandler):
|
||||
response_headers.append(('Set-Cookie', str(c.output(header=''))))
|
||||
start_response(status, response_headers)
|
||||
return response
|
||||
|
||||
|
@ -5,6 +5,7 @@ from urllib import urlencode
|
||||
from urlparse import urljoin
|
||||
from django.utils.datastructures import MultiValueDict, FileDict
|
||||
from django.utils.encoding import smart_str, iri_to_uri, force_unicode
|
||||
from utils import *
|
||||
|
||||
RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
|
||||
|
||||
|
34
django/http/utils.py
Normal file
34
django/http/utils.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""
|
||||
Functions that modify an HTTP request or response in some way.
|
||||
"""
|
||||
|
||||
# This group of functions are run as part of the response handling, after
|
||||
# everything else, including all response middleware. Think of them as
|
||||
# "compulsory response middleware". Be careful about what goes here, because
|
||||
# it's a little fiddly to override this behaviour, so they should be truly
|
||||
# universally applicable.
|
||||
|
||||
def fix_location_header(request, response):
|
||||
"""
|
||||
Ensures that we always use an absolute URI in any location header in the
|
||||
response. This is required by RFC 2616, section 14.30.
|
||||
|
||||
Code constructing response objects is free to insert relative paths and
|
||||
this function converts them to absolute paths.
|
||||
"""
|
||||
if 'Location' in response and request.get_host():
|
||||
response['Location'] = request.build_absolute_uri(response['Location'])
|
||||
return response
|
||||
|
||||
def conditional_content_removal(request, response):
|
||||
"""
|
||||
Removes the content of responses for HEAD requests, 1xx, 204 and 304
|
||||
responses. Ensures compliance with RFC 2616, section 4.3.
|
||||
"""
|
||||
if 100 <= response.status_code < 200 or response.status_code in (204, 304):
|
||||
response.content = ''
|
||||
response['Content-Length'] = 0
|
||||
if request.method == 'HEAD':
|
||||
response.content = ''
|
||||
return response
|
||||
|
@ -6,8 +6,6 @@ class ConditionalGetMiddleware(object):
|
||||
Last-Modified header, and the request has If-None-Match or
|
||||
If-Modified-Since, the response is replaced by an HttpNotModified.
|
||||
|
||||
Removes the content from any response to a HEAD request.
|
||||
|
||||
Also sets the Date and Content-Length response-headers.
|
||||
"""
|
||||
def process_response(self, request, response):
|
||||
@ -18,19 +16,17 @@ class ConditionalGetMiddleware(object):
|
||||
if response.has_header('ETag'):
|
||||
if_none_match = request.META.get('HTTP_IF_NONE_MATCH', None)
|
||||
if if_none_match == response['ETag']:
|
||||
response.status_code = 304
|
||||
response.content = ''
|
||||
response['Content-Length'] = '0'
|
||||
# Setting the status is enough here. The response handling path
|
||||
# automatically removes content for this status code (in
|
||||
# http.conditional_content_removal()).
|
||||
response.status = 304
|
||||
|
||||
if response.has_header('Last-Modified'):
|
||||
if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None)
|
||||
if if_modified_since == response['Last-Modified']:
|
||||
response.status_code = 304
|
||||
response.content = ''
|
||||
response['Content-Length'] = '0'
|
||||
|
||||
if request.method == 'HEAD':
|
||||
response.content = ''
|
||||
# Setting the status code is enough here (same reasons as
|
||||
# above).
|
||||
response.status = 304
|
||||
|
||||
return response
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
Form classes
|
||||
"""
|
||||
|
||||
import copy
|
||||
from copy import deepcopy
|
||||
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.utils.html import escape
|
||||
@ -21,18 +21,6 @@ def pretty_name(name):
|
||||
name = name[0].upper() + name[1:]
|
||||
return name.replace('_', ' ')
|
||||
|
||||
class SortedDictFromList(SortedDict):
|
||||
"A dictionary that keeps its keys in the order in which they're inserted."
|
||||
# This is different than django.utils.datastructures.SortedDict, because
|
||||
# this takes a list/tuple as the argument to __init__().
|
||||
def __init__(self, data=None):
|
||||
if data is None: data = []
|
||||
self.keyOrder = [d[0] for d in data]
|
||||
dict.__init__(self, dict(data))
|
||||
|
||||
def copy(self):
|
||||
return SortedDictFromList([(k, copy.deepcopy(v)) for k, v in self.items()])
|
||||
|
||||
class DeclarativeFieldsMetaclass(type):
|
||||
"""
|
||||
Metaclass that converts Field attributes to a dictionary called
|
||||
@ -50,7 +38,7 @@ class DeclarativeFieldsMetaclass(type):
|
||||
if hasattr(base, 'base_fields'):
|
||||
fields = base.base_fields.items() + fields
|
||||
|
||||
attrs['base_fields'] = SortedDictFromList(fields)
|
||||
attrs['base_fields'] = SortedDict(fields)
|
||||
|
||||
new_class = type.__new__(cls, name, bases, 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.
|
||||
# Instances should always modify self.fields; they should not modify
|
||||
# self.base_fields.
|
||||
self.fields = self.base_fields.copy()
|
||||
self.fields = deepcopy(self.base_fields)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.as_table()
|
||||
|
@ -5,9 +5,10 @@ and database field objects.
|
||||
|
||||
from django.utils.translation import ugettext
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
from util import ValidationError
|
||||
from forms import BaseForm, SortedDictFromList
|
||||
from forms import BaseForm
|
||||
from fields import Field, ChoiceField, IntegerField
|
||||
from formsets import BaseFormSet, formset_for_form, DELETION_FIELD_NAME
|
||||
from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
|
||||
@ -91,7 +92,7 @@ def form_for_model(model, form=BaseForm, fields=None,
|
||||
formfield = formfield_callback(f)
|
||||
if formfield:
|
||||
field_list.append((f.name, formfield))
|
||||
base_fields = SortedDictFromList(field_list)
|
||||
base_fields = SortedDict(field_list)
|
||||
return type(opts.object_name + 'Form', (form,),
|
||||
{'base_fields': base_fields, '_model': model,
|
||||
'save': make_model_save(model, fields, 'created')})
|
||||
@ -120,7 +121,7 @@ def form_for_instance(instance, form=BaseForm, fields=None,
|
||||
formfield = formfield_callback(f, initial=current_value)
|
||||
if formfield:
|
||||
field_list.append((f.name, formfield))
|
||||
base_fields = SortedDictFromList(field_list)
|
||||
base_fields = SortedDict(field_list)
|
||||
return type(opts.object_name + 'InstanceForm', (form,),
|
||||
{'base_fields': base_fields, '_model': model,
|
||||
'save': make_instance_save(instance, fields, 'changed')})
|
||||
@ -129,8 +130,8 @@ def form_for_fields(field_list):
|
||||
"""
|
||||
Returns a Form class for the given list of Django database field instances.
|
||||
"""
|
||||
fields = SortedDictFromList([(f.name, f.formfield())
|
||||
for f in field_list if f.editable])
|
||||
fields = SortedDict([(f.name, f.formfield())
|
||||
for f in field_list if f.editable])
|
||||
return type('FormForFields', (BaseForm,), {'base_fields': fields})
|
||||
|
||||
class QuerySetIterator(object):
|
||||
@ -156,14 +157,22 @@ class ModelChoiceField(ChoiceField):
|
||||
def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
|
||||
required=True, widget=Select, label=None, initial=None,
|
||||
help_text=None):
|
||||
self.queryset = queryset
|
||||
self.empty_label = empty_label
|
||||
self.cache_choices = cache_choices
|
||||
# Call Field instead of ChoiceField __init__() because we don't need
|
||||
# ChoiceField.__init__().
|
||||
Field.__init__(self, required, widget, label, initial, help_text)
|
||||
self.queryset = queryset
|
||||
|
||||
def _get_queryset(self):
|
||||
return self._queryset
|
||||
|
||||
def _set_queryset(self, queryset):
|
||||
self._queryset = queryset
|
||||
self.widget.choices = self.choices
|
||||
|
||||
queryset = property(_get_queryset, _set_queryset)
|
||||
|
||||
def _get_choices(self):
|
||||
# If self._choices is set, then somebody must have manually set
|
||||
# the property self.choices. In this case, just return self._choices.
|
||||
@ -191,7 +200,7 @@ class ModelChoiceField(ChoiceField):
|
||||
if value in ('', None):
|
||||
return None
|
||||
try:
|
||||
value = self.queryset.model._default_manager.get(pk=value)
|
||||
value = self.queryset.get(pk=value)
|
||||
except self.queryset.model.DoesNotExist:
|
||||
raise ValidationError(ugettext(u'Select a valid choice. That'
|
||||
u' choice is not one of the'
|
||||
@ -218,7 +227,7 @@ class ModelMultipleChoiceField(ModelChoiceField):
|
||||
final_values = []
|
||||
for val in value:
|
||||
try:
|
||||
obj = self.queryset.model._default_manager.get(pk=val)
|
||||
obj = self.queryset.get(pk=val)
|
||||
except self.queryset.model.DoesNotExist:
|
||||
raise ValidationError(ugettext(u'Select a valid choice. %s is'
|
||||
u' not one of the available'
|
||||
|
@ -42,7 +42,7 @@ class ClientHandler(BaseHandler):
|
||||
# Apply response middleware
|
||||
for middleware_method in self._response_middleware:
|
||||
response = middleware_method(request, response)
|
||||
|
||||
response = self.apply_response_fixes(request, response)
|
||||
finally:
|
||||
dispatcher.send(signal=signals.request_finished)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import re
|
||||
import unittest
|
||||
from urlparse import urlsplit
|
||||
from urlparse import urlsplit, urlunsplit
|
||||
|
||||
from django.http import QueryDict
|
||||
from django.db import transaction
|
||||
@ -74,7 +74,7 @@ class TestCase(unittest.TestCase):
|
||||
super(TestCase, self).__call__(result)
|
||||
|
||||
def assertRedirects(self, response, expected_url, status_code=302,
|
||||
target_status_code=200):
|
||||
target_status_code=200, host=None):
|
||||
"""Asserts that a response redirected to a specific URL, and that the
|
||||
redirect URL can be loaded.
|
||||
|
||||
@ -86,6 +86,10 @@ class TestCase(unittest.TestCase):
|
||||
" (expected %d)" % (response.status_code, status_code)))
|
||||
url = response['Location']
|
||||
scheme, netloc, path, query, fragment = urlsplit(url)
|
||||
e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url)
|
||||
if not (e_scheme or e_netloc):
|
||||
expected_url = urlunsplit(('http', host or 'testserver', e_path,
|
||||
e_query, e_fragment))
|
||||
self.assertEqual(url, expected_url,
|
||||
"Response redirected to '%s', expected '%s'" % (url, expected_url))
|
||||
|
||||
|
@ -62,12 +62,10 @@ class SortedDict(dict):
|
||||
else:
|
||||
self.keyOrder = [key for key, value in data]
|
||||
|
||||
def __deepcopy__(self,memo):
|
||||
def __deepcopy__(self, memo):
|
||||
from copy import deepcopy
|
||||
obj = self.__class__()
|
||||
for k, v in self.items():
|
||||
obj[k] = deepcopy(v, memo)
|
||||
return obj
|
||||
return self.__class__([(key, deepcopy(value, memo))
|
||||
for key, value in self.iteritems()])
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
dict.__setitem__(self, key, value)
|
||||
|
@ -47,7 +47,7 @@ Initialization
|
||||
==============
|
||||
|
||||
To activate sitemap generation on your Django site, add this line to your
|
||||
URLconf_:
|
||||
URLconf_::
|
||||
|
||||
(r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps})
|
||||
|
||||
|
@ -440,6 +440,8 @@ the data in the database when the form is instantiated.
|
||||
>>> from django.newforms import ModelChoiceField, ModelMultipleChoiceField
|
||||
|
||||
>>> 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('')
|
||||
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.']
|
||||
|
||||
# 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 ####################################################
|
||||
|
||||
>>> 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)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
@ -517,7 +533,7 @@ Traceback (most recent call last):
|
||||
...
|
||||
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
|
||||
# than caching it at time of instantiation.
|
||||
>>> Category.objects.create(id=6, name='Sixth', url='6th')
|
||||
@ -525,7 +541,7 @@ ValidationError: [u'Enter a list of values.']
|
||||
>>> f.clean([6])
|
||||
[<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
|
||||
# than caching it at time of instantiation.
|
||||
>>> 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.']
|
||||
|
||||
# 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 ############################################################
|
||||
|
||||
>>> PhoneNumberForm = form_for_model(PhoneNumber)
|
||||
|
@ -83,13 +83,16 @@ class ClientTest(TestCase):
|
||||
def test_redirect(self):
|
||||
"GET a URL that redirects elsewhere"
|
||||
response = self.client.get('/test_client/redirect_view/')
|
||||
# Check that the response was a 302 (redirect)
|
||||
self.assertRedirects(response, 'http://testserver/test_client/get_view/')
|
||||
# Check that the response was a 302 (redirect) and that
|
||||
# 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/')
|
||||
# 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):
|
||||
"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.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):
|
||||
"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/')
|
||||
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):
|
||||
"Request a page that modifies the session"
|
||||
# Session value isn't set initially
|
||||
|
@ -13,7 +13,10 @@ urlpatterns = patterns('',
|
||||
(r'^form_view/$', views.form_view),
|
||||
(r'^form_view_with_template/$', views.form_view_with_template),
|
||||
(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'^permission_protected_view/$', views.permission_protected_view),
|
||||
(r'^permission_protected_method_view/$', views.permission_protected_method_view),
|
||||
(r'^session_view/$', views.session_view),
|
||||
(r'^broken_view/$', views.broken_view),
|
||||
(r'^mail_sending_view/$', views.mail_sending_view),
|
||||
|
@ -3,7 +3,7 @@ from xml.dom.minidom import parseString
|
||||
from django.core.mail import EmailMessage, SMTPConnection
|
||||
from django.template import Context, Template
|
||||
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 import fields
|
||||
from django.shortcuts import render_to_response
|
||||
@ -130,6 +130,38 @@ def login_protected_view_changed_redirect(request):
|
||||
return HttpResponse(t.render(c))
|
||||
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):
|
||||
"A view that modifies the session"
|
||||
request.session['tobacconist'] = 'hovercraft'
|
||||
|
@ -119,7 +119,7 @@ class AssertRedirectsTests(TestCase):
|
||||
try:
|
||||
self.assertRedirects(response, '/test_client/get_view/')
|
||||
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):
|
||||
"An assertion is raised if the response redirects to another target"
|
||||
|
Loading…
x
Reference in New Issue
Block a user