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

newforms-admin: Merged to [4502]

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@4503 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2007-02-13 16:15:10 +00:00
parent af908e2e8d
commit 649423e81c
42 changed files with 671 additions and 66 deletions

19
AUTHORS
View File

@ -1,7 +1,6 @@
Django was originally created in late 2003 at World Online, the Web division
of the Lawrence Journal-World newspaper in Lawrence, Kansas.
The PRIMARY AUTHORS are (and/or have been):
Adrian Holovaty <http://www.holovaty.com/>, who originally created Django with
@ -45,6 +44,7 @@ answer newbie questions, and generally made Django that much better:
adurdin@gmail.com
akaihola
Andreas
andy@jadedplanet.net
ant9000@netwise.it
David Ascher <http://ascher.ca/>
Arthur <avandorp@gmail.com>
@ -53,10 +53,12 @@ answer newbie questions, and generally made Django that much better:
Shannon -jj Behrens <http://jjinux.blogspot.com/>
Esdras Beleza <linux@esdrasbeleza.com>
James Bennett
Ben <afternoon@uk2.net>
Paul Bissex <http://e-scribe.com/>
Simon Blanchard
Andrew Brehaut <http://brehaut.net/blog>
andy@jadedplanet.net
brut.alll@gmail.com
Jonathan Buchanan <jonathan.buchanan@gmail.com>
Antonio Cavedoni <http://cavedoni.com/>
C8E
Chris Chamberlin <dja@cdc.msbx.net>
@ -66,17 +68,19 @@ answer newbie questions, and generally made Django that much better:
crankycoder@gmail.com
Matt Croydon <http://www.postneo.com/>
dackze+django@gmail.com
Dirk Datzert <dummy@habmalnefrage.de>
Jonathan Daugherty (cygnus) <http://www.cprogrammer.org/>
dave@thebarproject.com
Jason Davies (Esaj) <http://www.jasondavies.com/>
Alex Dedul
deric@monowerks.com
dne@mayonnaise.net
Maximillian Dornseif <md@hudora.de>
dummy@habmalnefrage.de
Jeremy Dunck <http://dunck.us/>
Andy Dustman <farcepest@gmail.com>
Clint Ecker
Enrico <rico.bl@gmail.com>
Marc Fargas <telenieko@telenieko.com>
favo@exoweb.net
Eric Floehr <eric@intellovations.com>
gandalf@owca.info
@ -84,16 +88,18 @@ answer newbie questions, and generally made Django that much better:
martin.glueck@gmail.com
Simon Greenhill <dev@simon.net.nz>
Espen Grindhaug <http://grindhaug.org/>
Brian Harring <ferringb@gmail.com>
Brant Harris
Hawkeye
heckj@mac.com
Joe Heck <http://www.rhonabwy.com/wp/>
Joel Heenan <joelh-django@planetjoel.com>
hipertracker@gmail.com
Ian Holsman <http://feh.holsman.net/>
Kieran Holland <http://www.kieranholland.com>
Robert Rock Howard <http://djangomojo.com/>
Jason Huggins <http://www.jrandolph.com/blog/>
Baurzhan Ismagulov <ibr@radix50.net>
Tom Insam
Baurzhan Ismagulov <ibr@radix50.net>
jcrasta@gmail.com
Michael Josephson <http://www.sdjournal.com/>
jpellerin@gmail.com
@ -112,6 +118,7 @@ answer newbie questions, and generally made Django that much better:
Jeong-Min Lee <falsetru@gmail.com>
Christopher Lenz <http://www.cmlenz.net/>
lerouxb@gmail.com
Waylan Limberg <waylan@gmail.com>
limodou
mattmcc
Martin Maney <http://www.chipy.org/Martin_Maney>
@ -162,8 +169,8 @@ answer newbie questions, and generally made Django that much better:
Aaron Swartz <http://www.aaronsw.com/>
Tyson Tate <tyson@fallingbullets.com>
Tom Tobin
Tom Insam
Joe Topjian <http://joe.terrarum.net/geek/code/python/django/>
torne-django@wolfpuppy.org.uk
Karen Tracey <graybark@bellsouth.net>
Makoto Tsuyuki <mtsuyuki@gmail.com>
Amit Upadhyay

View File

@ -1,8 +1,10 @@
include AUTHORS
include INSTALL
include LICENSE
recursive-include docs *
recursive-include scripts *
recursive-include django/conf/locale *
recursive-include django/contrib/admin/templates
recursive-include django/contrib/admin/media
recursive-include django/contrib/comments/templates
recursive-include django/contrib/sitemaps/templates
recursive-include django/contrib/admin/templates *
recursive-include django/contrib/admin/media *
recursive-include django/contrib/comments/templates *
recursive-include django/contrib/sitemaps/templates *

View File

