Django是python里著名的三大Web框架之一(另二个是Flask和Tornado)。
Django原生跟Flask一样,是同步的。
当前对异步处理要求越来越多,Django Channels应运而生,用于在后台处理Http和Websockets请求。
后期Channels计划合并到django发行版本中。
本文后端源码参考: https://testdriven.io/courses/real-time-app-with-django-channels-and-angular/part-one-intro/关键字:django Vue.js Channels Websockets
本系列通过一个实时打车(类似于Didi)的实战项目,来学习如何综合运用:
后端 Django (v2.2) Channels (v2.2) : 处理Websockets、异步请求、群发群收RESTful: DRF (v3.9.2) : HTTP, Sessioin鉴权 前端 Vue.js (v2.6) Vuex + Vue-router + axios UI: 同时支持桌面端和手机端 Vuetify (v1.5) Redis (v4.0) Postgresql (v10) Python (v3.7) 开发环境 Ubuntu 18.04 (Win10用户参考 这里 ) 部署到生产环境 Demo: https://didi-taxi.herokuapp.com/
Github : 需要源码的请留言界面功能介绍:
Django: http://www.liujiangblog.com/course/django/2 Django Channels: https://channels.readthedocs.io/en/latest/tutorial/index.html DRF (Django REST Framework): https://www.django-rest-framework.org/tutorial/quickstart/ Vue: https://www.jianshu.com/p/51c8b35e2c9f Vuex: https://vuex.vuejs.org/zh/ Vue-Router: https://router.vuejs.org/zh/ 一、Django搭建后台框架
不要重复造轮子,这里有Best Practice的脚手架: django-vue-template
直接克隆到本地: $ mkdir didi-project $ cd didi-project/ $ git clone https://github.com/kevinqqnj/django-vue-template.git . <=注意有个点 LICENSE Pipfile.lock README.md backend package.json src yarn.lock Pipfile Procfile app.json manage.py public vue.config.js
Python虚拟环境,使用pipenv
如果没安装,使用pip3 install pipenv
安装$ pipenv shell Creating a virtualenv for this project… Pipfile: /mnt/c/Users/git/didi-project/Pipfile Using /usr/bin/python3 (3.7.3) to create virtualenv… ⠦ Creating virtual environment...Using base prefix '/usr' Running virtualenv with interpreter /usr/bin/python3 ✔ Successfully created virtual environment! # 安装python依赖,以及Pillow (didi-project) /mnt/c/Users/git/didi-project$ pipenv install (didi-project) /mnt/c/Users/git/didi-project$ pipenv install pillow
验证django后台已经正常启动 (didi-project) $ python manage.py runserver Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). May 05, 2019 - 10:20:42 Django version 2.2, using settings 'backend.settings.dev' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
此时,打开浏览器,输入
http://localhost:8000/admin
,应该能看到登录页面:
DRF
提供了很多鉴权方式,由于后续要使用Websockets
通讯,而Websockets默认是继承HTTP的Cookie(session)鉴权的,所以选择Session。在设置里,使用自定义User模型,并指明Session鉴权 # /backend/settings/dev.py AUTH_USER_MODEL = 'api.User' REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication',
把模型登记到django admin里: # /backend/api/admin.py from django.contrib import admin from django.contrib.auth.admin import UserAdmin as DefaultUserAdmin from .models import User @admin.register(User) class UserAdmin(DefaultUserAdmin):
数据库迁移 (didi-project) $ python manage.py makemigrations api Migrations for 'api': backend/api/migrations/0001_initial.py - Create model User (didi-project) $ python manage.py migrate Operations to perform: Apply all migrations: admin, api, auth, contenttypes, sessions Running migrations: Applying sessions.0001_initial... OK
创建超级(admin)用户
(didi-project) $ python manage.py createsuperuser Username: admin Email address: aaa@bbb.com Password: Password (again): This password is entirely numeric. Bypass password validation and create user anyway? [y/N]: y Superuser created successfully.
此时,可以用这个账号登录
http://localhost:8000/admin
了我们先来创建一个需要登录的视图: LogOutView
# /backend/api/views.py from rest_framework import generics, permissions, status, views, viewsets from rest_framework.response import Response class LogOutView(views.APIView): permission_classes = (permissions.IsAuthenticated,) def post(self, *args, **kwargs): logout(self.request) return Response(status=status.HTTP_204_NO_CONTENT) !!删除:class MessageViewSet(viewsets.ModelViewSet)
添加
/api/log_out/
路由# /backend/urls.py from .api.views import index_view, serve_worker_view, LogOutView path('api/log_out/', LogOutView.as_view(), name='log_out'), !!删除: router = routers.DefaultRouter() router.register('messages', MessageViewSet) path('api/', include(router.urls)),
测试 - Session保护的路由:
确保admin页面已经退出登录,然后输入
http://localhost:8000/api/log_out/
。应该看到403,需要登录的提示:
# /backend/api/serializers.py from django.contrib.auth import get_user_model from django.contrib.auth.models import Group from django.conf import settings from rest_framework import serializers from urllib.parse import urljoin class UserSerializer(serializers.ModelSerializer): password1 = serializers.CharField(write_only=True) password2 = serializers.CharField(write_only=True) def validate(self, data): if data['password1'] != data['password2']: raise serializers.ValidationError('两次密码不一致') return data def create(self, validated_data): data = { key: value for key, value in validated_data.items() if key not in ('password1', 'password2') data['password'] = validated_data['password1'] user = self.Meta.model.objects.create_user(**data) return user class Meta: model = get_user_model() fields = ( 'id', 'username', 'password1', 'password2', 'first_name', 'last_name', read_only_fields = ('id',)
DRF视图里引用这个序列化器 # /backend/api/views.py class SignUpView(generics.CreateAPIView): queryset = get_user_model().objects.all() serializer_class = UserSerializer
# /backend/urls.py from .api.views import index_view, serve_worker_view, SignUpView, LogOutView path('api/sign_up/', SignUpView.as_view(), name='sign_up'),
测试 - 创建用户:
使用
curl
或浏览器http://localhost:8000/api/sign_up/
均可。(didi-project) $ curl -i -d '{"username":"user1", "password1":"aaa", "password2":"aaa"}' -H 'Content-Type: application/json' localhost:8000/api/sign_up/ HTTP/1.1 201 Created Date: Sun, 05 May 2019 13:11:25 GMT Server: WSGIServer/0.2 CPython/3.7.3 Content-Type: application/json Vary: Accept, Cookie Allow: POST, OPTIONS X-Frame-Options: SAMEORIGIN Content-Length: 58 {"id":2,"username":"user1","first_name":"","last_name":""}
登录视图:
使用AuthenticationForm
表单,来验证登录的数据。并且在返回的Headers
里写入sessioinid
,以便后续的Websockets
鉴权使用# /backend/api/views.py class LogInView(views.APIView): @staticmethod def post(request): form = AuthenticationForm(data=request.data) if form.is_valid(): user = form.get_user() login(request, user=user) rsp = UserSerializer(user).data rsp['sessionid'] = request.session.session_key return Response(rsp) else: return Response(form.errors, status=status.HTTP_400_BAD_REQUEST)
# /backend/urls.py from .api.views import index_view, serve_worker_view, SignUpView, LogInView, LogOutView path('api/log_in/', LogInView.as_view(), name='log_in'),
测试 - 用户登录:
使用
curl
或浏览器http://localhost:8000/api/log_in/
均可。(didi-project) $ curl -i -d '{"username":"user1", "password":"aaa"}' -H 'Content-Type: application/json' localhost:8000/api/log_in/ HTTP/1.1 200 OK Date: Sun, 05 May 2019 13:19:30 GMT Server: WSGIServer/0.2 CPython/3.7.3 Content-Type: application/json Vary: Accept, Cookie Allow: POST, OPTIONS X-Frame-Options: SAMEORIGIN Content-Length: 105 Set-Cookie: csrftoken=baEYChsNnKet2RkapIzWsxxxxxxz9xrJUf94Z23ZXoUauxkjq6iEC7Pr2F2; expires=Sun, 03 May 2020 13:19:30 GMT; Max-Age=31449600; Path=/; SameSite=Lax Set-Cookie: sessionid=rue1qryxj84z77d0azeyo6l61i230u4z; expires=Sun, 19 May 2019 13:19:30 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax {"id":2,"username":"user1","first_name":"","last_name":"","sessionid":"rue1qryxj8xxx7d0azeyoxxxxx30u4z"}
搭建项目框架,实现后台鉴权系统。
带你进入异步Django+Vue的世界 - Didi打车实战(2) https://www.jianshu.com/p/f6a83315e055
Vue + Vuetify 前端鉴权实现
带你进入异步Django+Vue的世界 - Didi打车实战(3)
Vue websockets实现
带你进入异步Django+Vue的世界 - Didi打车实战(4)
Channels + ASGI服务器
带你进入异步Django+Vue的世界 - Didi打车实战(5)
Websockets通讯 + 群发群收
带你进入异步Django+Vue的世界 - Didi打车实战(6)
Vue群发群收
带你进入异步Django+Vue的世界 - Didi打车实战(X)