1
0
mirror of https://github.com/django/django.git synced 2025-07-06 10:49:17 +00:00

queryset-refactor: Merged to [6220]

git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@6337 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2007-09-15 21:42:51 +00:00
parent ea9cd54213
commit 5ce2e6c2c8
36 changed files with 883 additions and 67 deletions

View File

@ -232,6 +232,7 @@ answer newbie questions, and generally made Django that much better:
phil@produxion.net
phil.h.smith@gmail.com
Gustavo Picon
pigletto
Luke Plant <http://lukeplant.me.uk/>
plisk
Daniel Poelzleithner <http://poelzi.org/>
@ -262,6 +263,7 @@ answer newbie questions, and generally made Django that much better:
SmileyChris <smileychris@gmail.com>
smurf@smurf.noris.de
sopel
Leo Soto <leo.soto@gmail.com>
Wiliam Alves de Souza <wiliamsouza83@gmail.com>
Georgi Stanojevski <glisha@gmail.com>
Vasiliy Stavenko <stavenko@gmail.com>
@ -274,6 +276,7 @@ answer newbie questions, and generally made Django that much better:
Swaroop C H <http://www.swaroopch.info>
Aaron Swartz <http://www.aaronsw.com/>
Ville Säävuori <http://www.unessa.net/>
Tyler Tarabula <tyler.tarabula@gmail.com>
Tyson Tate <tyson@fallingbullets.com>
Frank Tegtmeyer <fte@fte.to>
thebjorn <bp@datakortet.no>

View File

@ -43,7 +43,7 @@ p.file-upload { line-height:20px; margin:0; padding:0; color:#666; font-size:11p
/* CALENDARS & CLOCKS */
.calendarbox, .clockbox { margin:5px auto; font-size:11px; width:16em; text-align:center; background:white; position:relative; }
.clockbox { width:9em; }
.clockbox { width:auto; }
.calendar { margin:0; padding: 0; }
.calendar table { margin:0; padding:0; border-collapse:collapse; background:white; width:99%; }
.calendar caption, .calendarbox h2 { margin: 0; font-size:11px; text-align:center; border-top:none; }

View File

@ -195,6 +195,19 @@ var DateTimeShortcuts = {
openCalendar: function(num) {
var cal_box = document.getElementById(DateTimeShortcuts.calendarDivName1+num)
var cal_link = document.getElementById(DateTimeShortcuts.calendarLinkName+num)
var inp = DateTimeShortcuts.calendarInputs[num];
// Determine if the current value in the input has a valid date.
// If so, draw the calendar with that date's year and month.
if (inp.value) {
var date_parts = inp.value.split('-');
var year = date_parts[0];
var month = parseFloat(date_parts[1]);
if (year.match(/\d\d\d\d/) && month >= 1 && month <= 12) {
DateTimeShortcuts.calendars[num].drawDate(month, year);
}
}
// Recalculate the clockbox position
// is it left-to-right or right-to-left layout ?

View File

@ -2,7 +2,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME
from django.http import HttpResponseRedirect
from urllib import quote
def user_passes_test(test_func, login_url=None):
def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
"""
Decorator for views that checks that the user passes the given test,
redirecting to the log-in page if necessary. The test should be a callable
@ -15,20 +15,25 @@ def user_passes_test(test_func, login_url=None):
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, quote(request.get_full_path())))
return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, quote(request.get_full_path())))
_checklogin.__doc__ = view_func.__doc__
_checklogin.__dict__ = view_func.__dict__
return _checklogin
return _dec
login_required = user_passes_test(lambda u: u.is_authenticated())
login_required.__doc__ = (
def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME):
"""
Decorator for views that checks that the user is logged in, redirecting
to the log-in page if necessary.
"""
actual_decorator = user_passes_test(
lambda u: u.is_authenticated(),
redirect_field_name=redirect_field_name
)
if function:
return actual_decorator(function)
return actual_decorator
def permission_required(perm, login_url=None):
"""

View File

@ -9,10 +9,10 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.utils.translation import ugettext as _
def login(request, template_name='registration/login.html'):
def login(request, template_name='registration/login.html', redirect_field_name=REDIRECT_FIELD_NAME):
"Displays the login form and handles the login action."
manipulator = AuthenticationForm(request)
redirect_to = request.REQUEST.get(REDIRECT_FIELD_NAME, '')
redirect_to = request.REQUEST.get(redirect_field_name, '')
if request.POST:
errors = manipulator.get_validation_errors(request.POST)
if not errors:
@ -35,7 +35,7 @@ def login(request, template_name='registration/login.html'):
return render_to_response(template_name, {
'form': oldforms.FormWrapper(manipulator, request.POST, errors),
REDIRECT_FIELD_NAME: redirect_to,
redirect_field_name: redirect_to,
'site_name': current_site.name,
}, context_instance=RequestContext(request))
@ -56,12 +56,12 @@ def logout_then_login(request, login_url=None):
login_url = settings.LOGIN_URL
return logout(request, login_url)
def redirect_to_login(next, login_url=None):
def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
"Redirects the user to the login page, passing the given 'next' page"
if not login_url:
from django.conf import settings
login_url = settings.LOGIN_URL
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, next))
return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, next))
def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html',
email_template_name='registration/password_reset_email.html'):

