~ubuntu-branches/ubuntu/quantal/python-django/quantal

« back to all changes in this revision

Viewing changes to django/newforms/models.py

  • Committer: Bazaar Package Importer
  • Author(s): Scott James Remnant, Eddy Mulyono
  • Date: 2008-09-16 12:18:47 UTC
  • mfrom: (1.1.5 upstream) (4.1.1 lenny)
  • Revision ID: james.westby@ubuntu.com-20080916121847-mg225rg5mnsdqzr0
Tags: 1.0-1ubuntu1
* Merge from Debian (LP: #264191), remaining changes:
  - Run test suite on build.

[Eddy Mulyono]
* Update patch to workaround network test case failures.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
"""
2
 
Helper functions for creating Form classes from Django models
3
 
and database field objects.
4
 
"""
5
 
 
6
 
from django.utils.translation import gettext
7
 
from util import ValidationError
8
 
from forms import BaseForm, DeclarativeFieldsMetaclass, SortedDictFromList
9
 
from fields import Field, ChoiceField
10
 
from widgets import Select, SelectMultiple, MultipleHiddenInput
11
 
 
12
 
__all__ = ('save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
13
 
           'ModelChoiceField', 'ModelMultipleChoiceField')
14
 
 
15
 
def model_save(self, commit=True):
16
 
    """
17
 
    Creates and returns model instance according to self.clean_data.
18
 
 
19
 
    This method is created for any form_for_model Form.
20
 
    """
21
 
    if self.errors:
22
 
        raise ValueError("The %s could not be created because the data didn't validate." % self._model._meta.object_name)
23
 
    return save_instance(self, self._model(), commit)
24
 
 
25
 
def save_instance(form, instance, commit=True):
26
 
    """
27
 
    Saves bound Form ``form``'s clean_data into model instance ``instance``.
28
 
 
29
 
    Assumes ``form`` has a field for every non-AutoField database field in
30
 
    ``instance``. If commit=True, then the changes to ``instance`` will be
31
 
    saved to the database. Returns ``instance``.
32
 
    """
33
 
    from django.db import models
34
 
    opts = instance.__class__._meta
35
 
    if form.errors:
36
 
        raise ValueError("The %s could not be changed because the data didn't validate." % opts.object_name)
37
 
    clean_data = form.clean_data
38
 
    for f in opts.fields:
39
 
        if not f.editable or isinstance(f, models.AutoField):
40
 
            continue
41
 
        setattr(instance, f.name, clean_data[f.name])
42
 
    if commit:
43
 
        instance.save()
44
 
        for f in opts.many_to_many:
45
 
            setattr(instance, f.attname, clean_data[f.name])
46
 
    # GOTCHA: If many-to-many data is given and commit=False, the many-to-many
47
 
    # data will be lost. This happens because a many-to-many options cannot be
48
 
    # set on an object until after it's saved. Maybe we should raise an
49
 
    # exception in that case.
50
 
    return instance
51
 
 
52
 
def make_instance_save(instance):
53
 
    "Returns the save() method for a form_for_instance Form."
54
 
    def save(self, commit=True):
55
 
        return save_instance(self, instance, commit)
56
 
    return save
57
 
 
58
 
def form_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield()):
59
 
    """
60
 
    Returns a Form class for the given Django model class.
61
 
 
62
 
    Provide ``form`` if you want to use a custom BaseForm subclass.
63
 
 
64
 
    Provide ``formfield_callback`` if you want to define different logic for
65
 
    determining the formfield for a given database field. It's a callable that
66
 
    takes a database Field instance and returns a form Field instance.
67
 
    """
68
 
    opts = model._meta
69
 
    field_list = []
70
 
    for f in opts.fields + opts.many_to_many:
71
 
        if not f.editable:
72
 
            continue
73
 
        formfield = formfield_callback(f)
74
 
        if formfield:
75
 
            field_list.append((f.name, formfield))
76
 
    fields = SortedDictFromList(field_list)
77
 
    return type(opts.object_name + 'Form', (form,), {'base_fields': fields, '_model': model, 'save': model_save})
78
 
 
79
 
