1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49:12 +00:00

gis: Merged revisions 7280-7353 via svnmerge from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@7354 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2008-03-23 17:18:58 +00:00
parent b0a895f9e8
commit 2efc34dc5e
60 changed files with 2258 additions and 1125 deletions

View File

@ -59,6 +59,7 @@ answer newbie questions, and generally made Django that much better:
Arthur <avandorp@gmail.com> Arthur <avandorp@gmail.com>
David Avsajanishvili <avsd05@gmail.com> David Avsajanishvili <avsd05@gmail.com>
axiak@mit.edu axiak@mit.edu
Niran Babalola <niran@niran.org>
Morten Bagai <m@bagai.com> Morten Bagai <m@bagai.com>
Mikaël Barbero <mikael.barbero nospam at nospam free.fr> Mikaël Barbero <mikael.barbero nospam at nospam free.fr>
Jiri Barton Jiri Barton
@ -145,6 +146,7 @@ answer newbie questions, and generally made Django that much better:
Jorge Gajon <gajon@gajon.org> Jorge Gajon <gajon@gajon.org>
gandalf@owca.info gandalf@owca.info
Marc Garcia <marc.garcia@accopensys.com> Marc Garcia <marc.garcia@accopensys.com>
Alex Gaynor <alex.gaynor@gmail.com>
Andy Gayton <andy-django@thecablelounge.com> Andy Gayton <andy-django@thecablelounge.com>
Baishampayan Ghose Baishampayan Ghose
Dimitris Glezos <dimitris@glezos.com> Dimitris Glezos <dimitris@glezos.com>
@ -243,6 +245,7 @@ answer newbie questions, and generally made Django that much better:
michael.mcewan@gmail.com michael.mcewan@gmail.com
michal@plovarna.cz michal@plovarna.cz
Mikko Hellsing <mikko@sorl.net> Mikko Hellsing <mikko@sorl.net>
Daniel Lindsley <polarcowz@gmail.com>
Orestis Markou <orestis@orestis.gr> Orestis Markou <orestis@orestis.gr>
Slawek Mikula <slawek dot mikula at gmail dot com> Slawek Mikula <slawek dot mikula at gmail dot com>
mitakummaa@gmail.com mitakummaa@gmail.com
@ -256,6 +259,7 @@ answer newbie questions, and generally made Django that much better:
Robin Munn <http://www.geekforgod.com/> Robin Munn <http://www.geekforgod.com/>
Robert Myers <myer0052@gmail.com> Robert Myers <myer0052@gmail.com>
Nebojša Dorđević Nebojša Dorđević
Doug Napoleone <doug@dougma.com>
Gopal Narayanan <gopastro@gmail.com> Gopal Narayanan <gopastro@gmail.com>
Fraser Nevett <mail@nevett.org> Fraser Nevett <mail@nevett.org>
Sam Newman <http://www.magpiebrain.com/> Sam Newman <http://www.magpiebrain.com/>
@ -269,6 +273,7 @@ answer newbie questions, and generally made Django that much better:
Barry Pederson <bp@barryp.org> Barry Pederson <bp@barryp.org>
permonik@mesias.brnonet.cz permonik@mesias.brnonet.cz
petr.marhoun@gmail.com petr.marhoun@gmail.com
peter@mymart.com
pgross@thoughtworks.com pgross@thoughtworks.com
phaedo <http://phaedo.cx/> phaedo <http://phaedo.cx/>
phil@produxion.net phil@produxion.net
@ -307,6 +312,7 @@ answer newbie questions, and generally made Django that much better:
serbaut@gmail.com serbaut@gmail.com
John Shaffer <jshaffer2112@gmail.com> John Shaffer <jshaffer2112@gmail.com>
Pete Shinners <pete@shinners.org> Pete Shinners <pete@shinners.org>
Leo Shklovskii
jason.sidabras@gmail.com jason.sidabras@gmail.com
Jozko Skrablin <jozko.skrablin@gmail.com> Jozko Skrablin <jozko.skrablin@gmail.com>
Ben Slavin <benjamin.slavin@gmail.com> Ben Slavin <benjamin.slavin@gmail.com>

View File

@ -287,7 +287,7 @@ SESSION_COOKIE_PATH = '/' # The path of the sessio
SESSION_SAVE_EVERY_REQUEST = False # Whether to save the session data on every request. SESSION_SAVE_EVERY_REQUEST = False # Whether to save the session data on every request.
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether sessions expire when a user closes his browser. SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether sessions expire when a user closes his browser.
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # The module to store session data SESSION_ENGINE = 'django.contrib.sessions.backends.db' # The module to store session data
SESSION_FILE_PATH = '/tmp/' # Directory to store session files if using the file session module SESSION_FILE_PATH = None # Directory to store session files if using the file session module. If set to None the backend will use a sensible default.
######### #########
# CACHE # # CACHE #

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,9 @@ class SessionStore(SessionBase):
Implements a file based session store. Implements a file based session store.
""" """
def __init__(self, session_key=None): def __init__(self, session_key=None):
self.storage_path = getattr(settings, "SESSION_FILE_PATH", tempfile.gettempdir()) self.storage_path = getattr(settings, "SESSION_FILE_PATH", None)
if not self.storage_path:
self.storage_path = tempfile.gettempdir()
# Make sure the storage path is valid. # Make sure the storage path is valid.
if not os.path.isdir(self.storage_path): if not os.path.isdir(self.storage_path):

View File

@ -4,6 +4,7 @@ from django.contrib.sites.models import Site, RequestSite
from django.utils import feedgenerator from django.utils import feedgenerator
from django.utils.encoding import smart_unicode, iri_to_uri from django.utils.encoding import smart_unicode, iri_to_uri
from django.conf import settings from django.conf import settings
from django.template import RequestContext
def add_domain(domain, url): def add_domain(domain, url):
if not (url.startswith('http://') or url.startswith('https://')): if not (url.startswith('http://') or url.startswith('https://')):
@ -55,18 +56,23 @@ class Feed(object):
return attr() return attr()
return attr return attr
def get_object(self, bits):
return None
def get_feed(self, url=None): def get_feed(self, url=None):
""" """
Returns a feedgenerator.DefaultFeed object, fully populated, for Returns a feedgenerator.DefaultFeed object, fully populated, for
this feed. Raises FeedDoesNotExist for invalid parameters. this feed. Raises FeedDoesNotExist for invalid parameters.
""" """
if url: if url:
try: bits = url.split('/')
obj = self.get_object(url.split('/'))
except (AttributeError, ObjectDoesNotExist):
raise FeedDoesNotExist
else: else:
obj = None bits = []
try:
obj = self.get_object(bits)
except ObjectDoesNotExist:
raise FeedDoesNotExist
if Site._meta.installed: if Site._meta.installed:
current_site = Site.objects.get_current() current_site = Site.objects.get_current()
@ -119,9 +125,9 @@ class Feed(object):
else: else:
author_email = author_link = None author_email = author_link = None
feed.add_item( feed.add_item(
title = title_tmp.render(Context({'obj': item, 'site': current_site})), title = title_tmp.render(RequestContext(self.request, {'obj': item, 'site': current_site})),
link = link, link = link,
description = description_tmp.render(Context({'obj': item, 'site': current_site})), description = description_tmp.render(RequestContext(self.request, {'obj': item, 'site': current_site})),
unique_id = self.__get_dynamic_attr('item_guid', item, link), unique_id = self.__get_dynamic_attr('item_guid', item, link),
enclosure = enc, enclosure = enc,
pubdate = self.__get_dynamic_attr('item_pubdate', item), pubdate = self.__get_dynamic_attr('item_pubdate', item),

View File

@ -2,20 +2,21 @@
Tools for sending email. Tools for sending email.
""" """
from django.conf import settings
from django.utils.encoding import smart_str, force_unicode
from email import Charset, Encoders
from email.MIMEText import MIMEText
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.Header import Header
from email.Utils import formatdate, parseaddr, formataddr
import mimetypes import mimetypes
import os import os
import smtplib import smtplib
import socket import socket
import time import time
import random import random
from email import Charset, Encoders
from email.MIMEText import MIMEText
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.Header import Header
from email.Utils import formatdate, parseaddr, formataddr
from django.conf import settings
from django.utils.encoding import smart_str, force_unicode
# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from # Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
# some spam filters. # some spam filters.
@ -38,8 +39,9 @@ class CachedDnsName(object):
DNS_NAME = CachedDnsName() DNS_NAME = CachedDnsName()
# Copied from Python standard library and modified to used the cached hostname # Copied from Python standard library, with the following modifications:
# for performance. # * Used cached hostname for performance.
# * Added try/except to support lack of getpid() in Jython (#5496).
def make_msgid(idstring=None): def make_msgid(idstring=None):
"""Returns a string suitable for RFC 2822 compliant Message-ID, e.g: """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
@ -53,7 +55,7 @@ def make_msgid(idstring=None):
try: try:
pid = os.getpid() pid = os.getpid()
except AttributeError: except AttributeError:
# Not getpid() in Jython, for example. # No getpid() in Jython, for example.
pid = 1 pid = 1
randint = random.randrange(100000) randint = random.randrange(100000)
if idstring is None: if idstring is None:
@ -68,7 +70,7 @@ class BadHeaderError(ValueError):
pass pass
def forbid_multi_line_headers(name, val): def forbid_multi_line_headers(name, val):
"Forbids multi-line headers, to prevent header injection." """Forbids multi-line headers, to prevent header injection."""
if '\n' in val or '\r' in val: if '\n' in val or '\r' in val:
raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name)) raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
try: try:
@ -112,14 +114,17 @@ class SMTPConnection(object):
def open(self): def open(self):
""" """
Ensure we have a connection to the email server. Returns whether or not Ensures we have a connection to the email server. Returns whether or
a new connection was required. not a new connection was required (True or False).
""" """
if self.connection: if self.connection:
# Nothing to do if the connection is already open. # Nothing to do if the connection is already open.
return False return False
try: try:
self.connection = smtplib.SMTP(self.host, self.port) # If local_hostname is not specified, socket.getfqdn() gets used.
# For performance, we use the cached FQDN for local_hostname.
self.connection = smtplib.SMTP(self.host, self.port,
local_hostname=DNS_NAME.get_fqdn())
if self.use_tls: if self.use_tls:
self.connection.ehlo() self.connection.ehlo()
self.connection.starttls() self.connection.starttls()
@ -132,7 +137,7 @@ class SMTPConnection(object):
raise raise
def close(self): def close(self):
"""Close the connection to the email server.""" """Closes the connection to the email server."""
try: try:
try: try:
self.connection.quit() self.connection.quit()
@ -149,7 +154,7 @@ class SMTPConnection(object):
def send_messages(self, email_messages): def send_messages(self, email_messages):
""" """
Send one or more EmailMessage objects and return the number of email Sends one or more EmailMessage objects and returns the number of email
messages sent. messages sent.
""" """
if not email_messages: if not email_messages:
@ -192,7 +197,7 @@ class EmailMessage(object):
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
connection=None, attachments=None, headers=None): connection=None, attachments=None, headers=None):
""" """
Initialise a single email message (which can be sent to multiple Initialize a single email message (which can be sent to multiple
recipients). recipients).
All strings used to create the message can be unicode strings (or UTF-8 All strings used to create the message can be unicode strings (or UTF-8
@ -221,7 +226,8 @@ class EmailMessage(object):
def message(self): def message(self):
encoding = self.encoding or settings.DEFAULT_CHARSET encoding = self.encoding or settings.DEFAULT_CHARSET
msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET), self.content_subtype, encoding) msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
self.content_subtype, encoding)
if self.attachments: if self.attachments:
body_msg = msg body_msg = msg
msg = SafeMIMEMultipart(_subtype=self.multipart_subtype) msg = SafeMIMEMultipart(_subtype=self.multipart_subtype)
@ -237,8 +243,6 @@ class EmailMessage(object):
msg['To'] = ', '.join(self.to) msg['To'] = ', '.join(self.to)
msg['Date'] = formatdate() msg['Date'] = formatdate()
msg['Message-ID'] = make_msgid() msg['Message-ID'] = make_msgid()
if self.bcc:
msg['Bcc'] = ', '.join(self.bcc)
for name, value in self.extra_headers.items(): for name, value in self.extra_headers.items():
msg[name] = value msg[name] = value
return msg return msg
@ -251,7 +255,7 @@ class EmailMessage(object):
return self.to + self.bcc return self.to + self.bcc
def send(self, fail_silently=False): def send(self, fail_silently=False):
"""Send the email message.""" """Sends the email message."""
return self.get_connection(fail_silently).send_messages([self]) return self.get_connection(fail_silently).send_messages([self])
def attach(self, filename=None, content=None, mimetype=None): def attach(self, filename=None, content=None, mimetype=None):
@ -278,7 +282,7 @@ class EmailMessage(object):
def _create_attachment(self, filename, content, mimetype=None): def _create_attachment(self, filename, content, mimetype=None):
""" """
Convert the filename, content, mimetype triple into a MIME attachment Converts the filename, content, mimetype triple into a MIME attachment
object. object.
""" """
if mimetype is None: if mimetype is None:
@ -295,7 +299,8 @@ class EmailMessage(object):
attachment.set_payload(content) attachment.set_payload(content)
Encoders.encode_base64(attachment) Encoders.encode_base64(attachment)
if filename: if filename:
attachment.add_header('Content-Disposition', 'attachment', filename=filename) attachment.add_header('Content-Disposition', 'attachment',
filename=filename)
return attachment return attachment
class EmailMultiAlternatives(EmailMessage): class EmailMultiAlternatives(EmailMessage):
@ -310,7 +315,8 @@ class EmailMultiAlternatives(EmailMessage):
"""Attach an alternative content representation.""" """Attach an alternative content representation."""
self.attach(content=content, mimetype=mimetype) self.attach(content=content, mimetype=mimetype)
def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None): def send_mail(subject, message, from_email, recipient_list,
fail_silently=False, auth_user=None, auth_password=None):
""" """
Easy wrapper for sending a single message to a recipient list. All members Easy wrapper for sending a single message to a recipient list. All members
of the recipient list will see the other recipients in the 'To' field. of the recipient list will see the other recipients in the 'To' field.
@ -323,9 +329,11 @@ def send_mail(subject, message, from_email, recipient_list, fail_silently=False,
""" """
connection = SMTPConnection(username=auth_user, password=auth_password, connection = SMTPConnection(username=auth_user, password=auth_password,
fail_silently=fail_silently) fail_silently=fail_silently)
return EmailMessage(subject, message, from_email, recipient_list, connection=connection).send() return EmailMessage(subject, message, from_email, recipient_list,
connection=connection).send()
def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None): def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
auth_password=None):
""" """
Given a datatuple of (subject, message, from_email, recipient_list), sends Given a datatuple of (subject, message, from_email, recipient_list), sends
each message to each recipient list. Returns the number of e-mails sent. each message to each recipient list. Returns the number of e-mails sent.
@ -340,18 +348,18 @@ def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password
""" """
connection = SMTPConnection(username=auth_user, password=auth_password, connection = SMTPConnection(username=auth_user, password=auth_password,
fail_silently=fail_silently) fail_silently=fail_silently)
messages = [EmailMessage(subject, message, sender, recipient) for subject, message, sender, recipient in datatuple] messages = [EmailMessage(subject, message, sender, recipient)
for subject, message, sender, recipient in datatuple]
return connection.send_messages(messages) return connection.send_messages(messages)
def mail_admins(subject, message, fail_silently=False): def mail_admins(subject, message, fail_silently=False):
"Sends a message to the admins, as defined by the ADMINS setting." """Sends a message to the admins, as defined by the ADMINS setting."""
EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message, EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
settings.SERVER_EMAIL, [a[1] for a in settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]
settings.ADMINS]).send(fail_silently=fail_silently) ).send(fail_silently=fail_silently)
def mail_managers(subject, message, fail_silently=False): def mail_managers(subject, message, fail_silently=False):
"Sends a message to the managers, as defined by the MANAGERS setting." """Sends a message to the managers, as defined by the MANAGERS setting."""
EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message, EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
settings.SERVER_EMAIL, [a[1] for a in settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
settings.MANAGERS]).send(fail_silently=fail_silently) ).send(fail_silently=fail_silently)

