Model Creation calls its related model save method? - python

Having these kind of model definitions and a relationship between the two:
class Car(models.Model):
description = models.CharField(max_length=35)
def save(self, **kwargs):
invalidate_cache()
super().save(**kwargs)
def delete(self, **kwargs):
invalidate_cache()
return super().delete(**kwargs)
class Passenger(models.Model):
car = models.ForeignKey(Car, related_name='passengers')
I have defined custom save and delete on the Car model because when a car instance is modified I have to perform some extra operations, in particular I need to invalidate a cache.
My doubt is: creating/updating/deleting a related model would call these custom methods?
I'll try to be more clear:
c1 = Car(description='super fast car')
p = Passenger(car=c1)
Clearly the creation of the c1 calls the Car.save but would the creation of the instance p of Passenger call the Car.save or not?
From my tests it seems so but I want to be more sure it wasn't just a specific case and this happens all the time in the Django model handling cycle (I could not find a specific documentation on this).

Related

django simple history - using model methods?

I'm using django-simple-history:
http://django-simple-history.readthedocs.io/en/latest/
I have a model, which I would like to apply its methods on an historical instance. Example:
from simple_history.models import HistoricalRecords
class Person(models.Model):
firstname = models.CharField(max_length=20)
lastname = models.CharField(max_length=20)
history = HistoricalRecords()
def fullName(self):
return firstname + lastname
person = Person.objects.get(pk=1) # Person instance
for historyPerson in person.history:
historyPerson.fullName() # wont work.
Since the class HistoricalPerson does not inherit the methods of Person. But using Person methods actually make sense, since they share the same fields..
Any solution for this? I'd prefer something simple, not like duplicating every method in my models for the history instances..
I found another workaround (maybe it's just the addon had been updated and got this feature). It's based on the documentation: adding-additional-fields-to-historical-models
HistoricalRecords field accepts bases parameter which sets a class that history objects will inherit. But you can't just set bases=[Person] inside Person class description, because it's not yet initialized.
So I ended up with an abstract class, which is inherited by both Person class and HistoricalRecords field. So the example from the question would look like:
class AbstractPerson(models.Model):
class Meta:
abstract = True
firstname = models.CharField(max_length=20)
lastname = models.CharField(max_length=20)
def fullName(self):
return firstname + lastname
class Person(AbstractPerson):
history = HistoricalRecords(bases=[AbstractPerson])
And now history objects can use fullName method.
For anyone else having the same problem, I made it work by calling the method from the original class on the historical record object. So for the example in the question, a solution could be:
for historyPerson in person.history:
Person.fullName(historyPerson)
This works because methods are very much like functions in Python, except that when you call a method on an instance, the instance is implicitly passed as the first parameter for the method. So if you have a class like:
class Foo:
def method(self):
....
doing
f = Foo()
f.method()
is the same as:
f = Foo()
Foo.method(f)
I don't know exactly why simple-history does not copy the original model's methods though. One reason might be that since it allows you to exclude fields to be recorded, having the original methods might not make sense, since a method might not work if it uses fields that are not recorded in the historical record.

Hardcoding attributes in django models

OK here goes, this is one of those questions that makes perfect sense in my head but is difficult to explain properly :) I have a django app where I want to store records for lots of different items of equipment. Each type of equipment will have a custom model to store its attributes, such as MyEquipment below. Each type of equipment will also have a 'category', which would be useful to store as an attribute.
class Category(models.Model):
code = models.CharField('Category', max_length=4, unique=True)
description = models.CharField('Description', max_length=30)
...
class MyEquipment(models.Model):
serial = models.IntegerField()
...
To save this attribute to my model I could use a foreign key to Category but I don't need to because every record in MyEquipment must be the same Category. So then I thought maybe I could hardcode the Category in the MyEquipment meta like this:
class MyEquipment(models.Model):
serial = models.IntegerField()
...
class Meta:
category = Category.objects.get(code='EC')
But then this would rely on the Category model being populated with data to build the MyEquipment model. To me this doesn't seem best practice, using data that may or may not exist to define the structure of another model. Is there a better way I should be using to set which Category the MyEquipment model is related to?
EDIT
Thanks for the discussion below, it's made me realise perhaps I wasn't clear on my original post. So what I want to do is have a way of linking MyEquipment to a Category. So I can do something like this:
>>> from myapp.models import MyEquipment
>>> MyEquipment.CATEGORY
<Category: EC>
I want to link the whole model to a Category, so I can process each model in different ways in my view depending on which category it is. Having thought about the problem a bit more, I can get this functionality by writing MyEquipment like this:
class MyEquipment(models.Model):
CATEGORY = Category.objects.get(code='EC')
serial = models.IntegerField()
...
This way works, but is it the best way? I guess the model would do this get operation everytime the class is instantiated? Is there a more efficient method?
You can't do this anyway; the Meta class doesn't support arbitrary attributes.
The best thing would be to define this as a property, which you can access via the instance itself. To make it more efficient, you could memoize it on the class.
#property
def category(self):
_category = getattr(self, '_category', None)
if not _category:
self.__class__._category = _category = Category.objects.get(code='EC')
return _category
but ... every record in MyEquipment must be the same Category
Then you don't need any relationship. As you said already, every record in MyEquipment are same Category, why do you want to store relation in db?
UPD: Solution with model inheritance
class Place(models.Model):
category = models.ForeignKey(Category)
class Meta:
abstract = True
def save(self, *args, **kwargs):
self.category = Category.objects.get(name=self.CATEGORY)
return super(Place, self).save(*args, **kwargs)
class Restaurant(Place):
...fields...
CATEGORY = 'RE'
class Building(Place):
...fields...
CATEGORY = 'BU'

