Custom save for inline admin form

I’m starting to a bit frustrated and could really use some direction on this. My site is centered around a sheet music catalog. So each piece of sheet music will have one or more sample images to go with it. For my sheet admin view, I have a single sheet editor with a stacked inline for sample pages. That part is working fine. What I’m trying to add is a way of saying, adding a checkbox to generate a custom cover and then when I save part of the save method runs a script to build the image then saves it as another record in the database. My first thought was a custom save() method in my sheet admin form but I don’t see how to reference the images from the save method. Then I thought maybe looking at adding the save to the inline form but then I can’t reference the parent data. Can someone give a few ideas of where to go with this?

From your explanation I would add the methods to the Model. Both the generation of the album art and also the save method. Though I would pass the generation as a argument to the save method and call the generation method while processing the save.
If you could provide some code it would be easier to help you.

Thanks for taking a look at this. I also realized a wrinkle to all this because during the saving of the samples, I need access to the composer inline as well to be able to generate the composer. Let me write this out so hopefully I’m a bit clearer.

Somewhere during the save/udate process for each sheet of music the following is done:

  1. Check if the update_cover BooleanField is checked.
  2. If it is true, pull the title from the sheet form (model?) as well as the composers from the composer inline form.
  3. Create a custom cover that includes the title and a formatted artist string (this part I have worked out)
  4. Insert the new created cover in with the rest of the sample art that is generated from the SampleInline.

I tried to cut down the code as much as possible removing lots of excess fields such as publisher and genre so its hopefully a bit manageable:

## models.py

class Composer(models.Model):
name_text = models.CharField(max_length=150, unique=True)
#sheets = models.ManyToManyField(Sheet)

def __str__(self):
    return self.name_text

class Meta:
    ordering = ['name_text']

class Sheet(models.Model):
title_text = models.CharField(max_length=200, blank=False) 
composers = models.ManyToManyField(Composer)

class SampleImage(models.Model):
sheet = models.ForeignKey(Sheet, related_name = "samples", on_delete=models.CASCADE, null=True)
sample_type = models.IntegerField(choices=IMAGE_CHOICES, default=IMAGE_CHOICES[-1][0])
sample_image = models.ImageField(upload_to=sample_image_upload_to)
order = models.IntegerField(blank=True,  null=True)

def display_sample_image(self):        
    width = 200
    height = width * self.sample_image.height / self.sample_image.width
    return format_html('<img src="' + self.sample_image.url + '" width=' + str(width) + ' height=' + str(height) + ">")        
display_sample_image.short_description = "Image"

class Meta:
    ordering = ('order',)

def __str__(self):
    return dict(IMAGE_CHOICES)[self.sample_type] + " for " + self.sheet.title_text

#admin.py

from django.contrib import admin
from django import forms
from django.utils.html import format_html
from django.urls import reverse
from .models import Sheet, Composer, SampleImage

class ComposerInline(admin.StackedInline):

model = Sheet.composers.through
extra = 2

verbose_name = "Composer"
verbose_name_plural = "Composers"

class SampleInline(admin.StackedInline):

model = SampleImage
extra = 3

verbose_name = "Sample Page"
verbose_name_plural = "Sample Pages"

fieldsets = (
    (None, {
        'fields' : ("sample_type", "sample_image", "order", "display_sample_image",)
    }),        
)    
readonly_fields = ("display_sample_image",)

def display_sample_image(self, obj):
    return obj.display_sample_image()       
display_sample_image.short_description = "Display"    


class SheetAdminForm(forms.ModelForm):

model = Sheet

title_text = forms.CharField(widget=forms.Textarea, required=True)
description_text = forms.CharField(widget=forms.Textarea, required=False)

update_cover = forms.BooleanField(required=False)
 

class SheetAdmin(admin.ModelAdmin):   

list_display = ('title_text', 'get_composers')
ordering = ('title_text',)

fieldsets = [
    (None,                  {'fields' : ['title_text']}),

    ('Cover Generator',     {'fields' : ['update_cover', ('override_title', 'updated_title'), ('override_composers', 'updated_composers'), ('override_level', 'updated_level'), ('override_instrumentation', 'updated_instrumentation'), ('override_intproperty', 'updated_intproperty'), ('override_collection', 'updated_collection'),]})
]

inlines = [ComposerInline, SampleInline]
exclude = ('composer', 'samples')
form = SheetAdminForm

# Register your models here.
admin.site.register(Sheet, SheetAdmin)
admin.site.register(Composer)
admin.site.register(SampleImage)

I think I’m with you up to here. I see that if I override save() in the model, my model is already filled in with everything I need so all I need to do is get all the parts I need to pass to the art generator (title, composers, etc). So now I need to have in the save whether or not a cover was requested or in other words the part where you say pass the generation as a argument – where might I do that?

Ok I think I have it worked out. Here is what I have:

class SheetAdmin(admin.ModelAdmin):   

    ## Snipped ##

        def save_model(self, request, obj, form, change):        
            obj.update_cover = form.cleaned_data.get("update_cover")
            super().save_model(request, obj, form, change)

then...

class Sheet(models.Model):

    ## Snipped ##

    def save(self, *args, **kwargs):
    if self.update_cover: 
        print("Yep lets make a cover....")
        super().save(*args, **kwargs)

I think I can puzzle out the rest. I’ll be sure to follow up if I have trouble. Thanks again!

1 Like

I was just about to write this - also I was thinking about the save method in save_model you could also add an argument like

super().save_model(request, obj, form, change, update_cover=should_update_cover)

and the use it in the save method:

def save(self, update_cover=False, *args, **kwargs):
    if update_cover:
      [...]

Oh so thats how I can pass keywords and arguments. When I tried it on a different save method I got an error. That works too.

Thanks!
Joel