Override of ModelAdmin.save_model not being called - django

I'm using the GenerickStackedInline which is a subclass of InlineModelAdmin which goes to ModelAdmin. When I override save_model method... it's not being called.
class LocatedItemStackedInline(generic.GenericStackedInline):
template = "admin/location_app/located_items/stacked.html"
model = LocatedItem
extra = 1
form = MyModelForm
raw_id_fields = ('location',)
def save_model(self, request, obj, form, change):
import ipdb;ipdb.set_trace()
super(LocatedItemStackedInline, self).save_model(request, obj, form, change)
def save_form(self, request, form, change):
import ipdb;ipdb.set_trace()
super(LocatedItemStackedInline, self).save_form(request, form, change)
So, I'm missing something?
Any clue?
Regards

The problem was that I was overriding the save_model method on the InlineAdmin instead of on the ModelAdmin itself.
Now is being called...
Cheers.

http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_model
describes the function you're talking about. My best guess is that you're confused about when and where that will be called. Also, are you sure you're actually working with the latest revision?
Edit: I'd guess that inline ModelAdmin objects may behave differently, given their otherwise special status.

Related

Django Admin save_model() creates a new object when I only try to edit

My original issue was that I was trying to attach the current user to new entries, so I override the save_model method under admin.ModelAdmin to do
def save_model(self, request, obj, form, change):
obj.submit_usr = request.user
super().save_model(request, obj, form, change)
This works great when I try to create new object or new entry in my db, but when I try to edit the existing one, it still creates a new entry
my model looks like (I took out all the settings to avoid being messy here)
class AppUser(models.Model):
app_name = model.CharField()
submit_usr = models.ForeignKey()
submit_date = model.DateTimeField()
I know that the change arg indicates if there's a change in existing entry, but I still don't have a way to tell Django that I only want to modify the current entry instead of creating a new one.
Any ideas how to achieve that?
You can try
def save_model(self, request, obj, form, change):
if not change:
obj.submit_usr = request.user
super().save_model(request, obj, form, change)

How to access inline data in django ModelAdmin

I need to process a file uploaded in a django admin form. I've added a file upload field to the form:
class ExampleInline(admin.TabularInline):
model = OtherExample
extra = 1
class ExampleForm(forms.ModelForm):
filedata = forms.FileField()
class Meta:
model = ExampleModel
class ExampleModelAdmin(admin.ModelAdmin):
form = ExampleForm
inlines = [ExampleInline,]
This renders the form exactly like I want it to render. The data returned in Request is exactly what I expect.
The issue is that I want to access the contents of the inline.
class ExampleAdmin(admin.ModelAdmin):
...
def save_model(self, Request, obj, form, change):
the_file = form.cleaned_data['filedata']
# do amazing things to contents of file
At this point I want to reference the results of what the user selected in the inline. Whatever they picked for OtherExample.
How do I access that through the form? I would prefer not to go through the Request but am willing to do that. I'm also willing to examine save_related(self,request, form, formset, change)
save_related can do this, although it's called after the form is saved so you'll end up saving the object twice. You can access the object as form.instance or formset.instance.
def save_related(self, request, form, formsets, change):
obj = form.instance
# whatever your formset dependent logic is to change obj.filedata
obj.save()
super(ExampleAdmin, self).save_related(request, form, formsets, change)

Save the related objects before the actual object being edited on django admin

