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.
See also ticket:17615#comment:6, request to open a new ticket, here it is.
6 month later because I moved to another system and had to install Django
again and ran into this bug again. I made a copy of the example etc.
Here is a complete example, starting with 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','a...@test.com');
INSERT INTO test_contact VALUES (2,'bbb','b...@test.com');
INSERT INTO test_contact VALUES (3,'ccc','c...@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 - a...@test.com>, <Contact: bbb - b...@test.com>, <Contact:
ccc - c...@test.com>]
data = { 'subject': 'aaa' , 'email' : 'a...@test.com' }
}}}
So far, so good, but now we ran in some problems
{{{
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.']}
}}}
Solution, see also ticket:17615#comment:7
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)
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/21040>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* owner: nobody => sduveen
* needs_better_patch: => 0
* status: new => assigned
* needs_tests: => 0
* needs_docs: => 0
--
Ticket URL: <https://code.djangoproject.com/ticket/21040#comment:1>
* status: assigned => closed
* resolution: => worksforme
Comment:
I'm not getting the error. I have a test doing the same thing here:
https://github.com/schuyler1d/django/commit/cef85acabbdd7970b878f9c8393110757cdc56ee
(which could be pulled just to add a test to the system)
If kvanman can tweak the error to cause the issue, then maybe we can re-
open.
--
Ticket URL: <https://code.djangoproject.com/ticket/21040#comment:2>
Comment (by kvanman@…):
I hope if I understand correctly did you try my example??? Your answer
doesn't make sense.
--
Ticket URL: <https://code.djangoproject.com/ticket/21040#comment:3>
* status: closed => new
* resolution: worksforme =>
Comment:
please try my example ...
--
Ticket URL: <https://code.djangoproject.com/ticket/21040#comment:4>
* status: new => closed
* resolution: => invalid
Comment:
The problem is the use of a ModelForm for an abstract model.
In #19271 claudep (core developer) stated that ModelForm is not meant to
be used with abstract models. If you think this is wrong and your use case
is legitimate, you should bring this up on the django-dev mailinglist.
--
Ticket URL: <https://code.djangoproject.com/ticket/21040#comment:5>