From fe361e678a46dc4c717c79c2f12b3ba32293b81a Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Wed, 31 Jan 2007 23:43:09 +0000 Subject: [PATCH] Merged revisions 4186 to 4454 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/sqlalchemy@4455 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 6 + django/bin/daily_cleanup.py | 2 + django/conf/global_settings.py | 2 +- django/conf/project_template/settings.py | 2 +- .../admin/auth/user/change_password.html | 52 + .../contrib/admin/templates/admin/base.html | 5 +- .../admin/templates/admin/change_form.html | 2 + .../admin/templates/admin/change_list.html | 2 + .../contrib/admin/templatetags/admin_list.py | 14 +- django/contrib/admin/urls.py | 2 + django/contrib/admin/views/auth.py | 41 +- django/contrib/admin/views/main.py | 10 +- django/contrib/admin/views/template.py | 10 +- django/contrib/auth/forms.py | 43 +- django/contrib/auth/models.py | 2 +- django/contrib/auth/views.py | 8 +- django/contrib/comments/views/comments.py | 32 +- django/contrib/contenttypes/models.py | 14 +- django/contrib/csrf/middleware.py | 2 +- django/contrib/formtools/preview.py | 13 +- django/contrib/sessions/middleware.py | 3 +- django/core/cache/backends/dummy.py | 6 +- django/core/handlers/base.py | 5 +- django/core/management.py | 4 +- django/core/serializers/python.py | 2 +- django/db/backends/ado_mssql/creation.py | 1 - django/db/backends/mysql/base.py | 8 +- django/db/backends/mysql/creation.py | 1 - django/db/backends/oracle/creation.py | 1 - django/db/backends/postgresql/base.py | 35 +- django/db/backends/postgresql/creation.py | 1 - django/db/backends/sqlite3/creation.py | 1 - django/db/models/fields/__init__.py | 128 +- django/db/models/fields/generic.py | 4 +- django/db/models/fields/related.py | 132 +- django/db/models/manager.py | 8 +- django/db/models/manipulators.py | 8 +- django/db/models/query.py | 102 +- django/forms/__init__.py | 1009 +----------- django/newforms/__init__.py | 2 +- django/newforms/extras/__init__.py | 1 + django/newforms/extras/widgets.py | 59 + django/newforms/fields.py | 275 +++- django/newforms/forms.py | 120 +- django/newforms/models.py | 99 +- django/newforms/util.py | 5 + django/newforms/widgets.py | 157 +- django/oldforms/__init__.py | 1010 ++++++++++++ django/shortcuts/__init__.py | 15 +- django/template/defaultfilters.py | 30 +- django/utils/datastructures.py | 6 +- django/utils/dateformat.py | 4 +- django/utils/simplejson/LICENSE.txt | 2 +- django/utils/simplejson/__init__.py | 47 +- django/utils/simplejson/decoder.py | 6 +- django/utils/simplejson/encoder.py | 98 +- django/utils/simplejson/jsonfilter.py | 40 + django/utils/simplejson/scanner.py | 3 +- django/utils/text.py | 31 +- django/views/generic/create_update.py | 6 +- docs/add_ons.txt | 16 +- docs/apache_auth.txt | 4 +- docs/api_stability.txt | 46 +- docs/authentication.txt | 22 +- docs/cache.txt | 2 +- docs/contributing.txt | 168 +- docs/csrf.txt | 37 +- docs/db-api.txt | 61 +- docs/design_philosophies.txt | 2 +- docs/django-admin.txt | 10 +- docs/email.txt | 8 +- docs/faq.txt | 24 +- docs/fastcgi.txt | 2 +- docs/flatpages.txt | 14 +- docs/forms.txt | 46 +- docs/generic_views.txt | 58 +- docs/i18n.txt | 20 +- docs/install.txt | 6 +- docs/legacy_databases.txt | 20 +- docs/middleware.txt | 15 +- docs/model-api.txt | 29 +- docs/modpython.txt | 2 +- docs/newforms.txt | 784 ++++++++- docs/outputting_csv.txt | 6 +- docs/outputting_pdf.txt | 2 +- docs/overview.txt | 2 +- docs/redirects.txt | 16 +- docs/request_response.txt | 4 +- docs/sessions.txt | 21 +- docs/settings.txt | 43 +- docs/sitemaps.txt | 20 +- docs/sites.txt | 12 +- docs/static_files.txt | 6 +- docs/syndication_feeds.txt | 14 +- docs/templates.txt | 26 +- docs/templates_python.txt | 24 +- docs/testing.txt | 12 +- docs/tutorial01.txt | 10 +- docs/tutorial02.txt | 10 +- docs/tutorial03.txt | 15 +- docs/tutorial04.txt | 6 +- docs/url_dispatch.txt | 14 +- setup.py | 14 +- tests/modeltests/basic/models.py | 15 +- tests/modeltests/custom_columns/models.py | 94 +- tests/modeltests/generic_relations/models.py | 34 +- .../modeltests/get_object_or_404/__init__.py | 0 tests/modeltests/get_object_or_404/models.py | 86 + tests/modeltests/lookup/models.py | 15 + tests/modeltests/many_to_many/models.py | 26 +- tests/modeltests/model_forms/__init__.py | 0 tests/modeltests/model_forms/models.py | 284 ++++ tests/modeltests/or_lookups/models.py | 15 + tests/modeltests/serializers/models.py | 21 + tests/modeltests/test_client/views.py | 2 +- tests/regressiontests/defaultfilters/tests.py | 20 + tests/regressiontests/forms/tests.py | 1399 +++++++++++++++-- .../invalid_admin_options/__init__.py | 0 .../invalid_admin_options/models.py | 337 ++++ tests/runtests.py | 39 +- 120 files changed, 5900 insertions(+), 1899 deletions(-) create mode 100644 django/contrib/admin/templates/admin/auth/user/change_password.html create mode 100644 django/newforms/extras/__init__.py create mode 100644 django/newforms/extras/widgets.py create mode 100644 django/oldforms/__init__.py create mode 100644 django/utils/simplejson/jsonfilter.py create mode 100644 tests/modeltests/get_object_or_404/__init__.py create mode 100644 tests/modeltests/get_object_or_404/models.py create mode 100644 tests/modeltests/model_forms/__init__.py create mode 100644 tests/modeltests/model_forms/models.py create mode 100644 tests/regressiontests/invalid_admin_options/__init__.py create mode 100644 tests/regressiontests/invalid_admin_options/models.py diff --git a/AUTHORS b/AUTHORS index dbbf6e7bad..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 @@ -118,13 +119,16 @@ answer newbie questions, and generally made Django that much better: Manuzhai Petar Marić mark@junklight.com + Yasushi Masuda mattycakes@gmail.com Jason McBrayer mccutchen@gmail.com michael.mcewan@gmail.com + mitakummaa@gmail.com mmarshall Eric Moritz Robin Munn + Robert Myers Nebojša Dorđević Fraser Nevett Sam Newman @@ -149,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 @@ -160,6 +165,7 @@ answer newbie questions, and generally made Django that much better: Tom Insam Joe Topjian Karen Tracey + Makoto Tsuyuki Amit Upadhyay Geert Vanderkelen Milton Waddams 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/conf/global_settings.py b/django/conf/global_settings.py index 36fee9ec6d..245096590d 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -25,7 +25,7 @@ ADMINS = () INTERNAL_IPS = () # Local time zone for this installation. All choices can be found here: -# http://www.postgresql.org/docs/current/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE +# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE TIME_ZONE = 'America/Chicago' # Language code for this installation. All choices can be found here: diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py index d6f34a28db..a44bc172f0 100644 --- a/django/conf/project_template/settings.py +++ b/django/conf/project_template/settings.py @@ -17,7 +17,7 @@ DATABASE_HOST = '' # Set to empty string for localhost. Not used wit DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. # Local time zone for this installation. All choices can be found here: -# http://www.postgresql.org/docs/current/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE +# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE TIME_ZONE = 'America/Chicago' # Language code for this installation. All choices can be found here: 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..3d359ecf8f --- /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 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/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html index b63604b268..d3e8c96b91 100644 --- a/django/contrib/admin/templates/admin/base.html +++ b/django/contrib/admin/templates/admin/base.html @@ -38,7 +38,10 @@
{% block pretitle %}{% endblock %} {% block content_title %}{% if title %}

{{ title|escape }}