View File

@ -0,0 +1,57 @@
"""
An alphabetical list of provinces and territories for use as `choices`
in a formfield., and a mapping of province misspellings/abbreviations to
normalized abbreviations
Source: http://www.canada.gc.ca/othergov/prov_e.html
This exists in this standalone file so that it's only imported into memory
when explicitly needed.
"""
PROVINCE_CHOICES = (
('AB', 'Alberta'),
('BC', 'British Columbia'),
('MB', 'Manitoba'),
('NB', 'New Brunswick'),
('NF', 'Newfoundland and Labrador'),
('NT', 'Northwest Territories'),
('NS', 'Nova Scotia'),
('NU', 'Nunavut'),
('ON', 'Ontario'),
('PE', 'Prince Edward Island'),
('QC', 'Quebec'),
('SK', 'Saskatchewan'),
('YK', 'Yukon')
)
PROVINCES_NORMALIZED = {
'ab': 'AB',
'alberta': 'AB',
'bc': 'BC',
'b.c.': 'BC',
'british columbia': 'BC',
'mb': 'MB',
'manitoba': 'MB',
'nf': 'NF',
'newfoundland': 'NF',
'newfoundland and labrador': 'NF',
'nt': 'NT',
'northwest territories': 'NT',
'ns': 'NS',
'nova scotia': 'NS',
'nu': 'NU',
'nunavut': 'NU',
'on': 'ON',
'ontario': 'ON',
'pe': 'PE',
'pei': 'PE',
'p.e.i.': 'PE',
'prince edward island': 'PE',
'qc': 'QC',
'quebec': 'QC',
'sk': 'SK',
'saskatchewan': 'SK',
'yk': 'YK',
'yukon': 'YK',
}

View File

@ -0,0 +1,112 @@
"""
Canada-specific Form helpers
"""
from django.newforms import ValidationError
from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
from django.newforms.util import smart_unicode
from django.utils.translation import gettext, ugettext
import re
phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$')
sin_re = re.compile(r"^(\d{3})-(\d{3})-(\d{3})$")
class CAPostalCodeField(RegexField):
"""Canadian postal code field."""
def __init__(self, *args, **kwargs):
super(CAPostalCodeField, self).__init__(r'^[ABCEGHJKLMNPRSTVXYZ]\d[A-Z] \d[A-Z]\d$',
max_length=None, min_length=None,
error_message=gettext(u'Enter a postal code in the format XXX XXX.'),
*args, **kwargs)
class CAPhoneNumberField(Field):
"""Canadian phone number field."""
def clean(self, value):
"""Validate a phone number.
"""
super(CAPhoneNumberField, self).clean(value)
if value in EMPTY_VALUES:
return u''
value = re.sub('(\(|\)|\s+)', '', smart_unicode(value))
m = phone_digits_re.search(value)
if m:
return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3))
raise ValidationError(u'Phone numbers must be in XXX-XXX-XXXX format.')
class CAProvinceField(Field):
"""
A form field that validates its input is a Canadian province name or abbreviation.
It normalizes the input to the standard two-leter postal service
abbreviation for the given province.
"""
def clean(self, value):
from ca_provinces import PROVINCES_NORMALIZED
super(CAProvinceField, self).clean(value)
if value in EMPTY_VALUES:
return u''
try:
value = value.strip().lower()
except AttributeError:
pass
else:
try:
return PROVINCES_NORMALIZED[value.strip().lower()].decode('ascii')
except KeyError:
pass
raise ValidationError(u'Enter a Canadian province or territory.')
class CAProvinceSelect(Select):
"""
A Select widget that uses a list of Canadian provinces and
territories as its choices.
"""
def __init__(self, attrs=None):
from ca_provinces import PROVINCE_CHOICES # relative import
super(CAProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES)
class CASocialInsuranceNumberField(Field):
"""
A Canadian Social Insurance Number (SIN).
Checks the following rules to determine whether the number is valid:
* Conforms to the XXX-XXX-XXXX format.
* Passes the check digit process "Luhn Algorithm"
See: http://en.wikipedia.org/wiki/Social_Insurance_Number
"""
def clean(self, value):
super(CASocialInsuranceNumberField, self).clean(value)
if value in EMPTY_VALUES:
return u''
msg = ugettext('Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format.')
match = re.match(sin_re, value)
if not match:
raise ValidationError(msg)
number = u'%s-%s-%s' % (match.group(1), match.group(2), match.group(3))
check_number = u'%s%s%s' % (match.group(1), match.group(2), match.group(3))
if not self.luhn_checksum_is_valid(check_number):
raise ValidationError(msg)
return number
def luhn_checksum_is_valid(self, number):
"""
Checks to make sure that the SIN passes a luhn mod-10 checksum
See: http://en.wikipedia.org/wiki/Luhn_algorithm
"""
sum = 0
num_digits = len(number)
oddeven = num_digits & 1
for count in range(0, num_digits):
digit = int(number[count])
if not (( count & 1 ) ^ oddeven ):
digit = digit * 2
if digit > 9:
digit = digit - 9
sum = sum + digit
return ( (sum % 10) == 0 )

