From d304ccad8c24e29b7328019b81e93f792af482d0 Mon Sep 17 00:00:00 2001 From: Joseph Kocherhans Date: Fri, 15 Jun 2007 04:02:33 +0000 Subject: [PATCH] newforms-admin: Merged to trunk [5473]. git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@5475 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 5 + django/__init__.py | 7 + django/conf/global_settings.py | 3 +- django/conf/locale/nl/LC_MESSAGES/django.mo | Bin 38004 -> 38003 bytes django/conf/locale/nl/LC_MESSAGES/django.po | 3 +- django/contrib/auth/__init__.py | 5 + django/contrib/sessions/models.py | 6 +- django/core/management.py | 13 +- django/db/backends/postgresql/base.py | 13 +- .../db/backends/postgresql_psycopg2/base.py | 13 +- django/db/backends/util.py | 2 +- django/newforms/models.py | 5 +- django/template/defaulttags.py | 70 +++++--- django/test/client.py | 7 +- django/views/debug.py | 12 +- docs/authentication.txt | 2 +- docs/db-api.txt | 8 +- docs/man/django-admin.1 | 162 ++++++++++++++++++ docs/model-api.txt | 5 + docs/newforms.txt | 145 ++++++++++++++-- docs/templates.txt | 22 ++- scripts/rpm-install.sh | 5 + .../serializers_regress/tests.py | 17 +- tests/regressiontests/templates/tests.py | 14 ++ 24 files changed, 465 insertions(+), 79 deletions(-) create mode 100644 docs/man/django-admin.1 diff --git a/AUTHORS b/AUTHORS index fde35507a0..cd136fe06c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -73,6 +73,7 @@ answer newbie questions, and generally made Django that much better: Michal Chruszcz Ian Clelland crankycoder@gmail.com + Pete Crosier Matt Croydon flavio.curella@gmail.com Jure Cuhalev @@ -130,6 +131,7 @@ answer newbie questions, and generally made Django that much better: junzhang.jn@gmail.com Antti Kaihola Ben Dean Kawamura + ian.g.kelly@gmail.com Garth Kidd kilian Sune Kirkeby @@ -169,6 +171,7 @@ answer newbie questions, and generally made Django that much better: mitakummaa@gmail.com mmarshall Eric Moritz + mrmachine Robin Munn Robert Myers Nebojša Dorđević @@ -218,6 +221,7 @@ answer newbie questions, and generally made Django that much better: Aaron Swartz Ville Säävuori Tyson Tate + Frank Tegtmeyer thebjorn Zach Thompson Tom Tobin @@ -234,6 +238,7 @@ answer newbie questions, and generally made Django that much better: wangchun Dan Watson Chris Wesseling + James Wheare charly.wilhelm@gmail.com Rachel Willmer Gary Wilson diff --git a/django/__init__.py b/django/__init__.py index 71ac421a6d..b6540949f8 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1 +1,8 @@ VERSION = (0, 97, 'newforms-admin') + +def get_version(): + "Returns the version as a human-format string." + v = '.'.join([str(i) for i in VERSION[:-1]]) + if VERSION[-1]: + v += '-' + VERSION[-1] + return v diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 2be6a4ef95..8bdeb64efc 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -241,7 +241,8 @@ TRANSACTIONS_MANAGED = False # The User-Agent string to use when checking for URL validity through the # isExistingURL validator. -URL_VALIDATOR_USER_AGENT = "Django/0.96pre (http://www.djangoproject.com)" +from django import get_version +URL_VALIDATOR_USER_AGENT = "Django/%s (http://www.djangoproject.com)" % get_version() ############## # MIDDLEWARE # diff --git a/django/conf/locale/nl/LC_MESSAGES/django.mo b/django/conf/locale/nl/LC_MESSAGES/django.mo index f4be11f7c5dd91fcf546271dc7eecabfd7ee902e..4c70c28ff4f42542acfa2e5c8ff7fdb8b424225c 100644 GIT binary patch delta 608 zcmXZYzbnLX9LMpu@10+V^DA`MZPqJ;GEg_@Qgj(9GVljTbc2-dAQsE_dv36Z@fJ}h zDYL<5vsxG|h{3m9dA{mBJwBiJ=g0fi`==3nH-hV%eAsDbUy)fCCfI{PY}SWa%wYjj zIOR^eCDi>{?8QYK!d1-Rrl0Tn{t)#~WqCRSQ zy{%@}&A#8wp@NO0?oFTqO=3HiP<_j_&}@K3g~AxFp&C`)12=Zh-Ani0eRQ8uL0(Z4 z`SAK5Dp2OcYy?M9{fZ$AB`TvXET9rEp(e3{n#?*X*cK}JK57z2sQ1UH_c5x$nV&aN zLGCb*52*VusG#$2EQVQpq8_H}W`meVH7=q?H-maO=lhDgjGEv^Gbyxog6eiNiS|M(t)+FPX_-%wQ0kbz?6M;}`}w zg^? z>#4-7gL|JFqJoX1-c6taO=24JsJVqCqKQM1LKfFhgZAAcH*v4sTlc|za$itE-cTF) z^m-E&D05~ugjv+QT*N_%3aAGQsKiUCO{}0cvyKY3gG#PGZQ=yge~Ri)Py?>~dkq!j z9z%RYy?;dooqy*r$f1sEOrM+e;|OYS4z;=&RO6iAi|#UNgB#VxXmc;v-Q24Fln#S` DQcGO~ diff --git a/django/conf/locale/nl/LC_MESSAGES/django.po b/django/conf/locale/nl/LC_MESSAGES/django.po index 89862155d8..9aaeef8af9 100644 --- a/django/conf/locale/nl/LC_MESSAGES/django.po +++ b/django/conf/locale/nl/LC_MESSAGES/django.po @@ -2202,10 +2202,11 @@ msgstr[0] "dag" msgstr[1] "dagen" #: utils/timesince.py:16 +# In the timesince context it is stilistically wrong to use the plural for hour in Dutch. msgid "hour" msgid_plural "hours" msgstr[0] "uur" -msgstr[1] "uren" +msgstr[1] "uur" #: utils/timesince.py:17 msgid "minute" diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py index d37450805f..14ae020674 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -53,6 +53,8 @@ def login(request, user): user.save() request.session[SESSION_KEY] = user.id request.session[BACKEND_SESSION_KEY] = user.backend + if hasattr(request, 'user'): + request.user = user def logout(request): """ @@ -66,6 +68,9 @@ def logout(request): del request.session[BACKEND_SESSION_KEY] except KeyError: pass + if hasattr(request, 'user'): + from django.contrib.auth.models import AnonymousUser + request.user = AnonymousUser() def get_user(request): from django.contrib.auth.models import AnonymousUser diff --git a/django/contrib/sessions/models.py b/django/contrib/sessions/models.py index 77718407e1..521a2abee9 100644 --- a/django/contrib/sessions/models.py +++ b/django/contrib/sessions/models.py @@ -1,4 +1,4 @@ -import base64, md5, random, sys, datetime +import base64, md5, random, sys, datetime, os, time import cPickle as pickle from django.db import models from django.utils.translation import gettext_lazy as _ @@ -14,9 +14,9 @@ class SessionManager(models.Manager): def get_new_session_key(self): "Returns session key that isn't being used." # The random module is seeded when this Apache child is created. - # Use person_id and SECRET_KEY as added salt. + # Use SECRET_KEY as added salt. while 1: - session_key = md5.new(str(random.randint(0, sys.maxint - 1)) + str(random.randint(0, sys.maxint - 1)) + settings.SECRET_KEY).hexdigest() + session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1), os.getpid(), time.time(), settings.SECRET_KEY)).hexdigest() try: self.get(session_key=session_key) except self.model.DoesNotExist: diff --git a/django/core/management.py b/django/core/management.py index a273d275c1..29ee9c2f90 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -3,14 +3,17 @@ import django from django.core.exceptions import ImproperlyConfigured -import os, re, shutil, sys, textwrap from optparse import OptionParser from django.utils import termcolors +import os, re, shutil, sys, textwrap # For Python 2.3 if not hasattr(__builtins__, 'set'): from sets import Set as set +# For backwards compatibility: get_version() used to be in this module. +get_version = django.get_version + MODULE_TEMPLATE = ''' {%% if perms.%(app)s.%(addperm)s or perms.%(app)s.%(changeperm)s %%} {%% if perms.%(app)s.%(changeperm)s %%}{%% endif %%}%(name)s{%% if perms.%(app)s.%(changeperm)s %%}{%% endif %%} @@ -93,14 +96,6 @@ def _get_sequence_list(): # field as the field to which it points. get_rel_data_type = lambda f: (f.get_internal_type() in ('AutoField', 'PositiveIntegerField', 'PositiveSmallIntegerField')) and 'IntegerField' or f.get_internal_type() -def get_version(): - "Returns the version as a human-format string." - from django import VERSION - v = '.'.join([str(i) for i in VERSION[:-1]]) - if VERSION[-1]: - v += '-' + VERSION[-1] - return v - def get_sql_create(app): "Returns a list of the CREATE TABLE SQL statements for the given app." from django.db import get_creation_module, models diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index a5b38a883d..fedbb6b7f1 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -219,22 +219,27 @@ def get_sql_sequence_reset(style, model_list): from django.db import models output = [] for model in model_list: + # Use `coalesce` to set the sequence for each model to the max pk value if there are records, + # or 1 if there are none. Set the `is_called` property (the third argument to `setval`) to true + # if there are records (as the max pk value is already in use), otherwise set it to false. for f in model._meta.fields: if isinstance(f, models.AutoField): - output.append("%s setval('%s', (%s max(%s) %s %s));" % \ + output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ (style.SQL_KEYWORD('SELECT'), style.SQL_FIELD(quote_name('%s_%s_seq' % (model._meta.db_table, f.column))), - style.SQL_KEYWORD('SELECT'), style.SQL_FIELD(quote_name(f.column)), + style.SQL_FIELD(quote_name(f.column)), + style.SQL_KEYWORD('IS NOT'), style.SQL_KEYWORD('FROM'), style.SQL_TABLE(quote_name(model._meta.db_table)))) break # Only one AutoField is allowed per model, so don't bother continuing. for f in model._meta.many_to_many: - output.append("%s setval('%s', (%s max(%s) %s %s));" % \ + output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ (style.SQL_KEYWORD('SELECT'), style.SQL_FIELD(quote_name('%s_id_seq' % f.m2m_db_table())), - style.SQL_KEYWORD('SELECT'), style.SQL_FIELD(quote_name('id')), + style.SQL_FIELD(quote_name('id')), + style.SQL_KEYWORD('IS NOT'), style.SQL_KEYWORD('FROM'), style.SQL_TABLE(f.m2m_db_table()))) return output diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index 9898b121f4..d9ad363ac1 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -176,22 +176,27 @@ def get_sql_sequence_reset(style, model_list): from django.db import models output = [] for model in model_list: + # Use `coalesce` to set the sequence for each model to the max pk value if there are records, + # or 1 if there are none. Set the `is_called` property (the third argument to `setval`) to true + # if there are records (as the max pk value is already in use), otherwise set it to false. for f in model._meta.fields: if isinstance(f, models.AutoField): - output.append("%s setval('%s', (%s max(%s) %s %s));" % \ + output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ (style.SQL_KEYWORD('SELECT'), style.SQL_FIELD(quote_name('%s_%s_seq' % (model._meta.db_table, f.column))), - style.SQL_KEYWORD('SELECT'), style.SQL_FIELD(quote_name(f.column)), + style.SQL_FIELD(quote_name(f.column)), + style.SQL_KEYWORD('IS NOT'), style.SQL_KEYWORD('FROM'), style.SQL_TABLE(quote_name(model._meta.db_table)))) break # Only one AutoField is allowed per model, so don't bother continuing. for f in model._meta.many_to_many: - output.append("%s setval('%s', (%s max(%s) %s %s));" % \ + output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ (style.SQL_KEYWORD('SELECT'), style.SQL_FIELD(quote_name('%s_id_seq' % f.m2m_db_table())), - style.SQL_KEYWORD('SELECT'), style.SQL_FIELD(quote_name('id')), + style.SQL_FIELD(quote_name('id')), + style.SQL_KEYWORD('IS NOT'), style.SQL_KEYWORD('FROM'), style.SQL_TABLE(f.m2m_db_table()))) return output diff --git a/django/db/backends/util.py b/django/db/backends/util.py index 18f120b600..81c752e664 100644 --- a/django/db/backends/util.py +++ b/django/db/backends/util.py @@ -91,7 +91,7 @@ def typecast_boolean(s): return str(s)[0].lower() == 't' def typecast_decimal(s): - if s is None: + if s is None or s == '': return None return decimal.Decimal(s) diff --git a/django/newforms/models.py b/django/newforms/models.py index 700c9fe33d..874e1148af 100644 --- a/django/newforms/models.py +++ b/django/newforms/models.py @@ -20,9 +20,8 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True """ Saves bound Form ``form``'s cleaned_data into model instance ``instance``. - Assumes ``form`` has a field for every non-AutoField database field in - ``instance``. If commit=True, then the changes to ``instance`` will be - saved to the database. Returns ``instance``. + If commit=True, then the changes to ``instance`` will be saved to the + database. Returns ``instance``. """ from django.db import models opts = instance.__class__._meta diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 371b57e430..6a6665a445 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -5,6 +5,15 @@ from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG from django.template import get_library, Library, InvalidTemplateLibrary from django.conf import settings import sys +import re + +if not hasattr(__builtins__, 'reversed'): + # For Python 2.3. + # From http://www.python.org/doc/current/tut/node11.html + def reversed(data): + for index in xrange(len(data)-1, -1, -1): + yield data[index] + register = Library() @@ -61,8 +70,8 @@ class FirstOfNode(Node): return '' class ForNode(Node): - def __init__(self, loopvar, sequence, reversed, nodelist_loop): - self.loopvar, self.sequence = loopvar, sequence + def __init__(self, loopvars, sequence, reversed, nodelist_loop): + self.loopvars, self.sequence = loopvars, sequence self.reversed = reversed self.nodelist_loop = nodelist_loop @@ -72,7 +81,7 @@ class ForNode(Node): else: reversed = '' return "" % \ - (self.loopvar, self.sequence, len(self.nodelist_loop), reversed) + (', '.join( self.loopvars ), self.sequence, len(self.nodelist_loop), reversed) def __iter__(self): for node in self.nodelist_loop: @@ -102,11 +111,8 @@ class ForNode(Node): values = list(values) len_values = len(values) if self.reversed: - # From http://www.python.org/doc/current/tut/node11.html - def reverse(data): - for index in range(len(data)-1, -1, -1): - yield data[index] - values = reverse(values) + values = reversed(values) + unpack = len(self.loopvars) > 1 for i, item in enumerate(values): context['forloop'] = { # shortcuts for current loop iteration number @@ -120,9 +126,20 @@ class ForNode(Node): 'last': (i == len_values - 1), 'parentloop': parentloop, } - context[self.loopvar] = item + if unpack: + # If there are multiple loop variables, unpack the item into them. + context.update(dict(zip(self.loopvars, item))) + else: + context[self.loopvars[0]] = item for node in self.nodelist_loop: nodelist.append(node.render(context)) + if unpack: + # The loop variables were pushed on to the context so pop them + # off again. This is necessary because the tag lets the length + # of loopvars differ to the length of each set of items and we + # don't want to leave any vars from the previous loop on the + # context. + context.pop() context.pop() return nodelist.render(context) @@ -486,7 +503,7 @@ def do_filter(parser, token): nodelist = parser.parse(('endfilter',)) parser.delete_first_token() return FilterNode(filter_expr, nodelist) -filter = register.tag("filter", do_filter) +do_filter = register.tag("filter", do_filter) #@register.tag def firstof(parser, token): @@ -530,8 +547,14 @@ def do_for(parser, token): {% endfor %} - You can also loop over a list in reverse by using + You can loop over a list in reverse by using ``{% for obj in list reversed %}``. + + You can also unpack multiple values from a two-dimensional array:: + + {% for key,value in dict.items %} + {{ key }}: {{ value }} + {% endfor %} The for loop sets a number of variables available within the loop: @@ -552,18 +575,23 @@ def do_for(parser, token): """ bits = token.contents.split() - if len(bits) == 5 and bits[4] != 'reversed': - raise TemplateSyntaxError, "'for' statements with five words should end in 'reversed': %s" % token.contents - if len(bits) not in (4, 5): - raise TemplateSyntaxError, "'for' statements should have either four or five words: %s" % token.contents - if bits[2] != 'in': - raise TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents - loopvar = bits[1] - sequence = parser.compile_filter(bits[3]) - reversed = (len(bits) == 5) + if len(bits) < 4: + raise TemplateSyntaxError, "'for' statements should have at least four words: %s" % token.contents + + reversed = bits[-1] == 'reversed' + in_index = reversed and -3 or -2 + if bits[in_index] != 'in': + raise TemplateSyntaxError, "'for' statements should use the format 'for x in y': %s" % token.contents + + loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',') + for var in loopvars: + if not var or ' ' in var: + raise TemplateSyntaxError, "'for' tag received an invalid argument: %s" % token.contents + + sequence = parser.compile_filter(bits[in_index+1]) nodelist_loop = parser.parse(('endfor',)) parser.delete_first_token() - return ForNode(loopvar, sequence, reversed, nodelist_loop) + return ForNode(loopvars, sequence, reversed, nodelist_loop) do_for = register.tag("for", do_for) def do_ifequal(parser, token, negate): diff --git a/django/test/client.py b/django/test/client.py index c3110f02ec..e4fd54c23b 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -65,10 +65,7 @@ def encode_multipart(boundary, data): if isinstance(value, file): lines.extend([ '--' + boundary, - 'Content-Disposition: form-data; name="%s"' % key, - '', - '--' + boundary, - 'Content-Disposition: form-data; name="%s_file"; filename="%s"' % (key, value.name), + 'Content-Disposition: form-data; name="%s"; filename="%s"' % (key, value.name), 'Content-Type: application/octet-stream', '', value.read() @@ -251,4 +248,4 @@ class Client: return True else: return False - \ No newline at end of file + diff --git a/django/views/debug.py b/django/views/debug.py index 2530350e26..a534f17b33 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -2,7 +2,7 @@ from django.conf import settings from django.template import Template, Context, TemplateDoesNotExist from django.utils.html import escape from django.http import HttpResponseServerError, HttpResponseNotFound -import os, re +import os, re, sys HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST') @@ -131,6 +131,8 @@ def technical_500_response(request, exc_type, exc_value, tb): 'request': request, 'request_protocol': request.is_secure() and "https" or "http", 'settings': get_safe_settings(), + 'sys_executable' : sys.executable, + 'sys_version_info' : '%d.%d.%d' % sys.version_info[0:3], 'template_info': template_info, 'template_does_not_exist': template_does_not_exist, 'loader_debug_info': loader_debug_info, @@ -334,6 +336,14 @@ TECHNICAL_500_TEMPLATE = """ Exception Location: {{ lastframe.filename|escape }} in {{ lastframe.function|escape }}, line {{ lastframe.lineno }} + + Python Executable: + {{ sys_executable|escape }} + + + Python Version: + {{ sys_version_info }} + {% if template_does_not_exist %} diff --git a/docs/authentication.txt b/docs/authentication.txt index 091a30a895..12b61db538 100644 --- a/docs/authentication.txt +++ b/docs/authentication.txt @@ -730,7 +730,7 @@ Django developers are currently discussing. Default permissions ------------------- -Three basic permissions -- add, create and delete -- are automatically created +Three basic permissions -- add, change and delete -- are automatically created for each Django model that has a ``class Admin`` set. Behind the scenes, these permissions are added to the ``auth_permission`` database table when you run ``manage.py syncdb``. diff --git a/docs/db-api.txt b/docs/db-api.txt index 775913d693..e7b8183f6c 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -388,7 +388,7 @@ The lookup parameters (``**kwargs``) should be in the format described in `Field lookups`_ below. Multiple parameters are joined via ``AND`` in the underlying SQL statement, and the whole thing is enclosed in a ``NOT()``. -This example excludes all entries whose ``pub_date`` is the current date/time +This example excludes all entries whose ``pub_date`` is later than 2005-1-3 AND whose ``headline`` is "Hello":: Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello') @@ -398,8 +398,8 @@ In SQL terms, that evaluates to:: SELECT ... WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello') -This example excludes all entries whose ``pub_date`` is the current date/time -OR whose ``headline`` is "Hello":: +This example excludes all entries whose ``pub_date`` is later than 2005-1-3 +AND whose headline is NOT "Hello":: Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline='Hello') @@ -598,7 +598,7 @@ related ``Person`` *and* the related ``City``:: p = b.author # Doesn't hit the database. c = p.hometown # Doesn't hit the database. - sv = Book.objects.get(id=4) # No select_related() in this example. + b = Book.objects.get(id=4) # No select_related() in this example. p = b.author # Hits the database. c = p.hometown # Hits the database. diff --git a/docs/man/django-admin.1 b/docs/man/django-admin.1 new file mode 100644 index 0000000000..fec5053ccb --- /dev/null +++ b/docs/man/django-admin.1 @@ -0,0 +1,162 @@ +.TH "django-admin.py" "1" "June 2007" "Django Project" "" +.SH "NAME" +django\-admin.py \- Utility script for the Django web framework +.SH "SYNOPSIS" +.B django\-admin.py +.I +.B [options] +.sp +.SH "DESCRIPTION" +This utility script provides commands for creation and maintenance of Django +projects and apps. +.sp +With the exception of +.BI startproject, +all commands listed below can also be performed with the +.BI manage.py +script found at the top level of each Django project directory. +.sp +.SH "ACTIONS" +.TP +.BI "adminindex [" "appname ..." "]" +Prints the admin\-index template snippet for the given app name(s). +.TP +.BI "createcachetable [" "tablename" "]" +Creates the table needed to use the SQL cache backend +.TP +.B dbshell +Runs the command\-line client for the current +.BI DATABASE_ENGINE. +.TP +.B diffsettings +Displays differences between the current +.B settings.py +and Django's default settings. Settings that don't appear in the defaults are +followed by "###". +.TP +.B inspectdb +Introspects the database tables in the database specified in settings.py and outputs a Django +model module. +.TP +.BI "install [" "appname ..." "]" +Executes +.B sqlall +for the given app(s) in the current database. +.TP +.BI "reset [" "appname ..." "]" +Executes +.B sqlreset +for the given app(s) in the current database. +.TP +.BI "runfcgi [" "KEY=val" "] [" "KEY=val" "] " "..." +Runs this project as a FastCGI application. Requires flup. Use +.B runfcgi help +for help on the KEY=val pairs. +.TP +.BI "runserver [" "\-\-noreload" "] [" "\-\-adminmedia=ADMIN_MEDIA_PATH" "] [" "port|ipaddr:port" "]" +Starts a lightweight Web server for development. +.TP +.BI "shell [" "\-\-plain" "]" +Runs a Python interactive interpreter. Tries to use IPython, if it's available. +The +.BI \-\-plain +option forces the use of the standard Python interpreter even when IPython is +installed. +.TP +.BI "sql [" "appname ..." "]" +Prints the CREATE TABLE SQL statements for the given app name(s). +.TP +.BI "sqlall [" "appname ..." "]" +Prints the CREATE TABLE, initial\-data and CREATE INDEX SQL statements for the +given model module name(s). +.TP +.BI "sqlclear [" "appname ..." "]" +Prints the DROP TABLE SQL statements for the given app name(s). +.TP +.BI "sqlindexes [" "appname ..." "]" +Prints the CREATE INDEX SQL statements for the given model module name(s). +.TP +.BI "sqlinitialdata [" "appname ..." "]" +Prints the initial INSERT SQL statements for the given app name(s). +.TP +.BI "sqlreset [" "appname ..." "]" +Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app +name(s). +.TP +.BI "sqlsequencereset [" "appname ..." "]" +Prints the SQL statements for resetting PostgreSQL sequences for the +given app name(s). +.TP +.BI "startapp [" "appname" "]" +Creates a Django app directory structure for the given app name in +the current directory. +.TP +.BI "startproject [" "projectname" "]" +Creates a Django project directory structure for the given project name +in the current directory. +.TP +.BI syncdb +Creates the database tables for all apps in INSTALLED_APPS whose tables +haven't already been created. +.TP +.BI "test [" "\-\-verbosity" "] [" "appname ..." "]" +Runs the test suite for the specified applications, or the entire project if +no apps are specified +.TP +.BI validate +Validates all installed models. +.SH "OPTIONS" +.TP +.I \-\-version +Show program's version number and exit. +.TP +.I \-h, \-\-help +Show this help message and exit. +.TP +.I \-\-settings=SETTINGS +Python path to settings module, e.g. "myproject.settings.main". If +this isn't provided, the DJANGO_SETTINGS_MODULE environment variable +will be used. +.TP +.I \-\-pythonpath=PYTHONPATH +Lets you manually add a directory the Python path, +e.g. "/home/djangoprojects/myproject". +.TP +.I \-\-plain +Use plain Python, not IPython, for the "shell" command. +.TP +.I \-\-noinput +Do not prompt the user for input. +.TP +.I \-\-noreload +Disable the development server's auto\-reloader. +.TP +.I \-\-verbosity=VERBOSITY +Verbosity level: 0=minimal output, 1=normal output, 2=all output. +.TP +.I \-\-adminmedia=ADMIN_MEDIA_PATH +Specifies the directory from which to serve admin media when using the development server. + +.SH "ENVIRONMENT" +.TP +.I DJANGO_SETTINGS_MODULE +In the absence of the +.BI \-\-settings +option, this environment variable defines the settings module to be read. +It should be in Python-import form, e.g. "myproject.settings". + +.SH "SEE ALSO" +Full descriptions of all these options, with examples, as well as documentation +for the rest of the Django framework, can be found on the Django site: +.sp +.I http://www.djangoproject.com/documentation/ +.sp +or in the distributed documentation. +.SH "AUTHORS/CREDITS" +Originally developed at World Online in Lawrence, Kansas, USA. Refer to the +AUTHORS file in the Django distribution for contributors. +.sp +.SH "LICENSE" +New BSD license. For the full license text refer to the LICENSE file in the +Django distribution. + diff --git a/docs/model-api.txt b/docs/model-api.txt index a6bb8a5cfc..09440f2b56 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -447,6 +447,11 @@ and doesn't give a 404 response). The admin represents this as an ```` (a single-line input). +``URLField`` takes an optional argument, ``maxlength``, the maximum length (in +characters) of the field. The maxlength is enforced at the database level and +in Django's validation. If you don't specify ``maxlength``, a default of 200 +is used. + ``USStateField`` ~~~~~~~~~~~~~~~~ diff --git a/docs/newforms.txt b/docs/newforms.txt index bb6c179648..1511791a7d 100644 --- a/docs/newforms.txt +++ b/docs/newforms.txt @@ -299,12 +299,19 @@ required. In this example, the data dictionary doesn't include a value for the In this above example, the ``cleaned_data`` value for ``nick_name`` is set to an empty string, because ``nick_name`` is ``CharField``, and ``CharField``\s treat empty values as an empty string. Each field type knows what its "blank" value -is -- e.g., for ``DateField``, it's ``None`` instead of the empty string. +is -- e.g., for ``DateField``, it's ``None`` instead of the empty string. For +full details on each field's behavior in this case, see the "Empty value" note +for each field in the "Built-in ``Field`` classes" section below. + +You can write code to perform validation for particular form fields (based on +their name) or for the form as a whole (considering combinations of various +fields). More information about this is in the `Custom form and field +validation`_ section, below. Behavior of unbound forms ~~~~~~~~~~~~~~~~~~~~~~~~~ -It's meaningless to request "clean" data in a form with no data, but, for the +It's meaningless to request "cleaned" data in a form with no data, but, for the record, here's what happens with unbound forms:: >>> f = ContactForm() @@ -606,8 +613,13 @@ Using forms in views and templates ---------------------------------- Let's put this all together and use the ``ContactForm`` example in a Django -view and template. This example view displays the contact form by default and -validates/processes it if accessed via a POST request:: +view and template. + +Simple view example +~~~~~~~~~~~~~~~~~~~ + +This example view displays the contact form by default and validates/processes +it if accessed via a POST request:: def contact(request): if request.method == 'POST': @@ -619,12 +631,12 @@ validates/processes it if accessed via a POST request:: form = ContactForm() return render_to_response('contact.html', {'form': form}) -Simple template output -~~~~~~~~~~~~~~~~~~~~~~ +Simple template example +~~~~~~~~~~~~~~~~~~~~~~~ -The template, ``contact.html``, is responsible for displaying the form as HTML. -To do this, we can use the techniques outlined in the "Outputting forms as HTML" -section above. +The template in the above view example, ``contact.html``, is responsible for +displaying the form as HTML. To do this, we can use the techniques outlined in +the "Outputting forms as HTML" section above. The simplest way to display a form's HTML is to use the variable on its own, like this:: @@ -677,7 +689,7 @@ The easiest way is to iterate over the form's fields, with This iteration technique is useful if you want to apply the same HTML formatting to each field, or if you don't know the names of the form fields -ahead of time. Note that the fields will be listed in the order in which +ahead of time. Note that the fields will be iterated over in the order in which they're defined in the ``Form`` class. Alternatively, you can arrange the form's fields explicitly, by name. Do that @@ -701,7 +713,10 @@ For example:: Subclassing forms ----------------- -If you subclass a custom ``Form`` class, the resulting ``Form`` class will +If you have multiple ``Form`` classes that share fields, you can use +subclassing to remove redundancy. + +When you subclass a custom ``Form`` class, the resulting subclass will include all fields of the parent class(es), followed by the fields you define in the subclass. @@ -1202,6 +1217,114 @@ custom ``Field`` classes. To do this, just create a subclass of mentioned above (``required``, ``label``, ``initial``, ``widget``, ``help_text``). +Custom form and field validation +--------------------------------- + +Form validation happens when the data is cleaned. If you want to customise +this process, there are various places you can change, each one serving a +different purpose. Thee types of cleaning methods are run during form +processing. These are normally executed when you call the ``is_valid()`` +method on a form. There are other things that can trigger cleaning and +validation (accessing the ``errors`` attribute or calling ``full_clean()`` +directly), but normally they won't be needed. + +In general, any cleaning method can raise ``ValidationError`` if there is a +problem with the data it is processing, passing the relevant error message to +the ``ValidationError`` constructor. If no ``ValidationError`` is raised, the +method should return the cleaned (normalised) data as a Python object. + +If you detect multiple errors during a cleaning method and wish to signal all +of them to the form submitter, it is possible to pass a list of errors to the +``ValidationError`` constructor. + +The three types of cleaning methods are: + + * The ``clean()`` method on a Field subclass. This is responsible + for cleaning the data in a way that is generic for that type of field. + For example, a FloatField will turn the data into a Python ``float`` or + raise a ``ValidationError``. + + * The ``clean_()`` method in a form subclass -- where + ```` is replaced with the name of the form field attribute. + This method does any cleaning that is specific to that particular + attribute, unrelated to the type of field that it is. This method is not + passed any parameters. You will need to look up the value of the field + in ``self.cleaned_data`` and remember that it will be a Python object + at this point, not the original string submitted in the form (it will be + in ``cleaned_data`` because the general field ``clean()`` method, above, + has already cleaned the data once). + + For example, if you wanted to validate that the contents of a + ``CharField`` called ``serialnumber`` was unique, + ``clean_serialnumber()`` would be the right place to do this. You don't + need a specific field (it's just a ``CharField``), but you want a + formfield-specific piece of validation and, possibly, + cleaning/normalizing the data. + + * The Form subclass's ``clean()`` method. This method can perform + any validation that requires access to multiple fields from the form at + once. This is where you might put in things to check that if field ``A`` + is supplied, field ``B`` must contain a valid email address and the + like. The data that this method returns is the final ``cleaned_data`` + attribute for the form, so don't forget to return the full list of + cleaned data if you override this method (by default, ``Form.clean()`` + just returns ``self.cleaned_data``). + + Note that any errors raised by your ``Form.clean()`` override will not + be associated with any field in particular. They go into a special + "field" (called ``__all__``, which you can access via the + ``non_field_errors()`` method if you need to. + +These methods are run in the order given above, one field at a time. That is, +for each field in the form (in the order they are declared in the form +definition), the ``Field.clean()`` method (or it's override) is run, then +``clean_()``. Finally, once those two methods are run for every +field, the ``Form.clean()`` method, or it's override, is executed. + +As mentioned above, any of these methods can raise a ``ValidationError``. For +any field, if the ``Field.clean()`` method raises a ``ValidationError``, any +field-specific cleaning method is not called. However, the cleaning methods +for all remaining fields are still executed. + +The ``clean()`` method for the ``Form`` class or subclass is always run. If +that method raises a ``ValidationError``, ``cleaned_data`` will be an empty +dictionary. + +The previous paragraph means that if you are overriding ``Form.clean()``, you +should iterate through ``self.cleaned_data.items()``, possibly considering the +``_errors`` dictionary attribute on the form as well. In this way, you will +already know which fields have passed their individual validation requirements. + +A simple example +~~~~~~~~~~~~~~~~ + +Here's a simple example of a custom field that validates its input is a string +containing comma-separated e-mail addresses, with at least one address. We'll +keep it simple and assume e-mail validation is contained in a function called +``is_valid_email()``. The full class:: + + from django import newforms as forms + + class MultiEmailField(forms.Field): + def clean(self, value): + emails = value.split(',') + for email in emails: + if not is_valid_email(email): + raise forms.ValidationError('%s is not a valid e-mail address.' % email) + if not emails: + raise forms.ValidationError('Enter at least one e-mail address.') + return emails + +Let's alter the ongoing ``ContactForm`` example to demonstrate how you'd use +this in a form. Simply use ``MultiEmailField`` instead of ``forms.EmailField``, +like so:: + + class ContactForm(forms.Form): + subject = forms.CharField(max_length=100) + message = forms.CharField() + senders = MultiEmailField() + cc_myself = forms.BooleanField() + Generating forms for models =========================== diff --git a/docs/templates.txt b/docs/templates.txt index d8b511dedc..cb8e238f43 100644 --- a/docs/templates.txt +++ b/docs/templates.txt @@ -447,7 +447,7 @@ for ~~~ Loop over each item in an array. For example, to display a list of athletes -given ``athlete_list``:: +provided in ``athlete_list``::
    {% for athlete in athlete_list %} @@ -455,7 +455,25 @@ given ``athlete_list``:: {% endfor %}
