Code

Opened 6 years ago

Closed 6 years ago

Last modified 16 months ago

#7938 closed Uncategorized (invalid)

"Editable = False" + "Edit Inline" in new forms admin

Reported by: magneto Owned by: brosner
Component: contrib.admin Version: master
Severity: Normal Keywords: newforms-admin
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: yes
Needs tests: yes Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

An error involved which now seems realted to the editable = false + edit inline

class HandleOption(models.Model):
      handle_option_id = models.PositiveIntegerField(primary_key=True, editable = false)
      name = models.CharField(max_length=100)

class Product(models.Model):
      product_id = models.PositiveIntegerField(primary_key=True, editable = false)
      name = models.CharField(max_length=100)

class ProductHandling(models.Model):
	product_handling_id = models.PositiveIntegerField(primary_key=True, editable = false)
	product = models.ForeignKey(Product)
	handle_option = models.ForeignKey(HandleOption, core = True)
	
# Admin bits

class ProductHandling_Inline(admin.StackedInline):
	model = ProductHandling

class ProductOptions(admin.ModelAdmin):
	inlines = [ProductHandling_Inline]

The form renders properly, However on 'Save' we get a trace back

Traceback:
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/django/core/handlers/base.py" in get_response
  87.                 response = callback(request, *callback_args, **callback_kwargs)
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/django/contrib/admin/sites.py" in root
  155.                 return self.model_page(request, *url.split('/', 2))
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/django/views/decorators/cache.py" in _wrapped_view_func
  44.         response = view_func(request, *args, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/django/contrib/admin/sites.py" in model_page
  172.         return admin_obj(request, rest_of_url)
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/django/contrib/admin/options.py" in __call__
  251.             return self.change_view(request, unquote(url))
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/django/contrib/admin/options.py" in change_view
  550.                 return self.save_change(request, form, inline_formsets)
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/django/db/transaction.py" in _commit_on_success
  198.                 res = func(*args, **kw)
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/django/contrib/admin/options.py" in save_change
  390.                 formset.save()
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/django/forms/models.py" in save
  339.         return self.save_existing_objects(commit) + self.save_new_objects(commit)
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/django/forms/models.py" in save_existing_objects
  355.             obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]]

Exception Type: KeyError at /admin/products/product/647/
Exception Value: None

The variables at the time of save at the traceback point

###
# existing_objects
###
{
3221L: <ProductHandling>, 
3222L: <ProductHandling>,
3223L: <ProductHandling>, 
}

###
# form.cleaned_data
###
{'DELETE': False, 'product_handling_id': None, 'handle_option': <HandleOption: [Postprocessing] - frame - Black Metal Frame>} 

###
# self.model._meta.pk.attname
###
product_handling_id

IF i Take OUT editable

class ProductHandling(models.Model):
	product_handling_id = models.PositiveIntegerField(primary_key=True)
	product = models.ForeignKey(Product)
	handle_option = models.ForeignKey(HandleOption, core = True)

All functions well, BUT there is a 'blank' row in the admin form (obviously a <input hidden> but with an attribute Label present)

seems in the edit_inline case where a field is not editable, but is the explicit primary Key (not the usual automatic Primary key), it should be treated in the same fashion as the auto primary key field

Attachments (3)

7938_custom_primary_key_formset_fix.1.diff (824 bytes) - added by brosner 6 years ago.
7938_force_pk_presentation.diff (2.6 KB) - added by magneto 6 years ago.
Force the display of editable=false, pk=true for inline models (the field ends up being hidden)
7983_auto_field.diff (7.6 KB) - added by magneto 6 years ago.
(bad diff file)

Download all attachments as: .zip

Change History (21)

comment:1 Changed 6 years ago by brosner

  • Needs documentation unset
  • Needs tests unset
  • Owner changed from nobody to brosner
  • Patch needs improvement unset
  • Status changed from new to assigned
  • Triage Stage changed from Unreviewed to Accepted

comment:2 Changed 6 years ago by brosner

Ok, what needs to be fixed here is the BaseModelFormSet always makes the primary key as a hidden field. That is wrong and should understand the difference between an automatically created primary key and a custom override. When it is overidden such as your case above it should always allow the field to edited by the user. It would be wrong to treat them the same since Django cannot provide the value implicitly. Therefore the exception you see in the editable=False case is correct. You would have to provide it yourself.

comment:3 Changed 6 years ago by brosner

Actually let me re-think this. I am missing something here.

comment:4 Changed 6 years ago by magneto

basically if 'editable = True' (or not set)

the 'inline edit' _does_ hide the field because it is a primary Key, so that is correct .. what is werid is that is shows the 'label' for it still

i.e. in the above examples

product handling id | <input hidden>
handle option | <select menu>

it really should be just

<input hidden>
handle option | <select menu>

but if editable = false, it forgets to add the input hidden
so the form skeleton is just

handle_option | <select menu>

thus the 'save aux elements' in the form.cleaned_data is lacking the PK to save it

comment:5 Changed 6 years ago by brosner

I disagree. When editable=True on a custom primary key and non-AutoField the field should *show* on the form. There is no way of providing that value otherwise unless of course you provide it at the code level. The correct fix and I stand by what I said earlier is that custom primary keys should always show on the form.

Changed 6 years ago by brosner

comment:6 Changed 6 years ago by brosner

  • Needs tests set

comment:7 Changed 6 years ago by magneto

Sure, Editable = True can certainly show the input box .. not much to disagree about there, that's just one part of the bug.

The other part is the 'editable = false',

In the inline Model, being a Primary Key, should always slap a hidden field regardless of the edit-ability of it

the above patch still does not address that little issue.

