写在前面:写这篇文章的初衷是为了push自己做数据库的大项目。
我深知自己的惰性...项目从三月份出需求到现在快要到ddl了我一直都没有动,时间一拖再拖,不想成为赶ddl的战士。而且最近发生了一些事情,让我重新开始审视自己的方向。
大一的时候进来接触java的皮毛,之后就用各种借口逃避,觉得自己不适合后端开发。在此后的差不多一年时间内,我一边羡慕着别人项目经理丰富,一边给自己洗脑说大家都会,我以后也会会的。
时间来到java开发课,这门课程要求大家组队完成项目,我成功抱到了大佬的大腿,靠着拙劣的ui和需求分析在小组内拖后腿,直到结课也没有了解到任何关于开发的知识;
再到后面双创项目,我再次没有按时完成交给我的任务。在最后赶DDL的时候,我疯狂怀疑自己:我是不是真的学不会?
我一直觉得开发不是我不学不会,是我还没入门。当然入门之后也会有很多难点,但是起码有个抓手。我一直泛泛地游走在开发的大门外,用一些一知半解的术语和外行人搭话,但是怎么能一直这么下去呢?
本次数据库课程的大项目为小组形式开发论文管理系统,采用前后端分离架构。但是考虑到我自己java学的很烂,短时间上手SpringBoot很难,所以选择了Django。
python是在短学期课程上学的理论,之后实践课也是洒洒水就那么过去了。之后虽然做过一些简单的爬虫,但是对python的语法和特性实在不是很理解。此外,对于http、接口方面的知识也是浅浅地了解过。
所以写这样的文章,记录一下自己的django学习历程。希望日后回望的时候,不要再说出“我不适合敲代码”这样的话。
一.我想达到的效果
网上查了很多教程,甚至花钱买了复用看起来比较多的优秀源码,暂时发现是下面两种架构:
1.Django采用MTV模式,使用Templates模版,需要部分前端知识,前后端不分离;
2.前后端分离,只开发后端,后端开发将接口文档给到前端。
由于Django本身是MTV架构,所以可以预想到前后端分离其实并不是主流接受的,我在后续学习中也会补补前端的坑,但是现在敏捷开发,以实现为主。
关于接口文档,在Springboot中使用过swagger2自动生成;在django中则有restful风格的接口文档,但是restful是同一个url使用GET、POST、UPDATE、DELETE四种不同的http请求方法,而在日常开发中基本上只会用到GET和DELETE(其实本质是还没有研究透,所以不去使用这个。
希望能做到的是使用类似swagger的第三方库生成接口文档,但是本机一直安装失败,还需要再摸索一下。
二.简单接口测试
参考链接:使用Django开发简单接口:文章增删改查 - 秦无殇 - 博客园
1.准备工作不再赘述,建立blog_demo项目和blog_api应用,将csrf暂时注释掉;
2.blog-models 新建User类和Article类:
class User(models.Model):
id = models.AutoField(primary_key = True)
username = models.CharField(max_length=50)
password = models.CharField(max_length=100)
#active inactive
status = models.CharField(max_length=100)
class Article(models.Model):
id = models.AutoField(primary_key = True)
title = models.CharField(max_length=50)
content= models.TextField()
#deleted alive
status = models.CharField(max_length=10)
其中status字段原作者设置的不太好,用关键字-数字代替比较合适。
补充一点在model中学到的知识:
1.数字类
- Django为每一个表隐式设计了自增的id字段,其作用相当于User类中的第一句id语句,在有对象实例化的时候不需要显式声明。Django Model默认提供,可以被重写.
- BooleanField对应tinyint(1)。布尔类型字段,一般用于记录状态标记。
- DecimalField对应decimal。开发对数据精准要求较高大的业务时考虑使用。比如:cash=models.DecimalField(max_length, decimal_places=2, default=0,verbose_name=“消费金额”),就是定义长度为8位、精度位2位的数字,例如数字:666666.66。)
- IntergerField对应int(11)。同AutoField一样,唯一的差别就是不自增。
- PositiveIntegerField。同IntegerField,只包含正整数.
- SmallIntegerField对应smallint。小整数时一般会用到。
2.字符型
django对应到Mysql中有两种类型:longtext和varchar。
除了TextField是longtest类型外,其他属于varchar类型。
- CharField对应varchar。基础的varchar类型。
- URLField:继承自CharField,但是实现了对URL特特殊处理。用来存储URL数据,非URL数据可以在业务层就拒绝掉,不会存入数据库中。
- UUIDField:对应char(32),除了在PostgreSQL中使用的是uuid类型外,在其他数据库中均是固定长度char(32),用来存放生成的唯一id。
- EmailField:同URLfield一样继承自CharField,多了对email的特殊处理。
- FileField:同URLField一样,它继承自CharField,对了对文件的特殊处理。
- TextField:对应longtext,一般用于存放大量文本内容,比如新闻正文、博客正文。
- ImageField:继承自FileField,用来处理图像。
3.时间型
django中有三种日期类型,分别对应Mysql的date、datetime和time。
- DateField对应date
- DateTimeField对应datetime
- TimeField对应time
4.关系类型 ForeignKey
这里外键写在哪个表里一定要注意
a=models.ForeignKey(A)
- OneToOneField,一对一
- ManyToManyField,多对多
5.参数
上面所提到的各种field的参数,一般有如下:
- default:设置默认值
- db_column:数据库中字段名
- verbose_name:后台显示对应的名称
- help_text:对字段的解释说明
- null=True/False:针对数据库,如果 null=True, 表示数据库的该字段可以为空。
- blank=True/False: 针对表单,如果 blank=True,表示表单填写该字段的时候可以不填
需要注意的是,日期型(DateField、TimeField、DateTimeField)和数字型(IntegerField、DecimalField、FloatField)不能接受空字符串,如要想要在填写表单的时候这两种类型的字段为空的话,则需要同时设置null=True、blank=True;
另外,设置完null=True后需要重新更新一下数据库。
- on_delete:用在外键属性中,on_delete=models.CASCADE/SET_NULL/NO_ACTION?SET_DEFAULT
- default=timezone.now:时间类中创建时自动填充当前时间,一般用于存储创建时间
- default=auto_now:时间类中修改时自动填充修改时间,一般用于存储update_time
- choices=xx:需预定义xx的内容,一般是选择较少的性别/用户类型等。
3.blog_api.views.py下对Article的增删改查:
我们一个个来看
【查】为了方便验证后面增、删、改的结果,先实现查的功能。
查在我的理解里分为两种,list和search,由于是入门所以仅实现全部的list和对username查找的search。目前还没有做分页,后面会学习使用django的分页器paginator完善。
列出所有status是active的用户信息:
def list_articles(req):
if req.method == 'GET':
articles = {}
query_art = Article.objects.all()
query_art = query_art.filter(status='alive')
for art in query_art:
articles[art.title] = art.content
return JsonResponse({
'status': 200,
'all_titles': articles,
'msg': 'this query is successful!',
})
因为使用的是逻辑删除,设置status,所以list在筛选的时候加了个filter;
使用字典key-value的模式,但是我自己现在有点迷,不知道怎么让每条记录都呈现出来,再摸索一下。
postman测试接口:
search:
def search_articles(request):
#serializers.serialize("json",models)
if request.method == 'POST':
req = json.loads(request.body)
info = req.get('data')
articles={}
filter_title=info['title']
filter_content=info['content']
ans=Article.objects.filter(Q(title__icontains=filter_title)&Q(content__icontains=filter_content))
for art in ans:
articles[art.title] = art.content
return JsonResponse({
'status':200,
'data':articles,
'msg':'ok'
})
这个接口是我自己写的,格式借鉴了上面list的结构。
使用Django自带的Q查询,icontains不区分大小写,contains区分大小写。
测试一下已经加入的五条数据:
title中含有test(大小写均可);
再测试一下:
可以看到这个简单的对title和content的模糊搜索已经完成了。
【增】
#新增文章
def add_article(request):
if request.method == "POST":
req = json.loads(request.body)
#print (req)
key_flag = req.get("title") and req.get("content") and len(req)==2
#判断请求体是否正确
if key_flag:
title = req["title"]
content = req["content"]
#title返回的是一个list
title_exist = Article.objects.filter(title=title)
#判断是否存在同名title
if len(title_exist) != 0:
return JsonResponse({"status":400,"msg":"title already exist,fail to publish."})
'''插入数据'''
add_art = Article(title=title,content=content,status="alive")
add_art.save()
return JsonResponse({"status":200,"msg":"publish article success."})
else:
return JsonResponse({"status":401,"message":"please check param."})
json.load(request.body)表明在postman测接口的时候要使用body传参;
req.get("参数名")可以获得request中的参数,但是从原博主给出的代码猜测只能获得一个参数;
model类.objects.filter()可以对数据库中的数据进行筛选;
插入/修改数据之后一定要save();
对于原博主的JsonResponse的状态码,小白不太确定,感觉Json的状态码应该是用户自定义的,所以这里也可以正常事件返回0,异常时间流返回1/2/3之类的。
在postman中测试:
尤其注意,当手动输入json时一定要是双引号;最后一个参数后没有逗号;url最后一个斜杠不要缺。
查看一下是否添加成功:
【删】
这里原博主采用了另外一种处理方法,将需要删除的文章的id通过url传递
def delete_article(request,art_id):
if request.method =="POST":
try:
art = Article.objects.get(id=art_id)
art.status='deleted'
art.save()
return JsonResponse({
'status':200,
'msg':'delete success'
})
except Article.DoesNotExist:
return JsonResponse({
'status':300,
'msg':"article not exist"
})
对应的url:
path('delete_articles/',delete_article),
原博主直接采用从数据库中删除的方法,但是我由于要做逻辑删除所以将status字段设置成了deleted(还需要完善)。
目前这种方式只能支持单个删除,而不支持批量删除,后续会再改进;
在postman中测试:
查看list:
虽然我这里并没有显示id,但是由于id是autoField,所以排在第一条的记录被删除,也印证了是正确的;
尝试删除并不存在的文章:
【改】修改部分的url使用和delete一样的逻辑
path('update_articles/',modify_article),
views部分的代码如下:
def modify_article(request,art_id):
if request.method =='POST':
req = json.loads(request.body)
try:
art = Article.objects.get(id=art_id)
key_flag = req.get('title') and req.get('content')
if key_flag:
title = req['title']
content = req['content']
title_exist=Article.objects.filter(title=title)
if len(title_exist) > 1:
return JsonResponse({
'status':400,
'msg':'title already exists.'
})
mod_art =Article.objects.get(id=art.id)
mod_art.title=title
mod_art.content=content
mod_art.save()
return JsonResponse({
'status':'200',
'msg':'change success.'
})
except Article.DoesNotExist:
return JsonResponse({
'status':300,
'mag':'article does not exist,fail to change',
})
但是原博主的代码有一点缺陷,前端form中出现的所有字段都必须传回后端,但是实际上修改的可能只有一个字段,因而使用这种模式可能会是消息体比较大,下面在user类中会有所改善。
在postman中测试:
在list中查看修改结果:
但是在测试中,我注意到我自己修改原博主的逻辑删除又很不完善的地方。此处应该限制用户只对active的文章做修改和删除操作,后面会修改。
对Article的CRUD简单操作基本上到这里就结束了。
其实我也有看到一些项目中,User继承AbstractUser、view继承CreateView、UpdateView等,但是由于当时连简单的增删改查都不会手写实现,也没有形成使用虚拟环境的好习惯,所以只是望而生畏,后面也会做尝试。如果观众老爷有比较推荐的方式也很希望大家能指点一下小白。
此外,原文章中还介绍了朴素的鉴权,实现简单的认证机制,防止在未登录时进行上述操作。
在urls.py中增加用于鉴权的接口:
path("auth/",get_token)
在views.py前面新增函数get_token(request)
import hashlib
#获取token
def get_token(request):
req = json.loads(request.body)
uname = req["username"]
upwd = req["password"]
if request.method == "POST":
try:
tmppwd =User.objects.get(uname=uname).upwd
if upwd == tmppwd:
md5 = hashlib.md5()
#把密码变成一个长度固定的字符串
md5.update(upwd.encode("utf-8"))
return JsonResponse({"status":"BS.201","X-Token":md5.hexdigest()})
else:
return JsonResponse({"status":"BS.401","msg":"username or password may wrong."})
except User.DoesNotExist:
return JsonResponse({"status":"BS.500","msg":"username is not exist."})
该函数使用md5加密明文密码。使用到了内建模块hashlib,hashlib提供了常见的摘要算法,如MD5、SHA1等。
之后做认证动作:
这里的认证动作本不应该这么简单,简单粗暴地将用户生成的token取出放在代码中是非常不可取的。
#认证动作
def user_auth(request):
token = request.META.get("HTTP_X_TOKEN",b'')
print (token)
if token:
#暂时先写上auth接口返回的数据
if token=="e10adc3949ba59abbe56e057f20f883e":
return "auth_sucess"
else:
return "auth_fail"
else:
return "auth_fail"
在接口中调用user_auth函数,以增加文章接口为例,在文章前面加上鉴权语句:
auth_res=user_auth(request)
if(auth_res == 'auth_fail'):
return JsonResponse({
'status':401,
'msg':'user auth failed'
})
测试该接口:
没有鉴权:
有鉴权:
在读代码的过程中也了解到有内置的鉴权方法,如LoginRequiredMixin类的使用和@login_required的注解等。
明天会再巩固python的数据结构,优化CRUD的操作,实现1.多条件查询;2.查询结果分页;3.批量删除。并把自己项目中的简单增删改查写完,至于一些复杂的级联删除可以留在后续分析。学习鉴权(token、session、cache),把自己项目中的登录注册完成。



