Blaž Šnuderl
Blaž Šnuderl

Reputation: 378

Django admin form and inlined model with default value

I am updating a django app from 1.4 to 1.8 and have hit a small problem with django admin.

My models look like this

def new_key():
   return binascii.b2a_hex(os.urandom(20))

class ApiKey(models.Model):
  customer = models.ForeignKey(UserProfile)
  key = models.CharField(max_length=40, default=new_key)

And admin.py is

class ApiKeyInline(admin.StackedInline):
  model = ApiKey
  extra = 0

class UserProfileAdmin(admin.ModelAdmin):
  inlines = [ApiKeyInline]

admin.site.register(UserProfile, UserProfileAdmin)

When using admin page api key get correctly populated with a random value. However when saving the UserProfile it doesn't get saved, it is as if nothing was added. If I manually change a a single leter in the autogenerated key saving works correctly. It seems to me this is a problem with django detecting a change or something like this.

Any suggestions? Code worked in 1.4.

Upvotes: 2

Views: 2077

Answers (2)

matali
matali

Reputation: 186

There is a solution in one of the old threads: How to force-save an "empty"/unchanged django admin inline?

You should mark your inline form as always changed.

from django.forms import ModelForm
from .models import UserProfile

class AlwaysChangedModelForm(ModelForm):
    def has_changed(self):
        """ Should returns True if data differs from initial. 
            By always returning true even unchanged inlines will get   validated and saved."""
        return True

#An inline model
class ApiKey(admin.StackedInline):
    model = ApiKey
    extra = 0
    form = AlwaysChangedModelForm

Upvotes: 4

Art
Art

Reputation: 2335

A short investigation lead me to the save_new_objects of class BaseModelFormSet, located in django/forms/models.py. It has the following check: if not form.has_changed()

Looks promising, huh? Now, we want to "enhance" this method. Where to start? Well... Inlines inherit from InlineModelAdmin, which has an get_formset method. So...

class ApiKeyInline(admin.StackedInline):
    model = ApiKey
    extra = 0

    def get_formset(self, *args, **kwargs):
        formset = super(EmailInlineAdmin, self).get_formset(*args, **kwargs)
        # at this point, formset is a generated class called like "ApiKeyFormSet"
        # as it is a regular python objects, no one stops us from playing with it
        formset.save_new_objects = ApiKeyInline.my_own_save_new_objects_method
        return formset

    def my_own_save_new_objects_method(self, commit=True):
        # here should be something like 
        # django/forms/models.py    BaseModelFormSet.save_new_objects
        return self.new_objects

The contents of my_own_save_new_objects_method you have to edit yourself. The original method, once again, is in dist-packages/django/forms/models.py , either call it via super or write something entirely yours, anyways skip the check.

Also, maybe it is a terrible and overcomplicated solution. I have a feeling that there should be a better one. In example, not setting default=new_key in your model, but setting a default value for the form field, in example.

Upvotes: 1

Related Questions