@ -7,6 +7,7 @@ a list of all possible variables.
"""
import os
import time # Needed for Windows
from django.conf import global_settings
ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
@ -105,8 +106,10 @@ class Settings(object):
new_installed_apps.append(app)
self.INSTALLED_APPS = new_installed_apps
# move the time zone info into os.environ
os.environ['TZ'] = self.TIME_ZONE
if hasattr(time, 'tzset'):
# Move the time zone info into os.environ. See ticket #2315 for why
# we don't do this unconditionally (breaks Windows).
os.environ['TZ'] = self.TIME_ZONE
def get_all_members(self):
return dir(self)

View File

@ -18,6 +18,8 @@ DATABASE_PORT = '' # Set to empty string for default. Not used with
# Local time zone for this installation. All choices can be found here:
# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'America/Chicago'
# Language code for this installation. All choices can be found here:

View File

@ -4,6 +4,7 @@ from django.contrib.sites.models import Site
from django.template import Context, loader
from django.core import validators
from django import oldforms
from django.utils.translation import gettext as _
class UserCreationForm(oldforms.Manipulator):
"A form that creates a user, with no privileges, from the given username and password."

View File

@ -78,6 +78,7 @@ class Feed(object):
author_link = self.__get_dynamic_attr('author_link', obj),
author_email = self.__get_dynamic_attr('author_email', obj),
categories = self.__get_dynamic_attr('categories', obj),
feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
)
try:
@ -116,5 +117,6 @@ class Feed(object):
author_email = author_email,
author_link = author_link,
categories = self.__get_dynamic_attr('item_categories', item),
item_copyright = self.__get_dynamic_attr('item_copyright', item),
)
return feed

View File

@ -50,4 +50,9 @@ class LazyDate(object):
return (datetime.datetime.now() + self.delta).date()
def __getattr__(self, attr):
if attr == 'delta':
# To fix ticket #3377. Note that normal accesses to LazyDate.delta
# (after construction) will still work, because they don't go
# through __getattr__). This is mainly needed for unpickling.
raise AttributeError
return getattr(self.__get_value__(), attr)

View File

@ -167,17 +167,16 @@ class QuerySet(object):
def iterator(self):
"Performs the SELECT database lookup of this QuerySet."
# self._select is a dictionary, and dictionaries' key order is
# undefined, so we convert it to a list of tuples.
extra_select = self._select.items()
cursor = connection.cursor()
try:
select, sql, params = self._get_sql_clause()
except EmptyResultSet:
raise StopIteration
# self._select is a dictionary, and dictionaries' key order is
# undefined, so we convert it to a list of tuples.
extra_select = self._select.items()
cursor = connection.cursor()
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
fill_cache = self._select_related
index_end = len(self.model._meta.fields)
@ -198,9 +197,12 @@ class QuerySet(object):
"Performs a SELECT COUNT() and returns the number of records as an integer."
counter = self._clone()
counter._order_by = ()
counter._select_related = False
offset = counter._offset
limit = counter._limit
counter._offset = None
counter._limit = None
counter._select_related = False
try:
select, sql, params = counter._get_sql_clause()
@ -214,7 +216,16 @@ class QuerySet(object):
cursor.execute("SELECT COUNT(DISTINCT(%s))" % id_col + sql, params)
else:
cursor.execute("SELECT COUNT(*)" + sql, params)
return cursor.fetchone()[0]
count = cursor.fetchone()[0]
# Apply any offset and limit constraints manually, since using LIMIT or
# OFFSET in SQL doesn't change the output of COUNT.
if offset:
count = max(0, count - offset)
if limit:
count = min(limit, count)
return count
def get(self, *args, **kwargs):
"Performs the SELECT and returns a single object matching the given keyword arguments."
@ -523,11 +534,18 @@ class QuerySet(object):
return select, " ".join(sql), params
class ValuesQuerySet(QuerySet):
def iterator(self):
def __init__(self, *args, **kwargs):
super(ValuesQuerySet, self).__init__(*args, **kwargs)
# select_related and select aren't supported in values().
self._select_related = False
self._select = {}
def iterator(self):
try:
select, sql, params = self._get_sql_clause()
except EmptyResultSet:
raise StopIteration
# self._fields is a list of field names to fetch.
if self._fields:
columns = [self.model._meta.get_field(f, many_to_many=False).column for f in self._fields]
@ -536,14 +554,8 @@ class ValuesQuerySet(QuerySet):
columns = [f.column for f in self.model._meta.fields]
field_names = [f.attname for f in self.model._meta.fields]
cursor = connection.cursor()
try:
select, sql, params = self._get_sql_clause()
except EmptyResultSet:
raise StopIteration
select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns]
cursor = connection.cursor()
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
@ -592,9 +604,6 @@ class EmptyQuerySet(QuerySet):
super(EmptyQuerySet, self).__init__(model)
self._result_cache = []
def iterator(self):
raise StopIteration
def count(self):
return 0
@ -606,6 +615,9 @@ class EmptyQuerySet(QuerySet):
c._result_cache = []
return c
def _get_sql_clause(self):
raise EmptyResultSet
class QOperator(object):
"Base class for QAnd and QOr"
def __init__(self, *args):
@ -881,8 +893,14 @@ def lookup_inner(path, lookup_type, value, opts, table, column):
new_opts = field.rel.to._meta
new_column = new_opts.pk.column
join_column = field.column
raise FieldFound
raise FieldFound
elif path:
# For regular fields, if there are still items on the path,
# an error has been made. We munge "name" so that the error
# properly identifies the cause of the problem.
name += LOOKUP_SEPARATOR + path[0]
else:
raise FieldFound
except FieldFound: # Match found, loop has been shortcut.
pass

View File

@ -68,7 +68,10 @@ class RelatedObject(object):
# object
return [attr]
else:
return [None] * self.field.rel.num_in_admin
if self.field.rel.min_num_in_admin:
return [None] * max(self.field.rel.num_in_admin, self.field.rel.min_num_in_admin)
else:
return [None] * self.field.rel.num_in_admin
def get_db_prep_lookup(self, lookup_type, value):
# Defer to the actual field definition for db prep

View File

@ -160,7 +160,7 @@ class HttpResponse(object):
self._charset = settings.DEFAULT_CHARSET
if not mimetype:
mimetype = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, settings.DEFAULT_CHARSET)
if hasattr(content, '__iter__'):
if not isinstance(content, basestring) and hasattr(content, '__iter__'):
self._container = content
self._is_string = False
else:

View File

@ -356,7 +356,7 @@ class ChoiceField(Field):
return value
valid_values = set([str(k) for k, v in self.choices])
if value not in valid_values:
raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % value)
raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.'))
return value
class MultipleChoiceField(ChoiceField):

View File

@ -117,7 +117,13 @@ class TemplateDoesNotExist(Exception):
pass
class VariableDoesNotExist(Exception):
pass
def __init__(self, msg, params=()):
self.msg = msg
self.params = params
def __str__(self):
return self.msg % self.params
class InvalidTemplateLibrary(Exception):
pass
@ -660,7 +666,7 @@ def resolve_variable(path, context):
try: # list-index lookup
current = current[int(bits[0])]
except (IndexError, ValueError, KeyError):
raise VariableDoesNotExist, "Failed lookup for key [%s] in %r" % (bits[0], current) # missing attribute
raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bits[0], current)) # missing attribute
except Exception, e:
if getattr(e, 'silent_variable_failure', False):
current = settings.TEMPLATE_STRING_IF_INVALID

View File

@ -49,6 +49,9 @@ class Context(object):
return True
return False
def __contains__(self, key):
return self.has_key(key)
def get(self, key, otherwise=None):
for d in self.dicts:
if d.has_key(key):

View File

@ -119,6 +119,21 @@ def truncatewords(value, arg):
value = str(value)
return truncate_words(value, length)
def truncatewords_html(value, arg):
"""
Truncates HTML after a certain number of words
Argument: Number of words to truncate after
"""
from django.utils.text import truncate_html_words
try:
length = int(arg)
except ValueError: # invalid literal for int()
return value # Fail silently.
if not isinstance(value, basestring):
value = str(value)
return truncate_html_words(value, length)
def upper(value):
"Converts a string into all uppercase"
return value.upper()
@ -126,6 +141,8 @@ def upper(value):
def urlencode(value):
"Escapes a value for use in a URL"
import urllib
if not isinstance(value, basestring):
value = str(value)
return urllib.quote(value)
def urlize(value):
@ -534,6 +551,7 @@ register.filter(timesince)
register.filter(timeuntil)
register.filter(title)
register.filter(truncatewords)
register.filter(truncatewords_html)
register.filter(unordered_list)
register.filter(upper)
register.filter(urlencode)

View File

@ -315,6 +315,25 @@ class TemplateTagNode(Node):
def render(self, context):
return self.mapping.get(self.tagtype, '')
class URLNode(Node):
def __init__(self, view_name, args, kwargs):
self.view_name = view_name
self.args = args
self.kwargs = kwargs
def render(self, context):
from django.core.urlresolvers import reverse, NoReverseMatch
args = [arg.resolve(context) for arg in self.args]
kwargs = dict([(k, v.resolve(context)) for k, v in self.kwargs.items()])
try:
return reverse(self.view_name, args=args, kwargs=kwargs)
except NoReverseMatch:
try:
project_name = settings.SETTINGS_MODULE.split('.')[0]
return reverse(project_name + '.' + self.view_name, args=args, kwargs=kwargs)
except NoReverseMatch:
return ''
class WidthRatioNode(Node):
def __init__(self, val_expr, max_expr, max_width):
self.val_expr = val_expr
@ -868,6 +887,50 @@ def templatetag(parser, token):
return TemplateTagNode(tag)
templatetag = register.tag(templatetag)
def url(parser, token):
"""
Returns an absolute URL matching given view with its parameters. This is a
way to define links that aren't tied to a particular url configuration:
{% url path.to.some_view arg1,arg2,name1=value1 %}
The first argument is a path to a view. It can be an absolute python path
or just ``app_name.view_name`` without the project name if the view is
located inside the project. Other arguments are comma-separated values
that will be filled in place of positional and keyword arguments in the
URL. All arguments for the URL should be present.
For example if you have a view ``app_name.client`` taking client's id and
the corresponding line in a urlconf looks like this:
('^client/(\d+)/$', 'app_name.client')
and this app's urlconf is included into the project's urlconf under some
path:
('^clients/', include('project_name.app_name.urls'))
then in a template you can create a link for a certain client like this:
{% url app_name.client client.id %}
The URL will look like ``/clients/client/123/``.
"""
bits = token.contents.split(' ', 2)
if len(bits) < 2:
raise TemplateSyntaxError, "'%s' takes at least one argument (path to a view)" % bits[0]
args = []
kwargs = {}
if len(bits) > 2:
for arg in bits[2].split(','):
if '=' in arg:
k, v = arg.split('=', 1)
kwargs[k] = parser.compile_filter(v)
else:
args.append(parser.compile_filter(arg))
return URLNode(bits[1], args, kwargs)
url = register.tag(url)
#@register.tag
def widthratio(parser, token):
"""