View File

@ -243,7 +243,7 @@ def setup_environ(settings_mod):
# way. For example, if this file (manage.py) lives in a directory # way. For example, if this file (manage.py) lives in a directory
# "myproject", this code would add "/path/to/myproject" to sys.path. # "myproject", this code would add "/path/to/myproject" to sys.path.
project_directory, settings_filename = os.path.split(settings_mod.__file__) project_directory, settings_filename = os.path.split(settings_mod.__file__)
if not project_directory: if project_directory == os.curdir or not project_directory:
project_directory = os.getcwd() project_directory = os.getcwd()
project_name = os.path.basename(project_directory) project_name = os.path.basename(project_directory)
settings_name = os.path.splitext(settings_filename)[0] settings_name = os.path.splitext(settings_filename)[0]

View File

@ -20,8 +20,13 @@ class Command(LabelCommand):
# the parent directory. # the parent directory.
directory = os.getcwd() directory = os.getcwd()
if project_name in INVALID_PROJECT_NAMES: try:
proj_name = __import__(project_name)
if proj_name:
raise CommandError("%r conflicts with the name of an existing Python module and cannot be used as a project name. Please try another name." % project_name) raise CommandError("%r conflicts with the name of an existing Python module and cannot be used as a project name. Please try another name." % project_name)
except ImportError:
if project_name in INVALID_PROJECT_NAMES:
raise CommandError("%r contains an invalid project name. Please try another name." % project_name)
copy_helper(self.style, 'project', project_name, directory) copy_helper(self.style, 'project', project_name, directory)

View File

@ -1,46 +1,149 @@
class InvalidPage(Exception): class InvalidPage(Exception):
pass pass
class ObjectPaginator(object): class Paginator(object):
def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True):
self.object_list = object_list
self.per_page = per_page
self.orphans = orphans
self.allow_empty_first_page = allow_empty_first_page
self._num_pages = self._count = None
def validate_number(self, number):
"Validates the given 1-based page number."
try:
number = int(number)
except ValueError:
raise InvalidPage('That page number is not an integer')
if number < 1:
raise InvalidPage('That page number is less than 1')
if number > self.num_pages:
if number == 1 and self.allow_empty_first_page:
pass
else:
raise InvalidPage('That page contains no results')
return number
def page(self, number):
"Returns a Page object for the given 1-based page number."
number = self.validate_number(number)
bottom = (number - 1) * self.per_page
top = bottom + self.per_page
if top + self.orphans >= self.count:
top = self.count
return Page(self.object_list[bottom:top], number, self)
def _get_count(self):
"Returns the total number of objects, across all pages."
if self._count is None:
self._count = len(self.object_list)
return self._count
count = property(_get_count)
def _get_num_pages(self):
"Returns the total number of pages."
if self._num_pages is None:
hits = self.count - 1 - self.orphans
if hits < 1:
hits = 0
if hits == 0 and not self.allow_empty_first_page:
self._num_pages = 0
else:
self._num_pages = hits // self.per_page + 1
return self._num_pages
num_pages = property(_get_num_pages)
def _get_page_range(self):
""" """
This class makes pagination easy. Feed it a QuerySet or list, plus the number Returns a 1-based range of pages for iterating through within
of objects you want on each page. Then read the hits and pages properties to a template for loop.
see how many pages it involves. Call get_page with a page number (starting """
at 0) to get back a list of objects for that page. return range(1, self.num_pages + 1)
page_range = property(_get_page_range)
Finally, check if a page number has a next/prev page using class QuerySetPaginator(Paginator):
has_next_page(page_number) and has_previous_page(page_number). """
Like Paginator, but works on QuerySets.
"""
def _get_count(self):
if self._count is None:
self._count = self.object_list.count()
return self._count
count = property(_get_count)
Use orphans to avoid small final pages. For example: class Page(object):
13 records, num_per_page=10, orphans=2 --> pages==2, len(self.get_page(0))==10 def __init__(self, object_list, number, paginator):
12 records, num_per_page=10, orphans=2 --> pages==1, len(self.get_page(0))==12 self.object_list = object_list
self.number = number
self.paginator = paginator
def __repr__(self):
return '<Page %s of %s>' % (self.number, self.paginator.num_pages)
def has_next(self):
return self.number < self.paginator.num_pages
def has_previous(self):
return self.number > 1
def has_other_pages(self):
return self.has_previous() or self.has_next()
def next_page_number(self):
return self.number + 1
def previous_page_number(self):
return self.number - 1
def start_index(self):
"""
Returns the 1-based index of the first object on this page,
relative to total objects in the paginator.
"""
return (self.paginator.per_page * (self.number - 1)) + 1
def end_index(self):
"""
Returns the 1-based index of the last object on this page,
relative to total objects found (hits).
"""
if self.number == self.paginator.num_pages:
return self.paginator.count
return self.number * self.paginator.per_page
class ObjectPaginator(Paginator):
"""
Legacy ObjectPaginator class, for backwards compatibility.
Note that each method on this class that takes page_number expects a
zero-based page number, whereas the new API (Paginator/Page) uses one-based
page numbers.
""" """
def __init__(self, query_set, num_per_page, orphans=0): def __init__(self, query_set, num_per_page, orphans=0):
Paginator.__init__(self, query_set, num_per_page, orphans)
import warnings
warnings.warn("The ObjectPaginator is deprecated. Use django.core.paginator.Paginator instead.", DeprecationWarning)
# Keep these attributes around for backwards compatibility.
self.query_set = query_set self.query_set = query_set
self.num_per_page = num_per_page self.num_per_page = num_per_page
self.orphans = orphans
self._hits = self._pages = None self._hits = self._pages = None
self._page_range = None
def validate_page_number(self, page_number): def validate_page_number(self, page_number):
try: try:
page_number = int(page_number) page_number = int(page_number) + 1
except ValueError: except ValueError:
raise InvalidPage raise InvalidPage
if page_number < 0 or page_number > self.pages - 1: return self.validate_number(page_number)
raise InvalidPage
return page_number
def get_page(self, page_number): def get_page(self, page_number):
page_number = self.validate_page_number(page_number) try:
bottom = page_number * self.num_per_page page_number = int(page_number) + 1
top = bottom + self.num_per_page except ValueError:
if top + self.orphans >= self.hits: raise InvalidPage
top = self.hits return self.page(page_number).object_list
return self.query_set[bottom:top]
def has_next_page(self, page_number): def has_next_page(self, page_number):
"Does page $page_number have a 'next' page?"
return page_number < self.pages - 1 return page_number < self.pages - 1
def has_previous_page(self, page_number): def has_previous_page(self, page_number):
@ -52,7 +155,7 @@ class ObjectPaginator(object):
relative to total objects found (hits). relative to total objects found (hits).
""" """
page_number = self.validate_page_number(page_number) page_number = self.validate_page_number(page_number)
return (self.num_per_page * page_number) + 1 return (self.num_per_page * (page_number - 1)) + 1
def last_on_page(self, page_number): def last_on_page(self, page_number):
""" """
@ -60,40 +163,23 @@ class ObjectPaginator(object):
relative to total objects found (hits). relative to total objects found (hits).
""" """
page_number = self.validate_page_number(page_number) page_number = self.validate_page_number(page_number)
page_number += 1 # 1-base if page_number == self.num_pages:
if page_number == self.pages: return self.count
return self.hits
return page_number * self.num_per_page return page_number * self.num_per_page
def _get_hits(self): def _get_count(self):
if self._hits is None: # The old API allowed for self.object_list to be either a QuerySet or a
# Try .count() or fall back to len(). # list. Here, we handle both.
if self._count is None:
try: try:
self._hits = int(self.query_set.count()) self._count = self.object_list.count()
except (AttributeError, TypeError, ValueError): except TypeError:
# AttributeError if query_set has no object count. self._count = len(self.object_list)
# TypeError if query_set.count() required arguments. return self._count
# ValueError if int() fails. count = property(_get_count)
self._hits = len(self.query_set)
return self._hits
def _get_pages(self): # The old API called it "hits" instead of "count".
if self._pages is None: hits = count
hits = (self.hits - 1 - self.orphans)
if hits < 1:
hits = 0
self._pages = hits // self.num_per_page + 1
return self._pages
def _get_page_range(self): # The old API called it "pages" instead of "num_pages".
""" pages = Paginator.num_pages
Returns a 1-based range of pages for iterating through within
a template for loop.
"""
if self._page_range is None:
self._page_range = range(1, self.pages + 1)
return self._page_range
hits = property(_get_hits)
pages = property(_get_pages)
page_range = property(_get_page_range)

View File

@ -60,8 +60,6 @@ class Serializer(object):
""" """
if isinstance(field, models.DateTimeField): if isinstance(field, models.DateTimeField):
value = getattr(obj, field.name).strftime("%Y-%m-%d %H:%M:%S") value = getattr(obj, field.name).strftime("%Y-%m-%d %H:%M:%S")
elif isinstance(field, models.FileField):
value = getattr(obj, "get_%s_url" % field.name, lambda: None)()
else: else:
value = field.flatten_data(follow=None, obj=obj).get(field.name, "") value = field.flatten_data(follow=None, obj=obj).get(field.name, "")
return smart_unicode(value) return smart_unicode(value)

View File

@ -37,7 +37,9 @@ Optional Fcgi settings: (setting=value)
maxchildren=NUMBER hard limit number of processes / threads maxchildren=NUMBER hard limit number of processes / threads
daemonize=BOOL whether to detach from terminal. daemonize=BOOL whether to detach from terminal.
pidfile=FILE write the spawned process-id to this file. pidfile=FILE write the spawned process-id to this file.
workdir=DIRECTORY change to this directory when daemonizing workdir=DIRECTORY change to this directory when daemonizing.
outlog=FILE write stdout to this file.
errlog=FILE write stderr to this file.
Examples: Examples:
Run a "standard" fastcgi process on a file-descriptor Run a "standard" fastcgi process on a file-descriptor
@ -69,6 +71,8 @@ FASTCGI_OPTIONS = {
'minspare': 2, 'minspare': 2,
'maxchildren': 50, 'maxchildren': 50,
'maxrequests': 0, 'maxrequests': 0,
'outlog': None,
'errlog': None,
} }
def fastcgi_help(message=None): def fastcgi_help(message=None):
@ -150,9 +154,15 @@ def runfastcgi(argset=[], **kwargs):
else: else:
return fastcgi_help("ERROR: Invalid option for daemonize parameter.") return fastcgi_help("ERROR: Invalid option for daemonize parameter.")
daemon_kwargs = {}
if options['outlog']:
daemon_kwargs['out_log'] = options['outlog']
if options['errlog']:
daemon_kwargs['err_log'] = options['errlog']
if daemonize: if daemonize:
from django.utils.daemonize import become_daemon from django.utils.daemonize import become_daemon
become_daemon(our_home_dir=options["workdir"]) become_daemon(our_home_dir=options["workdir"], **daemon_kwargs)
if options["pidfile"]: if options["pidfile"]:
fp = open(options["pidfile"], "w") fp = open(options["pidfile"], "w")

View File

@ -847,6 +847,16 @@ class FilePathField(Field):
kwargs['max_length'] = kwargs.get('max_length', 100) kwargs['max_length'] = kwargs.get('max_length', 100)
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
def formfield(self, **kwargs):
defaults = {
'path': self.path,
'match': self.match,
'recursive': self.recursive,
'form_class': forms.FilePathField,
}
defaults.update(kwargs)
return super(FilePathField, self).formfield(**defaults)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [curry(oldforms.FilePathField, path=self.path, match=self.match, recursive=self.recursive)] return [curry(oldforms.FilePathField, path=self.path, match=self.match, recursive=self.recursive)]

View File

@ -548,6 +548,13 @@ class ForeignKey(RelatedField, Field):
params['choices'] = self.get_choices_default() params['choices'] = self.get_choices_default()
return field_objs, params return field_objs, params
def get_default(self):
"Here we check if the default value is an object and return the to_field if so."
field_default = super(ForeignKey, self).get_default()
if isinstance(field_default, self.rel.to):
return getattr(field_default, self.rel.get_related_field().attname)
return field_default
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
rel_field = self.rel.get_related_field() rel_field = self.rel.get_related_field()
if self.rel.raw_id_admin and not isinstance(rel_field, AutoField): if self.rel.raw_id_admin and not isinstance(rel_field, AutoField):

View File

@ -1,5 +1,5 @@
from django.conf import settings from django.conf import settings
from django.db import connection, transaction from django.db import connection, transaction, IntegrityError
from django.db.models.fields import DateField, FieldDoesNotExist from django.db.models.fields import DateField, FieldDoesNotExist
from django.db.models import signals, loading from django.db.models import signals, loading
from django.dispatch import dispatcher from django.dispatch import dispatcher
@ -285,11 +285,14 @@ class _QuerySet(object):
try: try:
return self.get(**kwargs), False return self.get(**kwargs), False
except self.model.DoesNotExist: except self.model.DoesNotExist:
try:
params = dict([(k, v) for k, v in kwargs.items() if '__' not in k]) params = dict([(k, v) for k, v in kwargs.items() if '__' not in k])
params.update(defaults) params.update(defaults)
obj = self.model(**params) obj = self.model(**params)
obj.save() obj.save()
return obj, True return obj, True
except IntegrityError, e:
return self.get(**kwargs), False
def latest(self, field_name=None): def latest(self, field_name=None):
""" """

View File

@ -82,6 +82,9 @@ class HttpRequest(object):
def is_secure(self): def is_secure(self):
return os.environ.get("HTTPS") == "on" return os.environ.get("HTTPS") == "on"
def is_ajax(self):
return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
def _set_encoding(self, val): def _set_encoding(self, val):
""" """
Sets the encoding used for GET/POST accesses. If the GET or POST Sets the encoding used for GET/POST accesses. If the GET or POST

View File

@ -3,6 +3,7 @@ Extra HTML Widget classes
""" """
import datetime import datetime
import re
from django.newforms.widgets import Widget, Select from django.newforms.widgets import Widget, Select
from django.utils.dates import MONTHS from django.utils.dates import MONTHS
@ -10,6 +11,8 @@ from django.utils.safestring import mark_safe
__all__ = ('SelectDateWidget',) __all__ = ('SelectDateWidget',)
RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$')
class SelectDateWidget(Widget): class SelectDateWidget(Widget):
""" """
A Widget that splits date input into three <select> boxes. A Widget that splits date input into three <select> boxes.
@ -32,28 +35,43 @@ class SelectDateWidget(Widget):
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
try: try:
value = datetime.date(*map(int, value.split('-')))
year_val, month_val, day_val = value.year, value.month, value.day year_val, month_val, day_val = value.year, value.month, value.day
except (AttributeError, TypeError, ValueError): except AttributeError:
year_val = month_val = day_val = None year_val = month_val = day_val = None
if isinstance(value, basestring):
match = RE_DATE.match(value)
if match:
year_val, month_val, day_val = [int(v) for v in match.groups()]
output = [] output = []
if 'id' in self.attrs:
id_ = self.attrs['id']
else:
id_ = 'id_%s' % name
month_choices = MONTHS.items() month_choices = MONTHS.items()
month_choices.sort() month_choices.sort()
select_html = Select(choices=month_choices).render(self.month_field % name, month_val) local_attrs = self.build_attrs(id=self.month_field % id_)
select_html = Select(choices=month_choices).render(self.month_field % name, month_val, local_attrs)
output.append(select_html) output.append(select_html)
day_choices = [(i, i) for i in range(1, 32)] day_choices = [(i, i) for i in range(1, 32)]
select_html = Select(choices=day_choices).render(self.day_field % name, day_val) local_attrs['id'] = self.day_field % id_
select_html = Select(choices=day_choices).render(self.day_field % name, day_val, local_attrs)
output.append(select_html) output.append(select_html)
year_choices = [(i, i) for i in self.years] year_choices = [(i, i) for i in self.years]
select_html = Select(choices=year_choices).render(self.year_field % name, year_val) local_attrs['id'] = self.year_field % id_
select_html = Select(choices=year_choices).render(self.year_field % name, year_val, local_attrs)
output.append(select_html) output.append(select_html)
return mark_safe(u'\n'.join(output)) return mark_safe(u'\n'.join(output))
def id_for_label(self, id_):
return '%s_month' % id_
id_for_label = classmethod(id_for_label)
def value_from_datadict(self, data, files, name): def value_from_datadict(self, data, files, name):
y, m, d = data.get(self.year_field % name), data.get(self.month_field % name), data.get(self.day_field % name) y, m, d = data.get(self.year_field % name), data.get(self.month_field % name), data.get(self.day_field % name)
if y and m and d: if y and m and d:

