问题
Let's say I have a model, employee
:
Employee
- Name:
CharField
- Email:
EmailField
- Type:
ForeignKey
The problem is with the type
field. I want the foreign key to be able to be several different types of models. For example, one for Software Developer
, another for Janitor
, and so on.
I could do this by having a foreign key field for all types, and setting many to null, but this seems bad. Does this make sense?
How can I achieve this? Thanks!!
回答1:
There are many ways you can model this level of polymorphism into a scenario like this.
OPTION 1 - Concrete Base Model
Including a employee_type
field which indicates the concrete model being used.
class Employee(models.Model):
name = models.CharField(max_length=50)
email = models.CharField(max_length=80)
employee_type = models.CharField(max_length=20)
class Janitor(Employee):
[...]
class SoftwareEngineer(Employee):
[...]
All mutual attributes can be stored in the Employee
model.
Once a concrete class (Software Engineer, Janitor etc.) is instantiated it will inherit all attributes from it's parent class.
Setting and using the employee_type
you can differentiate between which concrete class was created.
More information on this can be found here
[Update]
Using a django Signal the concrete class name can be derived and stored with the associated instance.
signals.py
from django.db.models.signals import pre_save
def store_classname(sender, instance, **kwargs):
instance.employee_type = instance.__class__.__name__
for subclass in Employee.__subclasses__():
pre_save.connect(store_classname, sender=subclass)
This ensures the correct identifier is stored every time.
Now in your view where you want to select the type of concrete class to be used, you can either continue using it in a condition like so:
views.py
#EXAMPLE USAGE
employee = Employee.objects.get(id=1)
if employee.employee_type == 'Janitor':
employee = Janitor.objects.get(id=employee.id)
Or derive it dynamically by using the model name and a lookup to global variables.
#EXAMPLE USAGE
from <you_app>.models import *
def get_class(classname):
cls = globals()[classname]
return cls
employee = Employee.objects.get(id=1)
concrete_employee = get_class(employee.employee_type)
[...]
Note: Be careful when changing names of parent or child models as this will affect historical records using the old model name. To fix this use django's update()
or bulk_update
function to convert all old names to new names. More info is here
OPTION 2 - django-polymorphic
Using a django package called django-polymorphic
, this allows all concrete classes to be returned when parent class is queried on.
models.py
from polymorphic.models import PolymorphicModel
class Employee(PolymorphicModel):
EMPLOYEE_TYPE_CHOICES = (
('Janitor', 'Janitor'),
('Software Engineer', 'Software Engineer'),
)
name = models.CharField(max_length=50)
email = models.CharField(max_length=80)
class Janitor(Employee):
[...]
class SoftwareEngineer(Employee):
predominant_programming_language= models.CharField(max_length=100)
[...]
When querying on the Employee
models the following will be returned
>>> Employee.objects.filter(id=1)
[ <Employee: id 1, name "Joe Bloggs", email "example@example.com">,
<SoftwareEngineer: id 1, name "Joe Bloggs", email "example@example.com", predominant_programming_language "Python">,]
来源:https://stackoverflow.com/questions/63019628/many-models-in-foreign-key