django-nested-admin icon indicating copy to clipboard operation
django-nested-admin copied to clipboard

[bug?] readonly_fields and and prepopulated_fields not working

Open S4r4h-O opened this issue 4 months ago • 4 comments

I have these admin classes:

class ProductVariationInline(NestedTabularInline):
    """Inline for product variation inside of product."""

    model = ProductVariation
    extra = 1
    inlines = [ProductVariationImageInline]


class ProductAdmin(ImportExportModelAdmin, NestedModelAdmin):
    list_display = ["name", "category", "price"]
    inlines = [ProductImageInline, ProductVariationInline]
    resource_classes = [ProductWithVariationsResource]
    readonly_fields = ["slug"]


class ProductVariationAdmin(ImportExportModelAdmin, NestedModelAdmin):
    list_display = ["name", "father_product", "price", "color"]
    inlines = [ProductVariationImageInline]
    readonly_fields = ["slug"]

readonly_fields and and prepopulated_fields work with the ProductAdmin, however they don't work with ProductVariationAdmin. Any ideas what I'm doing wrong?

Info:

requires-python = ">=3.12" dependencies = [ "celery>=5.5.3", "django>=5.2.7", "django-cors-headers>=4.9.0", "django-environ>=0.12.0", "django-filter>=25.2", "django-import-export>=4.3.13", "django-nested-admin>=4.1.6", "django-ninja>=1.4.5", "django-rest-api>=0.1.5", "django-smart-selects>=1.7.2", "djangorestframework>=3.16.1", "pillow>=12.0.0", "psycopg>=3.2.12", "pydantic>=2.12.3", "requests>=2.32.5", "uvicorn>=0.38.0", ]

S4r4h-O avatar Nov 05 '25 01:11 S4r4h-O

Could you provide a bit more detail, enough for me to try to reproduce it myself? I'd need the model definitions (only the minimum number of fields required to demonstrate the issue) and the inline definitions (which I'm guessing are the places where the prepopulated_fields and readonly_fields aren't working, and which aren't given in your code sample above).

fdintino avatar Nov 05 '25 15:11 fdintino

Here it is:

from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils.text import slugify
from smart_selects.db_fields import ChainedForeignKey


class Category(models.Model):
    name = models.CharField(max_length=50)
    image = models.URLField(null=True, blank=True)
    slug = models.SlugField(max_length=225, unique=True, blank=True)
    is_active = models.BooleanField(default=True)

    class Meta:
        verbose_name_plural = "Categories"

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)

    def __str__(self):
        return self.name


class SubCategory(models.Model):
    name = models.CharField(max_length=50)
    image = models.URLField(null=True, blank=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    slug = models.SlugField(max_length=255, unique=True, blank=True)

    @property
    def is_active(self):
        """True if father category is active."""
        return self.category.is_active

    class Meta:
        verbose_name_plural = "Sub Categories"

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)

    def __str__(self):
        return f"{self.name} ({self.category.name})"


