美文网首页python自学Vue.jsdjango
带你进入异步Django+Vue的世界 - Didi打车实战(1

带你进入异步Django+Vue的世界 - Didi打车实战(1

作者: 非梦nj | 来源:发表于2019-05-05 21:40 被阅读29次

    Django是python里著名的三大Web框架之一(另二个是Flask和Tornado)。
    Django原生跟Flask一样,是同步的。
    当前对异步处理要求越来越多,Django Channels应运而生,用于在后台处理Http和Websockets请求。
    后期Channels计划合并到django发行版本中。

    关键字: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用户参考这里)
    image.png

    界面功能介绍:

    • 左侧为乘客界面
      • 提出行程需求
      • 查看订单的状态
      • 查看历史行程。
    • 右侧为司机界面
      • 查看所有当前可以接的单子
      • 可以接单
      • 更新订单状态

    一、Django搭建后台框架

    不要重复造轮子,这里有Best Practice的脚手架:django-vue-template

    1. 直接克隆到本地:
    $ mkdir didi-project
    $ cd didi-project/
    $ git clone https://github.com/kevinqqnj/django-vue-template.git . <=注意有个点
    $ ls
    LICENSE  Pipfile.lock  README.md  backend    package.json  src            yarn.lock
    Pipfile  Procfile      app.json   manage.py  public        vue.config.js
    
    1. 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
    
    1. 验证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,应该能看到登录页面:

    image.png
    1. build前端文件(可选)
      yarn install
      yarn build

    二、鉴权

    DRF提供了很多鉴权方式,由于后续要使用Websockets通讯,而Websockets默认是继承HTTP的Cookie(session)鉴权的,所以选择Session。

    1. 在设置里,使用自定义User模型,并指明Session鉴权
    # /backend/settings/dev.py 
    
    AUTH_USER_MODEL = 'api.User'
    
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
                'rest_framework.authentication.SessionAuthentication',
            )
    }
    
    1. 把模型登记到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):
        ...
    
    1. 数据库迁移
    (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

    1. 我们先来创建一个需要登录的视图: 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,需要登录的提示:

      image.png
    • 从admin页面登录,然后在同一浏览器中,再进入http://localhost:8000/api/log_out/。此时看到允许POST的提示。
      (GET方法在LogOutView里没定义,所以405不允许)

    image.png
    • 由此证明,Session鉴权已经配置成功

    三、用户注册、登录

    LogOutView,分别来创建SignUpViewLogInView

    • 注册使用CreateAPIView,需要使用序列化器。
      判断两次密码是否一致,然后调用create_user方法创建用户
    # /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"}
    

    参考:https://testdriven.io/courses/real-time-app-with-django-channels-and-angular/part-one-intro/

    带你进入异步Django+Vue的世界 - Didi打车实战(2)
    Channels + ASGI服务器
    带你进入异步Django+Vue的世界 - Didi打车实战(3)
    Websockets通讯 + 群发群收
    带你进入异步Django+Vue的世界 - Didi打车实战(4)
    Vue.js前端
    ...
    带你进入异步Django+Vue的世界 - Didi打车实战(X)
    部署到服务器

    相关文章

      网友评论

        本文标题:带你进入异步Django+Vue的世界 - Didi打车实战(1

        本文链接:https://www.haomeiwen.com/subject/vfuhoqtx.html