related_name argument not working as expected in Django model? - python

I recently got a ForeignKey clash in my Django model. I have the need to have two foreign keys (owner, assigned_to) ultimately pointing to the same model (a user).
From what I understand I need a related_name argument to solve that problem. So I did that:
assigned_to = models.ForeignKey(TaskUser, blank=True, null=True, related_name='user_assignment')
and
owner = models.ForeignKey(TaskUser, related_name="user_ownership"
But I'm still getting an error:
tasks.task: Accessor for field 'owner' clashes with related field 'TaskUser.user
_ownership'. Add a related_name argument to the definition for 'owner'.
tasks.task: Reverse query name for field 'owner' clashes with related field 'TaskUser.user_ownership'. Add a related_name argument to the definition for 'owner'.
Why am I still getting this error?
There is one catch, owner is in a super class (BaseWidget) and assigned_to is in a sub class (Task). Are there issues with using related_name in an inheritance relationship? Do I need to just override the inheritance of owner and redefine related_name in the sub class instead? I'd appreciate any help!

If you have ForeignKey relationships in an abstract base class every class inheriting from it will have this relationship. As a result of this you must not 'hardcode' its related_name, because all sub classes will try to create the same accessor on the realted class (TaskUser in this case).
You should better do something like:
owner = models.ForeignKey(TaskUser, related_name="%(app_label)s_%(class)s_ownership")
See the django docs on this.

If you are using related_name in abstract base class you need to use a '%(app_label)s' and '%(class)s' in it.
Its mentioned in django doc
Be careful with related_name

Related

How can a model have multiple keys with the same type of models as values?

I'm making an application and I should make a Model which have 2 keys that saves models of the same type.
It's not easy to express in English so I will upload the image of the situation.
In Food Pair Model ( or we can call it table I think )
I want to refer Food Model But I wasn't able to use ForeignKey or ManyToManyField.
ERRORS:
food_test.FoodQuestion.left_food: (fields.E304) Reverse accessor for 'FoodQuestion.left_food' clashes with reverse accessor for 'FoodQuestion.right_food'.
HINT: Add or change a related_name argument to the definition for 'FoodQuestion.left_food' or 'FoodQuestion.right_food'.
food_test.FoodQuestion.right_food: (fields.E304) Reverse accessor for 'FoodQuestion.right_food' clashes with reverse accessor for 'FoodQuestion.left_food'.
HINT: Add or change a related_name argument to the definition for 'FoodQuestion.right_food' or 'FoodQuestion.left_food'.
I don't know what database relation to use in this case and how to make it.
What can I use for this case?
When you create a ForeignKey from one model to another Django will dynamically create a property on the model being referenced that will return a QuerySet with all objects that have the foreign key to that object
For example
class Foo(models.Model):
pass
class Bar(models.Model):
foo = models.ForeignKey(Foo, on_delete=models.CASCADE)
foo = Foo.objects.create()
bar = Bar.objects.create(foo=foo)
foo.bar_set.all() # This will return a queryset containing foo
By default this property will be <model_name_lowercase>_set. In your case because you have 2 foreign keys from one model to the same model Django is trying to create the same property on the Food model for each foreign key.
To get around this issue you can specify the name of this property using related_name, if you set this to '+' no reverse relation will be made at all or give them unique names
class FoodQuestion(models.Model):
left_food = models.ForeignKey(Food, on_delete=models.CASCADE, related_name='+')
left_food = models.ForeignKey(Food, on_delete=models.CASCADE, related_name='+')
You must define a unique related_name for each ForeignKey field in your FoodPair model.
class FoodPair(models.Model):
first_food = models.ForeignKey(Food, related_name="first_food")
second_food = models.ForeignKey(Food, related_name="second_food")
what_i_buy = models.ForeignKey(Food, related_name="what_i_buy")
If related_name is not defined Django automatically sets it and when there is multiple ForeignKey fields pointing to the same model the names clash.

How to set related_name in ManyToMany field in an abstract model?

I have this abstract model:
class HasSystemMessage(models.Model):
class Meta:
abstract = True
messages = models.ManyToManyField(SystemMessage, related_name=?)
I am going to use this abstract model in at least three other models, lets say, A, B, and C. How can I set the related_name dynamically for these classes? for example, for class B, I want the related_name to be Bs. Is it possible to do so?
To further clarify the question, The classes will look like this:
class B(HasSystemMessage):
# Some model fields
class A(HasSystemMessage):
# Some model fields
HasSystemMessage.objects.filter(a__contains=[some elements])
You can use %(class)s or %(app_label)s
class HasSystemMessage(models.Model):
class Meta:
abstract = True
messages = models.ManyToManyField(SystemMessage, related_name=%(app_label)s_%(class)s_related)
From Django docs
Be careful with related_name and related_query_name¶ If you are using
related_name or related_query_name on a ForeignKey or ManyToManyField,
you must always specify a unique reverse name and query name for the
field. This would normally cause a problem in abstract base classes,
since the fields on this class are included into each of the child
classes, with exactly the same values for the attributes (including
related_name and related_query_name) each time.
To work around this problem, when you are using related_name or
related_query_name in an abstract base class (only), part of the value
should contain '%(app_label)s' and '%(class)s'.
'%(class)s' is replaced by the lower-cased name of the child class
that the field is used in. '%(app_label)s' is replaced by the
lower-cased name of the app the child class is contained within. Each
installed application name must be unique and the model class names
within each app must also be unique, therefore the resulting name will
end up being different.
Ref: https://docs.djangoproject.com/en/2.0/topics/db/models/#be-careful-with-related-name-and-related-query-name
You Just need to put a string in this attribute which specifies the name of the reverse relation from the SystemMessage.Also read in Django Docs
Try this:
class HasSystemMessage(models.Model):
class Meta:
abstract = True
messages = models.ManyToManyField(SystemMessage, related_name='system_message')

Django related_name not found

I have this model:
class Person(models.Model):
something ...
employers = models.ManyToManyField('self', blank=True, related_name='employees')
When I do person.employees.all() I get this error: 'Person' object has no attribute 'employees'. Is the related name only created when there is an actual link in place. If yes, how can I check this?
EDIT: I'm aware of the hasattr() function. I'm still wondering why the attribute doesn't return an empty list when there's no related objects.
To use related_name with recursive many-to-many you need set symmetrical=False. Without it Django will not add employees attribute to the class. From the docs:
When Django processes this model, it identifies that it has a ManyToManyField on itself, and as a result, it doesn’t add a person_set attribute to the Person class. Instead, the ManyToManyField is assumed to be symmetrical – that is, if I am your friend, then you are my friend.
So you can add symmetrical=False to the field:
employers = models.ManyToManyField('self', blank=True, related_name='employees', symmetrical=False)
person.employees.all() # will work now
or just use employers attribute:
person.employers.all()

Django: reverse accessors for foreign keys clashing

I have two Django models which inherit from a base class:
- Request
- Inquiry
- Analysis
Request has two foreign keys to the built-in User model.
create_user = models.ForeignKey(User, related_name='requests_created')
assign_user = models.ForeignKey(User, related_name='requests_assigned')
For some reason I'm getting the error
Reverse accessor for 'Analysis.assign_user' clashes with reverse accessor for 'Inquiry.assign_user'.
Everything I've read says that setting the related_name should prevent the clash, but I'm still getting the same error. Can anyone think of why this would be happening? Thanks!
The related_name would ensure that the fields were not conflicting with each other, but you have two models, each of which has both of those fields. You need to put the name of the concrete model in each one, which you can do with some special string substitution:
create_user = models.ForeignKey(User, related_name='%(class)s_requests_created')

Two ManyToMany fields without backwards relation

I'm trying to have a model with 2 ManyToMany fields without allowing a backwards relation.
So here is the model:
class Camp(models.Model):
#...
free_options = models.ManyToManyField('Option', related_name='+')
paid_options = models.ManyToManyField('Option', related_name='+')
After trying to do
python manage.py syncdb
I'm getting the following error:
Error: One or more models did not validate: camps.camp: Accessor for
m2m field 'free_options' clashes with related m2m field 'Option.+'.
Add a related_name argument to the definition for 'free_options'.
camps.camp: Reverse query name for m2m field 'free_options' clashes
with related m2m field 'Option.+'. Add a related_name argument to the
definition for 'free_o ptions'.
Is it not possible to have 2 fields without backwards relation on the same model? how can i fix this?
Thanks!
I would ask why you're bothered by having the backwards relation, just don't use it if you don't want it. But to answer the question, no there isn't a way to remove it entirely.
According to the Django documentation for the related_name argument of the ManyToManyField:
If you have more than one ManyToManyField pointing to the same model
and want to suppress the backwards relations, set each related_name to
a unique value ending with '+'
Emphasis mine.
So if you want to do that, you should be able to simply:
class Camp(models.Model):
#...
free_options = models.ManyToManyField('Option', related_name='free_options+')
paid_options = models.ManyToManyField('Option', related_name='paid_options+')
# ^^^^^^^^^^^^
to suppress the backwards relation on multiple ManyToManyField's in the same model.
Hope this helps!

Categories

Resources