I can't wrap my head how to store / define the right model / relationship.
I have following data:
CATEGORY is their own model because i like to view / request in some situation only the category. But to this category I want set VALUE which I can only define onetime depending ON YYYY-MM.
What make me difficult to achieve / understand how i get the relationship between VALUE <-> YYYY-MM but still maintain the connection / dependency to only one CATEGORY "Rent".
I would look at doing something like this. You have two models with a foreign key, and value and YYYY-MM are attributes of the model with the foreign key.
class Category(models.model):
pass
class TimePeriod(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
value = models.IntegerField()
start_date = models.DateField()
class Meta:
constraints = [
models.UniqueConstraint(
fields=["category", "start_date"],
name="unique_start_date_per_category"
),
]
EDIT: Add UniqueConstraint
Related
Imagine there are three models named Movie, Actor, and Participation.
class Movie(models.Model):
identifier = models.CharField()
class Actor(models.Model):
name = models.CharField()
class Participation(models.Model):
movie_identifier = models.CharField()
actor = models.ForgeinKey(Actor, on_delete=models.CASCADE)
Let's assume that I can't use ForgeinKey for the movie in the Participation model.
how can I retrieve all the participation records of a movie with only one query?
Here is the solution if I had a foreign key for the movie in the participation table:
qs = Movie.objects.filter(identifier="an_identiier").prefetch_related("participations_set")
How can I do this without having a Movie foreign key in the Participation model?
Thanks!
One of the most important things when designing a database (hence when designing your models) is database normalization [Wikipedia].
You talk about Participation being related to multiple models like Movie, Series, Episode, etc. this means that Movie, Series, Episode all can be said to have something in common or they can be said to be a specialization of another entity let us say Participatable for the lack of a better word, or we can say Participatable is a generalization of Movie, Series, Episode, etc.
How do we model these? Well we will just have an extra model that our other models will have a OneToOneField with:
class Participatable(models.Model):
# Any common fields here
MOVIE = 'M'
SERIES = 'S'
TYPE_CHOICES = [
(MOVIE, 'Movie'),
(SERIES, 'Series'),
]
subject = models.CharField(max_length=1, choices=TYPE_CHOICES)
class Movie(models.Model):
# uncommon fields
participatable = models.OneToOneField(
Participatable,
on_delete=models.CASCADE,
related_name='movie',
)
class Series(models.Model):
# uncommon fields
participatable = models.OneToOneField(
Participatable,
on_delete=models.CASCADE,
related_name='series',
)
class Participation(models.Model):
participatable = models.ForgeinKey(Participatable, on_delete=models.CASCADE)
actor = models.ForgeinKey(Actor, on_delete=models.CASCADE)
Other than this solution which I find is the best for such modelling you can go with using the content-types framework which will essentially do what you do currently. That is it will use a field that stores the related id and also a foreign key that points to an entry in a table that will simply describe which table this id is for.
I have a shirt which can contain multiple colors, and multiple colors which can have multiple shirts. Normally I would express it the following way:
In django I have the many-to-many (https://docs.djangoproject.com/en/3.1/topics/db/examples/many_to_many/)
Example:
publications = models.ManyToManyField(Publication)
--
Can I create the table "Item_colors" consisting of 2 columns (no "ID" primary key) and design the models according to my diagram using the composite key:
class Item_colors(models.Model):
class Meta:
unique_together = (('cloth_item_id', 'color_id'),)
cloth_item_id = models.ForeignKey(Cloth_item, on_delete=models.CASCADE)
color_id = models.ForeignKey(Color, on_delete=models.CASCADE)
How is the many-to-many relation handled in a DB context, and does it yield better performance?
EDIT: https://code.djangoproject.com/wiki/MultipleColumnPrimaryKeys no avoiding primary keys in favor of composite keys saving columns :( at least for now..
How is the many-to-many relation handled in a DB context, and does it yield better performance?
With a junction table in the middle, so with an item_colors table. But the table contains a primary key, as does every model in Django.
If you do not specify a through=… parameter [Django-doc] to define the model for the junction table yourself, Django will automatically create such model. This model then has two ForeignKeys to the two models it connects as discussed in the database representation section of the documentation:
Behind the scenes, Django creates an intermediary join table to represent the many-to-many relationship. By default, this table name is generated using the name of the many-to-many field and the name of the table for the model that contains it. Since some databases don’t support table names above a certain length, these table names will be automatically truncated and a uniqueness hash will be used, e.g. author_books_9cdf. You can manually provide the name of the join table using the db_table option.
But the table thus has a primary key. This might be useful if the same object occurs a second time in the relation.
You can access the through model in the Article-Publication example for example with:
Article.publications.through
You thus can define a through model yourself, for example with:
class Color(models.Model):
color = models.CharField(max_length=128)
class ClothItem(models.Model):
item_name = models.CharField(max_length=128)
colors = models.ManyToManyField(
Color,
related_name='cloth_items'
through='ClothItemColors'
)
class ClothItemColors(models.Model):
cloth_item = models.ForeignKey(ClothItem, on_delete=models.CASCADE)
color = models.ForeignKey(Color, on_delete=models.CASCADE)
class Meta:
db_table = 'item_colors'
constraints = [
models.UniqueConstraint(
fields=('cloth_item', 'color'),
name='unique_cloth_color'
)
]
often an explicit through model is used to store extra information, for example the quantity:
class ClothItemColors(models.Model):
cloth_item = models.ForeignKey(ClothItem, on_delete=models.CASCADE)
color = models.ForeignKey(Color, on_delete=models.CASCADE)
quantity = models.IntegerField(default=0)
# …
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.
I have models like this:
class Vendor(models.Model):
title = models.CharField()
class Product(models.Model):
...
vendor = models.ForeignKey(Vendor, null=True, blank=True)
stock = models.ManyToManyField(Supplier, through='Stock')
class Stock(models.Model):
in_stock = models.BooleanField(default=True)
supplier = models.ForeignKey('catalog.Supplier', related_name='supplier_stock')
product = models.ForeignKey('catalog.Product', related_name='product_stock')
priority = models.IntegerField(default=0)
I designed models like this, because one Product can be supplied by different suppliers, and I need to know, what supplier exactly has this Product in stock.
So, in my view I want to get all results in values, to reduce number of queries and some specific logic. Also it duplicates me Product row with different Stock, by in python I group them up.
In my view I use:
Product.objects.all().values(
'id', 'title', 'vendor_code', 'vendor__title', 'price',
'product_stock__in_stock', 'stock__title', 'stock__id', 'stock__priority')
Because of INNER JOIN and null=True for Vendor related model, it returns me not all records for Product model. It just returns values where Vendor reference is set.
If I use 'vendor' instead of 'vendor__title' it returns me more results, than previous one, because in vendor field I can get {...'vendor': *id goes here*...} or {...'vendor': None...}, but I need the vendor__title value there. So any suggestions, how to achieve this?
Thanks in advance
Changed from vendor__title to product_stock__product__vendor__title helped me to fix my problem.
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.