高效的学习方法就是做题,所以这个就是一个用来实例的题目,学以致用,解决问题才是第一步,不然学到的知识没有用,没有用的知识就不会引起重视。
1、定义一个Car的model类,有以下属性
name字符串属性,唯一约束,最大长度为64个字符
color整型选择属性,选项((0,银色),(1,黑色),(2,红色),(3,灰色))默认银色
price,全精度小数类型,最高车价9999999.99
brand,图片文件类型,图片默认在img下,默认图片default.png
slogan,字符串类型
2、完成单查、群查接口
正确响应,显示所有字段信息
异常响应,要明确网络状态码
3、完成单增接口
name:必须提供,唯一的约束,名字只能是字母(汉字)和数字
color:可以不提供,提供就参与校验
price:不能为负值
image:不需要提供,提供也不要
slogan:必须是字母
re_slogan:必须和slogan相同
需要做哪些事情
创建模型类,在数据库中建表,添加数据
创建视图函数,返回给前台数据
给出响应类中的显示信息
创建序列化类,前台往后台发送数据
进行配置等等,配置路由,把视图函数的数据映射给路由等等
首先是创建项目
到指定的文件目录下,我创建的是drf_project20201005这个项目名,需要创建一个api名字的APP,
基本的配置在上一篇博客中有提到,https://blog.csdn.net/medigrat/article/details/108806709,
有需要os模块的导入,这边更新需要的数据库mysql配置,首先下载一个叫做pymysql的模块,在settings中导入这个模块,并且pymysql.install_as_mysqldb()
数据库里面的配置也需要更改,就是那个数据库名用mysql,数据库用我新建的drf_project20201005,用户名与密码都需要配置,
基础配置好了之后就开始了以下步骤的操作,也就是上面的需求分析的需要做的那些事情
1、创建模型类
class Car(models.Model):
COLOR_CHOICE=((0,"银色"),(1,"黑色"))
name=models.CharField(max_length=64,unique=True)
color=models.IntegerField(choice=COLOR_CHOICE,default=0)
price=models.DecimalField(max_digit=9,decimal_place=2)
brand=models.ImgField(upload_to="img",default="img/default.png")
slogan=models.CharField(max_length="64")
这边有一个注意点,这个img文件的配置
2、在settings中注册APP,在admin文件中注册Car模型类
from . import models
admin.site.register(models.Car)
3、数据迁移,建表,创建超级用户(在此之前,在settings中的app中注册rest_framework"等(哈哈)"App,我知道读者不喜欢不明确的,可是这边就是这个,如果有问题,看app有没有注册,please~,go on)
在terminal终端输入数据迁移命令:
python manage.py makemigrations
python manage.py migrate
创建超级用户:
python manage.py createsuperuser
从浏览器中进入后台管理,添加数据
添加好数据,恭喜你,你已经完成好了第一步啦!
接下来,
1、创建视图函数,定义get请求的单查群查接口
1-1正常响应与异常响应的信息
from rest_framework import APIView
from . import models
from rest_framework.response import Response
from rest_framework import status
class CarAPIView(APIView):
def get(self,request,*args,**kwargs):
pk = kwargs.get("pk")
if pk:
try:
obj = models.Car.objects.get(pk=pk)
serializer = serializers.CarModelSerializer(obj )
return Response(data={
"status":0,
"msg":"ok",
"results":serializer.data
})
else:
return Response(data={
"status":1,
"msg":"pk error"
},status=status.HTTP_400_BAD_REQUEST,exception= True)
else:
queryset = models.Car.objects.all()
serializer = serializers.CarModelSerializer(queryset ,many=True)
return Response(serializer .data)
穿插在里面,在api这个app下创建序列化类的文件serializers,
from rest_framework import serializers
class CarModelSerializer(serializers.Modelserializer):
class Meta:
model=models.Car
fields=["name","color","price","brand","slogan"]
这边有两个注意点,一个是color,一个是brand的序列化信息的展示,
question:为什么这个pk在kwargs里面,
明白了一点为什么pk=pk了,因为这里面是要有参数的,这个参数的值是pk,就是不知道这里面的pk是不是默认参数的写法?
分析正确响应与错误响应的问题,怎么会有这样的问题?
这边怎么写正确响应与错误响应的返回信息呢,正确响应直接往下执行,错误响应报错误信息,用try与except来写,错误会是什么错误,是单查的时候,没有pk所对应的对象,这个信息就会是错误,所以写在if pk的后面,也就是如果pk错误的话,就报pk有问题,这边有一个模块就是那个报错误的信息的对象,serializers.error,这个serializer是哪个导入的模块的吗?
这边还有一个是响应类分析,
response返回的是什么?
里面的参数分析
这边关注三个,一个是data,一个是status,一个是exception
data就是返回的从后台给到前台的字段信息,按照位置传参,所以可以用关键字给定参数,一开始的serializer.data直接就赋值给了data了,
定义post请求的单增接口
这边前台发送post请求的时候应该有问题,因为有post请求,可是什么东西都没有,一个完整的校验什么的,所以应该会有问题,
单增需要做什么事情,这个是用post请求过来的数据,也就是post带了数据包过去,数据包丢给序列化类来处理,然后序列化类做校验,如果ok,就还有一步骤叫做入库,入库之后,返回给前台一个信息,表示请求完成,
我们序列化类的校验,校验什么东西,也就是前台发送到后台的数据,这些数据,哪些发,以及一些基础校验规则还有自定义校验规则就是在这个序列化类中自定义的,这些就是那个题目要求的一些规范了,前台发过来的数据在哪边?
先写单增接口,把通道打通,然后写自定义的校验规则,
def post(self,request,*args,**kwargs):
request_data=request.data
serializer= serializers.CarModelSerializer(data=request_data)
if serializer.is_valid():
obj = serializer.save()
return Response({
"status":0,
"msg":"ok",
"results":"新增的那个对象"
},status=status.HTTP_201_CREATED)
else:
return Response({
"status":1,
"msg":serializer.errors,
},status=status.HTTP_400_BAD_REQUEST)
按照题目要求来写自定义的校验规则,一个是基础的校验规则,一个是自定义的校验规则,
name这个字段是在原本的serializer类中定义的字符串属性,所以就是必须提供的,这边的要求是只能是字母(汉字)和数字,
这个需要自定义的校验规则了,而且不是基础的校验规则了,
这边要用到一个局部钩子的概念,还有就是那个匹配的字母与汉字和数字的要求的概念,当有这个name需要校验的时候,就走这个数据,也就是说这个钩子,是外部进行定义的,没有就不走这个方法,有就触发这个方法,ok,相当于设置了一个暗卡在那边,
在serializers这个文件中的类中进行定义,这个是与内部类同一个代码块的这一行,
def validata_name(self,value):
import re
if not re.match(r'^[\u4e00-\u9fa5a-zA-Z-z0-9]+$'):
raise exception.VlidationError("汽车名不合法")
return value
color可以不提供,提供就参与校验,
这边就需要一个read_only与write_only的区别是什么?
这边的color在后台是一个整数选择属性,color是都可可以的,用来显示的选择属性是默认的是read_only属性,因为设置这个用来拿取color名称空间的属性的名称如果设置write_only的话是会报错的,可以不提供,提供就参与校验,那么就是write_only属性,写了就校验,查询的时候你也看不到这个。
只读;只写。只读就是只能看,不能修改与存储,只写,就是可以修改与存储,但是不能看,
这边写的就是write_only,这边写的就是write_only,color,在基础校验类中定义的
price:不能为负值,这个也在基础校验规则中定义,
image:不需要提供,提供也不要,意思是不参与序列化,
这个现在后台给了这个需要定义的啊,难道是因为这个就是一个choice,然后就不需要提供,(对的,就是这个思路,是一个默认的read_only的属性,)但是,提供了咋办呢?提供也不要的话,就是在前台上面不显示的啊,(错,是一个read_only,显示但是不需要这个提交)麻烦试一下,
extra_kwargs={
"color":{"write_only":True},
"price":{"min_value":0}
}
slogan:必须是字母
re_slogan:必须和slogan相同
这两个是自定义的规则,一个是本来就需要定义的,一个是自己自定义的,在做两个的匹配,
re_slogan=serializers.CharField(write_only=True)
这个需要写,write_only,不然就会报没有这个attrs这个属性的意思,这个是自己需要添加进去的
这个方法写在内部类的这一级别上,表示是字母,不能是其他的东西,跟name一样的定义,
def validate_slogan(self,value):
import re
if not re.match(r'^[u4e00-\u9fa5a-zA-Z-z]+$',value):
raise exceptions.ValidationError("标语不对")
re_slogan:必须和slogan相同,这个写在全局的钩子方法中,
用到了attrs的两个属性,一个是get,一个是pop,get拿到slogan,pop弹出re_slogan,做判断是否相等,不等就有错误,相等就返回这个attrs出来,
def validate(self,attrs):
slogan=attrs.get("slogan")
re_slogan=attrs.pop("re_slogan")
if slogan != re_slogan:
raise exceptions.ValidateError({'re_slogan':"确认标语有误"})
return attrs
做好规划,就是明天把这个尾巴结束掉,然后就是一个路由的配置的问题了
配置路由,反序列化的一些注意点
配置路由,主路由配置,图片路由配置,路由分发
include模块导入,serve模块的导入
from django.urls import include
from django.views.static import serve
from . import settings
urlpatterns=[
path('api/',include("api.urls"))
path('media/' ,serve,{"document_root":settings.MEDIA_ROOT})
]
settings中配置MEDIA_ROOT
MEDIA_ROOT=os.path.join(BASE_DIR,“media”)
用来返回media的url,
MEDIA_URL="/media/"
BASE_URL="http://127.0.0.1:8000"
反序列化展示的问题,
一个是img,一个是color,
color给到前台,这个信息给到前台做前台到后台是否可以传的实验,上面的问题,
img,需要用icon来做名称空间的调用。
xcolor是用来取具体的color的选择的属性的,而不是0,1,2,3,是汉字的属性
@property
def xcolor(self):
return self.get_color_display
@property
def icon(self):
return BASE_URL + MEDIA_URL + self.img.name
这样来显示颜色的中文,与具体的图片链接的url,
ok所有数据都ok,用postman来测试一下就可以了。做一下刚才那个color的测试,看提供了是不是也不校验,提供一个给他,看接收不接收,
来写这个代码,来,
api/admin.py
from django.contrib import admin
from . import models
admin.site.register(models.Car)
api/models.py
from django.db import models
from drf_project20201005.settings import BASE_URL, MEDIA_URL
class Car(models.Model):
COLOR_CHOICE=((0,"银色"),(1,"黑色"),(2,"红色"),(3,"蓝色"))
name=models.CharField(max_length=64,unique=True)
color=models.IntegerField(choices=COLOR_CHOICE,default=0)
price=models.DecimalField(max_digits=9,decimal_places=2)
brand=models.ImageField(upload_to="img",default="img/default.png")
slogan=models.CharField(max_length=64)
@property
def xcolor(self):
return self.get_color_display()
@property
def icon(self):
return BASE_URL + MEDIA_URL + self.brand.name
api/serializers.py
from rest_framework import serializers
from api import models
from rest_framework import exceptions
class CarModelSerializer(serializers.ModelSerializer):
re_slogan=serializers.CharField(write_only=True,max_length=64)
class Meta:
model=models.Car
fields=["name","color","xcolor","brand","price","icon","slogan","re_slogan"]
extra_kwargs = {
"color":{
"write_only":True,
},
"price": {
"min_value": 0,
},
"icon": {
"read_only": True
}
}
def validate_name(self,value):
import re
if not re.match(r'^[\u4e00-\u9fa5a-zA-Z-z0-9]+$',value):
raise exceptions.ValidationError("汽车名不合法")
else:
return value
def validate_slogan(self,value):
import re
if not re.match(r'^[\u4e00-\u9fa5a-zA-Z-z0-9]+$',value):
raise exceptions.ValidationError("标语不合法")
else:
return value
def validate(self,attrs):
slogan=attrs.get("slogan")
re_slogan=attrs.pop("re_slogan")
if slogan != re_slogan:
raise exceptions.ValidationError({"re_slogan":"标语确认不合法"})
else:
return attrs
api/urls.py
from django.conf.urls import url
from api import views
urlpatterns=[
url(r'^cars/$',views.CarAPIView.as_view()),
url(r'^cars/(?P\d+)$' ,views.CarAPIView.as_view()),
]
api/views.py
from django.shortcuts import render
from rest_framework import status
from rest_framework.views import APIView
from . import models
from . import serializers
from rest_framework.response import Response
from rest_framework import status
class CarAPIView(APIView):
def get(self,request,*args,**kwargs):
pk=kwargs.get("pk")
if pk:
try:
obj=models.Car.objects.get(pk=pk)
serializer=serializers.CarModelSerializer(obj)
return Response(
data={
"status":0,
"msg":"ok",
"results":serializer.data
},status=status.HTTP_200_OK
)
except:
return Response(
data={
"status": 0,
"msg": "ok",
}, status=status.HTTP_400_BAD_REQUEST
)
else:
queryset=models.Car.objects.all()
serializer = serializers.CarModelSerializer(queryset,many=True)
return Response(
data={
"status": 0,
"msg": "ok",
"results": serializer.data
}, status=status.HTTP_200_OK
)
def post(self,request,*args,**kwargs):
request_data=request.data
serializer=serializers.CarModelSerializer(data=request_data)
if serializer.is_valid():
obj=serializer.save()
return Response(
data={
"status": 0,
"msg": "ok",
"results": serializer.data
}, status=status.HTTP_200_OK
)
else:
return Response(
data={
"status": 0,
"msg": serializer.errors,
}, status=status.HTTP_400_BAD_REQUEST
)
drf_20201005/settings.py
from pathlib import Path
import os
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = 'opqi(od%4rg4_)ty(@#9956yteg9itnq$_7+30-13m+x=u@df@'
DEBUG = True
ALLOWED_HOSTS = []
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'api.apps.ApiConfig',
"rest_framework",
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'drf_project20201005.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'drf_project20201005.wsgi.application'
import pymysql
pymysql.install_as_MySQLdb()
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME':'drf_project20201005',
"USER":"root",
"PASSWORD":"root",
}
}
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
STATIC_URL = '/static/'
MEDIA_ROOT=os.path.join(BASE_DIR,"media")
MEDIA_URL="/media/"
BASE_URL="http://127.0.0.1:8000"
drf_20201005/urls.py
from django.contrib import admin
from django.urls import path,include
from django.views.static import serve
from . import settings
urlpatterns = [
path('admin/', admin.site.urls),
path('api/',include('api.urls')),
path('media/' ,serve,{"document_root":settings.MEDIA_ROOT})
]
是不是xcolor那个选项的默认是read_only啊,也许是的,那就不可以改了呀,要修改的话怎么办呢?奇怪?!
的确是的,这个反射的get_color_display是read_only,
要设置之前的那个color是write_only,是可以的,这样就可以显示这个get_color_display的显示了,
icon ok的,没有显示的可以的,问题是我想改这个呢?是用brand接收写write_only吗?试一下
这边也试了,是一个url的链接,所以是有需要进一步了解的,这个问题记录下来!前台往后台发送图片的要求,理解发送数据包的原理