class ProductImage(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey("content_type", "object_id")
    image = models.ImageField(upload_to="products/images", blank=True, null=True)


class Product(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField(max_length=150, unique=True, blank=True)
    sku = models.CharField(max_length=50, unique=True)
    erp_id = models.CharField(max_length=100, null=True, blank=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    sub_category = ChainedForeignKey(
        SubCategory,
        chained_field="category",
        chained_model_field="category",
        sort=True,
        null=True,
        blank=True,
    )
    price = models.FloatField()
    description = models.TextField(max_length=300)
    size = models.CharField(max_length=10, null=True, blank=True)
    weight = models.FloatField(null=True, blank=True)
    images = GenericRelation(ProductImage)

    @property
    def is_active(self):
        """True if respective category is active."""
        return self.category.is_active

    def save(self, *args, **kwargs):
        """Generate slug automatically if not inexistent."""
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)

    def __str__(self):
        return self.name


class ProductVariation(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField(max_length=150, blank=True)
    sku = models.CharField(max_length=500, unique=True)
    erp_id = models.CharField(max_length=100, null=True, blank=True)
    father_product = models.ForeignKey(
        Product, on_delete=models.CASCADE, related_name="variations"
    )
    price = models.FloatField()
    color = models.CharField(max_length=20, null=True, blank=True)
    images = GenericRelation(ProductImage)

    @property
    def is_variation(self):
        """True if variation (no shit)."""
        return True

    def save(self, *args, **kwargs):
        """Generate slug automatically if not inexistent."""
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)

    def __str__(self):
        return self.name

S4r4h-O avatar Nov 07 '25 18:11 S4r4h-O

Forgot to add the inlines:

from django.contrib import admin
from import_export import fields, resources, widgets
from import_export.admin import ImportExportModelAdmin
from nested_admin import (
    NestedGenericTabularInline,
    NestedModelAdmin,
    NestedTabularInline,
)

from products.actions import export_products_csv

from .actions import export_products_csv
from .models import Category, Product, ProductImage, ProductVariation, SubCategory


class ProductsResource(resources.ModelResource):
    father_product = fields.Field(
        column_name="father_product",
        attribute="father_product",
        widget=widgets.ForeignKeyWidget(Product, field="name"),
    )

    class Meta:
        model = ProductVariation


# Products admin
class ProductImageInline(NestedGenericTabularInline):
    """Inline for product images."""

    model = ProductImage
    extra = 1


class ProductVariationImageInline(NestedGenericTabularInline):
    """Inline for multiple nested product variation images."""

    model = ProductImage
    extra = 1


class ProductVariationInline(NestedTabularInline):
    """Inline for product variation inside of product."""

    model = ProductVariation
    extra = 1
    inlines = [ProductVariationImageInline]


class ProductAdmin(NestedModelAdmin):
    list_display = ["name", "sku", "category", "sub_category", "price"]
    search_fields = ["name", "sku", "erp_id", "category__name", "sub_category__name"]
    inlines = [ProductImageInline, ProductVariationInline]
    readonly_fields = ["slug"]
    actions = [export_products_csv]


class ProductVariationAdmin(ImportExportModelAdmin, NestedModelAdmin):
    list_display = ["name", "father_product", "price", "color"]
    inlines = [ProductVariationImageInline]
    resource_classes = [ProductsResource]
    readonly_fields = ["slug"]


# Categories admin
class SubCategoryInline(admin.StackedInline):
    """Inline for subcategory inside of category."""

    model = SubCategory
    extra = 1


class CategoryAdmin(admin.ModelAdmin):
    list_display = ["name", "is_active"]
    inlines = [SubCategoryInline]
    readonly_fields = ["slug"]


class SubCategoryAdmin(admin.ModelAdmin):
     list_display = ["name", "category"]
     readonly_fields = ["slug"]


admin.site.register(Category, CategoryAdmin)
admin.site.register(SubCategory, SubCategoryAdmin)
admin.site.register(Product, ProductAdmin)
admin.site.register(ProductVariation, ProductVariationAdmin)

S4r4h-O avatar Nov 09 '25 18:11 S4r4h-O

Is the issue that slug isn't showing up as a readonly-field in the "ProductVariation" inline of the "Product" admin? If so, that's because there isn't a readonly_fields defined for ProductVariationInline. The ProductVariationAdmin class is completely separate, so any attributes on that class won't have any effect on the inline. To have a readonly slug on the inline you would need to have:

class ProductVariationInline(NestedTabularInline):
    """Inline for product variation inside of product."""

    model = ProductVariation
    extra = 1
    inlines = [ProductVariationImageInline]
    readonly_fields = ["slug"]

Also, just an FYI, the admin definitions you've just provided don't actually have any nesting: you only have inlines = [...] on ModelAdmin classes; none of your InlineAdmin classes have their own inlines defined. So (at least for the example you provided) you don't need to use django-nested-admin.

fdintino avatar Nov 11 '25 00:11 fdintino