在众多业务相关的模型中,如Product,Project都有进行备注的需求,而且备注是多条的,即Product或Project等和备注是一对多的关系。
项目在刚开始的时候,定义Remark模型,并在Remark模型中定义和其他需要备注功能的模型的外键,实现一对多的关系。
一开始时,项目不大,要使用备注的模型不多,就3-5个左右,用这种方法还是能够接受的。但随着需要使用备注的模型越来越多,我就对这种设计越来越不爽,因为每新增一个新的模型,就要在Remark模型中新增一个外键,但一直没有找到什么好的方法(对Django了解得还是太少,直接就开干项目了)。
后面在做历史记录功能时,接触到了LogEntry这个模型,这个模型里使用了ContentType,在了解了ContentType的用法后,果断改造Remark模型。
class Remark(BaseModel):
info = RichTextUploadingField('备注', blank=True)
creater = models.ForeignKey(
User, on_delete=models.SET_NULL, blank=True, null=True,
verbose_name='创建者', related_name='remarks_created')
updater = models.ForeignKey(
User, on_delete=models.SET_NULL, blank=True, null=True,
verbose_name='更新者', related_name='remarks_updated')
content_type = models.ForeignKey(ContentType, blank=True,null = True,on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(blank=True,null = True)
content_object = GenericForeignKey('content_type', 'object_id')
精髓其实是在content_type,object_id和content_object上。
ContentType的用法不在这里过多讲解,就是通过content_type和object_id这两个属性就可以让某个Remark对象唯一对应到其他model对象上。
content_object不是一个field,自行搜索了解下GenericForeignKey就明白了。
使用了ContentType的设计后,不敢后面的需要新增多少模型,Remark这个model都不用动了,除非Remark本身的设计需要优化。
在CommonModel这个抽象类中,有一个get_remarks函数,其他继承了CommonModel的model对象通过调用get_remarks就可以获取到相关的所有备注信息了。
def get_remarks(self):
try:
remarks = Remark.objects.filter( Q(content_type = self.get_contentType(), object_id = self.id) )
return remarks
except Exception as err:
ccom_log_error(err)
return Remark.objects.none()