Storing related model references in Django?

I'm implementing a bare-bones history tracking mechanism for my Django app, in which the models I care to track override the save() and delete() methods. In each method, I create my history objects as necessary:
def save(self, *args, **kwargs):
super(MyModel, self).save(*args, **kwargs)
# Create the historical model based on what we were given
h = Historical_MyModel(**{field.attname: getattr(self, field.attname) for field in self._meta.fields})
# Set some other fields as necessary...
h.save()
Since the code for each save() and delete() method is similar, I figured a good way to prevent typing the same code is to create an abstract base class to have the similar code in one place. One thing I'm struggling with, however, is how to handle creating the Historical_{Model} instance for each child class (each Historical_{Model} is essentially a copy of the original model, with additional info like who made the change, when the change occurred, etc.).
In my base class, the method would look something like this, I think:
class HistoryTrackedModel(models.Model):
def save(self, *args, **kwargs):
super(self.model, self).save(*args, **kwargs)
# Create the historical model based on what we were given
h = SOME_HISTORICAL_MODEL_INSTANCE(**{field.attname: getattr(self, field.attname) for field in self._meta.fields})
# Other fields get set ...
h.save()
class Meta:
abstract = True
The SOME_HISTORICAL_MODEL_INSTANCE bit above is the piece I'm stuck on. How can I get the associated historical model for a specific model I'm tracking? Is there an easy way to store a reference to it in each child class? I'd like to prevent code duplication, and I thought this was the right avenue, but I'm stuck on this one point. Any help would be appreciated.
I think the most straightforward way would be to store the value as a class attribute:
class HistoricalFoo(models.Model):
...
class Foo(HistoryTrackedModel):
history_model = HistoricalFoo
....
class HistoryTrackedModel(models.Model):
def save(self):
...
h = self.history_model(...)
An alternative would be to generate the historical model names programmatically:
class HistoryTrackedModel(models.Model):
def save(self):
...
history_model = globals()["Historical" + self.__class__.__name__]
h = history_model(...)

Django sync one-to-one models

