Django. Foreign key - django

I have 2 tables:
Side and AdditionalCost
now AdditionalCost has the following foreign key field:
side = models.ForeignKey(Side, on_delete=models.CASCADE, related_name='costs')
I want to have another foreign key field in AdditionalCosts:
number_of_additional_installations = models.ForeignKey(Side, on_delete=models.CASCADE, related_name="number_of_additional_installations")
The Side model has the following field:
number_of_additional_installations = models.IntegerField(null=True, blank=True,
db_column='number_of_additional_installations',
verbose_name='Количество доп монтажей')
But I get the following error:
ERRORS:
<class 'kinetics.apps.address_program.admin.AdditionalCostInline'>: (admin.E202) 'address_program.AdditionalCost' has more than one ForeignKey to 'address_program.Side'.
address_program.AdditionalCost.number_of_additional_installations: (fields.E302) Reverse accessor for 'AdditionalCost.number_of_additional_installations' clashes with field name 'Side.number_of_additional_installations'.
HINT: Rename field 'Side.number_of_additional_installations', or add/change a related_name argument to the definition for field 'AdditionalCost.number_of_additional_installations'.
address_program.AdditionalCost.number_of_additional_installations: (fields.E303) Reverse query name for 'AdditionalCost.number_of_additional_installations' clashes with field name 'Side.number_of_additional_installations'.
HINT: Rename field 'Side.number_of_additional_installations', or add/change a related_name argument to the definition for field 'AdditionalCost.number_of_additional_installations'.
I cannot figure out why this happened because I see that code has these lines:
buyer_org = models.ForeignKey("acl.Organization", on_delete=models.SET_NULL, null=True, blank=True,
related_name='buyer_costs')
client_org = models.ForeignKey("acl.Organization", on_delete=models.SET_NULL, null=True, blank=True,
related_name='client_costs')
that are obviously two foreign fields that relate to columns of one model.
If you need full code of the models let me know, it is quite large but I can add it if you need. Thank you
p.s. If I rename the related_name of number_of_additional_installations, i still get the following error:
ERRORS:
<class 'kinetics.apps.address_program.admin.AdditionalCostInline'>: (admin.E202) 'address_program.AdditionalCost' has more than one ForeignKey to 'address_program.Side'.

This is because you already have
number_of_additional_installations defined in Sides which clashes with the related name you set in AdditionalCosts. Either you rename the field in Sides or you change the related_name parameter so it wont confuse Django which field to resolve.
This code:
buyer_org = models.ForeignKey("acl.Organization", on_delete=models.SET_NULL, null=True, blank=True,
related_name='buyer_costs')
client_org = models.ForeignKey("acl.Organization", on_delete=models.SET_NULL, null=True, blank=True,
related_name='client_costs')
is not the same case. These fields do not cause conflict because they are referring to different relationships even if they are referring to the same model. model.buyer_org and model.client_org returns different relationships. model.buyer_org maps with organization.buyer_costs which is exclusive of model.client_org which, in turn, maps to organization.client_costs.
What's happening with the way you specify Sides and AdditionalCosts though creates conflict.
You can call side.number_of_additional_installations which returns an integer field but at the same time you are telling django that the additional_costs.number_of_additional_installations map to side.number_of_additional_installations.

Related

What exactly the reverse query name in its related model in Django?

I got pretty much the same problem as in Django - reverse query name clash.
for the following code:
class Category(models.Model):
title = models.CharField(max_length=255)
featured_product = models.ForeignKey(
'Product', on_delete=models.SET_NULL, null=True)
class Product(models.Model):
title = models.CharField(max_length=255)
price = models.DecimalField(max_digits=6, decimal_places=2)
category = models.ForeignKey(Category, on_delete=models.PROTECT)
There will be an error saying:
store.Category.featured_product: (fields.E303) Reverse query name for 'store.Category.featured_product' clashes with field name 'store.Product.category'.
I know it the featured_productin Category should addrelated_name="+" to avoid this error. But since this is a many-to-one case, and I think the reverse query name for store.Category.featured_productinProductclass should be store.Product.category_set so it shouldn't have a conflict with store.Product.category.
Even if there weren't outright clashes (with the snippet you have, the generated reverse accessors should be Product.category_set and Category.product_set), I'd say Django is trying to be helpful so you don't make mistakes regarding the reverse accessors down the line.
As you noted, you can set related_name='+' to not create the reverse accessor at all, but I'd probably name the reverse accessor something like related_name='featured_product_for_categories'.
After all, your data model does make it possible for a product to be featured in multiple categories, and even categories it isn't part of.

Bypassing the unique constraint when defining a foreign key to just one field in Django

I've a model as shown below:
class Instance(models.Model):
data_version = models.IntegerField(default=1, unique=True)
name = models.CharField(max_length=200)
The data_version field has to be related to all other models in the application:
class Source(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=200)
version = models.ForeignKey(Instance, on_delete=models.CASCADE, to_field='data_version', null=True)
The problem here is that Django requires the field data_version to be a unique field for me to be able to define such a relationship but that simply doesn't fit into my use case. I need to have multiple Instance objects in the app, each with version numbers starting from 1.
What I'd like is to have a unique constraint on the combination of name and data_version but then Django doesn't allow defining Foreign key relationships as shown above. Is there a way I can bypass this restriction?