View File

@ -129,7 +129,7 @@ def do_block(parser, token):
parser.__loaded_blocks.append(block_name)
except AttributeError: # parser.__loaded_blocks isn't a list yet
parser.__loaded_blocks = [block_name]
nodelist = parser.parse(('endblock',))
nodelist = parser.parse(('endblock', 'endblock %s' % block_name))
parser.delete_first_token()
return BlockNode(block_name, nodelist)

View File

@ -1,6 +1,9 @@
import sys
from cStringIO import StringIO
from django.conf import settings
from django.core.handlers.base import BaseHandler
from django.core.handlers.wsgi import WSGIRequest
from django.core.signals import got_request_exception
from django.dispatch import dispatcher
from django.http import urlencode, SimpleCookie
from django.test import signals
@ -97,7 +100,16 @@ class Client:
def __init__(self, **defaults):
self.handler = ClientHandler()
self.defaults = defaults
self.cookie = SimpleCookie()
self.cookies = SimpleCookie()
self.session = {}
self.exc_info = None
def store_exc_info(self, *args, **kwargs):
"""
Utility method that can be used to store exceptions when they are
generated by a view.
"""
self.exc_info = sys.exc_info()
def request(self, **request):
"""
@ -108,7 +120,7 @@ class Client:
"""
environ = {
'HTTP_COOKIE': self.cookie,
'HTTP_COOKIE': self.cookies,
'PATH_INFO': '/',
'QUERY_STRING': '',
'REQUEST_METHOD': 'GET',
@ -126,6 +138,9 @@ class Client:
on_template_render = curry(store_rendered_templates, data)
dispatcher.connect(on_template_render, signal=signals.template_rendered)
# Capture exceptions created by the handler
dispatcher.connect(self.store_exc_info, signal=got_request_exception)
response = self.handler(environ)
# Add any rendered template detail to the response
@ -140,8 +155,19 @@ class Client:
else:
setattr(response, detail, None)
# Look for a signalled exception and reraise it
if self.exc_info:
raise self.exc_info[1], None, self.exc_info[2]
# Update persistent cookie and session data
if response.cookies:
self.cookie.update(response.cookies)
self.cookies.update(response.cookies)
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
from django.contrib.sessions.middleware import SessionWrapper
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
if cookie:
self.session = SessionWrapper(cookie.value)
return response

View File

@ -40,7 +40,7 @@ class SyndicationFeed(object):
"Base class for all syndication feeds. Subclasses should provide write()"
def __init__(self, title, link, description, language=None, author_email=None,
author_name=None, author_link=None, subtitle=None, categories=None,
feed_url=None):
feed_url=None, feed_copyright=None):
self.feed = {
'title': title,
'link': link,
@ -52,12 +52,13 @@ class SyndicationFeed(object):
'subtitle': subtitle,
'categories': categories or (),
'feed_url': feed_url,
'feed_copyright': feed_copyright,
}
self.items = []
def add_item(self, title, link, description, author_email=None,
author_name=None, author_link=None, pubdate=None, comments=None,
unique_id=None, enclosure=None, categories=()):
unique_id=None, enclosure=None, categories=(), item_copyright=None):
"""
Adds an item to the feed. All args are expected to be Python Unicode
objects except pubdate, which is a datetime.datetime object, and
@ -75,6 +76,7 @@ class SyndicationFeed(object):
'unique_id': unique_id,
'enclosure': enclosure,
'categories': categories or (),
'item_copyright': item_copyright,
})
def num_items(self):
@ -128,6 +130,8 @@ class RssFeed(SyndicationFeed):
handler.addQuickElement(u"language", self.feed['language'])
for cat in self.feed['categories']:
handler.addQuickElement(u"category", cat)
if self.feed['feed_copyright'] is not None:
handler.addQuickElement(u"copyright", self.feed['feed_copyright'])
self.write_items(handler)
self.endChannelElement(handler)
handler.endElement(u"rss")
@ -212,6 +216,8 @@ class Atom1Feed(SyndicationFeed):
handler.addQuickElement(u"subtitle", self.feed['subtitle'])
for cat in self.feed['categories']:
handler.addQuickElement(u"category", "", {u"term": cat})
if self.feed['feed_copyright'] is not None:
handler.addQuickElement(u"rights", self.feed['feed_copyright'])
self.write_items(handler)
handler.endElement(u"feed")
@ -252,10 +258,14 @@ class Atom1Feed(SyndicationFeed):
u"length": item['enclosure'].length,
u"type": item['enclosure'].mime_type})
# Categories:
# Categories.
for cat in item['categories']:
handler.addQuickElement(u"category", u"", {u"term": cat})
# Rights.
if item['item_copyright'] is not None:
handler.addQuickElement(u"rights", item['item_copyright'])
handler.endElement(u"entry")
# This isolates the decision of what the system default is, so calling code can

View File

@ -41,6 +41,66 @@ def truncate_words(s, num):
words.append('...')
return ' '.join(words)
def truncate_html_words(s, num):
"""
Truncates html to a certain number of words (not counting tags and comments).
Closes opened tags if they were correctly closed in the given html.
"""
length = int(num)
if length <= 0:
return ''
html4_singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input')
# Set up regular expressions
re_words = re.compile(r'&.*?;|<.*?>|([A-Za-z0-9][\w-]*)')
re_tag = re.compile(r'<(/)?([^ ]+?)(?: (/)| .*?)?>')
# Count non-HTML words and keep note of open tags
pos = 0
ellipsis_pos = 0
words = 0
open_tags = []
while words <= length:
m = re_words.search(s, pos)
if not m:
# Checked through whole string
break
pos = m.end(0)
if m.group(1):
# It's an actual non-HTML word
words += 1
if words == length:
ellipsis_pos = pos
continue
# Check for tag
tag = re_tag.match(m.group(0))
if not tag or ellipsis_pos:
# Don't worry about non tags or tags after our truncate point
continue
closing_tag, tagname, self_closing = tag.groups()
tagname = tagname.lower() # Element names are always case-insensitive
if self_closing or tagname in html4_singlets:
pass
elif closing_tag:
# Check for match in open tags list
try:
i = open_tags.index(tagname)
except ValueError:
pass
else:
# SGML: An end tag closes, back to the matching start tag, all unclosed intervening start tags with omitted end tags
open_tags = open_tags[i+1:]
else:
# Add it to the start of the open tags list
open_tags.insert(0, tagname)
if words <= length:
# Don't try to close tags if we don't need to truncate
return s
out = s[:ellipsis_pos] + ' ...'
# Close any tags still open
for tag in open_tags:
out += '</%s>' % tag
# Return string
return out
def get_valid_filename(s):
"""
Returns the given string converted to a string that can be used for a clean

