How can I define the ManyToManyField name in django?

Question:

I have this relationship

Class Item(models.Model):
   pass

Class Category(models.Model):
   items = models.ManyToManyField(Item)

I can define the field name as items for category and access it via category.items but I want to define a field name for Item too as item.categories rather than the default item.category

How can I achieve it?

Update

Tried

items = models.ManyToManyField(Item, related_name = "categories")

But I get

TypeError: Direct assignment to the reverse side of a many-to-many set is prohibited. Use categories.set() instead.

on Item.object.create(**data)

Asked By: Axeltherabbit

||

Answers:

See, ManyToManyField can’t make reverse relationship with related model as python is interpreted language, so it can’t read model class of previous one. Instead, you can do one thing …

# models.py

class Item(models.Model):
    item_name = models.CharField(max_length=255, default="")

    def __str__(self):
        return self.item_name


class Category(models.Model):
    category_name = models.CharField(max_length=255, default="")
    items = models.ManyToManyField(Item)

    def __str__(self):
        return self.category_name

After that, you can list down your requirements in views.py file.

# views.py

def get_items_by_categories(request):
    
    # Here, you will receive a set of items ...
    
    get_categories = Category.objects.all()

    # Filter out items with respect to categories ...

    get_items_list = [{"category": each.category_name, "items": each.items} for each in get_categories]

    return render(request, "categories.html", {"data": get_items_list})

Iterate your items with categories in categories.html file.

{% for each in data %}
    {% for content in each %}
        {{content.category}}
        {% for item in content.items.all %}
            {{item.item_name}}
        {% endfor %}
    {% endfor %}
{% endfor %}

I hope this solution will help you ..

Thank You !

Answered By: techbipin

The thing about many to many field is not an actual field. If you take a look at the generated schema you wouldnt find the field as a column in either of the table. What happens in the back is django creates a ItemCatagory table.

class ItemCatagory(models.Model):
      item = modes.ForegnKeyField(Item, related_name="catagories", on_delete... )
      catagory = models.ForegnKeyField(Item, related_name="items", on_delete... )

catagory.items will give u the ItemCatagory RelatedObject and if u do catagoty.items.all() it will give u QuerySet[ItemCatagory]. so the default model.save() method woont add those rship. so u would have to overide save method on either of the models.

Answered By: kalkidan Teklu

When you call Item.objects.create(), you need to omit the categories from the args. Then afterwards you can call set() to set the categories.

item = Item.objects.create()
item.categories.set(categories)

If you want to add to existing categories (rather than overwriting what’s there), call add() instead:

item = Item.objects.create()
item.categories.add(category)

Note: both add() and set() save the update to the database, so you don’t need to call item.save() afterwards


EDIT

It looks like with the default name, you can pass in a list of categories to the call to create() and it even seems like it works because you can access the list of categories in the attribute category:

category = Category.objects.create()
item = Item.objects.create(category=[category])
print(item.category)
# output: [<Category: Category object(1)>]

The problem with this is category is not how you set or access category objects on an item, it’s category_set, and that’s empty.

To see this, after running the above code fetch the item from the database, you can see that the category is not associated with the item (it wasn’t saved):

item = Item.objects.get(id=item.id)
print(item.category_set)
# output: []

(And if you try to do item.category, you get an attribute error.)

It’s confusing that Django lets you provide the category argument in a call to create() with the default related name, but fails with an error when the related_name is set. IMO it should have an error in both cases, because clearly passing an m2m list as an argument to create() does not work: you have to call set() or add().


Source: Many-to-Many relations

Answered By: kimbo