DRF:ModelSerializer+APIView+GenericAPIView+Mixin+Mixin2.0+最终合并版

#views.py

from django.shortcuts import render
from .serializers import SuperviseSerializers
from rest_framework.views import APIView
from .models import Supervise
from rest_framework.response import Response
from rest_framework import status
# Create your views here.

class SuperviseView(APIView):
    """督查视图"""
    def get(self,request):
        supervises = Supervise.objects.all()
        ser = SuperviseSerializers(instance=supervises,many=True)
        return Response(ser.data,status=status.HTTP_200_OK)
    
    def post(self,request):
        supervises = SuperviseSerializers(data=request.data)
        try:
            supervises.is_valid(raise_exception=True)
            supervises.save()
            return Response(supervises.data,status=status.HTTP_201_CREATED)
        except Exception as e:
            return Response(supervises.errors)

class SuperviseDetailView(APIView):
    def get(self,request,id):
        supervises = Supervise.objects.get(id=id)
        ser = SuperviseSerializers(instance=supervises)
        return Response(ser.data,status=status.HTTP_200_OK)
    def put(self,request,id):
        supervise = Supervise.objects.get(id=id)
        ser = SuperviseSerializers(instance=supervise,data=request.data)
        try:
            ser.is_valid(raise_exception=True)
            ser.save()
            return Response(ser.data,status=status.HTTP_200_OK)
        except Exception as e:
            return Response(ser.errors)
    def delete(self,request,id):
        supervise = Supervise.objects.get(id=id).delete()
        return Response(supervise,status=status.HTTP_200_OK)
# GenericAPIView版本
# views.py

from rest_framework.generics import GenericAPIView

