From 22e160945a38f82c7e30f7137b3fcac9dd778fba Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Sat, 25 Aug 2007 23:31:05 +0000 Subject: [PATCH] newforms-admin: Merged to [6013] git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@6014 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 6 +- django/__init__.py | 3 +- django/contrib/auth/views.py | 2 +- .../contrib/humanize/templatetags/humanize.py | 27 +++++++ django/core/management/commands/flush.py | 2 +- django/core/management/commands/runserver.py | 4 +- django/core/management/commands/sqlflush.py | 2 +- django/core/management/sql.py | 34 +++++++- django/db/backends/dummy/base.py | 10 +-- django/db/backends/oracle/base.py | 2 +- django/db/backends/postgresql/base.py | 3 - django/db/backends/postgresql/operations.py | 43 +++++----- .../db/backends/postgresql_psycopg2/base.py | 3 - django/utils/version.py | 39 ++++++++++ docs/add_ons.txt | 23 ++++++ docs/db-api.txt | 19 +++-- docs/django-admin.txt | 21 ++++- docs/faq.txt | 4 - docs/model-api.txt | 2 +- docs/newforms.txt | 78 ++++++++++++++----- docs/settings.txt | 9 ++- docs/testing.txt | 2 +- docs/tutorial01.txt | 33 ++++++++ tests/regressiontests/humanize/tests.py | 22 +++++- 24 files changed, 304 insertions(+), 89 deletions(-) create mode 100644 django/utils/version.py diff --git a/AUTHORS b/AUTHORS index eba4775fda..3b8a583e4a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -95,6 +95,7 @@ answer newbie questions, and generally made Django that much better: Alex Dedul deric@monowerks.com Max Derkachev + Sander Dijkhuis Jordan Dimov dne@mayonnaise.net Maximillian Dornseif @@ -137,6 +138,7 @@ answer newbie questions, and generally made Django that much better: Joe Heck Joel Heenan hipertracker@gmail.com + Deryck Hodge Brett Hoerner Ian Holsman Kieran Holland @@ -204,6 +206,7 @@ answer newbie questions, and generally made Django that much better: Andreas Mock Reza Mohammadi Aljosa Mohorovic + Ramiro Morales Eric Moritz mrmachine Robin Munn @@ -213,6 +216,7 @@ answer newbie questions, and generally made Django that much better: Fraser Nevett Sam Newman Neal Norwitz + Todd O'Bryan oggie rob Jay Parlar pavithran s @@ -228,10 +232,10 @@ answer newbie questions, and generally made Django that much better: Daniel Poelzleithner polpak@yahoo.com Matthias Pronk + Jyrki Pulliainen Johann Queuniet J. Rademaker Michael Radziej - Ramiro Morales Massimiliano Ravelli Brian Ray remco@diji.biz diff --git a/django/__init__.py b/django/__init__.py index b6540949f8..9c5fda133d 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -4,5 +4,6 @@ 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] + from django.utils.version import get_svn_revision + v = '%s-%s-%s' % (v, VERSION[-1], get_svn_revision()) return v diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 6c40228fab..f1129379d6 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -17,7 +17,7 @@ def login(request, template_name='registration/login.html'): errors = manipulator.get_validation_errors(request.POST) if not errors: # Light security check -- make sure redirect_to isn't garbage. - if not redirect_to or '://' in redirect_to or ' ' in redirect_to: + if not redirect_to or '//' in redirect_to or ' ' in redirect_to: from django.conf import settings redirect_to = settings.LOGIN_REDIRECT_URL from django.contrib.auth import login diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py index 699d9300b8..19591606f9 100644 --- a/django/contrib/humanize/templatetags/humanize.py +++ b/django/contrib/humanize/templatetags/humanize.py @@ -1,6 +1,9 @@ from django.utils.translation import ungettext, ugettext as _ from django.utils.encoding import force_unicode from django import template +from django.template import defaultfilters +from django.conf import settings +from datetime import date, timedelta import re register = template.Library() @@ -67,3 +70,27 @@ def apnumber(value): return value return (_('one'), _('two'), _('three'), _('four'), _('five'), _('six'), _('seven'), _('eight'), _('nine'))[value-1] register.filter(apnumber) + +def naturalday(value, arg=None): + """ + For date values that are tomorrow, today or yesterday compared to + present day returns representing string. Otherwise, returns a string + formatted according to settings.DATE_FORMAT. + """ + try: + value = date(value.year, value.month, value.day) + except AttributeError: + # Passed value wasn't a date object + return value + except ValueError: + # Date arguments out of range + return value + delta = value - date.today() + if delta.days == 0: + return _(u'today') + elif delta.days == 1: + return _(u'tomorrow') + elif delta.days == -1: + return _(u'yesterday') + return defaultfilters.date(value, arg) +register.filter(naturalday) diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index bd1dc204d2..395359d269 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -24,7 +24,7 @@ class Command(NoArgsCommand): except ImportError: pass - sql_list = sql_flush(self.style) + sql_list = sql_flush(self.style, only_django=True) if interactive: confirm = raw_input("""You have requested a flush of the database. diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py index f089e80b16..d06744e9fa 100644 --- a/django/core/management/commands/runserver.py +++ b/django/core/management/commands/runserver.py @@ -30,7 +30,7 @@ class Command(BaseCommand): raise CommandError("%r is not a valid port number." % port) use_reloader = options.get('use_reloader', True) - admin_media_dir = options.get('admin_media_dir', '') + admin_media_path = options.get('admin_media_path', '') shutdown_message = options.get('shutdown_message', '') quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C' @@ -42,7 +42,7 @@ class Command(BaseCommand): print "Development server is running at http://%s:%s/" % (addr, port) print "Quit the server with %s." % quit_command try: - path = admin_media_dir or django.__path__[0] + '/contrib/admin/media' + path = admin_media_path or django.__path__[0] + '/contrib/admin/media' handler = AdminMediaHandler(WSGIHandler(), path) run(addr, int(port), handler) except WSGIServerException, e: diff --git a/django/core/management/commands/sqlflush.py b/django/core/management/commands/sqlflush.py index 7d14fe61e1..261aa0d423 100644 --- a/django/core/management/commands/sqlflush.py +++ b/django/core/management/commands/sqlflush.py @@ -7,4 +7,4 @@ class Command(NoArgsCommand): def handle_noargs(self, **options): from django.core.management.sql import sql_flush - return '\n'.join(sql_flush(self.style)) + return '\n'.join(sql_flush(self.style, only_django=True)) diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 11056bbf3b..8f7f6a023a 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -13,6 +13,25 @@ def table_list(): cursor = connection.cursor() return get_introspection_module().get_table_list(cursor) +def django_table_list(only_existing=False): + """ + Returns a list of all table names that have associated Django models and + are in INSTALLED_APPS. + + If only_existing is True, the resulting list will only include the tables + that actually exist in the database. + """ + from django.db import models + tables = [] + for app in models.get_apps(): + for model in models.get_models(app): + tables.append(model._meta.db_table) + tables.extend([f.m2m_db_table() for f in model._meta.many_to_many]) + if only_existing: + existing = table_list() + tables = [t for t in tables if t in existing] + return tables + def installed_models(table_list): "Returns a set of all models that are installed, given a list of existing table names." from django.db import connection, models @@ -181,10 +200,19 @@ def sql_reset(app, style): "Returns a list of the DROP TABLE SQL, then the CREATE TABLE SQL, for the given module." return sql_delete(app, style) + sql_all(app, style) -def sql_flush(style): - "Returns a list of the SQL statements used to flush the database." +def sql_flush(style, only_django=False): + """ + Returns a list of the SQL statements used to flush the database. + + If only_django is True, then only table names that have associated Django + models and are in INSTALLED_APPS will be included. + """ from django.db import connection - statements = connection.ops.sql_flush(style, table_list(), sequence_list()) + if only_django: + tables = django_table_list() + else: + tables = table_list() + statements = connection.ops.sql_flush(style, tables, sequence_list()) return statements def sql_custom(app): diff --git a/django/db/backends/dummy/base.py b/django/db/backends/dummy/base.py index 50191f88fe..fd25d3038f 100644 --- a/django/db/backends/dummy/base.py +++ b/django/db/backends/dummy/base.py @@ -8,6 +8,7 @@ ImproperlyConfigured. """ from django.core.exceptions import ImproperlyConfigured +from django.db.backends import BaseDatabaseFeatures, BaseDatabaseOperations def complain(*args, **kwargs): raise ImproperlyConfigured, "You haven't set the DATABASE_ENGINE setting yet." @@ -21,13 +22,12 @@ class DatabaseError(Exception): class IntegrityError(DatabaseError): pass -class ComplainOnGetattr(object): - def __getattr__(self, *args, **kwargs): - complain() +class DatabaseOperations(BaseDatabaseOperations): + quote_name = complain class DatabaseWrapper(object): - features = ComplainOnGetattr() - ops = ComplainOnGetattr() + features = BaseDatabaseFeatures() + ops = DatabaseOperations() operators = {} cursor = complain _commit = complain diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 37cfd85282..23ce30f37e 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -70,7 +70,7 @@ class DatabaseOperations(BaseDatabaseOperations): return "DROP SEQUENCE %s;" % self.quote_name(get_sequence_name(table)) def field_cast_sql(self, db_type): - if db_type.endswith('LOB'): + if db_type and db_type.endswith('LOB'): return "DBMS_LOB.SUBSTR(%s)" else: return "%s" diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index ca07ae21d9..c8b87c2dd1 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -102,9 +102,6 @@ class DatabaseWrapper(BaseDatabaseWrapper): cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE]) cursor.execute("SET client_encoding to 'UNICODE'") cursor = UnicodeCursorWrapper(cursor, 'utf-8') - if self.ops.postgres_version is None: - cursor.execute("SELECT version()") - self.ops.postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')] return cursor def typecast_string(s): diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py index 21c017038f..9f36596ace 100644 --- a/django/db/backends/postgresql/operations.py +++ b/django/db/backends/postgresql/operations.py @@ -4,8 +4,17 @@ from django.db.backends import BaseDatabaseOperations # used by both the 'postgresql' and 'postgresql_psycopg2' backends. class DatabaseOperations(BaseDatabaseOperations): - def __init__(self, postgres_version=None): - self.postgres_version = postgres_version + def __init__(self): + self._postgres_version = None + + def _get_postgres_version(self): + if self._postgres_version is None: + from django.db import connection + cursor = connection.cursor() + cursor.execute("SELECT version()") + self._postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')] + return self._postgres_version + postgres_version = property(_get_postgres_version) def date_extract_sql(self, lookup_type, field_name): # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT @@ -52,28 +61,14 @@ class DatabaseOperations(BaseDatabaseOperations): for sequence_info in sequences: table_name = sequence_info['table'] column_name = sequence_info['column'] - if column_name and len(column_name)>0: - # sequence name in this case will be __seq - sql.append("%s %s %s %s %s %s;" % \ - (style.SQL_KEYWORD('ALTER'), - style.SQL_KEYWORD('SEQUENCE'), - style.SQL_FIELD(self.quote_name('%s_%s_seq' % (table_name, column_name))), - style.SQL_KEYWORD('RESTART'), - style.SQL_KEYWORD('WITH'), - style.SQL_FIELD('1') - ) - ) + if column_name and len(column_name) > 0: + sequence_name = '%s_%s_seq' % (table_name, column_name) else: - # sequence name in this case will be
_id_seq - sql.append("%s %s %s %s %s %s;" % \ - (style.SQL_KEYWORD('ALTER'), - style.SQL_KEYWORD('SEQUENCE'), - style.SQL_FIELD(self.quote_name('%s_id_seq' % table_name)), - style.SQL_KEYWORD('RESTART'), - style.SQL_KEYWORD('WITH'), - style.SQL_FIELD('1') - ) - ) + sequence_name = '%s_id_seq' % table_name + sql.append("%s setval('%s', 1, false);" % \ + (style.SQL_KEYWORD('SELECT'), + style.SQL_FIELD(self.quote_name(sequence_name))) + ) return sql else: return [] @@ -106,4 +101,4 @@ class DatabaseOperations(BaseDatabaseOperations): style.SQL_KEYWORD('IS NOT'), style.SQL_KEYWORD('FROM'), style.SQL_TABLE(f.m2m_db_table()))) - return output \ No newline at end of file + return output diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index 43ca7a1ec5..a7b080d505 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -64,7 +64,4 @@ class DatabaseWrapper(BaseDatabaseWrapper): cursor.tzinfo_factory = None if set_tz: cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE]) - if self.ops.postgres_version is None: - cursor.execute("SELECT version()") - self.ops.postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')] return cursor diff --git a/django/utils/version.py b/django/utils/version.py new file mode 100644 index 0000000000..cf8085653f --- /dev/null +++ b/django/utils/version.py @@ -0,0 +1,39 @@ +import django +import os.path +import re + +def get_svn_revision(path=None): + """ + Returns the SVN revision in the form SVN-XXXX, + where XXXX is the revision number. + + Returns SVN-unknown if anything goes wrong, such as an unexpected + format of internal SVN files. + + If path is provided, it should be a directory whose SVN info you want to + inspect. If it's not provided, this will use the root django/ package + directory. + """ + rev = None + if path is None: + path = django.__path__[0] + entries_path = '%s/.svn/entries' % path + + if os.path.exists(entries_path): + entries = open(entries_path, 'r').read() + # Versions >= 7 of the entries file are flat text. The first line is + # the version number. The next set of digits after 'dir' is the revision. + if re.match('(\d+)', entries): + rev_match = re.search('\d+\s+dir\s+(\d+)', entries) + if rev_match: + rev = rev_match.groups()[0] + # Older XML versions of the file specify revision as an attribute of + # the first entries node. + else: + from xml.dom import minidom + dom = minidom.parse(entries_path) + rev = dom.getElementsByTagName('entry')[0].getAttribute('revision') + + if rev: + return u'SVN-%s' % rev + return u'SVN-unknown' diff --git a/docs/add_ons.txt b/docs/add_ons.txt index 8682208970..a1d78b8685 100644 --- a/docs/add_ons.txt +++ b/docs/add_ons.txt @@ -138,6 +138,29 @@ Examples: You can pass in either an integer or a string representation of an integer. +naturalday +---------- + +**New in Django development version** + +For dates that are the current day or within one day, return "today", +"tomorrow" or "yesterday", as appropriate. Otherwise, format the date using +the passed in format string. + +**Argument:** Date formatting string as described in default tag now_. + +.. _now: ../templates/#now + +Examples (when 'today' is 17 Feb 2007): + + * ``16 Feb 2007`` becomes ``yesterday``. + * ``17 Feb 2007`` becomes ``today``. + * ``18 Feb 2007`` becomes ``tomorrow``. + * Any other day is formatted according to given argument or the + `DATE_FORMAT`_ setting if no argument is given. + +.. _DATE_FORMAT: ../settings/#date_format + flatpages ========= diff --git a/docs/db-api.txt b/docs/db-api.txt index 766a6ae519..3198f335c4 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -207,14 +207,23 @@ the database until you explicitly call ``save()``. The ``save()`` method has no return value. -Updating ``ForeignKey`` fields works exactly the same way; simply assign an -object of the right type to the field in question:: +Saving ForeignKey and ManyToManyField fields +-------------------------------------------- + +Updating ``ForeignKey`` fields works exactly the same way as saving a normal +field; simply assign an object of the right type to the field in question:: + + cheese_blog = Blog.objects.get(name="Cheddar Talk") + entry.blog = cheese_blog + entry.save() + +Updating a ``ManyToManyField`` works a little differently; use the ``add()`` +method on the field to add a record to the relation:: joe = Author.objects.create(name="Joe") - entry.author = joe - entry.save() + entry.authors.add(joe) -Django will complain if you try to assign an object of the wrong type. +Django will complain if you try to assign or add an object of the wrong type. How Django knows to UPDATE vs. INSERT ------------------------------------- diff --git a/docs/django-admin.txt b/docs/django-admin.txt index aea990c5dc..366483a47a 100644 --- a/docs/django-admin.txt +++ b/docs/django-admin.txt @@ -124,6 +124,13 @@ 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. +The behavior of this command has changed in the Django development version. +Previously, this command cleared *every* table in the database, including any +table that Django didn't know about (i.e., tables that didn't have associated +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``. + inspectdb --------- @@ -240,6 +247,7 @@ Executes the equivalent of ``sqlreset`` for the given appnames. runfcgi [options] ----------------- + Starts a set of FastCGI processes suitable for use with any web server which supports the FastCGI protocol. See the `FastCGI deployment documentation`_ for details. Requires the Python FastCGI module from @@ -337,7 +345,7 @@ Refer to the description of ``sqlcustom`` for an explanation of how to specify initial data. sqlclear [appname appname ...] --------------------------------------- +------------------------------ Prints the DROP TABLE SQL statements for the given appnames. @@ -360,18 +368,23 @@ table modifications, or insert any SQL functions into the database. Note that the order in which the SQL files are processed is undefined. +sqlflush +-------- + +Prints the SQL statements that would be executed for the `flush`_ command. + sqlindexes [appname appname ...] ----------------------------------------- +-------------------------------- Prints the CREATE INDEX SQL statements for the given appnames. sqlreset [appname appname ...] --------------------------------------- +------------------------------ Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given appnames. sqlsequencereset [appname appname ...] ----------------------------------------------- +-------------------------------------- Prints the SQL statements for resetting sequences for the given appnames. diff --git a/docs/faq.txt b/docs/faq.txt index 844ea77809..cef0508562 100644 --- a/docs/faq.txt +++ b/docs/faq.txt @@ -204,10 +204,6 @@ out a few points, we want to make sure they reflect the final state of things at Django 1.0, not some intermediary step. In other words, we don't want to spend a lot of energy creating screencasts yet, because Django APIs will shift. -In the meantime, though, check out this `unofficial Django screencast`_. - -.. _unofficial Django screencast: http://www.throwingbeans.org/django_screencasts.html - Is Django a content-management-system (CMS)? -------------------------------------------- diff --git a/docs/model-api.txt b/docs/model-api.txt index 0f872c3097..7dac54992f 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -344,7 +344,7 @@ development version. See the `Django 0.96 documentation`_ for the old behavior. ``ImageField`` ~~~~~~~~~~~~~~ -Like ``FileField``, but validates that the uploaded object is a valid +Like `FileField`_, but validates that the uploaded object is a valid image. Has two extra optional arguments, ``height_field`` and ``width_field``, which, if set, will be auto-populated with the height and width of the image each time a model instance is saved. diff --git a/docs/newforms.txt b/docs/newforms.txt index 503bd6634f..499892234c 100644 --- a/docs/newforms.txt +++ b/docs/newforms.txt @@ -1462,10 +1462,10 @@ commonly used groups of widgets: ``Textarea`` ```` ``CheckboxInput`` `` On a real web page, you probably don't want every widget to look the same. You -might want a larger input element for the comment, and you might want the -'name' widget to have some special CSS class. To do this, you specify a -custom widget for your fields, and specify some attributes to use +might want a larger input element for the comment, and you might want the +'name' widget to have some special CSS class. To do this, you specify a +custom widget for your fields, and specify some attributes to use when rendering those widgets:: class CommentForm(forms.Form): - name = forms.CharField( + name = forms.CharField( widget=forms.TextInput(attrs={'class':'special'})) url = forms.URLField() comment = forms.CharField( @@ -1546,19 +1546,19 @@ Custom Widgets -------------- When you start to write a lot of forms, you will probably find that you will -reuse certain sets of widget attributes over and over again. Rather than -repeat these attribute definitions every time you need them, Django allows +reuse certain sets of widget attributes over and over again. Rather than +repeat these attribute definitions every time you need them, Django allows you to capture those definitions as a custom widget. For example, if you find that you are including a lot of comment fields on forms, -you could capture the idea of a ``TextInput`` with a specific ``size`` attribute +you could capture the idea of a ``TextInput`` with a specific ``size`` attribute as a custom extension to the ``TextInput`` widget:: class CommentWidget(forms.TextInput): def __init__(self, *args, **kwargs): kwargs.setdefault('attrs',{}).update({'size': '40'}) - super(forms.TextInput, self).__init__(*args, **kwargs) - + super(CommentWidget, self).__init__(*args, **kwargs) + Then you can use this widget in your forms:: class CommentForm(forms.Form): @@ -1566,8 +1566,8 @@ Then you can use this widget in your forms:: url = forms.URLField() comment = forms.CharField(widget=CommentWidget) -You can even customize your custom widget, in the same way as you would -any other widget. Adding a once-off class to your ``CommentWidget`` is as +You can even customize your custom widget, in the same way as you would +any other widget. Adding a once-off class to your ``CommentWidget`` is as simple as adding an attribute definition:: class CommentForm(forms.Form): @@ -1582,14 +1582,14 @@ by defining:: class CommentInput(forms.CharField): widget = CommentWidget - + You can then use this field whenever you have a form that requires a comment:: class CommentForm(forms.Form): name = forms.CharField() url = forms.URLField() comment = CommentInput() - + Generating forms for models =========================== @@ -1934,6 +1934,42 @@ will raise ``ValueError`` if the data doesn't validate. ``form_for_instance()`` has ``form``, ``fields`` and ``formfield_callback`` arguments that behave the same way as they do for ``form_for_model()``. +Let's modify the earlier `contact form`_ view example a little bit. Suppose we +have a ``Message`` model that holds each contact submission. Something like:: + + class Message(models.Model): + subject = models.CharField(max_length=100) + message = models.TextField() + sender = models.EmailField() + cc_myself = models.BooleanField() + +You could use this model to create a form (using ``form_for_model()``). You +could also use existing ``Message`` instances to create a form for editing +messages. The earlier_ view can be changed slightly to accept the ``id`` value +of an existing ``Message`` and present it for editing:: + + def contact_edit(request, msg_id): + # Create the form from the message id. + message = get_object_or_404(Message, id=msg_id) + ContactForm = form_for_instance(message) + + if request.method == 'POST': + form = ContactForm(request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect('/url/on_success/') + else: + form = ContactForm() + return render_to_response('contact.html', {'form': form}) + +Aside from how we create the ``ContactForm`` class here, the main point to +note is that the form display in the ``GET`` branch of the function +will use the values from the ``message`` instance as initial values for the +form field. + +.. _contact form: `Simple view example`_ +.. _earlier: `Simple view example`_ + When should you use ``form_for_model()`` and ``form_for_instance()``? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/settings.txt b/docs/settings.txt index 050e377713..3f98296778 100644 --- a/docs/settings.txt +++ b/docs/settings.txt @@ -1101,10 +1101,11 @@ To disable this behavior, just remove all entries from the ``ADMINS`` setting. 404 errors ---------- -When ``DEBUG`` is ``False`` and your ``MIDDLEWARE_CLASSES`` setting includes -``CommonMiddleware``, Django will e-mail the users listed in the ``MANAGERS`` -setting whenever your code raises a 404 and the request has a referer. -(It doesn't bother to e-mail for 404s that don't have a referer.) +When ``DEBUG`` is ``False``, ``SEND_BROKEN_LINK_EMAILS`` is ``True`` and your +``MIDDLEWARE_CLASSES`` setting includes ``CommonMiddleware``, Django will +e-mail the users listed in the ``MANAGERS`` setting whenever your code raises +a 404 and the request has a referer. (It doesn't bother to e-mail for 404s +that don't have a referer.) You can tell Django to stop reporting particular 404s by tweaking the ``IGNORABLE_404_ENDS`` and ``IGNORABLE_404_STARTS`` settings. Both should be a diff --git a/docs/testing.txt b/docs/testing.txt index f189aa4578..e27479cc34 100644 --- a/docs/testing.txt +++ b/docs/testing.txt @@ -473,7 +473,7 @@ Once you have a ``Client`` instance, you can call any of the following methods: data. For example:: >>> c = Client() - >>> c.get('/login/', {'name': 'fred', 'passwd': 'secret'}) + >>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'}) ...will result in the evaluation of a POST request to this URL:: diff --git a/docs/tutorial01.txt b/docs/tutorial01.txt index cf2b76e9be..65ea7503d7 100644 --- a/docs/tutorial01.txt +++ b/docs/tutorial01.txt @@ -259,6 +259,22 @@ These concepts are represented by simple Python classes. Edit the choice = models.CharField(max_length=200) votes = models.IntegerField() +.. adminition:: Errors about ``max_length`` + + If Django gives you an error message saying that ``max_length`` is + not a valid argument, you're most likely using an old version of + Django. (This version of the tutorial is written for the latest + development version of Django.) If you're using a Subversion checkout + of Django's development version (see `the installation docs`_ for + more information), you shouldn't have any problems. + + If you want to stick with an older version of Django, you'll want to + switch to `the Django 0.96 tutorial`_, because this tutorial covers + several features that only exist in the Django development version. + +.. _the installation docs: ../install/ +.. _the Django 0.96 tutorial: ../0.96/tutorial01/ + The code is straightforward. Each model is represented by a class that subclasses ``django.db.models.Model``. Each model has a number of class variables, each of which represents a database field in the model. @@ -487,6 +503,23 @@ the ``polls/models.py`` file) and adding a ``__unicode__()`` method to both def __unicode__(self): return self.choice +.. admonition:: If ``__unicode__()`` doesn't seem to work + + If you add the ``__unicode__()`` method to your models and don't + see any change in how they're represented, you're most likely using + an old version of Django. (This version of the tutorial is written + for the latest development version of Django.) If you're using a + Subversion checkout of of Django's development version (see `the + installation docs`_ for more information), you shouldn't have any + problems. + + If you want to stick with an older version of Django, you'll want to + switch to `the Django 0.96 tutorial`_, because this tutorial covers + several features that only exist in the Django development version. + +.. _the installation docs: ../install/ +.. _the Django 0.96 tutorial: ../0.96/tutorial01/ + It's important to add ``__unicode__()`` methods to your models, not only for your own sanity when dealing with the interactive prompt, but also because objects' representations are used throughout Django's automatically-generated diff --git a/tests/regressiontests/humanize/tests.py b/tests/regressiontests/humanize/tests.py index f94d642973..196488ba6e 100644 --- a/tests/regressiontests/humanize/tests.py +++ b/tests/regressiontests/humanize/tests.py @@ -1,5 +1,8 @@ import unittest +from datetime import timedelta, date from django.template import Template, Context, add_to_builtins +from django.utils.dateformat import DateFormat +from django.utils.translation import ugettext as _ add_to_builtins('django.contrib.humanize.templatetags.humanize') @@ -8,13 +11,12 @@ class HumanizeTests(unittest.TestCase): def humanize_tester(self, test_list, result_list, method): # Using max below ensures we go through both lists # However, if the lists are not equal length, this raises an exception - for index in xrange(len(max(test_list,result_list))): + for index in xrange(max(len(test_list), len(result_list))): test_content = test_list[index] t = Template('{{ test_content|%s }}' % method) rendered = t.render(Context(locals())).strip() self.assertEqual(rendered, result_list[index], - msg="""%s test failed, produced %s, -should've produced %s""" % (method, rendered, result_list[index])) + msg="%s test failed, produced %s, should've produced %s" % (method, rendered, result_list[index])) def test_ordinal(self): test_list = ('1','2','3','4','11','12', @@ -49,6 +51,20 @@ should've produced %s""" % (method, rendered, result_list[index])) self.humanize_tester(test_list, result_list, 'apnumber') + def test_naturalday(self): + from django.template import defaultfilters + today = date.today() + yesterday = today - timedelta(days=1) + tomorrow = today + timedelta(days=1) + someday = today - timedelta(days=10) + notdate = u"I'm not a date value" + + test_list = (today, yesterday, tomorrow, someday, notdate) + someday_result = defaultfilters.date(someday) + result_list = (_(u'today'), _(u'yesterday'), _(u'tomorrow'), + someday_result, u"I'm not a date value") + self.humanize_tester(test_list, result_list, 'naturalday') + if __name__ == '__main__': unittest.main()
Comment: