alexakarpov
alexakarpov

Reputation: 1920

django admin and inline related models - filtering

I'm building an e-commerce project in Django. Initially I had a naive Product model, which allowed me to move forward. But later, I've added ProductType, ProductSpecification (with a ForeignKey to Type), and ProductSpecificationValue classes/tables (with a ForeignKey to both Specification and Product). The use case should be obvious: say I have a Product of a shirt ProductType, which can come in different colors. That means a color Specification.

So first, I create two Product Types, say Shirt and Book. Each has a price spec and weight spec. In addition, Shirt has a color spec. The problem is, when I am creating a new Product, and select any type, the drop-down for selecting a spec has 5 values to chose from - two prices, two weights, and a color.

Now, I don't know if it's a real problem - but it sure is very confusing. I understand that before I save a Product, the Django Admin UI doesn't yet know what Type it is gonna be, so can't possibly limit the specs to those of a chosen type only. However, even after saving the Product (which demands a Type), and subsequently try to edit it, the Specs drop-down still presents me with all 5 choices. Database shows the likely cause:

sqlite> select * from inventory_productspecification;
1|price |1
2|weight|1
3|price |2
4|weight|2
5|color |2

this query is not filtering by Type id (1 vs 2 in here). My guess some change in the admin.py might take care of this? Mine is pretty bare-bones:

class ProductSpecificationInline(admin.TabularInline):
    model = ProductSpecification


@ admin.register(ProductType)
class ProductTypeAdmin(admin.ModelAdmin):
    inlines = [
        ProductSpecificationInline,
    ]


class ProductSpecificationValueInline(admin.TabularInline):
    model = ProductSpecificationValue


@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    inlines = [
        ProductSpecificationValueInline,
    ]

(thank you for your time y'all)

Upvotes: 2

Views: 727

Answers (1)

YellowShark
YellowShark

Reputation: 2269

So it seems like there's a couple things to solve: #1 is the object creation process, and #2 is showing ProductSpecificationValues that are associated with the Product's ProductType.

#1: Don't show the ProductSpecificationValueInline when the Product is being created. The user should only be able to set this value after they have created the product and set the ProductType.

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    inlines = [
        ProductSpecificationValueInline,
    ]

    def get_inlines(self, request, obj):
        if not obj.id or not obj.product_type:
            return []  # ... then don't show any inlines
        return self.inlines

#2: The ProductSpecificationValueInline should only show specification values that are associated with the Product type.

class ProductSpecificationValueInline(admin.TabularInline):
    model = ProductSpecificationValue

    def get_formset(self, request, obj=None, **kwargs):
        inline_formset = super().get_formset(request, obj, **kwargs)
        # I don't know what you named your field(s), but this illustrates the concept
        # of changing the queryset that is used to populate the choices for this field
        qs = inline_formset.base_fields['product_specification'].queryset
        qs = qs.filter(product_type=obj.product_type)
        inline_formset.base_fields['product_specification'].queryset = qs
        return inline_formset

Obviously this might not line up 100% with your models (for example, are you using a ManyToMany field for the Product's ProductSpecification values? And what are the field names in the models?), but the general principle remains the same: in your inline class, you would override the get_formset method and alter the queryset for the form field(s) in question.

Glad to help you get a little closer if needed, but would need to see the code for your models.

Upvotes: 2

Related Questions