View File

@ -4,6 +4,7 @@ Field classes.
import copy import copy
import datetime import datetime
import os
import re import re
import time import time
# Python 2.3 fallbacks # Python 2.3 fallbacks
@ -31,7 +32,7 @@ __all__ = (
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField', 'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
'SplitDateTimeField', 'IPAddressField', 'SplitDateTimeField', 'IPAddressField', 'FilePathField',
) )
# These values, if given to to_python(), will trigger the self.required check. # These values, if given to to_python(), will trigger the self.required check.
@ -718,6 +719,33 @@ class MultiValueField(Field):
""" """
raise NotImplementedError('Subclasses must implement this method.') raise NotImplementedError('Subclasses must implement this method.')
class FilePathField(ChoiceField):
def __init__(self, path, match=None, recursive=False, required=True,
widget=Select, label=None, initial=None, help_text=None,
*args, **kwargs):
self.path, self.match, self.recursive = path, match, recursive
super(FilePathField, self).__init__(choices=(), required=required,
widget=widget, label=label, initial=initial, help_text=help_text,
*args, **kwargs)
self.choices = []
if self.match is not None:
self.match_re = re.compile(self.match)
if recursive:
for root, dirs, files in os.walk(self.path):
for f in files:
if self.match is None or self.match_re.search(f):
f = os.path.join(root, f)
self.choices.append((f, f.replace(path, "", 1)))
else:
try:
for f in os.listdir(self.path):
full_file = os.path.join(self.path, f)
if os.path.isfile(full_file) and (self.match is None or self.match_re.search(f)):
self.choices.append((full_file, f))
except OSError:
pass
self.widget.choices = self.choices
class SplitDateTimeField(MultiValueField): class SplitDateTimeField(MultiValueField):
default_error_messages = { default_error_messages = {
'invalid_date': _(u'Enter a valid date.'), 'invalid_date': _(u'Enter a valid date.'),

View File

@ -277,19 +277,18 @@ class ModelForm(BaseModelForm):
# Fields ##################################################################### # Fields #####################################################################
class QuerySetIterator(object): class ModelChoiceIterator(object):
def __init__(self, queryset, empty_label, cache_choices): def __init__(self, field):
self.queryset = queryset self.field = field
self.empty_label = empty_label self.queryset = field.queryset
self.cache_choices = cache_choices
def __iter__(self): def __iter__(self):
if self.empty_label is not None: if self.field.empty_label is not None:
yield (u"", self.empty_label) yield (u"", self.field.empty_label)
for obj in self.queryset: for obj in self.queryset:
yield (obj.pk, smart_unicode(obj)) yield (obj.pk, self.field.label_from_instance(obj))
# Clear the QuerySet cache if required. # Clear the QuerySet cache if required.
if not self.cache_choices: if not self.field.cache_choices:
self.queryset._result_cache = None self.queryset._result_cache = None
class ModelChoiceField(ChoiceField): class ModelChoiceField(ChoiceField):
@ -306,6 +305,7 @@ class ModelChoiceField(ChoiceField):
help_text=None, *args, **kwargs): help_text=None, *args, **kwargs):
self.empty_label = empty_label self.empty_label = empty_label
self.cache_choices = cache_choices self.cache_choices = cache_choices
# Call Field instead of ChoiceField __init__() because we don't need # Call Field instead of ChoiceField __init__() because we don't need
# ChoiceField.__init__(). # ChoiceField.__init__().
Field.__init__(self, required, widget, label, initial, help_text, Field.__init__(self, required, widget, label, initial, help_text,
@ -321,19 +321,30 @@ class ModelChoiceField(ChoiceField):
queryset = property(_get_queryset, _set_queryset) queryset = property(_get_queryset, _set_queryset)
# this method will be used to create object labels by the QuerySetIterator.
# Override it to customize the label.
def label_from_instance(self, obj):
"""
This method is used to convert objects into strings; it's used to
generate the labels for the choices presented by this object. Subclasses
can override this method to customize the display of the choices.
"""
return smart_unicode(obj)
def _get_choices(self): def _get_choices(self):
# If self._choices is set, then somebody must have manually set # If self._choices is set, then somebody must have manually set
# the property self.choices. In this case, just return self._choices. # the property self.choices. In this case, just return self._choices.
if hasattr(self, '_choices'): if hasattr(self, '_choices'):
return self._choices return self._choices
# Otherwise, execute the QuerySet in self.queryset to determine the # Otherwise, execute the QuerySet in self.queryset to determine the
# choices dynamically. Return a fresh QuerySetIterator that has not # choices dynamically. Return a fresh QuerySetIterator that has not been
# been consumed. Note that we're instantiating a new QuerySetIterator # consumed. Note that we're instantiating a new QuerySetIterator *each*
# *each* time _get_choices() is called (and, thus, each time # time _get_choices() is called (and, thus, each time self.choices is
# self.choices is accessed) so that we can ensure the QuerySet has not # accessed) so that we can ensure the QuerySet has not been consumed. This
# been consumed. # construct might look complicated but it allows for lazy evaluation of
return QuerySetIterator(self.queryset, self.empty_label, # the queryset.
self.cache_choices) return ModelChoiceIterator(self)
def _set_choices(self, value): def _set_choices(self, value):
# This method is copied from ChoiceField._set_choices(). It's necessary # This method is copied from ChoiceField._set_choices(). It's necessary

View File

@ -124,7 +124,10 @@ def floatformat(text, arg=-1):
d = int(arg) d = int(arg)
except ValueError: except ValueError:
return force_unicode(f) return force_unicode(f)
try:
m = f - int(f) m = f - int(f)
except OverflowError:
return force_unicode(f)
if not m and d < 0: if not m and d < 0:
return mark_safe(u'%d' % int(f)) return mark_safe(u'%d' % int(f))
else: else:

View File

@ -1,3 +1,4 @@
import urllib
import sys import sys
from cStringIO import StringIO from cStringIO import StringIO
from django.conf import settings from django.conf import settings
@ -208,7 +209,7 @@ class Client:
r = { r = {
'CONTENT_LENGTH': None, 'CONTENT_LENGTH': None,
'CONTENT_TYPE': 'text/html; charset=utf-8', 'CONTENT_TYPE': 'text/html; charset=utf-8',
'PATH_INFO': path, 'PATH_INFO': urllib.unquote(path),
'QUERY_STRING': urlencode(data, doseq=True), 'QUERY_STRING': urlencode(data, doseq=True),
'REQUEST_METHOD': 'GET', 'REQUEST_METHOD': 'GET',
} }
@ -227,7 +228,7 @@ class Client:
r = { r = {
'CONTENT_LENGTH': len(post_data), 'CONTENT_LENGTH': len(post_data),
'CONTENT_TYPE': content_type, 'CONTENT_TYPE': content_type,
'PATH_INFO': path, 'PATH_INFO': urllib.unquote(path),
'REQUEST_METHOD': 'POST', 'REQUEST_METHOD': 'POST',
'wsgi.input': StringIO(post_data), 'wsgi.input': StringIO(post_data),
} }

View File

@ -18,10 +18,10 @@ if os.name == 'posix':
# Second fork # Second fork
try: try:
if os.fork() > 0: if os.fork() > 0:
sys.exit(0) os._exit(0)
except OSError, e: except OSError, e:
sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror)) sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
sys.exit(1) os._exit(1)
si = open('/dev/null', 'r') si = open('/dev/null', 'r')
so = open(out_log, 'a+', 0) so = open(out_log, 'a+', 0)
@ -29,6 +29,8 @@ if os.name == 'posix':
os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno()) os.dup2(se.fileno(), sys.stderr.fileno())
# Set custom file descriptors so that they get proper buffering.
sys.stdout, sys.stderr = so, se
else: else:
def become_daemon(our_home_dir='.', out_log=None, err_log=None): def become_daemon(our_home_dir='.', out_log=None, err_log=None):
""" """

View File

@ -1,11 +1,12 @@
import os import os
import re import re
import sys import sys
import datetime
from django.conf import settings from django.conf import settings
from django.template import Template, Context, TemplateDoesNotExist from django.template import Template, Context, TemplateDoesNotExist
from django.utils.html import escape from django.utils.html import escape
from django.http import HttpResponseServerError, HttpResponseNotFound from django.http import HttpResponse, HttpResponseServerError, HttpResponseNotFound
from django.utils.encoding import smart_unicode from django.utils.encoding import smart_unicode
HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST') HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST')
@ -70,6 +71,11 @@ def technical_500_response(request, exc_type, exc_value, tb):
Create a technical server error response. The last three arguments are Create a technical server error response. The last three arguments are
the values returned from sys.exc_info() and friends. the values returned from sys.exc_info() and friends.
""" """
html = get_traceback_html(request, exc_type, exc_value, tb)
return HttpResponseServerError(html, mimetype='text/html')
def get_traceback_html(request, exc_type, exc_value, tb):
"Return HTML code for traceback."
template_info = None template_info = None
template_does_not_exist = False template_does_not_exist = False
loader_debug_info = None loader_debug_info = None
@ -153,13 +159,14 @@ def technical_500_response(request, exc_type, exc_value, tb):
'settings': get_safe_settings(), 'settings': get_safe_settings(),
'sys_executable': sys.executable, 'sys_executable': sys.executable,
'sys_version_info': '%d.%d.%d' % sys.version_info[0:3], 'sys_version_info': '%d.%d.%d' % sys.version_info[0:3],
'server_time': datetime.datetime.now(),
'django_version_info': get_version(), 'django_version_info': get_version(),
'sys_path' : sys.path, 'sys_path' : sys.path,
'template_info': template_info, 'template_info': template_info,
'template_does_not_exist': template_does_not_exist, 'template_does_not_exist': template_does_not_exist,
'loader_debug_info': loader_debug_info, 'loader_debug_info': loader_debug_info,
}) })
return HttpResponseServerError(t.render(c), mimetype='text/html') return t.render(c)
def technical_404_response(request, exception): def technical_404_response(request, exception):
"Create a technical 404 error response. The exception should be the Http404." "Create a technical 404 error response. The exception should be the Http404."
@ -190,7 +197,7 @@ def empty_urlconf(request):
c = Context({ c = Context({
'project_name': settings.SETTINGS_MODULE.split('.')[0] 'project_name': settings.SETTINGS_MODULE.split('.')[0]
}) })
return HttpResponseNotFound(t.render(c), mimetype='text/html') return HttpResponse(t.render(c), mimetype='text/html')
def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None): def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None):
""" """
@ -384,6 +391,10 @@ TECHNICAL_500_TEMPLATE = """
<th>Python Path:</th> <th>Python Path:</th>
<td>{{ sys_path }}</td> <td>{{ sys_path }}</td>
</tr> </tr>
<tr>
<th>Server time:</th>
<td>{{server_time|date:"r"}}</td>
</tr>
</table> </table>
</div> </div>
{% if unicode_hint %} {% if unicode_hint %}

View File

@ -1,7 +1,7 @@
from django.template import loader, RequestContext from django.template import loader, RequestContext
from django.http import Http404, HttpResponse from django.http import Http404, HttpResponse
from django.core.xheaders import populate_xheaders from django.core.xheaders import populate_xheaders
from django.core.paginator import ObjectPaginator, InvalidPage from django.core.paginator import QuerySetPaginator, InvalidPage
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
def object_list(request, queryset, paginate_by=None, page=None, def object_list(request, queryset, paginate_by=None, page=None,
@ -45,43 +45,47 @@ def object_list(request, queryset, paginate_by=None, page=None,
if extra_context is None: extra_context = {} if extra_context is None: extra_context = {}
queryset = queryset._clone() queryset = queryset._clone()
if paginate_by: if paginate_by:
paginator = ObjectPaginator(queryset, paginate_by) paginator = QuerySetPaginator(queryset, paginate_by, allow_empty_first_page=allow_empty)
if not page: if not page:
page = request.GET.get('page', 1) page = request.GET.get('page', 1)
try: try:
page_number = int(page) page_number = int(page)
except ValueError: except ValueError:
if page == 'last': if page == 'last':
page_number = paginator.pages page_number = paginator.num_pages
else: else:
# Page is not 'last', nor can it be converted to an int # Page is not 'last', nor can it be converted to an int.
raise Http404 raise Http404
try: try:
object_list = paginator.get_page(page_number - 1) page_obj = paginator.page(page_number)
except InvalidPage: except InvalidPage:
if page_number == 1 and allow_empty:
object_list = []
else:
raise Http404 raise Http404
c = RequestContext(request, { c = RequestContext(request, {
'%s_list' % template_object_name: object_list, '%s_list' % template_object_name: page_obj.object_list,
'is_paginated': paginator.pages > 1, 'paginator': paginator,
'results_per_page': paginate_by, 'page_obj': page_obj,
'has_next': paginator.has_next_page(page_number - 1),
'has_previous': paginator.has_previous_page(page_number - 1), # Legacy template context stuff. New templates should use page_obj
'page': page_number, # to access this instead.
'next': page_number + 1, 'is_paginated': page_obj.has_other_pages(),
'previous': page_number - 1, 'results_per_page': paginator.per_page,
'last_on_page': paginator.last_on_page(page_number - 1), 'has_next': page_obj.has_next(),
'first_on_page': paginator.first_on_page(page_number - 1), 'has_previous': page_obj.has_previous(),
'pages': paginator.pages, 'page': page_obj.number,
'hits' : paginator.hits, 'next': page_obj.next_page_number(),
'page_range' : paginator.page_range 'previous': page_obj.previous_page_number(),
'first_on_page': page_obj.start_index(),
'last_on_page': page_obj.end_index(),
'pages': paginator.num_pages,
'hits': paginator.count,
'page_range': paginator.page_range,
}, context_processors) }, context_processors)
else: else:
c = RequestContext(request, { c = RequestContext(request, {
'%s_list' % template_object_name: queryset, '%s_list' % template_object_name: queryset,
'is_paginated': False 'paginator': None,
'page_obj': None,
'is_paginated': False,
}, context_processors) }, context_processors)
if not allow_empty and len(queryset) == 0: if not allow_empty and len(queryset) == 0:
raise Http404 raise Http404

View File

@ -401,8 +401,8 @@ For example::
# ... # ...
def get_db_prep_save(self, value): def get_db_prep_save(self, value):
return ''.join([''.join(l) for l in (self.north, return ''.join([''.join(l) for l in (value.north,
self.east, self.south, self.west)]) value.east, value.south, value.west)])
``pre_save(self, model_instance, add)`` ``pre_save(self, model_instance, add)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1372,7 +1372,7 @@ equivalent::
Entry.objects.filter(blog__pk=3) # __pk implies __id__exact Entry.objects.filter(blog__pk=3) # __pk implies __id__exact
Lookups that span relationships Lookups that span relationships
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -------------------------------
Django offers a powerful and intuitive way to "follow" relationships in Django offers a powerful and intuitive way to "follow" relationships in
lookups, taking care of the SQL ``JOIN``\s for you automatically, behind the lookups, taking care of the SQL ``JOIN``\s for you automatically, behind the
@ -1396,7 +1396,7 @@ whose ``headline`` contains ``'Lennon'``::
Blog.objects.filter(entry__headline__contains='Lennon') Blog.objects.filter(entry__headline__contains='Lennon')
Escaping percent signs and underscores in LIKE statements Escaping percent signs and underscores in LIKE statements
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ---------------------------------------------------------
The field lookups that equate to ``LIKE`` SQL statements (``iexact``, The field lookups that equate to ``LIKE`` SQL statements (``iexact``,
``contains``, ``icontains``, ``startswith``, ``istartswith``, ``endswith`` ``contains``, ``icontains``, ``startswith``, ``istartswith``, ``endswith``

View File

@ -751,6 +751,19 @@ In addition to ``extra_context``, the template's context will be:
If the results are paginated, the context will contain these extra variables: If the results are paginated, the context will contain these extra variables:
* **New in Django development version:** ``paginator``: An instance of
``django.core.paginator.Paginator``.
* **New in Django development version:** ``page_obj``: An instance of
``django.core.paginator.Page``.
In older versions of Django, before ``paginator`` and ``page_obj`` were added
to this template's context, the template included several other variables
related to pagination. Note that you should *NOT* use these variables anymore;
use ``paginator`` and ``page_obj`` instead, because they let you do everything
these old variables let you do (and more!). But for legacy installations,
here's a list of those old template variables:
* ``results_per_page``: The number of objects per page. (Same as the * ``results_per_page``: The number of objects per page. (Same as the
``paginate_by`` parameter.) ``paginate_by`` parameter.)
@ -777,8 +790,8 @@ If the results are paginated, the context will contain these extra variables:
* ``hits``: The total number of objects across *all* pages, not just this * ``hits``: The total number of objects across *all* pages, not just this
page. page.
* **New in Django development version:** ``page_range``: A list of the * ``page_range``: A list of the page numbers that are available. This is
page numbers that are available. This is 1-based. 1-based.
Notes on pagination Notes on pagination
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~

View File

@ -167,6 +167,20 @@ These commands will install Django in your Python installation's
Installing the development version Installing the development version
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. admonition:: Tracking Django development
If you decide to use the latest development version of Django,
you'll want to pay close attention to `the development timeline`_,
and you'll want to keep an eye on `the list of
backwards-incompatible changes`_; this will help you stay on top
of any new features you might want to use, as well as any changes
you'll need to make to your code when updating your copy of Django
(for stable releases, any necessary changes are documented in the
release notes).
.. _the development timeline: http://code.djangoproject.com/timeline
.. _the list of backwards-incompatible changes: http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges
If you'd like to be able to update your Django code occasionally with the If you'd like to be able to update your Django code occasionally with the
latest bug fixes and improvements, follow these instructions: latest bug fixes and improvements, follow these instructions:

View File

@ -626,7 +626,8 @@ option is ignored.
``default`` ``default``
~~~~~~~~~~~ ~~~~~~~~~~~
The default value for the field. The default value for the field. This can be a value or a callable object. If
callable it will be called every time a new object is created.
``editable`` ``editable``
~~~~~~~~~~~~ ~~~~~~~~~~~~
@ -1788,14 +1789,15 @@ For example::
This example allows you to request ``Person.men.all()``, ``Person.women.all()``, This example allows you to request ``Person.men.all()``, ``Person.women.all()``,
and ``Person.people.all()``, yielding predictable results. and ``Person.people.all()``, yielding predictable results.
If you use custom ``Manager`` objects, take note that the first ``Manager`` If you use custom ``Manager`` objects, take note that the first
Django encounters (in order by which they're defined in the model) has a ``Manager`` Django encounters (in the order in which they're defined
special status. Django interprets the first ``Manager`` defined in a class as in the model) has a special status. Django interprets this first
the "default" ``Manager``. Certain operations -- such as Django's admin site -- ``Manager`` defined in a class as the "default" ``Manager``, and
use the default ``Manager`` to obtain lists of objects, so it's generally a several parts of Django (though not the admin application) will use
good idea for the first ``Manager`` to be relatively unfiltered. In the last that ``Manager`` exclusively for that model. As a result, it's often a
example, the ``people`` ``Manager`` is defined first -- so it's the default good idea to be careful in your choice of default manager, in order to
``Manager``. avoid a situation where overriding of ``get_query_set()`` results in
an inability to retrieve objects you'd like to work with.
Model methods Model methods
============= =============

View File

@ -231,6 +231,16 @@ For example::
# Create and save the new author instance. There's no need to do anything else. # Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save() >>> new_author = f.save()
Other than the ``save()`` and ``save_m2m()`` methods, a ``ModelForm``
works exactly the same way as any other ``newforms`` form. For
example, the ``is_valid()`` method is used to check for validity, the
``is_multipart()`` method is used to determine whether a form requires
multipart file upload (and hence whether ``request.FILES`` must be
passed to the form), etc.; see `the standard newforms documentation`_
for more information.
.. _the standard newforms documentation: ../newforms/
Using a subset of fields on the form Using a subset of fields on the form
------------------------------------ ------------------------------------

View File

@ -1333,13 +1333,14 @@ given length.
An ``UploadedFile`` object has two attributes: An ``UploadedFile`` object has two attributes:
====================== ===================================================== ====================== ====================================================
Argument Description Attribute Description
====================== ===================================================== ====================== ====================================================
``filename`` The name of the file, provided by the uploading ``filename`` The name of the file, provided by the uploading
client. client.
``content`` The array of bytes comprising the file content. ``content`` The array of bytes comprising the file content.
====================== ===================================================== ====================== ====================================================
The string representation of an ``UploadedFile`` is the same as the filename The string representation of an ``UploadedFile`` is the same as the filename
attribute. attribute.
@ -1349,6 +1350,38 @@ When you use a ``FileField`` on a form, you must also remember to
.. _`bind the file data to the form`: `Binding uploaded files to a form`_ .. _`bind the file data to the form`: `Binding uploaded files to a form`_
``FilePathField``
~~~~~~~~~~~~~~~~~
**New in Django development version**
* Default widget: ``Select``
* Empty value: ``None``
* Normalizes to: A unicode object
* Validates that the selected choice exists in the list of choices.
* Error message keys: ``required``, ``invalid_choice``
The field allows choosing from files inside a certain directory. It takes three
extra arguments:
============== ========== ===============================================
Argument Required? Description
============== ========== ===============================================
``path`` Yes The absolute path to the directory whose
contents you want listed. This directory must
exist.
``recursive`` No If ``False`` (the default) only the direct
contents of ``path`` will be offered as choices.
If ``True``, the directory will be descended
into recursively and all descendants will be
listed as choices.
``match`` No A regular expression pattern; only files with
names matching this expression will be allowed
as choices.
============== ========== ===============================================
``ImageField`` ``ImageField``
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
@ -1499,6 +1532,41 @@ the bottom of this document, for examples of their use.
``SplitDateTimeField`` ``SplitDateTimeField``
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
Fields which handle relationships
---------------------------------
For representing relationships between models, two fields are
provided which can derive their choices from a ``QuerySet``, and which
place one or more model objects into the ``cleaned_data`` dictionary
of forms in which they're used. Both of these fields have an
additional required argument:
``queryset``
A ``QuerySet`` of model objects from which the choices for the
field will be derived, and which will be used to validate the
user's selection.
``ModelChoiceField``
~~~~~~~~~~~~~~~~~~~~
Allows the selection of a single model object, suitable for representing a
foreign key. The method receives an object as an argument and must return a
string to represent it.
The labels for the choice field call the ``__unicode__`` method of the model to
generate string representations. To provide custom labels, subclass ``ModelChoiceField`` and override ``label_for_model``::
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return "My Object #%i" % obj.id
``ModelMultipleChoiceField``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows the selection of one or more model objects, suitable for representing a
many-to-many relation. As with ``ModelChoiceField``, you can use
``label_from_instance`` to customize the object labels.
Creating custom fields Creating custom fields
---------------------- ----------------------
@ -1569,9 +1637,9 @@ The three types of cleaning methods are:
These methods are run in the order given above, one field at a time. That is, These methods are run in the order given above, one field at a time. That is,
for each field in the form (in the order they are declared in the form for each field in the form (in the order they are declared in the form
definition), the ``Field.clean()`` method (or it's override) is run, then definition), the ``Field.clean()`` method (or its override) is run, then
``clean_<fieldname>()``. Finally, once those two methods are run for every ``clean_<fieldname>()``. Finally, once those two methods are run for every
field, the ``Form.clean()`` method, or it's override, is executed. field, the ``Form.clean()`` method, or its override, is executed.
As mentioned above, any of these methods can raise a ``ValidationError``. For As mentioned above, any of these methods can raise a ``ValidationError``. For
any field, if the ``Field.clean()`` method raises a ``ValidationError``, any any field, if the ``Field.clean()`` method raises a ``ValidationError``, any
@ -1693,7 +1761,7 @@ For example, take the following simple form::
comment = forms.CharField() comment = forms.CharField()
This form will include three default TextInput widgets, with default rendering - This form will include three default TextInput widgets, with default rendering -
no CSS class, no extra attributes. This means that the inputs boxes provided for no CSS class, no extra attributes. This means that the input boxes provided for
each widget will be rendered exactly the same:: each widget will be rendered exactly the same::
>>> f = CommentForm(auto_id=False) >>> f = CommentForm(auto_id=False)

133
docs/pagination.txt Normal file
View File

@ -0,0 +1,133 @@
==========
Pagination
==========
**New in Django development version**
Django provides a few classes that help you manage paginated data -- that is,
data that's split across several pages, with "Previous/Next" links. These
classes live in the module ``django/core/paginator.py``.
Example
=======
Give ``Paginator`` a list of objects, plus the number of items you'd like to
have on each page, and it gives you methods for accessing the items for each
page::
>>> from django.core.paginator import Paginator
>>> objects = ['john', 'paul', 'george', 'ringo']
>>> p = Paginator(objects, 2)
>>> p.count
4
>>> p.num_pages
2
>>> p.page_range
[1, 2]
>>> page1 = p.page(1)
>>> page1
<Page 1 of 2>
>>> page1.object_list
['john', 'paul']
>>> page2 = p.page(2)
>>> page2.object_list
['george', 'ringo']
>>> page2.has_next()
False
>>> page2.has_previous()
True
>>> page2.has_other_pages()
True
>>> page2.next_page_number()
3
>>> page2.previous_page_number()
1
>>> page2.start_index() # The 1-based index of the first item on this page
3
>>> page2.end_index() # The 1-based index of the last item on this page
4
>>> p.page(0)
Traceback (most recent call last):
...
InvalidPage
>>> p.page(3)
Traceback (most recent call last):
...
InvalidPage
``Paginator`` objects
=====================
Methods
-------
``page(number)`` -- Returns a ``Page`` object with the given 1-based index.
Raises ``InvalidPage`` if the given page number doesn't exist.
Attributes
----------
``count`` -- The total number of objects, across all pages.
``num_pages`` -- The total number of pages.
``page_range`` -- A 1-based range of page numbers, e.g., ``[1, 2, 3, 4]``.
``Page`` objects
================
Methods
-------
``has_next()`` -- Returns ``True`` if there's a next page.
``has_previous()`` -- Returns ``True`` if there's a previous page.
``has_other_pages()`` -- Returns ``True`` if there's a next *or* previous page.
``next_page_number()`` -- Returns the next page number. Note that this is
"dumb" and will return the next page number regardless of whether a subsequent
page exists.
``previous_page_number()`` -- Returns the previous page number. Note that this
is "dumb" and will return the previous page number regardless of whether a
previous page exists.
``start_index()`` -- Returns the 1-based index of the first object on the page,
relative to all of the objects in the paginator's list. For example, when
paginating a list of 5 objects with 2 objects per page, the second page's
``start_index()`` would return ``3``.
``end_index()`` -- Returns the 1-based index of the last object on the page,
relative to all of the objects in the paginator's list. For example, when
paginating a list of 5 objects with 2 objects per page, the second page's
``end_index()`` would return ``4``.
Attributes
----------
``object_list`` -- The list of objects on this page.
``number`` -- The 1-based page number for this page.
``paginator`` -- The associated ``Paginator`` object.
``QuerySetPaginator`` objects
=============================
Use ``QuerySetPaginator`` instead of ``Paginator`` if you're paginating across
a ``QuerySet`` from Django's database API. This is slightly more efficient, and
there are no API differences between the two classes.
The legacy ``ObjectPaginator`` class
====================================
The ``Paginator`` and ``Page`` classes are new in the Django development
version, as of revision 7306. In previous versions, Django provided an
``ObjectPaginator`` class that offered similar functionality but wasn't as
convenient. This class still exists, for backwards compatibility, but Django
now issues a ``DeprecationWarning`` if you try to use it.

View File

@ -141,6 +141,16 @@ All attributes except ``session`` should be considered read-only.
The raw HTTP POST data. This is only useful for advanced processing. Use The raw HTTP POST data. This is only useful for advanced processing. Use
``POST`` instead. ``POST`` instead.
``urlconf``
Not defined by Django itself, but will be read if other code
(e.g., a custom middleware class) sets it; when present, this will
be used as the root URLConf for the current request, overriding
the ``ROOT_URLCONF`` setting. See `How Django processes a
request`_ for details.
.. _How Django processes a request: ../url_dispatch/#how-django-processes-a-request
Methods Methods
------- -------
@ -189,6 +199,23 @@ Methods
Returns ``True`` if the request is secure; that is, if it was made with Returns ``True`` if the request is secure; that is, if it was made with
HTTPS. HTTPS.
``is_ajax()``
**New in Django development version**
Returns ``True`` if the request was made via an XMLHttpRequest by checking
the ``HTTP_X_REQUESTED_WITH`` header for the string *'XMLHttpRequest'*. The
following major Javascript libraries all send this header:
* jQuery
* Dojo
* MochiKit
* MooTools
* Prototype
* YUI
If you write your own XMLHttpRequest call (on the browser side), you will
have to set this header manually to use this method.
QueryDict objects QueryDict objects
----------------- -----------------

View File

@ -48,10 +48,10 @@ Using file-based sessions
To use file-based sessions, set the ``SESSION_ENGINE`` setting to To use file-based sessions, set the ``SESSION_ENGINE`` setting to
``"django.contrib.sessions.backends.file"``. ``"django.contrib.sessions.backends.file"``.
You might also want to set the ``SESSION_FILE_PATH`` setting (which You might also want to set the ``SESSION_FILE_PATH`` setting (which defaults
defaults to ``/tmp``) to control where Django stores session files. Be to output from ``tempfile.gettempdir()``, most likely ``/tmp``) to control
sure to check that your Web server has permissions to read and write to where Django stores session files. Be sure to check that your Web server has
this location. permissions to read and write to this location.
Using cache-based sessions Using cache-based sessions
-------------------------- --------------------------

View File

@ -185,8 +185,11 @@ ADMIN_MEDIA_PREFIX
Default: ``'/media/'`` Default: ``'/media/'``
The URL prefix for admin media -- CSS, JavaScript and images. Make sure to use The URL prefix for admin media -- CSS, JavaScript and images used by
a trailing slash. the Django administrative interface. Make sure to use a trailing
slash, and to have this be different from the ``MEDIA_URL`` setting
(since the same URL cannot be mapped onto two different sets of
files).
ADMINS ADMINS
------ ------
@ -754,7 +757,9 @@ ROOT_URLCONF
Default: Not defined Default: Not defined
A string representing the full Python import path to your root URLconf. For example: A string representing the full Python import path to your root URLconf. For example:
``"mydjangoapps.urls"``. See `How Django processes a request`_. ``"mydjangoapps.urls"``. Can be overridden on a per-request basis by
setting the attribute ``urlconf`` on the incoming ``HttpRequest``
object. See `How Django processes a request`_ for details.
.. _How Django processes a request: ../url_dispatch/#how-django-processes-a-request .. _How Django processes a request: ../url_dispatch/#how-django-processes-a-request

View File

@ -245,6 +245,13 @@ request to the URL ``/rss/beats/0613/``:
subclass of ``ObjectDoesNotExist``. Raising ``ObjectDoesNotExist`` in subclass of ``ObjectDoesNotExist``. Raising ``ObjectDoesNotExist`` in
``get_object()`` tells Django to produce a 404 error for that request. ``get_object()`` tells Django to produce a 404 error for that request.
**New in Django development version:** The ``get_object()`` method also
has a chance to handle the ``/rss/beats/`` url. In this case, ``bits``
will be an empty list. In our example, ``len(bits) != 1`` and an
``ObjectDoesNotExist`` exception will be raised, so ``/rss/beats/`` will
generate a 404 page. But you can handle this case however you like. For
example you could generate a combined feed for all beats.
* To generate the feed's ``<title>``, ``<link>`` and ``<description>``, * To generate the feed's ``<title>``, ``<link>`` and ``<description>``,
Django uses the ``title()``, ``link()`` and ``description()`` methods. In Django uses the ``title()``, ``link()`` and ``description()`` methods. In
the previous example, they were simple string class attributes, but this the previous example, they were simple string class attributes, but this

View File

@ -429,8 +429,9 @@ all block tags. For example::
# base.html # base.html
{% autoescape off %} {% autoescape off %}
<h1>{% block title %}</h1> <h1>{% block title %}{% endblock %}</h1>
{% block content %} {% block content %}
{% endblock %}
{% endautoescape %} {% endautoescape %}
@ -438,10 +439,11 @@ all block tags. For example::
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}This & that{% endblock %} {% block title %}This & that{% endblock %}
{% block content %}<b>Hello!</b>{% endblock %} {% block content %}{{ greeting }}{% endblock %}
Because auto-escaping is turned off in the base template, it will also be Because auto-escaping is turned off in the base template, it will also be
turned off in the child template, resulting in the following rendered HTML:: turned off in the child template, resulting in the following rendered
HTML when the ``greeting`` variable contains the string ``<b>Hello!</b>``::
<h1>This & that</h1> <h1>This & that</h1>
<b>Hello!</b> <b>Hello!</b>
@ -1222,20 +1224,20 @@ Built-in filter reference
add add
~~~ ~~~
Adds the arg to the value. Adds the argument to the value.
For example:: For example::
{{ value|add:2 }} {{ value|add:"2" }}
If ``value`` is 4, then the output will be 6. If ``value`` is ``4``, then the output will be ``6``.
addslashes addslashes
~~~~~~~~~~ ~~~~~~~~~~
Adds slashes before quotes. Useful for escaping strings in CSV, for example. Adds slashes before quotes. Useful for escaping strings in CSV, for example.
**New in Django development version**: for escaping data in JavaScript strings, **New in Django development version**: For escaping data in JavaScript strings,
use the `escapejs`_ filter instead. use the `escapejs`_ filter instead.
capfirst capfirst
@ -1257,7 +1259,7 @@ For example::
{{ value|cut:" "}} {{ value|cut:" "}}
If ``value`` is "String with spaces", the output will be ``Stringwithspaces``. If ``value`` is ``"String with spaces"``, the output will be ``"Stringwithspaces"``.
date date
~~~~ ~~~~
@ -1268,35 +1270,40 @@ For example::
{{ value|date:"D d M Y" }} {{ value|date:"D d M Y" }}
If ``value`` is a datetime object (ie. datetime.datetime.now()), the output If ``value`` is a ``datetime`` object (e.g., the result of
would be formatted like ``Wed 09 Jan 2008``. ``datetime.datetime.now()``), the output will be the string
``'Wed 09 Jan 2008'``.
default default
~~~~~~~ ~~~~~~~
If value is unavailable, use given default. If value evaluates to ``False``, use given default. Otherwise, use the value.
For example:: For example::
{{ value|default:"nothing" }} {{ value|default:"nothing" }}
If ``value`` is ``Undefined``, the output would be ``nothing``. If ``value`` is ``""`` (the empty string), the output will be ``nothing``.
default_if_none default_if_none
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
If value is ``None``, use given default. If (and only if) value is ``None``, use given default. Otherwise, use the
value.
Note that if an empty string is given, the default value will *not* be used.
Use the ``default`` filter if you want to fallback for empty strings.
For example:: For example::
{{ value|default_if_none:"nothing" }} {{ value|default_if_none:"nothing" }}
If ``value`` is ``None``, the output would be ``nothing``. If ``value`` is ``None``, the output will be the string ``"nothing"``.
dictsort dictsort
~~~~~~~~ ~~~~~~~~
Takes a list of dictionaries, returns that list sorted by the key given in Takes a list of dictionaries and returns that list sorted by the key given in
the argument. the argument.
For example:: For example::
@ -1306,7 +1313,7 @@ For example::
If ``value`` is:: If ``value`` is::
[ [
{'name': 'zed', 'age': 19} {'name': 'zed', 'age': 19},
{'name': 'amy', 'age': 22}, {'name': 'amy', 'age': 22},
{'name': 'joe', 'age': 31}, {'name': 'joe', 'age': 31},
] ]
@ -1316,34 +1323,30 @@ then the output would be::
[ [
{'name': 'amy', 'age': 22}, {'name': 'amy', 'age': 22},
{'name': 'joe', 'age': 31}, {'name': 'joe', 'age': 31},
{'name': 'zed', 'age': 19} {'name': 'zed', 'age': 19},
] ]
dictsortreversed dictsortreversed
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
Takes a list of dictionaries, returns that list sorted in reverse order by the Takes a list of dictionaries and returns that list sorted in reverse order by
key given in the argument. This works exactly the same as the above filter, but the key given in the argument. This works exactly the same as the above filter,
the returned value will be in reverse order. but the returned value will be in reverse order.
divisibleby divisibleby
~~~~~~~~~~~ ~~~~~~~~~~~
Returns true if the value is divisible by the argument. Returns ``True`` if the value is divisible by the argument.
For example:: For example::
{{ value|divisibleby:3 }} {{ value|divisibleby:"3" }}
If ``value`` is ``21``, the output would be ``True``. If ``value`` is ``21``, the output would be ``True``.
escape escape
~~~~~~ ~~~~~~
**New in Django development version:** The behaviour of this filter has
changed slightly in the development version (the affects are only applied
once, after all other filters).
Escapes a string's HTML. Specifically, it makes these replacements: Escapes a string's HTML. Specifically, it makes these replacements:
* ``<`` is converted to ``&lt;`` * ``<`` is converted to ``&lt;``
@ -1362,6 +1365,10 @@ applied to the result will only result in one round of escaping being done. So
it is safe to use this function even in auto-escaping environments. If you want it is safe to use this function even in auto-escaping environments. If you want
multiple escaping passes to be applied, use the ``force_escape`` filter. multiple escaping passes to be applied, use the ``force_escape`` filter.
**New in Django development version:** Due to auto-escaping, the behavior of
this filter has changed slightly. The replacements are only made once, after
all other filters are applied -- including filters before and after it.
escapejs escapejs
~~~~~~~~ ~~~~~~~~
@ -1392,7 +1399,7 @@ For example::
{{ value|first }} {{ value|first }}
If ``value`` is ``['a', 'b', 'c']``, the output would be `a`. If ``value`` is the list ``['a', 'b', 'c']``, the output will be ``'a'``.
fix_ampersands fix_ampersands
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
@ -1403,11 +1410,11 @@ For example::
{{ value|fix_ampersands }} {{ value|fix_ampersands }}
If ``value`` is ``Tom & Jerry``, the output would be ``Tom &amp; Jerry``. If ``value`` is ``Tom & Jerry``, the output will be ``Tom &amp; Jerry``.
**New in Django development version**: you probably don't need to use this **New in Django development version**: This filter generally is no longer
filter since ampersands will be automatically escaped. See escape_ for more on useful, because ampersands are automatically escaped in templates. See escape_
how auto-escaping works. for more on how auto-escaping works.
floatformat floatformat
~~~~~~~~~~~ ~~~~~~~~~~~
@ -1463,16 +1470,16 @@ filter.
get_digit get_digit
~~~~~~~~~ ~~~~~~~~~
Given a whole number, returns the requested digit of it, where 1 is the Given a whole number, returns the requested digit, where 1 is the right-most
right-most digit, 2 is the second-right-most digit, etc. Returns the original digit, 2 is the second-right-most digit, etc. Returns the original value for
value for invalid input (if input or argument is not an integer, or if argument invalid input (if input or argument is not an integer, or if argument is less
is less than 1). Otherwise, output is always an integer. than 1). Otherwise, output is always an integer.
For example:: For example::
{{ value|get_digit:2 }} {{ value|get_digit:"2" }}
If ``value`` is 123456789, the output would be ``8``. If ``value`` is ``123456789``, the output will be ``8``.
iriencode iriencode
~~~~~~~~~ ~~~~~~~~~
@ -1493,7 +1500,8 @@ For example::
{{ value|join:" // " }} {{ value|join:" // " }}
If ``value`` is ``['a', 'b', 'c']``, the output would be ``a // b // c``. If ``value`` is the list ``['a', 'b', 'c']``, the output will be the string
``"a // b // c"``.
last last
~~~~ ~~~~
@ -1506,29 +1514,30 @@ For example::
{{ value|last }} {{ value|last }}
If ``value`` is ``['a', 'b', 'c', 'd']``, the output would be ``d``. If ``value`` is the list ``['a', 'b', 'c', 'd']``, the output will be the string
``"d"``.
length length
~~~~~~ ~~~~~~
Returns the length of the value. Useful for lists. Returns the length of the value. This works for both strings and lists.
For example:: For example::
{{ value|length }} {{ value|length }}
If ``value`` is ``['a', 'b', 'c', 'd']``, the output would be ``4``. If ``value`` is ``['a', 'b', 'c', 'd']``, the output will be ``4``.
length_is length_is
~~~~~~~~~ ~~~~~~~~~
Returns a boolean of whether the value's length is the argument. Returns ``True`` if the value's length is the argument, or ``False`` otherwise.
For example:: For example::
{{ value|length_is:4 }} {{ value|length_is:"4" }}
If ``value`` is ``['a', 'b', 'c', 'd']``, the output would be ``True``. If ``value`` is ``['a', 'b', 'c', 'd']``, the output will be ``True``.
linebreaks linebreaks
~~~~~~~~~~ ~~~~~~~~~~
@ -1541,7 +1550,7 @@ For example::
{{ value|linebreaks }} {{ value|linebreaks }}
If ``value`` is ``Joel\nis a slug``, the output would be ``<p>Joe<br>is a If ``value`` is ``Joel\nis a slug``, the output will be ``<p>Joe<br>is a
slug</p>``. slug</p>``.
linebreaksbr linebreaksbr
@ -1571,7 +1580,7 @@ For example::
{{ value|lower }} {{ value|lower }}
If ``value`` is ``Joel Is a Slug``, the output would be ``joel is a slug``. If ``value`` is ``Still MAD At Yoko``, the output will be ``still mad at yoko``.
make_list make_list
~~~~~~~~~ ~~~~~~~~~
@ -1583,8 +1592,9 @@ For example::
{{ value|make_list }} {{ value|make_list }}
If ``value`` is "Joe", the output would be ``[u'J', u'o', u'e']. If ``value`` is If ``value`` is the string ``"Joe"``, the output would be the list
123, the output would be ``[1, 2, 3]``. ``[u'J', u'o', u'e']``. If ``value`` is ``123``, the output will be the list
``[1, 2, 3]``.
phone2numeric phone2numeric
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@ -1629,25 +1639,25 @@ __ http://www.python.org/doc/2.5/lib/module-pprint.html
random random
~~~~~~ ~~~~~~
Returns a random item from the list. Returns a random item from the given list.
For example:: For example::
{{ value|random }} {{ value|random }}
If ``value`` is ``['a', 'b', 'c', 'd']``, the output could be ``b``. If ``value`` is the list ``['a', 'b', 'c', 'd']``, the output could be ``"b"``.
removetags removetags
~~~~~~~~~~ ~~~~~~~~~~
Removes a space separated list of [X]HTML tags from the output. Removes a space-separated list of [X]HTML tags from the output.
For example:: For example::
{{ value|removetags:"b span"|safe }} {{ value|removetags:"b span"|safe }}
If ``value`` is ``<b>Joel</b> <button>is</button> a <span>slug</span>`` the If ``value`` is ``"<b>Joel</b> <button>is</button> a <span>slug</span>"`` the
output would be ``Joel <button>is</button> a slug``. output will be ``"Joel <button>is</button> a slug"``.
rjust rjust
~~~~~ ~~~~~
@ -1684,7 +1694,7 @@ For example::
{{ value|slugify }} {{ value|slugify }}
If ``value`` is ``Joel is a slug``, the output would be ``joel-is-a-slug``. If ``value`` is ``"Joel is a slug"``, the output will be ``"joel-is-a-slug"``.
stringformat stringformat
~~~~~~~~~~~~ ~~~~~~~~~~~~
@ -1700,7 +1710,7 @@ For example::
{{ value|stringformat:"s" }} {{ value|stringformat:"s" }}
If ``value`` is ``Joel is a slug``, the output would be ``Joel is a slug``. If ``value`` is ``"Joel is a slug"``, the output will be ``"Joel is a slug"``.
striptags striptags
~~~~~~~~~ ~~~~~~~~~
@ -1711,8 +1721,8 @@ For example::
{{ value|striptags }} {{ value|striptags }}
If ``value`` is ``<b>Joel</b> <button>is</button> a <span>slug</span>`` the If ``value`` is ``"<b>Joel</b> <button>is</button> a <span>slug</span>"``, the
output would be ``Joel is a slug``. output will be ``"Joel is a slug"``.
time time
~~~~ ~~~~
@ -1726,13 +1736,13 @@ For example::
{{ value|time:"H:i" }} {{ value|time:"H:i" }}
If ``value`` is ``datetime.datetime.now()``, the output would be formatted If ``value`` is equivalent to ``datetime.datetime.now()``, the output will be
like ``01:23``. the string ``"01:23"``.
timesince timesince
~~~~~~~~~ ~~~~~~~~~
Formats a date as the time since that date (i.e. "4 days, 6 hours"). Formats a date as the time since that date (e.g., "4 days, 6 hours").
Takes an optional argument that is a variable containing the date to use as Takes an optional argument that is a variable containing the date to use as
the comparison point (without the argument, the comparison point is *now*). the comparison point (without the argument, the comparison point is *now*).
@ -1774,7 +1784,7 @@ For example::
{{ value|truncatewords:2 }} {{ value|truncatewords:2 }}
If ``value`` is ``Joel is a slug``, the output would be ``Joel is ...``. If ``value`` is ``"Joel is a slug"``, the output will be ``"Joel is ..."``.
truncatewords_html truncatewords_html
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
@ -1792,10 +1802,8 @@ unordered_list
Recursively takes a self-nested list and returns an HTML unordered list -- Recursively takes a self-nested list and returns an HTML unordered list --
WITHOUT opening and closing <ul> tags. WITHOUT opening and closing <ul> tags.
**Changed in Django development version** **New in Django development version:** The format accepted by
``unordered_list`` has changed to be easier to understand.
The format accepted by ``unordered_list`` has changed to an easier to
understand format.
The list is assumed to be in the proper format. For example, if ``var`` contains The list is assumed to be in the proper format. For example, if ``var`` contains
``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``, then ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``, then
@ -1825,7 +1833,7 @@ For example::
{{ value|upper }} {{ value|upper }}
If ``value`` is ``Joel is a slug``, the output would be ``JOEL IS A SLUG``. If ``value`` is ``"Joel is a slug"``, the output will be ``"JOEL IS A SLUG"``.
urlencode urlencode
~~~~~~~~~ ~~~~~~~~~
@ -1844,9 +1852,9 @@ For example::
{{ value|urlize }} {{ value|urlize }}
If ``value`` is ``Check out www.djangoproject.com``, the output would be If ``value`` is ``"Check out www.djangoproject.com"``, the output will be
``Check out <a ``"Check out <a
href="http://www.djangoproject.com">www.djangoproject.com</a>``. href="http://www.djangoproject.com">www.djangoproject.com</a>"``.
urlizetrunc urlizetrunc
~~~~~~~~~~~ ~~~~~~~~~~~
@ -1862,9 +1870,9 @@ For example::
{{ value|urlizetrunc:15 }} {{ value|urlizetrunc:15 }}
If ``value`` is ``Check out www.djangoproject.com``, the output would be If ``value`` is ``"Check out www.djangoproject.com"``, the output would be
``Check out <a ``'Check out <a
href="http://www.djangoproject.com">www.djangopr...</a>``. href="http://www.djangoproject.com">www.djangopr...</a>'``.
wordcount wordcount
~~~~~~~~~ ~~~~~~~~~

View File

@ -38,6 +38,12 @@ A quick rundown:
data server-side, use ``method="post"``. This tip isn't specific to data server-side, use ``method="post"``. This tip isn't specific to
Django; it's just good Web development practice. Django; it's just good Web development practice.
* ``forloop.counter`` indicates how many times the ``for`` tag has
gone through its loop; for more information, see `the
documentation for the "for" tag`_.
.. _the documentation for the "for" tag: ../templates/#for
Now, let's create a Django view that handles the submitted data and does Now, let's create a Django view that handles the submitted data and does
something with it. Remember, in `Tutorial 3`_, we created a URLconf for the something with it. Remember, in `Tutorial 3`_, we created a URLconf for the
polls application that includes this line:: polls application that includes this line::

View File

@ -32,9 +32,11 @@ How Django processes a request
When a user requests a page from your Django-powered site, this is the When a user requests a page from your Django-powered site, this is the
algorithm the system follows to determine which Python code to execute: algorithm the system follows to determine which Python code to execute:
1. Django looks at the ``ROOT_URLCONF`` setting in your `settings file`_. 1. Django determines the root URLConf module to use; ordinarily
This should be a string representing the full Python import path to your this is the value of the ``ROOT_URLCONF`` setting in your
URLconf. For example: ``"mydjangoapps.urls"``. `settings file`_, but if the incoming ``HttpRequest`` object
has an attribute called ``urlconf``, its value will be used in
place of the ``ROOT_URLCONF`` setting.
2. Django loads that Python module and looks for the variable 2. Django loads that Python module and looks for the variable
``urlpatterns``. This should be a Python list, in the format returned by ``urlpatterns``. This should be a Python list, in the format returned by
the function ``django.conf.urls.defaults.patterns()``. the function ``django.conf.urls.defaults.patterns()``.

View File

@ -71,8 +71,9 @@ u'ABC123'
>>> fran.save() >>> fran.save()
>>> Employee.objects.filter(last_name__exact='Jones') >>> Employee.objects.filter(last_name__exact='Jones')
[<Employee: Dan Jones>, <Employee: Fran Jones>] [<Employee: Dan Jones>, <Employee: Fran Jones>]
>>> Employee.objects.in_bulk(['ABC123', 'XYZ456']) >>> emps = Employee.objects.in_bulk(['ABC123', 'XYZ456'])
{u'XYZ456': <Employee: Fran Jones>, u'ABC123': <Employee: Dan Jones>} >>> emps['ABC123']
<Employee: Dan Jones>
>>> b = Business(name='Sears') >>> b = Business(name='Sears')
>>> b.save() >>> b.save()

View File

@ -76,8 +76,11 @@ Article 4
# in_bulk() takes a list of IDs and returns a dictionary mapping IDs # in_bulk() takes a list of IDs and returns a dictionary mapping IDs
# to objects. # to objects.
>>> Article.objects.in_bulk([1, 2]) >>> arts = Article.objects.in_bulk([1, 2])
{1: <Article: Article 1>, 2: <Article: Article 2>} >>> arts[1]
<Article: Article 1>
>>> arts[2]
<Article: Article 2>
>>> Article.objects.in_bulk([3]) >>> Article.objects.in_bulk([3])
{3: <Article: Article 3>} {3: <Article: Article 3>}
>>> Article.objects.in_bulk([1000]) >>> Article.objects.in_bulk([1000])

View File

@ -41,25 +41,33 @@ __test__ = {'API_TESTS':u"""
True True
# Attempt to add a Musician without a first_name. # Attempt to add a Musician without a first_name.
>>> man.get_validation_errors(MultiValueDict({'last_name': ['Blakey']})) >>> man.get_validation_errors(MultiValueDict({'last_name': ['Blakey']}))['first_name']
{'first_name': [u'This field is required.']} [u'This field is required.']
# Attempt to add a Musician without a first_name and last_name. # Attempt to add a Musician without a first_name and last_name.
>>> man.get_validation_errors(MultiValueDict({})) >>> errors = man.get_validation_errors(MultiValueDict({}))
{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.']} >>> errors['first_name']
[u'This field is required.']
>>> errors['last_name']
[u'This field is required.']
# Attempt to create an Album without a name or musician. # Attempt to create an Album without a name or musician.
>>> man = Album.AddManipulator() >>> man = Album.AddManipulator()
>>> man.get_validation_errors(MultiValueDict({})) >>> errors = man.get_validation_errors(MultiValueDict({}))
{'musician': [u'This field is required.'], 'name': [u'This field is required.']} >>> errors['musician']
[u'This field is required.']
>>> errors['name']
[u'This field is required.']
# Attempt to create an Album with an invalid musician. # Attempt to create an Album with an invalid musician.
>>> man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['foo']})) >>> errors = man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['foo']}))
{'musician': [u"Select a valid choice; 'foo' is not in [u'', u'1']."]} >>> errors['musician']
[u"Select a valid choice; 'foo' is not in [u'', u'1']."]
# Attempt to create an Album with an invalid release_date. # Attempt to create an Album with an invalid release_date.
>>> man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['1'], 'release_date': 'today'})) >>> errors = man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['1'], 'release_date': 'today'}))
{'release_date': [u'Enter a valid date in YYYY-MM-DD format.']} >>> errors['release_date']
[u'Enter a valid date in YYYY-MM-DD format.']
# Create an Album without a release_date (because it's optional). # Create an Album without a release_date (because it's optional).
>>> data = MultiValueDict({'name': ['Ella and Basie'], 'musician': ['1']}) >>> data = MultiValueDict({'name': ['Ella and Basie'], 'musician': ['1']})

View File

@ -234,8 +234,12 @@ We can also subclass the Meta inner class to change the fields list.
>>> f = CategoryForm({'name': 'Entertainment', 'slug': 'entertainment', 'url': 'entertainment'}) >>> f = CategoryForm({'name': 'Entertainment', 'slug': 'entertainment', 'url': 'entertainment'})
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.cleaned_data >>> f.cleaned_data['url']
{'url': u'entertainment', 'name': u'Entertainment', 'slug': u'entertainment'} u'entertainment'
>>> f.cleaned_data['name']
u'Entertainment'
>>> f.cleaned_data['slug']
u'entertainment'
>>> obj = f.save() >>> obj = f.save()
>>> obj >>> obj
<Category: Entertainment> <Category: Entertainment>
@ -245,8 +249,12 @@ True
>>> f = CategoryForm({'name': "It's a test", 'slug': 'its-test', 'url': 'test'}) >>> f = CategoryForm({'name': "It's a test", 'slug': 'its-test', 'url': 'test'})
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.cleaned_data >>> f.cleaned_data['url']
{'url': u'test', 'name': u"It's a test", 'slug': u'its-test'} u'test'
>>> f.cleaned_data['name']
u"It's a test"
>>> f.cleaned_data['slug']
u'its-test'
>>> obj = f.save() >>> obj = f.save()
>>> obj >>> obj
<Category: It's a test> <Category: It's a test>
@ -259,8 +267,12 @@ save() on the resulting model instance.
>>> f = CategoryForm({'name': 'Third test', 'slug': 'third-test', 'url': 'third'}) >>> f = CategoryForm({'name': 'Third test', 'slug': 'third-test', 'url': 'third'})
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.cleaned_data >>> f.cleaned_data['url']
{'url': u'third', 'name': u'Third test', 'slug': u'third-test'} u'third'
>>> f.cleaned_data['name']
u'Third test'
>>> f.cleaned_data['slug']
u'third-test'
>>> obj = f.save(commit=False) >>> obj = f.save(commit=False)
>>> obj >>> obj
<Category: Third test> <Category: Third test>
@ -272,8 +284,10 @@ True
If you call save() with invalid data, you'll get a ValueError. If you call save() with invalid data, you'll get a ValueError.
>>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'}) >>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'})
>>> f.errors >>> f.errors['name']
{'name': [u'This field is required.'], 'slug': [u'This field is required.']} [u'This field is required.']
>>> f.errors['slug']
[u'This field is required.']
>>> f.cleaned_data >>> f.cleaned_data
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -645,6 +659,19 @@ Traceback (most recent call last):
... ...
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
# check that we can safely iterate choices repeatedly
>>> gen_one = list(f.choices)
>>> gen_two = f.choices
>>> gen_one[2]
(2L, u"It's a test")
>>> list(gen_two)
[(u'', u'---------'), (1L, u'Entertainment'), (2L, u"It's a test"), (3L, u'Third')]
# check that we can override the label_from_instance method to print custom labels (#4620)
>>> f.queryset = Category.objects.all()
>>> f.label_from_instance = lambda obj: "category " + str(obj)
>>> list(f.choices)
[(u'', u'---------'), (1L, 'category Entertainment'), (2L, "category It's a test"), (3L, 'category Third'), (4L, 'category Fourth')]
# ModelMultipleChoiceField #################################################### # ModelMultipleChoiceField ####################################################
@ -730,6 +757,10 @@ Traceback (most recent call last):
... ...
ValidationError: [u'Select a valid choice. 4 is not one of the available choices.'] ValidationError: [u'Select a valid choice. 4 is not one of the available choices.']
>>> f.queryset = Category.objects.all()
>>> f.label_from_instance = lambda obj: "multicategory " + str(obj)
>>> list(f.choices)
[(1L, 'multicategory Entertainment'), (2L, "multicategory It's a test"), (3L, 'multicategory Third'), (4L, 'multicategory Fourth')]
# PhoneNumberField ############################################################ # PhoneNumberField ############################################################
@ -739,8 +770,10 @@ ValidationError: [u'Select a valid choice. 4 is not one of the available choices
>>> f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'}) >>> f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'})
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.cleaned_data >>> f.cleaned_data['phone']
{'phone': u'312-555-1212', 'description': u'Assistance'} u'312-555-1212'
>>> f.cleaned_data['description']
u'Assistance'
# FileField ################################################################### # FileField ###################################################################
@ -766,7 +799,7 @@ True
<class 'django.newforms.fields.UploadedFile'> <class 'django.newforms.fields.UploadedFile'>
>>> instance = f.save() >>> instance = f.save()
>>> instance.file >>> instance.file
u'.../test1.txt' u'...test1.txt'
# Edit an instance that already has the file defined in the model. This will not # Edit an instance that already has the file defined in the model. This will not
# save the file again, but leave it exactly as it is. # save the file again, but leave it exactly as it is.
@ -775,10 +808,10 @@ u'.../test1.txt'
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.cleaned_data['file'] >>> f.cleaned_data['file']
u'.../test1.txt' u'...test1.txt'
>>> instance = f.save() >>> instance = f.save()
>>> instance.file >>> instance.file
u'.../test1.txt' u'...test1.txt'
# Delete the current file since this is not done by Django. # Delete the current file since this is not done by Django.
@ -791,7 +824,7 @@ u'.../test1.txt'
True True
>>> instance = f.save() >>> instance = f.save()
>>> instance.file >>> instance.file
u'.../test2.txt' u'...test2.txt'
>>> instance.delete() >>> instance.delete()
@ -810,7 +843,7 @@ True
True True
>>> instance = f.save() >>> instance = f.save()
>>> instance.file >>> instance.file
u'.../test3.txt' u'...test3.txt'
>>> instance.delete() >>> instance.delete()
# ImageField ################################################################### # ImageField ###################################################################
@ -832,7 +865,7 @@ True
<class 'django.newforms.fields.UploadedFile'> <class 'django.newforms.fields.UploadedFile'>
>>> instance = f.save() >>> instance = f.save()
>>> instance.image >>> instance.image
u'.../test.png' u'...test.png'
# Edit an instance that already has the image defined in the model. This will not # Edit an instance that already has the image defined in the model. This will not
# save the image again, but leave it exactly as it is. # save the image again, but leave it exactly as it is.
@ -841,10 +874,10 @@ u'.../test.png'
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.cleaned_data['image'] >>> f.cleaned_data['image']
u'.../test.png' u'...test.png'
>>> instance = f.save() >>> instance = f.save()
>>> instance.image >>> instance.image
u'.../test.png' u'...test.png'
# Delete the current image since this is not done by Django. # Delete the current image since this is not done by Django.
@ -857,7 +890,7 @@ u'.../test.png'
True True
>>> instance = f.save() >>> instance = f.save()
>>> instance.image >>> instance.image
u'.../test2.png' u'...test2.png'
>>> instance.delete() >>> instance.delete()
@ -876,7 +909,7 @@ True
True True
>>> instance = f.save() >>> instance = f.save()
>>> instance.image >>> instance.image
u'.../test3.png' u'...test3.png'
>>> instance.delete() >>> instance.delete()
"""} """}