-You can also loop over a list in reverse by using ``{% for obj in list reversed %}``. +You can loop over a list in reverse by using ``{% for obj in list reversed %}``. + +**New in Django development version** +If you need to loop over a list of lists, you can unpack the values +in eachs sub-list into a set of known names. For example, if your context contains +a list of (x,y) coordinates called ``points``, you could use the following +to output the list of points:: + + {% for x, y in points %} + There is a point at {{ x }},{{ y }} + {% endfor %} + +This can also be useful if you need to access the items in a dictionary. +For example, if your context contained a dictionary ``data``, the following +would display the keys and values of the dictionary:: + + {% for key, value in data.items %} + {{ key }}: {{ value }} + {% endfor %} The for loop sets a number of variables available within the loop: diff --git a/scripts/rpm-install.sh b/scripts/rpm-install.sh index d3d95bcdb1..f337a789b1 100644 --- a/scripts/rpm-install.sh +++ b/scripts/rpm-install.sh @@ -21,3 +21,8 @@ done # Make sure we match foo.pyo and foo.pyc along with foo.py (but only once each) sed -e "/\.py[co]$/d" -e "s/\.py$/.py*/" DIRS FILES >INSTALLED_FILES +mkdir -p ${RPM_BUILD_ROOT}/%{_mandir}/man1/ +cp docs/man/* ${RPM_BUILD_ROOT}/%{_mandir}/man1/ +cat << EOF >> INSTALLED_FILES +%doc %{_mandir}/man1/*" +EOF diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py index cd27041eb2..1a144c8356 100644 --- a/tests/regressiontests/serializers_regress/tests.py +++ b/tests/regressiontests/serializers_regress/tests.py @@ -285,11 +285,11 @@ def fieldsTest(format, self): obj = ComplexModel(field1='first',field2='second',field3='third') obj.save() - + # Serialize then deserialize the test database serialized_data = serializers.serialize(format, [obj], indent=2, fields=('field1','field3')) result = serializers.deserialize(format, serialized_data).next() - + # Check that the deserialized object contains data in only the serialized fields. self.assertEqual(result.object.field1, 'first') self.assertEqual(result.object.field2, '') @@ -301,19 +301,20 @@ def streamTest(format, self): obj = ComplexModel(field1='first',field2='second',field3='third') obj.save() - + # Serialize the test database to a stream - stream = StringIO() + stream = StringIO() serializers.serialize(format, [obj], indent=2, stream=stream) - + # Serialize normally for a comparison string_data = serializers.serialize(format, [obj], indent=2) # Check that the two are the same - self.assertEqual(string_data, stream.buffer()) + self.assertEqual(string_data, stream.getvalue()) stream.close() - + for format in serializers.get_serializer_formats(): setattr(SerializerTests, 'test_'+format+'_serializer', curry(serializerTest, format)) setattr(SerializerTests, 'test_'+format+'_serializer_fields', curry(fieldsTest, format)) - setattr(SerializerTests, 'test_'+format+'_serializer_stream', curry(fieldsTest, format)) + if format != 'python': + setattr(SerializerTests, 'test_'+format+'_serializer_stream', curry(streamTest, format)) diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index a5ed2dbf56..e983a539ae 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -289,6 +289,20 @@ class Templates(unittest.TestCase): 'for-tag-vars02': ("{% for val in values %}{{ forloop.counter0 }}{% endfor %}", {"values": [6, 6, 6]}, "012"), 'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"), 'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"), + 'for-tag-unpack01': ("{% for key,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), + 'for-tag-unpack03': ("{% for key, value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), + 'for-tag-unpack04': ("{% for key , value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), + 'for-tag-unpack05': ("{% for key ,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), + 'for-tag-unpack06': ("{% for key value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError), + 'for-tag-unpack07': ("{% for key,,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError), + 'for-tag-unpack08': ("{% for key,value, in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError), + # Ensure that a single loopvar doesn't truncate the list in val. + 'for-tag-unpack09': ("{% for val in items %}{{ val.0 }}:{{ val.1 }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), + # Otherwise, silently truncate if the length of loopvars differs to the length of each set of items. + 'for-tag-unpack10': ("{% for x,y in items %}{{ x }}:{{ y }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'orange'))}, "one:1/two:2/"), + 'for-tag-unpack11': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, ("one:1,/two:2,/", "one:1,INVALID/two:2,INVALID/")), + 'for-tag-unpack12': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2))}, ("one:1,carrot/two:2,/", "one:1,carrot/two:2,INVALID/")), + 'for-tag-unpack13': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'cheese'))}, ("one:1,carrot/two:2,cheese/", "one:1,carrot/two:2,cheese/")), ### IF TAG ################################################################ 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),