Code

Opened 8 years ago

Closed 7 years ago

#2470 closed defect (duplicate)

unique_together and edit_inline causes error in admin, depending on uniqueness order

Reported by: dk@… Owned by: adrian
Component: contrib.admin Version: master
Severity: normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: UI/UX:

Description

Given this model snippet, I get an error displaying Country objects in the admin.
The error message is: 'ChangeManipulator' object has no attribute 'isUniquecountry_price'

class Amount( models.Model):
    amount    = models.FloatField( _("Amount"), decimal_places=2, max_digits=5, core=True)
    country   = models.ForeignKey( Country)
    price     = models.ForeignKey( Price, edit_inline=models.TABULAR)

    class Meta:
        verbose_name = _('Amount')
        verbose_name_plural = _('Amounts')
        unique_together = (('country', 'price'),)

However, the error does not occur if the uniqueness constraint is changed to read:

 unique_together = (('price', 'country'),)

(Postgres, 0.95 rev 3512)

Attachments (0)

Change History (5)

comment:1 Changed 8 years ago by electrolinux@…

A little bit more on that. Given this model:

class Poll(models.Model):
    question = models.CharField(maxlength=200)
    def __str__(self): return self.question
    class Admin: pass

class Choice(models.Model):
    poll = models.ForeignKey(Poll,edit_inline=models.TABULAR,num_in_admin=5)
    position = models.IntegerField(core=True)
    choice = models.CharField(maxlength=200,core=True)
    votes = models.IntegerField(blank=True,default=0)
    def __str__(self): return '%s (%d)' % (self.choice, self.votes)
    class Meta:
        # 1)
        #unique_together = (('position','poll'),)
        # 2)
        unique_together = (('poll','position'),)

1) this raise AttributeError: 'AddManipulator' object has no attribute 'isUniqueposition_poll'
when Poll.AddManager (or ChangeManager) instance is created (Yes, Poll, not Choice)

2) this don't, but is not taken into account, and lead to an integrity error if trying to add a duplicate entry

See also related tickets: #2415, #1751

Here is the traceback from case 1:

Traceback (most recent call last):
File "/usr/local/lib/python2.4/site-packages/django/core/handlers/base.py" in get_response
  74. response = callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python2.4/site-packages/django/contrib/admin/views/decorators.py" in _checklogin
  55. return view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.4/site-packages/django/views/decorators/cache.py" in _wrapped_view_func
  39. response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.4/site-packages/django/contrib/admin/views/main.py" in change_stage
  313. manipulator = model.ChangeManipulator(object_id)
File "/usr/local/lib/python2.4/site-packages/django/db/models/manipulators.py" in __init__
  272. super(AutomaticChangeManipulator, self).__init__(follow=follow)
File "/usr/local/lib/python2.4/site-packages/django/db/models/manipulators.py" in __init__
  75. self.fields.extend(f.get_manipulator_fields(self.opts, self, self.change, fol))
File "/usr/local/lib/python2.4/site-packages/django/db/models/related.py" in get_manipulator_fields
  119. name_prefix=prefix, rel=True))
File "/usr/local/lib/python2.4/site-packages/django/db/models/fields/__init__.py" in get_manipulator_fields
  231. params['validator_list'].append(getattr(manipulator, 'isUnique%s' % '_'.join(field_name_list)))

  AttributeError at /admin/poll/poll/1/
  'ChangeManipulator' object has no attribute 'isUniqueposition_poll'

And here is the code around line 119 in db.models.related.py (get_manipulator_fields)

113        fields = []
114        for i in range(count):
115            for f in self.opts.fields + self.opts.many_to_many:
116                if follow.get(f.name, False):
117                    prefix = '%s.%d.' % (self.var_name, i)
118                    fields.extend(f.get_manipulator_fields(self.opts, manipulator, change,
119                                                           name_prefix=prefix, rel=True))

That's all for now. Hope this help!
--
didier

comment:2 Changed 8 years ago by electrolinux@…

The traceback with some more info in it, if it can help:

#  /usr/local/lib/python2.4/site-packages/django/contrib/admin/views/main.py in change_stage

 306. if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
 307. raise PermissionDenied
 308.
 309. if request.POST and request.POST.has_key("_saveasnew"):
 310. return add_stage(request, app_label, model_name, form_url='../../add/')
 311.
 312. try:

 313. manipulator = model.ChangeManipulator(object_id) ...

 314. except ObjectDoesNotExist:
 315. raise Http404
 316.
 317. if request.POST:
 318. new_data = request.POST.copy()
319.

▼ Local vars
Variable 	Value
app_label 	
'poll'
model 	
<class 'devel.poll.models.Poll'>
model_name 	
'poll'
object_id 	
'1'
opts 	
<Options for Poll>
request 	
<WSGIRequest GET:<MultiValueDict: {}>, [...]

