1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49:12 +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>
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

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"
"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 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

View File

@ -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"

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
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)

View File

@ -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):
def apply_response_fixes(self, 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.
Applies each of the functions in self.response_fixes to the request and
response, modifying the response in the process. Returns the new
response.
"""
if 'Location' in response and request.get_host():
response['Location'] = request.build_absolute_uri(response['Location'])
for func in self.response_fixes:
response = func(request, response)
return response

View File

@ -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)

View File

@ -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

View File

@ -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
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
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

View File

@ -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()

View File

@ -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,7 +130,7 @@ 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())
fields = SortedDict([(f.name, f.formfield())
for f in field_list if f.editable])
return type('FormForFields', (BaseForm,), {'base_fields': fields})
@ -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'

View File

@ -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)

View File

@ -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))

View File

@ -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)

View File

@ -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})

View File

@ -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)

View File

@ -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

View File

@ -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),

View File

@ -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'

View File

@ -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"