View File

@ -0,0 +1,38 @@
from django import newforms as forms
DEFAULT_DATE_INPUT_FORMATS = (
'%Y-%m-%d', '%d/%m/%Y', '%d/%m/%y', # '2006-10-25', '25/10/2006', '25/10/06'
'%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
'%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006'
'%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
'%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
)
DEFAULT_DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
'%d/%m/%Y %H:%M:%S', # '25/10/2006 14:30:59'
'%d/%m/%Y %H:%M', # '25/10/2006 14:30'
'%d/%m/%Y', # '25/10/2006'
'%d/%m/%y %H:%M:%S', # '25/10/06 14:30:59'
'%d/%m/%y %H:%M', # '25/10/06 14:30'
'%d/%m/%y', # '25/10/06'
)
class DateField(forms.DateField):
"""
A date input field which uses non-US date input formats by default.
"""
def __init__(self, input_formats=None, *args, **kwargs):
input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
super(DateField, self).__init__(input_formats=input_formats, *args, **kwargs)
class DateTimeField(forms.DateTimeField):
"""
A date and time input field which uses non-US date and time input formats
by default.
"""
def __init__(self, input_formats=None, *args, **kwargs):
input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
super(DateTimeField, self).__init__(input_formats=input_formats, *args, **kwargs)

View File

@ -142,7 +142,7 @@ def fix_location_header(request, response):
Code constructing response objects is free to insert relative paths and
this function converts them to absolute paths.
"""
if 'Location' in response.headers and http.get_host(request):
if 'location' in response.headers and http.get_host(request):
response['Location'] = request.build_absolute_uri(response['Location'])
return response

View File

@ -1,4 +1,5 @@
from django.core.management.base import AppCommand
from django.utils.encoding import force_unicode
from django.utils.text import capfirst
MODULE_TEMPLATE = ''' {%% if perms.%(app)s.%(addperm)s or perms.%(app)s.%(changeperm)s %%}
@ -24,7 +25,7 @@ class Command(AppCommand):
output.append(MODULE_TEMPLATE % {
'app': app_label,
'mod': model._meta.module_name,
'name': capfirst(model._meta.verbose_name_plural),
'name': force_unicode(capfirst(model._meta.verbose_name_plural)),
'addperm': model._meta.get_add_permission(),
'changeperm': model._meta.get_change_permission(),
})

View File

@ -7,6 +7,9 @@ class Command(BaseCommand):
make_option('--verbosity', action='store', dest='verbosity', default='1',
type='choice', choices=['0', '1', '2'],
help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
make_option('--addrport', action='store', dest='addrport',
type='string', default='',
help='port number or ipaddr:port to run the server on'),
)
help = 'Runs a development server with data from the given fixture(s).'
args = '[fixture ...]'
@ -19,6 +22,7 @@ class Command(BaseCommand):
from django.test.utils import create_test_db
verbosity = int(options.get('verbosity', 1))
addrport = options.get('addrport')
# Create a test database.
db_name = create_test_db(verbosity=verbosity)
@ -30,4 +34,4 @@ class Command(BaseCommand):
# a strange error -- it causes this handle() method to be called
# multiple times.
shutdown_message = '\nServer stopped.\nNote that the test database, %r, has not been deleted. You can explore it on your own.' % db_name
call_command('runserver', shutdown_message=shutdown_message, use_reloader=False)
call_command('runserver', addrport=addrport, shutdown_message=shutdown_message, use_reloader=False)

View File

@ -1,5 +1,6 @@
import sys
from django.core.management.color import color_style
from django.utils.itercompat import is_iterable
class ModelErrorCollection:
def __init__(self, outfile=sys.stdout):
@ -51,7 +52,8 @@ def get_validation_errors(outfile, app=None):
if f.prepopulate_from is not None and type(f.prepopulate_from) not in (list, tuple):
e.add(opts, '"%s": prepopulate_from should be a list or tuple.' % f.name)
if f.choices:
if not hasattr(f.choices, '__iter__'):
if isinstance(f.choices, basestring) or \
not is_iterable(f.choices):
e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
else:
for c in f.choices:

View File

@ -438,21 +438,6 @@ class FormatStylePlaceholderCursor(Database.Cursor):
"""
charset = 'utf-8'
def _rewrite_args(self, query, params=None):
if params is None:
params = []
else:
params = self._format_params(params)
args = [(':arg%d' % i) for i in range(len(params))]
query = smart_str(query, self.charset) % tuple(args)
# cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
# it does want a trailing ';' but not a trailing '/'. However, these
# characters must be included in the original query in case the query
# is being passed to SQL*Plus.
if query.endswith(';') or query.endswith('/'):
query = query[:-1]
return query, params
def _format_params(self, params):
if isinstance(params, dict):
result = {}
@ -464,12 +449,35 @@ class FormatStylePlaceholderCursor(Database.Cursor):
return tuple([smart_str(p, self.charset, True) for p in params])
def execute(self, query, params=None):
query, params = self._rewrite_args(query, params)
if params is None:
params = []
else:
params = self._format_params(params)
args = [(':arg%d' % i) for i in range(len(params))]
# cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
# it does want a trailing ';' but not a trailing '/'. However, these
# characters must be included in the original query in case the query
# is being passed to SQL*Plus.
if query.endswith(';') or query.endswith('/'):
query = query[:-1]
query = smart_str(query, self.charset) % tuple(args)
return Database.Cursor.execute(self, query, params)
def executemany(self, query, params=None):
query, params = self._rewrite_args(query, params)
return Database.Cursor.executemany(self, query, params)
try:
args = [(':arg%d' % i) for i in range(len(params[0]))]
except (IndexError, TypeError):
# No params given, nothing to do
return None
# cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
# it does want a trailing ';' but not a trailing '/'. However, these
# characters must be included in the original query in case the query
# is being passed to SQL*Plus.
if query.endswith(';') or query.endswith('/'):
query = query[:-1]
query = smart_str(query, self.charset) % tuple(args)
new_param_list = [self._format_params(i) for i in params]
return Database.Cursor.executemany(self, query, new_param_list)
def fetchone(self):
return to_unicode(Database.Cursor.fetchone(self))

