Django-前后端分离开发以及定制API

1.什么是前后端分离开发:

就是前后端工程师约定好数据交互接口,并行的进行开发和测试,后端只提供数据,不负责将数据渲染到页面上,前端通过HTTP请求获取数据并负责将数据渲染到页面上,这个工作是交给浏览器中的JavaScript代码来完成。

2.前后端开发的好处:

  • 1.提升开发效率
  • 2.增强代码的可维护性。
    1. 支持多终端和服务化架构。

3.数据接口(FBV - 基于函数的视图):

FBV - 基于函数的视图
CBV - 基于类的视图
前后端分离的开发模式下,后端需要为前端提供数据接口,这些接口通常返回JSON格式的数据。在Django项目中,我们可以先将对象处理成字典,然后就可以利用Django封装的JsonResponse向浏览器返回JSON格式的数据,例如以下例子

def show_subjects(request):
    queryset = Subject.objects.all()   #获取所有学科对象
    subjects = []  #定义一个空字典
    for subject in queryset:    #遍历查询学科的到的对象,并把数据处理一个字典保存在subjects列表容器中
        subjects.append({
            'no': subject.no,
            'name': subject.name,
            'intro': subject.intro,
            'isHot': subject.is_hot
        })
    return JsonResponse(subjects, safe=False)  #利用JsonResponse完成对列表的序列化,返回json格式的数据。由于序列化的是一个列表而不是字典,所以需要指定safe参数的值为False 

值得注意的是,这样的处理虽然没问题,但是如果对象的属性很多时候,情况就比较糟糕了,为此我们可以使用一个比较小众的第三库bpmappers来简化将对象转成字典的操作

4.使用bpmappers

4.1 安装第三方库bpmappers

pip install bpmappers

4.2 创建一个mappers.py文件编写映射器,实现对象到字典的转换。

from bpmappers.djangomodel import ModelMapper

from poll2.models import Subject

class SubjectMapper(ModelMapper):

    class Meta:
        model = Subject#相关联的类
        fields = ('carno', 'owner')  #包含的字段 exclude()排除的字段


4.3 打开view.py修改视图函数

ef show_subjects(request):
    queryset = Subject.objects.all()
    subjects = []
    for subject in queryset:
        subjects.append(SubjectMapper(subject).as_dict()) #序列化成字典

  #生成式:subjects = [SubjectMapper(subject).as_dict()
                                for subject in queryset]
    return JsonResponse(subjects, safe=False)

4.4 配置URL映射,然后访问该接口,可以得到如下所示的JSON格式数据。
前端部分代码如下所示:


前端

5.网络API接口设计和配置

网络API - 通过HTTP请求一个URL获得(JSON)数据
4.5安装三方库djangorestframework

pip install djangorestframework 
pip install drf-extensions

4.6配置setting.py文件

INSTALLED_APPS = [
   
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',


]

#配置一个数据库
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'apidemo',
        'HOST': '47.107.87.249',
        'PORT': 3306,
        'USER': 'renwoxing',  #需要授权操作
        'PASSWORD': 'xxx',
        'CHARSET': 'utf8',
        'TIME_ZONE': 'Asia/Chongqing',

    }
}

#配置缓存
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': [
            'redis://47.107.87.249/0',
        ],
        'KEY_PREFIX': 'apidemo',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
            'CONNECTION_POOL_KWARGS': {
                'max_connections': 512,
            },
            'PASSWORD': '     ',
        }
    },

4.7用二维表生成模型
4.7.1创建应用,执行以下代码:

python manage.py startapp common

4.7.2打开应用目录下的model.py 在终端执行以下代码(前提是数据库已经配好且有操作权限):

python manage.py inspectdb > common/models.py

查看model.py可以看到已经迁移成功,接下来需要根据需要对生成的模型进行微调


迁移成功如图所示

5. REST架构

REST架构简单介绍
REpresentational State Transfer ---> 表述性状态转移
REST架构两大特点: 无状态和幂等性
HTTP协议请求行 GET / POST / DELETE / PUT / PATCH
新建 ---> POST-不需要幂等性
查看 ---> GET
更新 ---> PUT/PATCH
删除 ---> DELETE

5.1 使用drf给的装饰器修改视图函数,例如

from rest_framework.decorators import api_view
@api_view(('GET', ))  #限制视图函数支持哪些请求方法
def get_provinces(request):
    """获取省级行政区域"""

    queryset = District.objects\
