Model relationships in Django - django

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',)

Related

How to implement element ordering accross two different models in Django?

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.

Django Filtering Many-to-Many relationship without ManyToManyField "through" relationship

Given these models:
class Event(models.Model):
name = models.CharField()
class Project(models.Model):
name = models.CharField()
class EventProject(models.Model):
event= models.ForeignKey(Event)
project= models.ForeignKey(Project)
Is there a way to get all the Events for each Project like this: with the property project.events = [<array of Event>]?
I found a similar question, however one of the tables has the members = models.ManyToManyField(Person, through='Membership') relationship so it's easier to do. I can't do as in the related question and add a relationship as I cannot change the model.
It is straight Forward
Event.objects.filter(eventproject__project=project)
Filtering through ForeignKey works both forward and backward in Django. You can use the model name in lower case for backward filtering.
https://docs.djangoproject.com/en/3.1/topics/db/queries/#lookups-that-span-relationships

How do I reuse single model field validators for forms without using ModelForm?

How can I reuse model field validators when creating a form. I can't use ModelForm because the form only uses part of the model's fields and I also have additional form fields.
Minimal example
I have a model for an (encryption) key that is always 32 characters in length and I want to use this restriction in the model and forms that accept that key.
models.py
class EncryptionKey(models.Model)
key = models.CharField("Encryption Key", max_length=32, validators=[validators.MinLengthValidator(32)])
forms.py
class NewUserForm(forms.Form):
first_name = forms.CharField(label='First Name', required=True, max_length=256)
last_name = forms.CharField(label='Last Name', required=True, max_length=256)
key = # How do I reuse the key from the model here?
I am looking for a idiomatic way to do this in Django 2.1.
I spend >20 minutes googling for this but all I find is how to use ModelForm.
Not sure if it is idiomatic or not but you can use fields_for_model helper from django.forms.models:
from django.forms.models import fields_for_model
from yourapp.models import EncryptionKey
class NewUserForm(forms.Form):
first_name = forms.CharField(label='First Name', required=True, max_length=256)
last_name = forms.CharField(label='Last Name', required=True, max_length=256)
key = fields_for_model(EncryptionKey, ['key'])['key']
I am not even sure if that is documented.
I think I found the "canonical" answer in the Django docs for Models (emphasis mine):
Abstract base classes
Abstract base classes are useful when you want to put some common
information into a number of other models. You write your base class
and put abstract=True in the Meta class. This model will then not be
used to create any database table. Instead, when it is used as a base
class for other models, its fields will be added to those of the child
class.
An example:
from django.db import models
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
class Student(CommonInfo):
home_group = models.CharField(max_length=5)
The Student model will have three fields: name, age and home_group.
The CommonInfo model cannot be used as a normal Django model, since it
is an abstract base class. It does not generate a database table or
have a manager, and cannot be instantiated or saved directly.
Fields inherited from abstract base classes can be overridden with
another field or value, or be removed with None.
For many uses, this type of model inheritance will be exactly what you
want. It provides a way to factor out common information at the Python
level, while still only creating one database table per child model at
the database level.
The only downside I can see with this, is that inheritance is used for "has-a" relations and not "is-a". Following the above example: A Student is modelled as being a specialization of common info but obviously isn't.

"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.

Should I use a seperate table instead of many to many field in Django

I needed to assign one or more categories to a list of submissions, I initially used a table with two foreign keys to accomplish this until I realized Django has a many-to-many field, however following the documentation I haven't been able to duplicate what I did with original table.
My question is : Is there a benefit to using many-to-many field instead of manually creating a relationship table? If better, are there any example on submitting and retrieving many-to-many fields with Django?
From the Django docs on Many-to-Many relationships:
When you're only dealing with simple many-to-many relationships such
as mixing and matching pizzas and toppings, a standard ManyToManyField
is all you need. However, sometimes you may need to associate data
with the relationship between two models.
In short: If you have a simple relationship a Many-To_Many field is better (creates and manages the extra table for you). If you need multiple extra details then create your own model with foreign keys. So it really depends on the situation.
Update :- Examples as requested:
From the docs:
class Person(models.Model):
name = models.CharField(max_length=128)
def __unicode__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __unicode__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
You can see through this example that membership details (date_joined and invite_reason) are kept in addition to the many-to-many relationship.
However on a simplified example from the docs:
class Topping(models.Model):
ingredient = models.CharField(max_length=128)
class Pizza(models.Model):
name = models.CharField(max_length=128)
toppings = models.ManyToManyField(Topping)
There seems no need for any extra data and hence no extra model.
Update 2 :-
An example of how to remove the relationship.
In the first example i gave you have this extra model Membership you just delete the relationship and its details like a normal model.
for membership in Membership.objects.filter(person__pk=1)
membership.delete()
Viola! easy as pie.
For the second example you need to use .remove() (or .clear() to remove all):
apple = Toppings.objects.get(pk=4)
super_pizza = Pizza.objects.get(pk=12)
super_pizza.toppings.remove(apple)
super_pizza.save()
And that one is done too!