I simplify my code structure, which contains two models:
# created by third part app, not Django one
# but we share same DB, so i have access to this one
class A(models.Model):
title = models.TextField()
# other fields ...
class Meta:
manage = False
class B(models.Model):
model_a = models.OneToOneField(A, related_name='+')
# other fields, to extend model A functionality
Is this a good way to extend third part app model A with my additional fields and methods? Now i have problem to sync this models true one-to-one field. Since I don't have access to trigger model A creation.
In ideal world i should have CarA and CarB. And CarB = CarA relation should be created if CarB exists.
I base this idea on Django 1.5 user extension. Is this clear enough? Or should i do something else?
You could use a property to create the B instance on access if it doesn't exist yet, ie,
class A(models.Model):
title = models.TextField()
# other fields ...
class Meta:
manage = False
#property
def b(self):
if not hasattr(self, "__bcache"):
self.__bcache, created = B.objects.get_or_create(model_a = self)
return self.__bcache
It seems like you're new to both Python and Django so let's explain quickly...
First, the "#property" part: it's a decorator that turns the following function into a computed attribute - IOW you use it as an attribute (myA.b.whatever), and under the hood it turns it into a method call (myA.b().whatever). It's not strictly required here, we would have used an explicit getter (the same method named get_a()) but it's cleaner that way.
Then our method implementation: obviously we don't want to hit the database each time someone looks up A.b, so
first we check if an attribute named __bcache ("b" "cache") is set on the current instance.
if not, we call B.objects.get_or_create(a_model=self) which will either retrieve the existing B instance for this A instance or create one if none exists yet and we store this B instance as self.__bcache so next call will retrieve it directly from __bcache instead of hitting the database.
and finally we return self.__bcache that is now garanteed to exists and point to the related B instance.

Single Table Inheritance in Django

