Django将某个数据库字段给多个app使用

前言

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的控制台页面的情况是这样的:
paradin

可见里面就一个叫Articles的app,点开之后,发现对应的项目也很简单,只有idtitle这两个字段而已:
paradin

本次试验的目的就是新建立一个文章统计计数的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
10
INSTALLED_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
10
from 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_typeobject_id的字段命名都是作为字符串参数传进content_object的。

配置了数据库,肯定需要python manage.py makemigrationspython manage.py migrate
paradin

数据更新完毕之后,修改一下负责后台展示的admin.py

1
2
3
4
5
6
7
from django.contrib import admin
from .models import ReadNum #引用ReadNum这个模型
# Register your models here.

@admin.register(ReadNum) #装饰器
class ReadNumAdmin(admin.ModelAdmin):
list_display = ('read_num','content_object')

此时刷新一下django页面就看到read_stats这个app已经注册成功了:
paradin

由于是新的,所以里面空空如也,点击一下ADD,就可以输入值了:Read num就是设定的“阅读次数”,Content type这个数据是一个选择项,选择需要对应的数据库模型,即Article这个app里的models.py的类—Article,而Object idArticles对应的文章编号:
paradin

这样达到了后台配置“将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
18
from 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
7
from django.contrib import admin
from .models import Article
# Register your models here.

@admin.register(Article)
class Article(admin.ModelAdmin):
list_display = ('id','title','get_read_num') #这里新加上刚才的那个方法

由于admin.py里返回的必须是字段,所以我们才在models.py里添加了一个方法去生成字段。

刷新一下Django后台页面,就看到效果了:
paradin

至此,这个read_num数据就同时被两个APP关联分享了。至于再把read_num通过一定的处理方法之后映射到html前端就很简单了。

参考资料

https://docs.djangoproject.com/en/2.1/ref/contrib/contenttypes/ (官方文档)

-------------This article is over!Thanks for reading!-------------
感谢您请我喝咖啡!(o´ω`o)
0%