diff --git a/AUTHORS b/AUTHORS index 645913e7c4..c7aba07430 100644 --- a/AUTHORS +++ b/AUTHORS @@ -78,6 +78,7 @@ answer newbie questions, and generally made Django that much better: Clint Ecker Enrico favo@exoweb.net + Eric Floehr gandalf@owca.info Baishampayan Ghose martin.glueck@gmail.com @@ -127,6 +128,7 @@ answer newbie questions, and generally made Django that much better: mmarshall Eric Moritz Robin Munn + Robert Myers Nebojša Dorđević Fraser Nevett Sam Newman @@ -151,6 +153,7 @@ answer newbie questions, and generally made Django that much better: serbaut@gmail.com Pete Shinners SmileyChris + smurf@smurf.noris.de sopel Thomas Steinacher nowell strite diff --git a/django/bin/daily_cleanup.py b/django/bin/daily_cleanup.py index 667e0f16c6..3b83583d73 100644 --- a/django/bin/daily_cleanup.py +++ b/django/bin/daily_cleanup.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + """ Daily cleanup job. diff --git a/django/contrib/admin/templates/admin/auth/user/change_password.html b/django/contrib/admin/templates/admin/auth/user/change_password.html new file mode 100644 index 0000000000..80990faa24 --- /dev/null +++ b/django/contrib/admin/templates/admin/auth/user/change_password.html @@ -0,0 +1,52 @@ +{% extends "admin/base_site.html" %} +{% load i18n admin_modify adminmedia %} +{% block extrahead %}{{ block.super }} + +{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %} +{% endblock %} +{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %} +{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %} +{% block userlinks %}{% trans 'Documentation' %} / {% trans 'Change password' %} / {% trans 'Log out' %}{% endblock %} +{% block breadcrumbs %}{% if not is_popup %} + +{% endif %}{% endblock %} +{% block content %}
+
{% block form_top %}{% endblock %} +
+{% if is_popup %}{% endif %} +{% if form.error_dict %} +

+ {% blocktrans count form.error_dict.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %} +

+{% endif %} + +

{% blocktrans with original.username|escape as username %}Enter a new username and password for the user {{ username }}.{% endblocktrans %}

+ +
+ +
+ {{ form.password1.html_error_list }} + {{ form.password1 }} +
+ +
+ {{ form.password2.html_error_list }} + {{ form.password2 }} +

{% trans 'Enter the same password as above, for verification.' %}

+
+ +
+ +
+ +
+ + +
+
+{% endblock %} diff --git a/django/contrib/admin/urls.py b/django/contrib/admin/urls.py index aaf9841e45..508bb3a1ca 100644 --- a/django/contrib/admin/urls.py +++ b/django/contrib/admin/urls.py @@ -29,6 +29,8 @@ urlpatterns = patterns('', # "Add user" -- a special-case view ('^auth/user/add/$', 'django.contrib.admin.views.auth.user_add_stage'), + # "Change user password" -- another special-case view + ('^auth/user/(\d+)/password/$', 'django.contrib.admin.views.auth.user_change_password'), # Add/change/delete/history ('^([^/]+)/([^/]+)/$', 'django.contrib.admin.views.main.change_list'), diff --git a/django/contrib/admin/views/auth.py b/django/contrib/admin/views/auth.py index abc02e96c4..bea1f8533c 100644 --- a/django/contrib/admin/views/auth.py +++ b/django/contrib/admin/views/auth.py @@ -1,10 +1,11 @@ from django.contrib.admin.views.decorators import staff_member_required -from django.contrib.auth.forms import UserCreationForm +from django.contrib.auth.forms import UserCreationForm, AdminPasswordChangeForm from django.contrib.auth.models import User from django.core.exceptions import PermissionDenied from django import oldforms, template -from django.shortcuts import render_to_response +from django.shortcuts import render_to_response, get_object_or_404 from django.http import HttpResponseRedirect +from django.utils.html import escape def user_add_stage(request): if not request.user.has_perm('auth.change_user'): @@ -42,3 +43,35 @@ def user_add_stage(request): 'username_help_text': User._meta.get_field('username').help_text, }, context_instance=template.RequestContext(request)) user_add_stage = staff_member_required(user_add_stage) + +def user_change_password(request, id): + if not request.user.has_perm('auth.change_user'): + raise PermissionDenied + user = get_object_or_404(User, pk=id) + manipulator = AdminPasswordChangeForm(user) + if request.method == 'POST': + new_data = request.POST.copy() + errors = manipulator.get_validation_errors(new_data) + if not errors: + new_user = manipulator.save(new_data) + msg = _('Password changed successfully.') + request.user.message_set.create(message=msg) + return HttpResponseRedirect('..') + else: + errors = new_data = {} + form = oldforms.FormWrapper(manipulator, new_data, errors) + return render_to_response('admin/auth/user/change_password.html', { + 'title': _('Change password: %s') % escape(user.username), + 'form': form, + 'is_popup': request.REQUEST.has_key('_popup'), + 'add': True, + 'change': False, + 'has_delete_permission': False, + 'has_change_permission': True, + 'has_absolute_url': False, + 'first_form_field_id': 'id_password1', + 'opts': User._meta, + 'original': user, + 'show_save': True, + }, context_instance=template.RequestContext(request)) +user_change_password = staff_member_required(user_change_password) diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 548f8b9687..282038e205 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -46,8 +46,8 @@ def quote(s): """ Ensure that primary key values do not confuse the admin URLs by escaping any '/', '_' and ':' characters. Similar to urllib.quote, except that the - quoting is slightly different so that it doesn't get autoamtically - unquoted by the web browser. + quoting is slightly different so that it doesn't get automatically + unquoted by the Web browser. """ if type(s) != type(''): return s diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index aea52d1f2a..7700ec7d7a 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -126,3 +126,18 @@ class PasswordChangeForm(oldforms.Manipulator): "Saves the new password." self.user.set_password(new_data['new_password1']) self.user.save() + +class AdminPasswordChangeForm(oldforms.Manipulator): + "A form used to change the password of a user in the admin interface." + def __init__(self, user): + self.user = user + self.fields = ( + oldforms.PasswordField(field_name='password1', length=30, maxlength=60, is_required=True), + oldforms.PasswordField(field_name='password2', length=30, maxlength=60, is_required=True, + validator_list=[validators.AlwaysMatchesOtherField('password1', _("The two password fields didn't match."))]), + ) + + def save(self, new_data): + "Saves the new password." + self.user.set_password(new_data['password1']) + self.user.save() diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 58cc07efa9..4f4f0b7538 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -91,7 +91,7 @@ class User(models.Model): first_name = models.CharField(_('first name'), maxlength=30, blank=True) last_name = models.CharField(_('last name'), maxlength=30, blank=True) email = models.EmailField(_('e-mail address'), blank=True) - password = models.CharField(_('password'), maxlength=128, help_text=_("Use '[algo]$[salt]$[hexdigest]'")) + password = models.CharField(_('password'), maxlength=128, help_text=_("Use '[algo]$[salt]$[hexdigest]' or use the change password form.")) is_staff = models.BooleanField(_('staff status'), default=False, help_text=_("Designates whether the user can log into this admin site.")) is_active = models.BooleanField(_('active'), default=True, help_text=_("Designates whether this user can log into the Django admin. Unselect this instead of deleting accounts.")) is_superuser = models.BooleanField(_('superuser status'), default=False, help_text=_("Designates that this user has all permissions without explicitly assigning them.")) diff --git a/django/contrib/formtools/preview.py b/django/contrib/formtools/preview.py index 9a9371b5f8..daecba7928 100644 --- a/django/contrib/formtools/preview.py +++ b/django/contrib/formtools/preview.py @@ -48,6 +48,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.http import Http404 from django.shortcuts import render_to_response +from django.template.context import RequestContext import cPickle as pickle import md5 @@ -91,7 +92,9 @@ class FormPreview(object): def preview_get(self, request): "Displays the form" f = self.form(auto_id=AUTO_ID) - return render_to_response(self.form_template, {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state}) + return render_to_response(self.form_template, + {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state}, + context_instance=RequestContext(request)) def preview_post(self, request): "Validates the POST data. If valid, displays the preview page. Else, redisplays form." @@ -100,9 +103,9 @@ class FormPreview(object): if f.is_valid(): context['hash_field'] = self.unused_name('hash') context['hash_value'] = self.security_hash(request, f) - return render_to_response(self.preview_template, context) + return render_to_response(self.preview_template, context, context_instance=RequestContext(request)) else: - return render_to_response(self.form_template, context) + return render_to_response(self.form_template, context, context_instance=RequestContext(request)) def post_post(self, request): "Validates the POST data. If valid, calls done(). Else, redisplays form." @@ -112,7 +115,9 @@ class FormPreview(object): return self.failed_hash(request) # Security hash failed. return self.done(request, f.clean_data) else: - return render_to_response(self.form_template, {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state}) + return render_to_response(self.form_template, + {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state}, + context_instance=RequestContext(request)) # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ######################## diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 38a385208a..650da4faca 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -98,9 +98,11 @@ class DatabaseWrapper(local): kwargs['port'] = int(settings.DATABASE_PORT) kwargs.update(self.options) self.connection = Database.connect(**kwargs) - cursor = self.connection.cursor() - if self.connection.get_server_info() >= '4.1': - cursor.execute("SET NAMES 'utf8'") + cursor = self.connection.cursor() + if self.connection.get_server_info() >= '4.1': + cursor.execute("SET NAMES 'utf8'") + else: + cursor = self.connection.cursor() if settings.DEBUG: return util.CursorDebugWrapper(MysqlDebugWrapper(cursor), self) return cursor diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index 3dceb5bf52..e7714a73d9 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -43,7 +43,7 @@ class UnicodeCursorWrapper(object): return self.cursor.execute(sql, [smart_basestring(p, self.charset) for p in params]) def executemany(self, sql, param_list): - new_param_list = [[smart_basestring(p, self.charset) for p in params] for params in param_list] + new_param_list = [tuple([smart_basestring(p, self.charset) for p in params]) for params in param_list] return self.cursor.executemany(sql, new_param_list) def __getattr__(self, attr): diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 75d1f6e27a..e51c779550 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -729,6 +729,10 @@ class ManyToManyField(RelatedField, Field): return getattr(obj, self.attname).all() def formfield(self, initial=None): + # If initial is passed in, it's a list of related objects, but the + # MultipleChoiceField takes a list of IDs. + if initial is not None: + initial = [i._get_pk_val() for i in initial] return forms.MultipleChoiceField(choices=self.get_choices_default(), required=not self.blank, label=capfirst(self.verbose_name), initial=initial) class ManyToOneRel(object): diff --git a/django/shortcuts/__init__.py b/django/shortcuts/__init__.py index 76d54917ad..be2155bb09 100644 --- a/django/shortcuts/__init__.py +++ b/django/shortcuts/__init__.py @@ -4,20 +4,29 @@ from django.template import loader from django.http import HttpResponse, Http404 - +from django.db.models.manager import Manager def render_to_response(*args, **kwargs): return HttpResponse(loader.render_to_string(*args, **kwargs)) load_and_render = render_to_response # For backwards compatibility. def get_object_or_404(klass, *args, **kwargs): + if isinstance(klass, Manager): + manager = klass + klass = manager.model + else: + manager = klass._default_manager try: - return klass._default_manager.get(*args, **kwargs) + return manager.get(*args, **kwargs) except klass.DoesNotExist: raise Http404 def get_list_or_404(klass, *args, **kwargs): - obj_list = list(klass._default_manager.filter(*args, **kwargs)) + if isinstance(klass, Manager): + manager = klass + else: + manager = klass._default_manager + obj_list = list(manager.filter(*args, **kwargs)) if not obj_list: raise Http404 return obj_list diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 969ef7b28b..1d0f78ce12 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -27,20 +27,38 @@ def fix_ampersands(value): from django.utils.html import fix_ampersands return fix_ampersands(value) -def floatformat(text): +def floatformat(text, arg=-1): """ - Displays a floating point number as 34.2 (with one decimal place) -- but - only if there's a point to be displayed + If called without an argument, displays a floating point + number as 34.2 -- but only if there's a point to be displayed. + With a positive numeric argument, it displays that many decimal places + always. + With a negative numeric argument, it will display that many decimal + places -- but only if there's places to be displayed. + Examples: + num1 = 34.23234 + num2 = 34.00000 + num1|floatformat results in 34.2 + num2|floatformat is 34 + num1|floatformat:3 is 34.232 + num2|floatformat:3 is 34.000 + num1|floatformat:-3 is 34.232 + num2|floatformat:-3 is 34 """ try: f = float(text) except ValueError: return '' + try: + d = int(arg) + except ValueError: + return str(f) m = f - int(f) - if m: - return '%.1f' % f - else: + if not m and d < 0: return '%d' % int(f) + else: + formatstr = '%%.%df' % abs(d) + return formatstr % f def linenumbers(value): "Displays text with line numbers" diff --git a/docs/db-api.txt b/docs/db-api.txt index 13a32bd0b8..0af2c773fb 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -1704,6 +1704,46 @@ For every ``ImageField``, the object will have ``get_FOO_height()`` and ``get_FOO_width()`` methods, where ``FOO`` is the name of the field. This returns the height (or width) of the image, as an integer, in pixels. +Shortcuts +========= + +As you develop views, you will discover a number of common idioms in the +way you use the database API. Django encodes some of these idioms as +shortcuts that can be used to simplify the process of writing views. + +get_object_or_404() +------------------- + +One common idiom to use ``get()`` and raise ``Http404`` if the +object doesn't exist. This idiom is captured by ``get_object_or_404()``. +This function takes a Django model as its first argument and an +arbitrary number of keyword arguments, which it passes to the manager's +``get()`` function. It raises ``Http404`` if the object doesn't +exist. For example:: + + # Get the Entry with a primary key of 3 + e = get_object_or_404(Entry, pk=3) + +When you provide a model to this shortcut function, the default manager +is used to execute the underlying ``get()`` query. If you don't want to +use the default manager, or you want to search a list of related objects, +you can provide ``get_object_or_404()`` with a manager object, instead. +For example:: + + # Get the author of blog instance `e` with a name of 'Fred' + a = get_object_or_404(e.authors, name='Fred') + + # Use a custom manager 'recent_entries' in the search for an + # entry with a primary key of 3 + e = get_object_or_404(Entry.recent_entries, pk=3) + +get_list_or_404() +----------------- + +``get_list_or_404`` behaves the same was as ``get_object_or_404()`` +-- except the it uses using ``filter()`` instead of ``get()``. It raises +``Http404`` if the list is empty. + Falling back to raw SQL ======================= diff --git a/docs/model-api.txt b/docs/model-api.txt index 1aa8c811f4..725ec52141 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -94,7 +94,7 @@ Django places only two restrictions on model field names: the way Django's query lookup syntax works. For example:: class Example(models.Model): - foo__bar = models.IntegerField() 'foo__bar' has two underscores! + foo__bar = models.IntegerField() # 'foo__bar' has two underscores! These limitations can be worked around, though, because your field name doesn't necessarily have to match your database column name. See `db_column`_ below. diff --git a/docs/newforms.txt b/docs/newforms.txt index ec721effc4..7744d83e5e 100644 --- a/docs/newforms.txt +++ b/docs/newforms.txt @@ -72,6 +72,10 @@ The library deals with these concepts: * **Form** -- A collection of fields that knows how to validate itself and display itself as HTML. +The library is decoupled from the other Django components, such as the database +layer, views and templates. It relies only on Django settings, a couple of +``django.utils`` helper functions and Django's internationalization system. + Form objects ============ @@ -282,12 +286,41 @@ example, in the ``ContactForm`` example, the fields are defined in the order ``subject``, ``message``, ``sender``, ``cc_myself``. To reorder the HTML output, just change the order in which those fields are listed in the class. -Using forms to validate data ----------------------------- +More granular output +~~~~~~~~~~~~~~~~~~~~ -In addition to HTML form display, a ``Form`` class is responsible for -validating data. +The ``as_p()``, ``as_ul()`` and ``as_table()`` methods are simply shortcuts for +lazy developers -- they're not the only way a form object can be displayed. +To display the HTML for a single field in your form, use dictionary lookup +syntax using the field's name as the key, and print the resulting object:: + + >>> f = ContactForm() + >>> print f['subject'] + + >>> print f['message'] + + >>> print f['sender'] + + >>> print f['cc_myself'] + + +Call ``str()`` or ``unicode()`` on the field to get its rendered HTML as a +string or Unicode object, respectively:: + + >>> str(f['subject']) + '' + >>> unicode(f['subject']) + u'' + +The field-specific output honors the form object's ``auto_id`` setting:: + + >>> f = ContactForm(auto_id=False) + >>> print f['message'] + + >>> f = ContactForm(auto_id='id_%s') + >>> print f['message'] + More coming soon ================ @@ -297,6 +330,9 @@ http://code.djangoproject.com/browser/django/trunk/tests/regressiontests/forms/t -- the unit tests for ``django.newforms``. This can give you a good idea of what's possible. +If you're really itching to learn and use this library, please be patient. +We're working hard on finishing both the code and documentation. + Using forms with templates ========================== diff --git a/docs/sessions.txt b/docs/sessions.txt index dd4a581d91..20c2b3fbcf 100644 --- a/docs/sessions.txt +++ b/docs/sessions.txt @@ -217,6 +217,23 @@ browser-length cookies -- cookies that expire as soon as the user closes his or her browser. Use this if you want people to have to log in every time they open a browser. +Clearing the session table +========================== + +Note that session data can accumulate in the ``django_session`` database table +and Django does *not* provide automatic purging. Therefore, it's your job to +purge expired sessions on a regular basis. + +To understand this problem, consider what happens when a user uses a session. +When a user logs in, Django adds a row to the ``django_session`` database +table. Django updates this row each time the session data changes. If the user +logs out manually, Django deletes the row. But if the user does *not* log out, +the row never gets deleted. + +Django provides a sample clean-up script in ``django/bin/daily_cleanup.py``. +That script deletes any session in the session table whose ``expire_date`` is +in the past -- but your application may have different requirements. + Settings ======== diff --git a/docs/templates.txt b/docs/templates.txt index b4cc47b9f3..d0cd69f1d2 100644 --- a/docs/templates.txt +++ b/docs/templates.txt @@ -924,13 +924,31 @@ Replaces ampersands with ``&`` entities. floatformat ~~~~~~~~~~~ -Rounds a floating-point number to one decimal place -- but only if there's a -decimal part to be displayed. For example: +When used without an argument, rounds a floating-point number to one decimal +place -- but only if there's a decimal part to be displayed. For example: * ``36.123`` gets converted to ``36.1`` * ``36.15`` gets converted to ``36.2`` * ``36`` gets converted to ``36`` +**New in Django development version** + +If used with a numeric integer argument, ``floatformat`` rounds a number to that +many decimal places. For example: + + * ``36.1234`` with floatformat:3 gets converted to ``36.123`` + * ``36`` with floatformat:4 gets converted to ``36.0000`` + +If the argument passed to ``floatformat`` is negative, it will round a number to +that many decimal places -- but only if there's a decimal part to be displayed. +For example: + + * ``36.1234`` with floatformat:-3 gets converted to ``36.123`` + * ``36`` with floatformat:-4 gets converted to ``36`` + +Using ``floatformat`` with no argument is equivalent to using ``floatformat`` with +an argument of ``-1``. + get_digit ~~~~~~~~~ diff --git a/docs/tutorial03.txt b/docs/tutorial03.txt index c4c1b4c546..41d11d9e6e 100644 --- a/docs/tutorial03.txt +++ b/docs/tutorial03.txt @@ -300,7 +300,7 @@ rewritten:: The ``get_object_or_404()`` function takes a Django model module as its first argument and an arbitrary number of keyword arguments, which it passes to the -module's ``get_object()`` function. It raises ``Http404`` if the object doesn't +module's ``get()`` function. It raises ``Http404`` if the object doesn't exist. .. admonition:: Philosophy diff --git a/setup.py b/setup.py index f403029efe..13ad065681 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ from distutils.core import setup from distutils.command.install import INSTALL_SCHEMES import os +import sys # Tell distutils to put the data_files in platform-specific installation # locations. See here for an explanation: @@ -23,7 +24,13 @@ for dirpath, dirnames, filenames in os.walk(django_dir): package = dirpath[len_root_dir:].lstrip('/').replace('/', '.') packages.append(package) else: - data_files.append((dirpath, [os.path.join(dirpath, f) for f in filenames])) + data_files.append([dirpath, [os.path.join(dirpath, f) for f in filenames]]) + +# Small hack for working with bdist_wininst. +# See http://mail.python.org/pipermail/distutils-sig/2004-August/004134.html +if sys.argv[1] == 'bdist_wininst': + for file_info in data_files: + file_info[0] = '/PURELIB/%s' % file_info[0] # Dynamically calculate the version based on django.VERSION. version = "%d.%d-%s" % (__import__('django').VERSION) diff --git a/tests/modeltests/get_object_or_404/__init__.py b/tests/modeltests/get_object_or_404/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/modeltests/get_object_or_404/models.py b/tests/modeltests/get_object_or_404/models.py new file mode 100644 index 0000000000..79bcc6f52c --- /dev/null +++ b/tests/modeltests/get_object_or_404/models.py @@ -0,0 +1,86 @@ +""" +34. DB-API Shortcuts + +get_object_or_404 is a shortcut function to be used in view functions for +performing a get() lookup and raising a Http404 exception if a DoesNotExist +exception was rasied during the get() call. + +get_list_or_404 is a shortcut function to be used in view functions for +performing a filter() lookup and raising a Http404 exception if a DoesNotExist +exception was rasied during the filter() call. +""" + +from django.db import models +from django.http import Http404 +from django.shortcuts import get_object_or_404, get_list_or_404 + +class Author(models.Model): + name = models.CharField(maxlength=50) + + def __str__(self): + return self.name + +class ArticleManager(models.Manager): + def get_query_set(self): + return super(ArticleManager, self).get_query_set().filter(authors__name__icontains='sir') + +class Article(models.Model): + authors = models.ManyToManyField(Author) + title = models.CharField(maxlength=50) + objects = models.Manager() + by_a_sir = ArticleManager() + + def __str__(self): + return self.title + +__test__ = {'API_TESTS':""" +# Create some Authors. +>>> a = Author.objects.create(name="Brave Sir Robin") +>>> a.save() +>>> a2 = Author.objects.create(name="Patsy") +>>> a2.save() + +# No Articles yet, so we should get a Http404 error. +>>> get_object_or_404(Article, title="Foo") +Traceback (most recent call last): +... +Http404 + +# Create an Article. +>>> article = Article.objects.create(title="Run away!") +>>> article.authors = [a, a2] +>>> article.save() + +# get_object_or_404 can be passed a Model to query. +>>> get_object_or_404(Article, title__contains="Run") + + +# We can also use the the Article manager through an Author object. +>>> get_object_or_404(a.article_set, title__contains="Run") + + +# No articles containing "Camelot". This should raise a Http404 error. +>>> get_object_or_404(a.article_set, title__contains="Camelot") +Traceback (most recent call last): +... +Http404 + +# Custom managers can be used too. +>>> get_object_or_404(Article.by_a_sir, title="Run away!") + + +# get_list_or_404 can be used to get lists of objects +>>> get_list_or_404(a.article_set, title__icontains='Run') +[] + +# Http404 is returned if the list is empty +>>> get_list_or_404(a.article_set, title__icontains='Shrubbery') +Traceback (most recent call last): +... +Http404 + +# Custom managers can be used too. +>>> get_list_or_404(Article.by_a_sir, title__icontains="Run") +[] + +"""} diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 60a3defde5..7ce89c61a6 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -1,7 +1,8 @@ """ 34. Generating HTML forms from models -Django provides shortcuts for creating Form objects from a model class. +Django provides shortcuts for creating Form objects from a model class and a +model instance. The function django.newforms.form_for_model() takes a model class and returns a Form that is tied to the model. This Form works just like any other Form, @@ -9,6 +10,13 @@ with one additional method: create(). The create() method creates an instance of the model and returns that newly created instance. It saves the instance to the database if create(save=True), which is default. If you pass create(save=False), then you'll get the object without saving it. + +The function django.newforms.form_for_instance() takes a model instance and +returns a Form that is tied to the instance. This form works just like any +other Form, with one additional method: apply_changes(). The apply_changes() +method updates the model instance. It saves the changes to the database if +apply_changes(save=True), which is default. If you pass save=False, then you'll +get the object without saving it. """ from django.db import models @@ -28,7 +36,7 @@ class Writer(models.Model): class Article(models.Model): headline = models.CharField(maxlength=50) - pub_date = models.DateTimeField() + pub_date = models.DateField() writer = models.ForeignKey(Writer) categories = models.ManyToManyField(Category, blank=True) @@ -80,6 +88,8 @@ __test__ = {'API_TESTS': """ >>> Category.objects.all() [, ] +If you call create() with save=False, then it will return an object that hasn't +yet been saved. In this case, it's up to you to save it. >>> f = CategoryForm({'name': 'Third test', 'url': 'third'}) >>> f.errors {} @@ -94,6 +104,7 @@ __test__ = {'API_TESTS': """ >>> Category.objects.all() [, , ] +If you call create() with invalid data, you'll get a ValueError. >>> f = CategoryForm({'name': '', 'url': 'foo'}) >>> f.errors {'name': [u'This field is required.']} @@ -102,7 +113,6 @@ __test__ = {'API_TESTS': """ Traceback (most recent call last): ... ValueError: The Category could not be created because the data didn't validate. - >>> f = CategoryForm({'name': '', 'url': 'foo'}) >>> f.create() Traceback (most recent call last): @@ -181,4 +191,27 @@ True >>> new_art = Article.objects.get(id=1) >>> new_art.headline 'New headline' + +Add some categories and test the many-to-many form output. +>>> new_art.categories.all() +[] +>>> new_art.categories.add(Category.objects.get(name='Entertainment')) +>>> new_art.categories.all() +[] +>>> TestArticleForm = form_for_instance(new_art) +>>> f = TestArticleForm(auto_id=False) +>>> print f.as_ul() +
  • Headline:
  • +
  • Pub date:
  • +
  • Writer:
  • +
  • Categories:
  • + """} diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py index 32d6ef5202..439a40c31b 100644 --- a/tests/regressiontests/defaultfilters/tests.py +++ b/tests/regressiontests/defaultfilters/tests.py @@ -11,6 +11,26 @@ r""" '0.0' >>> floatformat(0.0) '0' +>>> floatformat(7.7,3) +'7.700' +>>> floatformat(6.000000,3) +'6.000' +>>> floatformat(13.1031,-3) +'13.103' +>>> floatformat(11.1197, -2) +'11.12' +>>> floatformat(11.0000, -2) +'11' +>>> floatformat(11.000001, -2) +'11.00' +>>> floatformat(8.2798, 3) +'8.280' +>>> floatformat('foo') +'' +>>> floatformat(13.1031, 'bar') +'13.1031' +>>> floatformat('foo', 'bar') +'' >>> addslashes('"double quotes" and \'single quotes\'') '\\"double quotes\\" and \\\'single quotes\\\'' diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index 79a320c131..0852fbcf0e 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -2482,7 +2482,7 @@ demonstrate some of the library's abilities. # SelectDateWidget ############################################################ >>> from django.newforms.extras import SelectDateWidget ->>> w = SelectDateWidget() +>>> w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016')) >>> print w.render('mydate', '') >>> w.render('mydate', None) == w.render('mydate', '') True @@ -2594,7 +2594,6 @@ True """ diff --git a/tests/regressiontests/invalid_admin_options/__init__.py b/tests/regressiontests/invalid_admin_options/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/invalid_admin_options/models.py b/tests/regressiontests/invalid_admin_options/models.py new file mode 100644 index 0000000000..43bcc533ba --- /dev/null +++ b/tests/regressiontests/invalid_admin_options/models.py @@ -0,0 +1,337 @@ +""" +Admin options + +Test invalid and valid admin options to make sure that +model validation is working properly. +""" + +from django.db import models +model_errors = "" + +# TODO: Invalid admin options should not cause a metaclass error +##This should fail gracefully but is causing a metaclass error +#class BadAdminOption(models.Model): +# "Test nonexistent admin option" +# name = models.CharField(maxlength=30) +# +# class Admin: +# nonexistent = 'option' +# +#model_errors += """invalid_admin_options.badadminoption: "admin" attribute, if given, must be set to a models.AdminOptions() instance. +#""" + +class ListDisplayBadOne(models.Model): + "Test list_display, list_display must be a list or tuple" + first_name = models.CharField(maxlength=30) + + class Admin: + list_display = 'first_name' + +model_errors += """invalid_admin_options.listdisplaybadone: "admin.list_display", if given, must be set to a list or tuple. +""" + +class ListDisplayBadTwo(models.Model): + "Test list_display, list_display items must be attributes, methods or properties." + first_name = models.CharField(maxlength=30) + + class Admin: + list_display = ['first_name','nonexistent'] + +model_errors += """invalid_admin_options.listdisplaybadtwo: "admin.list_display" refers to 'nonexistent', which isn't an attribute, method or property. +""" +class ListDisplayBadThree(models.Model): + "Test list_display, list_display items can not be a ManyToManyField." + first_name = models.CharField(maxlength=30) + nick_names = models.ManyToManyField('ListDisplayGood') + + class Admin: + list_display = ['first_name','nick_names'] + +model_errors += """invalid_admin_options.listdisplaybadthree: "admin.list_display" doesn't support ManyToManyFields ('nick_names'). +""" + +class ListDisplayGood(models.Model): + "Test list_display, Admin list_display can be a attribute, method or property." + first_name = models.CharField(maxlength=30) + + def _last_name(self): + return self.first_name + last_name = property(_last_name) + + def full_name(self): + return "%s %s" % (self.first_name, self.last_name) + + class Admin: + list_display = ['first_name','last_name','full_name'] + +class ListDisplayLinksBadOne(models.Model): + "Test list_display_links, item must be included in list_display." + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + + class Admin: + list_display = ['last_name'] + list_display_links = ['first_name'] + +model_errors += """invalid_admin_options.listdisplaylinksbadone: "admin.list_display_links" refers to 'first_name', which is not defined in "admin.list_display". +""" + +class ListDisplayLinksBadTwo(models.Model): + "Test list_display_links, must be a list or tuple." + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + + class Admin: + list_display = ['first_name','last_name'] + list_display_links = 'last_name' + +model_errors += """invalid_admin_options.listdisplaylinksbadtwo: "admin.list_display_links", if given, must be set to a list or tuple. +""" + +# TODO: Fix list_display_links validation or remove the check for list_display +## This is failing but the validation which should fail is not. +#class ListDisplayLinksBadThree(models.Model): +# "Test list_display_links, must define list_display to use list_display_links." +# first_name = models.CharField(maxlength=30) +# last_name = models.CharField(maxlength=30) +# +# class Admin: +# list_display_links = ('first_name',) +# +#model_errors += """invalid_admin_options.listdisplaylinksbadthree: "admin.list_display" must be defined for "admin.list_display_links" to be used. +#""" + +class ListDisplayLinksGood(models.Model): + "Test list_display_links, Admin list_display_list can be a attribute, method or property." + first_name = models.CharField(maxlength=30) + + def _last_name(self): + return self.first_name + last_name = property(_last_name) + + def full_name(self): + return "%s %s" % (self.first_name, self.last_name) + + class Admin: + list_display = ['first_name','last_name','full_name'] + list_display_links = ['first_name','last_name','full_name'] + +class ListFilterBadOne(models.Model): + "Test list_filter, must be a list or tuple." + first_name = models.CharField(maxlength=30) + + class Admin: + list_filter = 'first_name' + +model_errors += """invalid_admin_options.listfilterbadone: "admin.list_filter", if given, must be set to a list or tuple. +""" + +class ListFilterBadTwo(models.Model): + "Test list_filter, must be a field not a property or method." + first_name = models.CharField(maxlength=30) + + def _last_name(self): + return self.first_name + last_name = property(_last_name) + + def full_name(self): + return "%s %s" % (self.first_name, self.last_name) + + class Admin: + list_filter = ['first_name','last_name','full_name'] + +model_errors += """invalid_admin_options.listfilterbadtwo: "admin.list_filter" refers to 'last_name', which isn't a field. +invalid_admin_options.listfilterbadtwo: "admin.list_filter" refers to 'full_name', which isn't a field. +""" + +class DateHierarchyBadOne(models.Model): + "Test date_hierarchy, must be a date or datetime field." + first_name = models.CharField(maxlength=30) + birth_day = models.DateField() + + class Admin: + date_hierarchy = 'first_name' + +# TODO: Date Hierarchy needs to check if field is a date/datetime field. +#model_errors += """invalid_admin_options.datehierarchybadone: "admin.date_hierarchy" refers to 'first_name', which isn't a date field or datetime field. +#""" + +class DateHierarchyBadTwo(models.Model): + "Test date_hieracrhy, must be a field." + first_name = models.CharField(maxlength=30) + birth_day = models.DateField() + + class Admin: + date_hierarchy = 'nonexistent' + +model_errors += """invalid_admin_options.datehierarchybadtwo: "admin.date_hierarchy" refers to 'nonexistent', which isn't a field. +""" + +class DateHierarchyGood(models.Model): + "Test date_hieracrhy, must be a field." + first_name = models.CharField(maxlength=30) + birth_day = models.DateField() + + class Admin: + date_hierarchy = 'birth_day' + +class SearchFieldsBadOne(models.Model): + "Test search_fields, must be a list or tuple." + first_name = models.CharField(maxlength=30) + + class Admin: + search_fields = ('nonexistent') + +# TODO: Add search_fields validation +#model_errors += """invalid_admin_options.seacrhfieldsbadone: "admin.search_fields", if given, must be set to a list or tuple. +#""" + +class SearchFieldsBadTwo(models.Model): + "Test search_fields, must be a field." + first_name = models.CharField(maxlength=30) + + def _last_name(self): + return self.first_name + last_name = property(_last_name) + + class Admin: + search_fields = ['first_name','last_name'] + +# TODO: Add search_fields validation +#model_errors += """invalid_admin_options.seacrhfieldsbadone: "admin.search_fields" refers to 'last_name', which isn't a field. +#""" + +class SearchFieldsGood(models.Model): + "Test search_fields, must be a list or tuple." + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + + class Admin: + search_fields = ['first_name','last_name'] + + +class JsBadOne(models.Model): + "Test js, must be a list or tuple" + name = models.CharField(maxlength=30) + + class Admin: + js = 'test.js' + +# TODO: Add a js validator +#model_errors += """invalid_admin_options.jsbadone: "admin.js", if given, must be set to a list or tuple. +#""" + +class SaveAsBad(models.Model): + "Test save_as, should be True or False" + name = models.CharField(maxlength=30) + + class Admin: + save_as = 'not True or False' + +# TODO: Add a save_as validator. +#model_errors += """invalid_admin_options.saveasbad: "admin.save_as", if given, must be set to True or False. +#""" + +class SaveOnTopBad(models.Model): + "Test save_on_top, should be True or False" + name = models.CharField(maxlength=30) + + class Admin: + save_on_top = 'not True or False' + +# TODO: Add a save_on_top validator. +#model_errors += """invalid_admin_options.saveontopbad: "admin.save_on_top", if given, must be set to True or False. +#""" + +class ListSelectRelatedBad(models.Model): + "Test list_select_related, should be True or False" + name = models.CharField(maxlength=30) + + class Admin: + list_select_related = 'not True or False' + +# TODO: Add a list_select_related validator. +#model_errors += """invalid_admin_options.listselectrelatebad: "admin.list_select_related", if given, must be set to True or False. +#""" + +class ListPerPageBad(models.Model): + "Test list_per_page, should be a positive integer value." + name = models.CharField(maxlength=30) + + class Admin: + list_per_page = 89.3 + +# TODO: Add a list_per_page validator. +#model_errors += """invalid_admin_options.listperpagebad: "admin.list_per_page", if given, must be a positive integer. +#""" + +class FieldsBadOne(models.Model): + "Test fields, should be a tuple" + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + + class Admin: + fields = 'not a tuple' + +# TODO: Add a fields validator. +#model_errors += """invalid_admin_options.fieldsbadone: "admin.fields", if given, must be a tuple. +#""" + +class FieldsBadTwo(models.Model): + """Test fields, 'fields' dict option is required.""" + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + + class Admin: + fields = ('Name', {'description': 'this fieldset needs fields'}) + +# TODO: Add a fields validator. +#model_errors += """invalid_admin_options.fieldsbadtwo: "admin.fields" each fieldset must include a 'fields' dict. +#""" + +class FieldsBadThree(models.Model): + """Test fields, 'classes' and 'description' are the only allowable extra dict options.""" + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + + class Admin: + fields = ('Name', {'fields': ('first_name','last_name'),'badoption': 'verybadoption'}) + +# TODO: Add a fields validator. +#model_errors += """invalid_admin_options.fieldsbadthree: "admin.fields" fieldset options must be either 'classes' or 'description'. +#""" + +class FieldsGood(models.Model): + "Test fields, working example" + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + birth_day = models.DateField() + + class Admin: + fields = ( + ('Name', {'fields': ('first_name','last_name'),'classes': 'collapse'}), + (None, {'fields': ('birth_day',),'description': 'enter your b-day'}) + ) + +class OrderingBad(models.Model): + "Test ordering, must be a field." + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + + class Admin: + ordering = 'nonexistent' + +# TODO: Add a ordering validator. +#model_errors += """invalid_admin_options.orderingbad: "admin.ordering" refers to 'nonexistent', which isn't a field. +#""" + +## TODO: Add a manager validator, this should fail gracefully. +#class ManagerBad(models.Model): +# "Test manager, must be a manager object." +# first_name = models.CharField(maxlength=30) +# +# class Admin: +# manager = 'nonexistent' +# +#model_errors += """invalid_admin_options.managerbad: "admin.manager" refers to 'nonexistent', which isn't a Manager(). +#""" \ No newline at end of file