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:
parent
b0a895f9e8
commit
2efc34dc5e
6
AUTHORS
6
AUTHORS
@ -59,6 +59,7 @@ answer newbie questions, and generally made Django that much better:
|
||||
Arthur <avandorp@gmail.com>
|
||||
David Avsajanishvili <avsd05@gmail.com>
|
||||
axiak@mit.edu
|
||||
Niran Babalola <niran@niran.org>
|
||||
Morten Bagai <m@bagai.com>
|
||||
Mikaël Barbero <mikael.barbero nospam at nospam free.fr>
|
||||
Jiri Barton
|
||||
@ -145,6 +146,7 @@ answer newbie questions, and generally made Django that much better:
|
||||
Jorge Gajon <gajon@gajon.org>
|
||||
gandalf@owca.info
|
||||
Marc Garcia <marc.garcia@accopensys.com>
|
||||
Alex Gaynor <alex.gaynor@gmail.com>
|
||||
Andy Gayton <andy-django@thecablelounge.com>
|
||||
Baishampayan Ghose
|
||||
Dimitris Glezos <dimitris@glezos.com>
|
||||
@ -243,6 +245,7 @@ answer newbie questions, and generally made Django that much better:
|
||||
michael.mcewan@gmail.com
|
||||
michal@plovarna.cz
|
||||
Mikko Hellsing <mikko@sorl.net>
|
||||
Daniel Lindsley <polarcowz@gmail.com>
|
||||
Orestis Markou <orestis@orestis.gr>
|
||||
Slawek Mikula <slawek dot mikula at gmail dot com>
|
||||
mitakummaa@gmail.com
|
||||
@ -256,6 +259,7 @@ answer newbie questions, and generally made Django that much better:
|
||||
Robin Munn <http://www.geekforgod.com/>
|
||||
Robert Myers <myer0052@gmail.com>
|
||||
Nebojša Dorđević
|
||||
Doug Napoleone <doug@dougma.com>
|
||||
Gopal Narayanan <gopastro@gmail.com>
|
||||
Fraser Nevett <mail@nevett.org>
|
||||
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>
|
||||
permonik@mesias.brnonet.cz
|
||||
petr.marhoun@gmail.com
|
||||
peter@mymart.com
|
||||
pgross@thoughtworks.com
|
||||
phaedo <http://phaedo.cx/>
|
||||
phil@produxion.net
|
||||
@ -307,6 +312,7 @@ answer newbie questions, and generally made Django that much better:
|
||||
serbaut@gmail.com
|
||||
John Shaffer <jshaffer2112@gmail.com>
|
||||
Pete Shinners <pete@shinners.org>
|
||||
Leo Shklovskii
|
||||
jason.sidabras@gmail.com
|
||||
Jozko Skrablin <jozko.skrablin@gmail.com>
|
||||
Ben Slavin <benjamin.slavin@gmail.com>
|
||||
|
@ -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_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_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 #
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,9 @@ class SessionStore(SessionBase):
|
||||
Implements a file based session store.
|
||||
"""
|
||||
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.
|
||||
if not os.path.isdir(self.storage_path):
|
||||
|
@ -4,6 +4,7 @@ from django.contrib.sites.models import Site, RequestSite
|
||||
from django.utils import feedgenerator
|
||||
from django.utils.encoding import smart_unicode, iri_to_uri
|
||||
from django.conf import settings
|
||||
from django.template import RequestContext
|
||||
|
||||
def add_domain(domain, url):
|
||||
if not (url.startswith('http://') or url.startswith('https://')):
|
||||
@ -55,18 +56,23 @@ class Feed(object):
|
||||
return attr()
|
||||
return attr
|
||||
|
||||
def get_object(self, bits):
|
||||
return None
|
||||
|
||||
def get_feed(self, url=None):
|
||||
"""
|
||||
Returns a feedgenerator.DefaultFeed object, fully populated, for
|
||||
this feed. Raises FeedDoesNotExist for invalid parameters.
|
||||
"""
|
||||
if url:
|
||||
try:
|
||||
obj = self.get_object(url.split('/'))
|
||||
except (AttributeError, ObjectDoesNotExist):
|
||||
raise FeedDoesNotExist
|
||||
bits = url.split('/')
|
||||
else:
|
||||
obj = None
|
||||
bits = []
|
||||
|
||||
try:
|
||||
obj = self.get_object(bits)
|
||||
except ObjectDoesNotExist:
|
||||
raise FeedDoesNotExist
|
||||
|
||||
if Site._meta.installed:
|
||||
current_site = Site.objects.get_current()
|
||||
@ -119,9 +125,9 @@ class Feed(object):
|
||||
else:
|
||||
author_email = author_link = None
|
||||
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,
|
||||
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),
|
||||
enclosure = enc,
|
||||
pubdate = self.__get_dynamic_attr('item_pubdate', item),
|
||||
|
@ -2,20 +2,21 @@
|
||||
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 os
|
||||
import smtplib
|
||||
import socket
|
||||
import time
|
||||
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
|
||||
# some spam filters.
|
||||
@ -38,8 +39,9 @@ class CachedDnsName(object):
|
||||
|
||||
DNS_NAME = CachedDnsName()
|
||||
|
||||
# Copied from Python standard library and modified to used the cached hostname
|
||||
# for performance.
|
||||
# Copied from Python standard library, with the following modifications:
|
||||
# * Used cached hostname for performance.
|
||||
# * Added try/except to support lack of getpid() in Jython (#5496).
|
||||
def make_msgid(idstring=None):
|
||||
"""Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
|
||||
|
||||
@ -53,7 +55,7 @@ def make_msgid(idstring=None):
|
||||
try:
|
||||
pid = os.getpid()
|
||||
except AttributeError:
|
||||
# Not getpid() in Jython, for example.
|
||||
# No getpid() in Jython, for example.
|
||||
pid = 1
|
||||
randint = random.randrange(100000)
|
||||
if idstring is None:
|
||||
@ -68,7 +70,7 @@ class BadHeaderError(ValueError):
|
||||
pass
|
||||
|
||||
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:
|
||||
raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
|
||||
try:
|
||||
@ -101,7 +103,7 @@ class SMTPConnection(object):
|
||||
"""
|
||||
|
||||
def __init__(self, host=None, port=None, username=None, password=None,
|
||||
use_tls=None, fail_silently=False):
|
||||
use_tls=None, fail_silently=False):
|
||||
self.host = host or settings.EMAIL_HOST
|
||||
self.port = port or settings.EMAIL_PORT
|
||||
self.username = username or settings.EMAIL_HOST_USER
|
||||
@ -112,14 +114,17 @@ class SMTPConnection(object):
|
||||
|
||||
def open(self):
|
||||
"""
|
||||
Ensure we have a connection to the email server. Returns whether or not
|
||||
a new connection was required.
|
||||
Ensures we have a connection to the email server. Returns whether or
|
||||
not a new connection was required (True or False).
|
||||
"""
|
||||
if self.connection:
|
||||
# Nothing to do if the connection is already open.
|
||||
return False
|
||||
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:
|
||||
self.connection.ehlo()
|
||||
self.connection.starttls()
|
||||
@ -132,7 +137,7 @@ class SMTPConnection(object):
|
||||
raise
|
||||
|
||||
def close(self):
|
||||
"""Close the connection to the email server."""
|
||||
"""Closes the connection to the email server."""
|
||||
try:
|
||||
try:
|
||||
self.connection.quit()
|
||||
@ -149,7 +154,7 @@ class SMTPConnection(object):
|
||||
|
||||
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.
|
||||
"""
|
||||
if not email_messages:
|
||||
@ -192,7 +197,7 @@ class EmailMessage(object):
|
||||
def __init__(self, subject='', body='', from_email=None, to=None, bcc=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).
|
||||
|
||||
All strings used to create the message can be unicode strings (or UTF-8
|
||||
@ -221,7 +226,8 @@ class EmailMessage(object):
|
||||
|
||||
def message(self):
|
||||
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:
|
||||
body_msg = msg
|
||||
msg = SafeMIMEMultipart(_subtype=self.multipart_subtype)
|
||||
@ -237,8 +243,6 @@ class EmailMessage(object):
|
||||
msg['To'] = ', '.join(self.to)
|
||||
msg['Date'] = formatdate()
|
||||
msg['Message-ID'] = make_msgid()
|
||||
if self.bcc:
|
||||
msg['Bcc'] = ', '.join(self.bcc)
|
||||
for name, value in self.extra_headers.items():
|
||||
msg[name] = value
|
||||
return msg
|
||||
@ -251,7 +255,7 @@ class EmailMessage(object):
|
||||
return self.to + self.bcc
|
||||
|
||||
def send(self, fail_silently=False):
|
||||
"""Send the email message."""
|
||||
"""Sends the email message."""
|
||||
return self.get_connection(fail_silently).send_messages([self])
|
||||
|
||||
def attach(self, filename=None, content=None, mimetype=None):
|
||||
@ -278,7 +282,7 @@ class EmailMessage(object):
|
||||
|
||||
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.
|
||||
"""
|
||||
if mimetype is None:
|
||||
@ -295,7 +299,8 @@ class EmailMessage(object):
|
||||
attachment.set_payload(content)
|
||||
Encoders.encode_base64(attachment)
|
||||
if filename:
|
||||
attachment.add_header('Content-Disposition', 'attachment', filename=filename)
|
||||
attachment.add_header('Content-Disposition', 'attachment',
|
||||
filename=filename)
|
||||
return attachment
|
||||
|
||||
class EmailMultiAlternatives(EmailMessage):
|
||||
@ -310,7 +315,8 @@ class EmailMultiAlternatives(EmailMessage):
|
||||
"""Attach an alternative content representation."""
|
||||
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
|
||||
of the recipient list will see the other recipients in the 'To' field.
|
||||
@ -322,10 +328,12 @@ def send_mail(subject, message, from_email, recipient_list, fail_silently=False,
|
||||
functionality should use the EmailMessage class directly.
|
||||
"""
|
||||
connection = SMTPConnection(username=auth_user, password=auth_password,
|
||||
fail_silently=fail_silently)
|
||||
return EmailMessage(subject, message, from_email, recipient_list, connection=connection).send()
|
||||
fail_silently=fail_silently)
|
||||
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
|
||||
each message to each recipient list. Returns the number of e-mails sent.
|
||||
@ -339,19 +347,19 @@ def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password
|
||||
functionality should use the EmailMessage class directly.
|
||||
"""
|
||||
connection = SMTPConnection(username=auth_user, password=auth_password,
|
||||
fail_silently=fail_silently)
|
||||
messages = [EmailMessage(subject, message, sender, recipient) for subject, message, sender, recipient in datatuple]
|
||||
fail_silently=fail_silently)
|
||||
messages = [EmailMessage(subject, message, sender, recipient)
|
||||
for subject, message, sender, recipient in datatuple]
|
||||
return connection.send_messages(messages)
|
||||
|
||||
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,
|
||||
settings.SERVER_EMAIL, [a[1] for a in
|
||||
settings.ADMINS]).send(fail_silently=fail_silently)
|
||||
settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]
|
||||
).send(fail_silently=fail_silently)
|
||||
|
||||
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,
|
||||
settings.SERVER_EMAIL, [a[1] for a in
|
||||
settings.MANAGERS]).send(fail_silently=fail_silently)
|
||||
|
||||
settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
|
||||
).send(fail_silently=fail_silently)
|
||||
|
@ -243,7 +243,7 @@ def setup_environ(settings_mod):
|
||||
# way. For example, if this file (manage.py) lives in a directory
|
||||
# "myproject", this code would add "/path/to/myproject" to sys.path.
|
||||
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_name = os.path.basename(project_directory)
|
||||
settings_name = os.path.splitext(settings_filename)[0]
|
||||
|
@ -20,8 +20,13 @@ class Command(LabelCommand):
|
||||
# the parent directory.
|
||||
directory = os.getcwd()
|
||||
|
||||
if project_name in INVALID_PROJECT_NAMES:
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
|
||||
|
@ -1,46 +1,149 @@
|
||||
class InvalidPage(Exception):
|
||||
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):
|
||||
"""
|
||||
Returns a 1-based range of pages for iterating through within
|
||||
a template for loop.
|
||||
"""
|
||||
return range(1, self.num_pages + 1)
|
||||
page_range = property(_get_page_range)
|
||||
|
||||
class QuerySetPaginator(Paginator):
|
||||
"""
|
||||
This class makes pagination easy. Feed it a QuerySet or list, plus the number
|
||||
of objects you want on each page. Then read the hits and pages properties to
|
||||
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.
|
||||
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)
|
||||
|
||||
Finally, check if a page number has a next/prev page using
|
||||
has_next_page(page_number) and has_previous_page(page_number).
|
||||
class Page(object):
|
||||
def __init__(self, object_list, number, paginator):
|
||||
self.object_list = object_list
|
||||
self.number = number
|
||||
self.paginator = paginator
|
||||
|
||||
Use orphans to avoid small final pages. For example:
|
||||
13 records, num_per_page=10, orphans=2 --> pages==2, len(self.get_page(0))==10
|
||||
12 records, num_per_page=10, orphans=2 --> pages==1, len(self.get_page(0))==12
|
||||
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):
|
||||
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.num_per_page = num_per_page
|
||||
self.orphans = orphans
|
||||
self._hits = self._pages = None
|
||||
self._page_range = None
|
||||
|
||||
def validate_page_number(self, page_number):
|
||||
try:
|
||||
page_number = int(page_number)
|
||||
page_number = int(page_number) + 1
|
||||
except ValueError:
|
||||
raise InvalidPage
|
||||
if page_number < 0 or page_number > self.pages - 1:
|
||||
raise InvalidPage
|
||||
return page_number
|
||||
return self.validate_number(page_number)
|
||||
|
||||
def get_page(self, page_number):
|
||||
page_number = self.validate_page_number(page_number)
|
||||
bottom = page_number * self.num_per_page
|
||||
top = bottom + self.num_per_page
|
||||
if top + self.orphans >= self.hits:
|
||||
top = self.hits
|
||||
return self.query_set[bottom:top]
|
||||
try:
|
||||
page_number = int(page_number) + 1
|
||||
except ValueError:
|
||||
raise InvalidPage
|
||||
return self.page(page_number).object_list
|
||||
|
||||
def has_next_page(self, page_number):
|
||||
"Does page $page_number have a 'next' page?"
|
||||
return page_number < self.pages - 1
|
||||
|
||||
def has_previous_page(self, page_number):
|
||||
@ -52,7 +155,7 @@ class ObjectPaginator(object):
|
||||
relative to total objects found (hits).
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
@ -60,40 +163,23 @@ class ObjectPaginator(object):
|
||||
relative to total objects found (hits).
|
||||
"""
|
||||
page_number = self.validate_page_number(page_number)
|
||||
page_number += 1 # 1-base
|
||||
if page_number == self.pages:
|
||||
return self.hits
|
||||
if page_number == self.num_pages:
|
||||
return self.count
|
||||
return page_number * self.num_per_page
|
||||
|
||||
def _get_hits(self):
|
||||
if self._hits is None:
|
||||
# Try .count() or fall back to len().
|
||||
def _get_count(self):
|
||||
# The old API allowed for self.object_list to be either a QuerySet or a
|
||||
# list. Here, we handle both.
|
||||
if self._count is None:
|
||||
try:
|
||||
self._hits = int(self.query_set.count())
|
||||
except (AttributeError, TypeError, ValueError):
|
||||
# AttributeError if query_set has no object count.
|
||||
# TypeError if query_set.count() required arguments.
|
||||
# ValueError if int() fails.
|
||||
self._hits = len(self.query_set)
|
||||
return self._hits
|
||||
self._count = self.object_list.count()
|
||||
except TypeError:
|
||||
self._count = len(self.object_list)
|
||||
return self._count
|
||||
count = property(_get_count)
|
||||
|
||||
def _get_pages(self):
|
||||
if self._pages is None:
|
||||
hits = (self.hits - 1 - self.orphans)
|
||||
if hits < 1:
|
||||
hits = 0
|
||||
self._pages = hits // self.num_per_page + 1
|
||||
return self._pages
|
||||
# The old API called it "hits" instead of "count".
|
||||
hits = count
|
||||
|
||||
def _get_page_range(self):
|
||||
"""
|
||||
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)
|
||||
# The old API called it "pages" instead of "num_pages".
|
||||
pages = Paginator.num_pages
|
||||
|
@ -60,8 +60,6 @@ class Serializer(object):
|
||||
"""
|
||||
if isinstance(field, models.DateTimeField):
|
||||
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:
|
||||
value = field.flatten_data(follow=None, obj=obj).get(field.name, "")
|
||||
return smart_unicode(value)
|
||||
|
@ -37,7 +37,9 @@ Optional Fcgi settings: (setting=value)
|
||||
maxchildren=NUMBER hard limit number of processes / threads
|
||||
daemonize=BOOL whether to detach from terminal.
|
||||
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:
|
||||
Run a "standard" fastcgi process on a file-descriptor
|
||||
@ -69,6 +71,8 @@ FASTCGI_OPTIONS = {
|
||||
'minspare': 2,
|
||||
'maxchildren': 50,
|
||||
'maxrequests': 0,
|
||||
'outlog': None,
|
||||
'errlog': None,
|
||||
}
|
||||
|
||||
def fastcgi_help(message=None):
|
||||
@ -150,9 +154,15 @@ def runfastcgi(argset=[], **kwargs):
|
||||
else:
|
||||
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:
|
||||
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"]:
|
||||
fp = open(options["pidfile"], "w")
|
||||
|
@ -847,6 +847,16 @@ class FilePathField(Field):
|
||||
kwargs['max_length'] = kwargs.get('max_length', 100)
|
||||
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):
|
||||
return [curry(oldforms.FilePathField, path=self.path, match=self.match, recursive=self.recursive)]
|
||||
|
||||
|
@ -548,6 +548,13 @@ class ForeignKey(RelatedField, Field):
|
||||
params['choices'] = self.get_choices_default()
|
||||
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):
|
||||
rel_field = self.rel.get_related_field()
|
||||
if self.rel.raw_id_admin and not isinstance(rel_field, AutoField):
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 import signals, loading
|
||||
from django.dispatch import dispatcher
|
||||
@ -285,11 +285,14 @@ class _QuerySet(object):
|
||||
try:
|
||||
return self.get(**kwargs), False
|
||||
except self.model.DoesNotExist:
|
||||
params = dict([(k, v) for k, v in kwargs.items() if '__' not in k])
|
||||
params.update(defaults)
|
||||
obj = self.model(**params)
|
||||
obj.save()
|
||||
return obj, True
|
||||
try:
|
||||
params = dict([(k, v) for k, v in kwargs.items() if '__' not in k])
|
||||
params.update(defaults)
|
||||
obj = self.model(**params)
|
||||
obj.save()
|
||||
return obj, True
|
||||
except IntegrityError, e:
|
||||
return self.get(**kwargs), False
|
||||
|
||||
def latest(self, field_name=None):
|
||||
"""
|
||||
|
@ -82,6 +82,9 @@ class HttpRequest(object):
|
||||
def is_secure(self):
|
||||
return os.environ.get("HTTPS") == "on"
|
||||
|
||||
def is_ajax(self):
|
||||
return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
|
||||
|
||||
def _set_encoding(self, val):
|
||||
"""
|
||||
Sets the encoding used for GET/POST accesses. If the GET or POST
|
||||
|
@ -3,6 +3,7 @@ Extra HTML Widget classes
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from django.newforms.widgets import Widget, Select
|
||||
from django.utils.dates import MONTHS
|
||||
@ -10,6 +11,8 @@ from django.utils.safestring import mark_safe
|
||||
|
||||
__all__ = ('SelectDateWidget',)
|
||||
|
||||
RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$')
|
||||
|
||||
class SelectDateWidget(Widget):
|
||||
"""
|
||||
A Widget that splits date input into three <select> boxes.
|
||||
@ -32,28 +35,43 @@ class SelectDateWidget(Widget):
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
try:
|
||||
value = datetime.date(*map(int, value.split('-')))
|
||||
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
|
||||
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 = []
|
||||
|
||||
if 'id' in self.attrs:
|
||||
id_ = self.attrs['id']
|
||||
else:
|
||||
id_ = 'id_%s' % name
|
||||
|
||||
month_choices = MONTHS.items()
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
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:
|
||||
|
@ -4,6 +4,7 @@ Field classes.
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
# Python 2.3 fallbacks
|
||||
@ -31,7 +32,7 @@ __all__ = (
|
||||
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
|
||||
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
|
||||
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
|
||||
'SplitDateTimeField', 'IPAddressField',
|
||||
'SplitDateTimeField', 'IPAddressField', 'FilePathField',
|
||||
)
|
||||
|
||||
# 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.')
|
||||
|
||||
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):
|
||||
default_error_messages = {
|
||||
'invalid_date': _(u'Enter a valid date.'),
|
||||
|
@ -277,19 +277,18 @@ class ModelForm(BaseModelForm):
|
||||
|
||||
# Fields #####################################################################
|
||||
|
||||
class QuerySetIterator(object):
|
||||
def __init__(self, queryset, empty_label, cache_choices):
|
||||
self.queryset = queryset
|
||||
self.empty_label = empty_label
|
||||
self.cache_choices = cache_choices
|
||||
class ModelChoiceIterator(object):
|
||||
def __init__(self, field):
|
||||
self.field = field
|
||||
self.queryset = field.queryset
|
||||
|
||||
def __iter__(self):
|
||||
if self.empty_label is not None:
|
||||
yield (u"", self.empty_label)
|
||||
if self.field.empty_label is not None:
|
||||
yield (u"", self.field.empty_label)
|
||||
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.
|
||||
if not self.cache_choices:
|
||||
if not self.field.cache_choices:
|
||||
self.queryset._result_cache = None
|
||||
|
||||
class ModelChoiceField(ChoiceField):
|
||||
@ -306,6 +305,7 @@ class ModelChoiceField(ChoiceField):
|
||||
help_text=None, *args, **kwargs):
|
||||
self.empty_label = empty_label
|
||||
self.cache_choices = cache_choices
|
||||
|
||||
# Call Field instead of ChoiceField __init__() because we don't need
|
||||
# ChoiceField.__init__().
|
||||
Field.__init__(self, required, widget, label, initial, help_text,
|
||||
@ -321,19 +321,30 @@ class ModelChoiceField(ChoiceField):
|
||||
|
||||
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):
|
||||
# If self._choices is set, then somebody must have manually set
|
||||
# the property self.choices. In this case, just return self._choices.
|
||||
if hasattr(self, '_choices'):
|
||||
return self._choices
|
||||
|
||||
# Otherwise, execute the QuerySet in self.queryset to determine the
|
||||
# choices dynamically. Return a fresh QuerySetIterator that has not
|
||||
# been consumed. Note that we're instantiating a new QuerySetIterator
|
||||
# *each* time _get_choices() is called (and, thus, each time
|
||||
# self.choices is accessed) so that we can ensure the QuerySet has not
|
||||
# been consumed.
|
||||
return QuerySetIterator(self.queryset, self.empty_label,
|
||||
self.cache_choices)
|
||||
# choices dynamically. Return a fresh QuerySetIterator that has not been
|
||||
# consumed. Note that we're instantiating a new QuerySetIterator *each*
|
||||
# time _get_choices() is called (and, thus, each time self.choices is
|
||||
# accessed) so that we can ensure the QuerySet has not been consumed. This
|
||||
# construct might look complicated but it allows for lazy evaluation of
|
||||
# the queryset.
|
||||
return ModelChoiceIterator(self)
|
||||
|
||||
def _set_choices(self, value):
|
||||
# This method is copied from ChoiceField._set_choices(). It's necessary
|
||||
|
@ -124,7 +124,10 @@ def floatformat(text, arg=-1):
|
||||
d = int(arg)
|
||||
except ValueError:
|
||||
return force_unicode(f)
|
||||
m = f - int(f)
|
||||
try:
|
||||
m = f - int(f)
|
||||
except OverflowError:
|
||||
return force_unicode(f)
|
||||
if not m and d < 0:
|
||||
return mark_safe(u'%d' % int(f))
|
||||
else:
|
||||
|
@ -1,3 +1,4 @@
|
||||
import urllib
|
||||
import sys
|
||||
from cStringIO import StringIO
|
||||
from django.conf import settings
|
||||
@ -208,7 +209,7 @@ class Client:
|
||||
r = {
|
||||
'CONTENT_LENGTH': None,
|
||||
'CONTENT_TYPE': 'text/html; charset=utf-8',
|
||||
'PATH_INFO': path,
|
||||
'PATH_INFO': urllib.unquote(path),
|
||||
'QUERY_STRING': urlencode(data, doseq=True),
|
||||
'REQUEST_METHOD': 'GET',
|
||||
}
|
||||
@ -227,7 +228,7 @@ class Client:
|
||||
r = {
|
||||
'CONTENT_LENGTH': len(post_data),
|
||||
'CONTENT_TYPE': content_type,
|
||||
'PATH_INFO': path,
|
||||
'PATH_INFO': urllib.unquote(path),
|
||||
'REQUEST_METHOD': 'POST',
|
||||
'wsgi.input': StringIO(post_data),
|
||||
}
|
||||
|
@ -18,10 +18,10 @@ if os.name == 'posix':
|
||||
# Second fork
|
||||
try:
|
||||
if os.fork() > 0:
|
||||
sys.exit(0)
|
||||
os._exit(0)
|
||||
except OSError, e:
|
||||
sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
|
||||
sys.exit(1)
|
||||
os._exit(1)
|
||||
|
||||
si = open('/dev/null', 'r')
|
||||
so = open(out_log, 'a+', 0)
|
||||
@ -29,6 +29,8 @@ if os.name == 'posix':
|
||||
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||
os.dup2(so.fileno(), sys.stdout.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:
|
||||
def become_daemon(our_home_dir='.', out_log=None, err_log=None):
|
||||
"""
|
||||
|
@ -1,11 +1,12 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.template import Template, Context, TemplateDoesNotExist
|
||||
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
|
||||
|
||||
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
|
||||
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_does_not_exist = False
|
||||
loader_debug_info = None
|
||||
@ -153,13 +159,14 @@ def technical_500_response(request, exc_type, exc_value, tb):
|
||||
'settings': get_safe_settings(),
|
||||
'sys_executable': sys.executable,
|
||||
'sys_version_info': '%d.%d.%d' % sys.version_info[0:3],
|
||||
'server_time': datetime.datetime.now(),
|
||||
'django_version_info': get_version(),
|
||||
'sys_path' : sys.path,
|
||||
'template_info': template_info,
|
||||
'template_does_not_exist': template_does_not_exist,
|
||||
'loader_debug_info': loader_debug_info,
|
||||
})
|
||||
return HttpResponseServerError(t.render(c), mimetype='text/html')
|
||||
return t.render(c)
|
||||
|
||||
def technical_404_response(request, exception):
|
||||
"Create a technical 404 error response. The exception should be the Http404."
|
||||
@ -190,7 +197,7 @@ def empty_urlconf(request):
|
||||
c = Context({
|
||||
'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):
|
||||
"""
|
||||
@ -384,6 +391,10 @@ TECHNICAL_500_TEMPLATE = """
|
||||
<th>Python Path:</th>
|
||||
<td>{{ sys_path }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Server time:</th>
|
||||
<td>{{server_time|date:"r"}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% if unicode_hint %}
|
||||
|
@ -1,7 +1,7 @@
|
||||
from django.template import loader, RequestContext
|
||||
from django.http import Http404, HttpResponse
|
||||
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
|
||||
|
||||
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 = {}
|
||||
queryset = queryset._clone()
|
||||
if paginate_by:
|
||||
paginator = ObjectPaginator(queryset, paginate_by)
|
||||
paginator = QuerySetPaginator(queryset, paginate_by, allow_empty_first_page=allow_empty)
|
||||
if not page:
|
||||
page = request.GET.get('page', 1)
|
||||
try:
|
||||
page_number = int(page)
|
||||
except ValueError:
|
||||
if page == 'last':
|
||||
page_number = paginator.pages
|
||||
page_number = paginator.num_pages
|
||||
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
|
||||
try:
|
||||
object_list = paginator.get_page(page_number - 1)
|
||||
page_obj = paginator.page(page_number)
|
||||
except InvalidPage:
|
||||
if page_number == 1 and allow_empty:
|
||||
object_list = []
|
||||
else:
|
||||
raise Http404
|
||||
raise Http404
|
||||
c = RequestContext(request, {
|
||||
'%s_list' % template_object_name: object_list,
|
||||
'is_paginated': paginator.pages > 1,
|
||||
'results_per_page': paginate_by,
|
||||
'has_next': paginator.has_next_page(page_number - 1),
|
||||
'has_previous': paginator.has_previous_page(page_number - 1),
|
||||
'page': page_number,
|
||||
'next': page_number + 1,
|
||||
'previous': page_number - 1,
|
||||
'last_on_page': paginator.last_on_page(page_number - 1),
|
||||
'first_on_page': paginator.first_on_page(page_number - 1),
|
||||
'pages': paginator.pages,
|
||||
'hits' : paginator.hits,
|
||||
'page_range' : paginator.page_range
|
||||
'%s_list' % template_object_name: page_obj.object_list,
|
||||
'paginator': paginator,
|
||||
'page_obj': page_obj,
|
||||
|
||||
# Legacy template context stuff. New templates should use page_obj
|
||||
# to access this instead.
|
||||
'is_paginated': page_obj.has_other_pages(),
|
||||
'results_per_page': paginator.per_page,
|
||||
'has_next': page_obj.has_next(),
|
||||
'has_previous': page_obj.has_previous(),
|
||||
'page': page_obj.number,
|
||||
'next': page_obj.next_page_number(),
|
||||
'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)
|
||||
else:
|
||||
c = RequestContext(request, {
|
||||
'%s_list' % template_object_name: queryset,
|
||||
'is_paginated': False
|
||||
'paginator': None,
|
||||
'page_obj': None,
|
||||
'is_paginated': False,
|
||||
}, context_processors)
|
||||
if not allow_empty and len(queryset) == 0:
|
||||
raise Http404
|
||||
|
@ -401,8 +401,8 @@ For example::
|
||||
# ...
|
||||
|
||||
def get_db_prep_save(self, value):
|
||||
return ''.join([''.join(l) for l in (self.north,
|
||||
self.east, self.south, self.west)])
|
||||
return ''.join([''.join(l) for l in (value.north,
|
||||
value.east, value.south, value.west)])
|
||||
|
||||
``pre_save(self, model_instance, add)``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -1372,7 +1372,7 @@ equivalent::
|
||||
Entry.objects.filter(blog__pk=3) # __pk implies __id__exact
|
||||
|
||||
Lookups that span relationships
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
-------------------------------
|
||||
|
||||
Django offers a powerful and intuitive way to "follow" relationships in
|
||||
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')
|
||||
|
||||
Escaping percent signs and underscores in LIKE statements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
---------------------------------------------------------
|
||||
|
||||
The field lookups that equate to ``LIKE`` SQL statements (``iexact``,
|
||||
``contains``, ``icontains``, ``startswith``, ``istartswith``, ``endswith``
|
||||
|
@ -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:
|
||||
|
||||
* **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
|
||||
``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
|
||||
page.
|
||||
|
||||
* **New in Django development version:** ``page_range``: A list of the
|
||||
page numbers that are available. This is 1-based.
|
||||
* ``page_range``: A list of the page numbers that are available. This is
|
||||
1-based.
|
||||
|
||||
Notes on pagination
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -167,6 +167,20 @@ These commands will install Django in your Python installation's
|
||||
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
|
||||
latest bug fixes and improvements, follow these instructions:
|
||||
|
||||
|
@ -626,7 +626,8 @@ option is ignored.
|
||||
``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``
|
||||
~~~~~~~~~~~~
|
||||
@ -1788,14 +1789,15 @@ For example::
|
||||
This example allows you to request ``Person.men.all()``, ``Person.women.all()``,
|
||||
and ``Person.people.all()``, yielding predictable results.
|
||||
|
||||
If you use custom ``Manager`` objects, take note that the first ``Manager``
|
||||
Django encounters (in order by which they're defined in the model) has a
|
||||
special status. Django interprets the first ``Manager`` defined in a class as
|
||||
the "default" ``Manager``. Certain operations -- such as Django's admin site --
|
||||
use the default ``Manager`` to obtain lists of objects, so it's generally a
|
||||
good idea for the first ``Manager`` to be relatively unfiltered. In the last
|
||||
example, the ``people`` ``Manager`` is defined first -- so it's the default
|
||||
``Manager``.
|
||||
If you use custom ``Manager`` objects, take note that the first
|
||||
``Manager`` Django encounters (in the order in which they're defined
|
||||
in the model) has a special status. Django interprets this first
|
||||
``Manager`` defined in a class as the "default" ``Manager``, and
|
||||
several parts of Django (though not the admin application) will use
|
||||
that ``Manager`` exclusively for that model. As a result, it's often a
|
||||
good idea to be careful in your choice of default manager, in order to
|
||||
avoid a situation where overriding of ``get_query_set()`` results in
|
||||
an inability to retrieve objects you'd like to work with.
|
||||
|
||||
Model methods
|
||||
=============
|
||||
|
@ -231,6 +231,16 @@ For example::
|
||||
# Create and save the new author instance. There's no need to do anything else.
|
||||
>>> 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
|
||||
------------------------------------
|
||||
|
||||
|
@ -1333,13 +1333,14 @@ given length.
|
||||
|
||||
An ``UploadedFile`` object has two attributes:
|
||||
|
||||
====================== =====================================================
|
||||
Argument Description
|
||||
====================== =====================================================
|
||||
====================== ====================================================
|
||||
Attribute Description
|
||||
====================== ====================================================
|
||||
``filename`` The name of the file, provided by the uploading
|
||||
client.
|
||||
|
||||
``content`` The array of bytes comprising the file content.
|
||||
====================== =====================================================
|
||||
====================== ====================================================
|
||||
|
||||
The string representation of an ``UploadedFile`` is the same as the filename
|
||||
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`_
|
||||
|
||||
``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``
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
@ -1499,6 +1532,41 @@ the bottom of this document, for examples of their use.
|
||||
``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
|
||||
----------------------
|
||||
|
||||
@ -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,
|
||||
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
|
||||
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
|
||||
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()
|
||||
|
||||
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::
|
||||
|
||||
>>> f = CommentForm(auto_id=False)
|
||||
|
133
docs/pagination.txt
Normal file
133
docs/pagination.txt
Normal 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.
|
@ -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
|
||||
``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
|
||||
-------
|
||||
|
||||
@ -189,6 +199,23 @@ Methods
|
||||
Returns ``True`` if the request is secure; that is, if it was made with
|
||||
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
|
||||
-----------------
|
||||
|
||||
|
@ -48,10 +48,10 @@ Using file-based sessions
|
||||
To use file-based sessions, set the ``SESSION_ENGINE`` setting to
|
||||
``"django.contrib.sessions.backends.file"``.
|
||||
|
||||
You might also want to set the ``SESSION_FILE_PATH`` setting (which
|
||||
defaults to ``/tmp``) to control where Django stores session files. Be
|
||||
sure to check that your Web server has permissions to read and write to
|
||||
this location.
|
||||
You might also want to set the ``SESSION_FILE_PATH`` setting (which defaults
|
||||
to output from ``tempfile.gettempdir()``, most likely ``/tmp``) to control
|
||||
where Django stores session files. Be sure to check that your Web server has
|
||||
permissions to read and write to this location.
|
||||
|
||||
Using cache-based sessions
|
||||
--------------------------
|
||||
|
@ -185,8 +185,11 @@ ADMIN_MEDIA_PREFIX
|
||||
|
||||
Default: ``'/media/'``
|
||||
|
||||
The URL prefix for admin media -- CSS, JavaScript and images. Make sure to use
|
||||
a trailing slash.
|
||||
The URL prefix for admin media -- CSS, JavaScript and images used by
|
||||
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
|
||||
------
|
||||
@ -754,7 +757,9 @@ ROOT_URLCONF
|
||||
Default: Not defined
|
||||
|
||||
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
|
||||
|
||||
|
@ -245,6 +245,13 @@ request to the URL ``/rss/beats/0613/``:
|
||||
subclass of ``ObjectDoesNotExist``. Raising ``ObjectDoesNotExist`` in
|
||||
``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>``,
|
||||
Django uses the ``title()``, ``link()`` and ``description()`` methods. In
|
||||
the previous example, they were simple string class attributes, but this
|
||||
|
@ -429,8 +429,9 @@ all block tags. For example::
|
||||
# base.html
|
||||
|
||||
{% autoescape off %}
|
||||
<h1>{% block title %}</h1>
|
||||
<h1>{% block title %}{% endblock %}</h1>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
{% endautoescape %}
|
||||
|
||||
|
||||
@ -438,10 +439,11 @@ all block tags. For example::
|
||||
|
||||
{% extends "base.html" %}
|
||||
{% 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
|
||||
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>
|
||||
<b>Hello!</b>
|
||||
@ -1222,20 +1224,20 @@ Built-in filter reference
|
||||
add
|
||||
~~~
|
||||
|
||||
Adds the arg to the value.
|
||||
Adds the argument to the value.
|
||||
|
||||
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
|
||||
~~~~~~~~~~
|
||||
|
||||
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.
|
||||
|
||||
capfirst
|
||||
@ -1257,7 +1259,7 @@ For example::
|
||||
|
||||
{{ 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
|
||||
~~~~
|
||||
@ -1268,35 +1270,40 @@ For example::
|
||||
|
||||
{{ value|date:"D d M Y" }}
|
||||
|
||||
If ``value`` is a datetime object (ie. datetime.datetime.now()), the output
|
||||
would be formatted like ``Wed 09 Jan 2008``.
|
||||
If ``value`` is a ``datetime`` object (e.g., the result of
|
||||
``datetime.datetime.now()``), the output will be the string
|
||||
``'Wed 09 Jan 2008'``.
|
||||
|
||||
default
|
||||
~~~~~~~
|
||||
|
||||
If value is unavailable, use given default.
|
||||
If value evaluates to ``False``, use given default. Otherwise, use the value.
|
||||
|
||||
For example::
|
||||
|
||||
{{ 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
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
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::
|
||||
|
||||
{{ 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
|
||||
~~~~~~~~
|
||||
|
||||
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.
|
||||
|
||||
For example::
|
||||
@ -1306,7 +1313,7 @@ For example::
|
||||
If ``value`` is::
|
||||
|
||||
[
|
||||
{'name': 'zed', 'age': 19}
|
||||
{'name': 'zed', 'age': 19},
|
||||
{'name': 'amy', 'age': 22},
|
||||
{'name': 'joe', 'age': 31},
|
||||
]
|
||||
@ -1316,34 +1323,30 @@ then the output would be::
|
||||
[
|
||||
{'name': 'amy', 'age': 22},
|
||||
{'name': 'joe', 'age': 31},
|
||||
{'name': 'zed', 'age': 19}
|
||||
{'name': 'zed', 'age': 19},
|
||||
]
|
||||
|
||||
dictsortreversed
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Takes a list of dictionaries, returns that list sorted in reverse order by the
|
||||
key given in the argument. This works exactly the same as the above filter, but
|
||||
the returned value will be in reverse order.
|
||||
Takes a list of dictionaries and returns that list sorted in reverse order by
|
||||
the key given in the argument. This works exactly the same as the above filter,
|
||||
but the returned value will be in reverse order.
|
||||
|
||||
divisibleby
|
||||
~~~~~~~~~~~
|
||||
|
||||
Returns true if the value is divisible by the argument.
|
||||
Returns ``True`` if the value is divisible by the argument.
|
||||
|
||||
For example::
|
||||
|
||||
{{ value|divisibleby:3 }}
|
||||
{{ value|divisibleby:"3" }}
|
||||
|
||||
If ``value`` is ``21``, the output would be ``True``.
|
||||
|
||||
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:
|
||||
|
||||
* ``<`` is converted to ``<``
|
||||
@ -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
|
||||
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
|
||||
~~~~~~~~
|
||||
|
||||
@ -1392,7 +1399,7 @@ For example::
|
||||
|
||||
{{ 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
|
||||
~~~~~~~~~~~~~~
|
||||
@ -1403,11 +1410,11 @@ For example::
|
||||
|
||||
{{ value|fix_ampersands }}
|
||||
|
||||
If ``value`` is ``Tom & Jerry``, the output would be ``Tom & Jerry``.
|
||||
If ``value`` is ``Tom & Jerry``, the output will be ``Tom & Jerry``.
|
||||
|
||||
**New in Django development version**: you probably don't need to use this
|
||||
filter since ampersands will be automatically escaped. See escape_ for more on
|
||||
how auto-escaping works.
|
||||
**New in Django development version**: This filter generally is no longer
|
||||
useful, because ampersands are automatically escaped in templates. See escape_
|
||||
for more on how auto-escaping works.
|
||||
|
||||
floatformat
|
||||
~~~~~~~~~~~
|
||||
@ -1463,16 +1470,16 @@ filter.
|
||||
get_digit
|
||||
~~~~~~~~~
|
||||
|
||||
Given a whole number, returns the requested digit of it, where 1 is the
|
||||
right-most digit, 2 is the second-right-most digit, etc. Returns the original
|
||||
value for invalid input (if input or argument is not an integer, or if argument
|
||||
is less than 1). Otherwise, output is always an integer.
|
||||
Given a whole number, returns the requested digit, where 1 is the right-most
|
||||
digit, 2 is the second-right-most digit, etc. Returns the original value for
|
||||
invalid input (if input or argument is not an integer, or if argument is less
|
||||
than 1). Otherwise, output is always an integer.
|
||||
|
||||
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
|
||||
~~~~~~~~~
|
||||
@ -1493,7 +1500,8 @@ For example::
|
||||
|
||||
{{ 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
|
||||
~~~~
|
||||
@ -1506,29 +1514,30 @@ For example::
|
||||
|
||||
{{ 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
|
||||
~~~~~~
|
||||
|
||||
Returns the length of the value. Useful for lists.
|
||||
Returns the length of the value. This works for both strings and lists.
|
||||
|
||||
For example::
|
||||
|
||||
{{ 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
|
||||
~~~~~~~~~
|
||||
|
||||
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::
|
||||
|
||||
{{ 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
|
||||
~~~~~~~~~~
|
||||
@ -1541,7 +1550,7 @@ For example::
|
||||
|
||||
{{ 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>``.
|
||||
|
||||
linebreaksbr
|
||||
@ -1571,7 +1580,7 @@ For example::
|
||||
|
||||
{{ 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
|
||||
~~~~~~~~~
|
||||
@ -1583,8 +1592,9 @@ For example::
|
||||
|
||||
{{ value|make_list }}
|
||||
|
||||
If ``value`` is "Joe", the output would be ``[u'J', u'o', u'e']. If ``value`` is
|
||||
123, the output would be ``[1, 2, 3]``.
|
||||
If ``value`` is the string ``"Joe"``, the output would be the list
|
||||
``[u'J', u'o', u'e']``. If ``value`` is ``123``, the output will be the list
|
||||
``[1, 2, 3]``.
|
||||
|
||||
phone2numeric
|
||||
~~~~~~~~~~~~~
|
||||
@ -1629,25 +1639,25 @@ __ http://www.python.org/doc/2.5/lib/module-pprint.html
|
||||
random
|
||||
~~~~~~
|
||||
|
||||
Returns a random item from the list.
|
||||
Returns a random item from the given list.
|
||||
|
||||
For example::
|
||||
|
||||
{{ 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
|
||||
~~~~~~~~~~
|
||||
|
||||
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::
|
||||
|
||||
{{ value|removetags:"b span"|safe }}
|
||||
|
||||
If ``value`` is ``<b>Joel</b> <button>is</button> a <span>slug</span>`` the
|
||||
output would be ``Joel <button>is</button> a slug``.
|
||||
If ``value`` is ``"<b>Joel</b> <button>is</button> a <span>slug</span>"`` the
|
||||
output will be ``"Joel <button>is</button> a slug"``.
|
||||
|
||||
rjust
|
||||
~~~~~
|
||||
@ -1684,7 +1694,7 @@ For example::
|
||||
|
||||
{{ 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
|
||||
~~~~~~~~~~~~
|
||||
@ -1700,7 +1710,7 @@ For example::
|
||||
|
||||
{{ 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
|
||||
~~~~~~~~~
|
||||
@ -1711,8 +1721,8 @@ For example::
|
||||
|
||||
{{ value|striptags }}
|
||||
|
||||
If ``value`` is ``<b>Joel</b> <button>is</button> a <span>slug</span>`` the
|
||||
output would be ``Joel is a slug``.
|
||||
If ``value`` is ``"<b>Joel</b> <button>is</button> a <span>slug</span>"``, the
|
||||
output will be ``"Joel is a slug"``.
|
||||
|
||||
time
|
||||
~~~~
|
||||
@ -1726,13 +1736,13 @@ For example::
|
||||
|
||||
{{ value|time:"H:i" }}
|
||||
|
||||
If ``value`` is ``datetime.datetime.now()``, the output would be formatted
|
||||
like ``01:23``.
|
||||
If ``value`` is equivalent to ``datetime.datetime.now()``, the output will be
|
||||
the string ``"01:23"``.
|
||||
|
||||
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
|
||||
the comparison point (without the argument, the comparison point is *now*).
|
||||
@ -1774,7 +1784,7 @@ For example::
|
||||
|
||||
{{ 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
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
@ -1792,10 +1802,8 @@ unordered_list
|
||||
Recursively takes a self-nested list and returns an HTML unordered list --
|
||||
WITHOUT opening and closing <ul> tags.
|
||||
|
||||
**Changed in Django development version**
|
||||
|
||||
The format accepted by ``unordered_list`` has changed to an easier to
|
||||
understand format.
|
||||
**New in Django development version:** The format accepted by
|
||||
``unordered_list`` has changed to be easier to understand.
|
||||
|
||||
The list is assumed to be in the proper format. For example, if ``var`` contains
|
||||
``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``, then
|
||||
@ -1825,7 +1833,7 @@ For example::
|
||||
|
||||
{{ 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
|
||||
~~~~~~~~~
|
||||
@ -1844,9 +1852,9 @@ For example::
|
||||
|
||||
{{ value|urlize }}
|
||||
|
||||
If ``value`` is ``Check out www.djangoproject.com``, the output would be
|
||||
``Check out <a
|
||||
href="http://www.djangoproject.com">www.djangoproject.com</a>``.
|
||||
If ``value`` is ``"Check out www.djangoproject.com"``, the output will be
|
||||
``"Check out <a
|
||||
href="http://www.djangoproject.com">www.djangoproject.com</a>"``.
|
||||
|
||||
urlizetrunc
|
||||
~~~~~~~~~~~
|
||||
@ -1862,9 +1870,9 @@ For example::
|
||||
|
||||
{{ value|urlizetrunc:15 }}
|
||||
|
||||
If ``value`` is ``Check out www.djangoproject.com``, the output would be
|
||||
``Check out <a
|
||||
href="http://www.djangoproject.com">www.djangopr...</a>``.
|
||||
If ``value`` is ``"Check out www.djangoproject.com"``, the output would be
|
||||
``'Check out <a
|
||||
href="http://www.djangoproject.com">www.djangopr...</a>'``.
|
||||
|
||||
wordcount
|
||||
~~~~~~~~~
|
||||
|
@ -38,6 +38,12 @@ A quick rundown:
|
||||
data server-side, use ``method="post"``. This tip isn't specific to
|
||||
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
|
||||
something with it. Remember, in `Tutorial 3`_, we created a URLconf for the
|
||||
polls application that includes this line::
|
||||
|
@ -32,9 +32,11 @@ How Django processes a request
|
||||
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:
|
||||
|
||||
1. Django looks at the ``ROOT_URLCONF`` setting in your `settings file`_.
|
||||
This should be a string representing the full Python import path to your
|
||||
URLconf. For example: ``"mydjangoapps.urls"``.
|
||||
1. Django determines the root URLConf module to use; ordinarily
|
||||
this is the value of the ``ROOT_URLCONF`` setting in your
|
||||
`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
|
||||
``urlpatterns``. This should be a Python list, in the format returned by
|
||||
the function ``django.conf.urls.defaults.patterns()``.
|
||||
|
@ -71,8 +71,9 @@ u'ABC123'
|
||||
>>> fran.save()
|
||||
>>> Employee.objects.filter(last_name__exact='Jones')
|
||||
[<Employee: Dan Jones>, <Employee: Fran Jones>]
|
||||
>>> Employee.objects.in_bulk(['ABC123', 'XYZ456'])
|
||||
{u'XYZ456': <Employee: Fran Jones>, u'ABC123': <Employee: Dan Jones>}
|
||||
>>> emps = Employee.objects.in_bulk(['ABC123', 'XYZ456'])
|
||||
>>> emps['ABC123']
|
||||
<Employee: Dan Jones>
|
||||
|
||||
>>> b = Business(name='Sears')
|
||||
>>> b.save()
|
||||
|
@ -76,8 +76,11 @@ Article 4
|
||||
|
||||
# in_bulk() takes a list of IDs and returns a dictionary mapping IDs
|
||||
# to objects.
|
||||
>>> Article.objects.in_bulk([1, 2])
|
||||
{1: <Article: Article 1>, 2: <Article: Article 2>}
|
||||
>>> arts = Article.objects.in_bulk([1, 2])
|
||||
>>> arts[1]
|
||||
<Article: Article 1>
|
||||
>>> arts[2]
|
||||
<Article: Article 2>
|
||||
>>> Article.objects.in_bulk([3])
|
||||
{3: <Article: Article 3>}
|
||||
>>> Article.objects.in_bulk([1000])
|
||||
|
@ -41,25 +41,33 @@ __test__ = {'API_TESTS':u"""
|
||||
True
|
||||
|
||||
# Attempt to add a Musician without a first_name.
|
||||
>>> man.get_validation_errors(MultiValueDict({'last_name': ['Blakey']}))
|
||||
{'first_name': [u'This field is required.']}
|
||||
>>> man.get_validation_errors(MultiValueDict({'last_name': ['Blakey']}))['first_name']
|
||||
[u'This field is required.']
|
||||
|
||||
# Attempt to add a Musician without a first_name and last_name.
|
||||
>>> man.get_validation_errors(MultiValueDict({}))
|
||||
{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.']}
|
||||
>>> errors = man.get_validation_errors(MultiValueDict({}))
|
||||
>>> 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.
|
||||
>>> man = Album.AddManipulator()
|
||||
>>> man.get_validation_errors(MultiValueDict({}))
|
||||
{'musician': [u'This field is required.'], 'name': [u'This field is required.']}
|
||||
>>> errors = man.get_validation_errors(MultiValueDict({}))
|
||||
>>> errors['musician']
|
||||
[u'This field is required.']
|
||||
>>> errors['name']
|
||||
[u'This field is required.']
|
||||
|
||||
# Attempt to create an Album with an invalid musician.
|
||||
>>> man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['foo']}))
|
||||
{'musician': [u"Select a valid choice; 'foo' is not in [u'', u'1']."]}
|
||||
>>> errors = man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['foo']}))
|
||||
>>> errors['musician']
|
||||
[u"Select a valid choice; 'foo' is not in [u'', u'1']."]
|
||||
|
||||
# Attempt to create an Album with an invalid release_date.
|
||||
>>> 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 = man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['1'], 'release_date': 'today'}))
|
||||
>>> errors['release_date']
|
||||
[u'Enter a valid date in YYYY-MM-DD format.']
|
||||
|
||||
# Create an Album without a release_date (because it's optional).
|
||||
>>> data = MultiValueDict({'name': ['Ella and Basie'], 'musician': ['1']})
|
||||
|
@ -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.is_valid()
|
||||
True
|
||||
>>> f.cleaned_data
|
||||
{'url': u'entertainment', 'name': u'Entertainment', 'slug': u'entertainment'}
|
||||
>>> f.cleaned_data['url']
|
||||
u'entertainment'
|
||||
>>> f.cleaned_data['name']
|
||||
u'Entertainment'
|
||||
>>> f.cleaned_data['slug']
|
||||
u'entertainment'
|
||||
>>> obj = f.save()
|
||||
>>> obj
|
||||
<Category: Entertainment>
|
||||
@ -245,8 +249,12 @@ True
|
||||
>>> f = CategoryForm({'name': "It's a test", 'slug': 'its-test', 'url': 'test'})
|
||||
>>> f.is_valid()
|
||||
True
|
||||
>>> f.cleaned_data
|
||||
{'url': u'test', 'name': u"It's a test", 'slug': u'its-test'}
|
||||
>>> f.cleaned_data['url']
|
||||
u'test'
|
||||
>>> f.cleaned_data['name']
|
||||
u"It's a test"
|
||||
>>> f.cleaned_data['slug']
|
||||
u'its-test'
|
||||
>>> obj = f.save()
|
||||
>>> obj
|
||||
<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.is_valid()
|
||||
True
|
||||
>>> f.cleaned_data
|
||||
{'url': u'third', 'name': u'Third test', 'slug': u'third-test'}
|
||||
>>> f.cleaned_data['url']
|
||||
u'third'
|
||||
>>> f.cleaned_data['name']
|
||||
u'Third test'
|
||||
>>> f.cleaned_data['slug']
|
||||
u'third-test'
|
||||
>>> obj = f.save(commit=False)
|
||||
>>> obj
|
||||
<Category: Third test>
|
||||
@ -272,8 +284,10 @@ True
|
||||
|
||||
If you call save() with invalid data, you'll get a ValueError.
|
||||
>>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'})
|
||||
>>> f.errors
|
||||
{'name': [u'This field is required.'], 'slug': [u'This field is required.']}
|
||||
>>> f.errors['name']
|
||||
[u'This field is required.']
|
||||
>>> f.errors['slug']
|
||||
[u'This field is required.']
|
||||
>>> f.cleaned_data
|
||||
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.']
|
||||
|
||||
# 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 ####################################################
|
||||
|
||||
@ -730,6 +757,10 @@ Traceback (most recent call last):
|
||||
...
|
||||
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 ############################################################
|
||||
|
||||
@ -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.is_valid()
|
||||
True
|
||||
>>> f.cleaned_data
|
||||
{'phone': u'312-555-1212', 'description': u'Assistance'}
|
||||
>>> f.cleaned_data['phone']
|
||||
u'312-555-1212'
|
||||
>>> f.cleaned_data['description']
|
||||
u'Assistance'
|
||||
|
||||
# FileField ###################################################################
|
||||
|
||||
@ -766,7 +799,7 @@ True
|
||||
<class 'django.newforms.fields.UploadedFile'>
|
||||
>>> instance = f.save()
|
||||
>>> instance.file
|
||||
u'.../test1.txt'
|
||||
u'...test1.txt'
|
||||
|
||||
# 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.
|
||||
@ -775,10 +808,10 @@ u'.../test1.txt'
|
||||
>>> f.is_valid()
|
||||
True
|
||||
>>> f.cleaned_data['file']
|
||||
u'.../test1.txt'
|
||||
u'...test1.txt'
|
||||
>>> instance = f.save()
|
||||
>>> instance.file
|
||||
u'.../test1.txt'
|
||||
u'...test1.txt'
|
||||
|
||||
# Delete the current file since this is not done by Django.
|
||||
|
||||
@ -791,7 +824,7 @@ u'.../test1.txt'
|
||||
True
|
||||
>>> instance = f.save()
|
||||
>>> instance.file
|
||||
u'.../test2.txt'
|
||||
u'...test2.txt'
|
||||
|
||||
>>> instance.delete()
|
||||
|
||||
@ -810,7 +843,7 @@ True
|
||||
True
|
||||
>>> instance = f.save()
|
||||
>>> instance.file
|
||||
u'.../test3.txt'
|
||||
u'...test3.txt'
|
||||
>>> instance.delete()
|
||||
|
||||
# ImageField ###################################################################
|
||||
@ -832,7 +865,7 @@ True
|
||||
<class 'django.newforms.fields.UploadedFile'>
|
||||
>>> instance = f.save()
|
||||
>>> instance.image
|
||||
u'.../test.png'
|
||||
u'...test.png'
|
||||
|
||||
# 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.
|
||||
@ -841,10 +874,10 @@ u'.../test.png'
|
||||
>>> f.is_valid()
|
||||
True
|
||||
>>> f.cleaned_data['image']
|
||||
u'.../test.png'
|
||||
u'...test.png'
|
||||
>>> instance = f.save()
|
||||
>>> instance.image
|
||||
u'.../test.png'
|
||||
u'...test.png'
|
||||
|
||||
# Delete the current image since this is not done by Django.
|
||||
|
||||
@ -857,7 +890,7 @@ u'.../test.png'
|
||||
True
|
||||
>>> instance = f.save()
|
||||
>>> instance.image
|
||||
u'.../test2.png'
|
||||
u'...test2.png'
|
||||
|
||||
>>> instance.delete()
|
||||
|
||||
@ -876,7 +909,7 @@ True
|
||||
True
|
||||
>>> instance = f.save()
|
||||
>>> instance.image
|
||||
u'.../test3.png'
|
||||
u'...test3.png'
|
||||
>>> instance.delete()
|
||||
|
||||
"""}
|
||||
|
@ -4,6 +4,11 @@
|
||||
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
|
||||
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
|
||||
@ -16,70 +21,210 @@ class Article(models.Model):
|
||||
return self.headline
|
||||
|
||||
__test__ = {'API_TESTS':"""
|
||||
# prepare a list of objects for pagination
|
||||
# Prepare a list of objects for pagination.
|
||||
>>> from datetime import datetime
|
||||
>>> for x in range(1, 10):
|
||||
... a = Article(headline='Article %s' % x, pub_date=datetime(2005, 7, 29))
|
||||
... 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
|
||||
>>> paginator = ObjectPaginator(Article.objects.all(), 5)
|
||||
|
||||
# the paginator knows how many hits and pages it contains
|
||||
>>> paginator.hits
|
||||
9
|
||||
|
||||
>>> paginator.pages
|
||||
2
|
||||
>>> paginator.page_range
|
||||
[1, 2]
|
||||
|
||||
# get the first page (zero-based)
|
||||
# Get the first page.
|
||||
>>> paginator.get_page(0)
|
||||
[<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)
|
||||
True
|
||||
|
||||
>>> paginator.has_previous_page(0)
|
||||
False
|
||||
|
||||
# check the second page
|
||||
>>> paginator.has_next_page(1)
|
||||
False
|
||||
|
||||
>>> paginator.has_previous_page(1)
|
||||
True
|
||||
|
||||
>>> paginator.first_on_page(0)
|
||||
1
|
||||
>>> paginator.first_on_page(1)
|
||||
6
|
||||
>>> paginator.last_on_page(0)
|
||||
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)
|
||||
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.
|
||||
>>> for x in range(10, 13):
|
||||
... 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.pages
|
||||
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.pages
|
||||
2
|
||||
|
||||
# The paginator can provide a list of all available pages.
|
||||
>>> paginator = ObjectPaginator(Article.objects.all(), 10)
|
||||
>>> paginator.page_range
|
||||
[1, 2]
|
||||
"""}
|
||||
|
@ -41,8 +41,8 @@ __test__ = {'API_TESTS':"""
|
||||
23
|
||||
|
||||
>>> p = Person(**dict(valid_params, id='foo'))
|
||||
>>> p.validate()
|
||||
{'id': [u'This value must be an integer.']}
|
||||
>>> p.validate()['id']
|
||||
[u'This value must be an integer.']
|
||||
|
||||
>>> p = Person(**dict(valid_params, id=None))
|
||||
>>> p.validate()
|
||||
@ -75,8 +75,8 @@ True
|
||||
False
|
||||
|
||||
>>> p = Person(**dict(valid_params, is_child='foo'))
|
||||
>>> p.validate()
|
||||
{'is_child': [u'This value must be either True or False.']}
|
||||
>>> p.validate()['is_child']
|
||||
[u'This value must be either True or False.']
|
||||
|
||||
>>> p = Person(**dict(valid_params, name=u'Jose'))
|
||||
>>> p.validate()
|
||||
@ -115,8 +115,8 @@ datetime.date(2000, 5, 3)
|
||||
datetime.date(2000, 5, 3)
|
||||
|
||||
>>> p = Person(**dict(valid_params, birthdate='foo'))
|
||||
>>> p.validate()
|
||||
{'birthdate': [u'Enter a valid date in YYYY-MM-DD format.']}
|
||||
>>> p.validate()['birthdate']
|
||||
[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.validate()
|
||||
@ -143,11 +143,15 @@ datetime.datetime(2002, 4, 3, 0, 0)
|
||||
u'john@example.com'
|
||||
|
||||
>>> p = Person(**dict(valid_params, email=22))
|
||||
>>> p.validate()
|
||||
{'email': [u'Enter a valid e-mail address.']}
|
||||
>>> p.validate()['email']
|
||||
[u'Enter a valid e-mail address.']
|
||||
|
||||
# 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()
|
||||
{'favorite_moment': [u'This field is required.'], 'birthdate': [u'This field is required.']}
|
||||
>>> p = Person(name='John Doe', is_child=True, email='abc@def.com')
|
||||
>>> errors = p.validate()
|
||||
>>> errors['favorite_moment']
|
||||
[u'This field is required.']
|
||||
>>> errors['birthdate']
|
||||
[u'This field is required.']
|
||||
|
||||
"""}
|
||||
|
@ -22,7 +22,7 @@ classes that demonstrate some of the library's abilities.
|
||||
>>> from django.newforms.extras import SelectDateWidget
|
||||
>>> w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016'))
|
||||
>>> print w.render('mydate', '')
|
||||
<select name="mydate_month">
|
||||
<select name="mydate_month" id="id_mydate_month">
|
||||
<option value="1">January</option>
|
||||
<option value="2">February</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="12">December</option>
|
||||
</select>
|
||||
<select name="mydate_day">
|
||||
<select name="mydate_day" id="id_mydate_day">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</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="31">31</option>
|
||||
</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>
|
||||
@ -84,7 +84,7 @@ classes that demonstrate some of the library's abilities.
|
||||
>>> w.render('mydate', None) == w.render('mydate', '')
|
||||
True
|
||||
>>> 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="2">February</option>
|
||||
<option value="3">March</option>
|
||||
@ -98,7 +98,7 @@ True
|
||||
<option value="11">November</option>
|
||||
<option value="12">December</option>
|
||||
</select>
|
||||
<select name="mydate_day">
|
||||
<select name="mydate_day" id="id_mydate_day">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
@ -131,7 +131,74 @@ True
|
||||
<option value="30">30</option>
|
||||
<option value="31">31</option>
|
||||
</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="2008">2008</option>
|
||||
<option value="2009">2009</option>
|
||||
@ -252,8 +319,8 @@ ValidationError: [u'This field is required.']
|
||||
</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>
|
||||
|
||||
>>> f.cleaned_data
|
||||
{'field1': u'some text,JP,2007-04-25 06:24:00'}
|
||||
>>> f.cleaned_data['field1']
|
||||
u'some text,JP,2007-04-25 06:24:00'
|
||||
|
||||
|
||||
# IPAddressField ##################################################################
|
||||
|
@ -1133,6 +1133,33 @@ u''
|
||||
>>> f.clean(None)
|
||||
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 ##########################################################
|
||||
|
||||
>>> f = SplitDateTimeField()
|
||||
|
@ -36,8 +36,8 @@ True
|
||||
u''
|
||||
>>> p.errors.as_text()
|
||||
u''
|
||||
>>> p.cleaned_data
|
||||
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
|
||||
>>> p.cleaned_data["first_name"], p.cleaned_data["last_name"], p.cleaned_data["birthday"]
|
||||
(u'John', u'Lennon', datetime.date(1940, 10, 9))
|
||||
>>> print p['first_name']
|
||||
<input type="text" name="first_name" value="John" id="id_first_name" />
|
||||
>>> print p['last_name']
|
||||
@ -68,8 +68,12 @@ Empty dictionaries are valid, too.
|
||||
>>> p = Person({})
|
||||
>>> p.is_bound
|
||||
True
|
||||
>>> p.errors
|
||||
{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.'], 'birthday': [u'This field is required.']}
|
||||
>>> p.errors['first_name']
|
||||
[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()
|
||||
False
|
||||
>>> 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>'
|
||||
|
||||
>>> p = Person({'last_name': u'Lennon'})
|
||||
>>> p.errors
|
||||
{'first_name': [u'This field is required.'], 'birthday': [u'This field is required.']}
|
||||
>>> p.errors['first_name']
|
||||
[u'This field is required.']
|
||||
>>> p.errors['birthday']
|
||||
[u'This field is required.']
|
||||
>>> p.is_valid()
|
||||
False
|
||||
>>> p.errors.as_ul()
|
||||
@ -175,8 +181,13 @@ but cleaned_data contains only the form's fields.
|
||||
>>> p = Person(data)
|
||||
>>> p.is_valid()
|
||||
True
|
||||
>>> p.cleaned_data
|
||||
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
|
||||
>>> p.cleaned_data['first_name']
|
||||
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,
|
||||
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.is_valid()
|
||||
True
|
||||
>>> f.cleaned_data
|
||||
{'nick_name': u'', 'first_name': u'John', 'last_name': u'Lennon'}
|
||||
>>> f.cleaned_data['nick_name']
|
||||
u''
|
||||
>>> f.cleaned_data['first_name']
|
||||
u'John'
|
||||
>>> f.cleaned_data['last_name']
|
||||
u'Lennon'
|
||||
|
||||
For DateFields, it's set to None.
|
||||
>>> class OptionalPersonForm(Form):
|
||||
@ -203,8 +218,12 @@ For DateFields, it's set to None.
|
||||
>>> f = OptionalPersonForm(data)
|
||||
>>> f.is_valid()
|
||||
True
|
||||
>>> f.cleaned_data
|
||||
{'birth_date': None, 'first_name': u'John', 'last_name': u'Lennon'}
|
||||
>>> print f.cleaned_data['birth_date']
|
||||
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.
|
||||
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
|
||||
returns a list of input.
|
||||
>>> f = SongForm({'name': 'Yesterday'}, auto_id=False)
|
||||
>>> f.errors
|
||||
{'composers': [u'This field is required.']}
|
||||
>>> f.errors['composers']
|
||||
[u'This field is required.']
|
||||
>>> f = SongForm({'name': 'Yesterday', 'composers': ['J']}, auto_id=False)
|
||||
>>> f.errors
|
||||
{}
|
||||
>>> f.cleaned_data
|
||||
{'composers': [u'J'], 'name': u'Yesterday'}
|
||||
>>> f.cleaned_data['composers']
|
||||
[u'J']
|
||||
>>> f.cleaned_data['name']
|
||||
u'Yesterday'
|
||||
>>> f = SongForm({'name': 'Yesterday', 'composers': ['J', 'P']}, auto_id=False)
|
||||
>>> f.errors
|
||||
{}
|
||||
>>> f.cleaned_data
|
||||
{'composers': [u'J', u'P'], 'name': u'Yesterday'}
|
||||
>>> f.cleaned_data['composers']
|
||||
[u'J', u'P']
|
||||
>>> f.cleaned_data['name']
|
||||
u'Yesterday'
|
||||
|
||||
Validation errors are HTML-escaped when output as HTML.
|
||||
>>> 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 = UserRegistration({}, auto_id=False)
|
||||
>>> f.errors
|
||||
{'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']}
|
||||
>>> f.errors['username']
|
||||
[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.errors
|
||||
{'password2': [u'Please make sure your passwords match.']}
|
||||
>>> f.errors['password2']
|
||||
[u'Please make sure your passwords match.']
|
||||
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
|
||||
>>> f.errors
|
||||
{}
|
||||
>>> f.cleaned_data
|
||||
{'username': u'adrian', 'password1': u'foo', 'password2': u'foo'}
|
||||
>>> f.cleaned_data['username']
|
||||
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
|
||||
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>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>
|
||||
>>> f.errors
|
||||
{'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']}
|
||||
>>> f.errors['username']
|
||||
[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.errors
|
||||
{'__all__': [u'Please make sure your passwords match.']}
|
||||
>>> f.errors['__all__']
|
||||
[u'Please make sure your passwords match.']
|
||||
>>> print f.as_table()
|
||||
<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>
|
||||
@ -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.errors
|
||||
{}
|
||||
>>> f.cleaned_data
|
||||
{'username': u'adrian', 'password1': u'foo', 'password2': u'foo'}
|
||||
>>> f.cleaned_data['username']
|
||||
u'adrian'
|
||||
>>> f.cleaned_data['password1']
|
||||
u'foo'
|
||||
>>> f.cleaned_data['password2']
|
||||
u'foo'
|
||||
|
||||
# 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
|
||||
validation error rather than using the initial value for 'username'.
|
||||
>>> p = UserRegistration({'password': 'secret'})
|
||||
>>> p.errors
|
||||
{'username': [u'This field is required.']}
|
||||
>>> p.errors['username']
|
||||
[u'This field is required.']
|
||||
>>> p.is_valid()
|
||||
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
|
||||
validation error rather than using the initial value for 'username'.
|
||||
>>> p = UserRegistration({'password': 'secret'}, initial={'username': 'django'})
|
||||
>>> p.errors
|
||||
{'username': [u'This field is required.']}
|
||||
>>> p.errors['username']
|
||||
[u'This field is required.']
|
||||
>>> p.is_valid()
|
||||
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
|
||||
validation error rather than using the initial value for 'username'.
|
||||
>>> p = UserRegistration({'password': 'secret'}, initial={'username': initial_django})
|
||||
>>> p.errors
|
||||
{'username': [u'This field is required.']}
|
||||
>>> p.errors['username']
|
||||
[u'This field is required.']
|
||||
>>> p.is_valid()
|
||||
False
|
||||
|
||||
@ -1258,8 +1297,12 @@ actual field name.
|
||||
{}
|
||||
>>> p.is_valid()
|
||||
True
|
||||
>>> p.cleaned_data
|
||||
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
|
||||
>>> p.cleaned_data['first_name']
|
||||
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
|
||||
work as expected.
|
||||
@ -1269,8 +1312,12 @@ work as expected.
|
||||
... 'person1-birthday': u''
|
||||
... }
|
||||
>>> p = Person(data, prefix='person1')
|
||||
>>> p.errors
|
||||
{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.'], 'birthday': [u'This field is required.']}
|
||||
>>> p.errors['first_name']
|
||||
[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
|
||||
[u'This field is required.']
|
||||
>>> p['person1-first_name'].errors
|
||||
@ -1286,8 +1333,12 @@ the form doesn't "see" the fields.
|
||||
... 'birthday': u'1940-10-9'
|
||||
... }
|
||||
>>> p = Person(data, prefix='person1')
|
||||
>>> p.errors
|
||||
{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.'], 'birthday': [u'This field is required.']}
|
||||
>>> p.errors['first_name']
|
||||
[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
|
||||
of the same form.
|
||||
@ -1302,13 +1353,21 @@ of the same form.
|
||||
>>> p1 = Person(data, prefix='person1')
|
||||
>>> p1.is_valid()
|
||||
True
|
||||
>>> p1.cleaned_data
|
||||
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
|
||||
>>> p1.cleaned_data['first_name']
|
||||
u'John'
|
||||
>>> p1.cleaned_data['last_name']
|
||||
u'Lennon'
|
||||
>>> p1.cleaned_data['birthday']
|
||||
datetime.date(1940, 10, 9)
|
||||
>>> p2 = Person(data, prefix='person2')
|
||||
>>> p2.is_valid()
|
||||
True
|
||||
>>> p2.cleaned_data
|
||||
{'first_name': u'Jim', 'last_name': u'Morrison', 'birthday': datetime.date(1943, 12, 8)}
|
||||
>>> p2.cleaned_data['first_name']
|
||||
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
|
||||
form can alter that behavior by implementing the add_prefix() method. This
|
||||
@ -1333,8 +1392,12 @@ self.prefix.
|
||||
>>> p = Person(data, prefix='foo')
|
||||
>>> p.is_valid()
|
||||
True
|
||||
>>> p.cleaned_data
|
||||
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
|
||||
>>> p.cleaned_data['first_name']
|
||||
u'John'
|
||||
>>> p.cleaned_data['last_name']
|
||||
u'Lennon'
|
||||
>>> p.cleaned_data['birthday']
|
||||
datetime.date(1940, 10, 9)
|
||||
|
||||
# Forms with NullBooleanFields ################################################
|
||||
|
||||
|
@ -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()
|
||||
|
||||
"""}
|
0
tests/regressiontests/syndication/__init__.py
Normal file
0
tests/regressiontests/syndication/__init__.py
Normal file
0
tests/regressiontests/syndication/models.py
Normal file
0
tests/regressiontests/syndication/models.py
Normal file
14
tests/regressiontests/syndication/tests.py
Normal file
14
tests/regressiontests/syndication/tests.py
Normal 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)
|
18
tests/regressiontests/syndication/urls.py
Normal file
18
tests/regressiontests/syndication/urls.py
Normal 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,
|
||||
)}),
|
||||
)
|
@ -3,7 +3,7 @@ Regression tests for the Test Client, especially the customized assertions.
|
||||
|
||||
"""
|
||||
from django.test import Client, TestCase
|
||||
from django.core import mail
|
||||
from django.core.urlresolvers import reverse
|
||||
import os
|
||||
|
||||
class AssertContainsTests(TestCase):
|
||||
@ -261,3 +261,31 @@ class LoginTests(TestCase):
|
||||
# Check that assertRedirects uses the original client, not the
|
||||
# default client.
|
||||
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')
|
||||
|
||||
|
||||
|
@ -5,5 +5,6 @@ urlpatterns = patterns('',
|
||||
(r'^no_template_view/$', views.no_template_view),
|
||||
(r'^file_upload/$', views.file_upload_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)
|
||||
)
|
||||
|
@ -1,7 +1,5 @@
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.mail import EmailMessage, SMTPConnection
|
||||
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError
|
||||
from django.shortcuts import render_to_response
|
||||
|
||||
def no_template_view(request):
|
||||
"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")
|
||||
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):
|
||||
"A view that redirects all requests to the GET view"
|
||||
return HttpResponseRedirect('/test_client_regress/get_view/')
|
||||
|
@ -19,4 +19,7 @@ urlpatterns = patterns('',
|
||||
(r'^middleware/', include('regressiontests.middleware.urls')),
|
||||
|
||||
(r'^utils/', include('regressiontests.utils.urls')),
|
||||
|
||||
# test urlconf for syndication tests
|
||||
(r'^syndication/', include('regressiontests.syndication.urls')),
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user