Django

Code

Changeset 4552

Show
Ignore:
Timestamp:
02/20/07 23:14:28 (2 years ago)
Author:
adrian
Message:

Fixed #3534 -- newforms ModelChoiceField? and ModelMultipleChoiceField? no longer cache choices. Instead, they calculate choices via a fresh database query each time the widget is rendered and clean() is called

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/newforms/models.py

    r4548 r4552  
    44""" 
    55 
     6from django.utils.translation import gettext 
     7from util import ValidationError 
    68from forms import BaseForm, DeclarativeFieldsMetaclass, SortedDictFromList 
    7 from fields import ChoiceField, MultipleChoiceField 
    8 from widgets import Select, SelectMultiple 
     9from fields import Field, ChoiceField 
     10from widgets import Select, SelectMultiple, MultipleHiddenInput 
    911 
    1012__all__ = ('save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields', 
     
    105107    return type('FormForFields', (BaseForm,), {'base_fields': fields}) 
    106108 
     109class 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 
    107122class ModelChoiceField(ChoiceField): 
    108123    "A ChoiceField whose choices are a model QuerySet." 
    109     def __init__(self, queryset, empty_label=u"---------", **kwargs): 
    110         self.model = queryset.model 
    111         choices = [(obj._get_pk_val(), str(obj)) for obj in queryset] 
    112         if empty_label is not None: 
    113             choices = [(u"", empty_label)] + choices 
    114         ChoiceField.__init__(self, choices=choices, **kwargs) 
     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. 
     143        return QuerySetIterator(self.queryset, self.empty_label, self.cache_choices) 
     144 
     145    def _set_choices(self, value): 
     146        # This method is copied from ChoiceField._set_choices(). It's necessary 
     147        # because property() doesn't allow a subclass to overwrite only 
     148        # _get_choices without implementing _set_choices. 
     149        self._choices = self.widget.choices = list(value) 
     150 
     151    choices = property(_get_choices, _set_choices) 
    115152 
    116153    def clean(self, value): 
    117         value = ChoiceField.clean(self, value) 
    118         if not value
     154        Field.clean(self, value) 
     155        if value in ('', None)
    119156            return None 
    120157        try: 
    121             value = self.model._default_manager.get(pk=value) 
    122         except self.model.DoesNotExist: 
     158            value = self.queryset.model._default_manager.get(pk=value) 
     159        except self.queryset.model.DoesNotExist: 
    123160            raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.')) 
    124161        return value 
    125162 
    126 class ModelMultipleChoiceField(MultipleChoiceField): 
     163class ModelMultipleChoiceField(ModelChoiceField): 
    127164    "A MultipleChoiceField whose choices are a model QuerySet." 
    128     def __init__(self, queryset, **kwargs): 
    129         self.model = queryset.model 
    130         MultipleChoiceField.__init__(self, choices=[(obj._get_pk_val(), str(obj)) for obj in queryset], **kwargs) 
     165    hidden_widget = MultipleHiddenInput 
     166    def __init__(self, queryset, cache_choices=False, required=True, 
     167            widget=SelectMultiple, label=None, initial=None, help_text=None): 
     168        super(ModelMultipleChoiceField, self).__init__(queryset, None, cache_choices, 
     169            required, widget, label, initial, help_text) 
    131170 
    132171    def clean(self, value): 
    133         value = MultipleChoiceField.clean(self, value) 
    134         if not value: 
     172        if self.required and not value: 
     173            raise ValidationError(gettext(u'This field is required.')) 
     174        elif not self.required and not value: 
    135175            return [] 
    136         return self.model._default_manager.filter(pk__in=value) 
     176        if not isinstance(value, (list, tuple)): 
     177            raise ValidationError(gettext(u'Enter a list of values.')) 
     178        final_values = [] 
     179        for val in value: 
     180            try: 
     181                obj = self.queryset.model._default_manager.get(pk=val) 
     182            except self.queryset.model.DoesNotExist: 
     183                raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val) 
     184            else: 
     185                final_values.append(obj) 
     186        return final_values 
  • django/trunk/tests/modeltests/model_forms/models.py

    r4548 r4552  
    290290<Category: Third> 
    291291 
     292Here, we demonstrate that choices for a ForeignKey ChoiceField are determined 
     293at runtime, based on the data in the database when the form is displayed, not 
     294the data in the database when the form is instantiated. 
     295>>> ArticleForm = form_for_model(Article) 
     296>>> f = ArticleForm(auto_id=False) 
     297>>> print f.as_ul() 
     298<li>Headline: <input type="text" name="headline" maxlength="50" /></li> 
     299<li>Pub date: <input type="text" name="pub_date" /></li> 
     300<li>Writer: <select name="writer"> 
     301<option value="" selected="selected">---------</option> 
     302<option value="1">Mike Royko</option> 
     303<option value="2">Bob Woodward</option> 
     304</select></li> 
     305<li>Article: <textarea name="article"></textarea></li> 
     306<li>Categories: <select multiple="multiple" name="categories"> 
     307<option value="1">Entertainment</option> 
     308<option value="2">It&#39;s a test</option> 
     309<option value="3">Third</option> 
     310</select>  Hold down "Control", or "Command" on a Mac, to select more than one.</li> 
     311>>> Category.objects.create(name='Fourth', url='4th') 
     312<Category: Fourth> 
     313>>> Writer.objects.create(name='Carl Bernstein') 
     314<Writer: Carl Bernstein> 
     315>>> print f.as_ul() 
     316<li>Headline: <input type="text" name="headline" maxlength="50" /></li> 
     317<li>Pub date: <input type="text" name="pub_date" /></li> 
     318<li>Writer: <select name="writer"> 
     319<option value="" selected="selected">---------</option> 
     320<option value="1">Mike Royko</option> 
     321<option value="2">Bob Woodward</option> 
     322<option value="3">Carl Bernstein</option> 
     323</select></li> 
     324<li>Article: <textarea name="article"></textarea></li> 
     325<li>Categories: <select multiple="multiple" name="categories"> 
     326<option value="1">Entertainment</option> 
     327<option value="2">It&#39;s a test</option> 
     328<option value="3">Third</option> 
     329<option value="4">Fourth</option> 
     330</select>  Hold down "Control", or "Command" on a Mac, to select more than one.</li> 
     331 
    292332# ModelChoiceField ############################################################ 
    293333 
     
    311351>>> f.clean(2) 
    312352<Category: It's a test> 
     353 
     354# Add a Category object *after* the ModelChoiceField has already been 
     355# instantiated. This proves clean() checks the database during clean() rather 
     356# than caching it at time of instantiation. 
     357>>> Category.objects.create(name='Fifth', url='5th') 
     358<Category: Fifth> 
     359>>> f.clean(5) 
     360<Category: Fifth> 
     361 
     362# Delete a Category object *after* the ModelChoiceField has already been 
     363# instantiated. This proves clean() checks the database during clean() rather 
     364# than caching it at time of instantiation. 
     365>>> Category.objects.get(url='5th').delete() 
     366>>> f.clean(5) 
     367Traceback (most recent call last): 
     368... 
     369ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] 
    313370 
    314371>>> f = ModelChoiceField(Category.objects.filter(pk=1), required=False) 
     
    318375>>> f.clean('1') 
    319376<Category: Entertainment> 
    320 >>> f.clean('2') 
     377>>> f.clean('100') 
    321378Traceback (most recent call last): 
    322379... 
     
    346403>>> f.clean((1, '2')) 
    347404[<Category: Entertainment>, <Category: It's a test>] 
    348 >>> f.clean(['nonexistent']) 
    349 Traceback (most recent call last): 
    350 ... 
    351 ValidationError: [u'Select a valid choice. nonexistent is not one of the available choices.'] 
     405>>> f.clean(['100']) 
     406Traceback (most recent call last): 
     407... 
     408ValidationError: [u'Select a valid choice. 100 is not one of the available choices.'] 
    352409>>> f.clean('hello') 
    353410Traceback (most recent call last): 
    354411... 
    355412ValidationError: [u'Enter a list of values.'] 
     413 
     414# Add a Category object *after* the ModelChoiceField has already been 
     415# instantiated. This proves clean() checks the database during clean() rather 
     416# than caching it at time of instantiation. 
     417>>> Category.objects.create(id=6, name='Sixth', url='6th') 
     418<Category: Sixth> 
     419>>> f.clean([6]) 
     420[<Category: Sixth>] 
     421 
     422# Delete a Category object *after* the ModelChoiceField has already been 
     423# instantiated. This proves clean() checks the database during clean() rather 
     424# than caching it at time of instantiation. 
     425>>> Category.objects.get(url='6th').delete() 
     426>>> f.clean([6]) 
     427Traceback (most recent call last): 
     428... 
     429ValidationError: [u'Select a valid choice. 6 is not one of the available choices.'] 
     430 
    356431>>> f = ModelMultipleChoiceField(Category.objects.all(), required=False) 
    357432>>> f.clean([]) 
     
    359434>>> f.clean(()) 
    360435[] 
    361 >>> f.clean(['4']) 
    362 Traceback (most recent call last): 
    363 ... 
    364 ValidationError: [u'Select a valid choice. 4 is not one of the available choices.'] 
    365 >>> f.clean(['3', '4']) 
    366 Traceback (most recent call last): 
    367 ... 
    368 ValidationError: [u'Select a valid choice. 4 is not one of the available choices.'] 
    369 >>> f.clean(['1', '5']) 
    370 Traceback (most recent call last): 
    371 ... 
    372 ValidationError: [u'Select a valid choice. 5 is not one of the available choices.'] 
     436>>> f.clean(['10']) 
     437Traceback (most recent call last): 
     438... 
     439ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] 
     440>>> f.clean(['3', '10']) 
     441Traceback (most recent call last): 
     442... 
     443ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] 
     444>>> f.clean(['1', '10']) 
     445Traceback (most recent call last): 
     446... 
     447ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] 
    373448"""}