Django Relationship Name Collisions - Abstract Model has multiple relationships with another Model - django

My application consists of three Models: Users, Topics, and Responses.
Each Response may either be addressed at a Topic, or at another Response, but are largely identical. In the interest of not duplicating code, I thought it best to make an abstract model, Response, which ResponseResponse and TopicResponse inherit from.
from django.contrib.auth.models import User
from django.db.models import (CASCADE, CharField, DateTimeField,
ForeignKey, ManyToManyField, Model,
TextField)
class Topic(Model):
name = CharField(max_length = 100)
class Response(Model):
body = TextField()
topics = ManyToManyField(Topic)
agreers = ManyToManyField(User)
proposal = TextField()
affirms = ManyToManyField(User, related_name = 'affirmers')
rejects = ManyToManyField(User, related_name = 'rejectors')
class Meta:
abstract = True
class TopicResponse(Response):
responseTo = ForeignKey(Topic, on_delete = CASCADE)
class ResponseResponse(Response):
responseTo = ForeignKey(Response, on_delete = CASCADE)
The issue with this is that the User has two conflicting relationships called affirmers and two called rejectors.
If I don't give them related names, then instead TopicResponse and ResponseResponse each have three conflicting relationships, all of them called TopicResponse or ResponseResponse, respectively, (one for agreers, one for affirms, one for rejects).
An example error message is:
app.TopicResponse.rejects: (fields.E305) Reverse query name for 'TopicResponse.rejects' clashes with reverse query name for 'ResponseResponse.rejects'.
HINT: Add or change a related_name argument to the definition for 'TopicResponse.rejects' or 'ResponseResponse.rejects'.
If I leave off the related_name argument, I get error messages like this:
app.ResponseResponse.rejects: (fields.E304) Reverse accessor for 'ResponseResponse.rejects' clashes with reverse accessor for 'ResponseResponse.affirms'.
HINT: Add or change a related_name argument to the definition for 'ResponseResponse.rejects' or 'ResponseResponse.affirms'.
What can I do to fix all of these conflicts? I need to somehow have the related_name dynamically generated with the name of the of the concrete instance of the Model (like it is if you don't specify it) plus the name of the relationship.

You have to make the related name unique. As stated in the docs you can add %(class)s or %(app_label)s. These are then replaced by the child class values:
class Topic(Model):
name = CharField(max_length = 100)
class Response(Model):
body = TextField()
topics = ManyToManyField(Topic)
agreers = ManyToManyField(User)
proposal = TextField()
affirms = ManyToManyField(User, related_name = '%(app_label)s_%(class)saffirmers')
rejects = ManyToManyField(User, related_name = '%(app_label)s_%(class)srejectors')
class Meta:
abstract = True
class TopicResponse(Response):
responseTo = ForeignKey(Topic, on_delete = CASCADE)
class ResponseResponse(Response):
responseTo = ForeignKey(Response, on_delete = CASCADE)

Related

How to create "related_name" relation with parent models of "through" model (about related_name inheritance)

I have 4 models including one M2M "through" model enabling to had an index:
class TargetShape(models.Model):
pass
class Page(models.Model):
target_shapes = models.ManyToManyField(TargetShape, through='PageElement', related_name='pages')
class PageElement(models.Model):
target_shape = models.ForeignKey(TargetShape, related_name='page_elements')
page = models.ForeignKey(Page, related_name='page_elements')
index = models.PositiveIntegerField(verbose_name='Order')
class WorkingSession(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='working_sessions')
I defined another model Resolution that enables me to link all of them together in order to create the following related_name relations:
working_session.resolutions
user.resolutions
target_shape.resolutions
page.resolutions
page_element.resolutions
To have it working, I had to declare:
class Resolution(models.Model):
# relations that would be needed from a DRY perspective:
page_element = models.ForeignKey(PageElement, related_name='resolutions')
working_session = models.ForeignKey(WorkingSession, related_name='resolutions')
# relations I want to exist to use user.resolutions, target_shape.resolutions, page.resolutions:
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='resolutions')
target_shape = models.ForeignKey(TargetShape, related_name='resolutions')
page = models.ForeignKey(Page, related_name='resolutions')
However, that's not very DRY. From a DRY perspective, I should be able to declare Resolution as linking to only PageElement and WorkingSession, and deduce / inherit the relation with the parent models:
class Resolution(models.Model):
page_element = models.ForeignKey(PageElement, related_name='resolutions')
working_session = models.ForeignKey(WorkingSession, related_name='resolutions')
But in that case, how can I create the following relations:
user.resolutions
target_shape.resolutions
page.resolutions
without going through:
user.working_sessions.resolutions
target_shape.page_elements.resolutions
page.page_elements.resolutions
All I can think about is to:
either declare all fields as I already did
or declare in the User, TargetShape and Page models a property that respectively returns:
Resolution.objects.filter(working_session__user=self)
Resolution.objects.filter(page_element__target_shape=self)
Resolution.objects.filter(page_element__page=self)
That would surely work, but that's not very elegant ... and not very Django-ish
Does anybody know a more Django-ish way to define this relation?
I found out another way: declare in the User, TargetShape and Page models a property that returns a queryset:
class Resolution(models.Model):
page_element = models.ForeignKey(PageElement, related_name='resolutions')
working_session = models.ForeignKey(WorkingSession, related_name='resolutions')
class PageElement(models.Model):
target_shape = models.ForeignKey(TargetShape, related_name='page_elements')
page = models.ForeignKey(Page, related_name='page_elements')
index = models.PositiveIntegerField(verbose_name='Order')
class WorkingSession(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='working_sessions')
class TargetShape(models.Model):
#property
def resolutions(self):
return Resolution.objects.filter(page_element__target_shape=self)
class Page(models.Model):
#property
def resolutions(self):
return Resolution.objects.filter(page_element__page=self)
target_shapes = models.ManyToManyField(TargetShape, through='PageElement', related_name='pages')

Django Created By and Modified By in an Abstract Base Class

I want to create an Abstract Base Class that other models inherit to track some basic information (Created_Date, Mod_date, Created_by, Mod_by, etc) here is an example of what I would like:
class MetaDataModel(models.Model):
added_by = models.ForeignKey(settings.AUTH_USER_MODEL, )
modified_by = models.ForeignKey(settings.AUTH_USER_MODEL)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
class Meta:
abstract = True
However, when I use this class in more than one other model I receive an error telling me:
takeoffs.Takeoff.added_by: (fields.E304) Reverse accessor for
'Takeoff.added_by' clashes with reverse accessor for
'InputTakeoffItem.added_by'.
HINT: Add or change a related_name argument to the definition for 'Takeoff.added_by' or 'InputTakeoffItem.added_by'.
across all models it's implemented in.
How can I create a ABC to track both who created and last modified an Item?
Sorry after struggling with this for a couple days, I actually found the answer in Django's documentation. There are two variables(?) you can add to the related_name field:
%(app_label)s
%(class)s
This will make the related_name unique to each class inheriting from it.
Documentation here: https://docs.djangoproject.com/en/1.10/topics/db/models/#abstract-related-name

How to set related_name with a ManyToManyField using a through table and pointing to self

I have a task class, that can have sub-tasks, so it's a cyclic relationship. I'm passing it through a linker model/table like so:
class Task(models.Model):
title = models.CharField(max_length=100)
description = models.TextField(blank=True)
completed = models.BooleanField(default=False)
project = models.ForeignKey('Project', related_name="tasks")
dependancy = models.ManyToManyField('self', through='Dependancy', null=True,
blank=True, through_fields=('task', 'sub_task'), symmetrical=False)
def __str__(self):
return self.title
class Dependancy(models.Model):
task = models.ForeignKey(Task)
sub_task = models.ForeignKey(Task)
But I get this error:
ERRORS:
gantt_charts.dependency.sub_task: (fields.E303) Reverse query name for 'dependency.sub_task' clashes with field name 'Task.dependency'.
HINT: Rename field 'Task.dependency', or add/change a related_name argument to the definition for field 'dependency.sub_task'.
gantt_charts.dependency.sub_task: (fields.E304) Reverse accessor for 'dependency.sub_task' clashes with reverse accessor for 'dependency.task'.
HINT: Add or change a related_name argument to the definition for 'dependency.sub_task' or 'dependency.task'.
gantt_charts.dependency.task: (fields.E303) Reverse query name for 'dependency.task' clashes with field name 'Task.dependency'.
HINT: Rename field 'Task.dependency', or add/change a related_name argument to the definition for field 'dependency.task'.
gantt_charts.dependency.task: (fields.E304) Reverse accessor for 'dependency.task' clashes with reverse accessor for 'dependency.sub_task'.
HINT: Add or change a related_name argument to the definition for 'dependency.task' or 'dependency.sub_task'.
Obviously I need to set the related name on the Dependency.sub_task and Dependency.task field, and following the solution here is to name them something like task_task and task_sub_task, but that sounds wrong, unintuitive and confusing.
What would be a clear and concise name for them? It'd be easier if I wasn't getting confused over what related_names were, when using a linking table.
Given a Task instance, how can you access all the Dependencies that have that as their task? Or their sub_task? That's the purpose of related_nameā€”it provides the name for the attribute that Django will create on Task to point to that group of things.
You're seeing that error because Django automatically uses the name <model>_set, and since you have two ForeignKeys pointing to the same model, the default name will conflict.
Now, it might be that you never need to directly access Dependencies that way. If that's the case you can add related_name='+' to both fields and the reverse attribute won't be created at all (and your errors will disappear).
If you do want to access them, the name is up to you. I prefer longer-but-more-descriptive names to make their purpose clear. I might model the problem like this:
class Task(models.Model):
subtasks = models.ManyToManyField('self',
through='Dependancy',
symmetrical=False,
through_fields=('supertask', 'subtask'),
related_name='supertasks')
class Dependancy(models.Model):
supertask = models.ForeignKey(Task, related_name='dependencies_as_supertask')
subtask = models.ForeignKey(Task, related_name='dependencies_as_subtask')
class Meta:
unique_together = ('supertask', 'subtask')
>>> task = Task.objects.get()
>>> # all the Tasks that are supertasks of this one
>>> task.supertasks
>>> # all the Tasks that are subtasks of this one
>>> task.subtasks
>>> # all the Dependencies with this Task as the supertask
>>> task.dependencies_as_supertask
>>> # all the Dependencies with this Task as the subtask
>>> task.dependencies_as_subtask

django model query back reference filter confusion

Given the following models
class Category(models.Model):
name = models.CharField(max_length=50)
class Business(models.Model):
name = models.CharField(max_length=50)
category = models.ForeignKey(Category, related_name="businesses")
class Package(models.Model):
business_id = models.ForeignKey(Business)
status = models.CharField(max_length=50)
I have 2 following queries get list of business and categories which the packages are live:
filter_businesses = Business.objects.filter(package__status = 'live')
filter_categories = Category.objects.filter(businesses__package__status = 'live')
Now the questions is, given the related name "businesses" should be equals to category.business_set, why shouldn't the filter in first query be package_set?
Suppose you have two related models: SomeModel and SomeOtherModel, and SomeOtherModel.somemodel is a ForeignKey to SomeModel.
Given any SomeModel instance, the someothermodel_set property is a manager for the related model already filtered. For example:
>>> your_some_model_instance = SomeModel.objects.all()[0]
In this case your_some_model_instance.shomeothermodel_set is equivalent to:
>>> SomeOtherModel.objects.filter(somemodel=your_some_model_instance)
[ update ]
sorry perhaps I didn't explain my questions more clearer, it's complicated to explain... I understand that XX_set and related_name refer to the manager, what I want to ask is in the first query why not use (package_set_status = 'live') given the second working query (businesses_package__status = 'live'), it's confusing because the second query references to the manager(by related_name), but the first query is not...
The filter interface uses the convention relatedmodelname__relatedmodelfield; In your example, related_name was used to give a fancier name to the backreference, but this is not its main purpose; the purpose of the related_name parameter in ForeignKey fields is solving the ambiguity in cases where relatedmodelname clashes with an already existing field at the ForeignKey.