def form_for_instance(instance, form=BaseForm, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
80
 
    """
81
 
    Returns a Form class for the given Django model instance.
82
 
 
83
 
    Provide ``form`` if you want to use a custom BaseForm subclass.
84
 
 
85
 
    Provide ``formfield_callback`` if you want to define different logic for
86
 
    determining the formfield for a given database field. It's a callable that
87
 
    takes a database Field instance, plus **kwargs, and returns a form Field
88
 
    instance with the given kwargs (i.e. 'initial').
89
 
    """
90
 
    model = instance.__class__
91
 
    opts = model._meta
92
 
    field_list = []
93
 
    for f in opts.fields + opts.many_to_many:
94
 
        if not f.editable:
95
 
            continue
96
 
        current_value = f.value_from_object(instance)
97
 
        formfield = formfield_callback(f, initial=current_value)
98
 
        if formfield:
99
 
            field_list.append((f.name, formfield))
100
 
    fields = SortedDictFromList(field_list)
101
 
    return type(opts.object_name + 'InstanceForm', (form,),
102
 
        {'base_fields': fields, '_model': model, 'save': make_instance_save(instance)})
103
 
 
104
 
def form_for_fields(field_list):
105
 
    "Returns a Form class for the given list of Django database field instances."
106
 
    fields = SortedDictFromList([(f.name, f.formfield()) for f in field_list if f.editable])
107
 
    return type('FormForFields', (BaseForm,), {'base_fields': fields})
108
 
 
109
 
class QuerySetIterator(object):
110
 
    def __init__(self, queryset, empty_label, cache_choices):
111
 
        self.queryset, self.empty_label, self.cache_choices = queryset, empty_label, cache_choices
112
 
 
113
 
    def __iter__(self):
114
 
        if self.empty_label is not None:
115
 
            yield (u"", self.empty_label)
116
 
        for obj in self.queryset:
117
 
            yield (obj._get_pk_val(), str(obj))
118
 
        # Clear the QuerySet cache if required.
119
 
        if not self.cache_choices:
120
 
            self.queryset._result_cache = None
121
 
 
122
 
class ModelChoiceField(ChoiceField):
123
 
    "A ChoiceField whose choices are a model QuerySet."
124
 
    # This class is a subclass of ChoiceField for purity, but it doesn't
125
 
    # actually use any of ChoiceField's implementation.
126
 
    def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
127
 
            required=True, widget=Select, label=None, initial=None, help_text=None):
128
 
        self.queryset = queryset
129
 
        self.empty_label = empty_label
130
 
        self.cache_choices = cache_choices
131
 
        # Call Field instead of ChoiceField __init__() because we don't need
132
 
        # ChoiceField.__init__().
133
 
        Field.__init__(self, required, widget, label, initial, help_text)
134
 
        self.widget.choices = self.choices
135
 
 
136
 
    def _get_choices(self):
137
 
        # If self._choices is set, then somebody must have manually set
138
 
        # the property self.choices. In this case, just return self._choices.
139
 
        if hasattr(self, '_choices'):
140
 
            return self._choices
141
 
        # Otherwise, execute the QuerySet in self.queryset to determine the
142
 
        # choices dynamically. Return a fresh QuerySetIterator that has not
143
 
        # been consumed. Note that we're instantiating a new QuerySetIterator
144
 
        # *each* time _get_choices() is called (and, thus, each time
145
 
        # self.choices is accessed) so that we can ensure the QuerySet has not
146
 
        # been consumed.
147
 
        return QuerySetIterator(self.queryset, self.empty_label, self.cache_choices)
148
 
 
149
 
    def _set_choices(self, value):
150
 
        # This method is copied from ChoiceField._set_choices(). It's necessary
151
 
        # because property() doesn't allow a subclass to overwrite only
152
 
        # _get_choices without implementing _set_choices.
153
 
        self._choices = self.widget.choices = list(value)
154
 
 
155
 
    choices = property(_get_choices, _set_choices)
156
 
 
157
 
    def clean(self, value):
158
 
        Field.clean(self, value)
159
 
        if value in ('', None):
160
 
            return None
161
 
        try:
162
 
            value = self.queryset.model._default_manager.get(pk=value)
163
 
        except self.queryset.model.DoesNotExist:
164
 
            raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.'))
165
 
        return value
166
 
 
167
 
class ModelMultipleChoiceField(ModelChoiceField):
168
 
    "A MultipleChoiceField whose choices are a model QuerySet."
169
 
    hidden_widget = MultipleHiddenInput
170
 
    def __init__(self, queryset, cache_choices=False, required=True,
171
 
            widget=SelectMultiple, label=None, initial=None, help_text=None):
172
 
        super(ModelMultipleChoiceField, self).__init__(queryset, None, cache_choices,
173
 
            required, widget, label, initial, help_text)
174
 
 
175
 
    def clean(self, value):
176
 
        if self.required and not value:
177
 
            raise ValidationError(gettext(u'This field is required.'))
178
 
        elif not self.required and not value:
179
 
            return []
180
 
        if not isinstance(value, (list, tuple)):
181
 
            raise ValidationError(gettext(u'Enter a list of values.'))
182
 
        final_values = []
183
 
        for val in value:
184
 
            try:
185
 
                obj = self.queryset.model._default_manager.get(pk=val)
186
 
            except self.queryset.model.DoesNotExist:
187
 
                raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val)
188
 
            else:
189
 
                final_values.append(obj)
190
 
        return final_values