0

I have two basic models, Product and Image with the latter assigned to the Product via Foreign Key:

class Image(models.Model):
    name = models.CharField(max_length=100)
    image = models.ImageField(upload_to=sku_dir)
    description = models.CharField(max_length=100, default='', blank=True)
    product = models.ForeignKey(product.Product, on_delete=models.CASCADE, related_name='images')

In admin, I am using the Image model inline:

class ImageInline(admin.TabularInline):
    model = image.Image
    extra = 1

@admin.register(product.Product)
class ProductAdmin(admin.ModelAdmin):
    search_fields = ['sku', 'name']
    list_display = ['sku', 'name', 'price']
    autocomplete_fields = ['genus']
    inlines = [ImageInline,]

I would like to add an additional field to Image to control which image is displayed by default - ideally there would be a radio button for each Image displayed inline on the Product admin form and only one of the images could be selected at once.

As I am still learning Django, I sense there is an easy way to do this but I don't know the proper terminology specific to Django to search for an answer (similar to how it took me awhile to discover that "inline" was a term used to display one model's form inside another).

How can I add a radio button to each Image which only allows one Image to be selected in the inline form?

pspahn
  • 2,770
  • 4
  • 40
  • 60
  • Maybe this helps? https://stackoverflow.com/questions/1432745/select-item-in-django-admin-inline-with-radio-buttons – Jim Apr 16 '20 at 23:34
  • @Jim I guess I thought this would have been an existing pattern but it appears not. I'd like to avoid doing anything unconventional and radio buttons have "idiot-proof" built into them but I guess I will just have to use an IntegerField as a positional value instead which will be prone to user input errors but can be done with the out-of-the-box features. Also interesting that I searched pages and pages of SO questions and never saw the one you linked. – pspahn Apr 17 '20 at 01:27
  • i'll hack around a bit and see if i can come up with something... – Jim Apr 17 '20 at 02:36

2 Answers2

1

One way I can think of to get similar functionality (although not exactly what you are looking for) would be to add a new field to your Product model called default_image. You have this field set as a ForeignKey to the Image model (you will need to use related_name to avoid a reverse query name clash). You use a custom form for the Product model that limits the queryset of the default_image field to the images associated with a particular product.

In the admin panel, you'll then have a drop down (or radio button if you use a radio widget) to select the default image for that Product - the choices will be the images found related to that product and this will allow you to select one (and only one) default image for a particular product.

I set up a quick test (using the models in the Django tutorial to avoid misinterpreting your setup):

models.py:

from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)

    # this field will hold the preferred choice for this question - 
    # setting one is optional
    preferred_choice = models.ForeignKey('Choice', on_delete=models.CASCADE, related_name='preferred_choice')


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)

admin.py:

from django.contrib import admin
from django import forms
from .models import Question, Choice


# Setup a custom form
class QuestionForm(forms.ModelForm):
    class Meta:
        model = Question
        fields = "__all__"


    # call super and then set the queryset of choices to be the ones related to 
    # the question we are working on
    def __init__(self, *args, **kwargs):
        super(QuestionForm, self).__init__(*args, **kwargs)
        if self.instance:
            self.fields['preferred_choice'].queryset = Choice.objects.filter(question=self.instance)


class ChoiceInline(admin.TabularInline):
    model = Choice


class QuestionAdmin(admin.ModelAdmin):
    form = QuestionForm
    inlines = [ChoiceInline]


admin.site.register(Question, QuestionAdmin)

Another possible solution would be to add a boolean/checkbox field to the Image model called "default_image", but you would have to implement some kind of custom save logic to make sure only one image had the field set to True

That's the way it was handled in this question, and JS was used to 1) make the checkboxes look like radio buttons, and 2) make sure the other options were set to False when one was set to True

Not perfect solutions, but hopefully that helps at least point you in the right direction...

Jim
  • 2,300
  • 1
  • 19
  • 43
  • I had considered your first suggestion, and what I didn't like was that you have to save the model with a new image and then edit it again afterwards to get the select input to repopulate. For the second, adding a bunch of custom forms and JS isn't a road I want to go down at this time. Unless something else comes along which is better, I think I'll just use an `IntegerField` labeled 'position' for each Image with the model `ordering` set to that field. – pspahn Apr 17 '20 at 16:37
0

i been struggling with the same issue before and i ended up adding default field (boolean) to image model which can only be applied to one image per product.

Also i added order_by field (integer) to control which image displayed first.

And of course you can add Javascript to the frontend to make this process easier for example:

  1. Clicking on inline image would make it default nicely with CSS
  2. Clicking on arrow up/down to re arrange images
Yousef Alm
  • 238
  • 1
  • 8