Is it possible to save the related objects before the actual object being edited on a django admin form?
For example:
in models.py
class Parent(model.Model):
pass
class Child(model.Model):
parent = models.ForeignKey(Parent)
#receiver(post_save,sender = Parent)
def notify_parent_save(sender, instance=None, **kwargs):
print "Parent save"
#receiver(post_save,sender = Child)
def notify_child_save(sender, instance=None, **kwargs):
print "Child saved"
in admin.py
class ChildInline(admin.TabularInline):
model = Child
extra = 1
class ParentsAdmin(admin.ModelAdmin):
inlines = [ChildInline]
admin.site.register(Parent,ParentsAdmin)
Now, in django admin if I save a parent object, it will output on the console.
Parent save
Child save
I need this to happen in revese order:
Child save
Parent save
The following will save the children first:
class ParentAdmin(admin.ModelAdmin):
inlines = [ChildInline]
def save_model(self, request, obj, form, change):
pass # don't actually save the parent instance
def save_formset(self, request, form, formset, change):
formset.save() # this will save the children
form.instance.save() # form.instance is the parent
I was having issues with the answers in this post, so I figured out a more concise answer. I was having an issue because using django-fsm, the other answers here would try to save the model multiple times (once for every formset) rather than once at the end.
def save_model(self, request, obj, form, change):
if not obj.pk: # call super method if object has no primary key
super(YourAdmin, self).save_model(request, obj, form, change)
else:
pass # don't actually save the parent instance
def save_related(self, request, form, formsets, change):
form.save_m2m()
for formset in formsets:
self.save_formset(request, form, formset, change=change)
super(YourAdmin, self).save_model(request, form.instance, form, change)
This essential just flips the order of save_model and save_related as called in Django ModelAdmin source
ccrisan's answer brought me on the right track, but I think there is a flaw regarding save behavior of instances that do not yet exist in the database. In this case it's not possible to save the related objects first, because there is no foreign key that they can point to. For me the following extension did the trick:
class ParentAdmin(admin.ModelAdmin):
inlines = [ChildInline]
def save_model(self, request, obj, form, change):
if not obj.pk: # call super method if object has no primary key
super(ParentAdmin, self).save_model(request, obj, form, change)
else:
pass # don't actually save the parent instance
def save_formset(self, request, form, formset, change):
formset.save() # this will save the children
form.instance.save() # form.instance is the parent
Depending on what you exactly want to do in your signals, can you just change the post_save to pre_save for the Child model ?

Trouble overriding both ModelAdmin.save_model() and ModelForm.save()

I want to override a ModelForm's save() function so that it updates a field on the model if the user pressed a particular submit button. I also want to check through some other fields and update their values, and I've done this on the ModelAdmin's save_model() function. However, the save_model() function is being passed None for the object. If I comment out the form's save() function, then the save_model() function works as expected.
Is there an issue with overriding both, or have I made a mistake somewhere?
Here's a minimal example:
admin.py:
class TestAdmin(admin.ModelAdmin):
form = TestForm
def save_model(self, request, obj, form, change):
print 'test'
super(PostAdmin, self).save_model(request, obj, form, change)
admin.site.register(TestModel, TestAdmin)
forms.py:
class TestForm(forms.ModelForm):
class Meta:
model = TestModel
def save(self, force_insert=False, force_update=False, commit=True):
print 'test'
super(TestForm, self).save(commit=True)
Your ModelForm needs to return the instance.
As far as I remember, just prior to save_model, the admin does a save(commit=False) and passes the unsaved instance to save_model. If you don't return anything, save() == None.
return super(CategoryForm, self).save(commit=True)
If you are overriding the ModelAdmin.save_model, and not calling the super().save_model (in your example you are, I can see.), you should explicitly call the form.save().
If you are calling the ModelForm.save somehow, through super or explicit calling, I don't see why it wouldn't work; but I can tell you that if I were to override the save_model, my preference would be to limit myself to overriding the Model.save().

How to access request.user from an admin ModelForm clean method?

I'm doing some stuff on 'clean' on an admin ModelForm:
class MyAdminForm(forms.ModelForm):
def clean(self):
# Some stuff happens...
request.user.message_set.create(message="Some stuff happened")
class MyAdmin(admin.ModelAdmin):
form = MyAdminForm
Other than the threadlocals hack - how do I access request.user to set a message? I can't pass it to the form constructor because doesn't get called from my code.
You can't do it on the form without passing the user into the form constructor. Instead you can use the ModelAdmin.save_model function which is given the request object.
The save_model method is given the
HttpRequest, a model instance, a
ModelForm instance and a boolean value
based on whether it is adding or
changing the object. Here you can do
any pre- or post-save operations.
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_model
Edit:
Since you want to put the logic/messages in the clean function you could do something like:
class MyAdminForm(forms.ModelForm):
user_messages = []
def clean(self):
# Some stuff happens...
user_messages.append("Some stuff happened")
class MyAdmin(admin.ModelAdmin):
form = MyAdminForm
def save_model(self, request, obj, form, change):
for message in form.user_messages:
request.user.message_set.create(message=message)
Very late edit:
user.message_set is set to be deprecated in Django 1.4. You should instead use ModelAdmin.message_user. https://docs.djangoproject.com/en/1.3/ref/contrib/admin/#django.contrib.admin.ModelAdmin.message_user
You would have to explicitly pass it there in constructor, which isn't a thing, that is usually done.
Are you sure you want to put that stuff into a form? What exactly you would like to do there? Isn't raising ValidationError enough?