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

带你进入异步Django+Vue的世界 - Didi打车实战(2) https://www.jianshu.com/p/f6a83315e055
Vue + Vuetify 前端鉴权实现
Demo: https://didi-taxi.herokuapp.com/

后台数据模型设计

数据模型是后台的灵魂,需要考虑周全。
数据模型的更新,使用python manage.py makemigrations可以很方便地迁移

  1. User,继承AbstractUser
    group指明用户是乘客还是司机
    photo用来上存储用户的头像
# /backend/api/models.py
from django.db import models
from django.conf import settings
from django.shortcuts import reverse
from django.contrib.auth.models import AbstractUser

import uuid


class User(AbstractUser):
    photo = models.ImageField(upload_to='photos', null=True, blank=True)

    @property
    def group(self):
        groups = self.groups.all()
        return groups[0].name if groups else None
  1. Trip,继承通用模型Model
    iduuid4来指明一下唯一的订单编号
    pick_up_address/drop_off_address指明上车地点和目的地
    status用来存储订单的状态:
  • 下单REQUESTED
  • 已接单STARTED
  • 行程中IN_PROGRESS
  • 行程结束COMPLETED
    driver/rider是外键,关联User模型
# /backend/api/models.py
class Trip(models.Model):
    REQUESTED = 'REQUESTED'
    STARTED = 'STARTED'
    IN_PROGRESS = 'IN_PROGRESS'
    COMPLETED = 'COMPLETED'
    STATUSES = (
        (REQUESTED, REQUESTED),
        (STARTED, STARTED),
        (IN_PROGRESS, IN_PROGRESS),
        (COMPLETED, COMPLETED),
    )

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    pick_up_address = models.CharField(max_length=255)
    drop_off_address = models.CharField(max_length=255)
    status = models.CharField(max_length=20, choices=STATUSES, default=REQUESTED)
    driver = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.DO_NOTHING,
        related_name='trip_as_driver'
    )
    rider = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.DO_NOTHING,
        related_name='trip_as_rider'
    )

    def __str__(self):
        return f'{self.id}'

    def get_absolute_url(self):
        return reverse('trip:trip_detail', kwargs={'trip_id': self.id})
  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, Trip


@admin.register(User)
class UserAdmin(DefaultUserAdmin):
    ...


@admin.register(Trip)
class TripAdmin(admin.ModelAdmin):
    fields = (
        'id', 'pick_up_address', 'drop_off_address', 'status',
        'driver', 'rider', 'created', 'updated',
    )
    list_display = (
        'id', 'pick_up_address', 'drop_off_address', 'status',
        'driver', 'rider', 'created', 'updated',
    )
    list_filter = ('status',)
    readonly_fields = (
        'id', 'created', 'updated',
    ) 
  1. DRF只处理鉴权和trips view,所以先删除不需要的URL:
# /backend/urls.py 删除后如下所示:
from django.contrib import admin
from django.urls import path, re_path, include

from .api.views import index_view, serve_worker_view


urlpatterns = [
    # http://localhost:8000/
    path('', index_view, name='index'),

    # serve static files for PWA
    path('index.html', index_view, name='index'),
    re_path(r'^(?Pmanifest).json$', serve_worker_view, name='manifest'),
    re_path(r'^(?P[-\w\d.]+).js$', serve_worker_view, name='serve_worker'),
    re_path(r'^(?Probots).txt$', serve_worker_view, name='robots'),

    # http://localhost:8000/admin/
    path('admin/', admin.site.urls),

    # support vue-router history mode
    re_path(r'^\S+$', index_view, name='SPA_reload'),
]

删除不需要的view:

# /backend/api/views.py
删除 from .models import Message, MessageSerializer
删除 class MessageViewSet

模型更新:

(didi-project) git/didi-project$ python manage.py makemigrations
Migrations for 'api':
  backend/api/migrations/0002_auto_20190518_0708.py
    - Create model Trip
    - Delete model Message
    - Add field photo to user
    - Add field driver to trip
    - Add field rider to trip
(didi-project) git/didi-project$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, api, auth, contenttypes, sessions
Running migrations:
  Applying api.0002_auto_20190518_0708... OK

在Admin里测试下Trip

创建几个测试用户,然后创建Trip订单:


带你进入异步Django+Vue的世界 - Didi打车实战(3)_第1张图片
image.png
带你进入异步Django+Vue的世界 - Didi打车实战(3)_第2张图片
image.png

用户查看Trip功能

  1. 后端需要提供Serializer、View、Url

Serializer

# trips/serializers.py
from .models import Trip