View File

@ -133,8 +133,12 @@ class SQLiteCursorWrapper(Database.Cursor):
return Database.Cursor.execute(self, query, params)
def executemany(self, query, param_list):
query = self.convert_query(query, len(param_list[0]))
return Database.Cursor.executemany(self, query, param_list)
try:
query = self.convert_query(query, len(param_list[0]))
return Database.Cursor.executemany(self, query, param_list)
except (IndexError,TypeError):
# No parameter list provided
return None
def convert_query(self, query, num_params):
return query % tuple("?" * num_params)

View File

@ -12,7 +12,7 @@ from django.db.models.loading import register_models, get_model
from django.dispatch import dispatcher
from django.utils.datastructures import SortedDict
from django.utils.functional import curry
from django.utils.encoding import smart_str, force_unicode
from django.utils.encoding import smart_str, force_unicode, smart_unicode
from django.conf import settings
from itertools import izip
import types
@ -213,7 +213,7 @@ class Model(object):
pk_val = self._get_pk_val()
# Note: the comparison with '' is required for compatibility with
# oldforms-style model creation.
pk_set = pk_val is not None and pk_val != u''
pk_set = pk_val is not None and smart_unicode(pk_val) != u''
record_exists = True
if pk_set:
# Determine whether a record with the primary key already exists.

View File

@ -693,7 +693,8 @@ class DecimalField(Field):
class EmailField(CharField):
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 75
if 'max_length' not in kwargs:
kwargs['max_length'] = 75
CharField.__init__(self, *args, **kwargs)
def get_internal_type(self):

View File

@ -52,9 +52,19 @@ class Options(object):
del meta_attrs['__doc__']
for attr_name in DEFAULT_NAMES:
setattr(self, attr_name, meta_attrs.pop(attr_name, getattr(self, attr_name)))
# unique_together can be either a tuple of tuples, or a single
# tuple of two strings. Normalize it to a tuple of tuples, so that
# calling code can uniformly expect that.
ut = meta_attrs.pop('unique_together', getattr(self, 'unique_together'))
if ut and not isinstance(ut[0], (tuple, list)):
ut = (ut,)
setattr(self, 'unique_together', ut)
# verbose_name_plural is a special case because it uses a 's'
# by default.
setattr(self, 'verbose_name_plural', meta_attrs.pop('verbose_name_plural', string_concat(self.verbose_name, 's')))
# Any leftover attributes must be invalid.
if meta_attrs != {}:
raise TypeError, "'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())

View File

@ -246,7 +246,7 @@ class HttpResponse(object):
else:
self._container = [content]
self._is_string = True
self.headers = {'Content-Type': content_type}
self.headers = {'content-type': content_type}
self.cookies = SimpleCookie()
if status:
self.status_code = status
@ -258,24 +258,20 @@ class HttpResponse(object):
+ '\n\n' + self.content
def __setitem__(self, header, value):
self.headers[header] = value
self.headers[header.lower()] = value
def __delitem__(self, header):
try:
del self.headers[header]
del self.headers[header.lower()]
except KeyError:
pass
def __getitem__(self, header):
return self.headers[header]
return self.headers[header.lower()]
def has_header(self, header):
"Case-insensitive check for a header"
header = header.lower()
for key in self.headers.keys():
if key.lower() == header:
return True
return False
return self.headers.has_key(header.lower())
def set_cookie(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=None):
self.cookies[key] = value
@ -304,7 +300,7 @@ class HttpResponse(object):
content = property(_get_content, _set_content)
def __iter__(self):
self._iterator = self._container.__iter__()
self._iterator = iter(self._container)
return self
def next(self):

View File