{% endif %}{% endblock %} - {% block content %}{{ content }}{% endblock %} + {% block content %} + {% block object-tools %}{% endblock %} + {{ content }} + {% endblock %} {% block sidebar %}{% endblock %}
diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index b1fdc5ebdb..7e7b639139 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -16,11 +16,13 @@ {% endif %}{% endblock %} {% block content %}
+{% block object-tools %} {% if change %}{% if not is_popup %} {% endif %}{% endif %} +{% endblock %}
{% block form_top %}{% endblock %}
{% if is_popup %}{% endif %} diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index bd2304bd52..f50a73c934 100644 --- a/django/contrib/admin/templates/admin/change_list.html +++ b/django/contrib/admin/templates/admin/change_list.html @@ -7,9 +7,11 @@ {% block coltype %}flex{% endblock %} {% block content %}
+{% block object-tools %} {% if has_add_permission %} {% endif %} +{% endblock %}
{% block search %}{% search_form cl %}{% endblock %} {% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %} diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 832b3562cd..3c0c6f0ac2 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -101,6 +101,10 @@ def result_headers(cl): "url": cl.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}), "class_attrib": (th_classes and ' class="%s"' % ' '.join(th_classes) or '')} +def _boolean_icon(field_val): + BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'} + return '%s' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val) + def items_for_result(cl, result): first = True pk = cl.lookup_opts.pk.attname @@ -114,9 +118,14 @@ def items_for_result(cl, result): try: attr = getattr(result, field_name) allow_tags = getattr(attr, 'allow_tags', False) + boolean = getattr(attr, 'boolean', False) if callable(attr): attr = attr() - result_repr = str(attr) + if boolean: + allow_tags = True + result_repr = _boolean_icon(attr) + else: + result_repr = str(attr) except (AttributeError, ObjectDoesNotExist): result_repr = EMPTY_CHANGELIST_VALUE else: @@ -147,8 +156,7 @@ def items_for_result(cl, result): row_class = ' class="nowrap"' # Booleans are special: We use images. elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField): - BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'} - result_repr = '%s' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val) + result_repr = _boolean_icon(field_val) # FloatFields are special: Zero-pad the decimals. elif isinstance(f, models.FloatField): if field_val is not None: 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 03876bb4ac..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 forms, template -from django.shortcuts import render_to_response +from django import oldforms, template +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'): @@ -24,7 +25,7 @@ def user_add_stage(request): return HttpResponseRedirect('../%s/' % new_user.id) else: errors = new_data = {} - form = forms.FormWrapper(manipulator, new_data, errors) + form = oldforms.FormWrapper(manipulator, new_data, errors) return render_to_response('admin/auth/user/add_form.html', { 'title': _('Add user'), 'form': form, @@ -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 c9cff0e374..282038e205 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -1,4 +1,4 @@ -from django import forms, template +from django import oldforms, template from django.conf import settings from django.contrib.admin.filterspecs import FilterSpec from django.contrib.admin.views.decorators import staff_member_required @@ -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 @@ -283,7 +283,7 @@ def add_stage(request, app_label, model_name, show_delete=False, form_url='', po errors = {} # Populate the FormWrapper. - form = forms.FormWrapper(manipulator, new_data, errors) + form = oldforms.FormWrapper(manipulator, new_data, errors) c = template.RequestContext(request, { 'title': _('Add %s') % opts.verbose_name, @@ -374,7 +374,7 @@ def change_stage(request, app_label, model_name, object_id): errors = {} # Populate the FormWrapper. - form = forms.FormWrapper(manipulator, new_data, errors) + form = oldforms.FormWrapper(manipulator, new_data, errors) form.original = manipulator.original_object form.order_objects = [] diff --git a/django/contrib/admin/views/template.py b/django/contrib/admin/views/template.py index 93d110b045..a3b4538b10 100644 --- a/django/contrib/admin/views/template.py +++ b/django/contrib/admin/views/template.py @@ -1,6 +1,6 @@ from django.contrib.admin.views.decorators import staff_member_required from django.core import validators -from django import template, forms +from django import template, oldforms from django.template import loader from django.shortcuts import render_to_response from django.contrib.sites.models import Site @@ -25,17 +25,17 @@ def template_validator(request): request.user.message_set.create(message='The template is valid.') return render_to_response('admin/template_validator.html', { 'title': 'Template validator', - 'form': forms.FormWrapper(manipulator, new_data, errors), + 'form': oldforms.FormWrapper(manipulator, new_data, errors), }, context_instance=template.RequestContext(request)) template_validator = staff_member_required(template_validator) -class TemplateValidator(forms.Manipulator): +class TemplateValidator(oldforms.Manipulator): def __init__(self, settings_modules): self.settings_modules = settings_modules site_list = Site.objects.in_bulk(settings_modules.keys()).values() self.fields = ( - forms.SelectField('site', is_required=True, choices=[(s.id, s.name) for s in site_list]), - forms.LargeTextField('template', is_required=True, rows=25, validator_list=[self.isValidTemplate]), + oldforms.SelectField('site', is_required=True, choices=[(s.id, s.name) for s in site_list]), + oldforms.LargeTextField('template', is_required=True, rows=25, validator_list=[self.isValidTemplate]), ) def isValidTemplate(self, field_data, all_data): diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 24c69cb73e..7700ec7d7a 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -3,16 +3,16 @@ from django.contrib.auth import authenticate from django.contrib.sites.models import Site from django.template import Context, loader from django.core import validators -from django import forms +from django import oldforms -class UserCreationForm(forms.Manipulator): +class UserCreationForm(oldforms.Manipulator): "A form that creates a user, with no privileges, from the given username and password." def __init__(self): self.fields = ( - forms.TextField(field_name='username', length=30, maxlength=30, is_required=True, + oldforms.TextField(field_name='username', length=30, maxlength=30, is_required=True, validator_list=[validators.isAlphaNumeric, self.isValidUsername]), - forms.PasswordField(field_name='password1', length=30, maxlength=60, is_required=True), - forms.PasswordField(field_name='password2', length=30, maxlength=60, is_required=True, + 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."))]), ) @@ -27,7 +27,7 @@ class UserCreationForm(forms.Manipulator): "Creates the user." return User.objects.create_user(new_data['username'], '', new_data['password1']) -class AuthenticationForm(forms.Manipulator): +class AuthenticationForm(oldforms.Manipulator): """ Base class for authenticating users. Extend this to get a form that accepts username/password logins. @@ -41,9 +41,9 @@ class AuthenticationForm(forms.Manipulator): """ self.request = request self.fields = [ - forms.TextField(field_name="username", length=15, maxlength=30, is_required=True, + oldforms.TextField(field_name="username", length=15, maxlength=30, is_required=True, validator_list=[self.isValidUser, self.hasCookiesEnabled]), - forms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True), + oldforms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True), ] self.user_cache = None @@ -68,11 +68,11 @@ class AuthenticationForm(forms.Manipulator): def get_user(self): return self.user_cache -class PasswordResetForm(forms.Manipulator): +class PasswordResetForm(oldforms.Manipulator): "A form that lets a user request a password reset" def __init__(self): self.fields = ( - forms.EmailField(field_name="email", length=40, is_required=True, + oldforms.EmailField(field_name="email", length=40, is_required=True, validator_list=[self.isValidUserEmail]), ) @@ -105,16 +105,16 @@ class PasswordResetForm(forms.Manipulator): } send_mail('Password reset on %s' % site_name, t.render(Context(c)), None, [self.user_cache.email]) -class PasswordChangeForm(forms.Manipulator): +class PasswordChangeForm(oldforms.Manipulator): "A form that lets a user change his password." def __init__(self, user): self.user = user self.fields = ( - forms.PasswordField(field_name="old_password", length=30, maxlength=30, is_required=True, + oldforms.PasswordField(field_name="old_password", length=30, maxlength=30, is_required=True, validator_list=[self.isValidOldPassword]), - forms.PasswordField(field_name="new_password1", length=30, maxlength=30, is_required=True, + oldforms.PasswordField(field_name="new_password1", length=30, maxlength=30, is_required=True, validator_list=[validators.AlwaysMatchesOtherField('new_password2', _("The two 'new password' fields didn't match."))]), - forms.PasswordField(field_name="new_password2", length=30, maxlength=30, is_required=True), + oldforms.PasswordField(field_name="new_password2", length=30, maxlength=30, is_required=True), ) def isValidOldPassword(self, new_data, all_data): @@ -126,3 +126,18 @@ class PasswordChangeForm(forms.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/auth/views.py b/django/contrib/auth/views.py index 6882755787..fda17b91fb 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -1,6 +1,6 @@ from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm -from django import forms +from django import oldforms from django.shortcuts import render_to_response from django.template import RequestContext from django.contrib.sites.models import Site @@ -26,7 +26,7 @@ def login(request, template_name='registration/login.html'): errors = {} request.session.set_test_cookie() return render_to_response(template_name, { - 'form': forms.FormWrapper(manipulator, request.POST, errors), + 'form': oldforms.FormWrapper(manipulator, request.POST, errors), REDIRECT_FIELD_NAME: redirect_to, 'site_name': Site.objects.get_current().name, }, context_instance=RequestContext(request)) @@ -62,7 +62,7 @@ def password_reset(request, is_admin_site=False, template_name='registration/pas else: form.save(email_template_name=email_template_name) return HttpResponseRedirect('%sdone/' % request.path) - return render_to_response(template_name, {'form': forms.FormWrapper(form, new_data, errors)}, + return render_to_response(template_name, {'form': oldforms.FormWrapper(form, new_data, errors)}, context_instance=RequestContext(request)) def password_reset_done(request, template_name='registration/password_reset_done.html'): @@ -77,7 +77,7 @@ def password_change(request, template_name='registration/password_change_form.ht if not errors: form.save(new_data) return HttpResponseRedirect('%sdone/' % request.path) - return render_to_response(template_name, {'form': forms.FormWrapper(form, new_data, errors)}, + return render_to_response(template_name, {'form': oldforms.FormWrapper(form, new_data, errors)}, context_instance=RequestContext(request)) password_change = login_required(password_change) diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py index 3640da90fe..12330afe41 100644 --- a/django/contrib/comments/views/comments.py +++ b/django/contrib/comments/views/comments.py @@ -1,5 +1,5 @@ from django.core import validators -from django import forms +from django import oldforms from django.core.mail import mail_admins, mail_managers from django.http import Http404 from django.core.exceptions import ObjectDoesNotExist @@ -28,37 +28,37 @@ class PublicCommentManipulator(AuthenticationForm): else: return [] self.fields.extend([ - forms.LargeTextField(field_name="comment", maxlength=3000, is_required=True, + oldforms.LargeTextField(field_name="comment", maxlength=3000, is_required=True, validator_list=[self.hasNoProfanities]), - forms.RadioSelectField(field_name="rating1", choices=choices, + oldforms.RadioSelectField(field_name="rating1", choices=choices, is_required=ratings_required and num_rating_choices > 0, validator_list=get_validator_list(1), ), - forms.RadioSelectField(field_name="rating2", choices=choices, + oldforms.RadioSelectField(field_name="rating2", choices=choices, is_required=ratings_required and num_rating_choices > 1, validator_list=get_validator_list(2), ), - forms.RadioSelectField(field_name="rating3", choices=choices, + oldforms.RadioSelectField(field_name="rating3", choices=choices, is_required=ratings_required and num_rating_choices > 2, validator_list=get_validator_list(3), ), - forms.RadioSelectField(field_name="rating4", choices=choices, + oldforms.RadioSelectField(field_name="rating4", choices=choices, is_required=ratings_required and num_rating_choices > 3, validator_list=get_validator_list(4), ), - forms.RadioSelectField(field_name="rating5", choices=choices, + oldforms.RadioSelectField(field_name="rating5", choices=choices, is_required=ratings_required and num_rating_choices > 4, validator_list=get_validator_list(5), ), - forms.RadioSelectField(field_name="rating6", choices=choices, + oldforms.RadioSelectField(field_name="rating6", choices=choices, is_required=ratings_required and num_rating_choices > 5, validator_list=get_validator_list(6), ), - forms.RadioSelectField(field_name="rating7", choices=choices, + oldforms.RadioSelectField(field_name="rating7", choices=choices, is_required=ratings_required and num_rating_choices > 6, validator_list=get_validator_list(7), ), - forms.RadioSelectField(field_name="rating8", choices=choices, + oldforms.RadioSelectField(field_name="rating8", choices=choices, is_required=ratings_required and num_rating_choices > 7, validator_list=get_validator_list(8), ), @@ -117,13 +117,13 @@ class PublicCommentManipulator(AuthenticationForm): mail_managers("Comment posted by sketchy user (%s)" % self.user_cache.username, c.get_as_text()) return c -class PublicFreeCommentManipulator(forms.Manipulator): +class PublicFreeCommentManipulator(oldforms.Manipulator): "Manipulator that handles public free (unregistered) comments" def __init__(self): self.fields = ( - forms.TextField(field_name="person_name", maxlength=50, is_required=True, + oldforms.TextField(field_name="person_name", maxlength=50, is_required=True, validator_list=[self.hasNoProfanities]), - forms.LargeTextField(field_name="comment", maxlength=3000, is_required=True, + oldforms.LargeTextField(field_name="comment", maxlength=3000, is_required=True, validator_list=[self.hasNoProfanities]), ) @@ -221,9 +221,9 @@ def post_comment(request): from django.contrib.auth import login login(request, manipulator.get_user()) if errors or request.POST.has_key('preview'): - class CommentFormWrapper(forms.FormWrapper): + class CommentFormWrapper(oldforms.FormWrapper): def __init__(self, manipulator, new_data, errors, rating_choices): - forms.FormWrapper.__init__(self, manipulator, new_data, errors) + oldforms.FormWrapper.__init__(self, manipulator, new_data, errors) self.rating_choices = rating_choices def ratings(self): field_list = [self['rating%d' % (i+1)] for i in range(len(rating_choices))] @@ -302,7 +302,7 @@ def post_free_comment(request): comment = errors and '' or manipulator.get_comment(new_data) return render_to_response('comments/free_preview.html', { 'comment': comment, - 'comment_form': forms.FormWrapper(manipulator, new_data, errors), + 'comment_form': oldforms.FormWrapper(manipulator, new_data, errors), 'options': options, 'target': target, 'hash': security_hash, diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py index a95748a9a1..3384134cb2 100644 --- a/django/contrib/contenttypes/models.py +++ b/django/contrib/contenttypes/models.py @@ -1,6 +1,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +CONTENT_TYPE_CACHE = {} class ContentTypeManager(models.Manager): def get_for_model(self, model): """ @@ -8,10 +9,15 @@ class ContentTypeManager(models.Manager): ContentType if necessary. """ opts = model._meta - # The str() is needed around opts.verbose_name because it's a - # django.utils.functional.__proxy__ object. - ct, created = self.model._default_manager.get_or_create(app_label=opts.app_label, - model=opts.object_name.lower(), defaults={'name': str(opts.verbose_name)}) + key = (opts.app_label, opts.object_name.lower()) + try: + ct = CONTENT_TYPE_CACHE[key] + except KeyError: + # The str() is needed around opts.verbose_name because it's a + # django.utils.functional.__proxy__ object. + ct, created = self.model._default_manager.get_or_create(app_label=key[0], + model=key[1], defaults={'name': str(opts.verbose_name)}) + CONTENT_TYPE_CACHE[key] = ct return ct class ContentType(models.Model): diff --git a/django/contrib/csrf/middleware.py b/django/contrib/csrf/middleware.py index f6f78867dc..93a9484ca6 100644 --- a/django/contrib/csrf/middleware.py +++ b/django/contrib/csrf/middleware.py @@ -11,7 +11,7 @@ import md5 import re import itertools -_ERROR_MSG = "

