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:
parent
af908e2e8d
commit
649423e81c
17
AUTHORS
17
AUTHORS
@ -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,15 +88,17 @@ 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/>
|
||||
Tom Insam
|
||||
Baurzhan Ismagulov <ibr@radix50.net>
|
||||
jcrasta@gmail.com
|
||||
Michael Josephson <http://www.sdjournal.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
|
||||
|
10
MANIFEST.in
10
MANIFEST.in
@ -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 *
|
||||
|
@ -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,7 +106,9 @@ class Settings(object):
|
||||
new_installed_apps.append(app)
|
||||
self.INSTALLED_APPS = new_installed_apps
|
||||
|
||||
# move the time zone info into os.environ
|
||||
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):
|
||||
|
@ -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:
|
||||
|
@ -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."
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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,7 +893,13 @@ 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
|
||||
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.
|
||||
|
@ -67,6 +67,9 @@ class RelatedObject(object):
|
||||
# A one-to-one relationship, so just return the single related
|
||||
# object
|
||||
return [attr]
|
||||
else:
|
||||
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
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
=================
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
if not errors:
|
||||
manipulator.do_html2python(new_data)
|
||||
if not errors:
|
||||
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)
|
||||
if not errors:
|
||||
manipulator.do_html2python(new_data)
|
||||
if not errors:
|
||||
|
||||
# Send e-mail using new_data here...
|
||||
|
||||
|
@ -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
|
||||
===============
|
||||
|
@ -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
|
||||
------------------------
|
||||
|
||||
|
@ -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
|
||||
=======================
|
||||
|
@ -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
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -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
19
scripts/rpm-install.sh
Normal 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
|
@ -1,3 +1,4 @@
|
||||
[bdist_rpm]
|
||||
doc_files = docs/*.txt
|
||||
install-script = scripts/rpm-install.sh
|
||||
|
||||
|
@ -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
|
||||
|
||||
"""}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
)
|
||||
|
@ -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.")
|
||||
|
@ -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)
|
||||
|
@ -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 ############################################################
|
||||
|
||||
|
@ -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.
|
||||
|
10
tests/regressiontests/templates/urls.py
Normal file
10
tests/regressiontests/templates/urls.py
Normal 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),
|
||||
)
|
10
tests/regressiontests/templates/views.py
Normal file
10
tests/regressiontests/templates/views.py
Normal 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
|
@ -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
|
||||
|
@ -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')),
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user