@ -14,7 +14,8 @@ def render_to_response(*args, **kwargs):
Returns a HttpResponse whose content is filled with the result of calling
django.template.loader.render_to_string() with the passed arguments.
"""
return HttpResponse(loader.render_to_string(*args, **kwargs))
httpresponse_kwargs = {'mimetype': kwargs.pop('mimetype')}
return HttpResponse(loader.render_to_string(*args, **kwargs), **httpresponse_kwargs)
load_and_render = render_to_response # For backwards compatibility.
def _get_queryset(klass):

View File

@ -58,6 +58,7 @@ import re
from inspect import getargspec
from django.conf import settings
from django.template.context import Context, RequestContext, ContextPopException
from django.utils.itercompat import is_iterable
from django.utils.functional import curry, Promise
from django.utils.text import smart_split
from django.utils.encoding import smart_unicode, force_unicode
@ -900,7 +901,7 @@ class Library(object):
if not getattr(self, 'nodelist', False):
from django.template.loader import get_template, select_template
if hasattr(file_name, '__iter__'):
if not isinstance(file_name, basestring) and is_iterable(file_name):
t = select_template(file_name)
else:
t = get_template(file_name)

View File

@ -16,6 +16,7 @@ from django.test import signals
from django.utils.functional import curry
from django.utils.encoding import smart_str
from django.utils.http import urlencode
from django.utils.itercompat import is_iterable
BOUNDARY = 'BoUnDaRyStRiNg'
MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
@ -74,21 +75,22 @@ def encode_multipart(boundary, data):
'',
value.read()
])
elif hasattr(value, '__iter__'):
for item in value:
else:
if not isinstance(value, basestring) and is_iterable(value):
for item in value:
lines.extend([
'--' + boundary,
'Content-Disposition: form-data; name="%s"' % to_str(key),
'',
to_str(item)
])
else:
lines.extend([
'--' + boundary,
'Content-Disposition: form-data; name="%s"' % to_str(key),
'',
to_str(item)
to_str(value)
])
else:
lines.extend([
'--' + boundary,
'Content-Disposition: form-data; name="%s"' % to_str(key),
'',
to_str(value)
])
lines.extend([
'--' + boundary + '--',

View File

@ -57,3 +57,13 @@ else:
tee = compat_tee
if hasattr(itertools, 'groupby'):
groupby = itertools.groupby
def is_iterable(x):
"A implementation independent way of checking for iterables"
try:
iter(x)
except TypeError:
return False
else:
return True

View File

@ -402,11 +402,29 @@ introduced in Python 2.4::
def my_view(request):
# ...
In the Django development version, ``login_required`` also takes an optional
``redirect_field_name`` parameter. Example::
from django.contrib.auth.decorators import login_required
def my_view(request):
# ...
my_view = login_required(redirect_field_name='redirect_to')(my_view)
Again, an equivalent example of the more compact decorator syntax introduced in Python 2.4::
from django.contrib.auth.decorators import login_required
@login_required(redirect_field_name='redirect_to')
def my_view(request):
# ...
``login_required`` does the following:
* If the user isn't logged in, redirect to ``settings.LOGIN_URL``
(``/accounts/login/`` by default), passing the current absolute URL
in the query string as ``next``. For example:
in the query string as ``next`` or the value of ``redirect_field_name``.
For example:
``/accounts/login/?next=/polls/3/``.
* If the user is logged in, execute the view normally. The view code is
free to assume the user is logged in.

View File

@ -58,4 +58,29 @@ How to use Databrowse
4. Run the Django server and visit ``/databrowse/`` in your browser.
Requiring user login
====================
You can restrict access to logged-in users with only a few extra lines of
code. Simply add the following import to your URLconf::
from django.contrib.auth.decorators import login_required
Then modify the URLconf so that the ``databrowse.site.root`` view is decorated
with ``login_required``::
(r'^databrowse/(.*)', login_required(databrowse.site.root)),
If you haven't already added support for user logins to your URLconf, as
described in the `user authentication docs`_, then you will need to do so
now with the following mapping::
(r'^accounts/login/$', 'django.contrib.auth.views.login'),
The final step is to create the login form required by
``django.contrib.auth.views.login``. The `user authentication docs`_
provide full details and a sample template that can be used for this
purpose.
.. _template loader docs: ../templates_python/#loader-types
.. _user authentication docs: ../authentication/

View File

@ -627,14 +627,34 @@ This is useful in a number of ways:
in any way, knowing that whatever data changes you're making are only
being made to a test database.
Note that this server can only run on the default port on localhost; it does
not yet accept a ``host`` or ``port`` parameter.
Also note that it does *not* automatically detect changes to your Python source
code (as ``runserver`` does). It does, however, detect changes to templates.
Note that this server does *not* automatically detect changes to your Python
source code (as ``runserver`` does). It does, however, detect changes to
templates.
.. _unit tests: ../testing/
--addrport [port number or ipaddr:port]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Use ``--addrport`` to specify a different port, or IP address and port, from
the default of 127.0.0.1:8000. This value follows exactly the same format and
serves exactly the same function as the argument to the ``runserver`` subcommand.
Examples:
To run the test server on port 7000 with ``fixture1`` and ``fixture2``::
django-admin.py testserver --addrport 7000 fixture1 fixture2
django-admin.py testserver fixture1 fixture2 --addrport 8080
(The above statements are equivalent. We include both of them to demonstrate
that it doesn't matter whether the options come before or after the
``testserver`` command.)
To run on 1.2.3.4:7000 with a `test` fixture::
django-admin.py testserver --addrport 1.2.3.4:7000 test
--verbosity
~~~~~~~~~~~

View File

@ -221,8 +221,10 @@ The admin represents this as an ``<input type="text">`` (a single-line input).
~~~~~~~~~~~~~~
A ``CharField`` that checks that the value is a valid e-mail address.
This doesn't accept ``max_length``; its ``max_length`` is automatically set to
75.
In Django 0.96, this doesn't accept ``max_length``; its ``max_length`` is
automatically set to 75. In the Django development version, ``max_length`` is
set to 75 by default, but you can specify it to override default behavior.
``FileField``
~~~~~~~~~~~~~
@ -1224,6 +1226,13 @@ together. It's used in the Django admin and is enforced at the database
level (i.e., the appropriate ``UNIQUE`` statements are included in the
``CREATE TABLE`` statement).
**New in Django development version**
For convenience, unique_together can be a single list when dealing
with a single set of fields::
unique_together = ("driver", "restaurant")
``verbose_name``
----------------

41
docs/shortcuts.txt Normal file
View File

@ -0,0 +1,41 @@
=========================
Django shortcut functions
=========================
The package ``django.shortcuts`` collects helper functions and classes that
"span" multiple levels of MVC. In other words, these functions/classes
introduce controlled coupling for convenience's sake.
``render_to_response``
======================
``django.shortcuts.render_to_response`` renders a given template with a given
context dictionary and returns an ``HttpResponse`` object with that rendered
text.
Example::
from django.shortcuts import render_to_response
r = render_to_response('myapp/template.html', {'foo': 'bar'})
This example is equivalent to::
from django.http import HttpResponse
from django.template import Context, loader
t = loader.get_template('myapp/template.html')
c = Context({'foo': 'bar'})
r = HttpResponse(t.render(c))
``get_object_or_404``
=====================
``django.shortcuts.get_object_or_404`` calls ``get()`` on a given model
manager, but it raises ``django.http.Http404`` instead of the model's
``DoesNotExist`` exception.
``get_list_or_404``
===================
``django.shortcuts.get_list_or_404`` returns the result of ``filter()`` on a
given model manager, raising ``django.http.Http404`` if the resulting list is
empty.

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
"""
14. Using a custom primary key
@ -92,4 +93,8 @@ DoesNotExist: Employee matching query does not exist.
>>> Business.objects.filter(employees__first_name__startswith='Fran')
[<Business: Sears>]
# Primary key may be unicode string
>>> emp = Employee(employee_code='jaźń')
>>> emp.save()
"""}

View File

@ -250,6 +250,22 @@ class ClientTest(TestCase):
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')"
# Get the page without logging in. Should result in 302.
response = self.client.get('/test_client/login_protected_view_custom_redirect/')
self.assertRedirects(response, 'http://testserver/accounts/login/?redirect_to=/test_client/login_protected_view_custom_redirect/')
# 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_view_custom_redirect/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['user'].username, 'testclient')
def test_view_with_bad_login(self):
"Request a page that is protected with @login, but use bad credentials"

View File

@ -13,6 +13,7 @@ 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_view_custom_redirect/$', views.login_protected_view_changed_redirect),
(r'^session_view/$', views.session_view),
(r'^broken_view/$', views.broken_view),
(r'^mail_sending_view/$', views.mail_sending_view),

View File

@ -122,6 +122,14 @@ def login_protected_view(request):
return HttpResponse(t.render(c))
login_protected_view = login_required(login_protected_view)
def login_protected_view_changed_redirect(request):
"A simple view that is login protected with a custom redirect field set"
t = Template('This is a login protected test. Username is {{ user.username }}.', name='Login Template')
c = Context({'user': request.user})
return HttpResponse(t.render(c))
login_protected_view_changed_redirect = login_required(redirect_field_name="redirect_to")(login_protected_view_changed_redirect)
def session_view(request):
"A view that modifies the session"
request.session['tobacconist'] = 'hovercraft'

View File

@ -0,0 +1,29 @@
from django.db import models
class Square(models.Model):
root = models.IntegerField()
square = models.PositiveIntegerField()
def __unicode__(self):
return "%s ** 2 == %s" % (self.root, self.square)
__test__ = {'API_TESTS': """
#4896: Test cursor.executemany
>>> from django.db import connection
>>> cursor = connection.cursor()
>>> cursor.executemany('INSERT INTO BACKENDS_SQUARE (ROOT, SQUARE) VALUES (%s, %s)',
... [(i, i**2) for i in range(-5, 6)]) and None or None
>>> Square.objects.order_by('root')
[<Square: -5 ** 2 == 25>, <Square: -4 ** 2 == 16>, <Square: -3 ** 2 == 9>, <Square: -2 ** 2 == 4>, <Square: -1 ** 2 == 1>, <Square: 0 ** 2 == 0>, <Square: 1 ** 2 == 1>, <Square: 2 ** 2 == 4>, <Square: 3 ** 2 == 9>, <Square: 4 ** 2 == 16>, <Square: 5 ** 2 == 25>]
#4765: executemany with params=None or params=[] does nothing
>>> cursor.executemany('INSERT INTO BACKENDS_SQUARE (ROOT, SQUARE) VALUES (%s, %s)', None) and None or None
>>> Square.objects.count()
11
>>> cursor.executemany('INSERT INTO BACKENDS_SQUARE (ROOT, SQUARE) VALUES (%s, %s)', []) and None or None
>>> Square.objects.count()
11
"""}

View File

@ -1818,4 +1818,380 @@ u''
u''
>>> f.clean(u'')
u''
# CAPostalCodeField ##############################################################
CAPostalCodeField validates that the data is a six-character Canadian postal code.
>>> from django.contrib.localflavor.ca.forms import CAPostalCodeField
>>> f = CAPostalCodeField()
>>> f.clean('T2S 2H7')
u'T2S 2H7'
>>> f.clean('T2S 2H')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean('2T6 H8I')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean('T2S2H')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean(90210)
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean(None)
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f = CAPostalCodeField(required=False)
>>> f.clean('T2S 2H7')
u'T2S 2H7'
>>> f.clean('T2S2H7')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean('T2S 2H')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean('2T6 H8I')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean('T2S2H')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean(90210)
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean(None)
u''
>>> f.clean('')
u''
# CAPhoneNumberField ##########################################################
CAPhoneNumberField validates that the data is a valid Canadian phone number,
including the area code. It's normalized to XXX-XXX-XXXX format.
Note: This test is exactly the same as the USPhoneNumberField except using a real
Candian area code
>>> from django.contrib.localflavor.ca.forms import CAPhoneNumberField
>>> f = CAPhoneNumberField()
>>> f.clean('403-555-1212')
u'403-555-1212'
>>> f.clean('4035551212')
u'403-555-1212'
>>> f.clean('403 555-1212')
u'403-555-1212'
>>> f.clean('(403) 555-1212')
u'403-555-1212'
>>> f.clean('403 555 1212')
u'403-555-1212'
>>> f.clean('403.555.1212')
u'403-555-1212'
>>> f.clean('403.555-1212')
u'403-555-1212'
>>> f.clean(' (403) 555.1212 ')
u'403-555-1212'
>>> f.clean('555-1212')
Traceback (most recent call last):
...
ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
>>> f.clean('403-55-1212')
Traceback (most recent call last):
...
ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
>>> f.clean(None)
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f = CAPhoneNumberField(required=False)
>>> f.clean('403-555-1212')
u'403-555-1212'
>>> f.clean('4035551212')
u'403-555-1212'
>>> f.clean('403 555-1212')
u'403-555-1212'
>>> f.clean('(403) 555-1212')
u'403-555-1212'
>>> f.clean('403 555 1212')
u'403-555-1212'
>>> f.clean('403.555.1212')
u'403-555-1212'
>>> f.clean('403.555-1212')
u'403-555-1212'
>>> f.clean(' (403) 555.1212 ')
u'403-555-1212'
>>> f.clean('555-1212')
Traceback (most recent call last):
...
ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
>>> f.clean('403-55-1212')
Traceback (most recent call last):
...
ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
>>> f.clean(None)
u''
>>> f.clean('')
u''
# CAProvinceField ################################################################
CAProvinceField validates that the data is either an abbreviation or name of a
Canadian province.
>>> from django.contrib.localflavor.ca.forms import CAProvinceField
>>> f = CAProvinceField()
>>> f.clean('ab')
u'AB'
>>> f.clean('BC')
u'BC'
>>> f.clean('nova scotia')
u'NS'
>>> f.clean(' manitoba ')
u'MB'
>>> f.clean('T2S 2H7')
Traceback (most recent call last):
...
ValidationError: [u'Enter a Canadian province or territory.']
>>> f.clean(None)
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f = CAProvinceField(required=False)
>>> f.clean('ab')
u'AB'
>>> f.clean('BC')
u'BC'
>>> f.clean('nova scotia')
u'NS'
>>> f.clean(' manitoba ')
u'MB'
>>> f.clean('T2S 2H7')
Traceback (most recent call last):
...
ValidationError: [u'Enter a Canadian province or territory.']
>>> f.clean(None)
u''
>>> f.clean('')
u''
# CAProvinceSelect ###############################################################
CAProvinceSelect is a Select widget that uses a list of Canadian provinces/territories
as its choices.
>>> from django.contrib.localflavor.ca.forms import CAProvinceSelect
>>> w = CAProvinceSelect()
>>> print w.render('province', 'AB')
<select name="province">
<option value="AB" selected="selected">Alberta</option>
<option value="BC">British Columbia</option>
<option value="MB">Manitoba</option>
<option value="NB">New Brunswick</option>
<option value="NF">Newfoundland and Labrador</option>
<option value="NT">Northwest Territories</option>
<option value="NS">Nova Scotia</option>
<option value="NU">Nunavut</option>
<option value="ON">Ontario</option>
<option value="PE">Prince Edward Island</option>
<option value="QC">Quebec</option>
<option value="SK">Saskatchewan</option>
<option value="YK">Yukon</option>
</select>
# CASocialInsuranceNumberField #################################################
>>> from django.contrib.localflavor.ca.forms import CASocialInsuranceNumberField
>>> f = CASocialInsuranceNumberField()
>>> f.clean('046-454-286')
u'046-454-286'
>>> f.clean('046-454-287')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format.']
>>> f.clean('046 454 286')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format.']
>>> f.clean('046-44-286')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format.']
## Generic DateField ##########################################################
>>> from django.contrib.localflavor.generic.forms import *
A DateField that uses generic dd/mm/yy dates instead of mm/dd/yy where
appropriate.
>>> import datetime
>>> f = DateField()
>>> f.clean(datetime.date(2006, 10, 25))
datetime.date(2006, 10, 25)
>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30))
datetime.date(2006, 10, 25)
>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59))
datetime.date(2006, 10, 25)
>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200))
datetime.date(2006, 10, 25)
>>> f.clean('2006-10-25')
datetime.date(2006, 10, 25)
>>> f.clean('25/10/2006')
datetime.date(2006, 10, 25)
>>> f.clean('25/10/06')
datetime.date(2006, 10, 25)
>>> f.clean('Oct 25 2006')
datetime.date(2006, 10, 25)
>>> f.clean('October 25 2006')
datetime.date(2006, 10, 25)
>>> f.clean('October 25, 2006')
datetime.date(2006, 10, 25)
>>> f.clean('25 October 2006')
datetime.date(2006, 10, 25)
>>> f.clean('25 October, 2006')
datetime.date(2006, 10, 25)
>>> f.clean('2006-4-31')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid date.']
>>> f.clean('200a-10-25')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid date.']
>>> f.clean('10/25/06')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid date.']
>>> f.clean(None)
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f = DateField(required=False)
>>> f.clean(None)
>>> repr(f.clean(None))
'None'
>>> f.clean('')
>>> repr(f.clean(''))
'None'
DateField accepts an optional input_formats parameter:
>>> f = DateField(input_formats=['%Y %m %d'])
>>> f.clean(datetime.date(2006, 10, 25))
datetime.date(2006, 10, 25)
>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30))
datetime.date(2006, 10, 25)
>>> f.clean('2006 10 25')
datetime.date(2006, 10, 25)
The input_formats parameter overrides all default input formats,
so the default formats won't work unless you specify them:
>>> f.clean('2006-10-25')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid date.']
>>> f.clean('25/10/2006')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid date.']
>>> f.clean('25/10/06')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid date.']
## Generic DateTimeField ######################################################
A DateField that uses generic dd/mm/yy dates instead of mm/dd/yy where
appropriate.
>>> import datetime
>>> f = DateTimeField()
>>> f.clean(datetime.date(2006, 10, 25))
datetime.datetime(2006, 10, 25, 0, 0)
>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30))
datetime.datetime(2006, 10, 25, 14, 30)
>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59))
datetime.datetime(2006, 10, 25, 14, 30, 59)
>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200))
datetime.datetime(2006, 10, 25, 14, 30, 59, 200)
>>> f.clean('2006-10-25 14:30:45')
datetime.datetime(2006, 10, 25, 14, 30, 45)
>>> f.clean('2006-10-25 14:30:00')
datetime.datetime(2006, 10, 25, 14, 30)
>>> f.clean('2006-10-25 14:30')
datetime.datetime(2006, 10, 25, 14, 30)
>>> f.clean('2006-10-25')
datetime.datetime(2006, 10, 25, 0, 0)
>>> f.clean('25/10/2006 14:30:45')
datetime.datetime(2006, 10, 25, 14, 30, 45)
>>> f.clean('25/10/2006 14:30:00')
datetime.datetime(2006, 10, 25, 14, 30)
>>> f.clean('25/10/2006 14:30')
datetime.datetime(2006, 10, 25, 14, 30)
>>> f.clean('25/10/2006')
datetime.datetime(2006, 10, 25, 0, 0)
>>> f.clean('25/10/06 14:30:45')
datetime.datetime(2006, 10, 25, 14, 30, 45)
>>> f.clean('25/10/06 14:30:00')
datetime.datetime(2006, 10, 25, 14, 30)
>>> f.clean('25/10/06 14:30')
datetime.datetime(2006, 10, 25, 14, 30)
>>> f.clean('25/10/06')
datetime.datetime(2006, 10, 25, 0, 0)
>>> f.clean('hello')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid date/time.']
>>> f.clean('2006-10-25 4:30 p.m.')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid date/time.']
DateField accepts an optional input_formats parameter:
>>> f = DateTimeField(input_formats=['%Y %m %d %I:%M %p'])
>>> f.clean(datetime.date(2006, 10, 25))
datetime.datetime(2006, 10, 25, 0, 0)
>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30))
datetime.datetime(2006, 10, 25, 14, 30)
>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59))
datetime.datetime(2006, 10, 25, 14, 30, 59)
>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200))
datetime.datetime(2006, 10, 25, 14, 30, 59, 200)
>>> f.clean('2006 10 25 2:30 PM')
datetime.datetime(2006, 10, 25, 14, 30)
The input_formats parameter overrides all default input formats,
so the default formats won't work unless you specify them:
>>> f.clean('2006-10-25 14:30:45')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid date/time.']
>>> f = DateTimeField(required=False)
>>> f.clean(None)
>>> repr(f.clean(None))
'None'
>>> f.clean('')
>>> repr(f.clean(''))
'None'
"""