Managing inheritance in Django - python

So, in our database we had a Car model, but there could be different kinds of cars, with different fields and different processes associated to them, so I made some model classes that inherit from that Car, it's something like this:
class Car(models.Model):
field = models.CharField(max_length=300)
class CarA(Car):
field_a = models.CharField(max_length=300)
class CarA1(CarA):
field_a1 = models.CharField(max_length=300)
class CarA2(CarA):
field_a2 = models.CharField(max_length=300)
class CarB(Car):
field_b = models.CharField(max_length=300)
Also, there are trips, and each trip has a car associated to them:
class Trip(BaseModel):
car = models.ForeignKey(Car)
If I have a CarA1 instance, car_a1, I can save it in a Trip with car_a1.car_ptr, but it would be saved as a Car, and from there I don't think there's a way to get the CarA1 instance again except searching by the Car id in all the subclasses.
So, if I want to know which kind of car the trip has, or get a specific subclass field, it would be really complicated.
What do you suggest to do? I can just keep only the Car class with the specific fields nullable (field_a, field_b, etc), and a field to specify the kind of car, but that doesn't seem right, I would have to check on the kind to validate the fields, etc. But right now it seems like the better solution.
I'm using Django 1.11 and Python 3.6 btw.
Sorry I didn't use real code, I thought it would be way more complicated, also sorry if I didn't explain it very well, I'm sleep deprived and stressed.
Thanks!

The problem in your model is when a new type of car introduced say car c you need to create another table for that. It is not ideal and what you need is one table for store the car details(commonly used) and separate table say Properties to store other details and give a foreign-key relation to Car model.

First, you can get to the child model from the parent model (see model based inheritance).
If you want to stick with your inheritance based design, you could define a field in Car called car_type which would correspond to the type of the child table. For example:
class Car(models.Model):
CAR_A = 'car_a'
CAR_B = 'car_b'
CAR_TYPE_CHOICES = (
(CAR_A, 'Car_A'),
(CAR_B, 'Car_b'),
)
field = models.CharField(max_length=300)
car_type = models.CharField(max_length=20, choices=CAR_TYPE_CHOICES)
And then you could override the save method of the child models so that their car_type field is the one that corresponds to their model.
class CarA(Car):
field_a = models.CharField(max_length=300)
def save(self, *args, **kwargs):
self.car_type = Car.CAR_A
super(CarA, self).save(*args, **kwargs)
# ... similarly for CarB
Now you can get to the child model from Car based on its car_type.
if car.car_type === Car.CAR_A:
print(car.cara.field_a)
Note that this will start to get cumbersome if you have many levels of inheritance, so I would encourage you to reconsider your design.

Related

Make field of submodel unique in django

I want to make the name of a submodel unique but I can't think of a way to do it. Imagine I have the following model architecture:
class Animal(models.Model):
name = field.CharField(...)
class Meta:
abstract = False
class Panther(Animal):
class Meta:
...
class Tiger(Animal):
class Meta:
....
Now, what I want is that within the scope of the subclasses, the name of should be unique. So let's say I have a Tiger called JackTheTiger and a Panther called JackyThePanther, then no other Tiger with this name should allowed to be created and no other Panther with the name JackyThePanther should be allowed to be created.
But I want to be able to create a Tiger which is called JackyThePanther and a panther which is also called JackyThePanther. So the uniqueness should only be applied within the scope of the submodel.
I tried 2 ways to achieve what I want, both are not optimal:
I create a name field for each submodel and make it unique. But then I can't query all animals and serialize the name. It also seems like bad architecture to me
I make Animal abstract. But this is no option for me since I need the database table for animal
(3. I also thought about a custom validator but I don't think it's possible because I could only validate the instances of the Supermodel)
Is there another way to achieve what I intend? Help is very much appreciated, thanks in advance
Model inheritance in Django is rarely the best solution, and I don't think I'd turn to it in your case. You're already seeing the pitfalls it can come with in situations that aren't perfectly suited to it.
Meta.unique_together is a model option that enables what you seem to be looking for, though you'll have to change the approach you're using:
class Species(models.Model):
name = models.CharField(...)
class Animal(models.Model):
name = models.CharField(...)
species = models.ForeignKey(Species, on_delete=models.CASCADE)
class Meta:
unique_together = [['name', 'species']]
In this case, you'd likely maintain a data migration or fixture for species, as your current architecture has them predefined in code.
Note that this uses unique_together, which is currently valid, but the linked documentation mentions that UniqueConstrant is likely more future-proof. I'm sticking with the former as a demonstration here since it's the one I've used.
Maybe its your solution
class Animal(models.Model):
TYPE = (
(1, 'Tyger'),
(2, 'Panter')
)
name = models.CharField(max_length=64)
type_of_animal = models.IntegerField(choices=TYPE)
class Meta:
unique_together = [['name','type_of_animal']]
In this case you can create JackyThePanther for 'Tyger' and 'Panter'. You can expand logic, for example add 'Elephant' JackyThePanther
>>> animals = Animal.objects.all()
>>> animals = Animal.objects.filter(name='JackyThePanther')
>>> animals
<QuerySet [<Animal: Animal object (1)>, <Animal: Animal object (2)>]>