It's almost as if any place there is an " if not editable " it needs to be conditioned with " if not editable and not primary_key "

here is a small little patch .. however, it still shows the 'hidden' row in the inline stacked presentation .. but at least the PK is in the form and so it can update/save the object

Changed 6 years ago by magneto

Force the display of editable=false, pk=true for inline models (the field ends up being hidden)

comment:8 Changed 6 years ago by brosner

I still think we are not coming to the right agreement here. Let me outline each case as I see it:

  • If the primary key was automatically generated it is always hidden.
  • If the primary key was defined by hand *and* is a of type AutoField (aka self.model._meta.has_auto_field) it is always hidden regardless of the editable value.
  • If the primary key was defined by hand, is of some other type, and editable=True (or not given) it is always shown.
  • If the primary key was defined by hand, is of some other type, and editable=False then it is *not* shown, but if not given a value before saving the database will complain (correct behavior).

There doesn't need to be any changes to newforms outside of a formset because the bug is formset specific. newforms handles everything correctly. I hope I am not repeating what you are saying, but your patch indicates fixing the wrong thing and doesn't even appear correct.

comment:9 Changed 6 years ago by magneto

ya, the patch is not 'right' and is more to demonstrate what i think 'should' happen (the PK shows up, but is not editable).

but the issue involves your last statement

  • If the primary key was defined by hand, is of some other type, and editable=False then it is *not* shown, but if not given a value before saving the database will complain (correct behavior).

When one edits Inline, the PK still needs to be there for the saving to work. Its not the DB complaining, its the Form Validator complaining it cannot find the PK as it wants to update things.

#this works (does NOT show the editbox)
	product_handling_id = models.AutoField(primary_key=True)
# this works (but shows the edit box)
	product_handling_id = models.PositiveIntegerField(primary_key=True)
#this fails
	product_handling_id = models.AutoField(primary_key=True, editable = False)
#this fails
	product_handling_id = models.PositiveIntegerField(primary_key=True, editable = False)

If you try the admin from the above example the PK field never makes it into the form, thus makeing it editable when it should not be (PositiveIntegerField vs AutoField is really a Database Optimization, as the default AutoField translates into an IntField).

Soooo. It seems the Field definitions should allow for a keyword that says "I Am The AutoField", which given the proximity to v1.0 probably won't happen.

PositiveIntegerField(primary_key = True, auto_field = True)

comment:10 Changed 6 years ago by magneto

Ok, in order to effect the my auto_field = True Field option, here is a bigger patch, that does just that

used exactly like

my_col = models.PositiveIntegerField(primary_key = True, auto_field = True)

And this will be treated like any other "AutoField" field, except now we get to specify a custom db_type,

I Am aware i can probably just subclass AutoField, so if y'all think this is too silly, just mark the thing as fixed using the little patch brosner posted

Changed 6 years ago by magneto

(bad diff file)

comment:11 Changed 6 years ago by brosner

Ok, so we have finally uncovered that we were dealing with two different issues that are closely related. The patch I posted is indeed fixing part of the problem and I will commit. Either open a new ticket or see if it has been reported before, it likely has about a custom AutoField. It would likely take the route of just subclassing AutoField there.

comment:12 Changed 6 years ago by Erwin

The original problem also exists in the following situation

class Fruit(models.Model)
    name = models.Charfield("Name", max_length=50)

class Apple(models.Model)
    organization = models.OneToOneField('Fruit', primary_key=True
    color = models.Charfield("Name", max_length=50)

class AppleInline:
    model = Apple

class FruitAdmin
    inlines = [AppleInline]

In this case, when editing an existing product, the hidden field created doesn't have a value:

<input type="hidden" id="some_value" class="some_value" /> value="" is missing

comment:13 Changed 6 years ago by brosner

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

Ok, the original problem actually (I know realize) is a duplicate of #7888. Marking as so. However, this ticket also discovered a different bug that I will commit now.

comment:14 Changed 6 years ago by brosner

  • Resolution duplicate deleted
  • Status changed from closed to reopened

Actually thinking about it. This really isn't a duplicate, but rather a primary key should not be marked editable=False since that will caused the above error. The built-in AutoField returns None in its formfield method which causes it to not display but is still accessible in the cleaned_data dict. To accomplish this now just subclass the field type and do the same. Otherwise open a new ticket regarding custom primary keys since this describes it as being a formset issue, which it really isn't.

comment:15 Changed 6 years ago by brosner

  • Resolution set to invalid
  • Status changed from reopened to closed

comment:16 Changed 6 years ago by brosner

(In [8179]) Ensure that custom primary keys are always shown on a formset. Refs #7938. Thanks magneto for discovering this problem.

comment:17 Changed 5 years ago by steve918

  • Needs documentation set

I have a custom primary key that uses an auto-generated id that should not be user editable. As suggested formfield method returns None like so:

class HackInlineCharField(models.CharField):

    def formfield(self, **kwargs):
        return None


class ScreenShot(Model):
    
    id = HackInlineCharField(primary_key=True, max_length=32, default=unique_key)

but that results in the following error:

TemplateSyntaxError at /undroid-admin/stories/article/2/

Caught an exception while rendering: "Key 'id' not found in Form"

Is it currently possible to have non-editable custom primary keys in formsets?

comment:18 Changed 16 months ago by o`dnik

  • Easy pickings unset
  • Severity set to Normal
  • Type set to Uncategorized
  • UI/UX unset

Do not make your pk field editable=True!!! It may be a security issue in some cases. Instead, you should
p = form.save(commit=False)
p.custom_pk_field = custom_pk
p.save()
It's simple and works.

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.