403 Forbidden

Cross Site Request Forgery detected. Request aborted.

" +_ERROR_MSG = '

403 Forbidden

Cross Site Request Forgery detected. Request aborted.

' _POST_FORM_RE = \ re.compile(r'(]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE) 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/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py index 2337ad8a61..728caa7e19 100644 --- a/django/contrib/sessions/middleware.py +++ b/django/contrib/sessions/middleware.py @@ -1,5 +1,6 @@ from django.conf import settings from django.contrib.sessions.models import Session +from django.core.exceptions import SuspiciousOperation from django.utils.cache import patch_vary_headers import datetime @@ -55,7 +56,7 @@ class SessionWrapper(object): s = Session.objects.get(session_key=self.session_key, expire_date__gt=datetime.datetime.now()) self._session_cache = s.get_decoded() - except Session.DoesNotExist: + except (Session.DoesNotExist, SuspiciousOperation): self._session_cache = {} # Set the session_key to None to force creation of a new # key, for extra security. diff --git a/django/core/cache/backends/dummy.py b/django/core/cache/backends/dummy.py index c68f33616c..4c64161538 100644 --- a/django/core/cache/backends/dummy.py +++ b/django/core/cache/backends/dummy.py @@ -6,8 +6,8 @@ class CacheClass(BaseCache): def __init__(self, *args, **kwargs): pass - def get(self, *args, **kwargs): - pass + def get(self, key, default=None): + return default def set(self, *args, **kwargs): pass @@ -16,7 +16,7 @@ class CacheClass(BaseCache): pass def get_many(self, *args, **kwargs): - pass + return {} def has_key(self, *args, **kwargs): return False diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 85473a6353..ca48b301d4 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -60,7 +60,10 @@ class BaseHandler(object): if response: return response - resolver = urlresolvers.RegexURLResolver(r'^/', settings.ROOT_URLCONF) + # Get urlconf from request object, if available. Otherwise use default. + urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF) + + resolver = urlresolvers.RegexURLResolver(r'^/', urlconf) try: callback, callback_args, callback_kwargs = resolver.resolve(request.path) diff --git a/django/core/management.py b/django/core/management.py index d1a97c4a53..5e7ae0875b 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -25,7 +25,7 @@ APP_ARGS = '[appname ...]' # which has been installed. PROJECT_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf', '%s_template') -INVALID_PROJECT_NAMES = ('django', 'test') +INVALID_PROJECT_NAMES = ('django', 'site', 'test') # Set up the terminal color scheme. class dummy: pass @@ -708,7 +708,7 @@ def startproject(project_name, directory): "Creates a Django project for the given project_name in the given directory." from random import choice if project_name in INVALID_PROJECT_NAMES: - sys.stderr.write(style.ERROR("Error: %r isn't a valid project name. Please try another.\n" % project_name)) + sys.stderr.write(style.ERROR("Error: '%r' conflicts with the name of an existing Python module and cannot be used as a project name. Please try another name.\n" % project_name)) sys.exit(1) _start_helper('project', project_name, directory) # Create a random SECRET_KEY hash, and put it in the main settings. diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 859816c226..1e1e6f4bec 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -57,7 +57,7 @@ def Deserializer(object_list, **options): for d in object_list: # Look up the model and starting build a dict of data for it. Model = _get_model(d["model"]) - data = {Model._meta.pk.name : d["pk"]} + data = {Model._meta.pk.attname : d["pk"]} m2m_data = {} # Handle each field diff --git a/django/db/backends/ado_mssql/creation.py b/django/db/backends/ado_mssql/creation.py index 4d85d27ea5..5158ba02f9 100644 --- a/django/db/backends/ado_mssql/creation.py +++ b/django/db/backends/ado_mssql/creation.py @@ -21,6 +21,5 @@ DATA_TYPES = { 'SmallIntegerField': 'smallint', 'TextField': 'text', 'TimeField': 'time', - 'URLField': 'varchar(200)', 'USStateField': 'varchar(2)', } diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index e7e060e6c2..28c5b1c683 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/mysql/creation.py b/django/db/backends/mysql/creation.py index 116b490124..22ed901653 100644 --- a/django/db/backends/mysql/creation.py +++ b/django/db/backends/mysql/creation.py @@ -25,6 +25,5 @@ DATA_TYPES = { 'SmallIntegerField': 'smallint', 'TextField': 'longtext', 'TimeField': 'time', - 'URLField': 'varchar(200)', 'USStateField': 'varchar(2)', } diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index d45ceb64f5..da65df172e 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -21,6 +21,5 @@ DATA_TYPES = { 'SmallIntegerField': 'smallint', 'TextField': 'long', 'TimeField': 'timestamp', - 'URLField': 'varchar(200)', 'USStateField': 'varchar(2)', } diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index 1e48e9c3fa..054f74a0d3 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -20,6 +20,38 @@ except ImportError: # Import copy of _thread_local.py from Python 2.4 from django.utils._threading_local import local +def smart_basestring(s, charset): + if isinstance(s, unicode): + return s.encode(charset) + return s + +class UnicodeCursorWrapper(object): + """ + A thin wrapper around psycopg cursors that allows them to accept Unicode + strings as params. + + This is necessary because psycopg doesn't apply any DB quoting to + parameters that are Unicode strings. If a param is Unicode, this will + convert it to a bytestring using DEFAULT_CHARSET before passing it to + psycopg. + """ + def __init__(self, cursor, charset): + self.cursor = cursor + self.charset = charset + + def execute(self, sql, params=()): + return self.cursor.execute(sql, [smart_basestring(p, self.charset) for p in params]) + + def executemany(self, sql, 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): + if self.__dict__.has_key(attr): + return self.__dict__[attr] + else: + return getattr(self.cursor, attr) + class DatabaseWrapper(local): def __init__(self, **kwargs): self.connection = None @@ -45,6 +77,7 @@ class DatabaseWrapper(local): self.connection.set_isolation_level(1) # make transactions transparent to all cursors cursor = self.connection.cursor() cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE]) + cursor = UnicodeCursorWrapper(cursor, settings.DEFAULT_CHARSET) if settings.DEBUG: return util.CursorDebugWrapper(cursor, self) return cursor @@ -118,7 +151,7 @@ def get_pk_default_value(): try: Database.register_type(Database.new_type((1082,), "DATE", util.typecast_date)) except AttributeError: - raise Exception, "You appear to be using psycopg version 2, which isn't supported yet, because it's still in beta. Use psycopg version 1 instead: http://initd.org/projects/psycopg1" + raise Exception, "You appear to be using psycopg version 2. Set your DATABASE_ENGINE to 'postgresql_psycopg2' instead of 'postgresql'." Database.register_type(Database.new_type((1083,1266), "TIME", util.typecast_time)) Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp)) Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean)) diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py index 65a804ec40..6c130f368e 100644 --- a/django/db/backends/postgresql/creation.py +++ b/django/db/backends/postgresql/creation.py @@ -25,6 +25,5 @@ DATA_TYPES = { 'SmallIntegerField': 'smallint', 'TextField': 'text', 'TimeField': 'time', - 'URLField': 'varchar(200)', 'USStateField': 'varchar(2)', } diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py index e845179e64..77f570b2e8 100644 --- a/django/db/backends/sqlite3/creation.py +++ b/django/db/backends/sqlite3/creation.py @@ -24,6 +24,5 @@ DATA_TYPES = { 'SmallIntegerField': 'smallint', 'TextField': 'text', 'TimeField': 'time', - 'URLField': 'varchar(200)', 'USStateField': 'varchar(2)', } diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 8e8d68aad5..024fa95b8e 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -2,7 +2,8 @@ from django.db.models import signals from django.dispatch import dispatcher from django.conf import settings from django.core import validators -from django import forms +from django import oldforms +from django import newforms as forms from django.core.exceptions import ObjectDoesNotExist from django.utils.functional import curry from django.utils.itercompat import tee @@ -206,10 +207,10 @@ class Field(object): if self.choices: if self.radio_admin: - field_objs = [forms.RadioSelectField] + field_objs = [oldforms.RadioSelectField] params['ul_class'] = get_ul_class(self.radio_admin) else: - field_objs = [forms.SelectField] + field_objs = [oldforms.SelectField] params['choices'] = self.get_choices_default() else: @@ -218,7 +219,7 @@ class Field(object): def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): """ - Returns a list of forms.FormField instances for this field. It + Returns a list of oldforms.FormField instances for this field. It calculates the choices at runtime, not at compile time. name_prefix is a prefix to prepend to the "field_name" argument. @@ -333,6 +334,16 @@ class Field(object): return self._choices choices = property(_get_choices) + def formfield(self, **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} + defaults.update(kwargs) + return forms.CharField(**defaults) + + def value_from_object(self, obj): + "Returns the value of this field in the given model instance." + return getattr(obj, self.attname) + class AutoField(Field): empty_strings_allowed = False def __init__(self, *args, **kwargs): @@ -354,7 +365,7 @@ class AutoField(Field): return Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow) def get_manipulator_field_objs(self): - return [forms.HiddenField] + return [oldforms.HiddenField] def get_manipulator_new_data(self, new_data, rel=False): # Never going to be called @@ -369,6 +380,9 @@ class AutoField(Field): super(AutoField, self).contribute_to_class(cls, name) cls._meta.has_auto_field = True + def formfield(self, **kwargs): + return None + class BooleanField(Field): def __init__(self, *args, **kwargs): kwargs['blank'] = True @@ -381,11 +395,16 @@ class BooleanField(Field): raise validators.ValidationError, gettext("This value must be either True or False.") def get_manipulator_field_objs(self): - return [forms.CheckboxField] + return [oldforms.CheckboxField] + + def formfield(self, **kwargs): + defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + defaults.update(kwargs) + return forms.BooleanField(**defaults) class CharField(Field): def get_manipulator_field_objs(self): - return [forms.TextField] + return [oldforms.TextField] def to_python(self, value): if isinstance(value, basestring): @@ -397,10 +416,15 @@ class CharField(Field): raise validators.ValidationError, gettext_lazy("This field cannot be null.") return str(value) + def formfield(self, **kwargs): + defaults = {'max_length': self.maxlength, 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + defaults.update(kwargs) + return forms.CharField(**defaults) + # TODO: Maybe move this into contrib, because it's specialized. class CommaSeparatedIntegerField(CharField): def get_manipulator_field_objs(self): - return [forms.CommaSeparatedIntegerField] + return [oldforms.CommaSeparatedIntegerField] class DateField(Field): empty_strings_allowed = False @@ -462,12 +486,17 @@ class DateField(Field): return Field.get_db_prep_save(self, value) def get_manipulator_field_objs(self): - return [forms.DateField] + return [oldforms.DateField] - def flatten_data(self, follow, obj = None): + def flatten_data(self, follow, obj=None): val = self._get_val_from_obj(obj) return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')} + def formfield(self, **kwargs): + defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + defaults.update(kwargs) + return forms.DateField(**defaults) + class DateTimeField(DateField): def to_python(self, value): if isinstance(value, datetime.datetime): @@ -503,7 +532,7 @@ class DateTimeField(DateField): return Field.get_db_prep_lookup(self, lookup_type, value) def get_manipulator_field_objs(self): - return [forms.DateField, forms.TimeField] + return [oldforms.DateField, oldforms.TimeField] def get_manipulator_field_names(self, name_prefix): return [name_prefix + self.name + '_date', name_prefix + self.name + '_time'] @@ -526,6 +555,11 @@ class DateTimeField(DateField): return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''), time_field: (val is not None and val.strftime("%H:%M:%S") or '')} + def formfield(self, **kwargs): + defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + defaults.update(kwargs) + return forms.DateTimeField(**defaults) + class EmailField(CharField): def __init__(self, *args, **kwargs): kwargs['maxlength'] = 75 @@ -535,11 +569,16 @@ class EmailField(CharField): return "CharField" def get_manipulator_field_objs(self): - return [forms.EmailField] + return [oldforms.EmailField] def validate(self, field_data, all_data): validators.isValidEmail(field_data, all_data) + def formfield(self, **kwargs): + defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + defaults.update(kwargs) + return forms.EmailField(**defaults) + class FileField(Field): def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs): self.upload_to = upload_to @@ -599,7 +638,7 @@ class FileField(Field): os.remove(file_name) def get_manipulator_field_objs(self): - return [forms.FileUploadField, forms.HiddenField] + return [oldforms.FileUploadField, oldforms.HiddenField] def get_manipulator_field_names(self, name_prefix): return [name_prefix + self.name + '_file', name_prefix + self.name] @@ -627,7 +666,7 @@ class FilePathField(Field): Field.__init__(self, verbose_name, name, **kwargs) def get_manipulator_field_objs(self): - return [curry(forms.FilePathField, path=self.path, match=self.match, recursive=self.recursive)] + return [curry(oldforms.FilePathField, path=self.path, match=self.match, recursive=self.recursive)] class FloatField(Field): empty_strings_allowed = False @@ -636,7 +675,7 @@ class FloatField(Field): Field.__init__(self, verbose_name, name, **kwargs) def get_manipulator_field_objs(self): - return [curry(forms.FloatField, max_digits=self.max_digits, decimal_places=self.decimal_places)] + return [curry(oldforms.FloatField, max_digits=self.max_digits, decimal_places=self.decimal_places)] class ImageField(FileField): def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs): @@ -644,7 +683,7 @@ class ImageField(FileField): FileField.__init__(self, verbose_name, name, **kwargs) def get_manipulator_field_objs(self): - return [forms.ImageUploadField, forms.HiddenField] + return [oldforms.ImageUploadField, oldforms.HiddenField] def contribute_to_class(self, cls, name): super(ImageField, self).contribute_to_class(cls, name) @@ -670,7 +709,12 @@ class ImageField(FileField): class IntegerField(Field): empty_strings_allowed = False def get_manipulator_field_objs(self): - return [forms.IntegerField] + return [oldforms.IntegerField] + + def formfield(self, **kwargs): + defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + defaults.update(kwargs) + return forms.IntegerField(**defaults) class IPAddressField(Field): def __init__(self, *args, **kwargs): @@ -678,7 +722,7 @@ class IPAddressField(Field): Field.__init__(self, *args, **kwargs) def get_manipulator_field_objs(self): - return [forms.IPAddressField] + return [oldforms.IPAddressField] def validate(self, field_data, all_data): validators.isValidIPAddress4(field_data, None) @@ -689,22 +733,22 @@ class NullBooleanField(Field): Field.__init__(self, *args, **kwargs) def get_manipulator_field_objs(self): - return [forms.NullBooleanField] + return [oldforms.NullBooleanField] class PhoneNumberField(IntegerField): def get_manipulator_field_objs(self): - return [forms.PhoneNumberField] + return [oldforms.PhoneNumberField] def validate(self, field_data, all_data): validators.isValidPhone(field_data, all_data) class PositiveIntegerField(IntegerField): def get_manipulator_field_objs(self): - return [forms.PositiveIntegerField] + return [oldforms.PositiveIntegerField] class PositiveSmallIntegerField(IntegerField): def get_manipulator_field_objs(self): - return [forms.PositiveSmallIntegerField] + return [oldforms.PositiveSmallIntegerField] class SlugField(Field): def __init__(self, *args, **kwargs): @@ -716,15 +760,20 @@ class SlugField(Field): Field.__init__(self, *args, **kwargs) def get_manipulator_field_objs(self): - return [forms.TextField] + return [oldforms.TextField] class SmallIntegerField(IntegerField): def get_manipulator_field_objs(self): - return [forms.SmallIntegerField] + return [oldforms.SmallIntegerField] class TextField(Field): def get_manipulator_field_objs(self): - return [forms.LargeTextField] + return [oldforms.LargeTextField] + + def formfield(self, **kwargs): + defaults = {'required': not self.blank, 'widget': forms.Textarea, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + defaults.update(kwargs) + return forms.CharField(**defaults) class TimeField(Field): empty_strings_allowed = False @@ -760,24 +809,39 @@ class TimeField(Field): return Field.get_db_prep_save(self, value) def get_manipulator_field_objs(self): - return [forms.TimeField] + return [oldforms.TimeField] def flatten_data(self,follow, obj = None): val = self._get_val_from_obj(obj) return {self.attname: (val is not None and val.strftime("%H:%M:%S") or '')} -class URLField(Field): + def formfield(self, **kwargs): + defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + defaults.update(kwargs) + return forms.TimeField(**defaults) + +class URLField(CharField): def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): + kwargs['maxlength'] = kwargs.get('maxlength', 200) if verify_exists: kwargs.setdefault('validator_list', []).append(validators.isExistingURL) - Field.__init__(self, verbose_name, name, **kwargs) + self.verify_exists = verify_exists + CharField.__init__(self, verbose_name, name, **kwargs) def get_manipulator_field_objs(self): - return [forms.URLField] + return [oldforms.URLField] + + def get_internal_type(self): + return "CharField" + + def formfield(self, **kwargs): + defaults = {'required': not self.blank, 'verify_exists': self.verify_exists, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + defaults.update(kwargs) + return forms.URLField(**defaults) class USStateField(Field): def get_manipulator_field_objs(self): - return [forms.USStateField] + return [oldforms.USStateField] class XMLField(TextField): def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs): @@ -788,7 +852,7 @@ class XMLField(TextField): return "TextField" def get_manipulator_field_objs(self): - return [curry(forms.XMLLargeTextField, schema_path=self.schema_path)] + return [curry(oldforms.XMLLargeTextField, schema_path=self.schema_path)] class OrderingField(IntegerField): empty_strings_allowed=False @@ -801,4 +865,4 @@ class OrderingField(IntegerField): return "IntegerField" def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): - return [forms.HiddenField(name_prefix + self.name)] + return [oldforms.HiddenField(name_prefix + self.name)] diff --git a/django/db/models/fields/generic.py b/django/db/models/fields/generic.py index 7d7651029c..1ad8346e42 100644 --- a/django/db/models/fields/generic.py +++ b/django/db/models/fields/generic.py @@ -2,7 +2,7 @@ Classes allowing "generic" relations through ContentType and object-id fields. """ -from django import forms +from django import oldforms from django.core.exceptions import ObjectDoesNotExist from django.db import backend from django.db.models import signals @@ -98,7 +98,7 @@ class GenericRelation(RelatedField, Field): def get_manipulator_field_objs(self): choices = self.get_choices_default() - return [curry(forms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)] + return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)] def get_choices_default(self): return Field.get_choices(self, include_blank=False) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index bd9262d55a..b517747735 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -2,10 +2,12 @@ from django.db import backend, transaction from django.db.models import signals, get_model from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class from django.db.models.related import RelatedObject +from django.utils.text import capfirst from django.utils.translation import gettext_lazy, string_concat, ngettext from django.utils.functional import curry from django.core import validators -from django import forms +from django import oldforms +from django import newforms as forms from django.dispatch import dispatcher # For Python 2.3 @@ -256,8 +258,7 @@ class ForeignRelatedObjectsDescriptor(object): # Otherwise, just move the named objects into the set. if self.related.field.null: manager.clear() - for obj in value: - manager.add(obj) + manager.add(*value) def create_many_related_manager(superclass): """Creates a manager that subclasses 'superclass' (which is a Manager) @@ -315,28 +316,36 @@ def create_many_related_manager(superclass): # join_table: name of the m2m link table # source_col_name: the PK colname in join_table for the source object # target_col_name: the PK colname in join_table for the target object - # *objs - objects to add + # *objs - objects to add. Either object instances, or primary keys of object instances. from django.db import connection - # Add the newly created or already existing objects to the join table. - # First find out which items are already added, to avoid adding them twice - new_ids = set([obj._get_pk_val() for obj in objs]) - cursor = connection.cursor() - cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \ - (target_col_name, self.join_table, source_col_name, - target_col_name, ",".join(['%s'] * len(new_ids))), - [self._pk_val] + list(new_ids)) - if cursor.rowcount is not None and cursor.rowcount != 0: - existing_ids = set([row[0] for row in cursor.fetchmany(cursor.rowcount)]) - else: - existing_ids = set() + # If there aren't any objects, there is nothing to do. + if objs: + # Check that all the objects are of the right type + new_ids = set() + for obj in objs: + if isinstance(obj, self.model): + new_ids.add(obj._get_pk_val()) + else: + new_ids.add(obj) + # Add the newly created or already existing objects to the join table. + # First find out which items are already added, to avoid adding them twice + cursor = connection.cursor() + cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \ + (target_col_name, self.join_table, source_col_name, + target_col_name, ",".join(['%s'] * len(new_ids))), + [self._pk_val] + list(new_ids)) + if cursor.rowcount is not None and cursor.rowcount != 0: + existing_ids = set([row[0] for row in cursor.fetchmany(cursor.rowcount)]) + else: + existing_ids = set() - # Add the ones that aren't there already - for obj_id in (new_ids - existing_ids): - cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ - (self.join_table, source_col_name, target_col_name), - [self._pk_val, obj_id]) - transaction.commit_unless_managed() + # Add the ones that aren't there already + for obj_id in (new_ids - existing_ids): + cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ + (self.join_table, source_col_name, target_col_name), + [self._pk_val, obj_id]) + transaction.commit_unless_managed() def _remove_items(self, source_col_name, target_col_name, *objs): # source_col_name: the PK colname in join_table for the source object @@ -344,16 +353,22 @@ def create_many_related_manager(superclass): # *objs - objects to remove from django.db import connection - for obj in objs: - if not isinstance(obj, self.model): - raise ValueError, "objects to remove() must be %s instances" % self.model._meta.object_name - # Remove the specified objects from the join table - cursor = connection.cursor() - for obj in objs: - cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s = %%s" % \ - (self.join_table, source_col_name, target_col_name), - [self._pk_val, obj._get_pk_val()]) - transaction.commit_unless_managed() + # If there aren't any objects, there is nothing to do. + if objs: + # Check that all the objects are of the right type + old_ids = set() + for obj in objs: + if isinstance(obj, self.model): + old_ids.add(obj._get_pk_val()) + else: + old_ids.add(obj) + # Remove the specified objects from the join table + cursor = connection.cursor() + cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \ + (self.join_table, source_col_name, + target_col_name, ",".join(['%s'] * len(old_ids))), + [self._pk_val] + list(old_ids)) + transaction.commit_unless_managed() def _clear_items(self, source_col_name): # source_col_name: the PK colname in join_table for the source object @@ -405,8 +420,7 @@ class ManyRelatedObjectsDescriptor(object): manager = self.__get__(instance) manager.clear() - for obj in value: - manager.add(obj) + manager.add(*value) class ReverseManyRelatedObjectsDescriptor(object): # This class provides the functionality that makes the related-object @@ -447,8 +461,7 @@ class ReverseManyRelatedObjectsDescriptor(object): manager = self.__get__(instance) manager.clear() - for obj in value: - manager.add(obj) + manager.add(*value) class ForeignKey(RelatedField, Field): empty_strings_allowed = False @@ -493,13 +506,13 @@ class ForeignKey(RelatedField, Field): params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator)) else: if self.radio_admin: - field_objs = [forms.RadioSelectField] + field_objs = [oldforms.RadioSelectField] params['ul_class'] = get_ul_class(self.radio_admin) else: if self.null: - field_objs = [forms.NullSelectField] + field_objs = [oldforms.NullSelectField] else: - field_objs = [forms.SelectField] + field_objs = [oldforms.SelectField] params['choices'] = self.get_choices_default() return field_objs, params @@ -508,7 +521,7 @@ class ForeignKey(RelatedField, Field): if self.rel.raw_id_admin and not isinstance(rel_field, AutoField): return rel_field.get_manipulator_field_objs() else: - return [forms.IntegerField] + return [oldforms.IntegerField] def get_db_prep_save(self, value): if value == '' or value == None: @@ -539,6 +552,11 @@ class ForeignKey(RelatedField, Field): def contribute_to_related_class(self, cls, related): setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) + def formfield(self, **kwargs): + defaults = {'choices': self.get_choices_default(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + defaults.update(kwargs) + return forms.ChoiceField(**defaults) + class OneToOneField(RelatedField, IntegerField): def __init__(self, to, to_field=None, **kwargs): try: @@ -581,13 +599,13 @@ class OneToOneField(RelatedField, IntegerField): params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator)) else: if self.radio_admin: - field_objs = [forms.RadioSelectField] + field_objs = [oldforms.RadioSelectField] params['ul_class'] = get_ul_class(self.radio_admin) else: if self.null: - field_objs = [forms.NullSelectField] + field_objs = [oldforms.NullSelectField] else: - field_objs = [forms.SelectField] + field_objs = [oldforms.SelectField] params['choices'] = self.get_choices_default() return field_objs, params @@ -600,6 +618,11 @@ class OneToOneField(RelatedField, IntegerField): if not cls._meta.one_to_one_field: cls._meta.one_to_one_field = self + def formfield(self, **kwargs): + defaults = {'choices': self.get_choices_default(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + defaults.update(kwargs) + return forms.ChoiceField(**kwargs) + class ManyToManyField(RelatedField, Field): def __init__(self, to, **kwargs): kwargs['verbose_name'] = kwargs.get('verbose_name', None) @@ -610,6 +633,7 @@ class ManyToManyField(RelatedField, Field): limit_choices_to=kwargs.pop('limit_choices_to', None), raw_id_admin=kwargs.pop('raw_id_admin', False), symmetrical=kwargs.pop('symmetrical', True)) + self.db_table = kwargs.pop('db_table', None) if kwargs["rel"].raw_id_admin: kwargs.setdefault("validator_list", []).append(self.isValidIDList) Field.__init__(self, **kwargs) @@ -622,17 +646,20 @@ class ManyToManyField(RelatedField, Field): def get_manipulator_field_objs(self): if self.rel.raw_id_admin: - return [forms.RawIdAdminField] + return [oldforms.RawIdAdminField] else: choices = self.get_choices_default() - return [curry(forms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)] + return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)] def get_choices_default(self): return Field.get_choices(self, include_blank=False) def _get_m2m_db_table(self, opts): "Function that can be curried to provide the m2m table name for this relation" - return '%s_%s' % (opts.db_table, self.name) + if self.db_table: + return self.db_table + else: + return '%s_%s' % (opts.db_table, self.name) def _get_m2m_column_name(self, related): "Function that can be curried to provide the source column name for the m2m table" @@ -706,6 +733,19 @@ class ManyToManyField(RelatedField, Field): def set_attributes_from_rel(self): pass + def value_from_object(self, obj): + "Returns the value of this field in the given model instance." + return getattr(obj, self.attname).all() + + def formfield(self, **kwargs): + # If initial is passed in, it's a list of related objects, but the + # MultipleChoiceField takes a list of IDs. + if kwargs.get('initial') is not None: + kwargs['initial'] = [i._get_pk_val() for i in kwargs['initial']] + defaults = {'choices': self.get_choices_default(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + defaults.update(kwargs) + return forms.MultipleChoiceField(**defaults) + class ManyToOneRel(object): def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None, max_num_in_admin=None, num_extra_on_change=1, edit_inline=False, diff --git a/django/db/models/manager.py b/django/db/models/manager.py index 6005874516..b60eed262a 100644 --- a/django/db/models/manager.py +++ b/django/db/models/manager.py @@ -1,4 +1,4 @@ -from django.db.models.query import QuerySet +from django.db.models.query import QuerySet, EmptyQuerySet from django.dispatch import dispatcher from django.db.models import signals from django.db.models.fields import FieldDoesNotExist @@ -41,12 +41,18 @@ class Manager(object): ####################### # PROXIES TO QUERYSET # ####################### + + def get_empty_query_set(self): + return EmptyQuerySet(self.model) def get_query_set(self): """Returns a new QuerySet object. Subclasses can override this method to easily customise the behaviour of the Manager. """ return QuerySet(self.model) + + def none(self): + return self.get_empty_query_set() def all(self): return self.get_query_set() diff --git a/django/db/models/manipulators.py b/django/db/models/manipulators.py index c61e82f813..aea3aa70a7 100644 --- a/django/db/models/manipulators.py +++ b/django/db/models/manipulators.py @@ -1,5 +1,5 @@ from django.core.exceptions import ObjectDoesNotExist -from django import forms +from django import oldforms from django.core import validators from django.db.models.fields import FileField, AutoField from django.dispatch import dispatcher @@ -40,7 +40,7 @@ class ManipulatorDescriptor(object): self.man._prepare(model) return self.man -class AutomaticManipulator(forms.Manipulator): +class AutomaticManipulator(oldforms.Manipulator): def _prepare(cls, model): cls.model = model cls.manager = model._default_manager @@ -76,7 +76,7 @@ class AutomaticManipulator(forms.Manipulator): # Add field for ordering. if self.change and self.opts.get_ordered_objects(): - self.fields.append(forms.CommaSeparatedIntegerField(field_name="order_")) + self.fields.append(oldforms.CommaSeparatedIntegerField(field_name="order_")) def save(self, new_data): # TODO: big cleanup when core fields go -> use recursive manipulators. @@ -308,7 +308,7 @@ def manipulator_validator_unique_together(field_name_list, opts, self, field_dat def manipulator_validator_unique_for_date(from_field, date_field, opts, lookup_type, self, field_data, all_data): from django.db.models.fields.related import ManyToOneRel date_str = all_data.get(date_field.get_manipulator_field_names('')[0], None) - date_val = forms.DateField.html2python(date_str) + date_val = oldforms.DateField.html2python(date_str) if date_val is None: return # Date was invalid. This will be caught by another validator. lookup_kwargs = {'%s__year' % date_field.name: date_val.year} diff --git a/django/db/models/query.py b/django/db/models/query.py index 53ed63ae5b..51fa334e63 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1,5 +1,6 @@ from django.db import backend, connection, transaction from django.db.models.fields import DateField, FieldDoesNotExist +from django.db.models.fields.generic import GenericRelation from django.db.models import signals from django.dispatch import dispatcher from django.utils.datastructures import SortedDict @@ -25,6 +26,9 @@ QUERY_TERMS = ( # Larger values are slightly faster at the expense of more storage space. GET_ITERATOR_CHUNK_SIZE = 100 +class EmptyResultSet(Exception): + pass + #################### # HELPER FUNCTIONS # #################### @@ -168,7 +172,12 @@ class QuerySet(object): extra_select = self._select.items() cursor = connection.cursor() - select, sql, params = self._get_sql_clause() + + try: + select, sql, params = self._get_sql_clause() + except EmptyResultSet: + raise StopIteration + cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) fill_cache = self._select_related index_end = len(self.model._meta.fields) @@ -192,7 +201,12 @@ class QuerySet(object): counter._offset = None counter._limit = None counter._select_related = False - select, sql, params = counter._get_sql_clause() + + try: + select, sql, params = counter._get_sql_clause() + except EmptyResultSet: + return 0 + cursor = connection.cursor() if self._distinct: id_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table), @@ -523,7 +537,12 @@ class ValuesQuerySet(QuerySet): field_names = [f.attname for f in self.model._meta.fields] cursor = connection.cursor() - select, sql, params = self._get_sql_clause() + + try: + select, sql, params = self._get_sql_clause() + except EmptyResultSet: + raise StopIteration + select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns] cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) while 1: @@ -545,7 +564,12 @@ class DateQuerySet(QuerySet): if self._field.null: self._where.append('%s.%s IS NOT NULL' % \ (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column))) - select, sql, params = self._get_sql_clause() + + try: + select, sql, params = self._get_sql_clause() + except EmptyResultSet: + raise StopIteration + sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \ (backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column))), sql, self._order) @@ -562,6 +586,25 @@ class DateQuerySet(QuerySet): c._kind = self._kind c._order = self._order return c + +class EmptyQuerySet(QuerySet): + def __init__(self, model=None): + super(EmptyQuerySet, self).__init__(model) + self._result_cache = [] + + def iterator(self): + raise StopIteration + + def count(self): + return 0 + + def delete(self): + pass + + def _clone(self, klass=None, **kwargs): + c = super(EmptyQuerySet, self)._clone(klass, **kwargs) + c._result_cache = [] + return c class QOperator(object): "Base class for QAnd and QOr" @@ -571,10 +614,14 @@ class QOperator(object): def get_sql(self, opts): joins, where, params = SortedDict(), [], [] for val in self.args: - joins2, where2, params2 = val.get_sql(opts) - joins.update(joins2) - where.extend(where2) - params.extend(params2) + try: + joins2, where2, params2 = val.get_sql(opts) + joins.update(joins2) + where.extend(where2) + params.extend(params2) + except EmptyResultSet: + if not isinstance(self, QOr): + raise EmptyResultSet if where: return joins, ['(%s)' % self.operator.join(where)], params return joins, [], params @@ -628,8 +675,11 @@ class QNot(Q): self.q = q def get_sql(self, opts): - joins, where, params = self.q.get_sql(opts) - where2 = ['(NOT (%s))' % " AND ".join(where)] + try: + joins, where, params = self.q.get_sql(opts) + where2 = ['(NOT (%s))' % " AND ".join(where)] + except EmptyResultSet: + return SortedDict(), [], [] return joins, where2, params def get_where_clause(lookup_type, table_prefix, field_name, value): @@ -641,7 +691,11 @@ def get_where_clause(lookup_type, table_prefix, field_name, value): except KeyError: pass if lookup_type == 'in': - return '%s%s IN (%s)' % (table_prefix, field_name, ','.join(['%s' for v in value])) + in_string = ','.join(['%s' for id in value]) + if in_string: + return '%s%s IN (%s)' % (table_prefix, field_name, in_string) + else: + raise EmptyResultSet elif lookup_type == 'range': return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name) elif lookup_type in ('year', 'month', 'day'): @@ -926,18 +980,26 @@ def delete_objects(seen_objs): pk_list = [pk for pk,instance in seen_objs[cls]] for related in cls._meta.get_all_related_many_to_many_objects(): - for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): - cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \ - (qn(related.field.m2m_db_table()), - qn(related.field.m2m_reverse_name()), - ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), - pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]) + if not isinstance(related.field, GenericRelation): + for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): + cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \ + (qn(related.field.m2m_db_table()), + qn(related.field.m2m_reverse_name()), + ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), + pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]) for f in cls._meta.many_to_many: + if isinstance(f, GenericRelation): + from django.contrib.contenttypes.models import ContentType + query_extra = 'AND %s=%%s' % f.rel.to._meta.get_field(f.content_type_field_name).column + args_extra = [ContentType.objects.get_for_model(cls).id] + else: + query_extra = '' + args_extra = [] for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): - cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \ + cursor.execute(("DELETE FROM %s WHERE %s IN (%s)" % \ (qn(f.m2m_db_table()), qn(f.m2m_column_name()), - ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), - pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]) + ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]]))) + query_extra, + pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE] + args_extra) for field in cls._meta.fields: if field.rel and field.null and field.rel.to in seen_objs: for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): diff --git a/django/forms/__init__.py b/django/forms/__init__.py index 0b9ac05edb..68d3d245a2 100644 --- a/django/forms/__init__.py +++ b/django/forms/__init__.py @@ -1,1008 +1 @@ -from django.core import validators -from django.core.exceptions import PermissionDenied -from django.utils.html import escape -from django.conf import settings -from django.utils.translation import gettext, ngettext - -FORM_FIELD_ID_PREFIX = 'id_' - -class EmptyValue(Exception): - "This is raised when empty data is provided" - pass - -class Manipulator(object): - # List of permission strings. User must have at least one to manipulate. - # None means everybody has permission. - required_permission = '' - - def __init__(self): - # List of FormField objects - self.fields = [] - - def __getitem__(self, field_name): - "Looks up field by field name; raises KeyError on failure" - for field in self.fields: - if field.field_name == field_name: - return field - raise KeyError, "Field %s not found\n%s" % (field_name, repr(self.fields)) - - def __delitem__(self, field_name): - "Deletes the field with the given field name; raises KeyError on failure" - for i, field in enumerate(self.fields): - if field.field_name == field_name: - del self.fields[i] - return - raise KeyError, "Field %s not found" % field_name - - def check_permissions(self, user): - """Confirms user has required permissions to use this manipulator; raises - PermissionDenied on failure.""" - if self.required_permission is None: - return - if user.has_perm(self.required_permission): - return - raise PermissionDenied - - def prepare(self, new_data): - """ - Makes any necessary preparations to new_data, in place, before data has - been validated. - """ - for field in self.fields: - field.prepare(new_data) - - def get_validation_errors(self, new_data): - "Returns dictionary mapping field_names to error-message lists" - errors = {} - self.prepare(new_data) - for field in self.fields: - errors.update(field.get_validation_errors(new_data)) - val_name = 'validate_%s' % field.field_name - if hasattr(self, val_name): - val = getattr(self, val_name) - try: - field.run_validator(new_data, val) - except (validators.ValidationError, validators.CriticalValidationError), e: - errors.setdefault(field.field_name, []).extend(e.messages) - -# if field.is_required and not new_data.get(field.field_name, False): -# errors.setdefault(field.field_name, []).append(gettext_lazy('This field is required.')) -# continue -# try: -# validator_list = field.validator_list -# if hasattr(self, 'validate_%s' % field.field_name): -# validator_list.append(getattr(self, 'validate_%s' % field.field_name)) -# for validator in validator_list: -# if field.is_required or new_data.get(field.field_name, False) or hasattr(validator, 'always_test'): -# try: -# if hasattr(field, 'requires_data_list'): -# validator(new_data.getlist(field.field_name), new_data) -# else: -# validator(new_data.get(field.field_name, ''), new_data) -# except validators.ValidationError, e: -# errors.setdefault(field.field_name, []).extend(e.messages) -# # If a CriticalValidationError is raised, ignore any other ValidationErrors -# # for this particular field -# except validators.CriticalValidationError, e: -# errors.setdefault(field.field_name, []).extend(e.messages) - return errors - - def save(self, new_data): - "Saves the changes and returns the new object" - # changes is a dictionary-like object keyed by field_name - raise NotImplementedError - - def do_html2python(self, new_data): - """ - Convert the data from HTML data types to Python datatypes, changing the - object in place. This happens after validation but before storage. This - must happen after validation because html2python functions aren't - expected to deal with invalid input. - """ - for field in self.fields: - field.convert_post_data(new_data) - -class FormWrapper(object): - """ - A wrapper linking a Manipulator to the template system. - This allows dictionary-style lookups of formfields. It also handles feeding - prepopulated data and validation error messages to the formfield objects. - """ - def __init__(self, manipulator, data=None, error_dict=None, edit_inline=True): - self.manipulator = manipulator - if data is None: - data = {} - if error_dict is None: - error_dict = {} - self.data = data - self.error_dict = error_dict - self._inline_collections = None - self.edit_inline = edit_inline - - def __repr__(self): - return repr(self.__dict__) - - def __getitem__(self, key): - for field in self.manipulator.fields: - if field.field_name == key: - data = field.extract_data(self.data) - return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, [])) - if self.edit_inline: - self.fill_inline_collections() - for inline_collection in self._inline_collections: - if inline_collection.name == key: - return inline_collection - raise KeyError, "Could not find Formfield or InlineObjectCollection named %r" % key - - def fill_inline_collections(self): - if not self._inline_collections: - ic = [] - related_objects = self.manipulator.get_related_objects() - for rel_obj in related_objects: - data = rel_obj.extract_data(self.data) - inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict) - ic.append(inline_collection) - self._inline_collections = ic - - def has_errors(self): - return self.error_dict != {} - - def _get_fields(self): - try: - return self._fields - except AttributeError: - self._fields = [self.__getitem__(field.field_name) for field in self.manipulator.fields] - return self._fields - - fields = property(_get_fields) - -class FormFieldWrapper(object): - "A bridge between the template system and an individual form field. Used by FormWrapper." - def __init__(self, formfield, data, error_list): - self.formfield, self.data, self.error_list = formfield, data, error_list - self.field_name = self.formfield.field_name # for convenience in templates - - def __str__(self): - "Renders the field" - return str(self.formfield.render(self.data)) - - def __repr__(self): - return '' % self.formfield.field_name - - def field_list(self): - """ - Like __str__(), but returns a list. Use this when the field's render() - method returns a list. - """ - return self.formfield.render(self.data) - - def errors(self): - return self.error_list - - def html_error_list(self): - if self.errors(): - return '
  • %s