View File

@ -484,6 +484,29 @@ Alternatively, you can use a symlink called ``django`` that points to the
location of the branch's ``django`` package. If you want to switch back, just
change the symlink to point to the old code.
A third option is to use a `path file`_ (``<something>.pth``) which should
work on all systems (including Windows, which doesn't have symlinks
available). First, make sure there are no files, directories or symlinks named
``django`` in your ``site-packages`` directory. Then create a text file named
``django.pth`` and save it to your ``site-packages`` directory. That file
should contain a path to your copy of Django on a single line and optional
comments. Here is an example that points to multiple branches. Just uncomment
the line for the branch you want to use ('Trunk' in this example) and make
sure all other lines are commented::
# Trunk is a svn checkout of:
# http://code.djangoproject.com/svn/django/trunk/
#
/path/to/trunk
# <branch> is a svn checkout of:
# http://code.djangoproject.com/svn/django/branches/<branch>/
#
#/path/to/<branch>
# On windows a path may look like this:
# C:/path/to/<branch>
If you're using Django 0.95 or earlier and installed it using
``python setup.py install``, you'll have a directory called something like
``Django-0.95-py2.4.egg`` instead of ``django``. In this case, edit the file
@ -491,6 +514,8 @@ If you're using Django 0.95 or earlier and installed it using
file. Then copy the branch's version of the ``django`` directory into
``site-packages``.
.. _path file: http://docs.python.org/lib/module-site.html
Official releases
=================

