Vue全栈开发旅游网项目(5)-景点详情模块API接口设计
1.前言
通过提供 API,开发者可以重用现有的代码和功能,而不需要从头开始编写。
极大提高了开发效率并减少了重复工作。
同时也使得单独修改或替换系统的某个部分变得更加容易,而不会影响到其他部分。
2.创建公共模型
编写公共模型:
from django.db import modelsclass CommonModel(models.Model):#数据模型公共类is_valid = models.BooleanField('是否有效',default=True)created_at = models.DateTimeField('创建时间',auto_now_add=True)updated_at = models.DateTimeField('修改时间',auto_now=True)class Meta:abstract = True #此步必不可少 否则会创建表
2.1 删去重复项
至此,sight/models.py中的
与模型公共类相同的部分代码已再无他用,除之。
2.2 继承:
from utils.models import CommonModel # 继承
2.3 类的变化:
class Sight(models.Model) ---> class Sight(CommonModel)
3.创建表结构
3.1 生成单独文件夹用于储存表结构信息
python manage.py startapp accounts
3.2 创建表结构
文件地址:accounts/models.py
from django.db import models
from utils.models import CommonModelclass User(CommonModel):# 用户数据模型username = models.CharField("用户名", max_length=32, unique=True)password = models.CharField("密码", max_length=256)nickname = models.CharField("昵称", max_length=32, unique=True)avatar = models.ImageField("头像", null=True, upload_to="avatar/%Y%m")class Meta:db_table = "account_user"
3.2 激活该文件
3.2.1 定义
文件地址:accounts/apps.py
from django.apps import AppConfigclass AccountsConfig(AppConfig):default_auto_field = 'django.db.models.BigAutoField'name = 'accounts'
3.2.2 配置
在 Django 项目的 settings.py
文件中,
通过在 INSTALLED_APPS
中包含这个配置类,你可以确保 Django 在启动时加载这个应用。
INSTALLED_APPS = ['accounts.apps.AccountsConfig'#用户管理
]
通过以上两项操作,Django 会使用 AccountsConfig
类来配置 accounts
应用,
而不是使用默认的配置。
4. 测试
由于更改了sight中的部分信息用于创建公共模型
所以可测试sight/list界面代码 观察是否可执行 并功能与之前的一致
http://localhost:8000/sight/sight/list/
5.system模块下新建相关数据模型
文件地址:system/models.py 原基础代码可看:2.新建system模块并建立模型
img = models.ImageField('图片',upload_to='%Y%m/file',max_length=256)
图片地址列用于记录图片所在位置
summary = models.CharField('图片说明',max_length=32,null=True, blank=True )
图片说明(描述)列用于记录图片简介、名称等信息
user=models.ForeignKey(User,on_delete=models.SET(None),related_name="upload_image",verbose_name='上传的用户',null=True,blank=True)
用户id(主键)列,参数1【User】:用户数据源可以将用户id传入到该属性中;
参数2【on_delete=models.SET(None)】:级联关系;主键删除后,外键值空
参数3【related_name="upload_image"】:关联名字;
参数4【verbose_name】:提供了外键字段的人类可读名称;
参数5【null/blank=True】:允许为空null
content_type = models.ForeignKey(ContentType,on_delete=models.CASCADE)
类型适配列,如两个表没有配置主外键关系,还需要实现主外键关联查询,
即利用传统的处理方式,构建一张字典表,将主外关系建立起来。
在Django中提供构建主外关系的策略,可使用Django_ContentType实现该字典表功能。
ContentType特征:
每构建一个新数据源,都会在该表中进行注册,用于之后的主外关系,用于Django框架维护。
content_object = GenericForeignKey('content_type','object_id')
配置主外关联关系,如:向system_image_related表中添加数据时,
需要明确content_id对应Django_content_type表中哪一个id值。
class ImageRelated(CommonModel):#图片资源img = models.ImageField('图片',upload_to='%Y%m/file',max_length=256)summary = models.CharField('图片说明',max_length=32,null=True, blank=True )user = models.ForeignKey(User,on_delete=models.SET(None),related_name="upload_image",verbose_name='上传的用户',null=True,blank=True)content_type = models.ForeignKey(ContentType,on_delete=models.CASCADE)#关联名称标识object_id = models.IntegerField('关联的模型')content_object = GenericForeignKey('content_type','object_id')class Meta:db_table = 'system_image_related'
6.sight模块下新建相关数据模型
6.1 配置关于景点更多图片
GenericRelation用于非主外关系的一对多关联,与GenericForeignKey配合使用
参数1,关联目标,关联系统模块图片管理数据源;
参数2,标识名/关联对象名; 参数3:关联对象名(查询)
class Sight(CommonModel):""" 景点基础信息 """name = models.CharField('名称', max_length=64)desc = models.CharField('描述', max_length=256)#......images = GenericRelation(ImageRelated,verbose_name='关联的图片',related_query_name='rel_sight_images')class Meta:db_table ='sight'ordering = ['-updated_at']
6.2 配置景点详情数据模型
6.2.1 数据归类
新建文件sight/choices.py
from django.db import models
class TicketTpye(models.IntegerChoices):#门票类型ADULT = 11,'成人票'CHILD = 12,'儿童票'
class TicketStatus(models.IntegerChoices):#门票状态OPEN = 1,'开放购买'CLOSED = 0,'暂未开放'
class EntryWay(models.IntegerChoices):#入园方式BY_TICKET = 0,'短信换票入园'BY_CODE= 1,'凭验证码入园'
6.2.2 景点详情
class Info(models.Model):# 景点详情sight = models.OneToOneField(Sight,on_delete=models.CASCADE)entry_explain = models.CharField('参考说明',max_length=1024,null=True,blank=True)play_way = models.TextField('特色玩法',null=True,blank=True)tips = models.TextField('温馨提示',null=True,blank=True)traffic = models.TextField('交通到达',null=True,blank=True)class Meta:db_table = 'sight_info'
6.2.3 景点门票
class Ticket(CommonModel):#门票sight = models.ForeignKey(Sight, related_name='tickets', verbose_name='景点门票',on_delete=models.PROTECT)name = models.CharField('名称',max_length=128)desc = models.CharField('描述', max_length=64, null=True, blank=True)types = models.SmallIntegerField('类型',choices=TicketTpye.choices,default=TicketTpye.ADULT,help_text="默认为成人票")price = models.FloatField('价格(原价)')discount = models.FloatField('折扣',default=0)total_stock = models.PositiveIntegerField('总库存',default=0)remain_stock = models.PositiveIntegerField('剩余库存', default=0)expire_date = models.IntegerField('有效期', default=1)return_policy = models.CharField('退改政策', max_length=64, default='条件退')has_invoice = models.BooleanField('是否提供发票', default=True)entry_way = models.SmallIntegerField('入园方式',choices=EntryWay.choices,default=EntryWay.BY_TICKET)tips = models.TextField('预定须知', null=True, blank=True)remark = models.TextField('其他说明', null=True, blank=True)status = models.SmallIntegerField('状态',choices=TicketStatus.choices,default=TicketStatus.OPEN)class Meta:db_table = 'sight_ticket'
6.2.4 评论及回复
class Comment(CommonModel):# 评论及回复user = models.ForeignKey(User, verbose_name='评论人',related_name = 'comments',on_delete=models.CASCADE)sight = models.ForeignKey(Sight,verbose_name='景点',related_name = 'comments',on_delete = models.CASCADE)content = models.TextField('评论内容', blank=True, null=True)is_top = models.BooleanField('是否置顶',default = False)love_count = models.IntegerField('点赞次数',default=0)score = models.FloatField('评分',default=5)ip_address = models.CharField('IP地址', blank=True, null=True, max_length=64)is_public = models.SmallIntegerField('是否公开', default=1)reply = models.ForeignKey('self',blank=True,null=True,related_name = 'reply_comment',verbose_name = '回复',on_delete = models.CASCADE)images = GenericRelation(ImageRelated,verbose_name = '关联的图片',related_query_name = "rel_comment_images")class Meta:db_table = 'sight_comment'ordering = ['-love_count','-created_at']
7.设计响应单个对象的类
新建utils/serializers.py
7.1 序列化
class BaseSerializer(object):def __init__(self, obj):self.obj = objdef to_dict(self):return {}
7.2 对元数据进行序列化(分页)
class MetaSerializers(object):def __init__(self, page, page_count, total_count, **kwargs):""":param page: 当前页:param page_count:总页数:param total_count: 总记录数"""self.page = pageself.page_count = page_countself.total_count = total_countdef to_dict(self):return {"current_page":self.page,"page_count":self.page_count,"total_count":self.total_count}
7.3 数据列表页面类(封装)
class BaseListPageSerializer(object):#分页封装类#paginator页面信息def __init__(self, page_obj,paginator=None,object_list=[]):""":param page_obj: 当前页的对象:param paginator: 分页器对象:param object_list: 当前页的数据列表"""self.page_obj = page_objself.paginator = paginator if paginator else page_obj.paginatorself.object_list = object_list if object_list else page_obj.object_listdef get_object(self,obj):#转换成对象,子类重写return {}def to_dict(self):page = self.page_obj.numberpage_count = self.page_obj.paginator.num_pagestotal_count = self.page_obj.paginator.countmeta = MetaSerializers(page=page,page_count=page_count,total_count=total_count).to_dict()objects=[]for obj in self.object_list:objects.append(self.get_object(obj))return {'meta':meta,'objects':objects}
7.4 删除功能重复项
在sight/views.py的SightListView类中:
8.设计错误回应类
新建utils/response404.py
from django.http import JsonResponse
class NotFoundJsonResponse(JsonResponse):# 400 对应json的响应status_code = 400def __init__(self, *args, **kwargs):data = {"error_code":"404000","error_msg":"您访问的内容不存在或已删除"}super().__init__(data,*args, **kwargs)
9.景点列表接口开发
9.1 设计响应数据结构
新建sight/serializers.py
接受一个 Sight
模型的实例 obj
作为参数,并返回一个字典,该字典包含了要序列化的数据。
from utils.serializers import BaseListPageSerializerclass SightListSerializer(BaseListPageSerializer):def get_object(self, obj):return {'id': obj.id,'name': obj.name,'main_img': obj.main_img.url,'min_price': obj.min_price,'score': obj.score,'province': obj.province,'city': obj.city,'comment_count': 0}
9.2 设计视图函数
将刚刚7.4章节中,sight/views.py删除后的代码补全,并实现了对分页对象的序列化操作。
class SightListView(ListView):paginate_by = 5def get_queryset(self):query = Q(is_valid=True)is_hot = self.request.GET.get('is_hot',None)if is_hot:query = query & Q(is_hot=True)is_top = self.request.GET.get('is_top',None)if is_top:query = query & Q(is_top=True)queryset = Sight.objects.filter(query)return querysetdef render_to_response(self, context, **response_kwargs):page_obj = context['page_obj']👇if page_obj is not None:data = serializers.SightListSerializer(page_obj).to_dict()return http.JsonResponse(data)else:return NotFoundJsonResponse()👆
9.3 构建地址函数
文件地址:sight/urls.py
from django.urls import path
from sight import viewsurlpatterns = [path('sight/list/',views.SightListView.as_view(),name='sight_list')
]