Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Customizing a Widget for ManyToMany field to a Model that have a circular ForeignKey to itself

I have two models Category and Products.

  • a Product can have multiple Categories
  • a Category can have multiple Products.
  • Categories have a circular Foreign key, to itself.
  • not all Categories have the same depth level

Example:

  • Category A
    • Category A_1
    • category A_2
      • Category A_2_1
  • Category B
  • Category C
    • Category C_1

models.py

class Product:
    categories = models.ManyToManyField(Category)
    name = models.CharField(max_length=255)

class Category:
    categories = models.ForeignKey(self)
    name = models.CharField(max_length=255)

As Form I use a ModelForm:

class ProductForm(ModelForm):
    class Meta:
        model = Product
        fields = ['categories', 'name', 'short_description', 'description']
        widgets = {
            'categories': MyWidget,
        }

What I want to achieve:

I want to implement a conditional select (narrow options) On Product Form creation:

  1. Only top parent Categories(level 0 A,B,C) are available
  2. The user select a parent Category. If the parent has children a new Select box appear with his children(category level 1 A1,C1)
  3. The user select a level 1 Category (A1,C1). If the parent has children a new Select box appear with his children(level 2 A2)

    • The process is repeated until no children are availavable(recursive), an user select the "smallest" category in the tree
    • A new button is available for the user to add more categories and start the 1-3 process again
    • I want to do the select,add new select using JavaScript
    • On Form Submit I want to send only the last children categories

Options I thought:

  1. Change the ManyToMany coresponding default Fields - looks like there are no good hooks and/or inheritance
  2. Use a non-default custom Field instean of ManytoMany(like Charfield) - more complex on clean,saving Form
  3. Change/Inherit the widget. My issues is how to send the data to the default Field on submit, and get/show it on edit

Practical, let's say I have 7 Select Boxes, with the values for each:

  1. Parent1->Child11->Child111
  2. Parent2->Child21
  3. Parent3->Child31->Child311

How do I tell Django on browser submit(with other data) to send to ManyToMany the last Child in all three of them

I can gather them with Javascript, but I have to tell Django get this data, this is what you need.

I need some help as code and indication how to do this.

enter image description here

like image 714
user3541631 Avatar asked Oct 29 '22 23:10

user3541631


1 Answers

Django allows you to define the model you are referencing in a ForeignKey or ManyToManyField as an '<app_name>.<model_name>' string instead of having to import the model and directly assigning it. This resolves a lot of issues, especially circular imports.


Assuming you have the apps categories with a Category model and products with a Product model, this:

products/models.py:

class Product:
    categories = models.ManyToManyField(Category)
    name = models.CharField(max_length=255)

categories/models.py:

class Category:
    categories = models.ManyToManyField(self)
    name = models.CharField(max_length=255)

can directly translate to what you need:

products/models.py:

class Product:
    categories = models.ManyToManyField('categories.Category')
    name = models.CharField(max_length=255)

categories/models.py:

class Category:
    categories = models.ManyToManyField('self')
    name = models.CharField(max_length=255)

With this, you can have as many categories as you want:

category_one = Category.create(name='Category one')
category_two = Category.create(name='Category two')
category_three = Category.create(name='Category three')
category_three.categories.add(category_one, category_two)
some_product = Product.create(name='Test Product')
some_product.categories.add(category_three)

(ManyToManyField docs)


It is also important to note that with any Python class, self isn't the class itself – it's the instance. So you can't reference it outside of an instance method, there's a good explanation on why here. The only reason the 'self' string works here is because Django translates that to the class it's within, categories.Category –– so it might be a good idea to replace self with 'categories.Category' to be explicit.

like image 192
Cole Avatar answered Nov 16 '22 08:11

Cole