View File

@ -17,7 +17,12 @@ two things for you before delegating to ``django-admin.py``:
The ``django-admin.py`` script should be on your system path if you installed
Django via its ``setup.py`` utility. If it's not on your path, you can find it in
``site-packages/django/bin`` within your Python installation. Consider
symlinking to it from some place on your path, such as ``/usr/local/bin``.
symlinking it from some place on your path, such as ``/usr/local/bin``.
For Windows users, who do not have symlinking functionality available, you
can copy ``django-admin.py`` to a location on your existing path or edit the
``PATH`` settings (under ``Settings - Control Panel - System - Advanced - Environment...``)
to point to its installed location.
Generally, when working on a single Django project, it's easier to use
``manage.py``. Use ``django-admin.py`` with ``DJANGO_SETTINGS_MODULE``, or the

View File

@ -274,7 +274,7 @@ In your Web root directory, add this to a file named ``.htaccess`` ::
Then, create a small script that tells Apache how to spawn your FastCGI
program. Create a file ``mysite.fcgi`` and place it in your Web directory, and
be sure to make it executable ::
be sure to make it executable::
#!/usr/bin/python
import sys, os

View File

@ -173,10 +173,10 @@ creation view that takes validation into account::
# Check for validation errors
errors = manipulator.get_validation_errors(new_data)
manipulator.do_html2python(new_data)
if errors:
return render_to_response('places/errors.html', {'errors': errors})
else:
manipulator.do_html2python(new_data)
new_place = manipulator.save(new_data)
return HttpResponse("Place created: %s" % new_place)
@ -229,10 +229,10 @@ Below is the finished view::
# Check for errors.
errors = manipulator.get_validation_errors(new_data)
manipulator.do_html2python(new_data)
if not errors:
# No errors. This means we can save the data!
manipulator.do_html2python(new_data)
new_place = manipulator.save(new_data)
# Redirect to the object's "edit" page. Always use a redirect
@ -324,8 +324,8 @@ about editing an existing one? It's shockingly similar to creating a new one::
if request.method == 'POST':
new_data = request.POST.copy()
errors = manipulator.get_validation_errors(new_data)
manipulator.do_html2python(new_data)
if not errors:
manipulator.do_html2python(new_data)
manipulator.save(new_data)
# Do a post-after-redirect so that reload works, etc.
@ -406,8 +406,8 @@ Here's a simple function that might drive the above form::
if request.method == 'POST':
new_data = request.POST.copy()
errors = manipulator.get_validation_errors(new_data)
manipulator.do_html2python(new_data)
if not errors:
manipulator.do_html2python(new_data)
# Send e-mail using new_data here...

View File

@ -29,7 +29,7 @@ Test your installation by importing it in the Python interactive interpreter::
If that command doesn't raise any errors, the installation worked.
.. _user guide: http://www.reportlab.org/rsrc/userguide.pdf
.. _user guide: http://www.reportlab.com/docs/userguide.pdf
Write your view
===============

View File

@ -827,6 +827,11 @@ manual configuration option (see below), Django will *not* touch the ``TZ``
environment variable, and it'll be up to you to ensure your processes are
running in the correct environment.
.. note::
Django cannot reliably use alternate time zones in a Windows environment.
If you're running Django on Windows, this variable must be set to match the
system timezone.
URL_VALIDATOR_USER_AGENT
------------------------

View File

@ -478,6 +478,22 @@ This example illustrates all possible attributes and methods for a ``Feed`` clas
categories = ("python", "django") # Hard-coded list of categories.
# COPYRIGHT NOTICE -- One of the following three is optional. The
# framework looks for them in this order.
def copyright(self, obj):
"""
Takes the object returned by get_object() and returns the feed's
copyright notice as a normal Python string.
"""
def copyright(self):
"""
Returns the feed's copyright notice as a normal Python string.
"""
copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice.
# ITEMS -- One of the following three is required. The framework looks
# for them in this order.
@ -659,6 +675,23 @@ This example illustrates all possible attributes and methods for a ``Feed`` clas
item_categories = ("python", "django") # Hard-coded categories.
# ITEM COPYRIGHT NOTICE (only applicable to Atom feeds) -- One of the
# following three is optional. The framework looks for them in this
# order.
def item_copyright(self, obj):
"""
Takes an item, as returned by items(), and returns the item's
copyright notice as a normal Python string.
"""
def item_copyright(self):
"""
Returns the copyright notice for every item in the feed.
"""
item_copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice.
The low-level framework
=======================

