diff options
| author | Joseph Kocherhans <joseph@jkocherhans.com> | 2007-09-10 02:29:42 +0000 |
|---|---|---|
| committer | Joseph Kocherhans <joseph@jkocherhans.com> | 2007-09-10 02:29:42 +0000 |
| commit | 52578a1d1d06195bf0d4700ba44247833290ffef (patch) | |
| tree | 96641354a109687a1fcc7a0abd860b6eed0df665 | |
| parent | b5aa61a325136d6484ccbfc8a0efd98e8aaa964a (diff) | |
newforms-admin: Merged to [6081]
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@6082 bcc190cf-cafb-0310-a4f2-bffc1f526a37
27 files changed, 504 insertions, 263 deletions
@@ -80,6 +80,7 @@ answer newbie questions, and generally made Django that much better: Bryan Chow <bryan at verdjn dot com> Michal Chruszcz <troll@pld-linux.org> Ian Clelland <clelland@gmail.com> + Russell Cloran <russell@rucus.net> colin@owlfish.com crankycoder@gmail.com Pete Crosier <pete.crosier@gmail.com> diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 7a3f9ca7a5..d39d69eff1 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -18,9 +18,9 @@ def load_command_class(name): def call_command(name, *args, **options): """ Calls the given command, with the given options and args/kwargs. - + This is the primary API you should use for calling specific commands. - + Some examples: call_command('syncdb') call_command('shell', plain=True) @@ -52,76 +52,59 @@ class ManagementUtility(object): names = [f[:-3] for f in os.listdir(command_dir) if not f.startswith('_') and f.endswith('.py')] return dict([(name, load_command_class(name)) for name in names]) - def usage(self): + def print_help(self, argv): """ - Returns a usage string, for use with optparse. - - The string doesn't include the options (e.g., "--verbose"), because - optparse puts those in automatically. + Returns the help message, as a string. """ - usage = ["%prog command [options]\nactions:"] - commands = self.commands.items() + prog_name = os.path.basename(argv[0]) + usage = ['%s <subcommand> [options] [args]' % prog_name] + usage.append('Django command line tool, version %s' % django.get_version()) + usage.append("Type '%s help <subcommand>' for help on a specific subcommand." % prog_name) + usage.append('Available subcommands:') + commands = self.commands.keys() commands.sort() - for name, cmd in commands: - usage.append(' %s %s' % (name, cmd.args)) - usage.extend(textwrap.wrap(cmd.help, initial_indent=' ', subsequent_indent=' ')) - usage.append('') - return '\n'.join(usage[:-1]) # Cut off the last list element, an empty space. + for cmd in commands: + usage.append(' %s' % cmd) + print '\n'.join(usage) + + def fetch_command(self, subcommand, command_name): + """ + Tries to fetch the given subcommand, printing a message with the + appropriate command called from the command line (usually + django-admin.py or manage.py) if it can't be found. + """ + try: + return self.commands[subcommand] + except KeyError: + sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % (subcommand, command_name)) + sys.exit(1) def execute(self, argv=None): """ - Parses the given argv from the command line, determines which command - to run and runs the command. + Figures out which command is being run (the first arg), creates a parser + appropriate to that command, and runs it. """ if argv is None: argv = sys.argv - - # Create the parser object and parse the command-line args. - # TODO: Ideally each Command class would register its own options for - # add_option(), but we'd need to figure out how to allow for multiple - # Commands using the same options. The optparse library gets in the way - # by checking for conflicts: - # http://docs.python.org/lib/optparse-conflicts-between-options.html - parser = OptionParser(usage=self.usage(), version=get_version()) - parser.add_option('--settings', - help='The Python path to a settings module, e.g. "myproject.settings.main". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.') - parser.add_option('--pythonpath', - help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".') - parser.add_option('--plain', action='store_true', dest='plain', - help='When using "shell": Tells Django to use plain Python, not IPython.') - parser.add_option('--noinput', action='store_false', dest='interactive', default=True, - help='Tells Django to NOT prompt the user for input of any kind.') - parser.add_option('--noreload', action='store_false', dest='use_reloader', default=True, - help='When using "runserver": Tells Django to NOT use the auto-reloader.') - parser.add_option('--format', default='json', dest='format', - help='Specifies the output serialization format for fixtures') - parser.add_option('--indent', default=None, dest='indent', - type='int', help='Specifies the indent level to use when pretty-printing output') - parser.add_option('--verbosity', action='store', dest='verbosity', default='1', - type='choice', choices=['0', '1', '2'], - help='Verbosity level; 0=minimal output, 1=normal output, 2=all output') - parser.add_option('--adminmedia', dest='admin_media_path', default='', - help='When using "runserver": Specifies the directory from which to serve admin media.') - options, args = parser.parse_args(argv[1:]) - - # If the 'settings' or 'pythonpath' options were submitted, activate those. - if options.settings: - os.environ['DJANGO_SETTINGS_MODULE'] = options.settings - if options.pythonpath: - sys.path.insert(0, options.pythonpath) - - # Run the appropriate command. try: - command_name = args[0] + command_name = argv[1] except IndexError: - sys.stderr.write("Type '%s --help' for usage.\n" % os.path.basename(argv[0])) + sys.stderr.write("Type '%s help' for usage.\n" % os.path.basename(argv[0])) sys.exit(1) - try: - command = self.commands[command_name] - except KeyError: - sys.stderr.write("Unknown command: %r\nType '%s --help' for usage.\n" % (command_name, os.path.basename(argv[0]))) - sys.exit(1) - command.execute(*args[1:], **options.__dict__) + + if command_name == 'help': + if len(argv) > 2: + self.fetch_command(argv[2], argv[0]).print_help(argv[2:]) + else: + self.print_help(argv) + # Special-cases: We want 'django-admin.py --version' and + # 'django-admin.py --help' to work, for backwards compatibility. + elif argv[1:] == ['--version']: + print django.get_version() + elif argv[1:] == ['--help']: + self.print_help(argv) + else: + self.fetch_command(command_name, argv[0]).run(argv[1:]) class ProjectManagementUtility(ManagementUtility): """ diff --git a/django/core/management/base.py b/django/core/management/base.py index 978aa7902a..fb43895b51 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -1,13 +1,23 @@ +import django from django.core.exceptions import ImproperlyConfigured from django.core.management.color import color_style +import itertools +from optparse import make_option, OptionParser import sys import os +from traceback import print_exc class CommandError(Exception): pass class BaseCommand(object): # Metadata about this command. + option_list = ( + make_option('--settings', + help='The Python path to a settings module, e.g. "myproject.settings.main". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.'), + make_option('--pythonpath', + help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".'), + ) help = '' args = '' @@ -19,6 +29,43 @@ class BaseCommand(object): def __init__(self): self.style = color_style() + def get_version(self): + """ + Returns the Django version, which should be correct for all built-in + Django commands. User-supplied commands should override this method. + """ + return django.get_version() + + def usage(self): + usage = '%prog [options] ' + self.args + if self.help: + return '%s\n\n%s' % (usage, self.help) + else: + return usage + + def create_parser(self, prog_name): + return OptionParser(prog=prog_name, + usage=self.usage(), + version=self.get_version(), + option_list=self.option_list) + + def print_help(self, args): + parser = self.create_parser(args[0]) + parser.print_help() + + def run(self, args): + parser = self.create_parser(args[0]) + (options, args) = parser.parse_args(args[1:]) + if options.settings: + os.environ['DJANGO_SETTINGS_MODULE'] = options.settings + if options.pythonpath: + sys.path.insert(0, options.pythonpath) + try: + self.execute(*args, **options.__dict__) + except Exception, e: + print_exc() + parser.print_usage() + def execute(self, *args, **options): # Switch to English, because django-admin.py creates database content # like permissions, and those shouldn't contain any translations. @@ -69,7 +116,7 @@ class BaseCommand(object): raise NotImplementedError() class AppCommand(BaseCommand): - args = '[appname ...]' + args = '<appname appname ...>' def handle(self, *app_labels, **options): from django.db import models @@ -90,7 +137,7 @@ class AppCommand(BaseCommand): raise NotImplementedError() class LabelCommand(BaseCommand): - args = '[label ...]' + args = '<label label ...>' label = 'label' def handle(self, *labels, **options): @@ -168,4 +215,3 @@ def _make_writeable(filename): st = os.stat(filename) new_permissions = stat.S_IMODE(st.st_mode) | stat.S_IWUSR os.chmod(filename, new_permissions) - diff --git a/django/core/management/commands/createcachetable.py b/django/core/management/commands/createcachetable.py index c1fa1a26a5..c8df8d04f7 100644 --- a/django/core/management/commands/createcachetable.py +++ b/django/core/management/commands/createcachetable.py @@ -2,7 +2,7 @@ from django.core.management.base import LabelCommand class Command(LabelCommand): help = "Creates the table needed to use the SQL cache backend." - args = "[tablename]" + args = "<tablename>" label = 'tablename' requires_model_validation = False diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index 48677ca5fa..7bdcc4271f 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -1,8 +1,16 @@ from django.core.management.base import BaseCommand, CommandError +from optparse import make_option + class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('--format', default='json', dest='format', + help='Specifies the output serialization format for fixtures'), + make_option('--indent', default=None, dest='indent', type='int', + help='Specifies the indent level to use when pretty-printing output'), + ) help = 'Output the contents of the database as a fixture of the given format.' - args = '[--format] [--indent] [appname ...]' + args = '[appname ...]' def handle(self, *app_labels, **options): from django.db.models import get_app, get_apps, get_models diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index 395359d269..87a99b8beb 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -1,9 +1,16 @@ from django.core.management.base import NoArgsCommand, CommandError from django.core.management.color import no_style +from optparse import make_option class Command(NoArgsCommand): + option_list = NoArgsCommand.option_list + ( + make_option('--verbosity', action='store', dest='verbosity', default='1', + type='choice', choices=['0', '1', '2'], + help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'), + make_option('--noinput', action='store_false', dest='interactive', default=True, + help='Tells Django to NOT prompt the user for input of any kind.'), + ) help = "Executes ``sqlflush`` on the current database." - args = '[--verbosity] [--noinput]' def handle_noargs(self, **options): from django.conf import settings diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index 4cbdb55c67..83fc9b8ac9 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -1,5 +1,6 @@ from django.core.management.base import BaseCommand from django.core.management.color import no_style +from optparse import make_option import sys import os @@ -9,8 +10,13 @@ except NameError: from sets import Set as set # Python 2.3 fallback class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('--verbosity', action='store', dest='verbosity', default='1', + type='choice', choices=['0', '1', '2'], + help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'), + ) help = 'Installs the named fixture(s) in the database.' - args = "[--verbosity] fixture, fixture, ..." + args = "fixture [fixture ...]" def handle(self, *fixture_labels, **options): from django.db.models import get_apps diff --git a/django/core/management/commands/reset.py b/django/core/management/commands/reset.py index 5c89d94678..3e7ca9f689 100644 --- a/django/core/management/commands/reset.py +++ b/django/core/management/commands/reset.py @@ -1,9 +1,14 @@ from django.core.management.base import AppCommand, CommandError from django.core.management.color import no_style +from optparse import make_option class Command(AppCommand): + option_list = AppCommand.option_list + ( + make_option('--noinput', action='store_false', dest='interactive', default=True, + help='Tells Django to NOT prompt the user for input of any kind.'), + ) help = "Executes ``sqlreset`` for the given app(s) in the current database." - args = '[--noinput] [appname ...]' + args = '[appname ...]' output_transaction = True diff --git a/django/core/management/commands/runfcgi.py b/django/core/management/commands/runfcgi.py index 406a27b8c3..599ac2dcd2 100644 --- a/django/core/management/commands/runfcgi.py +++ b/django/core/management/commands/runfcgi.py @@ -14,3 +14,7 @@ class Command(BaseCommand): pass from django.core.servers.fastcgi import runfastcgi runfastcgi(args) + + def usage(self): + from django.core.servers.fastcgi import FASTCGI_HELP + return FASTCGI_HELP diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py index 272ca18c07..5b141d03ed 100644 --- a/django/core/management/commands/runserver.py +++ b/django/core/management/commands/runserver.py @@ -1,10 +1,17 @@ from django.core.management.base import BaseCommand, CommandError +from optparse import make_option import os import sys class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('--noreload', action='store_false', dest='use_reloader', default=True, + help='Tells Django to NOT use the auto-reloader.'), + make_option('--adminmedia', dest='admin_media_path', default='', + help='Specifies the directory from which to serve admin media.'), + ) help = "Starts a lightweight Web server for development." - args = '[--noreload] [--adminmedia=ADMIN_MEDIA_PATH] [optional port number, or ipaddr:port]' + args = '[optional port number, or ipaddr:port]' # Validation is called explicitly each time the server is reloaded. requires_model_validation = False diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py index bc76c44b97..3baf75c104 100644 --- a/django/core/management/commands/shell.py +++ b/django/core/management/commands/shell.py @@ -1,8 +1,12 @@ from django.core.management.base import NoArgsCommand +from optparse import make_option class Command(NoArgsCommand): + option_list = NoArgsCommand.option_list + ( + make_option('--plain', action='store_true', dest='plain', + help='Tells Django to use plain Python, not IPython.'), + ) help = "Runs a Python interactive interpreter. Tries to use IPython, if it's available." - args = '[--plain]' requires_model_validation = False diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py index f7b694d6e7..c1f360a985 100644 --- a/django/core/management/commands/syncdb.py +++ b/django/core/management/commands/syncdb.py @@ -1,5 +1,6 @@ from django.core.management.base import NoArgsCommand from django.core.management.color import no_style +from optparse import make_option import sys try: @@ -8,8 +9,14 @@ except NameError: from sets import Set as set # Python 2.3 fallback class Command(NoArgsCommand): + option_list = NoArgsCommand.option_list + ( + make_option('--verbosity', action='store', dest='verbosity', default='1', + type='choice', choices=['0', '1', '2'], + help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'), + make_option('--noinput', action='store_false', dest='interactive', default=True, + help='Tells Django to NOT prompt the user for input of any kind.'), + ) help = "Create the database tables for all apps in INSTALLED_APPS whose tables haven't already been created." - args = '[--verbosity] [--noinput]' def handle_noargs(self, **options): from django.db import connection, transaction, models diff --git a/django/core/management/commands/test.py b/django/core/management/commands/test.py index 8e27fd5606..f0e13a2069 100644 --- a/django/core/management/commands/test.py +++ b/django/core/management/commands/test.py @@ -1,9 +1,17 @@ from django.core.management.base import BaseCommand +from optparse import make_option import sys class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('--verbosity', action='store', dest='verbosity', default='1', + type='choice', choices=['0', '1', '2'], + help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'), + make_option('--noinput', action='store_false', dest='interactive', default=True, + help='Tells Django to NOT prompt the user for input of any kind.'), + ) help = 'Runs the test suite for the specified applications, or the entire site if no apps are specified.' - args = '[--verbosity] [--noinput] [appname ...]' + args = '[appname ...]' requires_model_validation = False diff --git a/django/core/management/commands/testserver.py b/django/core/management/commands/testserver.py index 907960894c..50a10a12bc 100644 --- a/django/core/management/commands/testserver.py +++ b/django/core/management/commands/testserver.py @@ -1,6 +1,13 @@ from django.core.management.base import BaseCommand +from optparse import make_option + class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('--verbosity', action='store', dest='verbosity', default='1', + type='choice', choices=['0', '1', '2'], + help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'), + ) help = 'Runs a development server with data from the given fixture(s).' args = '[fixture ...]' diff --git a/django/core/servers/fastcgi.py b/django/core/servers/fastcgi.py index 619758a0b1..de04a5af62 100644 --- a/django/core/servers/fastcgi.py +++ b/django/core/servers/fastcgi.py @@ -17,14 +17,12 @@ import sys, os __version__ = "0.1" __all__ = ["runfastcgi"] -FASTCGI_HELP = r"""runfcgi: +FASTCGI_HELP = r""" Run this project as a fastcgi (or some other protocol supported by flup) application. To do this, the flup package from http://www.saddi.com/software/flup/ is required. -Usage: - django-admin.py runfcgi --settings=yourproject.settings [fcgi settings] - manage.py runfcgi [fcgi settings] + runfcgi [options] [fcgi settings] Optional Fcgi settings: (setting=value) protocol=PROTOCOL fcgi, scgi, ajp, ... (default fcgi) diff --git a/django/core/validators.py b/django/core/validators.py index 8a93462b21..bdcf39e3aa 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -9,16 +9,17 @@ form field is required. """ import urllib2 -from django.conf import settings -from django.utils.translation import ugettext as _, ugettext_lazy, ungettext -from django.utils.functional import Promise, lazy -from django.utils.encoding import force_unicode import re try: from decimal import Decimal, DecimalException except ImportError: from django.utils._decimal import Decimal, DecimalException # Python 2.3 +from django.conf import settings +from django.utils.translation import ugettext as _, ugettext_lazy, ungettext +from django.utils.functional import Promise, lazy +from django.utils.encoding import force_unicode + _datere = r'\d{4}-\d{1,2}-\d{1,2}' _timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?' alnum_re = re.compile(r'^\w+$') @@ -148,7 +149,7 @@ def _isValidDate(date_string): date(year, month, day) except ValueError, e: msg = _('Invalid date: %s') % _(str(e)) - raise ValidationError, msg + raise ValidationError, msg def isValidANSIDate(field_data, all_data): if not ansi_date_re.search(field_data): @@ -251,7 +252,7 @@ def isExistingURL(field_data, all_data): raise ValidationError, _("The URL %s is a broken link.") % field_data except: # urllib2.URLError, httplib.InvalidURL, etc. raise ValidationError, _("The URL %s is a broken link.") % field_data - + def isValidUSState(field_data, all_data): "Checks that the given string is a valid two-letter U.S. state abbreviation" states = ['AA', 'AE', 'AK', 'AL', 'AP', 'AR', 'AS', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'FM', 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MH', 'MI', 'MN', 'MO', 'MP', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'PR', 'PW', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI', 'VT', 'WA', 'WI', 'WV', 'WY'] @@ -380,13 +381,13 @@ class NumberIsInRange(object): self.error_message = error_message def __call__(self, field_data, all_data): - # Try to make the value numeric. If this fails, we assume another + # Try to make the value numeric. If this fails, we assume another # validator will catch the problem. try: val = float(field_data) except ValueError: return - + # Now validate if self.lower and self.upper and (val < self.lower or val > self.upper): raise ValidationError(self.error_message) @@ -423,7 +424,7 @@ class IsValidDecimal(object): except DecimalException: raise ValidationError, _("Please enter a valid decimal number.") - pieces = str(val).split('.') + pieces = str(val).lstrip("-").split('.') decimals = (len(pieces) == 2) and len(pieces[1]) or 0 digits = len(pieces[0]) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index ab588bcca7..9cff51b7a8 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -1,3 +1,11 @@ +import datetime +import os +import time +try: + import decimal +except ImportError: + from django.utils import _decimal as decimal # for Python 2.3 + from django.db import get_creation_module from django.db.models import signals from django.dispatch import dispatcher @@ -12,11 +20,6 @@ from django.utils.text import capfirst from django.utils.translation import ugettext_lazy, ugettext as _ from django.utils.encoding import smart_unicode, force_unicode, smart_str from django.utils.maxlength import LegacyMaxlength -import datetime, os, time -try: - import decimal -except ImportError: - from django.utils import _decimal as decimal # for Python 2.3 class NOT_PROVIDED: pass @@ -380,7 +383,7 @@ class Field(object): def save_form_data(self, instance, data): setattr(instance, self.name, data) - + def formfield(self, form_class=forms.CharField, **kwargs): "Returns a django.newforms.Field instance for this database Field." defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} @@ -783,10 +786,10 @@ class FileField(Field): def save_form_data(self, instance, data): if data: getattr(instance, "save_%s_file" % self.name)(data.filename, data.content, save=False) - + def formfield(self, **kwargs): defaults = {'form_class': forms.FileField} - # If a file has been provided previously, then the form doesn't require + # If a file has been provided previously, then the form doesn't require # that a new file is provided this time. if 'initial' in kwargs: defaults['required'] = False @@ -916,10 +919,7 @@ class SlugField(CharField): # Set db_index=True unless it's been set manually. if 'db_index' not in kwargs: kwargs['db_index'] = True - Field.__init__(self, *args, **kwargs) - - def get_manipulator_field_objs(self): - return [oldforms.TextField] + super(SlugField, self).__init__(*args, **kwargs) class SmallIntegerField(IntegerField): def get_manipulator_field_objs(self): diff --git a/django/newforms/fields.py b/django/newforms/fields.py index bb8a3dc582..658eea6fba 100644 --- a/django/newforms/fields.py +++ b/django/newforms/fields.py @@ -190,7 +190,7 @@ class DecimalField(Field): value = Decimal(value) except DecimalException: raise ValidationError(ugettext('Enter a number.')) - pieces = str(value).split('.') + pieces = str(value).lstrip("-").split('.') decimals = (len(pieces) == 2) and len(pieces[1]) or 0 digits = len(pieces[0]) if self.max_value is not None and value > self.max_value: @@ -353,7 +353,7 @@ class UploadedFile(StrAndUnicode): def __init__(self, filename, content): self.filename = filename self.content = content - + def __unicode__(self): """ The unicode representation is the filename, so that the pre-database-insertion @@ -396,7 +396,7 @@ class ImageField(FileField): except IOError: # Python Imaging Library doesn't recognize it as an image raise ValidationError(ugettext(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image.")) return f - + class URLField(RegexField): def __init__(self, max_length=None, min_length=None, verify_exists=False, validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs): @@ -526,7 +526,7 @@ class ComboField(Field): class MultiValueField(Field): """ A Field that aggregates the logic of multiple Fields. - + Its clean() method takes a "decompressed" list of values, which are then cleaned into a single value according to self.fields. Each value in this list is cleaned by the corresponding field -- the first value is diff --git a/django/views/generic/date_based.py b/django/views/generic/date_based.py index c0f219f280..1a8c4c611a 100644 --- a/django/views/generic/date_based.py +++ b/django/views/generic/date_based.py @@ -1,9 +1,11 @@ +import datetime +import time + from django.template import loader, RequestContext from django.core.exceptions import ObjectDoesNotExist from django.core.xheaders import populate_xheaders from django.db.models.fields import DateTimeField from django.http import Http404, HttpResponse -import datetime, time def archive_index(request, queryset, date_field, num_latest=15, template_name=None, template_loader=loader, @@ -76,7 +78,7 @@ def archive_year(request, year, queryset, date_field, template_name=None, if not date_list and not allow_empty: raise Http404 if make_object_list: - object_list = queryset.filter(**lookup_kwargs).order_by(date_field) + object_list = queryset.filter(**lookup_kwargs) else: object_list = [] if not template_name: diff --git a/docs/contributing.txt b/docs/contributing.txt index b0df62fe99..f1ad0e3bb0 100644 --- a/docs/contributing.txt +++ b/docs/contributing.txt @@ -122,6 +122,11 @@ Patch style English than in code. Indentation is the most common example; it's hard to read patches when the only difference in code is that it's indented. + * When creating patches, always run ``svn diff`` from the top-level + ``trunk`` directory -- i.e., the one that contains ``django``, ``docs``, + ``tests``, ``AUTHORS``, etc. This makes it easy for other people to apply + your patches. + * Attach patches to a ticket in the `ticket tracker`_, using the "attach file" button. Please *don't* put the patch in the ticket description or comment unless it's a single line patch. diff --git a/docs/django-admin.txt b/docs/django-admin.txt index e3d1067dd3..cdd31a2c48 100644 --- a/docs/django-admin.txt +++ b/docs/django-admin.txt @@ -35,39 +35,61 @@ be consistent, but any example can use ``manage.py`` just as well. Usage ===== -``django-admin.py action [options]`` +``django-admin.py <subcommand> [options]`` -``manage.py action [options]`` +``manage.py <subcommand> [options]`` -``action`` should be one of the actions listed in this document. ``options``, -which is optional, should be zero or more of the options listed in this -document. +``subcommand`` should be one of the subcommands listed in this document. +``options``, which is optional, should be zero or more of the options available +for the given subcommand. -Run ``django-admin.py --help`` to display a help message that includes a terse -list of all available actions and options. +Getting runtime help +-------------------- -Most actions take a list of ``appname``s. An ``appname`` is the basename of the -package containing your models. For example, if your ``INSTALLED_APPS`` -contains the string ``'mysite.blog'``, the ``appname`` is ``blog``. +In Django 0.96, run ``django-admin.py --help`` to display a help message that +includes a terse list of all available subcommands and options. -Available actions -================= +In the Django development version, run ``django-admin.py help`` to display a +list of all available subcommands. Run ``django-admin.py help <subcommand>`` +to display a description of the given subcommand and a list of its available +options. + +App names +--------- + +Many subcommands take a list of "app names." An "app name" is the basename of +the package containing your models. For example, if your ``INSTALLED_APPS`` +contains the string ``'mysite.blog'``, the app name is ``blog``. + +Determining the version +----------------------- -adminindex [appname appname ...] +Run ``django-admin.py --version`` to display the current Django version. + +Examples of output:: + + 0.95 + 0.96 + 0.97-pre-SVN-6069 + +Available subcommands +===================== + +adminindex <appname appname ...> -------------------------------- -Prints the admin-index template snippet for the given appnames. +Prints the admin-index template snippet for the given app name(s). Use admin-index template snippets if you want to customize the look and feel of your admin's index page. See `Tutorial 2`_ for more information. .. _Tutorial 2: ../tutorial02/ -createcachetable [tablename] +createcachetable <tablename> ---------------------------- Creates a cache table named ``tablename`` for use with the database cache -backend. See the `cache documentation`_ for more information. +backend. See the `cache documentation`_ for more information. .. _cache documentation: ../cache/ @@ -100,26 +122,44 @@ example, the default settings don't define ``ROOT_URLCONF``, so Note that Django's default settings live in ``django/conf/global_settings.py``, if you're ever curious to see the full list of defaults. -dumpdata [appname appname ...] +dumpdata <appname appname ...> ------------------------------ -Output to standard output all data in the database associated with the named +Outputs to standard output all data in the database associated with the named application(s). -By default, the database will be dumped in JSON format. If you want the output -to be in another format, use the ``--format`` option (e.g., ``format=xml``). -You may specify any Django serialization backend (including any user specified -serialization backends named in the ``SERIALIZATION_MODULES`` setting). The -``--indent`` option can be used to pretty-print the output. - If no application name is provided, all installed applications will be dumped. The output of ``dumpdata`` can be used as input for ``loaddata``. +--format +~~~~~~~~ + +By default, ``dumpdata`` will format its output in JSON, but you can use the +``--format`` option to specify another format. Currently supported formats are +listed in `Serialization formats`_. + +Example usage:: + + django-admin.py dumpdata --format=xml + +.. _Serialization formats: ../serialization/#serialization-formats + +--indent +~~~~~~~~ + +By default, ``dumpdata`` will output all data on a single line. This isn't easy +for humans to read, so you can use the ``--indent`` option to pretty-print the +output with a number of indentation spaces. + +Example usage:: + + django-admin.py dumpdata --indent=4 + flush ----- -Return the database to the state it was in immediately after syncdb was +Returns the database to the state it was in immediately after syncdb was executed. This means that all data will be removed from the database, any post-synchronization handlers will be re-executed, and the ``initial_data`` fixture will be re-installed. @@ -131,6 +171,27 @@ models and/or weren't in ``INSTALLED_APPS``). Now, the command only clears tables that are represented by Django models and are activated in ``INSTALLED_APPS``. +--noinput +~~~~~~~~~ + +Use the ``--noinput`` option to suppress all user prompting, such as +"Are you sure?" confirmation messages. This is useful if ``django-admin.py`` +is being executed as an unattended, automated script. + +--verbosity +~~~~~~~~~~~ + +Use ``--verbosity`` to specify the amount of notification and debug information +that ``django-admin.py`` should print to the console. + + * ``0`` means no input. + * ``1`` means normal input (default). + * ``2`` means verbose input. + +Example usage:: + + django-admin.py flush --verbosity=2 + inspectdb --------- @@ -172,15 +233,14 @@ needed. ``inspectdb`` works with PostgreSQL, MySQL and SQLite. Foreign-key detection only works in PostgreSQL and with certain types of MySQL tables. -loaddata [fixture fixture ...] +loaddata <fixture fixture ...> ------------------------------ Searches for and loads the contents of the named fixture into the database. -A *Fixture* is a collection of files that contain the serialized contents of -the database. Each fixture has a unique name; however, the files that -comprise the fixture can be distributed over multiple directories, in -multiple applications. +A *fixture* is a collection of files that contain the serialized contents of +the database. Each fixture has a unique name, and the files that comprise the +fixture can be distributed over multiple directories, in multiple applications. Django will search in three locations for fixtures: @@ -240,16 +300,37 @@ The ``dumpdata`` command can be used to generate input for ``loaddata``. references in your data files - MySQL doesn't provide a mechanism to defer checking of row constraints until a transaction is committed. -reset [appname appname ...] +--verbosity +~~~~~~~~~~~ + +Use ``--verbosity`` to specify the amount of notification and debug information +that ``django-admin.py`` should print to the console. + + * ``0`` means no input. + * ``1`` means normal input (default). + * ``2`` means verbose input. + +Example usage:: + + django-admin.py loaddata --verbosity=2 + +reset <appname appname ...> --------------------------- -Executes the equivalent of ``sqlreset`` for the given appnames. +Executes the equivalent of ``sqlreset`` for the given app name(s). + +--noinput +~~~~~~~~~ + +Use the ``--noinput`` option to suppress all user prompting, such as +"Are you sure?" confirmation messages. This is useful if ``django-admin.py`` +is being executed as an unattended, automated script. runfcgi [options] ----------------- -Starts a set of FastCGI processes suitable for use with any web server -which supports the FastCGI protocol. See the `FastCGI deployment +Starts a set of FastCGI processes suitable for use with any Web server +that supports the FastCGI protocol. See the `FastCGI deployment documentation`_ for details. Requires the Python FastCGI module from `flup`_. @@ -289,8 +370,36 @@ machines on your network. To make your development server viewable to other machines on the network, use its own IP address (e.g. ``192.168.2.1``) or ``0.0.0.0``. -Examples: -~~~~~~~~~ +--adminmedia +~~~~~~~~~~~~ + +Use the ``--adminmedia`` option to tell Django where to find the various CSS +and JavaScript files for the Django admin interface. Normally, the development +server serves these files out of the Django source tree magically, but you'd +want to use this if you made any changes to those files for your own site. + +Example usage:: + + django-admin.py runserver --adminmedia=/tmp/new-admin-style/ + +--noreload +~~~~~~~~~~ + +Use the ``--noreload`` option to disable the use of the auto-reloader. This +means any Python code changes you make while the server is running will *not* +take effect if the particular Python modules have already been loaded into +memory. + +Examples of using different ports and addresses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Port 8000 on IP address 127.0.0.1:: + + django-admin.py runserver + +Port 8000 on IP address 1.2.3.4:: + + django-admin.py runserver 1.2.3.4:8000 Port 7000 on IP address 127.0.0.1:: @@ -331,31 +440,31 @@ option, like so:: .. _IPython: http://ipython.scipy.org/ -sql [appname appname ...] +sql <appname appname ...> ------------------------- -Prints the CREATE TABLE SQL statements for the given appnames. +Prints the CREATE TABLE SQL statements for the given app name(s). -sqlall [appname appname ...] +sqlall <appname appname ...> ---------------------------- -Prints the CREATE TABLE and initial-data SQL statements for the given appnames. +Prints the CREATE TABLE and initial-data SQL statements for the given app name(s). Refer to the description of ``sqlcustom`` for an explanation of how to specify initial data. -sqlclear [appname appname ...] +sqlclear <appname appname ...> ------------------------------ -Prints the DROP TABLE SQL statements for the given appnames. +Prints the DROP TABLE SQL statements for the given app name(s). -sqlcustom [appname appname ...] +sqlcustom <appname appname ...> ------------------------------- -Prints the custom SQL statements for the given appnames. +Prints the custom SQL statements for the given app name(s). For each model in each specified app, this command looks for the file -``<appname>/sql/<modelname>.sql``, where ``<appname>`` is the given appname and +``<appname>/sql/<modelname>.sql``, where ``<appname>`` is the given app name and ``<modelname>`` is the model's name in lowercase. For example, if you have an app ``news`` that includes a ``Story`` model, ``sqlcustom`` will attempt to read a file ``news/sql/story.sql`` and append it to the output of this @@ -373,31 +482,30 @@ sqlflush Prints the SQL statements that would be executed for the `flush`_ command. -sqlindexes [appname appname ...] +sqlindexes <appname appname ...> -------------------------------- -Prints the CREATE INDEX SQL statements for the given appnames. +Prints the CREATE INDEX SQL statements for the given app name(s). -sqlreset [appname appname ...] +sqlreset <appname appname ...> ------------------------------ -Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given appnames. +Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app name(s). -sqlsequencereset [appname appname ...] +sqlsequencereset <appname appname ...> -------------------------------------- -Prints the SQL statements for resetting sequences for the given -appnames. +Prints the SQL statements for resetting sequences for the given app name(s). See http://simon.incutio.com/archive/2004/04/21/postgres for more information. -startapp [appname] +startapp <appname> ------------------ Creates a Django app directory structure for the given app name in the current directory. -startproject [projectname] +startproject <projectname> -------------------------- Creates a Django project directory structure for the given project name in the @@ -435,14 +543,57 @@ with an appropriate extension (e.g. ``json`` or ``xml``). See the documentation for ``loaddata`` for details on the specification of fixture data files. +--verbosity +~~~~~~~~~~~ + +Use ``--verbosity`` to specify the amount of notification and debug information +that ``django-admin.py`` should print to the console. + + * ``0`` means no input. + * ``1`` means normal input (default). + * ``2`` means verbose input. + +Example usage:: + + django-admin.py syncdb --verbosity=2 + +--noinput +~~~~~~~~~ + +Use the ``--noinput`` option to suppress all user prompting, such as +"Are you sure?" confirmation messages. This is useful if ``django-admin.py`` +is being executed as an unattended, automated script. + test ---- -Discover and run tests for all installed models. See `Testing Django applications`_ for more information. +Runs tests for all installed models. See `Testing Django applications`_ +for more information. .. _testing Django applications: ../testing/ -testserver [fixture fixture ...] +--noinput +~~~~~~~~~ + +Use the ``--noinput`` option to suppress all user prompting, such as +"Are you sure?" confirmation messages. This is useful if ``django-admin.py`` +is being executed as an unattended, automated script. + +--verbosity +~~~~~~~~~~~ + +Use ``--verbosity`` to specify the amount of notification and debug information +that ``django-admin.py`` should print to the console. + + * ``0`` means no input. + * ``1`` means normal input (default). + * ``2`` means verbose input. + +Example usage:: + + django-admin.py test --verbosity=2 + +testserver <fixture fixture ...> -------------------------------- **New in Django development version** @@ -484,29 +635,31 @@ code (as ``runserver`` does). It does, however, detect changes to templates. .. _unit tests: ../testing/ -validate --------- - -Validates all installed models (according to the ``INSTALLED_APPS`` setting) -and prints validation errors to standard output. +--verbosity +~~~~~~~~~~~ -Available options -================= +Use ``--verbosity`` to specify the amount of notification and debug information +that ``django-admin.py`` should print to the console. ---settings ----------- + * ``0`` means no input. + * ``1`` means normal input (default). + * ``2`` means verbose input. Example usage:: - django-admin.py syncdb --settings=mysite.settings + django-admin.py testserver --verbosity=2 -Explicitly specifies the settings module to use. The settings module should be -in Python package syntax, e.g. ``mysite.settings``. If this isn't provided, -``django-admin.py`` will use the ``DJANGO_SETTINGS_MODULE`` environment -variable. +validate +-------- -Note that this option is unnecessary in ``manage.py``, because it takes care of -setting ``DJANGO_SETTINGS_MODULE`` for you. +Validates all installed models (according to the ``INSTALLED_APPS`` setting) +and prints validation errors to standard output. + +Default options +=============== + +Although some subcommands may allow their own custom options, every subcommand +allows for the following options: --pythonpath ------------ @@ -524,77 +677,20 @@ setting the Python path for you. .. _import search path: http://diveintopython.org/getting_to_know_python/everything_is_an_object.html ---format --------- - -Example usage:: - - django-admin.py dumpdata --format=xml - -Specifies the output format that will be used. The name provided must be the name -of a registered serializer. - ---help ------- - -Displays a help message that includes a terse list of all available actions and -options. - ---indent --------- - -Example usage:: - - django-admin.py dumpdata --indent=4 - -Specifies the number of spaces that will be used for indentation when -pretty-printing output. By default, output will *not* be pretty-printed. -Pretty-printing will only be enabled if the indent option is provided. - ---noinput ---------- - -Inform django-admin that the user should NOT be prompted for any input. Useful -if the django-admin script will be executed as an unattended, automated -script. - ---noreload +--settings ---------- -Disable the use of the auto-reloader when running the development server. - ---version ---------- - -Displays the current Django version. - -Example output:: - - 0.9.1 - 0.9.1 (SVN) - ---verbosity ------------ - Example usage:: - django-admin.py syncdb --verbosity=2 - -Verbosity determines the amount of notification and debug information that -will be printed to the console. '0' is no output, '1' is normal output, -and ``2`` is verbose output. - ---adminmedia ------------- - -Example usage:: + django-admin.py syncdb --settings=mysite.settings - django-admin.py --adminmedia=/tmp/new-admin-style/ +Explicitly specifies the settings module to use. The settings module should be +in Python package syntax, e.g. ``mysite.settings``. If this isn't provided, +``django-admin.py`` will use the ``DJANGO_SETTINGS_MODULE`` environment +variable. -Tells Django where to find the various CSS and JavaScript files for the admin -interface when running the development server. Normally these files are served -out of the Django source tree, but because some designers customize these files -for their site, this option allows you to test against custom versions. +Note that this option is unnecessary in ``manage.py``, because it takes care of +setting ``DJANGO_SETTINGS_MODULE`` for you. Extra niceties ============== diff --git a/docs/fastcgi.txt b/docs/fastcgi.txt index dff1689905..e50b9323bf 100644 --- a/docs/fastcgi.txt +++ b/docs/fastcgi.txt @@ -113,7 +113,7 @@ Running a preforked server on a Unix domain socket:: Run without daemonizing (backgrounding) the process (good for debugging):: - ./manage.py runfcgi daemonize=false socket=/tmp/mysite.sock + ./manage.py runfcgi daemonize=false socket=/tmp/mysite.sock maxrequests=1 Stopping the FastCGI daemon --------------------------- diff --git a/docs/model-api.txt b/docs/model-api.txt index 7dac54992f..efe62baf16 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -308,7 +308,7 @@ on the filesystem. Has three special arguments, of which the first is ``FilePathField`` will use to filter filenames. Note that the regex will be applied to the base filename, not the full path. Example: - ``"foo.*\.txt^"``, which will match a file called + ``"foo.*\.txt$"``, which will match a file called ``foo23.txt`` but not ``bar.txt`` or ``foo23.gif``. ``recursive`` Optional. Either ``True`` or ``False``. Default is @@ -1903,7 +1903,7 @@ as the value displayed to render an object in the Django admin site and as the value inserted into a template when it displays an object. Thus, you should always return a nice, human-readable string for the object's ``__str__``. Although this isn't required, it's strongly encouraged (see the description of -``__unicode__``, below, before putting ``_str__`` methods everywhere). +``__unicode__``, below, before putting ``__str__`` methods everywhere). For example:: diff --git a/docs/modpython.txt b/docs/modpython.txt index 5a1a3f0506..cbed0ee8c3 100644 --- a/docs/modpython.txt +++ b/docs/modpython.txt @@ -78,7 +78,7 @@ you have some applications under ``/usr/local/django-apps/`` (for example, ``DJANGO_SETTINGS_MODULE`` as in the above example. In this case, you would need to write your ``PythonPath`` directive as:: - PythonPath "['/var/production/django-apps/', '/var/www'] + sys.path" + PythonPath "['/usr/local/django-apps/', '/var/www'] + sys.path" With this path, ``import weblog`` and ``import mysite.settings`` will both work. If you had ``import blogroll`` in your code somewhere and ``blogroll`` diff --git a/docs/templates.txt b/docs/templates.txt index 8bfa40dc5f..9f2bec1c8b 100644 --- a/docs/templates.txt +++ b/docs/templates.txt @@ -1412,3 +1412,12 @@ A collection of template tags that can be useful while designing a website, such as a generator of Lorem Ipsum text. See the `webdesign documentation`_. .. _webdesign documentation: ../webdesign/ + +Next steps +========== + +Read the document `The Django template language: For Python programmers`_ if +you're interested in learning the template system from a technical +perspective -- how it works and how to extend it. + +.. _The Django template language: For Python programmers: ../templates_python/ diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index d27f0b0e54..47407573dd 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -32,6 +32,7 @@ ARTICLE_STATUS = ( class Category(models.Model): name = models.CharField(max_length=20) + slug = models.SlugField(max_length=20) url = models.CharField('The URL', max_length=40) def __unicode__(self): @@ -45,6 +46,7 @@ class Writer(models.Model): class Article(models.Model): headline = models.CharField(max_length=50) + slug = models.SlugField() pub_date = models.DateField() created = models.DateField(editable=False) writer = models.ForeignKey(Writer) @@ -79,9 +81,11 @@ __test__ = {'API_TESTS': """ >>> f = CategoryForm() >>> print f <tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr> +<tr><th><label for="id_slug">Slug:</label></th><td><input id="id_slug" type="text" name="slug" maxlength="20" /></td></tr> <tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr> >>> print f.as_ul() <li><label for="id_name">Name:</label> <input id="id_name" type="text" name="name" maxlength="20" /></li> +<li><label for="id_slug">Slug:</label> <input id="id_slug" type="text" name="slug" maxlength="20" /></li> <li><label for="id_url">The URL:</label> <input id="id_url" type="text" name="url" maxlength="40" /></li> >>> print f['name'] <input id="id_name" type="text" name="name" maxlength="20" /> @@ -89,24 +93,25 @@ __test__ = {'API_TESTS': """ >>> f = CategoryForm(auto_id=False) >>> print f.as_ul() <li>Name: <input type="text" name="name" maxlength="20" /></li> +<li>Slug: <input type="text" name="slug" maxlength="20" /></li> <li>The URL: <input type="text" name="url" maxlength="40" /></li> ->>> f = CategoryForm({'name': 'Entertainment', 'url': 'entertainment'}) +>>> f = CategoryForm({'name': 'Entertainment', 'slug': 'entertainment', 'url': 'entertainment'}) >>> f.is_valid() True >>> f.cleaned_data -{'url': u'entertainment', 'name': u'Entertainment'} +{'url': u'entertainment', 'name': u'Entertainment', 'slug': u'entertainment'} >>> obj = f.save() >>> obj <Category: Entertainment> >>> Category.objects.all() [<Category: Entertainment>] ->>> f = CategoryForm({'name': "It's a test", 'url': 'test'}) +>>> f = CategoryForm({'name': "It's a test", 'slug': 'its-test', 'url': 'test'}) >>> f.is_valid() True >>> f.cleaned_data -{'url': u'test', 'name': u"It's a test"} +{'url': u'test', 'name': u"It's a test", 'slug': u'its-test'} >>> obj = f.save() >>> obj <Category: It's a test> @@ -116,11 +121,11 @@ True If you call save() with commit=False, then it will return an object that hasn't yet been saved to the database. In this case, it's up to you to call save() on the resulting model instance. ->>> f = CategoryForm({'name': 'Third test', 'url': 'third'}) +>>> f = CategoryForm({'name': 'Third test', 'slug': 'third-test', 'url': 'third'}) >>> f.is_valid() True >>> f.cleaned_data -{'url': u'third', 'name': u'Third test'} +{'url': u'third', 'name': u'Third test', 'slug': u'third-test'} >>> obj = f.save(commit=False) >>> obj <Category: Third test> @@ -131,9 +136,9 @@ True [<Category: Entertainment>, <Category: It's a test>, <Category: Third test>] If you call save() with invalid data, you'll get a ValueError. ->>> f = CategoryForm({'name': '', 'url': 'foo'}) +>>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'}) >>> f.errors -{'name': [u'This field is required.']} +{'name': [u'This field is required.'], 'slug': [u'This field is required.']} >>> f.cleaned_data Traceback (most recent call last): ... @@ -142,7 +147,7 @@ AttributeError: 'CategoryForm' object has no attribute 'cleaned_data' Traceback (most recent call last): ... ValueError: The Category could not be created because the data didn't validate. ->>> f = CategoryForm({'name': '', 'url': 'foo'}) +>>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'}) >>> f.save() Traceback (most recent call last): ... @@ -160,6 +165,7 @@ fields with the 'choices' attribute are represented by a ChoiceField. >>> f = ArticleForm(auto_id=False) >>> print f <tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr> +<tr><th>Slug:</th><td><input type="text" name="slug" maxlength="50" /></td></tr> <tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr> <tr><th>Writer:</th><td><select name="writer"> <option value="" selected="selected">---------</option> @@ -210,7 +216,7 @@ current values are inserted as 'initial' data in each Field. >>> print f <tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /><br />Use both first and last names.</td></tr> ->>> art = Article(headline='Test article', pub_date=datetime.date(1988, 1, 4), writer=w, article='Hello.') +>>> art = Article(headline='Test article', slug='test-article', pub_date=datetime.date(1988, 1, 4), writer=w, article='Hello.') >>> art.save() >>> art.id 1 @@ -218,6 +224,7 @@ current values are inserted as 'initial' data in each Field. >>> f = TestArticleForm(auto_id=False) >>> print f.as_ul() <li>Headline: <input type="text" name="headline" value="Test article" maxlength="50" /></li> +<li>Slug: <input type="text" name="slug" value="test-article" maxlength="50" /></li> <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li> <li>Writer: <select name="writer"> <option value="">---------</option> @@ -236,7 +243,7 @@ current values are inserted as 'initial' data in each Field. <option value="2">It's a test</option> <option value="3">Third test</option> </select> Hold down "Control", or "Command" on a Mac, to select more than one.</li> ->>> f = TestArticleForm({'headline': u'Test headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'}) +>>> f = TestArticleForm({'headline': u'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'}) >>> f.is_valid() True >>> test_art = f.save() @@ -248,10 +255,11 @@ u'Test headline' You can create a form over a subset of the available fields by specifying a 'fields' argument to form_for_instance. ->>> PartialArticleForm = form_for_instance(art, fields=('headline','pub_date')) ->>> f = PartialArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04'}, auto_id=False) +>>> PartialArticleForm = form_for_instance(art, fields=('headline', 'slug', 'pub_date')) +>>> f = PartialArticleForm({'headline': u'New headline', 'slug': 'new-headline', 'pub_date': u'1988-01-04'}, auto_id=False) >>> print f.as_ul() <li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li> +<li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li> <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li> >>> f.is_valid() True @@ -272,6 +280,7 @@ Add some categories and test the many-to-many form output. >>> f = TestArticleForm(auto_id=False) >>> print f.as_ul() <li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li> +<li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li> <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li> <li>Writer: <select name="writer"> <option value="">---------</option> @@ -291,7 +300,7 @@ Add some categories and test the many-to-many form output. <option value="3">Third test</option> </select> Hold down "Control", or "Command" on a Mac, to select more than one.</li> ->>> f = TestArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04', +>>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', ... 'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']}) >>> new_art = f.save() >>> new_art.id @@ -301,7 +310,7 @@ Add some categories and test the many-to-many form output. [<Category: Entertainment>, <Category: It's a test>] Now, submit form data with no categories. This deletes the existing categories. ->>> f = TestArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04', +>>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', ... 'writer': u'1', 'article': u'Hello.'}) >>> new_art = f.save() >>> new_art.id @@ -312,7 +321,7 @@ Now, submit form data with no categories. This deletes the existing categories. Create a new article, with categories, via the form. >>> ArticleForm = form_for_model(Article) ->>> f = ArticleForm({'headline': u'The walrus was Paul', 'pub_date': u'1967-11-01', +>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', ... 'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']}) >>> new_art = f.save() >>> new_art.id @@ -323,7 +332,7 @@ Create a new article, with categories, via the form. Create a new article, with no categories, via the form. >>> ArticleForm = form_for_model(Article) ->>> f = ArticleForm({'headline': u'The walrus was Paul', 'pub_date': u'1967-11-01', +>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', ... 'writer': u'1', 'article': u'Test.'}) >>> new_art = f.save() >>> new_art.id @@ -335,7 +344,7 @@ Create a new article, with no categories, via the form. Create a new article, with categories, via the form, but use commit=False. The m2m data won't be saved until save_m2m() is invoked on the form. >>> ArticleForm = form_for_model(Article) ->>> f = ArticleForm({'headline': u'The walrus was Paul', 'pub_date': u'1967-11-01', +>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': u'1967-11-01', ... 'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']}) >>> new_art = f.save(commit=False) @@ -359,13 +368,14 @@ the Category model, we can use save_instance() to apply its changes to an existing Category instance. >>> class ShortCategory(Form): ... name = CharField(max_length=5) +... slug = CharField(max_length=5) ... url = CharField(max_length=3) >>> cat = Category.objects.get(name='Third test') >>> cat <Category: Third test> >>> cat.id 3 ->>> sc = ShortCategory({'name': 'Third', 'url': '3rd'}) +>>> sc = ShortCategory({'name': 'Third', 'slug': 'third', 'url': '3rd'}) >>> save_instance(sc, cat) <Category: Third> >>> Category.objects.get(id=3) @@ -378,6 +388,7 @@ the data in the database when the form is instantiated. >>> f = ArticleForm(auto_id=False) >>> print f.as_ul() <li>Headline: <input type="text" name="headline" maxlength="50" /></li> +<li>Slug: <input type="text" name="slug" maxlength="50" /></li> <li>Pub date: <input type="text" name="pub_date" /></li> <li>Writer: <select name="writer"> <option value="" selected="selected">---------</option> @@ -402,6 +413,7 @@ the data in the database when the form is instantiated. <Writer: Carl Bernstein> >>> print f.as_ul() <li>Headline: <input type="text" name="headline" maxlength="50" /></li> +<li>Slug: <input type="text" name="slug" maxlength="50" /></li> <li>Pub date: <input type="text" name="pub_date" /></li> <li>Writer: <select name="writer"> <option value="" selected="selected">---------</option> diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index d2da38f1c8..0284449a59 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -1169,6 +1169,31 @@ ValidationError: [u'Ensure that there are no more than 2 decimal places.'] Traceback (most recent call last): ... ValidationError: [u'Ensure that there are no more than 2 digits before the decimal point.'] +>>> f.clean('-12.34') +Decimal("-12.34") +>>> f.clean('-123.45') +Traceback (most recent call last): +... +ValidationError: [u'Ensure that there are no more than 4 digits in total.'] +>>> f.clean('-.12') +Decimal("-0.12") +>>> f.clean('-00.12') +Decimal("-0.12") +>>> f.clean('-000.12') +Decimal("-0.12") +>>> f.clean('-000.123') +Traceback (most recent call last): +... +ValidationError: [u'Ensure that there are no more than 2 decimal places.'] +>>> f.clean('-000.1234') +Traceback (most recent call last): +... +ValidationError: [u'Ensure that there are no more than 4 digits in total.'] +>>> f.clean('--0.12') +Traceback (most recent call last): +... +ValidationError: [u'Enter a number.'] + >>> f = DecimalField(max_digits=4, decimal_places=2, required=False) >>> f.clean('') |
