Making multiple Multiselect forms dynamically in Django admin for the same M2M linked model

25 Views Asked by At

Is it possible to have dynamically multiple "Multi-Select" Form in Admin panel that is related to same model based on a certain rule? Like: A separate multi-select form for each category flagged as main/parent category with it's children only as selection options.

Details:

I have Posts Model and Categories Model, linked to each other via ManyToManyField on POSTS, the plan for categories model to be hierarchal where the top level are main groups of categories and leaf categories are linked to them as parents.

In POSTS admin page, I'm doing normal Multi-select Forms, it shows normally as below:

Multi Select Form (Top level categories shown temporary now)

The Top level Categories will grow to maybe 6 with 10-15 options in each one.. it will make this form difficult to navigate or manage.

So I want Cat and cat2 to have their own multi-select form, where the listed categories in each will be their children only. So even later when I add a third cat3 parent, i'll show in a separate multi-select form as well.

I'm trying to avoid building a separate html page for adding Posts for this only, as all other things are ok and i thought there might be a solution for it.

Note: checked different Django tagging packages, but none of them help in this.

POST Model:

class Post(models.Model):
    # Post main information
    id = models.UUIDField(
        default=uuid.uuid4, unique=True, primary_key=True, editable=False
    )
    title = models.CharField(max_length=400, blank=True)
    categories = models.ManyToManyField(
        Category,
        related_name="posts",
        blank=False,
    )

    # for logs
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return self.title

Categories Model:

class Category(models.Model):
    parent = models.ForeignKey(
        "self", null=True, blank=True, related_name="children", on_delete=models.CASCADE
    )
    name = models.CharField(max_length=50)
    slug = models.SlugField()
    about = models.TextField(null=True, blank=True)
    is_available = models.BooleanField(default=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        # enforcing that there can not be two categories under a parent with same slug
        unique_together = (
            "slug",
            "parent",
        )
        verbose_name = "Category"
        verbose_name_plural = "Categories"

    def save(self, *args, **kwargs):
        # prevent a category to be itself parent
        if self.id and self.parent and self.id == self.parent.id:
            self.parent = None
        super().save(*args, **kwargs)

    def __str__(self):
        full_path = [self.name]
        k = self.parent
        while k is not None:
            full_path.append(k.name)
            k = k.parent
        return " -> ".join(full_path[::-1])

My Posts Admin Form:

class PostAdminForm(forms.ModelForm):
    categories = forms.ModelMultipleChoiceField(
        queryset=Category.objects.all(), required=False
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.initial and self.instance.id is not None:
            categories = self.instance.categories.all()

            self.initial.update({"categories": categories})

    class Meta:
        model = Post
        fields = "__all__"
0

There are 0 best solutions below