I have a model Person
which stores all data about people. I also have a Client
model which extends Person. I have another extending model OtherPerson
which also extends the Person
model. I want to create a Client pointing to a Person
, and ALSO create an OtherPerson
record which points to that Person
. Basically, I want one Person
object to be viewed as a Client
and and an OtherPerson
, depending on the current view. Is this possible with Django's ORM, or do I need to somehow write a Raw query to create this scenario. I am quite certain it is possible from the database side, because both child classes would just point to the parent Person class with their person_ptr_id field.
Simply put, if I create a Client
(and thus a Person
), can I also create an OtherPerson
object using the base Person
from the Client
. That way I can view them as a Client
OR as an OtherPerson
, and saving one will affect the Person
fields of each?
"Horizontal" Polymorphism?
Here is a cut down version of my models in case that clarifies:
class Person(models.Model):
"""
Any person in the system will have a standard set of details, fingerprint hit details, some clearances and items due, like TB Test.
"""
first_name = models.CharField(db_index=True, max_length=64, null=True, blank=True, help_text="First Name.")
middle_name = models.CharField(db_index=True, max_length=32, null=True, blank=True, help_text="Middle Name.")
last_name = models.CharField(db_index=True, max_length=64, null=True, blank=True, help_text="Last Name.")
alias = models.CharField(db_index=True, max_length=128, null=True, blank=True, help_text="Aliases.")
.
.
<some person methods like getPrintName, getAge, etc.>
class Client(Person):
date_of_first_contact = models.DateField(null=True, blank=True)
.
.
<some client methods>
class OtherPerson(Person):
active_date = models.DateField(null=True, blank=True)
termination_date = models.DateField(null=True, blank=True)
.
.
<some other person methods>
Okay, I hate to answer my own question, especially since it is sort of a repeat of (Django model inheritance: create sub-instance of existing instance (downcast)?
@Daniel Roseman got me out of a jam AGAIN. Gotta love that guy!
person = Person.objects.get(id=<my_person_id>)
client = Client(person_ptr_id=person.id)
client.__dict__.update(person.__dict__)
client.save()
other_person = OtherPerson(person_ptr_id=person.id)
other_person.__dict__.update(person.__dict__)
other_person.save()
If I have an existing Client
and want to make an OtherPerson
from them, which is my exact use-case, I just do this:
client_id = <ID of Client/Person I want to create an OtherPerson with>
p = Person.objects.get(id=client_id)
o = OtherPerson(person_ptr_id=p.id) # Note Person.id and Client.id are the same.
o.__dict__.update(p.__dict__)
o.save()
Now the person shows up as a Client on the clients screen and as an OtherPerson on the other person screen. I can get the OtherPerson version of the Person which has all the OtherPerson details and functions or I can get a Client version of that Person which has all the Client details and functions.
What you are doing is not possible as you do it, Django has specific rules for inheritance
The only possible schema is:
class Parent(models.Model):
class Meta:
abstract = True # MUST BE !!! This results in no relation generated in your DB
field0 = models.CharField(...
...
# here you're allowed to put some functions and some fields here
class Child(models.Model):
field1 = models.CharField(...
...
# Anything you want, this model will create a relation in your database with field0, field1, ...
class GrandChild(models.Model):
class Meta:
proxy = True # MUST BE !!! This results in no relation generated in your DB
# here you're not allowed to put DB fields, but you can override __init__ to change attributes of the fields: choices, default,... You also can add model methods.
This is because there is no DB inheritance in most DBGS. Thus you need to make you parent class abstract
!
You can't really do that with subclassing. When you subclass Person
, you're implicitly telling Django that you'll be creating subclasses, not Person
objects. It's a PITA to take a Person
and transmogrify it into a OtherPerson
later.
You probably want a OneToOneField
instead. Both Client
and OtherPerson
should be subclasses of models.Model
:
class Client(models.Model):
person = models.OneToOneField(Person, related_name="client")
# ...
class OtherPerson(models.Model):
person = models.OneToOneField(Person, related_name="other_person")
# ...
Then you can do things like:
pers = Person(...)
pers.save()
client = Client(person=pers, ...)
client.save()
other = OtherPerson(person=pers, ...)
other.save()
pers.other.termination_date = datetime.now()
pers.other.save()
See https://docs.djangoproject.com/en/dev/topics/db/examples/one_to_one/ for more.
As mentioned in a comment already, there is an open ticket for this very question: https://code.djangoproject.com/ticket/7623
In the meanwhile there is a proposed patch (https://github.com/django/django/compare/master...ar45:child_object_from_parent_model) which not using obj.__dict__
but creates an dictionary with all field values cycling over all fields.
Here a simplified function:
def create_child_from_parent_model(parent_obj, child_cls, init_values: dict):
attrs = {}
for field in parent_obj._meta._get_fields(reverse=False, include_parents=True):
if field.attname not in attrs:
attrs[field.attname] = getattr(parent_obj, field.attname)
attrs[child_cls._meta.parents[parent_obj.__class__].name] = parent_obj
attrs.update(init_values)
print(attrs)
return child_cls(**attrs)
person = Person.objects.get(id=<my_person_id>)
client = create_child_from_parent_model(person, Client, {})
client.save()
If you want to create a sibling:
client_person = getattr(person, person._meta.parents.get(Person).name)
other_person = create_child_from_parent_model(person, OhterPerson, {})
other_person.save()
This method has the advantage that methods that are overwritten by the child are not replaced by the original parent methods.
For me using the original answers obj.__dict__.update()
led to exceptions as I was using the FieldTracker
from model_utils
in the parent class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With