#不能直接写pid==null 
        .filter(pid__isnull=True).only('distid', 'name')
#序列化对象,是一个集合需要写上many=True
    serializer = DistrictSimpleSerializer(queryset, many=True)
    return Response(serializer.data)#框架给的response

5.3 利用drf框架里的ModelSerializer自定义序列化器,新建一个serializer.py文件,编写如下代码

class DistrictSimpleSerializer(serializers.ModelSerializer):
    """行政区域简单序列化器"""

    class Meta:
        model = District
        fields = ('distid', 'name')

5.4 创建映射
5.4.1 在自己的应用底下建立一个urls.py,便于管理自己的各种接口

from common.models import District
urlpatterns = [
    path('districts/', get_provinces),

5.4.2 修改全局映射URL

urlpatterns = [
    path('', index),
#相当于前缀
    path('api/', include('common.urls')),
    path('admin/', admin.site.urls),
]

此时就可以访问自己创建的接口看到数据了。

6. CBV 基于类的视图定制接口

6.1在view.py文件中定义类

继承drf框架的类
class EstateView(CacheResponseMixin, ListCreateAPIView, RetrieveUpdateDestroyAPIView):
    """楼盘视图"""
#定义查询集
    queryset = Estate.objects.all().defer('agents','district')
#序列化查询集
    serializer_class = EstateSerializer
  

6.2自定义序列化器

class EstateSerializer(serializers.ModelSerializer):
    """楼盘序列化器(GET、DELETE请求)"""

    class Meta:
        model = Estate
        exclude = ('district', 'agents')

6.3映射urls

urlpatterns = [
   
    path('estates/', EstateView.as_view()),
  path('estates/<int:pk>/', EstateView.as_view()),可以拿单个楼盘
  
]

在浏览器中输入127.0.0.1:8000/api/estates/便可以看到数据。

7.全套接口视图集,两行代码写接口(不推荐使用)

📌打开 view.py 定义视图函数:

from rest_framework.viewsets import ModelViewSet
class HouseTypeViewSet(ModelViewSet):
    queryset = HouseType.objects.all()
    serializer_class = HouseTypeSerializer

📌打开 urls.py创建映射 :

urlpatterns = [
    ...
]
# 路由器
router = SimpleRouter()   # 简单路由
router.register('housetypes', HouseTypeViewSet)   # 注册
urlpatterns += router.urls   # 添加

补充1

若需要拿到单个数据则可以用重写get方法。

打开view.py文件,找到定义的类,然后修改为如下:

继承drf框架的类
class EstateView(CacheResponseMixin, ListCreateAPIView, RetrieveUpdateDestroyAPIView):
    """楼盘视图"""
#定义查询集
    queryset = Estate.objects.all().defer('agents','district')

  
#重写get方法
  def get_serializer_class(self):
        if self.request.method in ('POST', 'PUT', 'PATCH'):
            return EstatePostSerializer
        else:
            return EstateSerializer

    def get(self, request, *args, **kwargs):
        if 'pk' in kwargs:
            cls = RetrieveAPIView
        else:
            cls = ListAPIView
        return cls.get(self, request, *args, **kwargs)

补充2

利用DRF进行 分页

1. PageNumberPagination   --->   按页码分页
2. LimitoffsetPagination   --->   跳过N条,查第N+1条
3. CursorPagination           --->游标进行分页

  1. 自定义分页(商业项目中不推荐使用,可能会暴露服务器规模)
中间键后面添加以下代码(全部实现分页)

# DRF配置文件
REST_FRAMEWORK = {
    # 按页码分页
    'DEFAULT_PAGINATION_CLASS':'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 5,
}
# 若单个视图类不想分页可以加以下代码:
    pagination_class = None


2)游标分页(不能使用缓存,应无法准确获取上下文信息)

# 定义游标分页类
class EstatePagination(CursorPagination):
    page_size_query_param = 'size' # 自定义单页显示记录数
    max_page_size = 20   # 单页最多记录数
    ordering = 'estateid'   # 按楼盘id分页
# 在视图类里面调用
pagination_class = EstatePagination


补充3

利用DRF配置缓存

添加缓存也有两种方式(继承父类CacheResponseMixin,加类装饰器)
准备:

  • 依赖项drf-extensions==0.5.0 为DRF提供缓存扩展
    1)修改配置文件setting.py
