5

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

cezar
  • 11,616
  • 6
  • 48
  • 84
user3541631
  • 3,686
  • 8
  • 48
  • 115
  • What does the HTML template look like? have you provided any logic such as if statements? I think this may be where your solution lies.. – johnashu Nov 22 '17 at 22:02
  • the HTML template it doesn't matter, the issue is that the information that I need regarding the relation between parent and children and depth level of the category it is not sent to the template. I need to overwrite the default Field allocated by the ManyToMany . I'm referring to the 'internal' Field ModelSelectMultiple – user3541631 Nov 24 '17 at 10:06

1 Answers1

2

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.

Cole
  • 1,715
  • 13
  • 23
  • I understand what Django is doing, this is not the issue, I need to overwrite the default 'internal' Field selected by Django for ManytoMany and also create a custom widget – user3541631 Nov 24 '17 at 10:04