I've got these Car and Bike models I can't modify.
They may need to be linked to a Ferry model which I can modify. I want to implement ordering of these elements of two different models in the database and I want to avoid using Generic Foreign Keys.
So far this is what I've come up with:
class Car(models.Model):
pass
class Bike(models.Model):
pass
class Ferry(models.Model):
pass
class Lot(models.Model):
position = SmallInteger()
car = models.ForeignKey(to=Car, null=True, related_name="ferries")
bike = models.ForeignKey(to=Bike, null=True, related_name="ferries")
ferry = models.ForeignKey(to=Ferry, null=False, related_name="load")
Now my goal is to be able to access directly all the elements of a particular Ferry – be they cars or bikes – ordered by position, and all the ferries (in the context of ferry travel bookings there may be several) of a particular car or bike:
some_ferry.load.all().order_by("position")
some_car.ferries.all()
How do I create these relations, including a sort of combination of (Car + Bike)?
So you can put ordering on the table or as you make a query and you can use django's __ notation to use fields on related objects (more here)
For example;
class Car(models.Model):
class Meta:
"""
Metadata
"""
ordering = ('ferries__position', )
class Bike(models.Model):
class Meta:
"""
Metadata
"""
ordering = ('ferries__position', )
class Ferry(models.Model):
class Meta:
"""
Metadata
"""
ordering = ('load__position', )
class Lot(models.Model):
position = SmallInteger()
car = models.ForeignKey(to=Car, null=True, related_name="ferries")
bike = models.ForeignKey(to=Bike, null=True, related_name="ferries")
ferry = models.ForeignKey(to=Ferry, null=False, related_name="load")
class Meta:
"""
Metadata
"""
ordering = ('position', )
Or in your queries it might be Bike.objects.all().order_by('ferries__position')
If you define the ordering in the model meta, then that'll be the default ordering for all queries on that model so you don't then have to worry about it when you're making queries or looking at admin etc.
I don't see a way you can achieve what you want without using Generic Foreign Key, since the relationship you envision does not exist in Django (and, actually, is not a nicely normalized relational database pattern. Other database paradigms, such as OODB and GraphDB do include this pattern).
What you can do is to define a method on class Ferry that would query for all instances of Cars and Bikes and join them in a single resultset.
Another option is to use library django-model-utils, which would allow you to define a model, let's say, Transportation, that would act as some sort of virtual model, and you can define models Bike and Car as subclasses of it. With this solution, you can create a foreign key on Lot pointing to Transportation, and you can query for Bike, Car, or the generic Transportation instances.
Related
I am new in Django an have trouble figuring out the right way of making model inheritance.
Let assume that I am making some kind of food app. I would then have a model for meet, a model for fruit, a model for vegetables and so on.
My question is: How can I make a nutrition content model, which can inherit from all of the above models but only from one at a time? For instance nutrition content of apple should only inherit from the fruit model.
If there was only one food model type I would use ForeignKey to handle the inheritance. I guess this is not an option when there are several options for models to inherit from.
I would like to use on_delete=models.CASCADE for the nutrition content model. Hence the request for inheritance.
Any suggestions will be appreciated.
Python class inheritance and Django model ForeignKey relationships are two completely different things. It sounds like you're referring to the latter.
Are the fields so different between the different food types that you actually need a different model for each one? The simplest way would be to just have single Food model for all food types.
class Food(models.model):
VEGETABLE = 'vegetable'
MEAT = 'meat'
FRUIT = 'fruit'
TYPE_CHOICES = [
(VEGETABLE, 'vegetable'),
(MEAT, 'meat'),
(FRUIT, 'fruit'),
]
type = models.CharField(max_length=10, choices=TYPE_CHOICES)
nutrition_content = models.OneToOneField('NutritionContent', on_delete=models.CASCADE)
# additional fields
class NutritionContent(models.Model):
# additional fields
If your fields are so different between food types that you need to have different models for each one, you can set up Food as a parent model that all child food type models have a OneToOneField relationship with. Then the NutritionContent model can still link with Food.
class Food(models.model):
nutrition_content = models.OneToOneField('NutritionContent', on_delete=models.CASCADE)
# fields that apply to all foods
class Vegetable(models.Model):
food = models.OneToOneField('Food', on_delete=models.CASCADE)
# additional fields that only apply to vegetables
class Meat(models.Model):
food = models.OneToOneField('Food', on_delete=models.CASCADE)
# additional fields that only apply to meat
class Fruit(models.Model):
food = models.OneToOneField('Food', on_delete=models.CASCADE)
# additional fields that only apply to fruit
class NutritionContent(models.Model):
# additional fields
Suppose I have following models:
class Author(Model):
name = CharField()
class Publication(Model):
name = CharField()
authors = ManyToManyField(Author)
class Meta:
abstract = True
class Book(Publication):
pass
class Article(Publication):
pass
class Journal(Publication):
pass
How to change code so that I can add through table to authors? If I write authors = ManyToManyField(Author, through='Relationship'), it will not work.
Django thankfully takes care of intermediate tables without any coding. You can even access them using .through on the M2M relationship manager, e.g. one_publication.authors.through.
You only need to specify the through table if you want to manage it yourself, e.g. because you want to add more fields than just the foreign keys of the two related entities. Is that the case here?
If yes, you have to create a Relationship model (consider giving it a more helpful name) that contains foreign keys to Publication and Author.
Update: If you want to add a default order to object lists from many-to-many relationships, an intermediate model would indeed be one way to achieve this:
class Relationship(models.Model):
author = models.ForeignKey(Author)
publication = models.ForeignKey(Publication)
# Any further fields that you need
class Meta:
ordering = ['author__last_name', 'author__first_name']
However, you can just as easily order your m2m relationships on querying them, without any intermediate model or through manager:
book = Book.objects.first()
for author in book.authors.order_by('last_name', 'first_name'):
# Will print in alphabetical order
print(f'Author: {author.first_name} {author.last_name}')
One caveat is that if you use prefetching, you need to specify the ordering in a Prefetch object to avoid executing the query twice, first for prefetching without ordering, than on access with ordering.
# 2 queries plus one for every book
books = Book.objects.prefetch_related('authors')
for book in books:
for author in book.authors.order_by('last_name', 'first_name'):
print(f'Author: {author.first_name} {author.last_name}')
# 2 queries regardless of number of books
books = Book.objects.prefetch_related(Prefetch('authors',
queryset=Author.objects.order_by('last_name', 'first_name')))
for book in books:
for author in book.authors.all():
print(f'Author: {author.first_name} {author.last_name}')
Here's a version of my models.py file. I've removed irrelevant fields and the model names are made up, for security reasons:
class FilmStudio(models.Model):
name = models.CharField(max_length=200, unique=True)
class ProductionCompany(models.Model):
name = models.CharField(max_length=200)
film_studio = models.ForeignKey(FilmStudio)
class Meta:
# Ensure that a given combination of ProductionCompany name and FilmStudio object is unique
unique_together = ('name', 'film_studio')
class Film(models.Model):
title = models.CharField(max_length=200)
production_company = models.ForeignKey(ProductionCompany)
class Actor(models.Model):
name = models.CharField(max_length=200)
films = models.ManyToManyField(Film, blank=True)
Although it is not explicitly defined, there is a many-to-many relationship between an Actor and a FilmStudio. This is evidenced by the following call to the Python API:
FilmStudio.objects.filter(productioncompany__film__actor__name='Samuel L. Jackson').distinct()
This returns all of the FilmStudio objects which Samuel L. Jackson is related to, and each one only once. What I'd like is to define extra fields on the relationship between an Actor and a FilmStudio (it doesn't work too well in this example, I know, but it makes sense for my scenario).
Following what is described in Extra fields on many-to-many relationships, I could use an intermediate model to define extra fields on the relationship between a Film and an Actor, for instance.
But this doesn't seem to help me with my problem. I don't want to define the Actor to FilmStudio relationship explicitly, since it's an existing relationship based on other relationships.
Is it possible to define fields on the relationship that I'm describing?
As far as I know, you are not able to do that.
The reason for that is that it is nowhere to store the extra fields of that relationship. If I understand you correctly, these "extra fields" are not implicit in the actor-film or productionstudio-film relationships, so even though you say they are implicit, the extra fields themselves are explicit.
You could try to emulate it by creating an explicit direct relationship whenever it is needed. Then you could simulate the extra fields using the model as an abstraction, but I am not sure if this is what you want. If you opt for this kind of solution you can use default values (in your abstraction) for filling in relationships that don't have a instance yet.
Does that explanation make sense to you?
EDIT:
(I have not double checked that the code works, so be vary)
OK, so you have the original models:
class FilmStudio(models.Model):
name = models.CharField(max_length=200, unique=True)
class ProductionCompany(models.Model):
name = models.CharField(max_length=200)
film_studio = models.ForeignKey(FilmStudio)
class Meta:
# Ensure that a given combination of ProductionCompany name and FilmStudio object is unique
unique_together = ('name', 'film_studio')
class Film(models.Model):
title = models.CharField(max_length=200)
production_company = models.ForeignKey(ProductionCompany)
class Actor(models.Model):
name = models.CharField(max_length=200)
films = models.ManyToManyField(Film, blank=True)
# The "solution" would be:
class ActorProductionComapny(models.Model):
production_company = models.ForeignKey(ProductionCompany, related_name='actors')
actor = models.ForeignKey(Actor, related_name='companies')
# your extra fields here
someproperty = models.CharField(max_length=200)
class Meta:
# let's say one per actor
unique_together = ('production_company', 'actor')
This is going to get messy really quickly
We use a F object like this:
FilmStudio.objects.filter(productioncompany__film__actor__name='Samuel L. Jackson',
productioncompany__film__actor=F('actors__actor'),
actors__someproperty="Plays poker with CEO").distinct()
The tricky part is going to be handling default values (i.e. when there is no value) This would have to be implemented using a custom Manager, but then I am out of my depth.
I will try to explain as well as I can, but it's going to be tricky.
If you want to make a filter on the relationship you may have to do something like this:
def filter_prod(pq_query, someproperty, actor_name):
if someproperty == "Default":
# Great, this means we can ignore the parallel relationship:
return pq_query.filter(productioncompany__film__actor__name=actor_name)
else:
# Here comes the hard part
FilmStudio.objects.filter(productioncompany__film__actor__name=actor_name,
productioncompany__film__actor=F('actors__actor'),
actors__someproperty=someproperty).distinct()
The thing I am trying to illustrate here is that there are two kinds of actor-productioncompany relationships, those with custom field values (non-default), and those without.
Now, you can make a custom getter that looks something like this:
class ProductionCompany(models.Model):
name = models.CharField(max_length=200)
film_studio = models.ForeignKey(FilmStudio)
def get_actors(self):
# This one is not lazy, so be aware
actors = list(self.actors)
# Get a list of actor IDs
actor_ids = [a.actor_id for a in actors]
for actor in Actor.objects.filter(films__production_company_id=self.id):
if actor.id not in actor_ids:
actors.append(ActorProductionComapny(actor=actor, production_company=self)
actor_ids.append(actor.id)
return actors
class Meta:
# Ensure that a given combination of ProductionCompany name and FilmStudio object is unique
unique_together = ('name', 'film_studio')
This should not save the relationship to the database until you call .save() on an instance. You can also add a custom save method that ignores/aports .save() calls where all the values are default. Just remember to check if it is a new instance or not, because you don't want it to cancel a "set back to default" call. You could also make it delete on a "set back to default", but check if you are allowed to do that within .save().
For even more complex queries (mix of default and non-default) you have Q-objects (further down on the page from F objects)
In short, you need to create an extra model to store this extra relational data between Actor and FilmStudio.
class Actor(models.Model):
name = models.CharField(max_length=200)
films = models.ManyToManyField(Film, blank=True)
film_studios = models.ManyToMany(FilmStudio, through='ActorFilmStudio')
class ActorFilmStudio(models.Model):
actor = models.ForeignKey(Actor)
film_studio = models.ForeignKey(FilmStudio)
# define extra data fields here
data1 = models.TextField()
data2 = models.IntegerField()
One way to think about this: the data you're trying to store belongs to an Actor-FilmStudio relation, and is not related in anyway to Film or ProductionCompany.
Your existing ability to retrieve the a set of Actors for a given FilmStudio (or vice-versa) does not necessarily imply you can store relational data belonging to these two models using the models defined in your example.
Keep in mind that each of the models you defined in your example are backed by a table in your database. In the case of Actor.films field, Django creates an extra table to store the many-to-many relationship data.
Since you're looking to store relational data between Actor and FilmStudio, you need to consider where the data will be stored in your database. Can you store the data in the Film model? or the ProductionCompany model?
I am new to Django and databases and after reading the Django documentation on models I have the following question:
Let's say I have 3 models: VehicleName, CarManufacturer and TruckManufacturer. I am trying to create a database relationship where CarMaunfacturer has many VehicleNames and also TruckManufacturer has many VehicleNames. What is the relationship here and how to define it in Django? Is it as simple as define a models.ForeignKey(VehicleName) in both CarManufacturer and TruckManufacturer?
Thanks.
from django.db import models
class CarManufacturer(models.Model):
vehicle_name = models.ForeignKey(VehicleName) # IS THIS CORRECT???
# ...
pass
class TruckManufacturer(models.Model):
vehicle_name = models.ForeignKey(VehicleName) # IS THIS CORRECT???
# ...
pass
class VehicleName(models.Model):
# ...
To do exactly what you're describing:
I am trying to create a database relationship where CarMaunfacturer has many VehicleNames and also TruckManufacturer has many VehicleNames
You'd create a nullable foreign key on VehicleName to both of your Manufacturer models:
class CarManufacturer(models.Model):
# field definitions here
class TruckManufacturer(models.Model):
# field definitions here
class VehicleName(models.Model):
car_manufacturer = models.ForeignKey(CarManufacturer, blank=True, null=True)
truck_manufacturer = models.ForeignKey(TruckManufacturer, blank=True, null=True)
Then, instances of CarManufacturer or TruckManufacturer can get the names via the vehiclename_set attribute.
For a more advanced design, I would probably try to abstract the shared manufacturer behavior into a single model, then use multi-table inheritance:
class Manufacturer(models.Model):
# shared car and truck manufacturer fields go here
class CarManufacturer(Manufacturer):
# car manufacturer specific fields go here
class TruckManufacturer(Manufacturer):
# truck manufacturer specific fields go here
class VehicleName(models.Model):
manufacturer = models.ForeignKey(Manufacturer)
See the multi-table inheritance docs for full details.
I do not think you are understanding the manufacturer to vehicle relationship property. What I think you are trying to show is that a certain Vehicle belongs to a certain manufacturer.
This type of relationship would actually be defined in the Vehicle class, as a foreign key, called manufacturer, in the Vehicle class.
In the case you are defining many vehicles under a manufacturer, you just need to rename the property to car_model or something of the like and you should be fine.
I think you have the understanding mapped out well enough. Just remember that foreign keys are only a property of one table, and say nothing about the other table itself until the relationship is established there also.
If you're working with a larger relationship, with multiple objects, you should look into using the Many-to-many field described in the django documentation.
They have an example that shows how an Articles have many Publications:
class Publication(models.Model):
title = models.CharField(max_length=30)
# On Python 3: def __str__(self):
def __unicode__(self):
return self.title
class Meta:
ordering = ('title',)
class Article(models.Model):
headline = models.CharField(max_length=100)
publications = models.ManyToManyField(Publication)
# On Python 3: def __str__(self):
def __unicode__(self):
return self.headline
class Meta:
ordering = ('headline',)
Let's say I have two models looking like this:
class ProductType(models.Model):
product_type = models.CharField(max_length=100)
class Product(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField()
product_type = models.ForeignKey(ProductType)
score = models.PositiveIntegerField(default=0)
Now I want to fetch the top two products (the two with highest score) from each ProductType.
So if I have Phones, Computers, TVs as ProductTypes I want the two best Phones, Computers, TVs.
As I don't even know how to do this in MySQL I've tried searching for the MySQL-solution but the ones I find is extremely complex and this doesn't feel like an extremely complex thing to do.
I am leaning towards making an own model for the top products and have a cronjob to fix this, but I'd like to see if there's an easier solution to this.
Well, you could make a method inside of the ProductType class that returns the top two results for its products:
class ProductType(models.Model):
product_type = models.CharField(max_length=100)
def TopTwoProducts(self):
return self.product_set.order_by('-score')[0:2]
Then you would just do something like:
for type in ProductType.objects.all():
type.TopTwoProducts()
While adam's solution is correct, a more django-ish way would be to use a manager.
See Managers versus class methods on James Bennett's blog
Among other advantages :
a manager carries all query-specific code, while avoiding to clutter the model class
the same manager class can be shared among several classes
the manager can be used directly on a model class, or via a one-to-many or m2m relation
Thus, for the above question :
from django.db import models
from django.db.models.manager import Manager
class ProductScoreManager(Manager):
use_for_related_field = True
def top_score(self, limit=2):
return self.get_query_set().order_by('-score')[:limit]
Then add this manager class as default manager for Product :
class Product(models.Model):
...
objects = ProductScoreManager()
...
Now, thanks to objects overriding the default manager, and use_for_related_field allowing its use in relation queries, the top_score method can be used in any model related to products.
myproducttype = ProductType.objects.get(...)
myproducttype.products.top_score() # return top 2 products
This allows a more consistent syntax : the related products is always accessed via products, top_score acting as a filter. Additionally, ProductType class is no more cluttered with Product's query logic.
Just filter out product type from Product model slice them like this -
product_type_list = ProductType.objects.value("id")
for product_type in product_type_list:
Product.objects.filter(
product_type=product_type
).order_by("-score")[0:2]