View File

@ -4,6 +4,11 @@
Django provides a framework for paginating a list of objects in a few lines Django provides a framework for paginating a list of objects in a few lines
of code. This is often useful for dividing search results or long lists of of code. This is often useful for dividing search results or long lists of
objects into easily readable pages. objects into easily readable pages.
In Django 0.96 and earlier, a single ObjectPaginator class implemented this
functionality. In the Django development version, the behavior is split across
two classes -- Paginator and Page -- that are more easier to use. The legacy
ObjectPaginator class is deprecated.
""" """
from django.db import models from django.db import models
@ -16,70 +21,210 @@ class Article(models.Model):
return self.headline return self.headline
__test__ = {'API_TESTS':""" __test__ = {'API_TESTS':"""
# prepare a list of objects for pagination # Prepare a list of objects for pagination.
>>> from datetime import datetime >>> from datetime import datetime
>>> for x in range(1, 10): >>> for x in range(1, 10):
... a = Article(headline='Article %s' % x, pub_date=datetime(2005, 7, 29)) ... a = Article(headline='Article %s' % x, pub_date=datetime(2005, 7, 29))
... a.save() ... a.save()
# create a basic paginator, 5 articles per page ####################################
# New/current API (Paginator/Page) #
####################################
>>> from django.core.paginator import Paginator, InvalidPage
>>> paginator = Paginator(Article.objects.all(), 5)
>>> paginator.count
9
>>> paginator.num_pages
2
>>> paginator.page_range
[1, 2]
# Get the first page.
>>> p = paginator.page(1)
>>> p
<Page 1 of 2>
>>> p.object_list
[<Article: Article 1>, <Article: Article 2>, <Article: Article 3>, <Article: Article 4>, <Article: Article 5>]
>>> p.has_next()
True
>>> p.has_previous()
False
>>> p.has_other_pages()
True
>>> p.next_page_number()
2
>>> p.previous_page_number()
0
>>> p.start_index()
1
>>> p.end_index()
5
# Get the second page.
>>> p = paginator.page(2)
>>> p
<Page 2 of 2>
>>> p.object_list
[<Article: Article 6>, <Article: Article 7>, <Article: Article 8>, <Article: Article 9>]
>>> p.has_next()
False
>>> p.has_previous()
True
>>> p.has_other_pages()
True
>>> p.next_page_number()
3
>>> p.previous_page_number()
1
>>> p.start_index()
6
>>> p.end_index()
9
# Invalid pages raise InvalidPage.
>>> paginator.page(0)
Traceback (most recent call last):
...
InvalidPage: ...
>>> paginator.page(3)
Traceback (most recent call last):
...
InvalidPage: ...
# Empty paginators with allow_empty_first_page=True.
>>> paginator = Paginator(Article.objects.filter(id=0), 5, allow_empty_first_page=True)
>>> paginator.count
0
>>> paginator.num_pages
1
>>> paginator.page_range
[1]
# Empty paginators with allow_empty_first_page=False.
>>> paginator = Paginator(Article.objects.filter(id=0), 5, allow_empty_first_page=False)
>>> paginator.count
0
>>> paginator.num_pages
0
>>> paginator.page_range
[]
# Paginators work with regular lists/tuples, too -- not just with QuerySets.
>>> paginator = Paginator([1, 2, 3, 4, 5, 6, 7, 8, 9], 5)
>>> paginator.count
9
>>> paginator.num_pages
2
>>> paginator.page_range
[1, 2]
# Get the first page.
>>> p = paginator.page(1)
>>> p
<Page 1 of 2>
>>> p.object_list
[1, 2, 3, 4, 5]
>>> p.has_next()
True
>>> p.has_previous()
False
>>> p.has_other_pages()
True
>>> p.next_page_number()
2
>>> p.previous_page_number()
0
>>> p.start_index()
1
>>> p.end_index()
5
################################
# Legacy API (ObjectPaginator) #
################################
# Don't print out the deprecation warnings during testing.
>>> from warnings import filterwarnings
>>> filterwarnings("ignore")
>>> from django.core.paginator import ObjectPaginator, InvalidPage >>> from django.core.paginator import ObjectPaginator, InvalidPage
>>> paginator = ObjectPaginator(Article.objects.all(), 5) >>> paginator = ObjectPaginator(Article.objects.all(), 5)
# the paginator knows how many hits and pages it contains
>>> paginator.hits >>> paginator.hits
9 9
>>> paginator.pages >>> paginator.pages
2 2
>>> paginator.page_range
[1, 2]
# get the first page (zero-based) # Get the first page.
>>> paginator.get_page(0) >>> paginator.get_page(0)
[<Article: Article 1>, <Article: Article 2>, <Article: Article 3>, <Article: Article 4>, <Article: Article 5>] [<Article: Article 1>, <Article: Article 2>, <Article: Article 3>, <Article: Article 4>, <Article: Article 5>]
# get the second page
>>> paginator.get_page(1)
[<Article: Article 6>, <Article: Article 7>, <Article: Article 8>, <Article: Article 9>]
# does the first page have a next or previous page?
>>> paginator.has_next_page(0) >>> paginator.has_next_page(0)
True True
>>> paginator.has_previous_page(0) >>> paginator.has_previous_page(0)
False False
# check the second page
>>> paginator.has_next_page(1)
False
>>> paginator.has_previous_page(1)
True
>>> paginator.first_on_page(0) >>> paginator.first_on_page(0)
1 1
>>> paginator.first_on_page(1)
6
>>> paginator.last_on_page(0) >>> paginator.last_on_page(0)
5 5
# Get the second page.
>>> paginator.get_page(1)
[<Article: Article 6>, <Article: Article 7>, <Article: Article 8>, <Article: Article 9>]
>>> paginator.has_next_page(1)
False
>>> paginator.has_previous_page(1)
True
>>> paginator.first_on_page(1)
6
>>> paginator.last_on_page(1) >>> paginator.last_on_page(1)
9 9
# Invalid pages raise InvalidPage.
>>> paginator.get_page(-1)
Traceback (most recent call last):
...
InvalidPage: ...
>>> paginator.get_page(2)
Traceback (most recent call last):
...
InvalidPage: ...
# Empty paginators with allow_empty_first_page=True.
>>> paginator = ObjectPaginator(Article.objects.filter(id=0), 5)
>>> paginator.count
0
>>> paginator.num_pages
1
>>> paginator.page_range
[1]
##################
# Orphan support #
##################
# Add a few more records to test out the orphans feature. # Add a few more records to test out the orphans feature.
>>> for x in range(10, 13): >>> for x in range(10, 13):
... Article(headline="Article %s" % x, pub_date=datetime(2006, 10, 6)).save() ... Article(headline="Article %s" % x, pub_date=datetime(2006, 10, 6)).save()
# With orphans set to 3 and 10 items per page, we should get all 12 items on a single page: # With orphans set to 3 and 10 items per page, we should get all 12 items on a single page.
>>> paginator = Paginator(Article.objects.all(), 10, orphans=3)
>>> paginator.num_pages
1
# With orphans only set to 1, we should get two pages.
>>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=1)
>>> paginator.num_pages
2
# LEGACY: With orphans set to 3 and 10 items per page, we should get all 12 items on a single page.
>>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=3) >>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=3)
>>> paginator.pages >>> paginator.pages
1 1
# With orphans only set to 1, we should get two pages: # LEGACY: With orphans only set to 1, we should get two pages.
>>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=1) >>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=1)
>>> paginator.pages >>> paginator.pages
2 2
# The paginator can provide a list of all available pages.
>>> paginator = ObjectPaginator(Article.objects.all(), 10)
>>> paginator.page_range
[1, 2]
"""} """}