View File

@ -253,6 +253,16 @@ Here are some tips for working with inheritance:
if you want to add to the contents of a parent block instead of
completely overriding it.
* **New in Django development version:** For extra readability, you can
optionally give a *name* to your ``{% endblock %}`` tag. For example::
{% block content %}
...
{% endblock content %}
In larger templates, this technique helps you see which ``{% block %}``
tags are being closed.
Finally, note that you can't define multiple ``{% block %}`` tags with the same
name in the same template. This limitation exists because a block tag works in
"both" directions. That is, a block tag doesn't just provide a hole to fill --
@ -819,6 +829,40 @@ The argument tells which template bit to output:
Note: ``opencomment`` and ``closecomment`` are new in the Django development version.
url
~~~
**New in Django development version**
**Note that the syntax for this tag may change in the future, as we make it more robust.**
Returns an absolute URL (i.e., a URL without the domain name) matching a given
view function and optional parameters. This is a way to output links without
violating the DRY principle by having to hard-code URLs in your templates::
{% url path.to.some_view arg1,arg2,name1=value1 %}
The first argument is a path to a view function in the format
``package.package.module.function``. Additional arguments are optional and
should be comma-separated values that will be used as positional and keyword
arguments in the URL. All arguments required by the URLconf should be present.
For example, suppose you have a view, ``app_name.client``, whose URLconf takes
a client ID. The URLconf line might look like this::
('^client/(\d+)/$', 'app_name.client')
If this app's URLconf is included into the project's URLconf under a path
such as this::
('^clients/', include('project_name.app_name.urls'))
...then, in a template, you can create a link to this view like this::
{% url app_name.client client.id %}
The template tag will output the string ``/clients/client/123/``.
widthratio
~~~~~~~~~~
@ -1133,6 +1177,16 @@ Truncates a string after a certain number of words.
**Argument:** Number of words to truncate after
truncatewords_html
~~~~~~~~~~~~~~~~~~
Similar to ``truncatewords``, except that it is aware of HTML tags. Any tags
that are opened in the string and not closed before the truncation point, are
closed immediately after the truncation.
This is less efficient than ``truncatewords``, so should only be used when it
is being passed HTML text.
unordered_list
~~~~~~~~~~~~~~

View File

@ -801,6 +801,70 @@ Python 2.4 and above::
If you leave off the ``name`` argument, as in the second example above, Django
will use the function's name as the tag name.
Passing template variables to the tag
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Although you can pass any number of arguments to a template tag using
``token.split_contents()``, the arguments are all unpacked as
string literals. A little more work is required in order to dynamic content (a
template variable) to a template tag as an argument.
While the previous examples have formatted the current time into a string and
returned the string, suppose you wanted to pass in a ``DateTimeField`` from an
object and have the template tag format that date-time::
<p>This post was last updated at {% format_time blog_entry.date_updated "%Y-%m-%d %I:%M %p" %}.</p>
Initially, ``token.split_contents()`` will return three values:
1. The tag name ``format_time``.
2. The string "blog_entry.date_updated" (without the surrounding quotes).
3. The formatting string "%Y-%m-%d %I:%M %p". The return value from
``split_contents()`` will include the leading and trailing quotes for
string literals like this.
Now your tag should begin to look like this::
from django import template
def do_format_time(parser, token):
try:
# split_contents() knows not to split quoted strings.
tag_name, date_to_be_formatted, format_string = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError, "%r tag requires exactly two arguments" % token.contents[0]
if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
return FormatTimeNode(date_to_be_formatted, format_string[1:-1])
You also have to change the renderer to retrieve the actual contents of the
``date_updated`` property of the ``blog_entry`` object. This can be
accomplished by using the ``resolve_variable()`` function in
``django.template``. You pass ``resolve_variable()`` the variable name and the
current context, available in the ``render`` method::
from django import template
from django.template import resolve_variable
import datetime
class FormatTimeNode(template.Node):
def __init__(self, date_to_be_formatted, format_string):
self.date_to_be_formatted = date_to_be_formatted
self.format_string = format_string
def render(self, context):
try:
actual_date = resolve_variable(self.date_to_be_formatted, context)
return actual_date.strftime(self.format_string)
except VariableDoesNotExist:
return ''
``resolve_variable`` will try to resolve ``blog_entry.date_updated`` and then
format it accordingly.
.. note::
The ``resolve_variable()`` function will throw a ``VariableDoesNotExist``
exception if it cannot resolve the string passed to it in the current
context of the page.
Shortcut for simple tags
~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -198,11 +198,6 @@ used as test conditions.
.. _Twill: http://twill.idyll.org/
.. _Selenium: http://www.openqa.org/selenium/
The Test Client is stateful; if a cookie is returned as part of a response,
that cookie is provided as part of the next request issued to that Client
instance. Expiry policies for these cookies are not followed; if you want
a cookie to expire, either delete it manually from ``client.cookies``, or
create a new Client instance (which will effectively delete all cookies).
Making requests
~~~~~~~~~~~~~~~
@ -296,6 +291,44 @@ for testing purposes:
.. _RFC2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
Exceptions
~~~~~~~~~~
If you point the Test Client at a view that raises an exception, that exception
will be visible in the test case. You can then use a standard ``try...catch``
block, or ``unittest.TestCase.assertRaises()`` to test for exceptions.
The only exceptions that are not visible in a Test Case are ``Http404``,
``PermissionDenied`` and ``SystemExit``. Django catches these exceptions
internally and converts them into the appropriate HTTP responses codes.
Persistent state
~~~~~~~~~~~~~~~~
The Test Client is stateful; if a cookie is returned as part of a response,
that cookie is provided as part of the next request issued by that Client
instance. Expiry policies for these cookies are not followed; if you want
a cookie to expire, either delete it manually or create a new Client
instance (which will effectively delete all cookies).
There are two properties of the Test Client which are used to store persistent
state information. If necessary, these properties can be interrogated as
part of a test condition.
=============== ==========================================================
Property Description
=============== ==========================================================
``cookies`` A Python ``SimpleCookie`` object, containing the current
values of all the client cookies.
``session`` A dictionary-like object containing session information.
See the `session documentation`_ for full details.
.. _`session documentation`: ../sessions/
Example
~~~~~~~
The following is a simple unit test using the Test Client::
import unittest

