Django

Code

Changeset 9297

Show
Ignore:
Timestamp:
10/31/08 17:07:05 (2 months ago)
Author:
brosner
Message:

Fixed #8882 -- When a foreign key is among the unique_together fields in an inline formset properly handle it.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/contrib/admin/helpers.py

    r9243 r9297  
    110110    def fields(self): 
    111111        for field_name in flatten_fieldsets(self.fieldsets): 
     112            if self.formset.fk.name == field_name: 
     113                continue 
    112114            yield self.formset.form.base_fields[field_name] 
    113115 
     
    131133        super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields) 
    132134     
     135    def __iter__(self): 
     136        for name, options in self.fieldsets: 
     137            yield InlineFieldset(self.formset, self.form, name, **options) 
     138     
    133139    def field_count(self): 
    134140        # tabular.html uses this function for colspan value. 
     
    143149    def pk_field(self): 
    144150        return AdminField(self.form, self.formset._pk_field.name, False) 
     151     
     152    def fk_field(self): 
     153        return AdminField(self.form, self.formset.fk.name, False) 
    145154 
    146155    def deletion_field(self): 
     
    152161        return AdminField(self.form, ORDERING_FIELD_NAME, False) 
    153162 
     163class InlineFieldset(Fieldset): 
     164    def __init__(self, formset, *args, **kwargs): 
     165        self.formset = formset 
     166        super(InlineFieldset, self).__init__(*args, **kwargs) 
     167         
     168    def __iter__(self): 
     169        for field in self.fields: 
     170            if self.formset.fk.name == field: 
     171                continue 
     172            yield Fieldline(self.form, field) 
     173             
    154174class AdminErrorList(forms.util.ErrorList): 
    155175    """ 
  • django/trunk/django/contrib/admin/templates/admin/edit_inline/stacked.html

    r9243 r9297  
    1919  {% endfor %} 
    2020  {{ inline_admin_form.pk_field.field }} 
     21  {{ inline_admin_form.fk_field.field }} 
    2122</div> 
    2223{% endfor %} 
  • django/trunk/django/contrib/admin/templates/admin/edit_inline/tabular.html

    r9243 r9297  
    2727          {% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original.content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %} 
    2828            </p>{% endif %} 
    29           {{ inline_admin_form.pk_field.field }} 
     29          {{ inline_admin_form.pk_field.field }} {{ inline_admin_form.fk_field.field }} 
    3030          {% spaceless %} 
    3131          {% for fieldset in inline_admin_form %} 
  • django/trunk/django/forms/models.py

    r9293 r9297  
    44""" 
    55 
    6 from django.utils.encoding import smart_unicode 
     6from django.utils.encoding import smart_unicode, force_unicode 
    77from django.utils.datastructures import SortedDict 
    88from django.utils.text import get_text_list, capfirst 
     
    469469            form.data[form.add_prefix(self._pk_field.name)] = None 
    470470        return form 
    471  
     471     
    472472    def get_queryset(self): 
    473473        """ 
     
    486486        super(BaseInlineFormSet, self).add_fields(form, index) 
    487487        if self._pk_field == self.fk: 
    488             form.fields[self._pk_field.name] = IntegerField(required=False, widget=HiddenInput) 
     488            form.fields[self._pk_field.name] = InlineForeignKeyField(self.instance, pk_field=True) 
     489        else: 
     490            form.fields[self.fk.name] = InlineForeignKeyField(self.instance, label=form.fields[self.fk.name].label) 
    489491 
    490492def _get_foreign_key(parent_model, model, fk_name=None): 
     
    538540    if fk.unique: 
    539541        max_num = 1 
    540     if exclude is not None: 
    541         exclude = list(exclude) 
    542         exclude.append(fk.name) 
    543     else: 
    544         exclude = [fk.name] 
    545542    kwargs = { 
    546543        'form': form, 
     
    560557 
    561558# Fields ##################################################################### 
     559 
     560class InlineForeignKeyHiddenInput(HiddenInput): 
     561    def _has_changed(self, initial, data): 
     562        return False 
     563 
     564class InlineForeignKeyField(Field): 
     565    """ 
     566    A basic integer field that deals with validating the given value to a 
     567    given parent instance in an inline. 
     568    """ 
     569    default_error_messages = { 
     570        'invalid_choice': _(u'The inline foreign key did not match the parent instance primary key.'), 
     571    } 
     572     
     573    def __init__(self, parent_instance, *args, **kwargs): 
     574        self.parent_instance = parent_instance 
     575        self.pk_field = kwargs.pop("pk_field", False) 
     576        if self.parent_instance is not None: 
     577            kwargs["initial"] = self.parent_instance.pk 
     578        kwargs["required"] = False 
     579        kwargs["widget"] = InlineForeignKeyHiddenInput 
     580        super(InlineForeignKeyField, self).__init__(*args, **kwargs) 
     581     
     582    def clean(self, value): 
     583        if value in EMPTY_VALUES: 
     584            if self.pk_field: 
     585                return None 
     586            # if there is no value act as we did before. 
     587            return self.parent_instance 
     588        # ensure the we compare the values as equal types. 
     589        if force_unicode(value) != force_unicode(self.parent_instance.pk): 
     590            raise ValidationError(self.error_messages['invalid_choice']) 
     591        if self.pk_field: 
     592            return self.parent_instance.pk 
     593        return self.parent_instance 
    562594 
    563595class ModelChoiceIterator(object): 
  • django/trunk/tests/modeltests/model_formsets/models.py

    r9293 r9297  
    9696class MexicanRestaurant(Restaurant): 
    9797    serves_tacos = models.BooleanField() 
     98 
     99# models for testing unique_together validation when a fk is involved and 
     100# using inlineformset_factory. 
     101class Repository(models.Model): 
     102    name = models.CharField(max_length=25) 
     103     
     104    def __unicode__(self): 
     105        return self.name 
     106 
     107class Revision(models.Model): 
     108    repository = models.ForeignKey(Repository) 
     109    revision = models.CharField(max_length=40) 
     110     
     111    class Meta: 
     112        unique_together = (("repository", "revision"),) 
     113     
     114    def __unicode__(self): 
     115        return u"%s (%s)" % (self.revision, unicode(self.repository)) 
    98116 
    99117# models for testing callable defaults (see bug #7975). If you define a model 
     
    376394>>> for form in formset.forms: 
    377395...     print form.as_p() 
    378 <p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" maxlength="100" /><input type="hidden" name="book_set-0-id" id="id_book_set-0-id" /></p> 
    379 <p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p> 
    380 <p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p> 
     396<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" maxlength="100" /><input type="hidden" name="book_set-0-author" value="1" id="id_book_set-0-author" /><input type="hidden" name="book_set-0-id" id="id_book_set-0-id" /></p> 
     397<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-author" value="1" id="id_book_set-1-author" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p> 
     398<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-author" value="1" id="id_book_set-2-author" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p> 
    381399 
    382400>>> data = { 
     
    410428>>> for form in formset.forms: 
    411429...     print form.as_p() 
    412 <p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Les Fleurs du Mal" maxlength="100" /><input type="hidden" name="book_set-0-id" value="1" id="id_book_set-0-id" /></p> 
    413 <p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p> 
    414 <p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p> 
     430<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Les Fleurs du Mal" maxlength="100" /><input type="hidden" name="book_set-0-author" value="1" id="id_book_set-0-author" /><input type="hidden" name="book_set-0-id" value="1" id="id_book_set-0-id" /></p> 
     431<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-author" value="1" id="id_book_set-1-author" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p> 
     432<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-author" value="1" id="id_book_set-2-author" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p> 
    415433 
    416434>>> data = { 
     
    455473 
    456474>>> new_author = Author.objects.create(name='Charles Baudelaire') 
    457 >>> formset.instance = new_author 
     475>>> formset = AuthorBooksFormSet(data, instance=new_author, save_as_new=True) 
    458476>>> [book for book in formset.save() if book.author.pk == new_author.pk] 
    459477[<Book: Les Fleurs du Mal>, <Book: Le Spleen de Paris>] 
     
    464482>>> for form in formset.forms: 
    465483...     print form.as_p() 
    466 <p><label for="id_test-0-title">Title:</label> <input id="id_test-0-title" type="text" name="test-0-title" maxlength="100" /><input type="hidden" name="test-0-id" id="id_test-0-id" /></p> 
    467 <p><label for="id_test-1-title">Title:</label> <input id="id_test-1-title" type="text" name="test-1-title" maxlength="100" /><input type="hidden" name="test-1-id" id="id_test-1-id" /></p> 
     484<p><label for="id_test-0-title">Title:</label> <input id="id_test-0-title" type="text" name="test-0-title" maxlength="100" /><input type="hidden" name="test-0-author" id="id_test-0-author" /><input type="hidden" name="test-0-id" id="id_test-0-id" /></p> 
     485<p><label for="id_test-1-title">Title:</label> <input id="id_test-1-title" type="text" name="test-1-title" maxlength="100" /><input type="hidden" name="test-1-author" id="id_test-1-author" /><input type="hidden" name="test-1-id" id="id_test-1-id" /></p> 
    468486 
    469487# Test a custom primary key ################################################### 
     
    487505>>> for form in formset.forms: 
    488506...     print form.as_p() 
    489 <p><label for="id_owner_set-0-name">Name:</label> <input id="id_owner_set-0-name" type="text" name="owner_set-0-name" maxlength="100" /><input type="hidden" name="owner_set-0-auto_id" id="id_owner_set-0-auto_id" /></p> 
    490 <p><label for="id_owner_set-1-name">Name:</label> <input id="id_owner_set-1-name" type="text" name="owner_set-1-name" maxlength="100" /><input type="hidden" name="owner_set-1-auto_id" id="id_owner_set-1-auto_id" /></p> 
     507<p><label for="id_owner_set-0-name">Name:</label> <input id="id_owner_set-0-name" type="text" name="owner_set-0-name" maxlength="100" /><input type="hidden" name="owner_set-0-place" value="1" id="id_owner_set-0-place" /><input type="hidden" name="owner_set-0-auto_id" id="id_owner_set-0-auto_id" /></p> 
     508<p><label for="id_owner_set-1-name">Name:</label> <input id="id_owner_set-1-name" type="text" name="owner_set-1-name" maxlength="100" /><input type="hidden" name="owner_set-1-place" value="1" id="id_owner_set-1-place" /><input type="hidden" name="owner_set-1-auto_id" id="id_owner_set-1-auto_id" /></p> 
    491509 
    492510>>> data = { 
     
    507525>>> for form in formset.forms: 
    508526...     print form.as_p() 
    509 <p><label for="id_owner_set-0-name">Name:</label> <input id="id_owner_set-0-name" type="text" name="owner_set-0-name" value="Joe Perry" maxlength="100" /><input type="hidden" name="owner_set-0-auto_id" value="1" id="id_owner_set-0-auto_id" /></p> 
    510 <p><label for="id_owner_set-1-name">Name:</label> <input id="id_owner_set-1-name" type="text" name="owner_set-1-name" maxlength="100" /><input type="hidden" name="owner_set-1-auto_id" id="id_owner_set-1-auto_id" /></p> 
    511 <p><label for="id_owner_set-2-name">Name:</label> <input id="id_owner_set-2-name" type="text" name="owner_set-2-name" maxlength="100" /><input type="hidden" name="owner_set-2-auto_id" id="id_owner_set-2-auto_id" /></p> 
     527<p><label for="id_owner_set-0-name">Name:</label> <input id="id_owner_set-0-name" type="text" name="owner_set-0-name" value="Joe Perry" maxlength="100" /><input type="hidden" name="owner_set-0-place" value="1" id="id_owner_set-0-place" /><input type="hidden" name="owner_set-0-auto_id" value="1" id="id_owner_set-0-auto_id" /></p> 
     528<p><label for="id_owner_set-1-name">Name:</label> <input id="id_owner_set-1-name" type="text" name="owner_set-1-name" maxlength="100" /><input type="hidden" name="owner_set-1-place" value="1" id="id_owner_set-1-place" /><input type="hidden" name="owner_set-1-auto_id" id="id_owner_set-1-auto_id" /></p> 
     529<p><label for="id_owner_set-2-name">Name:</label> <input id="id_owner_set-2-name" type="text" name="owner_set-2-name" maxlength="100" /><input type="hidden" name="owner_set-2-place" value="1" id="id_owner_set-2-place" /><input type="hidden" name="owner_set-2-auto_id" id="id_owner_set-2-auto_id" /></p> 
    512530 
    513531>>> data = { 
     
    546564>>> for form in formset.forms: 
    547565...     print form.as_p() 
    548 <p><label for="id_ownerprofile-0-age">Age:</label> <input type="text" name="ownerprofile-0-age" id="id_ownerprofile-0-age" /><input type="hidden" name="ownerprofile-0-owner" id="id_ownerprofile-0-owner" /></p> 
     566<p><label for="id_ownerprofile-0-age">Age:</label> <input type="text" name="ownerprofile-0-age" id="id_ownerprofile-0-age" /><input type="hidden" name="ownerprofile-0-owner" value="1" id="id_ownerprofile-0-owner" /></p> 
    549567 
    550568>>> data = { 
     
    584602...     print form.as_p() 
    585603<p><label for="id_location_set-0-lat">Lat:</label> <input id="id_location_set-0-lat" type="text" name="location_set-0-lat" maxlength="100" /></p> 
    586 <p><label for="id_location_set-0-lon">Lon:</label> <input id="id_location_set-0-lon" type="text" name="location_set-0-lon" maxlength="100" /><input type="hidden" name="location_set-0-id" id="id_location_set-0-id" /></p> 
     604<p><label for="id_location_set-0-lon">Lon:</label> <input id="id_location_set-0-lon" type="text" name="location_set-0-lon" maxlength="100" /><input type="hidden" name="location_set-0-place" value="1" id="id_location_set-0-place" /><input type="hidden" name="location_set-0-id" id="id_location_set-0-id" /></p> 
    587605 
    588606# Foreign keys in parents ######################################## 
     
    647665[{'__all__': [u'Price with this Price and Quantity already exists.']}] 
    648666 
     667# unique_together with inlineformset_factory 
     668# Also see bug #8882. 
     669 
     670>>> repository = Repository.objects.create(name=u'Test Repo') 
     671>>> FormSet = inlineformset_factory(Repository, Revision, extra=1) 
     672>>> data = { 
     673...     'revision_set-TOTAL_FORMS': '1', 
     674...     'revision_set-INITIAL_FORMS': '0', 
     675...     'revision_set-0-repository': repository.pk, 
     676...     'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76', 
     677...     'revision_set-0-DELETE': '', 
     678... } 
     679>>> formset = FormSet(data, instance=repository) 
     680>>> formset.is_valid() 
     681True 
     682>>> formset.save() 
     683[<Revision: 146239817507f148d448db38840db7c3cbf47c76 (Test Repo)>] 
     684 
     685# attempt to save the same revision against against the same repo. 
     686>>> data = { 
     687...     'revision_set-TOTAL_FORMS': '1', 
     688...     'revision_set-INITIAL_FORMS': '0', 
     689...     'revision_set-0-repository': repository.pk, 
     690...     'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76', 
     691...     'revision_set-0-DELETE': '', 
     692... } 
     693>>> formset = FormSet(data, instance=repository) 
     694>>> formset.is_valid() 
     695False 
     696>>> formset.errors 
     697[{'__all__': [u'Revision with this Repository and Revision already exists.']}] 
     698 
    649699# Use of callable defaults (see bug #7975). 
    650700 
     
    661711>>> print form.as_p() 
    662712<p><label for="id_membership_set-0-date_joined">Date joined:</label> <input type="text" name="membership_set-0-date_joined" value="..." id="id_membership_set-0-date_joined" /><input type="hidden" name="initial-membership_set-0-date_joined" value="..." id="id_membership_set-0-date_joined" /></p> 
    663 <p><label for="id_membership_set-0-karma">Karma:</label> <input type="text" name="membership_set-0-karma" id="id_membership_set-0-karma" /><input type="hidden" name="membership_set-0-id" id="id_membership_set-0-id" /></p> 
     713<p><label for="id_membership_set-0-karma">Karma:</label> <input type="text" name="membership_set-0-karma" id="id_membership_set-0-karma" /><input type="hidden" name="membership_set-0-person" value="1" id="id_membership_set-0-person" /><input type="hidden" name="membership_set-0-id" id="id_membership_set-0-id" /></p> 
    664714 
    665715# test for validation with callable defaults. Validations rely on hidden fields