Inheritance model update to its parent model

I need extend a model from another model.
Case:
core/models.py
class Master(models.Model):
code = models.CharField(max_length=30, unique=True)
name = models.CharField(max_length=100, blank=False, null=False)
class Meta:
abstract = True
class City(Master):
zipcode = models.IntegerField()
custom/models.py
from core.models import City
class City(City)
newfield = models.CharField(max_length=20)
custom is an app.
I have tried with proxy model but it is not what I need, since proxy model adds a new table. https://docs.djangoproject.com/en/2.2/topics/db/models/#proxy-models
I need is that when I migrate add the new field to City.
More info.
In core the table is created and in custom you can add new fields that the client needs. The idea is that core is only maintained as standard.
Proxy models don't add new tables. From the docs link you mentioned:
The MyPerson class operates on the same database table as its parent Person class.
If you want one table called core_city, and another called custom_city, the second one having an extra field, you simply subclass it. Perhaps it would be easier to use an alias:
from core.models import City as CoreCity
class City(CoreCity):
newfield = models.CharField(max_length=20)
custom_city will have all fields from core_city, plus a newfield. The description of how this works (and an example) is covered in the docs section Multi-table inheritance.
If what you want is to have one single database table, then you should use a proxy Model, however they really don't allow you to create new fields. The field should be created in the parent model, or otherwise exist in the database and not be handled by Django migrations at all.
You are looking for Abstract base classes models:
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 is the base class:
#core/models.py
class City(Master):
zipcode = models.IntegerField()
class Meta:
abstract = True # <--- here the trick
Here your model:
#custom/models.py
from core.models import City as CoreCity
class City(CoreCity):
newfield = models.CharField(max_length=20)
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.
You can update or create your class constants after its defined like this
from core.models import City
City.newfield = models.CharField(max_length=20)
You may need to use swappable models, using them you can define a City class and change it with whichever model you need later,
but that way you can't import and use the base City model directly, you may need to provide a method like get_city_model for that, as your public API.
class City(Master):
zipcode = models.IntegerField()
class Meta:
swappable = 'CORE_CITY_MODEL'
and maybe replace it later with some other model, then just set CORE_CITY_MODEL to that model in the form of 'app_name.model_name'.
The django.contrib.auth is a good example of this, you may consider checking User model and get_user_model method. Although I think you may face problems if you change your city model after you did run migrate, it may not move your data to the new table, but I'm not sure about this.

Best way to get around the unique-constraint for many-to-many relationships in Django?

I have a situation where I need to be able to add multiple copies of the same object to a many-to-many relationship.
Let's say that the problem is recording the types of furniture someone has. Here are my base models:
class Person(models.Model):
name = models.CharField(max_length=100)
class Furniture(models.Model):
furniture_name = models.CharField(max_length=100) #e.g. Chair, Sofa.
Lets say I want to record that Sam owns 3 chairs and 2 sofas. But I don't want to make more than one sofa object in the DB. How can I do this with a many-to-many relationship? The traditional many-to-many has a unique-constraint that prevents this.
I'm thinking of using a through table, with another field in the unique-constraint (date-purchased, or just a random string). Will that work?
Does anyone have a better way of doing this?
Creating a through table is a good approach here, as everything will be DRY and pretty easy to use:
class Ownership:
owner = models.ForeignKey(Person, on_delete=models.CASCADE)
furniture = models.ForeignKey(Furniture, on_delete=models.CASCADE)
items_owned = models.IntegerField(default=0)
class Meta:
unique_together = [
'owner', 'furniture'
]
person = Person.objects.get(name='Bob')
furniture = Furniture.objects.get(furniture_name='Sofa')
# update the number of items owned
Ownership.objects.update_or_create(
owner=person, furniture=furniture, defaults=dict(items_owned=3))
# get the number of items owned
person.ownership_set.get(furniture=furniture).items_owned
# or for example
Ownership.objects.get(owner__name='Bob', furniture__name='Sofa').items_owned
You can then abstract away this complex querying and updating logic with custom managers https://docs.djangoproject.com/en/2.0/topics/db/managers/#custom-managers

How to have extra django model fields depending on the value of a field?

