如何基于两个抽象类构建Django条件式Owner模型?
Django 4.1.x 实现二选一的 Owner 模型方案
核心思路
利用Django支持多抽象基类继承的特性,给Owner模型添加类型标记字段,通过属性控制和自定义逻辑实现"二选一"的实例类型限制,同时保留Person/Organization作为其他模型基类的能力。
1. 定义抽象基类 Person 和 Organization
这两个类作为抽象基类,既可以被Owner继承,也能作为其他业务模型(如Designer、Painter)的父类:
from django.db import models class Person(models.Model): first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) date_of_birth = models.DateField(null=True, blank=True) class Meta: abstract = True class Organization(models.Model): full_name = models.CharField(max_length=200) founding_date = models.DateField(null=True, blank=True) tax_id = models.CharField(max_length=50, null=True, blank=True) class Meta: abstract = True
2. 构建 Owner 模型
通过类型标记、动态属性和属性拦截实现二选一的限制:
from django.core.exceptions import AttributeError class Owner(models.Model, Person, Organization): OWNER_TYPES = ( ('person', '个人'), ('organization', '组织'), ) owner_type = models.CharField(max_length=20, choices=OWNER_TYPES) # 动态返回统一的name属性 @property def name(self): if self.owner_type == 'person': return f"{self.first_name} {self.last_name}" elif self.owner_type == 'organization': return self.full_name return "" # 拦截属性访问,只允许对应类型的属性 def __getattr__(self, name): person_attrs = [field.name for field in Person._meta.fields] org_attrs = [field.name for field in Organization._meta.fields] if self.owner_type == 'person' and name not in person_attrs: raise AttributeError(f"Person类型Owner不支持属性 {name}") elif self.owner_type == 'organization' and name not in org_attrs: raise AttributeError(f"Organization类型Owner不支持属性 {name}") return super().__getattr__(name) # 拦截属性设置,禁止设置非对应类型的属性 def __setattr__(self, name, value): if hasattr(self, 'owner_type'): person_attrs = [field.name for field in Person._meta.fields] org_attrs = [field.name for field in Organization._meta.fields] if self.owner_type == 'person' and name not in person_attrs and name != 'owner_type': raise AttributeError(f"Person类型Owner无法设置属性 {name}") elif self.owner_type == 'organization' and name not in org_attrs and name != 'owner_type': raise AttributeError(f"Organization类型Owner无法设置属性 {name}") super().__setattr__(name, value) class Meta: verbose_name = "所有者" verbose_name_plural = "所有者"
3. 自定义管理器简化实例创建(可选)
添加管理器确保创建Owner时的类型正确性和字段完整性:
class OwnerManager(models.Manager): def create_person(self, first_name, last_name, **kwargs): if not first_name or not last_name: raise ValueError("Person类型Owner必须填写first_name和last_name") return self.create(owner_type='person', first_name=first_name, last_name=last_name, **kwargs) def create_organization(self, full_name, **kwargs): if not full_name: raise ValueError("Organization类型Owner必须填写full_name") return self.create(owner_type='organization', full_name=full_name, **kwargs) # 在Owner模型中添加 objects = OwnerManager()
使用方式示例:
# 创建个人所有者 person_owner = Owner.objects.create_person(first_name="John", last_name="Doe", date_of_birth="1990-01-01") # 创建组织所有者 org_owner = Owner.objects.create_organization(full_name="Acme Corp", tax_id="123456789")
4. 基于抽象基类构建其他业务模型
直接继承Person或Organization即可:
class Designer(Person): specialty = models.CharField(max_length=100) years_of_experience = models.IntegerField(default=0) class Painter(Person): medium = models.CharField(max_length=50) studio_address = models.TextField(null=True, blank=True) class NonProfitOrg(Organization): mission_statement = models.TextField() funding_source = models.CharField(max_length=100)
关键优势
- 仅
Owner模型会生成数据库表,符合需求; - 通过类型标记和属性拦截严格实现"二选一"的实例限制;
Person/Organization保持抽象基类特性,可复用给其他业务模型;- 动态
name属性统一对外暴露名称字段,无需区分实例类型。
内容的提问来源于stack exchange,提问作者swiss_knight
相关产品推荐
相关产品推荐