' % '
  • '.join([escape(e) for e in self.errors()]) - else: - return '' - - def get_id(self): - return self.formfield.get_id() - -class FormFieldCollection(FormFieldWrapper): - "A utility class that gives the template access to a dict of FormFieldWrappers" - def __init__(self, formfield_dict): - self.formfield_dict = formfield_dict - - def __str__(self): - return str(self.formfield_dict) - - def __getitem__(self, template_key): - "Look up field by template key; raise KeyError on failure" - return self.formfield_dict[template_key] - - def __repr__(self): - return "" % self.formfield_dict - - def errors(self): - "Returns list of all errors in this collection's formfields" - errors = [] - for field in self.formfield_dict.values(): - if hasattr(field, 'errors'): - errors.extend(field.errors()) - return errors - - def has_errors(self): - return bool(len(self.errors())) - - def html_combined_error_list(self): - return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')]) - -class InlineObjectCollection(object): - "An object that acts like a sparse list of form field collections." - def __init__(self, parent_manipulator, rel_obj, data, errors): - self.parent_manipulator = parent_manipulator - self.rel_obj = rel_obj - self.data = data - self.errors = errors - self._collections = None - self.name = rel_obj.name - - def __len__(self): - self.fill() - return self._collections.__len__() - - def __getitem__(self, k): - self.fill() - return self._collections.__getitem__(k) - - def __setitem__(self, k, v): - self.fill() - return self._collections.__setitem__(k,v) - - def __delitem__(self, k): - self.fill() - return self._collections.__delitem__(k) - - def __iter__(self): - self.fill() - return iter(self._collections.values()) - - def items(self): - self.fill() - return self._collections.items() - - def fill(self): - if self._collections: - return - else: - var_name = self.rel_obj.opts.object_name.lower() - collections = {} - orig = None - if hasattr(self.parent_manipulator, 'original_object'): - orig = self.parent_manipulator.original_object - orig_list = self.rel_obj.get_list(orig) - - for i, instance in enumerate(orig_list): - collection = {'original': instance} - for f in self.rel_obj.editable_fields(): - for field_name in f.get_manipulator_field_names(''): - full_field_name = '%s.%d.%s' % (var_name, i, field_name) - field = self.parent_manipulator[full_field_name] - data = field.extract_data(self.data) - errors = self.errors.get(full_field_name, []) - collection[field_name] = FormFieldWrapper(field, data, errors) - collections[i] = FormFieldCollection(collection) - self._collections = collections - - -class FormField(object): - """Abstract class representing a form field. - - Classes that extend FormField should define the following attributes: - field_name - The field's name for use by programs. - validator_list - A list of validation tests (callback functions) that the data for - this field must pass in order to be added or changed. - is_required - A Boolean. Is it a required field? - Subclasses should also implement a render(data) method, which is responsible - for rending the form field in XHTML. - """ - def __str__(self): - return self.render('') - - def __repr__(self): - return 'FormField "%s"' % self.field_name - - def prepare(self, new_data): - "Hook for doing something to new_data (in place) before validation." - pass - - def html2python(data): - "Hook for converting an HTML datatype (e.g. 'on' for checkboxes) to a Python type" - return data - html2python = staticmethod(html2python) - - def render(self, data): - raise NotImplementedError - - def get_member_name(self): - if hasattr(self, 'member_name'): - return self.member_name - else: - return self.field_name - - def extract_data(self, data_dict): - if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'): - data = data_dict.getlist(self.get_member_name()) - else: - data = data_dict.get(self.get_member_name(), None) - if data is None: - data = '' - return data - - def convert_post_data(self, new_data): - name = self.get_member_name() - if new_data.has_key(self.field_name): - d = new_data.getlist(self.field_name) - try: - converted_data = [self.__class__.html2python(data) for data in d] - except ValueError: - converted_data = d - new_data.setlist(name, converted_data) - else: - try: - #individual fields deal with None values themselves - new_data.setlist(name, [self.__class__.html2python(None)]) - except EmptyValue: - new_data.setlist(name, []) - - - def run_validator(self, new_data, validator): - if self.is_required or new_data.get(self.field_name, False) or hasattr(validator, 'always_test'): - if hasattr(self, 'requires_data_list'): - validator(new_data.getlist(self.field_name), new_data) - else: - validator(new_data.get(self.field_name, ''), new_data) - - def get_validation_errors(self, new_data): - errors = {} - if self.is_required and not new_data.get(self.field_name, False): - errors.setdefault(self.field_name, []).append(gettext('This field is required.')) - return errors - try: - for validator in self.validator_list: - try: - self.run_validator(new_data, validator) - except validators.ValidationError, e: - errors.setdefault(self.field_name, []).extend(e.messages) - # If a CriticalValidationError is raised, ignore any other ValidationErrors - # for this particular field - except validators.CriticalValidationError, e: - errors.setdefault(self.field_name, []).extend(e.messages) - return errors - - def get_id(self): - "Returns the HTML 'id' attribute for this form field." - return FORM_FIELD_ID_PREFIX + self.field_name - -#################### -# GENERIC WIDGETS # -#################### - -class TextField(FormField): - input_type = "text" - def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, member_name=None): - if validator_list is None: validator_list = [] - self.field_name = field_name - self.length, self.maxlength = length, maxlength - self.is_required = is_required - self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list - if member_name != None: - self.member_name = member_name - - def isValidLength(self, data, form): - if data and self.maxlength and len(data.decode(settings.DEFAULT_CHARSET)) > self.maxlength: - raise validators.ValidationError, ngettext("Ensure your text is less than %s character.", - "Ensure your text is less than %s characters.", self.maxlength) % self.maxlength - - def hasNoNewlines(self, data, form): - if data and '\n' in data: - raise validators.ValidationError, gettext("Line breaks are not allowed here.") - - def render(self, data): - if data is None: - data = '' - maxlength = '' - if self.maxlength: - maxlength = 'maxlength="%s" ' % self.maxlength - if isinstance(data, unicode): - data = data.encode(settings.DEFAULT_CHARSET) - return '' % \ - (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', - self.field_name, self.length, escape(data), maxlength) - - def html2python(data): - return data - html2python = staticmethod(html2python) - -class PasswordField(TextField): - input_type = "password" - -class LargeTextField(TextField): - def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, maxlength=None): - if validator_list is None: validator_list = [] - self.field_name = field_name - self.rows, self.cols, self.is_required = rows, cols, is_required - self.validator_list = validator_list[:] - if maxlength: - self.validator_list.append(self.isValidLength) - self.maxlength = maxlength - - def render(self, data): - if data is None: - data = '' - if isinstance(data, unicode): - data = data.encode(settings.DEFAULT_CHARSET) - return '' % \ - (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', - self.field_name, self.rows, self.cols, escape(data)) - -class HiddenField(FormField): - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - self.field_name, self.is_required = field_name, is_required - self.validator_list = validator_list[:] - - def render(self, data): - return '' % \ - (self.get_id(), self.field_name, escape(data)) - -class CheckboxField(FormField): - def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False): - if validator_list is None: validator_list = [] - self.field_name = field_name - self.checked_by_default = checked_by_default - self.is_required = is_required - self.validator_list = validator_list[:] - - def render(self, data): - checked_html = '' - if data or (data is '' and self.checked_by_default): - checked_html = ' checked="checked"' - return '' % \ - (self.get_id(), self.__class__.__name__, - self.field_name, checked_html) - - def html2python(data): - "Convert value from browser ('on' or '') to a Python boolean" - if data == 'on': - return True - return False - html2python = staticmethod(html2python) - -class SelectField(FormField): - def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None): - if validator_list is None: validator_list = [] - if choices is None: choices = [] - self.field_name = field_name - # choices is a list of (value, human-readable key) tuples because order matters - self.choices, self.size, self.is_required = choices, size, is_required - self.validator_list = [self.isValidChoice] + validator_list - if member_name != None: - self.member_name = member_name - - def render(self, data): - output = ['') - return '\n'.join(output) - - def isValidChoice(self, data, form): - str_data = str(data) - str_choices = [str(item[0]) for item in self.choices] - if str_data not in str_choices: - raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data': str_data, 'choices': str_choices} - -class NullSelectField(SelectField): - "This SelectField converts blank fields to None" - def html2python(data): - if not data: - return None - return data - html2python = staticmethod(html2python) - -class RadioSelectField(FormField): - def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None): - if validator_list is None: validator_list = [] - if choices is None: choices = [] - self.field_name = field_name - # choices is a list of (value, human-readable key) tuples because order matters - self.choices, self.is_required = choices, is_required - self.validator_list = [self.isValidChoice] + validator_list - self.ul_class = ul_class - if member_name != None: - self.member_name = member_name - - def render(self, data): - """ - Returns a special object, RadioFieldRenderer, that is iterable *and* - has a default str() rendered output. - - This allows for flexible use in templates. You can just use the default - rendering: - - {{ field_name }} - - ...which will output the radio buttons in an unordered list. - Or, you can manually traverse each radio option for special layout: - - {% for option in field_name.field_list %} - {{ option.field }} {{ option.label }}
    - {% endfor %} - """ - class RadioFieldRenderer: - def __init__(self, datalist, ul_class): - self.datalist, self.ul_class = datalist, ul_class - def __str__(self): - "Default str() output for this radio field -- a
      " - output = ['' % (self.ul_class and ' class="%s"' % self.ul_class or '')] - output.extend(['
    • %s %s
    • ' % (d['field'], d['label']) for d in self.datalist]) - output.append('
    ') - return ''.join(output) - def __iter__(self): - for d in self.datalist: - yield d - def __len__(self): - return len(self.datalist) - datalist = [] - str_data = str(data) # normalize to string - for i, (value, display_name) in enumerate(self.choices): - selected_html = '' - if str(value) == str_data: - selected_html = ' checked="checked"' - datalist.append({ - 'value': value, - 'name': display_name, - 'field': '' % \ - (self.get_id() + '_' + str(i), self.field_name, value, selected_html), - 'label': '' % \ - (self.get_id() + '_' + str(i), display_name), - }) - return RadioFieldRenderer(datalist, self.ul_class) - - def isValidChoice(self, data, form): - str_data = str(data) - str_choices = [str(item[0]) for item in self.choices] - if str_data not in str_choices: - raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':str_data, 'choices':str_choices} - -class NullBooleanField(SelectField): - "This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None" - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - SelectField.__init__(self, field_name, choices=[('1', 'Unknown'), ('2', 'Yes'), ('3', 'No')], - is_required=is_required, validator_list=validator_list) - - def render(self, data): - if data is None: data = '1' - elif data == True: data = '2' - elif data == False: data = '3' - return SelectField.render(self, data) - - def html2python(data): - return {None: None, '1': None, '2': True, '3': False}[data] - html2python = staticmethod(html2python) - -class SelectMultipleField(SelectField): - requires_data_list = True - def render(self, data): - output = ['') - return '\n'.join(output) - - def isValidChoice(self, field_data, all_data): - # data is something like ['1', '2', '3'] - str_choices = [str(item[0]) for item in self.choices] - for val in map(str, field_data): - if val not in str_choices: - raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':val, 'choices':str_choices} - - def html2python(data): - if data is None: - raise EmptyValue - return data - html2python = staticmethod(html2python) - -class CheckboxSelectMultipleField(SelectMultipleField): - """ - This has an identical interface to SelectMultipleField, except the rendered - widget is different. Instead of a es. - - Of course, that results in multiple form elements for the same "single" - field, so this class's prepare() method flattens the split data elements - back into the single list that validators, renderers and save() expect. - """ - requires_data_list = True - def __init__(self, field_name, choices=None, ul_class='', validator_list=None): - if validator_list is None: validator_list = [] - if choices is None: choices = [] - self.ul_class = ul_class - SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list) - - def prepare(self, new_data): - # new_data has "split" this field into several fields, so flatten it - # back into a single list. - data_list = [] - for value, readable_value in self.choices: - if new_data.get('%s%s' % (self.field_name, value), '') == 'on': - data_list.append(value) - new_data.setlist(self.field_name, data_list) - - def render(self, data): - output = ['' % (self.ul_class and ' class="%s"' % self.ul_class or '')] - str_data_list = map(str, data) # normalize to strings - for value, choice in self.choices: - checked_html = '' - if str(value) in str_data_list: - checked_html = ' checked="checked"' - field_name = '%s%s' % (self.field_name, value) - output.append('
  • ' % \ - (self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html, - self.get_id() + escape(value), choice)) - output.append('') - return '\n'.join(output) - -#################### -# FILE UPLOADS # -#################### - -class FileUploadField(FormField): - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - self.field_name, self.is_required = field_name, is_required - self.validator_list = [self.isNonEmptyFile] + validator_list - - def isNonEmptyFile(self, field_data, all_data): - try: - content = field_data['content'] - except TypeError: - raise validators.CriticalValidationError, gettext("No file was submitted. Check the encoding type on the form.") - if not content: - raise validators.CriticalValidationError, gettext("The submitted file is empty.") - - def render(self, data): - return '' % \ - (self.get_id(), self.__class__.__name__, self.field_name) - - def html2python(data): - if data is None: - raise EmptyValue - return data - html2python = staticmethod(html2python) - -class ImageUploadField(FileUploadField): - "A FileUploadField that raises CriticalValidationError if the uploaded file isn't an image." - def __init__(self, *args, **kwargs): - FileUploadField.__init__(self, *args, **kwargs) - self.validator_list.insert(0, self.isValidImage) - - def isValidImage(self, field_data, all_data): - try: - validators.isValidImage(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - -#################### -# INTEGERS/FLOATS # -#################### - -class IntegerField(TextField): - def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, member_name=None): - if validator_list is None: validator_list = [] - validator_list = [self.isInteger] + validator_list - if member_name is not None: - self.member_name = member_name - TextField.__init__(self, field_name, length, maxlength, is_required, validator_list) - - def isInteger(self, field_data, all_data): - try: - validators.isInteger(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - if data == '' or data is None: - return None - return int(data) - html2python = staticmethod(html2python) - -class SmallIntegerField(IntegerField): - def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isSmallInteger] + validator_list - IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list) - - def isSmallInteger(self, field_data, all_data): - if not -32768 <= int(field_data) <= 32767: - raise validators.CriticalValidationError, gettext("Enter a whole number between -32,768 and 32,767.") - -class PositiveIntegerField(IntegerField): - def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isPositive] + validator_list - IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list) - - def isPositive(self, field_data, all_data): - if int(field_data) < 0: - raise validators.CriticalValidationError, gettext("Enter a positive number.") - -class PositiveSmallIntegerField(IntegerField): - def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isPositiveSmall] + validator_list - IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list) - - def isPositiveSmall(self, field_data, all_data): - if not 0 <= int(field_data) <= 32767: - raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.") - -class FloatField(TextField): - def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - self.max_digits, self.decimal_places = max_digits, decimal_places - validator_list = [self.isValidFloat] + validator_list - TextField.__init__(self, field_name, max_digits+2, max_digits+2, is_required, validator_list) - - def isValidFloat(self, field_data, all_data): - v = validators.IsValidFloat(self.max_digits, self.decimal_places) - try: - v(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - if data == '' or data is None: - return None - return float(data) - html2python = staticmethod(html2python) - -#################### -# DATES AND TIMES # -#################### - -class DatetimeField(TextField): - """A FormField that automatically converts its data to a datetime.datetime object. - The data should be in the format YYYY-MM-DD HH:MM:SS.""" - def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - self.field_name = field_name - self.length, self.maxlength = length, maxlength - self.is_required = is_required - self.validator_list = [validators.isValidANSIDatetime] + validator_list - - def html2python(data): - "Converts the field into a datetime.datetime object" - import datetime - try: - date, time = data.split() - y, m, d = date.split('-') - timebits = time.split(':') - h, mn = timebits[:2] - if len(timebits) > 2: - s = int(timebits[2]) - else: - s = 0 - return datetime.datetime(int(y), int(m), int(d), int(h), int(mn), s) - except ValueError: - return None - html2python = staticmethod(html2python) - -class DateField(TextField): - """A FormField that automatically converts its data to a datetime.date object. - The data should be in the format YYYY-MM-DD.""" - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidDate] + validator_list - TextField.__init__(self, field_name, length=10, maxlength=10, - is_required=is_required, validator_list=validator_list) - - def isValidDate(self, field_data, all_data): - try: - validators.isValidANSIDate(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - "Converts the field into a datetime.date object" - import time, datetime - try: - time_tuple = time.strptime(data, '%Y-%m-%d') - return datetime.date(*time_tuple[0:3]) - except (ValueError, TypeError): - return None - html2python = staticmethod(html2python) - -class TimeField(TextField): - """A FormField that automatically converts its data to a datetime.time object. - The data should be in the format HH:MM:SS or HH:MM:SS.mmmmmm.""" - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidTime] + validator_list - TextField.__init__(self, field_name, length=8, maxlength=8, - is_required=is_required, validator_list=validator_list) - - def isValidTime(self, field_data, all_data): - try: - validators.isValidANSITime(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - "Converts the field into a datetime.time object" - import time, datetime - try: - part_list = data.split('.') - try: - time_tuple = time.strptime(part_list[0], '%H:%M:%S') - except ValueError: # seconds weren't provided - time_tuple = time.strptime(part_list[0], '%H:%M') - t = datetime.time(*time_tuple[3:6]) - if (len(part_list) == 2): - t = t.replace(microsecond=int(part_list[1])) - return t - except (ValueError, TypeError, AttributeError): - return None - html2python = staticmethod(html2python) - -#################### -# INTERNET-RELATED # -#################### - -class EmailField(TextField): - "A convenience FormField for validating e-mail addresses" - def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidEmail] + validator_list - TextField.__init__(self, field_name, length, maxlength=maxlength, - is_required=is_required, validator_list=validator_list) - - def isValidEmail(self, field_data, all_data): - try: - validators.isValidEmail(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - -class URLField(TextField): - "A convenience FormField for validating URLs" - def __init__(self, field_name, length=50, maxlength=200, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidURL] + validator_list - TextField.__init__(self, field_name, length=length, maxlength=maxlength, - is_required=is_required, validator_list=validator_list) - - def isValidURL(self, field_data, all_data): - try: - validators.isValidURL(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - -class IPAddressField(TextField): - def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidIPAddress] + validator_list - TextField.__init__(self, field_name, length=length, maxlength=maxlength, - is_required=is_required, validator_list=validator_list) - - def isValidIPAddress(self, field_data, all_data): - try: - validators.isValidIPAddress4(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - return data or None - html2python = staticmethod(html2python) - -#################### -# MISCELLANEOUS # -#################### - -class FilePathField(SelectField): - "A SelectField whose choices are the files in a given directory." - def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None): - import os - from django.db.models import BLANK_CHOICE_DASH - if match is not None: - import re - match_re = re.compile(match) - choices = not is_required and BLANK_CHOICE_DASH[:] or [] - if recursive: - for root, dirs, files in os.walk(path): - for f in files: - if match is None or match_re.search(f): - choices.append((os.path.join(root, f), f)) - else: - try: - for f in os.listdir(path): - full_file = os.path.join(path, f) - if os.path.isfile(full_file) and (match is None or match_re.search(f)): - choices.append((full_file, f)) - except OSError: - pass - SelectField.__init__(self, field_name, choices, 1, is_required, validator_list) - -class PhoneNumberField(TextField): - "A convenience FormField for validating phone numbers (e.g. '630-555-1234')" - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidPhone] + validator_list - TextField.__init__(self, field_name, length=12, maxlength=12, - is_required=is_required, validator_list=validator_list) - - def isValidPhone(self, field_data, all_data): - try: - validators.isValidPhone(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - -class USStateField(TextField): - "A convenience FormField for validating U.S. states (e.g. 'IL')" - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidUSState] + validator_list - TextField.__init__(self, field_name, length=2, maxlength=2, - is_required=is_required, validator_list=validator_list) - - def isValidUSState(self, field_data, all_data): - try: - validators.isValidUSState(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - return data.upper() # Should always be stored in upper case - html2python = staticmethod(html2python) - -class CommaSeparatedIntegerField(TextField): - "A convenience FormField for validating comma-separated integer fields" - def __init__(self, field_name, maxlength=None, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isCommaSeparatedIntegerList] + validator_list - TextField.__init__(self, field_name, length=20, maxlength=maxlength, - is_required=is_required, validator_list=validator_list) - - def isCommaSeparatedIntegerList(self, field_data, all_data): - try: - validators.isCommaSeparatedIntegerList(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def render(self, data): - if data is None: - data = '' - elif isinstance(data, (list, tuple)): - data = ','.join(data) - return super(CommaSeparatedIntegerField, self).render(data) - -class RawIdAdminField(CommaSeparatedIntegerField): - def html2python(data): - if data: - return data.split(',') - else: - return [] - html2python = staticmethod(html2python) - -class XMLLargeTextField(LargeTextField): - """ - A LargeTextField with an XML validator. The schema_path argument is the - full path to a Relax NG compact schema to validate against. - """ - def __init__(self, field_name, schema_path, **kwargs): - self.schema_path = schema_path - kwargs.setdefault('validator_list', []).insert(0, self.isValidXML) - LargeTextField.__init__(self, field_name, **kwargs) - - def isValidXML(self, field_data, all_data): - v = validators.RelaxNGCompact(self.schema_path) - try: - v(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages +from django.oldforms import * diff --git a/django/newforms/__init__.py b/django/newforms/__init__.py index a445a21bfb..0d9c68f9e0 100644 --- a/django/newforms/__init__.py +++ b/django/newforms/__init__.py @@ -13,5 +13,5 @@ TODO: from util import ValidationError from widgets import * from fields import * -from forms import Form +from forms import * from models import * diff --git a/django/newforms/extras/__init__.py b/django/newforms/extras/__init__.py new file mode 100644 index 0000000000..a7f6a9b3f6 --- /dev/null +++ b/django/newforms/extras/__init__.py @@ -0,0 +1 @@ +from widgets import * diff --git a/django/newforms/extras/widgets.py b/django/newforms/extras/widgets.py new file mode 100644 index 0000000000..1011934fb8 --- /dev/null +++ b/django/newforms/extras/widgets.py @@ -0,0 +1,59 @@ +""" +Extra HTML Widget classes +""" + +from django.newforms.widgets import Widget, Select +from django.utils.dates import MONTHS +import datetime + +__all__ = ('SelectDateWidget',) + +class SelectDateWidget(Widget): + """ + A Widget that splits date input into three . """ - return self.as_widget(HiddenInput(), attrs) + return self.as_widget(self.field.hidden_widget(), attrs) def _data(self): - "Returns the data for this BoundField, or None if it wasn't given." - return self.form.data.get(self.name, None) + """ + Returns the data for this BoundField, or None if it wasn't given. + """ + return self.field.widget.value_from_datadict(self.form.data, self.html_name) data = property(_data) - def _verbose_name(self): - return pretty_name(self.name) - verbose_name = property(_verbose_name) - - def label_tag(self, contents=None): + def label_tag(self, contents=None, attrs=None): """ Wraps the given contents in a