I'm reporting this to the developers list as I feel this shows a
shortfall in (to me) expected behavior. I'm not sure this is exactly a
bug or simply a use case the unique validation wasn't designed for.

Basically, sometimes I want to create model formsets that use a
limited number of model fields. These model fields may have a unique
constraint using unique_together. The other field (or fields) of
unique together may not be included in the formset. Upon validating
the form, unique_together fields are only checked if all fields are
contained in the form. This means, my unique fields will not be
validated automatically. To achieve the validation I usually have to
copy parts of Django form validation, missing out on DRY.

I think one solution would be for model formsets to have a "static" or
"constant" fields argument. This could be a single value per field
that all forms in the formset would share for a particular set of
fields. These fields would not be a part of the rendered form, but
could be used for validating the forms and creating new models
instances. Thoughts?

An example where I might do this: suppose I have a "container" model.
This model has many "item" models created through a FK relationship.
Perhaps a field is unique together with the container. This example
could apply to any container/item relationship. I might create a
formset for all items of just one container for a bulk (unique)
rename. I have created a simple small example that illustrates my
point:

models.py

----
class Container(models.Model):
    pass

class Item(models.Model):
    container = models.ForeignKey(Container)
    name = models.CharField(max_length=100)

    class Meta:
        unique_together = ('container', 'name')

ItemFormSet = modelformset_factory(model=Item, fields=['name'])
----

tests.py
----
class ItemFormSetTestCase(TestCase):
    def test_unique_item_name(self):
        container = Container()
        container.save()
        item1 = Item(container=container, name='item1')
        item1.save()
        item2 = Item(container=container, name='item2')
        item2.save()
        data = {
            'form-TOTAL_FORMS': 2,
            'form-INITIAL_FORMS': 2,
            'form-MAX_NUM_FORMS': 2,
            'form-0-id': str(item1.pk),
            'form-0-name': 'newname',
            'form-1-id': str(item2.pk),
            'form-1-name': 'newname',
        }
        formset = ItemFormSet(
            data,
            queryset=Item.objects.filter(container=container))
        self.assertFalse(formset.is_valid())
---

This test fails because the uniqueness of name is not actually
validated. If I were to go ahead an save this "valid" form, I receive
the following error:

---
Traceback (most recent call last):
  File "/home/jon/djtest/djtest/myapp/tests.py", line 27, in
test_unique_item_name
    formset.save()
  File 
"/home/jon/djtest/venv/lib/python2.7/site-packages/django/forms/models.py",
line 636, in save
    return self.save_existing_objects(commit) + self.save_new_objects(commit)
  File 
"/home/jon/djtest/venv/lib/python2.7/site-packages/django/forms/models.py",
line 753, in save_existing_objects
    saved_instances.append(self.save_existing(form, obj, commit=commit))
  File 
"/home/jon/djtest/venv/lib/python2.7/site-packages/django/forms/models.py",
line 623, in save_existing
    return form.save(commit=commit)
  File 
"/home/jon/djtest/venv/lib/python2.7/site-packages/django/forms/models.py",
line 457, in save
    construct=False)
  File 
"/home/jon/djtest/venv/lib/python2.7/site-packages/django/forms/models.py",
line 103, in save_instance
    instance.save()
  File 
"/home/jon/djtest/venv/lib/python2.7/site-packages/django/db/models/base.py",
line 590, in save
    force_update=force_update, update_fields=update_fields)
  File 
"/home/jon/djtest/venv/lib/python2.7/site-packages/django/db/models/base.py",
line 618, in save_base
    updated = self._save_table(raw, cls, force_insert, force_update,
using, update_fields)
  File 
"/home/jon/djtest/venv/lib/python2.7/site-packages/django/db/models/base.py",
line 680, in _save_table
    forced_update)
  File 
"/home/jon/djtest/venv/lib/python2.7/site-packages/django/db/models/base.py",
line 724, in _do_update
    return filtered._update(values) > 0
  File 
"/home/jon/djtest/venv/lib/python2.7/site-packages/django/db/models/query.py",
line 598, in _update
    return query.get_compiler(self.db).execute_sql(CURSOR)
  File 
"/home/jon/djtest/venv/lib/python2.7/site-packages/django/db/models/sql/compiler.py",
line 1003, in execute_sql
    cursor = super(SQLUpdateCompiler, self).execute_sql(result_type)
  File 
"/home/jon/djtest/venv/lib/python2.7/site-packages/django/db/models/sql/compiler.py",
line 785, in execute_sql
    cursor.execute(sql, params)
  File 
"/home/jon/djtest/venv/lib/python2.7/site-packages/django/db/backends/utils.py",
line 65, in execute
    return self.cursor.execute(sql, params)
  File "/home/jon/djtest/venv/lib/python2.7/site-packages/django/db/utils.py",
line 94, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File 
"/home/jon/djtest/venv/lib/python2.7/site-packages/django/db/backends/utils.py",
line 65, in execute
    return self.cursor.execute(sql, params)
  File 
"/home/jon/djtest/venv/lib/python2.7/site-packages/django/db/backends/sqlite3/base.py",
line 485, in execute
    return Database.Cursor.execute(self, query, params)
IntegrityError: UNIQUE constraint failed: myapp_item.container_id,
myapp_item.name
---

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-developers+unsubscr...@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
Visit this group at http://groups.google.com/group/django-developers.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-developers/CADhq2b585%2Bzfm2XKkYYP6Ji%2B2A7BMHWRywHQ9nyOHOA53MabnA%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to