# 缓存混入类
REST_FRAMEWORK_EXTENSIONS = {
    'DEFAULT_CACHE_RESPONSE_TIMEOUT': 120,
    'DEFAULT_USE_CACHE': 'default',
    'DEFAULT_OBJECT_CACHE_KEY_FUNC': 'rest_framework_extensions.utils.default_object_cache_key_func',
    'DEFAULT_LIST_CACHE_KEY_FUNC': 'rest_framework_extensions.utils.default_list_cache_key_func',
}


  1. django里的装饰器@method_decorator可以将装饰函数的装饰器变成可以装饰类方法的装饰器

# 让视图类继承混入类:
class EstatesView(CacheResponseMixin):   
# 带Mixin的类是混入类,混入类必须写在前面

class EstateView(CacheResponseMixin, ListCreateAPIView, RetrieveUpdateDestroyAPIView):
    queryset = Estate.objects.all().defer('agents')
    filter_backends = (DjangoFilterBackend, OrderingFilter)
    filter_class = EstateFilterSet

@method_decorator(decorator=cache_page(500), name='list')
@method_decorator(decorator=cache_page(120), name='retrieve')
class HouseTypeViewSet(ModelViewSet):
    queryset = HouseType.objects.all()
    serializer_class = HouseTypeSerializer
    # 拒绝分页
    pagination_class = None

补充4

接口限流: 限制接口的访问频率

原理: 在缓存(Redis)内记录IP地址,并记录访问次数,当请求超过阈值则限制访问
配置文件,打开settings.py

REST_FRAMEWORK = {
    # 限流配置
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',
    ),
    'DEFAULT_THROTTLE_RATES': {
        'anon': '5/min',  #定义限流频率
    },
}

若不想限流,可以在视图类里面加入以下代码:


throttle_classes = ()

补充5

1.数据接口筛选(自定义序列化器和自定义模糊查询)

例如:

- view.py

class HouseInfoView(ListCreateAPIView, RetrieveUpdateDestroyAPIView):
    '''房源展示图集'''

    queryset = HouseInfo.objects.all().select_related('type', 'estate')
    #导入自定义的序列化器
    serializer_class = HouseInfoSerializer
    ==筛选部分==
    # 继承父类支持筛选(DjangoFilterBackend),排序(OrderingFilter)
    filter_backends = (DjangoFilterBackend, OrderingFilter)
    # 自定义方法查询
    filter_class = HouseInfoFilterSet
    # 按'area', 'price'排序
    ordering_fields = ('area', 'price')

=========================自定义序列化器====================================
- serializers.py

class HouseInfoSerializer(serializers.ModelSerializer):
    '''房源序列化器'''
    
    # 都是关联键,需要二次处理的字段,自定义序列化的方法(SerializerMethodField)
    
    type = serializers.SerializerMethodField()
    estate = serializers.SerializerMethodField()
    tags = serializers.SerializerMethodField()
    district = serializers.SerializerMethodField()

    @staticmethod
    def get_type(obj):  #obj是上述序列化对象
        # return obj.type.name  #显示type中的name名称,是一个字段
        return obj.type.name

    @staticmethod
    def get_estate(obj):
        # return obj.estate.name  #显示estate中的name名称,得到是一个字段,例如“楼盘名称”
        '''重写一个序列器,将字段("楼盘")可拓展查询为字典,字典中的字段由新定义的序列器(EstateSimpleSerializer)决定'''
        
        return EstateSimpleSerializer(obj.estate).data

    @staticmethod
    def get_tags(obj):
    '''tag是多对多的关系,因此返回是一个集合,可以用生成器的方式将对象一个个取出来'''
        return [tag.content for tag in obj.tags.all()]

    @staticmethod
    def get_district(obj):
    '''distid3无上下关联所以重定义一个district字段根据需要的筛选条件(distid=obj.distid3)查询出来的数据扔进自定义的序列器中(DistrictSimpleSerializer)进行序列化'''
        district = District.objects.filter(distid=obj.distid3).only('name').first()
        return DistrictSimpleSerializer(district).data


    class Meta:
        model = HouseInfo
        exclude = ('detail','distid3','pubdate','hassubway','isshared','userid','distid2','agent')
       