View File

@ -41,8 +41,8 @@ __test__ = {'API_TESTS':"""
23 23
>>> p = Person(**dict(valid_params, id='foo')) >>> p = Person(**dict(valid_params, id='foo'))
>>> p.validate() >>> p.validate()['id']
{'id': [u'This value must be an integer.']} [u'This value must be an integer.']
>>> p = Person(**dict(valid_params, id=None)) >>> p = Person(**dict(valid_params, id=None))
>>> p.validate() >>> p.validate()
@ -75,8 +75,8 @@ True
False False
>>> p = Person(**dict(valid_params, is_child='foo')) >>> p = Person(**dict(valid_params, is_child='foo'))
>>> p.validate() >>> p.validate()['is_child']
{'is_child': [u'This value must be either True or False.']} [u'This value must be either True or False.']
>>> p = Person(**dict(valid_params, name=u'Jose')) >>> p = Person(**dict(valid_params, name=u'Jose'))
>>> p.validate() >>> p.validate()
@ -115,8 +115,8 @@ datetime.date(2000, 5, 3)
datetime.date(2000, 5, 3) datetime.date(2000, 5, 3)
>>> p = Person(**dict(valid_params, birthdate='foo')) >>> p = Person(**dict(valid_params, birthdate='foo'))
>>> p.validate() >>> p.validate()['birthdate']
{'birthdate': [u'Enter a valid date in YYYY-MM-DD format.']} [u'Enter a valid date in YYYY-MM-DD format.']
>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3, 13, 23))) >>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3, 13, 23)))
>>> p.validate() >>> p.validate()
@ -143,11 +143,15 @@ datetime.datetime(2002, 4, 3, 0, 0)
u'john@example.com' u'john@example.com'
>>> p = Person(**dict(valid_params, email=22)) >>> p = Person(**dict(valid_params, email=22))
>>> p.validate() >>> p.validate()['email']
{'email': [u'Enter a valid e-mail address.']} [u'Enter a valid e-mail address.']
# Make sure that Date and DateTime return validation errors and don't raise Python errors. # Make sure that Date and DateTime return validation errors and don't raise Python errors.
>>> Person(name='John Doe', is_child=True, email='abc@def.com').validate() >>> p = Person(name='John Doe', is_child=True, email='abc@def.com')
{'favorite_moment': [u'This field is required.'], 'birthdate': [u'This field is required.']} >>> errors = p.validate()
>>> errors['favorite_moment']
[u'This field is required.']
>>> errors['birthdate']
[u'This field is required.']
"""} """}

View File

@ -22,7 +22,7 @@ classes that demonstrate some of the library's abilities.
>>> from django.newforms.extras import SelectDateWidget >>> from django.newforms.extras import SelectDateWidget
>>> w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016')) >>> w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016'))
>>> print w.render('mydate', '') >>> print w.render('mydate', '')
<select name="mydate_month"> <select name="mydate_month" id="id_mydate_month">
<option value="1">January</option> <option value="1">January</option>
<option value="2">February</option> <option value="2">February</option>
<option value="3">March</option> <option value="3">March</option>
@ -36,7 +36,7 @@ classes that demonstrate some of the library's abilities.
<option value="11">November</option> <option value="11">November</option>
<option value="12">December</option> <option value="12">December</option>
</select> </select>
<select name="mydate_day"> <select name="mydate_day" id="id_mydate_day">
<option value="1">1</option> <option value="1">1</option>
<option value="2">2</option> <option value="2">2</option>
<option value="3">3</option> <option value="3">3</option>
@ -69,7 +69,7 @@ classes that demonstrate some of the library's abilities.
<option value="30">30</option> <option value="30">30</option>
<option value="31">31</option> <option value="31">31</option>
</select> </select>
<select name="mydate_year"> <select name="mydate_year" id="id_mydate_year">
<option value="2007">2007</option> <option value="2007">2007</option>
<option value="2008">2008</option> <option value="2008">2008</option>
<option value="2009">2009</option> <option value="2009">2009</option>
@ -84,7 +84,7 @@ classes that demonstrate some of the library's abilities.
>>> w.render('mydate', None) == w.render('mydate', '') >>> w.render('mydate', None) == w.render('mydate', '')
True True
>>> print w.render('mydate', '2010-04-15') >>> print w.render('mydate', '2010-04-15')
<select name="mydate_month"> <select name="mydate_month" id="id_mydate_month">
<option value="1">January</option> <option value="1">January</option>
<option value="2">February</option> <option value="2">February</option>
<option value="3">March</option> <option value="3">March</option>
@ -98,7 +98,7 @@ True
<option value="11">November</option> <option value="11">November</option>
<option value="12">December</option> <option value="12">December</option>
</select> </select>
<select name="mydate_day"> <select name="mydate_day" id="id_mydate_day">
<option value="1">1</option> <option value="1">1</option>
<option value="2">2</option> <option value="2">2</option>
<option value="3">3</option> <option value="3">3</option>
@ -131,7 +131,74 @@ True
<option value="30">30</option> <option value="30">30</option>
<option value="31">31</option> <option value="31">31</option>
</select> </select>
<select name="mydate_year"> <select name="mydate_year" id="id_mydate_year">
<option value="2007">2007</option>
<option value="2008">2008</option>
<option value="2009">2009</option>
<option value="2010" selected="selected">2010</option>
<option value="2011">2011</option>
<option value="2012">2012</option>
<option value="2013">2013</option>
<option value="2014">2014</option>
<option value="2015">2015</option>
<option value="2016">2016</option>
</select>
Accepts a datetime or a string:
>>> w.render('mydate', datetime.date(2010, 4, 15)) == w.render('mydate', '2010-04-15')
True
Invalid dates still render the failed date:
>>> print w.render('mydate', '2010-02-31')
<select name="mydate_month" id="id_mydate_month">
<option value="1">January</option>
<option value="2" selected="selected">February</option>
<option value="3">March</option>
<option value="4">April</option>
<option value="5">May</option>
<option value="6">June</option>
<option value="7">July</option>
<option value="8">August</option>
<option value="9">September</option>
<option value="10">October</option>
<option value="11">November</option>
<option value="12">December</option>
</select>
<select name="mydate_day" id="id_mydate_day">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="14">14</option>
<option value="15">15</option>
<option value="16">16</option>
<option value="17">17</option>
<option value="18">18</option>
<option value="19">19</option>
<option value="20">20</option>
<option value="21">21</option>
<option value="22">22</option>
<option value="23">23</option>
<option value="24">24</option>
<option value="25">25</option>
<option value="26">26</option>
<option value="27">27</option>
<option value="28">28</option>
<option value="29">29</option>
<option value="30">30</option>
<option value="31" selected="selected">31</option>
</select>
<select name="mydate_year" id="id_mydate_year">
<option value="2007">2007</option> <option value="2007">2007</option>
<option value="2008">2008</option> <option value="2008">2008</option>
<option value="2009">2009</option> <option value="2009">2009</option>
@ -252,8 +319,8 @@ ValidationError: [u'This field is required.']
</select> </select>
<input type="text" name="field1_2_0" value="2007-04-25" id="id_field1_2_0" /><input type="text" name="field1_2_1" value="06:24:00" id="id_field1_2_1" /></td></tr> <input type="text" name="field1_2_0" value="2007-04-25" id="id_field1_2_0" /><input type="text" name="field1_2_1" value="06:24:00" id="id_field1_2_1" /></td></tr>
>>> f.cleaned_data >>> f.cleaned_data['field1']
{'field1': u'some text,JP,2007-04-25 06:24:00'} u'some text,JP,2007-04-25 06:24:00'
# IPAddressField ################################################################## # IPAddressField ##################################################################

View File

@ -1133,6 +1133,33 @@ u''
>>> f.clean(None) >>> f.clean(None)
u'' u''
# FilePathField ###############################################################
>>> import os
>>> from django import newforms as forms
>>> path = forms.__file__
>>> path = os.path.dirname(path) + '/'
>>> path
'.../django/newforms/'
>>> f = forms.FilePathField(path=path)
>>> f.choices.sort()
>>> f.choices
[('.../django/newforms/__init__.py', '__init__.py'), ('.../django/newforms/__init__.pyc', '__init__.pyc'), ('.../django/newforms/fields.py', 'fields.py'), ('.../django/newforms/fields.pyc', 'fields.pyc'), ('.../django/newforms/forms.py', 'forms.py'), ('.../django/newforms/forms.pyc', 'forms.pyc'), ('.../django/newforms/models.py', 'models.py'), ('.../django/newforms/models.pyc', 'models.pyc'), ('.../django/newforms/util.py', 'util.py'), ('.../django/newforms/util.pyc', 'util.pyc'), ('.../django/newforms/widgets.py', 'widgets.py'), ('.../django/newforms/widgets.pyc', 'widgets.pyc')]
>>> f.clean('fields.py')
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
>>> f.clean(path + 'fields.py')
u'.../django/newforms/fields.py'
>>> f = forms.FilePathField(path=path, match='^.*?\.py$')
>>> f.choices.sort()
>>> f.choices
[('.../django/newforms/__init__.py', '__init__.py'), ('.../django/newforms/fields.py', 'fields.py'), ('.../django/newforms/forms.py', 'forms.py'), ('.../django/newforms/models.py', 'models.py'), ('.../django/newforms/util.py', 'util.py'), ('.../django/newforms/widgets.py', 'widgets.py')]
>>> f = forms.FilePathField(path=path, recursive=True, match='^.*?\.py$')
>>> f.choices.sort()
>>> f.choices
[('.../django/newforms/__init__.py', '__init__.py'), ('.../django/newforms/extras/__init__.py', 'extras/__init__.py'), ('.../django/newforms/extras/widgets.py', 'extras/widgets.py'), ('.../django/newforms/fields.py', 'fields.py'), ('.../django/newforms/forms.py', 'forms.py'), ('.../django/newforms/models.py', 'models.py'), ('.../django/newforms/util.py', 'util.py'), ('.../django/newforms/widgets.py', 'widgets.py')]
# SplitDateTimeField ########################################################## # SplitDateTimeField ##########################################################
>>> f = SplitDateTimeField() >>> f = SplitDateTimeField()

View File

@ -36,8 +36,8 @@ True
u'' u''
>>> p.errors.as_text() >>> p.errors.as_text()
u'' u''
>>> p.cleaned_data >>> p.cleaned_data["first_name"], p.cleaned_data["last_name"], p.cleaned_data["birthday"]
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} (u'John', u'Lennon', datetime.date(1940, 10, 9))
>>> print p['first_name'] >>> print p['first_name']
<input type="text" name="first_name" value="John" id="id_first_name" /> <input type="text" name="first_name" value="John" id="id_first_name" />
>>> print p['last_name'] >>> print p['last_name']
@ -68,8 +68,12 @@ Empty dictionaries are valid, too.
>>> p = Person({}) >>> p = Person({})
>>> p.is_bound >>> p.is_bound
True True
>>> p.errors >>> p.errors['first_name']
{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.'], 'birthday': [u'This field is required.']} [u'This field is required.']
>>> p.errors['last_name']
[u'This field is required.']
>>> p.errors['birthday']
[u'This field is required.']
>>> p.is_valid() >>> p.is_valid()
False False
>>> p.cleaned_data >>> p.cleaned_data
@ -137,8 +141,10 @@ u'<li><label for="id_first_name">First name:</label> <input type="text" name="fi
u'<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" value="John" id="id_first_name" /></p>\n<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" id="id_last_name" /></p>\n<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></p>' u'<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" value="John" id="id_first_name" /></p>\n<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" id="id_last_name" /></p>\n<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></p>'
>>> p = Person({'last_name': u'Lennon'}) >>> p = Person({'last_name': u'Lennon'})
>>> p.errors >>> p.errors['first_name']
{'first_name': [u'This field is required.'], 'birthday': [u'This field is required.']} [u'This field is required.']
>>> p.errors['birthday']
[u'This field is required.']
>>> p.is_valid() >>> p.is_valid()
False False
>>> p.errors.as_ul() >>> p.errors.as_ul()
@ -175,8 +181,13 @@ but cleaned_data contains only the form's fields.
>>> p = Person(data) >>> p = Person(data)
>>> p.is_valid() >>> p.is_valid()
True True
>>> p.cleaned_data >>> p.cleaned_data['first_name']
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} u'John'
>>> p.cleaned_data['last_name']
u'Lennon'
>>> p.cleaned_data['birthday']
datetime.date(1940, 10, 9)
cleaned_data will include a key and value for *all* fields defined in the Form, cleaned_data will include a key and value for *all* fields defined in the Form,
even if the Form's data didn't include a value for fields that are not even if the Form's data didn't include a value for fields that are not
@ -191,8 +202,12 @@ empty string.
>>> f = OptionalPersonForm(data) >>> f = OptionalPersonForm(data)
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.cleaned_data >>> f.cleaned_data['nick_name']
{'nick_name': u'', 'first_name': u'John', 'last_name': u'Lennon'} u''
>>> f.cleaned_data['first_name']
u'John'
>>> f.cleaned_data['last_name']
u'Lennon'
For DateFields, it's set to None. For DateFields, it's set to None.
>>> class OptionalPersonForm(Form): >>> class OptionalPersonForm(Form):
@ -203,8 +218,12 @@ For DateFields, it's set to None.
>>> f = OptionalPersonForm(data) >>> f = OptionalPersonForm(data)
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.cleaned_data >>> print f.cleaned_data['birth_date']
{'birth_date': None, 'first_name': u'John', 'last_name': u'Lennon'} None
>>> f.cleaned_data['first_name']
u'John'
>>> f.cleaned_data['last_name']
u'Lennon'
"auto_id" tells the Form to add an "id" attribute to each form element. "auto_id" tells the Form to add an "id" attribute to each form element.
If it's a string that contains '%s', Django will use that as a format string If it's a string that contains '%s', Django will use that as a format string
@ -549,18 +568,22 @@ The MultipleHiddenInput widget renders multiple values as hidden fields.
When using CheckboxSelectMultiple, the framework expects a list of input and When using CheckboxSelectMultiple, the framework expects a list of input and
returns a list of input. returns a list of input.
>>> f = SongForm({'name': 'Yesterday'}, auto_id=False) >>> f = SongForm({'name': 'Yesterday'}, auto_id=False)
>>> f.errors >>> f.errors['composers']
{'composers': [u'This field is required.']} [u'This field is required.']
>>> f = SongForm({'name': 'Yesterday', 'composers': ['J']}, auto_id=False) >>> f = SongForm({'name': 'Yesterday', 'composers': ['J']}, auto_id=False)
>>> f.errors >>> f.errors
{} {}
>>> f.cleaned_data >>> f.cleaned_data['composers']
{'composers': [u'J'], 'name': u'Yesterday'} [u'J']
>>> f.cleaned_data['name']
u'Yesterday'
>>> f = SongForm({'name': 'Yesterday', 'composers': ['J', 'P']}, auto_id=False) >>> f = SongForm({'name': 'Yesterday', 'composers': ['J', 'P']}, auto_id=False)
>>> f.errors >>> f.errors
{} {}
>>> f.cleaned_data >>> f.cleaned_data['composers']
{'composers': [u'J', u'P'], 'name': u'Yesterday'} [u'J', u'P']
>>> f.cleaned_data['name']
u'Yesterday'
Validation errors are HTML-escaped when output as HTML. Validation errors are HTML-escaped when output as HTML.
>>> class EscapingForm(Form): >>> class EscapingForm(Form):
@ -598,16 +621,24 @@ including the current field (e.g., the field XXX if you're in clean_XXX()).
>>> f.errors >>> f.errors
{} {}
>>> f = UserRegistration({}, auto_id=False) >>> f = UserRegistration({}, auto_id=False)
>>> f.errors >>> f.errors['username']
{'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']} [u'This field is required.']
>>> f.errors['password1']
[u'This field is required.']
>>> f.errors['password2']
[u'This field is required.']
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False) >>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)
>>> f.errors >>> f.errors['password2']
{'password2': [u'Please make sure your passwords match.']} [u'Please make sure your passwords match.']
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False) >>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
>>> f.errors >>> f.errors
{} {}
>>> f.cleaned_data >>> f.cleaned_data['username']
{'username': u'adrian', 'password1': u'foo', 'password2': u'foo'} u'adrian'
>>> f.cleaned_data['password1']
u'foo'
>>> f.cleaned_data['password2']
u'foo'
Another way of doing multiple-field validation is by implementing the Another way of doing multiple-field validation is by implementing the
Form's clean() method. If you do this, any ValidationError raised by that Form's clean() method. If you do this, any ValidationError raised by that
@ -632,11 +663,15 @@ Form.clean() is required to return a dictionary of all clean data.
<tr><th>Username:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="username" maxlength="10" /></td></tr> <tr><th>Username:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="username" maxlength="10" /></td></tr>
<tr><th>Password1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="password" name="password1" /></td></tr> <tr><th>Password1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="password" name="password1" /></td></tr>
<tr><th>Password2:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="password" name="password2" /></td></tr> <tr><th>Password2:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="password" name="password2" /></td></tr>
>>> f.errors >>> f.errors['username']
{'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']} [u'This field is required.']
>>> f.errors['password1']
[u'This field is required.']
>>> f.errors['password2']
[u'This field is required.']
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False) >>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)
>>> f.errors >>> f.errors['__all__']
{'__all__': [u'Please make sure your passwords match.']} [u'Please make sure your passwords match.']
>>> print f.as_table() >>> print f.as_table()
<tr><td colspan="2"><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></td></tr> <tr><td colspan="2"><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></td></tr>
<tr><th>Username:</th><td><input type="text" name="username" value="adrian" maxlength="10" /></td></tr> <tr><th>Username:</th><td><input type="text" name="username" value="adrian" maxlength="10" /></td></tr>
@ -650,8 +685,12 @@ Form.clean() is required to return a dictionary of all clean data.
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False) >>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
>>> f.errors >>> f.errors
{} {}
>>> f.cleaned_data >>> f.cleaned_data['username']
{'username': u'adrian', 'password1': u'foo', 'password2': u'foo'} u'adrian'
>>> f.cleaned_data['password1']
u'foo'
>>> f.cleaned_data['password2']
u'foo'
# Dynamic construction ######################################################## # Dynamic construction ########################################################
@ -1024,8 +1063,8 @@ An 'initial' value is *not* used as a fallback if data is not provided. In this
example, we don't provide a value for 'username', and the form raises a example, we don't provide a value for 'username', and the form raises a
validation error rather than using the initial value for 'username'. validation error rather than using the initial value for 'username'.
>>> p = UserRegistration({'password': 'secret'}) >>> p = UserRegistration({'password': 'secret'})
>>> p.errors >>> p.errors['username']
{'username': [u'This field is required.']} [u'This field is required.']
>>> p.is_valid() >>> p.is_valid()
False False
@ -1069,8 +1108,8 @@ A dynamic 'initial' value is *not* used as a fallback if data is not provided.
In this example, we don't provide a value for 'username', and the form raises a In this example, we don't provide a value for 'username', and the form raises a
validation error rather than using the initial value for 'username'. validation error rather than using the initial value for 'username'.
>>> p = UserRegistration({'password': 'secret'}, initial={'username': 'django'}) >>> p = UserRegistration({'password': 'secret'}, initial={'username': 'django'})
>>> p.errors >>> p.errors['username']
{'username': [u'This field is required.']} [u'This field is required.']
>>> p.is_valid() >>> p.is_valid()
False False
@ -1123,8 +1162,8 @@ A callable 'initial' value is *not* used as a fallback if data is not provided.
In this example, we don't provide a value for 'username', and the form raises a In this example, we don't provide a value for 'username', and the form raises a
validation error rather than using the initial value for 'username'. validation error rather than using the initial value for 'username'.
>>> p = UserRegistration({'password': 'secret'}, initial={'username': initial_django}) >>> p = UserRegistration({'password': 'secret'}, initial={'username': initial_django})
>>> p.errors >>> p.errors['username']
{'username': [u'This field is required.']} [u'This field is required.']
>>> p.is_valid() >>> p.is_valid()
False False
@ -1258,8 +1297,12 @@ actual field name.
{} {}
>>> p.is_valid() >>> p.is_valid()
True True
>>> p.cleaned_data >>> p.cleaned_data['first_name']
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} u'John'
>>> p.cleaned_data['last_name']
u'Lennon'
>>> p.cleaned_data['birthday']
datetime.date(1940, 10, 9)
Let's try submitting some bad data to make sure form.errors and field.errors Let's try submitting some bad data to make sure form.errors and field.errors
work as expected. work as expected.
@ -1269,8 +1312,12 @@ work as expected.
... 'person1-birthday': u'' ... 'person1-birthday': u''
... } ... }
>>> p = Person(data, prefix='person1') >>> p = Person(data, prefix='person1')
>>> p.errors >>> p.errors['first_name']
{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.'], 'birthday': [u'This field is required.']} [u'This field is required.']
>>> p.errors['last_name']
[u'This field is required.']
>>> p.errors['birthday']
[u'This field is required.']
>>> p['first_name'].errors >>> p['first_name'].errors
[u'This field is required.'] [u'This field is required.']
>>> p['person1-first_name'].errors >>> p['person1-first_name'].errors
@ -1286,8 +1333,12 @@ the form doesn't "see" the fields.
... 'birthday': u'1940-10-9' ... 'birthday': u'1940-10-9'
... } ... }
>>> p = Person(data, prefix='person1') >>> p = Person(data, prefix='person1')
>>> p.errors >>> p.errors['first_name']
{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.'], 'birthday': [u'This field is required.']} [u'This field is required.']
>>> p.errors['last_name']
[u'This field is required.']
>>> p.errors['birthday']
[u'This field is required.']
With prefixes, a single data dictionary can hold data for multiple instances With prefixes, a single data dictionary can hold data for multiple instances
of the same form. of the same form.
@ -1302,13 +1353,21 @@ of the same form.
>>> p1 = Person(data, prefix='person1') >>> p1 = Person(data, prefix='person1')
>>> p1.is_valid() >>> p1.is_valid()
True True
>>> p1.cleaned_data >>> p1.cleaned_data['first_name']
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} u'John'
>>> p1.cleaned_data['last_name']
u'Lennon'
>>> p1.cleaned_data['birthday']
datetime.date(1940, 10, 9)
>>> p2 = Person(data, prefix='person2') >>> p2 = Person(data, prefix='person2')
>>> p2.is_valid() >>> p2.is_valid()
True True
>>> p2.cleaned_data >>> p2.cleaned_data['first_name']
{'first_name': u'Jim', 'last_name': u'Morrison', 'birthday': datetime.date(1943, 12, 8)} u'Jim'
>>> p2.cleaned_data['last_name']
u'Morrison'
>>> p2.cleaned_data['birthday']
datetime.date(1943, 12, 8)
By default, forms append a hyphen between the prefix and the field name, but a By default, forms append a hyphen between the prefix and the field name, but a
form can alter that behavior by implementing the add_prefix() method. This form can alter that behavior by implementing the add_prefix() method. This
@ -1333,8 +1392,12 @@ self.prefix.
>>> p = Person(data, prefix='foo') >>> p = Person(data, prefix='foo')
>>> p.is_valid() >>> p.is_valid()
True True
>>> p.cleaned_data >>> p.cleaned_data['first_name']
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} u'John'
>>> p.cleaned_data['last_name']
u'Lennon'
>>> p.cleaned_data['birthday']
datetime.date(1940, 10, 9)
# Forms with NullBooleanFields ################################################ # Forms with NullBooleanFields ################################################

View File

@ -0,0 +1,24 @@
from django.db import models
class Foo(models.Model):
a = models.CharField(max_length=10)
def get_foo():
return Foo.objects.get(id=1)
class Bar(models.Model):
b = models.CharField(max_length=10)
a = models.ForeignKey(Foo, default=get_foo)
__test__ = {'API_TESTS':"""
# Create a couple of Places.
>>> f = Foo.objects.create(a='abc')
>>> f.id
1
>>> b = Bar(b = "bcd")
>>> b.a
<Foo: Foo object>
>>> b.save()
"""}

View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
from django.test import TestCase
from django.test.client import Client
class SyndicationFeedTest(TestCase):
def test_complex_base_url(self):
"""
Tests that that the base url for a complex feed doesn't raise a 500
exception.
"""
c = Client()
response = c.get('/syndication/feeds/complex/')
self.assertEquals(response.status_code, 404)

View File

@ -0,0 +1,18 @@
from django.conf.urls.defaults import patterns
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.syndication import feeds
class ComplexFeed(feeds.Feed):
def get_object(self, bits):
if len(bits) != 1:
raise ObjectDoesNotExist
return None
urlpatterns = patterns('',
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {
'feed_dict': dict(
complex = ComplexFeed,
)}),
)

View File

@ -3,7 +3,7 @@ Regression tests for the Test Client, especially the customized assertions.
""" """
from django.test import Client, TestCase from django.test import Client, TestCase
from django.core import mail from django.core.urlresolvers import reverse
import os import os
class AssertContainsTests(TestCase): class AssertContainsTests(TestCase):
@ -261,3 +261,31 @@ class LoginTests(TestCase):
# Check that assertRedirects uses the original client, not the # Check that assertRedirects uses the original client, not the
# default client. # default client.
self.assertRedirects(response, "http://testserver/test_client_regress/get_view/") self.assertRedirects(response, "http://testserver/test_client_regress/get_view/")
class URLEscapingTests(TestCase):
def test_simple_argument_get(self):
"Get a view that has a simple string argument"
response = self.client.get(reverse('arg_view', args=['Slartibartfast']))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, 'Howdy, Slartibartfast')
def test_argument_with_space_get(self):
"Get a view that has a string argument that requires escaping"
response = self.client.get(reverse('arg_view', args=['Arthur Dent']))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, 'Hi, Arthur')
def test_simple_argument_post(self):
"Post for a view that has a simple string argument"
response = self.client.post(reverse('arg_view', args=['Slartibartfast']))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, 'Howdy, Slartibartfast')
def test_argument_with_space_post(self):
"Post for a view that has a string argument that requires escaping"
response = self.client.post(reverse('arg_view', args=['Arthur Dent']))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, 'Hi, Arthur')

View File

@ -5,5 +5,6 @@ urlpatterns = patterns('',
(r'^no_template_view/$', views.no_template_view), (r'^no_template_view/$', views.no_template_view),
(r'^file_upload/$', views.file_upload_view), (r'^file_upload/$', views.file_upload_view),
(r'^get_view/$', views.get_view), (r'^get_view/$', views.get_view),
url(r'^arg_view/(?P<name>.+)/$', views.view_with_argument, name='arg_view'),
(r'^login_protected_redirect_view/$', views.login_protected_redirect_view) (r'^login_protected_redirect_view/$', views.login_protected_redirect_view)
) )

View File

@ -1,7 +1,5 @@
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.mail import EmailMessage, SMTPConnection
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError
from django.shortcuts import render_to_response
def no_template_view(request): def no_template_view(request):
"A simple view that expects a GET request, and returns a rendered template" "A simple view that expects a GET request, and returns a rendered template"
@ -24,6 +22,18 @@ def get_view(request):
return HttpResponse("Hello world") return HttpResponse("Hello world")
get_view = login_required(get_view) get_view = login_required(get_view)
def view_with_argument(request, name):
"""A view that takes a string argument
The purpose of this view is to check that if a space is provided in
the argument, the test framework unescapes the %20 before passing
the value to the view.
"""
if name == 'Arthur Dent':
return HttpResponse('Hi, Arthur')
else:
return HttpResponse('Howdy, %s' % name)
def login_protected_redirect_view(request): def login_protected_redirect_view(request):
"A view that redirects all requests to the GET view" "A view that redirects all requests to the GET view"
return HttpResponseRedirect('/test_client_regress/get_view/') return HttpResponseRedirect('/test_client_regress/get_view/')

View File

@ -19,4 +19,7 @@ urlpatterns = patterns('',
(r'^middleware/', include('regressiontests.middleware.urls')), (r'^middleware/', include('regressiontests.middleware.urls')),
(r'^utils/', include('regressiontests.utils.urls')), (r'^utils/', include('regressiontests.utils.urls')),
# test urlconf for syndication tests
(r'^syndication/', include('regressiontests.syndication.urls')),
) )