I have a model in my Django project called Job. Each Job has a category. An example of a category could be tutoring. This can be represented as what my model looks like right now:
from __future__ import unicode_literals
from django.db import models
class Job(models.Model):
# Abbreviations for possible categories to be stored in the database.
TUTORING = "TU"
PETSITTING = "PS"
BABYSITTING = "BS"
INTERIOR_DESIGN = "IND"
SHOPPING = "SH"
SOFTWARE_DEVELOPMENT = "SD"
DESIGN = "DE"
ART = "AR"
HOUSEKEEPING = "HK"
OTHER = "OT"
JOB_CATEGORY_CHOICES = (
(TUTORING, 'Tutoring'),
(PETSITTING, "Petsitting"),
(BABYSITTING, "Babysitting"),
(INTERIOR_DESIGN, "Interior Design"),
(SHOPPING, "Shopping"),
(SOFTWARE_DEVELOPMENT, "Software Development"),
(DESIGN), "Design"),
(ART, "Art"),
(HOUSEKEEPING, "Housekeeping"),
(OTHER, "Other"),
)
created_at = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=255)
description = models.TextField()
category = models.CharField(max_length=3, choices=JOB_CATEGORY_CHOICES, default=OTHER,)
def __str__(self):
return self.title
Depending on the category of the Job, different fields are required. For example, if I take tutoring as the category again, then extra fields like address, subject, level of study and others are needed. If the category of the Job is software development however, extra fields like project_size and required_qualifications are needed.
Should I create a separate model for each type of Job or is there some kind of model inheritance I can use where job types inherit from the main Job model which holds all the common fields that all Jobs need.
Essentially, what is the best way to have extra fields depending on the Job category?
You have some options:
1. OneToOneField on various category models:
Pro:
allows other models to have FK to Job model. E.g. you could retrieve all of a person jobs via person.jobs.all() no matter which category.
Con:
Allows instances of different categories to relate to the same Job instance: Extra work is needed to maintain data integrity
More tables, more joins, slower queries
Adding a category always entails a migration!
2. Multi-Table inheritance:
Uses OneToOneField under the hood.
Pro:
as above + but each instance of a category will autocreate its own Job instance, so no collisions between categories.
Con:
More tables, more joins, slower queries. Obscures some of the db stuff that's going on.
Adding a category always entails a migration!
3. Job as an abstract base model
Pro: Single table for each category, faster queries
Con: separate relations need to be maintained for each category, no grouping possible at the db level.
Adding a category always entails a migration!
4. Put all the category specific fields in Job (and make them nullable)
Pro: One Table, easy relations, Queries for special categories via filter on category field still possible.
You can use specific model managers to handle categories: Job.tutoring.all()
Possibly many categories share various subsets of fields
No overengineering, easy maintainability.
Adding a new category will only require a migration if it requires a field that is not there yet. You could have a generic CharField used by multiple categories for different semantic purposes and access it via propertys with meaningful names. These cannot, however, be used in filters or qs-updates.
À la:
class Job(models.Model):
# ...
attribute = models.CharField(...)
def _get_attribute(self):
return self.attribute
def _set_attribute(self, value):
self.attribute = value
# for shopping
shop_name = property(_get_attribute, _set_attribute)
# for babysitting
family_name = property(_get_attribute, _set_attribute)
# then you can use
babysitting_job.family_name = 'Miller'
Con: Some fields are null for each job
While options 1-3 may better model the real world and make you feel good about the sophisticated model structure you have cooked up, I would not discard option 4 too quickly.
If the category fields are few and commonly shared between categories, this would be my way to go.
The optimal thing to do would be to use a OneToOneField. Before further explanation, I'll just use this example:
from django.db import models
class Menu(models.Model):
name = models.CharField(max_length=30)
class Item(models.Model):
menu = models.OneToOneField(Menu)
name = models.CharField(max_length=30)
description = models.CharField(max_length=100)
Menu here could compare to your Job model. Once an item in the menu is chosen, the Menu model basically extends the chosen Item's fields. Item here can be compared to your Job category.
You can read more on this stuff here.

Model redundant django tables?

I am working to figure out the model for a Django project: an app to track Books.
Among other fields, every Book has either/both a Printer and a Publisher, which are basically identical. So, here's how it stands:
class Book(models.Model):
title = models.CharField(max_length=100)
printer = models.ForeignKey('Printer')
publisher = models.ForeignKey('Publisher')
class Printer(models.Model):
name = models.CharField(max_length=100)
location = models.CharField(max_length=100)
class Publisher(models.Model):
name = models.CharField(max_length=100)
location = models.CharField(max_length=100)
It seems to me this is bad database form: it's not DRY. In addition, quite often, a Book might be printed by a firm which publishes the same or another book: in other words, the tables can overlap. So, the two models Printer and Publisher should really be combined, while they need to remain distinct in the admin.
My question: how best to do this? Should I create another model, Firm, and create one-to-one relationships between it and Printer/Publisher?
The Django way to handle that is to create an Abstract Base Model. This is the DRY way to create your models. Here is the code:
class BaseModel(models.Model):
name = models.CharField(max_length=100)
location = models.CharField(max_length=100)
class Meta:
abstract = True
class Printer(BaseModel):
pass
class Publisher(BaseModel):
pass
This will allow you to specify redundant fields only once. Also, if you need to add any extra fields to one model, just add them instead of using pass.

Categories

Resources