# /usr/local/lib/python2.4/site-packages/django/db/models/manipulators.py in __init__

 265. lookup_kwargs = {'%s__exact' % self.opts.one_to_one_field.rel.field_name: obj_key}
 266. self.opts.one_to_one_field.rel.to.get_model_module().complex_filter(limit_choices_to).get(**lookup_kwargs)
 267. params = dict([(f.attname, f.get_default()) for f in self.opts.fields])
 268. params[self.opts.pk.attname] = obj_key
 269. self.original_object = self.opts.get_model_module().Klass(**params)
 270. else:
 271. raise

 272. super(AutomaticChangeManipulator, self).__init__(follow=follow) ...

 273.
 274. def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data):
 275. from django.db.models.fields.related import ManyToOneRel
 276. from django.utils.text import get_text_list
 277. field_list = [opts.get_field(field_name) for field_name in field_name_list]
 278. if isinstance(field_list[0].rel, ManyToOneRel):

▼ Local vars
Variable 	Value
follow 	
None
obj_key 	
'1'
self 	
<django.db.models.manipulators.ChangeManipulator object at 0xb6ed40cc>

# /usr/local/lib/python2.4/site-packages/django/db/models/manipulators.py in __init__

  68. if self.follow.get(f.name, False):
  69. self.fields.extend(f.get_manipulator_fields(self.opts, self, self.change))
  70.
  71. # Add fields for related objects.
  72. for f in self.opts.get_all_related_objects():
  73. if self.follow.get(f.name, False):
  74. fol = self.follow[f.name]

  75. self.fields.extend(f.get_manipulator_fields(self.opts, self, self.change, fol)) ...

  76.
  77. # Add field for ordering.
  78. if self.change and self.opts.get_ordered_objects():
  79. self.fields.append(forms.CommaSeparatedIntegerField(field_name="order_"))
  80.
  81. def save(self, new_data):

▼ Local vars
Variable 	Value
f 	
<RelatedObject: choice related to poll>
fol 	
{'position': True, 'votes': True, 'poll': False, 'id': True, 'choice': True}
follow 	
None
self 	
<django.db.models.manipulators.ChangeManipulator object at 0xb6ed40cc>

# /usr/local/lib/python2.4/site-packages/django/db/models/related.py in get_manipulator_fields

 112.
 113. fields = []
 114. for i in range(count):
 115. for f in self.opts.fields + self.opts.many_to_many:
 116. if follow.get(f.name, False):
 117. prefix = '%s.%d.' % (self.var_name, i)
 118. fields.extend(f.get_manipulator_fields(self.opts, manipulator, change,

 119. name_prefix=prefix, rel=True)) ...

 120. return fields
 121.
 122. def __repr__(self):
 123. return "<RelatedObject: %s related to %s>" % (self.name, self.field.name)
 124.
 125. def bind(self, field_mapping, original, bound_related_object_class=BoundRelatedObject):

▼ Local vars
Variable 	Value
attr 	
<django.db.models.fields.related.RelatedManager object at 0xb6e75bac>
change 	
True
count 	
5L
f 	
<django.db.models.fields.IntegerField object at 0xb6f2e40c>
fields 	
[FormField "choice.0.id"]
follow 	
{'position': True, 'votes': True, 'poll': False, 'id': True, 'choice': True}
i 	
0
manipulator 	
<django.db.models.manipulators.ChangeManipulator object at 0xb6ed40cc>
opts 	
<Options for Poll>
prefix 	
'choice.0.'
self 	
<RelatedObject: choice related to poll>

# /usr/local/lib/python2.4/site-packages/django/db/models/fields/__init__.py in get_manipulator_fields

 224. rel is a boolean specifying whether this field is in a related context.
 225. """
 226. field_objs, params = self.prepare_field_objs_and_params(manipulator, name_prefix)
 227.
 228. # Add the "unique" validator(s).
 229. for field_name_list in opts.unique_together:
 230. if field_name_list[0] == self.name:

 231. params['validator_list'].append(getattr(manipulator, 'isUnique%s' % '_'.join(field_name_list))) ...

 232.
 233. # Add the "unique for..." validator(s).
 234. if self.unique_for_date:
 235. params['validator_list'].append(getattr(manipulator, 'isUnique%sFor%s' % (self.name, self.unique_for_date)))
 236. if self.unique_for_month:
 237. params['validator_list'].append(getattr(manipulator, 'isUnique%sFor%s' % (self.name, self.unique_for_month)))

▼ Local vars
Variable 	Value
change 	
True
field_name_list 	
('position', 'poll')
field_objs 	
[<class 'django.forms.IntegerField'>]
follow 	
True
manipulator 	
<django.db.models.manipulators.ChangeManipulator object at 0xb6ed40cc>
name_prefix 	
'choice.0.'
opts 	
<Options for Choice>
params 	
{'validator_list': []}
rel 	
True
self 	
<django.db.models.fields.IntegerField object at 0xb6f2e40c>

comment:3 Changed 8 years ago by ramiro

see also #526

comment:4 Changed 7 years ago by adrian

  • Version changed from magic-removal to SVN

comment:5 Changed 7 years ago by Simon G. <dev@…>

  • Resolution set to duplicate
  • Status changed from new to closed

Duplicate of #565

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.