前言
Django里经常会有这样的一个需求—-同样的一组数据要给很多个app使用。比如一个运维系统,运维人员的名单就既要给“项目部署”这个APP用又要给“责任负责人”这个APP用。如果每次都要去跨应用去from XXX.models import xxx
的话,代码感觉很不友好。那么要解决这个问题,就要用到django自带的ContentTypes
框架。以下是所用软件版本:
Django:2.1.1
Python:3.6.4
old app:Articles
new app:read_stats
原始状态与前期配置
目前在django的控制台页面的情况是这样的:
可见里面就一个叫Articles
的app,点开之后,发现对应的项目也很简单,只有id
和title
这两个字段而已:
本次试验的目的就是新建立一个文章统计计数的app,在里面配置数据库,然后让原来的blog这个app能够使用得到新app的数据项。
首先先建立一个专门用来计数的app,比如就叫read_stat
。那么就在django项目路径下python manage.py startapp read_stats
,再把这个新的app名称添加到settings.py
里:
1
2
3
4
5
6
7
8
9
10INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'article', #先加载django自身的app,然后是第三方app,最后是自己开发的app
'read_stats',
]
编辑一下read_stats
里的models.py
,创建模型先:
1
2
3
4
5
6
7
8
9
10from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey #这句话是固定的,引用类型
from django.contrib.contenttypes.models import ContentType #这句话是固定的,引用类型
# Create your models here.
class ReadNum(models.Model):
read_num = models.IntegerField(default=0) #设定read_num就是一个普通的数字
content_type = models.ForeignKey(ContentType,on_delete=models.DO_NOTHING) #说明这是一个外键,即关联的模型,加上后面的话的意思是:即使删除了这个字段也不会影响其他数据
object_id = models.PositiveIntegerField() #这里是一个主键,即pk
content_object = GenericForeignKey("content_type","object_id") #通过上面两个变量,配置成一个通用的外键
通过使用一个content_type
属性代替了实际的model
(如Post,Picture),而object_id
则代表了实际model中的一个实例的主键,其中,content_type
和object_id
的字段命名都是作为字符串参数传进content_object
的。
配置了数据库,肯定需要python manage.py makemigrations
和python manage.py migrate
:
数据更新完毕之后,修改一下负责后台展示的admin.py
:
1
2
3
4
5
6
7from django.contrib import admin
from .models import ReadNum #引用ReadNum这个模型
# Register your models here.
class ReadNumAdmin(admin.ModelAdmin):
list_display = ('read_num','content_object')
此时刷新一下django页面就看到read_stats这个app已经注册成功了:
由于是新的,所以里面空空如也,点击一下ADD
,就可以输入值了:Read num
就是设定的“阅读次数”,Content type
这个数据是一个选择项,选择需要对应的数据库模型,即Article这个app里的models.py
的类—Article
,而Object id
就Articles
对应的文章编号:
这样达到了后台配置“将Article应用里的第2篇文章的阅读次数上调到了99次”。
数据库的跨app配置
刚才手动在后台配置完毕,但是目前这个read_num
数据只能是在read_stats
这个app里自嗨。要给让Article
能够得到这个read_num
的话,就需要通过模型获取到具体数值,这里要用到ContentType.objects.get_for_model
方法。首先要配置Article
下的models.py
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18from django.db import models
from django.db.models.fields import exceptions #引入错误给try...except使用
from django.contrib.contenttypes.models import ContentType #引入ContentType
from read_stats.models import ReadNum #从另一个app里引入类
# Create your models here.
class Article(models.Model):
title = models.CharField(max_length=30)
content = models.TextField() #这是它原来的数据库内容
#添加一个方法给admin.py使用,如果有就直接返回值(字符串),如果没有object就返回一个0
def get_read_num(self):
try:
ct = ContentType.objects.get_for_model(self) #确定ContentType
readnum = ReadNum.objects.get(content_type=ct,object_id=self.pk) #每个readnum都是content_type和object_id对应的QuerySet
return readnum.read_num #这样返回就是一个具体的值,不然只是一个数据
except exceptions.ObjectDoesNotExist:
return 0
再修改Article
下的admin.py
,让后台可以体现出来read_num
:
1
2
3
4
5
6
7from django.contrib import admin
from .models import Article
# Register your models here.
class Article(admin.ModelAdmin):
list_display = ('id','title','get_read_num') #这里新加上刚才的那个方法
由于admin.py
里返回的必须是字段,所以我们才在models.py
里添加了一个方法去生成字段。
刷新一下Django后台页面,就看到效果了:
至此,这个read_num
数据就同时被两个APP关联分享了。至于再把read_num
通过一定的处理方法之后映射到html前端就很简单了。
参考资料
https://docs.djangoproject.com/en/2.1/ref/contrib/contenttypes/ (官方文档)