19
scripts/rpm-install.sh Normal file
View File

@ -0,0 +1,19 @@
#! /bin/sh
#
# this file is *inserted* into the install section of the generated
# spec file
#
# this is, what dist.py normally does
python setup.py install --root=${RPM_BUILD_ROOT} --record="INSTALLED_FILES"
for i in `cat INSTALLED_FILES`; do
if [ -f ${RPM_BUILD_ROOT}/$i ]; then
echo $i >>FILES
fi
if [ -d ${RPM_BUILD_ROOT}/$i ]; then
echo %dir $i >>DIRS
fi
done
cat DIRS FILES >INSTALLED_FILES

View File

@ -1,3 +1,4 @@
[bdist_rpm]
doc_files = docs/*.txt
install-script = scripts/rpm-install.sh

View File

@ -58,6 +58,17 @@ Article 4
>>> Article.objects.filter(headline__startswith='Blah blah').count()
0L
# count() should respect sliced query sets.
>>> articles = Article.objects.all()
>>> articles.count()
7L
>>> articles[:4].count()
4
>>> articles[1:100].count()
6L
>>> articles[10:100].count()
0
# Date and date/time lookups can also be done with strings.
>>> Article.objects.filter(pub_date__exact='2005-07-27 00:00:00').count()
3L
@ -198,6 +209,8 @@ DoesNotExist: Article matching query does not exist.
[]
>>> Article.objects.none().count()
0
>>> [article for article in Article.objects.none().iterator()]
[]
# using __in with an empty list should return an empty query set
>>> Article.objects.filter(id__in=[])
@ -206,4 +219,15 @@ DoesNotExist: Article matching query does not exist.
>>> Article.objects.exclude(id__in=[])
[<Article: Article with \ backslash>, <Article: Article% with percent sign>, <Article: Article_ with underscore>, <Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>]
# Programming errors are pointed out with nice error messages
>>> Article.objects.filter(pub_date_year='2005').count()
Traceback (most recent call last):
...
TypeError: Cannot resolve keyword 'pub_date_year' into field
>>> Article.objects.filter(headline__starts='Article')
Traceback (most recent call last):
...
TypeError: Cannot resolve keyword 'headline__starts' into field
"""}

View File

@ -99,3 +99,29 @@ class ClientTest(unittest.TestCase):
response = self.client.login('/test_client/login_protected_view/', 'otheruser', 'nopassword')
self.assertFalse(response)
def test_session_modifying_view(self):
"Request a page that modifies the session"
# Session value isn't set initially
try:
self.client.session['tobacconist']
self.fail("Shouldn't have a session value")
except KeyError:
pass
from django.contrib.sessions.models import Session
response = self.client.post('/test_client/session_view/')
# Check that the session was modified
self.assertEquals(self.client.session['tobacconist'], 'hovercraft')
def test_view_with_exception(self):
"Request a page that is known to throw an error"
self.assertRaises(KeyError, self.client.get, "/test_client/broken_view/")
#Try the same assertion, a different way
try:
self.client.get('/test_client/broken_view/')
self.fail('Should raise an error')
except KeyError:
pass

View File

@ -6,4 +6,6 @@ urlpatterns = patterns('',
(r'^post_view/$', views.post_view),
(r'^redirect_view/$', views.redirect_view),
(r'^login_protected_view/$', views.login_protected_view),
(r'^session_view/$', views.session_view),
(r'^broken_view/$', views.broken_view)
)

View File

@ -33,3 +33,16 @@ def login_protected_view(request):
return HttpResponse(t.render(c))
login_protected_view = login_required(login_protected_view)
def session_view(request):
"A view that modifies the session"
request.session['tobacconist'] = 'hovercraft'
t = Template('This is a view that modifies the session.',
name='Session Modifying View Template')
c = Context()
return HttpResponse(t.render(c))
def broken_view(request):
"""A view which just raises an exception, simulating a broken view."""
raise KeyError("Oops! Looks like you wrote some bad code.")

View File

@ -87,6 +87,20 @@ u'\xeb'
>>> truncatewords('A sentence with a few words in it', 'not a number')
'A sentence with a few words in it'
>>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 0)
''
>>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 2)
'<p>one <a href="#">two ...</a></p>'
>>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 4)
'<p>one <a href="#">two - three <br>four ...</a></p>'
>>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 5)
'<p>one <a href="#">two - three <br>four</a> five</p>'
>>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 100)
'<p>one <a href="#">two - three <br>four</a> five</p>'
>>> upper('Mixed case input')
'MIXED CASE INPUT'
@ -97,6 +111,8 @@ u'\xcb'
>>> urlencode('jack & jill')
'jack%20%26%20jill'
>>> urlencode(1)
'1'
>>> urlizetrunc('http://short.com/', 20)

View File

@ -1493,7 +1493,7 @@ u'1'
>>> f.clean('3')
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
>>> f = ChoiceField(choices=[('1', '1'), ('2', '2')], required=False)
>>> f.clean('')
@ -1507,7 +1507,7 @@ u'1'
>>> f.clean('3')
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
>>> f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')])
>>> f.clean('J')
@ -1515,7 +1515,7 @@ u'J'
>>> f.clean('John')
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. John is not one of the available choices.']
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
# NullBooleanField ############################################################

View File

@ -390,6 +390,21 @@ class Templates(unittest.TestCase):
'include03': ('{% include template_name %}', {'template_name': 'basic-syntax02', 'headline': 'Included'}, "Included"),
'include04': ('a{% include "nonexistent" %}b', {}, "ab"),
### NAMED ENDBLOCKS #######################################################
# Basic test
'namedendblocks01': ("1{% block first %}_{% block second %}2{% endblock second %}_{% endblock first %}3", {}, '1_2_3'),
# Unbalanced blocks
'namedendblocks02': ("1{% block first %}_{% block second %}2{% endblock first %}_{% endblock second %}3", {}, template.TemplateSyntaxError),
'namedendblocks03': ("1{% block first %}_{% block second %}2{% endblock %}_{% endblock second %}3", {}, template.TemplateSyntaxError),
'namedendblocks04': ("1{% block first %}_{% block second %}2{% endblock second %}_{% endblock third %}3", {}, template.TemplateSyntaxError),
'namedendblocks05': ("1{% block first %}_{% block second %}2{% endblock first %}", {}, template.TemplateSyntaxError),
# Mixed named and unnamed endblocks
'namedendblocks06': ("1{% block first %}_{% block second %}2{% endblock %}_{% endblock first %}3", {}, '1_2_3'),
'namedendblocks07': ("1{% block first %}_{% block second %}2{% endblock second %}_{% endblock %}3", {}, '1_2_3'),
### INHERITANCE ###########################################################
# Standard template with no inheritance
@ -630,6 +645,17 @@ class Templates(unittest.TestCase):
# Compare to a given parameter
'timeuntil04' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=1), 'b':NOW - timedelta(days=2)}, '1 day'),
'timeuntil05' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=2), 'b':NOW - timedelta(days=2, minutes=1)}, '1 minute'),
### URL TAG ########################################################
# Successes
'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
'url02' : ('{% url regressiontests.templates.views.client_action client.id,action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url03' : ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'),
# Failures
'url04' : ('{% url %}', {}, template.TemplateSyntaxError),
'url05' : ('{% url no_such_view %}', {}, ''),
'url06' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''),
}
# Register our custom template loader.

View File

@ -0,0 +1,10 @@
from django.conf.urls.defaults import *
from regressiontests.templates import views
urlpatterns = patterns('',
# Test urls for testing reverse lookups
(r'^$', views.index),
(r'^client/(\d+)/$', views.client),
(r'^client/(\d+)/(?P<action>[^/]+)/$', views.client_action),
)

View File

@ -0,0 +1,10 @@
# Fake views for testing url reverse lookup
def index(request):
pass
def client(request, id):
pass
def client_action(request, id, action):
pass

View File

@ -77,13 +77,19 @@ def django_tests(verbosity, tests_to_run):
old_root_urlconf = settings.ROOT_URLCONF
old_template_dirs = settings.TEMPLATE_DIRS
old_use_i18n = settings.USE_I18N
old_middleware_classes = settings.MIDDLEWARE_CLASSES
# Redirect some settings for the duration of these tests
# Redirect some settings for the duration of these tests.
settings.TEST_DATABASE_NAME = TEST_DATABASE_NAME
settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS
settings.ROOT_URLCONF = 'urls'
settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR),)
settings.USE_I18N = True
settings.MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.common.CommonMiddleware',
)
# Load all the ALWAYS_INSTALLED_APPS.
# (This import statement is intentionally delayed until after we
@ -91,7 +97,7 @@ def django_tests(verbosity, tests_to_run):
from django.db.models.loading import get_apps, load_app
get_apps()
# Load all the test model apps
# Load all the test model apps.
test_models = []
for model_dir, model_name in get_test_models():
model_label = '.'.join([model_dir, model_name])
@ -109,7 +115,7 @@ def django_tests(verbosity, tests_to_run):
sys.stderr.write("Error while importing %s:" % model_name + ''.join(traceback.format_exception(*sys.exc_info())[1:]))
continue
# Add tests for invalid models
# Add tests for invalid models.
extra_tests = []
for model_dir, model_name in get_invalid_models():
model_label = '.'.join([model_dir, model_name])
@ -120,12 +126,13 @@ def django_tests(verbosity, tests_to_run):
from django.test.simple import run_tests
run_tests(test_models, verbosity, extra_tests=extra_tests)
# Restore the old settings
# Restore the old settings.
settings.INSTALLED_APPS = old_installed_apps
settings.TESTS_DATABASE_NAME = old_test_database_name
settings.ROOT_URLCONF = old_root_urlconf
settings.TEMPLATE_DIRS = old_template_dirs
settings.USE_I18N = old_use_i18n
settings.MIDDLEWARE_CLASSES = old_middleware_classes
if __name__ == "__main__":
from optparse import OptionParser

View File

@ -7,4 +7,7 @@ urlpatterns = patterns('',
# Always provide the auth system login and logout views
(r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}),
(r'^accounts/logout/$', 'django.contrib.auth.views.login'),
# test urlconf for {% url %} template tag
(r'^url_tag/', include('regressiontests.templates.urls')),
)