Django ._meta and adding to ManyToMany fields

Question:

I haven’t had much luck finding other questions that helped with this, but apologies if I missed something and this is a duplicate.

I’m trying to add to some ManyToMany fields, without having to explicitly type out the names of the fields in the code (because the function I’m working on will be used to add to multiple fields and I’d rather not have to repeat the same code for every field). I’m having a hard time using ._meta to reference the model and field objects correctly so that .add() doesn’t throw an "AttributeError: ‘ManyToManyField’ object has no attribute ‘add’".

This is simplified because the full body of code is too long to post it all here, but in models.py, I have models defined similar to this:

class Sandwich(models.Model):
        name = models.CharField(max_length=MAX_CHAR_FIELD)

        veggies = models.ManyToManyField(Veggie)
        meats = models.ManyToManyField(Meat)
        

class Veggie(models.Model):
        name = models.CharField(max_length=MAX_CHAR_FIELD)

class Meat(models.Model):
        name = models.CharField(max_length=MAX_CHAR_FIELD)

Once instances of these are created and saved, I can successfully use .add() like this:

blt = Sandwich(name='blt')
blt.save()
lettuce = Veggies(name='lettuce')
lettuce.save()
tomato = Veggies(name='tomato')
tomato.save()
bacon = Meat(name='bacon')
bacon.save()

blt.veggies.add(lettuce)
blt.veggies.add(tomato)
blt.meats.add(bacon)

But if I try to use ._meta to get blt’s fields and add to them that way, I can’t. ie something like this,

field_name='meats'
field = blt._meta.get_field(field_name)  
field.add(bacon) 

will throw "AttributeError: ‘ManyToManyField’ object has no attribute ‘add’".

So, how can I use ._meta or a similar approach to get and refer to these fields in a way that will let me use .add()? (bonus round, how and why is "blt.meats" different than "blt._meta.get_field(‘meats’)" anyway?)

Asked By: lookoutthewindow

||

Answers:

Why do you want to do

field = blt._meta.get_field(field_name)  
field.add(bacon)

instead of

blt.meats.add(bacon)

in the first place?

If what you want is to access the attribute meats on the blt instance of the Sandwich class because you have the string 'meats' somewhere, then it’s plain python you’re after:

field_string = 'meats'
meats_attribute = getattr(blt, field_string, None)
if meats_attribute is not None:
    meats_attribute.add(bacon)

But if your at the point where you’re doing that sort of thing you might want to revise your data modelling.

Bonus round:
Call type() on blt.meats and on blt._meta.get_field(field_name) and see what each returns.

One is a ManyToManyField, the other a RelatedManager. First is an abstraction that allows you to tell Django you have a M2M relation between 2 models, so it can create a through table for you, the other is an interface for you to query those related objects (you can call .filter(), .exclude() on it… like querysets): https://docs.djangoproject.com/en/4.1/ref/models/relations/#django.db.models.fields.related.RelatedManager

Answered By: Clepsyd
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.