Django进阶关联表/模型:多场景文件关联的复用实现问询
可行实现思路
一、自定义关联字段方案
这是最直观的复用方式,针对每个文件类型定义绑定category的字段,自动处理文件创建和关联逻辑:
- 定义继承自
ForeignKey的自定义字段FileCategoryForeignKey,初始化时接收category参数 - 重写字段的
pre_save方法,当关联的文件字段有上传时,自动在all_files表创建对应category的记录,并把ID赋值给当前字段 - 示例代码:
from django.db import models from .models import AllFiles class FileCategoryForeignKey(models.ForeignKey): def __init__(self, category, **kwargs): self.category = category super().__init__(AllFiles, **kwargs) def pre_save(self, model_instance, add): # 匹配对应的文件字段(比如contract_file_id对应contract_file) file_field_name = self.name.replace('_id', '') file_obj = getattr(model_instance, file_field_name) if file_obj and add: all_file = AllFiles.objects.create( category=self.category, file=file_obj ) setattr(model_instance, self.name, all_file.id) return super().pre_save(model_instance, add)
- 使用示例:
class WorkOrder(models.Model): owner_id = models.UUIDField() contract_file = models.FileField(upload_to='contracts/', blank=True) contract_file_id = FileCategoryForeignKey( category='contract', on_delete=models.SET_NULL, null=True, blank=True )
二、Mixin方案
针对模型层面复用,封装文件上传和关联的通用逻辑:
- 定义
FileAssociatedMixin类,子类需声明file_category_map属性,指定{文件字段名: 对应category}的映射 - 在Mixin的
save方法中统一处理文件上传,自动创建all_files记录并关联 - 示例代码:
class FileAssociatedMixin: file_category_map = {} def save(self, *args, **kwargs): add = self._state.adding if add: for file_field, category in self.file_category_map.items(): file_obj = getattr(self, file_field) if file_obj: all_file = AllFiles.objects.create( category=category, file=file_obj ) setattr(self, f"{file_field}_id", all_file.id) super().save(*args, **kwargs)
- 使用示例:
class WorkOrder(FileAssociatedMixin, models.Model): owner_id = models.UUIDField() contract_file = models.FileField(upload_to='contracts/', blank=True) contract_file_id = models.ForeignKey(AllFiles, on_delete=models.SET_NULL, null=True, blank=True) file_category_map = { 'contract_file': 'contract' }
三、元类方案
适合批量处理多模型场景,自动为模型添加关联字段和处理逻辑:
- 定义
FileAssociatedMeta元类,扫描模型Meta类的file_fields配置,自动生成关联ID字段并绑定save逻辑 - 示例代码:
class FileAssociatedMeta(models.base.ModelBase): def __new__(cls, name, bases, attrs): file_config = attrs.get('Meta', {}).get('file_fields', {}) # 自动生成关联ID字段 for file_field, category in file_config.items(): attrs[f"{file_field}_id"] = models.ForeignKey( AllFiles, on_delete=models.SET_NULL, null=True, blank=True ) # 创建模型类并覆盖save方法 model_class = super().__new__(cls, name, bases, attrs) original_save = model_class.save def new_save(self, *args, **kwargs): add = self._state.adding if add: for file_field, category in self._meta.file_fields.items(): file_obj = getattr(self, file_field) if file_obj: all_file = AllFiles.objects.create( category=category, file=file_obj ) setattr(self, f"{file_field}_id", all_file.id) original_save(self, *args, **kwargs) model_class.save = new_save return model_class
- 使用示例:
class WorkOrder(models.Model, metaclass=FileAssociatedMeta): owner_id = models.UUIDField() contract_file = models.FileField(upload_to='contracts/', blank=True) class Meta: file_fields = { 'contract_file': 'contract' }
四、文件访问时的owner_id查询处理
在AllFiles模型中添加自定义方法,根据category反向查询对应模型的owner_id:
import uuid from django.db import models from .models import WorkOrder, LabourLicense class AllFiles(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) category = models.CharField(max_length=50) file = models.FileField(upload_to='files/') def get_owner_id(self): # 映射category到对应模型和关联字段 model_map = { 'contract': (WorkOrder, 'contract_file_id'), 'labour_lic': (LabourLicense, 'license_file_id') } model_class, field_name = model_map.get(self.category, (None, None)) if model_class: obj = model_class.objects.filter(**{field_name: self.id}).first() return obj.owner_id if obj else None
内容的提问来源于stack exchange,提问作者fishfin
相关产品推荐
相关产品推荐