Error using a base class field in subclass unique_together meta option

Using the following code:
class Organization(models.Model):
name = models.CharField(max_length="100",)
alias = models.SlugField()
...
class Division(Organization):
parent_org = models.ForeignKey(Organization)
class Meta:
unique_together=['parent_org', 'alias']
...
Trying to syncdb give me this error:
Error: One or more models did not validate:
organizations.division: "unique_together" refers to alias. This is not in the
same model as the unique_together statement.
Any help is appreciated,
Thanks,
Eric
This is by design. Reading the documentation for the unique_together option, it states that:
It's used in the Django admin and is enforced at the database level.
If you look at the table that a subclass creates, you'll see that it doesn't actually have the fields that its parent has. Instead, it gets a soft Foreign Key to the parent table with a field name called [field]_ptr_id, where [field] is the name of the table you're inheriting from excluding the app name. So your division table has a Primary Foreign Key called organization_ptr_id.
Now because unique_together is enforced at the database level using the UNIQUE constraint, there's no way that I know of for the database to actually apply that to a field not in the table.
Your best bet is probably through using Validators at your business-logic level, or re-thinking your database schema to support the constraint.
Edit: As Manoj pointed out, you could also try using Model Validators such as validate_unique.
[Model] Validators would work for you. Perhaps simplest, though, would be to use:
class BaseOrganization(models.Model):
name = models.CharField(max_length="100",)
alias = models.SlugField()
class Meta:
abstract = True
class Organization(BaseOrganization):
pass
class Division(BaseOrganization):
parent_org = models.ForeignKey(Organization)
class Meta:
unique_together=['parent_org', 'alias']
Note: as with your current code, you could not have subdivisions of divisions.
This is a solution I recently used in Django 1.6 (thanks to Manoj Govindan for the idea):
class Organization(models.Model):
name = models.CharField(max_length="100",)
alias = models.SlugField()
...
class Division(Organization):
parent_org = models.ForeignKey(Organization)
# override Model.validate_unique
def validate_unique(self, exclude=None):
# these next 5 lines are directly from the Model.validate_unique source code
unique_checks, date_checks = self._get_unique_checks(exclude=exclude)
errors = self._perform_unique_checks(unique_checks)
date_errors = self._perform_date_checks(date_checks)
for k, v in date_errors.items():
errors.setdefault(k, []).extend(v)
# here I get a list of all pairs of parent_org, alias from the database (returned
# as a list of tuples) & check for a match, in which case you add a non-field
# error to the error list
pairs = Division.objects.exclude(pk=self.pk).values_list('parent_org', 'alias')
if (self.parent_org, self.alias) in pairs:
errors.setdefault(NON_FIELD_ERRORS, []).append('parent_org and alias must be unique')
# finally you raise the ValidationError that includes all validation errors,
# including your new unique constraint
if errors:
raise ValidationError(errors)
This does not strictly apply the question but is very closely related; unique_together will work if the base class is abstract. You can mark abstract model classes as such using:
class Meta():
abstract = True
This will prevent django from creating a table for the class, and its fields will be directly included in any subclasses. In this situation, unique_together is possible because all fields are in the same table.
https://docs.djangoproject.com/en/1.5/topics/db/models/#abstract-base-classes
I had similar challenge when trying to apply unique_together to a permission group created by a given client.
class ClientGroup(Group):
client = models.ForeignKey(Client, on_delete=models.CASCADE)
is_active = models.BooleanField()
class Meta():
# looking at the db i found a field called group_ptr_id.
# adding group_ptr did solve the problem.
unique_together = ('group_ptr', 'client', 'is_active')