Is there explicit support for Single Table Inheritance in Django? Last I heard, the feature was still under development and debate.
Are there libraries/hacks I can use in the meantime to capture the basic behavior? I have a hierarchy that mixes different objects. The canonical example of a corporation structure with an Employee class, subclasses for types of employees, and a manager_id (parent_id) would be a good approximation of the problem I am solving.
In my case, I would like to represent the idea that an employee can manage other employees while being managed by a different employee. There are not separate classes for Manager and Worker, which makes this hard to spread across tables. Sub-classes would represent types of employees-programmers, accountants, sales, etc and would be independent of who supervises who (OK, I guess it's no longer a typical corporation in some respect).
Summary
Django's proxy models provide the basis for Single Table Inheritance.
However, some effort is required to make it work.
Skip to the end for a re-usable example.
Background
Martin Fowler describes Single Table Inheritance (STI) as follows:
Single Table Inheritance maps all fields of all classes of an inheritance structure into a single table.
This is precisely what Django's proxy model inheritance does.
Note, that, according to this blog post from 2010, proxy models have been around since Django 1.1.
A "normal" Django model is a concrete model, i.e. it has a dedicated table in the database.
There are two types of Django model that do not have dedicated database tables, viz. abstract models and proxy models:
Abstract models act as superclasses for concrete models. An abstract model can define fields, but it does not have a database table. The fields are only added to the database tables for its concrete subclasses.
Proxy models act as subclasses for concrete models. A proxy model cannot define new fields. Instead, it operates on the database table associated with its concrete superclass. In other words, a Django concrete model and its proxies all share a single table.
Django's proxy models provide the basis for Single Table Inheritance, viz. they allow different models to share a single table, and they allow us to define proxy-specific behavior on the Python side. However, Django's default object-relational mapping (ORM) does not provide all the behavior that would be expected, so a little customization is required. How much, that depends on your needs.
Let's build a minimal example, step by step, based on the simple data-model in the figure below:
Step 1: basic "proxy model inheritance"
Here's the content of models.py for a basic proxy inheritance implementation:
from django.db import models
class Party(models.Model):
name = models.CharField(max_length=20)
person_attribute = models.CharField(max_length=20)
organization_attribute = models.CharField(max_length=20)
class Person(Party):
class Meta:
proxy = True
class Organization(Party):
class Meta:
proxy = True
Person and Organization are two types of parties.
Only the Party model has a database table, so all the fields are defined on this model, including any fields that are specific either to Person or to Organization.
Because Party, Person, and Organization all use the Party database table, we can define a single ForeignKey field to Party, and assign instances of any of the three models to that field, as implied by the inheritance relation in the figure. Note, that, without inheritance, we would need a separate ForeignKey field for each model.
For example, suppose we define an Address model as follows:
class Address(models.Model):
party = models.ForeignKey(to=Party, on_delete=models.CASCADE)
We can then initialize an Address object using e.g. Address(party=person_instance) or Address(party=organization_instance).
So far, so good.
However, if we try to get a list of objects corresponding to a proxy model, using e.g. Person.objects.all(), we get a list of all Party objects instead, i.e. both Person objects and Organization objects. This is because the proxy models still use the model manager from the superclass (i.e. Party).
Step 2: add proxy model managers
To make sure that Person.objects.all() only returns Person objects, we need to assign a separate model manager that filters the Party queryset. To enable this filtering, we need a field that indicates which proxy model should be used for the object.
To be clear: creating a Person object implies adding a row to the Party table. The same goes for Organization. To distinguish between the two, we need a column to indicate if a row represents a Person or an Organization. For convenience and clarity, we add a field (i.e. column) called proxy_name, and use that to store the name of the proxy class.
So, enter the ProxyManager model manager and the proxy_name field:
from django.db import models
class ProxyManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(proxy_name=self.model.__name__)
class Party(models.Model):
proxy_name = models.CharField(max_length=20)
name = models.CharField(max_length=20)
person_attribute = models.CharField(max_length=20)
organization_attribute = models.CharField(max_length=20)
def save(self, *args, **kwargs):
self.proxy_name = type(self).__name__
super().save(*args, **kwargs)
class Person(Party):
class Meta:
proxy = True
objects = ProxyManager()
class Organization(Party):
class Meta:
proxy = True
objects = ProxyManager()
Now the queryset returned by Person.objects.all() will only contain Person objects (and the same for Organization).
However, this does not work in the case of a ForeignKey relation to Party, as in Address.party above, because that will always return a Party instance, regardless of the value of the proxy_name field (also see docs). For example, suppose we create an address = Address(party=person_instance), then address.party will return a Party instance, instead of a Person instance.
Step 3: extend the Party constructor
One way to deal with the related-field issue is to extend the Party.__new__ method, so it returns an instance of the class specified in the 'proxy_name' field. The end result looks like this:
class Party(models.Model):
PROXY_FIELD_NAME = 'proxy_name'
proxy_name = models.CharField(max_length=20)
name = models.CharField(max_length=20)
person_attribute = models.CharField(max_length=20)
organization_attribute = models.CharField(max_length=20)
def save(self, *args, **kwargs):
""" automatically store the proxy class name in the database """
self.proxy_name = type(self).__name__
super().save(*args, **kwargs)
def __new__(cls, *args, **kwargs):
party_class = cls
try:
# get proxy name, either from kwargs or from args
proxy_name = kwargs.get(cls.PROXY_FIELD_NAME)
if proxy_name is None:
proxy_name_field_index = cls._meta.fields.index(
cls._meta.get_field(cls.PROXY_FIELD_NAME))
proxy_name = args[proxy_name_field_index]
# get proxy class, by name, from current module
party_class = getattr(sys.modules[__name__], proxy_name)
finally:
return super().__new__(party_class)
Now address.party will actually return a Person instance if the proxy_name field is Person.
As a last step, we can make the whole thing re-usable:
Step 4: make it re-usable
To make our rudimentary Single-Table Inheritance implementation re-usable, we can use Django's abstract inheritance:
inheritance/models.py:
import sys
from django.db import models
class ProxySuper(models.Model):
class Meta:
abstract = True
proxy_name = models.CharField(max_length=20)
def save(self, *args, **kwargs):
""" automatically store the proxy class name in the database """
self.proxy_name = type(self).__name__
super().save(*args, **kwargs)
def __new__(cls, *args, **kwargs):
""" create an instance corresponding to the proxy_name """
proxy_class = cls
try:
field_name = ProxySuper._meta.get_fields()[0].name
proxy_name = kwargs.get(field_name)
if proxy_name is None:
proxy_name_field_index = cls._meta.fields.index(
cls._meta.get_field(field_name))
proxy_name = args[proxy_name_field_index]
proxy_class = getattr(sys.modules[cls.__module__], proxy_name)
finally:
return super().__new__(proxy_class)
class ProxyManager(models.Manager):
def get_queryset(self):
""" only include objects in queryset matching current proxy class """
return super().get_queryset().filter(proxy_name=self.model.__name__)
Then we can implement our inheritance structure as follows:
parties/models.py:
from django.db import models
from inheritance.models import ProxySuper, ProxyManager
class Party(ProxySuper):
name = models.CharField(max_length=20)
person_attribute = models.CharField(max_length=20)
organization_attribute = models.CharField(max_length=20)
class Person(Party):
class Meta:
proxy = True
objects = ProxyManager()
class Organization(Party):
class Meta:
proxy = True
objects = ProxyManager()
class Placement(models.Model):
party = models.ForeignKey(to=Party, on_delete=models.CASCADE)
More work may be required, depending on your needs, but I believe this covers some of the basics.
I think the OP is asking about Single-Table Inheritance as defined here:
Relational databases don't support inheritance, so when mapping from objects to databases we have to consider how to represent our nice inheritance structures in relational tables. When mapping to a relational database, we try to minimize the joins that can quickly mount up when processing an inheritance structure in multiple tables. Single Table Inheritance maps all fields of all classes of an inheritance structure into a single table.
That is, a single database table for a whole hierarchy of entity classes. Django does not support that kind of inheritance.
There are currently two forms of inheritance in Django - MTI (model table inheritance) and ABC (abstract base classes).
I wrote a tutorial on what's going on under the hood.
You can also reference the official docs on model inheritance.
See my attempt:
http://djangosnippets.org/snippets/2408/
An emulation of "table per hierarchy" a.k.a. "single table inheritance" in Django. The base class must hold all the fields. It's subclasses are not allowed to contain any additional fields and optimally they should be proxies.
Not exactly "single table inheritance", but close enough for many situations.
this might be of use: https://github.com/craigds/django-typed-models
It looks to be somewhat of an implementation of Single Table Inheritance but it has the limitation that subclasses can't have any extra fields.
here is a recent discussion on the django developer mailing list about STI:
https://groups.google.com/forum/#!msg/django-developers/-UOM8HNUnxg/6k34kopzerEJ
I think you can do something akin to this.
I have to implement a solution for this problem myself, and here was how I solved it:
class Citrus(models.Model):
how_acidic = models.PositiveIntegerField(max_value=100)
skin_color = models.CharField()
type = models.CharField()
class TangeloManager(models.Manager):
def get_query_set(self):
return super(TangeloManager, self).get_query_set().filter(type='Tangelo')
class Tangelo(models.Model):
how_acidic = models.PositiveIntegerField(max_value=100)
skin_color = models.CharField()
type = models.CharField()
objects = TangeloManager()
class Meta:
# 'appname' below is going to vary with the name of your app
db_table = u'appname_citrus'
This may have some locking issues... I'm not really sure how django handles that off the top of my head. Also, I didn't really test the above code, it's strictly for entertainment purposes, to hopefully put you on the right track.

Categories