class SuperviseView(GenericAPIView):
    queryset = Supervise.objects
    serializer_class = SuperviseSerializers

    def get(self,request):
        serializer = self.get_serializer(instance=self.get_queryset(),many=True)
        return Response(serializer.data)
    
    def post(self,request):
        serializer = self.get_serializer(data = request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response(serializer.errors)
    
class SuperviseDetailView(GenericAPIView):
    queryset = Supervise.objects
    serializer_class = SuperviseSerializers
    
    def get(self,request,pk):
        serializer = self.get_serializer(instance=self.get_object())
        return Response(serializer.data)
    
    def put(self,request,pk):
        serializer = self.get_serializer(instance=self.get_object(),data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response(serializer.errors)
        
    def delete(self,request,pk):
        self.get_object().delete()
        return Response()

# Mixin版本
# views.py
from rest_framework.mixins import CreateModelMixin,ListModelMixin,RetrieveModelMixin,DestroyModelMixin,UpdateModelMixin

class SuperviseView(GenericAPIView,ListModelMixin,CreateModelMixin):
    queryset = Supervise.objects
    serializer_class = SuperviseSerializers

    def get(self,request):
        return self.list(request)

    def post(self,request):
        return self.create(request)
    
class SuperviseDetailView(GenericAPIView,RetrieveModelMixin,DestroyModelMixin,UpdateModelMixin):
    queryset = Supervise.objects
    serializer_class = SuperviseSerializers

    def get(self,request,pk):
        return self.retrieve(request)
    
    def put(self,request,pk):
        return self.update(request)
    
    def delete(self,request,pk):
        return self.destroy(request)
from rest_framework.generics import ListCreateAPIView,RetrieveUpdateDestroyAPIView

class SuperviseView(ListCreateAPIView):
    queryset = Supervise.objects
    serializer_class = SuperviseSerializers

class SuperviseDetailView(RetrieveUpdateDestroyAPIView):
    queryset = Supervise.objects
    serializer_class = SuperviseSerializers
# views.py
# 最终版

from rest_framework.viewsets import ModelViewSet

class SuperviseView(ModelViewSet):
    queryset = Supervise.objects
    serializer_class = SuperviseSerializers


##########################################################
# urls.py
from rest_framework.routers import DefaultRouter

urlpatterns = [
]

router = DefaultRouter()
router.register('super',views.SuperviseView)

urlpatterns += router.urls

Python编程:从入门到实践(第2版)

第2章 变量和简单数据类型

变量的命名和使用
  • 变量名只能包含字母、数字和下划线。变量名能以字母或下划线打头,但不能以数字打头。
  • 变量名不能包含空格
  • 不要将Python关键字和函数名用作变量名
  • 变量名应既简短又具有描述性
    • name比n好
    • name_length比length_of_persons_name好
  • 慎用小写字母i和大写字母O,因为它们可能被人错看成数字1和0
  • 变量是可以赋给值的标签,也可以说变量指向特定的值
字符串

字符串就是一系列字符。在Python中,用引号括起的都是字符串,其中的引号可以是单引号,也可以是双引号

Docker技术入门与实战

Docker三大核心概念

  • 镜像Image
  • 容器Container
  • 仓库Repository
Docker镜像

类似于虚拟机镜像,一个只读的模板

例如,一个镜像可以包含一个基本的操作系统环境,里面仅安装了Apache应用程序

镜像是创建Docker容器的基础

Docker容器

容器是从镜像创建的应用运行实例。

它可以启动、开始、停止、删除,而这些容器都是彼此相互隔离、互不可见的。

可以把容器看作一个简易版的Linux系统环境(包括root用户权限、进程空间、用户空间和网络空间等)以及运行在其中的应用程序打包而成的盒子

镜像自身是只读的。容器从镜像启动的时候,会在镜像的最上层创建一个可写层

Docker仓库

Docker仓库类似于代码仓库,是Docker集中存放镜像文件的场所

仓库注册服务器是存放仓库的地方,其上往往存放着多个仓库

Docker仓库可分为公开仓库Public和私有仓库Private两种形式

Django项目部署实战

部署Django项目,着实让人头大,虽然一个月之前耗时2天,终于部署成功一个项目,但到今天,部署项目时,按照记录的部署笔记,却仍然失败多次。抱着永不气馁的决心,从零开始部署,耗时1天,终于部署成功,虽然脑袋已经昏沉,却仍打开笔记,决定详细记录整个部署过程,以免遗忘。

软件

  • django 3.1.1
  • uWSGI 2.0.19.1
  • nginx
  • 腾讯云ubuntu S18
  • python3.6

步骤

  • 服务器搭建django虚拟环境
python -m venv myenv        # myenv虚拟环境名称
source myenv/bin/activate   # 进入虚拟环境
pip install django
django-admin startproject blog  # blog 项目
django-admin startapp article   # 新建文章app
settings、createsuperuser       # 设置、创建管理员这些内容省略

  • uWSGI
# 在虚拟环境下安装uWSGI
pip install uWSGI
  • uWSGI测试
# 在django项目跟目录下,新建一个test.py测试文件
# test.py
def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return [b"Hello World"]
  • 运行uWSGI
uwsgi --http :8000 --wsgi-file test.py # 这步是为了验证uwsgi能否运行

# 在浏览器输入ip:8000 ,如果返回一个hello world,说明验证成功
# 如果验证不成功,那就找出问题,否则后面部署就无从谈起
  • 测试django项目
python manage.py runserver 0.0.0.0:8000 

  • django+uwsgi
# 当上面两项运行测试成功后,开始两者结合

uwsgi --http :8000 --module blog.wsgi   
# module 项目wsgi :加载指定的wsgi模块
# 这里是blog项目,在项目根目录运行
# 如果浏览器能够访问服务器,意味着uwsgi能够为虚拟环境中的django提供服务
# 以上操作,都是在虚拟环境中操作
# uwsgi 需要安装两次,一次是上面的虚拟环境中安装,第二次是全局安装,会在后续步骤中说明
  • 安装nginx
sudo apt install nginx # 全局安装,退出虚拟环境

# nginx 命令
sudo /etc/init.d/nginx start # 启动
                       restart # 重启
# 有多种命令 方式,这只是其中一种

# 安装完毕启动后,打开浏览器,默认80端口,可以看到welcome to nginx!
# 说明安装成功,如果80端口被其他服务占用,那就为nginx提供另一个端口

  • 配置nginx
# uwsgi_params 文件,其实etc/nginx 里面附带有这个文件
# 但为了能够确保部署成功,还是复制一份保存到项目根目录吧
# 之前部署成功的项目并没有保存在项目根目录
# include    /etc/nginx/uwsgi_params;
# 如果复制到项目根目录,记得修改路径

# 在项目根目录,新建config 目录
# 新建 blog_nginx.conf 文件
upstream django {
    # server unix:///path/to/your/mysite/mysite.sock; # for a file socket
    server 127.0.0.1:8001; # for a web port socket (we'll use this first)
}

# configuration of the server
server {
    # the port your site will be served on
    listen      8000;
    # the domain name it will serve for
    server_name .example.com; # substitute your machine's IP address or FQDN
    charset     utf-8;

    # max upload size
    client_max_body_size 75M;   # adjust to taste

    # Django media
    location /media  {
        alias /path/to/your/mysite/media;  # your Django project's media files - amend as required
    }

    location /static {
        alias /path/to/your/mysite/static; # your Django project's static files - amend as required
    }

    # Finally, send all non-media requests to the Django server.
    location / {
        uwsgi_pass  django;
        include     /path/to/your/mysite/uwsgi_params; # the uwsgi_params file you installed
    }
}

# 将文件链接到 /etc/nginx/sites-enabled  ,这里链接必须sudo ,全部绝对路径

sudo ln -s ~/path/to/your/mysite/mysite_nginx.conf /etc/nginx/sites-enabled/
  • django部署静态文件
# 在settings.py中添加
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
# 运行
python manage.py collectstatic

  • nginx基本测试
# 重启nginx
sudo /etc/init.d/nginx restart

# 重启后,可能会遇到一些问题,通过 提示输入xxx status nginx.xxx 命令,可以查看错误内容

# 可以添加一个media.png图片到 项目/media目录中,通过网址
# http://xxxx/media/media.png 访问,成功说明nginx 提供了正确的文件服务
uwsgi --socket :8001 --wsgi-file test.py
# nginx 端口与uwsgi通信
# socket:8001 是uwsgi协议,端口为8001,而对外使用8000端口访问


  • 使用unix socket ,而不是端口
# TCP端口socket简单,但开销大
# 使用unix socket 会比端口开销更少

# 编辑 blog_nginx.conf
server unix:///path/to/your/mysite/mysite.sock; # for a file socket
# server 127.0.0.1:8001; # for a web port socket (we'll use this first)

# 重启nginx ,再次运行uwsgi
uwsgi --socket blog.sock --wsgi-file test.py

# 如果错误,可以查看nginx错误日志 /var/log/nginx/error.log
connect() to unix:///path/to/your/mysite/mysite.sock failed (13: Permission
denied)
# 这样的提示 是权限问题

uwsgi --socket blog.sock --wsgi-file test.py --chmod-socket=664 # (more sensible)
# 增加权限
  • nginx 权限
# 这里需要修改nginx 的权限,修改sudo vim /etc/nginx/nginx.conf 文件
user www-data; # 第一行原内容
user 目前用的用户; # 把第一行注释掉,新增这句

  • 使用uwsgi和nginx运行django
uwsgi --socket blog.sock --module blog.wsgi --chmod-socket=664
  • 配置uwsgi以.ini文件运行
# 在项目根目录config 里面新建blog_uwsgi.ini 文件
# 内容如下


[uwsgi]

# Django-related settings
# the base directory (full path)
chdir           = /path/to/your/project
# Django's wsgi file
module          = project.wsgi
# the virtualenv (full path)
home            = /path/to/virtualenv

# process-related settings
# master
master          = true
# maximum number of worker processes
processes       = 10
# the socket (use the full path to be safe
socket          = /path/to/your/project/mysite.sock
# ... with appropriate permissions - may be needed
# chmod-socket    = 664
# clear environment on exit
vacuum          = true

  • 运行uswgi uswgi --ini blog_uwsgi.ini
  • 全局安装uWSGI
deactivate # 退出虚拟环境

sudo pip install uwsgi # 全局安装

  • 再次检查运行uwsgi,不是在虚拟环境 uwsgi --ini blog_uwsgi.ini
  • emperor 模式
# 每当修改配置文件,emperor将会自动重启vassal

sudo mkdir /etc/uwsgi
sudo mkdir /etc/uwsgi/vassals

# 这里链接仍然要绝对路径
sudo ln -s /path/to/your/mysite/mysite_uwsgi.ini /etc/uwsgi/vassals/

运行emperor 
uwsgi --emperor /etc/uwsgi/vassals --uid xxx --gid xxx
# uid和gid后面的xxx 是指目前自己运行的用户,同nginx用户一样
  • 最后一步 系统启动时运行uwsgi
# 系统启动时自动运行
# 编辑/etc/rc.local ,在exit 0 行前添加
/usr/local/bin/uwsgi --emperor /etc/uwsgi/vassals --uid www-data --gid www-data --daemonize /var/log/uwsgi-emperor.log

# 当然,里面的uid和gid 用户记得修改

到此部署完成

参考文档: https://uwsgi-docs-zh.readthedocs.io/zh_CN/latest/tutorials/Django_and_nginx.html

Django RestFramework

域名

  • 应该尽量将API部署在专用域名之下 https://api.example.com
  • 如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下https://example.org/api

版本

  • 应该将API的版本号放入URLhttps://api.example.com/v1/
  • 另一种做法是,将版本号放在HTTP头信息中,但不方便和直观

HTTP动词

GETSELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
DELETEDELETE):从服务器删除资源。


HEAD:获取资源的元数据。
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

# 例子
GET /zoos:列出所有动物园
POST /zoos:新建一个动物园
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

过滤信息

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。

下面是一些常见的参数。

?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件

参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。

状态码

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

错误处理(Error handling)

如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

{
error: "Invalid API key"
}

返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范。

GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档

Hypermedia API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。   比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。

{"link": { 
  "rel":   "collection https://www.example.com/zoos",
  "href":  "https://api.example.com/zoos",
  "title": "List of zoos",
  "type":  "application/vnd.yourformat+json"}}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。   Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

{
  "current_user_url": "https://api.github.com/user",
  "authorizations_url": "https://api.github.com/authorizations",
  // ...}
  

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。

{ “message”: “Requires authentication”, “documentation_url”: “https://developer.github.com/v3“} 上面代码表示,服务器给出了提示信息,以及文档的网址。

其他

  • API的身份认证应该使用OAuth 2.0框架。
  • 服务器返回的数据格式,应该尽量使用JSON,避免使用XML。