"already exists" error when using ManyToMany relationship between the same two models

class Product( models.Model ):
name = models.CharField(verbose_name="Name", max_length=255, null=True, blank=True)
the_products_inside_combo = models.ManyToManyField('self', verbose_name="Products Inside Combo", help_text="Only for Combo Products", blank=True)
However, I got this error when I tried to put the duplicate values:
From_product-to_product relationship with this From product and To
product already exists.
Screencap of the error.
Each pair (Product, Product) must be unique. This is why you get already exists error.
Behind the scenes, Django creates an intermediary join table to
represent the many-to-many relationship.
What do you want to do is to have many-to-many relationship between two models (nevermind that they are the same) with additional information stored - quantity (so you would have ProductA = 2x ProductB + ....
In order to model this relationship you will have to create intermediary model and use through option. Documentation explains it very well, so have a look:
https://docs.djangoproject.com/en/dev/topics/db/models/#intermediary-manytomany
Update
Here is minimal working example:
class Product(models.Model):
name = models.CharField(verbose_name='Name', max_length=255, null=True, blank=True)
products = models.ManyToManyField('self', through='ProductGroup', symmetrical=False)
def __str__(self):
return self.name
class ProductGroup(models.Model):
parent = models.ForeignKey('Product', related_name='+')
child = models.ForeignKey('Product', related_name='+')
and admin models:
class ProductGroupInline(admin.TabularInline):
model = Product.products.through
fk_name = 'parent'
class ProductAdmin(admin.ModelAdmin):
inlines = [
ProductGroupInline,
]
admin.site.register(Product, ProductAdmin)
admin.site.register(ProductGroup)
As you can see recursive Product-Product relation is modeled with ProductGroup (through parameter). Couple of notes:
Many-to-many fields with intermediate tables must not be symmetrical, hence symmetrical=False. Details.
Reverse accessors for ProductGroup are disabled ('+') (in general you can just rename them, however, you don't want to work with ProductGroup directly). Otherwise we would get Reverse accessor for 'ProductGroup.child' clashes with reverse accessor for 'ProductGroup.parent'..
In order to have a nice display of ManyToMany in admin we have to use inline models (ProductGroupInline). Read about them in documentation. Please note, however, fk_name field. We have to specify this because ProductGroup itself is ambiguous - both fields are foreign keys to the same model.
Be cautious with recurrency. If you would define, for example, __str__ on Product as: return self.products having ProductGroup with the same parent as the child you would loop infinitely.
As you can see in the screencap pairs can be duplicated now. Alternatively you would just add quantity field to ProductGroup and check for duplication when creating objects.

m2m for existing table (through/unique_together)

I have found in internet different examples on how to handle m2m relations with existing DB models, such as ex1 or here ex2, however I'm still not able to solve the error I get.
My models are depicted below. Basically, all the tables where created manually.
I got the following error message:
OperationalError: (1054, "Unknown column 'supervisor_project.id' in 'field list'").
I'm still a bit confused on when to use unique_together with through. Do you see any errors in the model below? The table supervisor_project has no id field and its PK is composed actually of two FK's, i.e. surrogate PK.
class Supervisor(models.Model):
name = models.CharField(max_length=45, blank=True, null=True, help_text="Name, e.g. John Smith")
class Meta:
managed = False
db_table = 'supervisor'
def __unicode__(self):
return self.name
class Project(models.Model):
title = models.CharField(max_length=45, blank=True, null=True)
supervisors = models.ManyToManyField(Supervisor, through='SupervisorProject', through_fields=('project', 'supervisor'))
class SupervisorProject(models.Model):
supervisor = models.ForeignKey('Supervisor', on_delete=models.CASCADE)
project = models.ForeignKey('Project', on_delete=models.CASCADE)
class Meta:
managed = False
db_table = 'supervisor_project'
unique_together = (('supervisor', 'project'),)
Django requires each model to have exactly one primary key field. It doesn't support multiple-column primary keys yet.
Since you haven't explicitly defined a primary key on the SupervisorProject model, Django assumes that there is an automatic primary key field id. When it includes the id field in a query, you get the error because it doesn't exist.
If possible, I would add an auto-incrementing id column to each intermediate table. There isn't a way to get Django to add the column to the tables automatically. You have set managed=False, so Django expects you to manage the database table.

Getting all unique values stored in a many-to-many field

I have a many-to-many field called categories and I would like to get distinct values stored in that field.
The following is my model:
class Book (models.Model):
categories=models.ManyToManyField(Category, related_name = 'categories', blank = True, null=True)
Here is my Category model:
class Category (MPTTModel):
category = models.CharField(max_length=250)
parent = TreeForeignKey('self', blank=True, null=True, related_name='children')
I'd like to get every single one of the categories related to a book. How would I do this?
If you want to get categories related to one instance of book, do book_inst.category_set.all(). There will not be duplicates.
But I think, you want to get all Categories which are related to any Book, you can do:
Category.objects.filter(categories__in=[Book.objects.all()]).distinct()
Basically, you need a reverse lookup from the category side to check if there is book for that category if yes, add to the resultant query set. Since, the related_name argument in the Book is 'categories', your reverse lookup would look something like this.
Category.objects.filter(categories__in = Book.objects.all())