class TripSerializer(serializers.ModelSerializer):
    class Meta:
        model = Trip
        fields = '__all__'
        read_only_fields = ('id', 'created', 'updated',)

其中三个字段,是只读的,不需要Serializer创建: id, created, updated .

View

Add the TripView to api/views.py:

# trips/views.py
from django.contrib.auth import get_user_model, login, logout
from django.contrib.auth.forms import AuthenticationForm
from rest_framework import generics, permissions, status, views, viewsets # new
from rest_framework.response import Response

from .models import Trip # new
from .serializers import TripSerializer, UserSerializer # new

class TripView(viewsets.ReadOnlyModelViewSet):
    permission_classes = (permissions.IsAuthenticated,)
    queryset = Trip.objects.all()
    serializer_class = TripSerializer

TripView非常基本,使用DRF ReadOnlyModelViewSet:返回trip列表和 trip详情 views.
这个路由是需要鉴权的。

URLs

在总路由里,添加trips.urls子路由:

# taxi/urls.py

from django.contrib import admin
from django.urls import include, path # new

from .api.views import index_view, serve_worker_view, SignUpView, LogInView, LogOutView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/sign_up/', SignUpView.as_view(), name='sign_up'),
    path('api/log_in/', LogInView.as_view(), name='log_in'),
    path('api/log_out/', LogOutView.as_view(), name='log_out'),
    path('api/trip/', include('api.urls', 'trip',)), # new
]

创建子路由文件:

# trips/urls.py

from django.urls import path

from .views import TripView

app_name = 'api'

urlpatterns = [
    path('', TripView.as_view({'get': 'list'}), name='trip_list'),
]

更新前端,显示Trips

Home.vue里,显示所有的订单信息


带你进入异步Django+Vue的世界 - Didi打车实战(3)_第3张图片
image.png
# /src/views/Home.vue



分成已完成订单和正在进行中的订单,用trip的status区别:

  computed: {
    ...mapState(['alert', 'user']),
    ...mapState('messages', ['trips']),
    userIsAuthenticated () {
      return this.user !== null && this.$store.getters.user !== undefined
    },
    trips_ongoing () {
      return this.trips.filter(obj => obj.status !== 'COMPLETED')
    },
    trips_done () {
      return this.trips.filter(obj => obj.status === 'COMPLETED')
    }
  },

装载此页面时,读取后台的trip信息:

  mounted () {
    if (this.userIsAuthenticated) {
      this.$store.dispatch('messages/getTrips')
    }
  },

Vuex store里,添加trips的操作:

# /src/store/modules/message.js
const state = {
  messages: [],
  trips: []
}

const mutations = {
  setTrips (state, messages) {
    state.trips = messages
  },

const actions = {
  getTrips ({ commit }) {
    messageService.fetchTrips()
      .then(messages => {
        commit('setTrips', messages)
      })
  },

ajax服务:

# /src/services/messageService.js
  fetchTrips () {
    return api.get(`trip/`)
      .then(response => response.data)
  },

以上是读取所有Trips的列表,对于单条trip记录的读取,需要后台添加view:

更新views.py

  • lookup_field 告诉后台通过id来查找trip记录
  • lookup_url_kwarg 是url的是kwarg名字
# api/views.py

class TripView(viewsets.ReadOnlyModelViewSet):
    lookup_field = 'id' # new
    lookup_url_kwarg = 'trip_id' # new
    permission_classes = (permissions.IsAuthenticated,)
    queryset = Trip.objects.all()
    serializer_class = TripSerializer

更新URL记录:

# api/urls.py

from django.urls import path, re_path  # changed

from .views import TripView


app_name = 'api'

urlpatterns = [
    path('', TripView.as_view({'get': 'list'}), name='trip_list'),
    path('/', TripView.as_view({'get': 'retrieve'}), name='trip_detail'),  # new
]

测试一下:

浏览器输入:http://localhost:8080/api/trip/6e446f7f-606d-488c-9274-f786b9f06800/,应该就可以查到详情了。

用户退出时,清除Trip记录

# /src/store/modules/messages.js
  signUserOut ({ commit }) {
    commit('setLoading', true, { root: true })
    messageService.signUserOut()
      .then(messages => {
        commit('setAlert', { type: 'info', msg: 'Log-out success!' }, { root: true })
        commit('setUser', null, { root: true })
        commit('setTrips', [])
        localStorage.removeItem('user')
        commit('setLoading', false, { root: true })
      })
  },

总结

这篇主要是数据库设计和前、后台的综合运用,加深印象。
下一篇,会进入到Django Channels + Websockets的使用。

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

你可能感兴趣的:(带你进入异步Django+Vue的世界 - Didi打车实战(3))