Opened 11 years ago

Closed 11 years ago

#21040 closed Bug (invalid)

Bug in db/models/base.py, 'NoneType' object has no attribute 'attname'

Reported by: kvanman@… Owned by: sduveen
Component: Database layer (models, ORM) Version: 1.5
Severity: Normal Keywords: AttributeError NoneType attname
Cc: Triage Stage: Unreviewed
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: yes UI/UX: no

Description

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.

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','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' }

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)

Change History (5)

comment:1 by sduveen, 11 years ago

Owner: changed from nobody to sduveen
Status: newassigned

comment:2 by sduveen, 11 years ago

Resolution: worksforme
Status: assignedclosed

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.

comment:3 by kvanman@…, 11 years ago

I hope if I understand correctly did you try my example??? Your answer doesn't make sense.

comment:4 by kvanman@…, 11 years ago

Resolution: worksforme
Status: closednew

please try my example ...

comment:5 by Koen Biermans <koen@…>, 11 years ago

Resolution: invalid
Status: newclosed

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.

Note: See TracTickets for help on using tickets.
Back to Top