Opened 14 years ago
Closed 13 years ago
#17615 closed Bug (fixed)
Unique field validation with multi-table inheritance
| Reported by: | David Bennett | Owned by: | nobody |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | 1.5 |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Ready for checkin | |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | yes |
| Easy pickings: | yes | UI/UX: | no |
Description
I've encountered an issue where if you have a Parent class with a unique field and a Child class that specifies its own primary_key, the unique field will fail validation when using a Child ModelForm.
I'm attaching a patch that includes a test and fix.
Attachments (2)
Change History (10)
by , 14 years ago
| Attachment: | unique_field_with_mti.diff added |
|---|
comment:1 by , 14 years ago
| Triage Stage: | Unreviewed → Accepted |
|---|
Confirmed and attached is a cleaned up version of the patch. I think this is ready for commit, but I am not going to tick that box as I have modified the patch.
comment:4 by , 13 years ago
| Patch needs improvement: | set |
|---|---|
| Resolution: | fixed |
| Status: | closed → new |
| Version: | 1.3 → 1.5 |
Got an error:
AttributeError: 'NoneType' object has no attribute 'attname'
Because a model can be 'abstract = True' doesn't have a pk, but a form (also 'abstract = True') can be used for validation 'is_valid()'. I use it for Ajax validation of a single item in a form with multiple items.
row 823-825:
# model_class_pk = self._get_pk_val(model_class._meta)
# if not self._state.adding and model_class_pk is not None:
# qs = qs.exclude(pk=model_class_pk)
comment:5 by , 13 years ago
| Resolution: | → fixed |
|---|---|
| Status: | new → closed |
Please open a new ticket with a full and understandable description of what you are reporting. Thanks.
follow-up: 8 comment:6 by , 13 years ago
| Resolution: | fixed |
|---|---|
| Status: | closed → new |
I'm sorry didn't find the time yesterday to create a complete example, but here it is:
a table with some values:
CREATE TABLE test_contact ( `id` int(11) NOT NULL auto_increment, `subject` varchar(64) NOT NULL, `email` varchar(64) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `subject` (`subject`) ) ENGINE=InnoDB; INSERT INTO test_contact VALUES (1,'aaa','aaa@test.com'); INSERT INTO test_contact VALUES (2,'bbb','bbb@test.com'); INSERT INTO test_contact VALUES (3,'ccc','ccc@test.com');
python manage.py shell
import django
django.get_version()
'1.5'
from django.db import models
class ContactSubjectManager(models.Manager):
def get_query_set(self):
return Contact.objects.values('id','subject').all()
class ContactSubject(models.Model):
subject = models.CharField(max_length=64, unique=True)
objects = ContactSubjectManager()
class Meta:
abstract = True
app_label = 'test'
class ContactEmailManager(models.Manager):
def get_query_set(self):
return Contact.objects.values('id','email').all()
class ContactEmail(models.Model):
email = models.CharField(max_length=64)
objects = ContactEmailManager()
class Meta:
abstract = True
app_label = 'test'
class Contact(ContactSubject,ContactEmail):
objects = models.Manager()
class Meta:
app_label = 'test'
def __unicode__(self):
return u'%s - %s' % (self.subject, self.email)
from django import forms
class ContactSubjectForm(forms.ModelForm):
subject = forms.RegexField(
regex = r'^[ 0-9a-zA-Z()-]+$',
max_length = 30,
min_length = 3,
error_messages = {'invalid': u'Please enter a valid subject.'})
class Meta:
abstract = True
model = ContactSubject
class ContactEmailForm(forms.ModelForm):
email = forms.EmailField(
error_messages = {'invalid': u'Please enter a valid email address.'})
class Meta:
abstract = True
model = ContactEmail
class ContactForm(ContactSubjectForm,ContactEmailForm):
class Meta:
model = Contact
f = Contact.objects.all()
f
[<Contact: aaa - aaa@test.com>, <Contact: bbb - bbb@test.com>, <Contact: ccc - ccc@test.com>]
data = { 'subject': 'aaa' , 'email' : 'aaa@test.com' }
f = ContactForm(data)
f.is_valid()
False
f.errors
{'subject': [u'Contact with this Subject already exists.']}
data = { 'subject': 'aaa'}
f = ContactSubjectForm(data)
f.is_valid()
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "…/lib/python2.7/site-packages/django/forms/forms.py", line 126, in is_valid
return self.is_bound and not bool(self.errors)
File "…/lib/python2.7/site-packages/django/forms/forms.py", line 117, in _get_errors
self.full_clean()
File "…l/lib/python2.7/site-packages/django/forms/forms.py", line 274, in full_clean
self._post_clean()
File "…/lib/python2.7/site-packages/django/forms/models.py", line 344, in _post_clean
self.validate_unique()
File "…/lib/python2.7/site-packages/django/forms/models.py", line 353, in validate_unique
self.instance.validate_unique(exclude=exclude)
File "…/lib/python2.7/site-packages/django/db/models/base.py", line 731, in validate_unique
errors = self._perform_unique_checks(unique_checks)
File "…/lib/python2.7/site-packages/django/db/models/base.py", line 823, in _perform_unique_checks
model_class_pk = self._get_pk_val(model_class._meta)
File "…/lib/python2.7/site-packages/django/db/models/base.py", line 466, in _get_pk_val
return getattr(self, meta.pk.attname)
AttributeError: 'NoneType' object has no attribute 'attname'
The output should be:
f.is_valid()
False
f.errors
{'subject': [u'Contact subject with this Subject already exists.']}
comment:7 by , 13 years ago
Solution:
In function _perform_unique_checks "lib/python2.7/site-packages/django/db/models/base.py" remove comment on lines 817-822
# Exclude the current object from the query if we are editing an
# instance (as opposed to creating a new one)
# Note that we need to use the pk as defined by model_class, not
# self.pk. These can be different fields because model inheritance
# allows single model to have effectively multiple primary keys.
# Refs #17615.
remove code lines:
823 model_class_pk = self._get_pk_val(model_class._meta) 824 if not self._state.adding and model_class_pk is not None: 825 qs = qs.exclude(pk=model_class_pk)
add these 2 lines (same as in Django 1.4)
if not self._state.adding and self.pk is not None:
qs = qs.exclude(pk=self.pk)
comment:8 by , 13 years ago
| Resolution: | → fixed |
|---|---|
| Status: | new → closed |
Replying to johan.holman@…:
I'm sorry didn't find the time yesterday to create a complete example, but here it is:
For our workflow - please open this in a new ticket with your two comments summarized in the description. You can then cross link to it being related to this ticket by putting the ticket number #17615 in the description. The original ticket was closed, and we generally don't revise committed fixes by reopening tickets. Thanks.
Test and fix.