=============================自定义查询方法============================       
       
       
# 引入搜索引擎 Whoosh +jieba(小型)  -----> 倒排索引
# 中科院研发的搜索引擎对中文适配度很高ElasticSearch / Solr  + 中文分词插件ik-analysis/smartcn/pinyin支持数据量大,并发高的查询支持
class HouseInfoFilterSet(django_filters.FilterSet):
    """自定义房源信息模糊查询"""
    # title字段模糊查询,contacts/startwith/endswith性能很差,建议搞个搜索引擎
    title = django_filters.CharFilter(lookup_expr='contains')
    minprice = django_filters.NumberFilter(field_name='price')
    maxprice = django_filters.NumberFilter(field_name='price')
    
    #自定义关键字查询方法
    keyword = django_filters.CharFilter(method='filter_by_keyword')

    # 自定义查询方法
    @staticmethod  #若不是静态方法,第一个参数是self
    def filter_by_keyword(queryset, key, value):
        queryset = queryset.filter(Q(title__contains=value) |
                                   Q(detail__startswith=value))
        return queryset

[站外图片上传中...(image-431ce9-1579510832645)]
补充5

导出excel工作簿(原生SQL查询)

pip install xlwt

前端页面中给一个连接并创建映射。例如:

<a href="/excel/">导出经理人报表</a>
urls.py

urlpatterns = [
   
    path('excel/', export_excel),

]

创建工作表视图函数

def export_excel(request):
#使用游标对象
    with connection.cursor() as cursor:
        cursor.execute('select t1.agentid, name, tel, total '
                       ' from tb_agent t1 inner join '
                       ' (select agentid, count(agentid) as total '
                       ' from tb_agent_estate group by agentid) t2 '
                       ' on t1.agentid=t2.agentid')

        agents = cursor.fetchall()
    workbook = xlwt.Workbook()
    #表名称
    sheet = workbook.add_sheet('经理人统计表')
    #表格字段
    titles = ('编号', '姓名', '联系电话', '楼盘数量')
    for col, title in enumerate(titles):
        sheet.write(0, col, title)
    for row, agent in enumerate(agents):
        for col in range(4):
            sheet.write(row + 1, col, agent[col])

            # str(只读) --->StringIO(可写)
            # bytes ---->BytesIO
            buffer = BytesIO()
            # buffer缓冲区,存储成二进制文件
            workbook.save(buffer)
            resp = HttpResponse(buffer.getvalue(),
                                content_type='application/vnd.ms-excel')
            # resp['content_type'] = 'application/vnd.ms-excel'
            resp['content-disposition'] = 'attachment;filename="data.xls"'  # inline直接打开文件,attachment下载文件
            return resp

补充6

软件开发过程模型

~ 传统过程模型:大型、超大型项目

  • 瀑布模型(经典模型):
    1. 可行性分析(研究做还是不做)---> 可行性分析报告
    2. 需求分析(研究做什么)--->
      头脑风暴 ---> 思维导图 ---> 需求规格说明书
      产品原型图 ---> Axure RP ---> 线框图/高保真原型
    3. 概要设计和详细设计
      数据库设计 ---> E-R图 ---> 物理模型图 <--- PowerDesigner
      面向对象设计(OOAD)---> UML ---> 类图 <--- StarUML / Enterprise Architect
      ~ is-a:继承
      ~ has-a:关联、聚合、合成
      ~ use-a:依赖
    4. 编码
    5. 测试(单元测试 ---> 系统测试 ---> 集成测试 ---> 验收测试)
    6. 交付(上线)+ 运维
      ~ 敏捷模型:迅速推出产品占领市场
      核心理念:增量迭代式开发
  • SCRUM(将开发过程分为若干个冲刺周期)
    1. 建立或更新需求池
      1. 计划会议(评估工作量、制定计划)
      2. 日常开发(站立会议、番茄工作法)
      3. 版本发布
      4. 评审会议(Show case)
      5. 回顾会议(总结得失)
        ~敏捷闭环工具 - JIRA/禅道
        ~团队开发工具 - 钉钉/ TeamBition

~ 版本控制 - 团队开发模式下如何使用Git
~ Git私服 ---> GitLab
~ Git标准工作流程:

  • git-flow
    • github-flow(PR流程)
      1. 克隆或者更新项目
        git clone URL
        git pull
      2. 基于master分支创建并切换到自己的分支
        git branch NAME
        git checkout NAME / git switch NAME
        git checkout -b NAME / git switch -C NAME
      3. 在自己的分支上做开发并实施版本控制
      4. git add FILE
      5. git commit -m message
      6. 把自己的分支推到服务器上
        git push -u origin NAME
      7. 在线发起合并请求(线上操作),请求将工作成果合并到master分支
      8. Pull Request / Merge Request
    • gitlab-flow
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容