安居客住房系统-基于Python-Django前后端分离开发(一)——初始化项目及ORM关系映射

"安居客"住房系统-基于Python-Django前后端分离开发

作者:代昌松

项目详情代码请参加我的代码仓库:https://gitee.com/dcstempt_ping/izufang_rent

所有的自由都源于自信,所有的自信都源于自律!!!


文章目录

  • "安居客"住房系统-基于Python-Django前后端分离开发
    • 软件项目开发流程简述(过程模型)
      • 传统过程模型---->瀑布模型
      • 敏捷开发(敏捷过程模型)----> 极限编程、精益过程、Scrum
    • 初始化项目
      • 设计`models.py`模型
    • MVC架构模式和数据接口设计
      • MVC架构概述
      • RESTful架构
      • REST概述
      • 用户跟踪
    • 附录一

软件项目开发流程简述(过程模型)

传统过程模型---->瀑布模型

  1. 项目可行性分析 ----> 可行性报告

  2. 需求分析 ----> 需求规格说明数 / 产品界面原型图

    • Axure RP (高保真原型、线框图)
  3. 概要设计和详细设计 ----> 数据库 / 面向对象模型

    • 数据库设计图(概念模型图 ----> ER图 / 物理模型图)

      安居客住房系统-基于Python-Django前后端分离开发(一)——初始化项目及ORM关系映射_第1张图片

    • UML建模(标准化图形符号)----> OOAD ----> 例图 / 类图 / 时序图

  4. 编码 / 调试 / 测试

    • 单元测试
    • 集成测试
    • 系统测试
    • 验收测试
  5. 部署 / 交付 / 维护

瀑布模型一层一层往下递推,每一个环节需要等待上一个环节完成后才能开始

缺点:开发环节无法并行,无法拥抱需求变化,开发团队士气低落

敏捷开发(敏捷过程模型)----> 极限编程、精益过程、Scrum

安居客住房系统-基于Python-Django前后端分离开发(一)——初始化项目及ORM关系映射_第2张图片

https://www.scrumcn.com/agile/scrum-knowledge-library/scrum.html

CASE工具辅助软件开发:版本控制工具、缺陷管理工具、持续集成工具、敏捷闭环工具(JIRA、禅道 https://www.zentao.net)

初始化项目

项目初始文件:https://gitee.com/dcstempt_ping/izufang_rent 其中的Initialize projec分支

在项目初始版本中,已经完成了Django项目的创建,基础配置的修改、项目依赖项的搭建、数据库配置、Redis高速缓存配置、Django-Debug-Toolbar的配置、数据库建立新用户和建表、数据库ORM模型的映射。

commmo文件中的的utils中已经提前写入和项目需要的一些函数,validators文件中包含项目中将会用到的正则表达式。

在初始项目的基础上,我们还需要建立一个api应用,用来存放api的数据接口,并且在izufang/urls.py中修改添加api应用的路由

from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('api.urls'))
]

完成了项目的初始化工作,咱们就可以开始搭建我们的项目的.

设计models.py模型

yum install docker-ce

关系型数据库支持事务来保证数据的强一致性。事务的ACID特性:A(原子性,不可分割,要么全做,要么不做);C(一致性,事务前后数据状态一致);I(隔离性, 多个并发事务不能看到彼此的中间状态);D(持久性:事务完成后,保证数据持久化)

修改后的ORM模型代码请参考附录一

【说明】

  1. 省市区需要形成三级联动,由与python是面向对象式编程,需要将pid修改为parent,并且修改为ForeignKey,表示多对一关系,一个地区只能有一个上一级地区,但是一个地区可以有多个次级区域,并且此处需要自参照,需要用to=self

  2. 表示真或假的是数据,都修改为BooleanField,并且默认为False

  3. 楼盘Estate需要关联三级行政区域,需要将distid修改为ForeignKey可以拿到对应地区的对象。

  4. 经理人和楼盘是多对多的关系,但是在数据库中只有两张表是无法形成多对多关系,假如需要从经理人查询到楼盘信息,需要在添加estates = models.ManyToManyField(to='Estate', through='AgentEstate'),通过through来指定中间实体,所以这时需要建立一个中间实体AgentEstate来建立经理和楼盘之间的关系。

  5. 在经理人和楼盘的中间实体中,由于经理对于楼盘可以是一对多,楼盘对于经理也是一对多,所以在agnetestate字段都修改为foreign

  6. AgentEstate类中国,有一个unique_together,表示一个经理对一个楼盘是唯一索引。

  7. 用户和角色,用户和权限,房源和标签同样是多对多关系,他们之间都需要模型中间体,与经理人和楼兰修改方法相同,此处不再赘述。

  8. 在用户名和邮箱中添加unique属性保证其唯一性。

  9. User中为什么不适用用户名Username做主键?用户名中可能包含非英文的字符,需要通过百分号编码quote转换。URL和HTTP请求行、请求头中都不能出现非URL字符,所有的非URL字符都要处理为百分号编码。所以处理起来比较麻烦,需要用没有实际意义的字段作为主键,例如userid.

  10. 用户密码需要转换成哈希摘要保存在数据库中:例如MD5,SHA1,SH256都是单向哈希函数,只能通过对象转换为标识符,但是不能通过标识符反解出对象。通过标识符就可以识别出对象是否被伪造篡改,因此可以验证密码的唯一性。这个标识符也被称为数字签名或指纹。

  11. on_delete表示:保证数据完整性(一般在程序中保证数据完整性,而在数据库中不添加外键约束,从而提高数据库的性能),在删除数据的时候,选择DO_NOTHING表示不做任何操作。

  12. 关系型数据库如何保证数据完整性:

    • 实体完整性 - 每一条记录都是唯一的 --> 主键/唯一索引
    • 参照完整性- 外键
    • 领域完整性- 数据库中的数据都是有效数据 - 数据类型 / 类型长度 / 非空约束 / 默认值约束 / 检查约束
  13. 数据一致性问题?

  14. 若想自定义一个父类BaseModel

    class BaseModel(model.Model):
    	create_time = model.DateTimeField(auto_now_add=True)
        update_time = model.DateTimeField(auto_now_add=True)
        deleted = models.BooleanFiled(default=False)
        
        class Meta:
            abstract = True
       		# 抽象类:表示这个类只能用来继承
    # 模型中其他的类继承`BaseModel`可以新增加创建时间、更新时间和删除时间属性
    
  15. HouseInfo类中,户型所属的"二级行政区"或“三级行政区”字段中有一个related_name='+',表示不允许从"一"查询“多”,地区多余房子是一对多,而房子对于地区是多对一,但是属于三级行政区的房子同样属于其对应的二级行政区,此处禁止反查防止房子所在地区的二异性。

【补充】

若要将一个较大的文件生成哈希摘要,需要读取他的信息,每次只读取其中的512个字节或者1024个字节(最好为512的倍数),通过迭代的方法读完整个文件时一个表妥善的做法,防止服务器的内存被占用

import hashlib
hasher = md5()

with open('<文件地址>', 'rb') as file:
	data = file.read(512)
    while data:
        hasher.update(data)
        data = file.read(512)
    print(hasher.hexdigest())
    
==================================================================================
from hashlib import sha256

with open('<文件地址>', 'rb') as file:
    file_iter = iter(lambda: file.read(2048), b'')
    for data in file_iter:
        hasher.update(data)
	 print(hasher.hexdigest())

MVC架构模式和数据接口设计

MVC架构概述

在Python的web框架中,Django无疑是最具有代表性的重量级选手,开发者可以基于Django快速地开发可靠的web应用程序,因为它减少了Web开发中不必要的开销,对于常用的设计和开发模式进行了封装,并对MVC架构提供了支持(Django中称之为MTV架构)。MVC是软件系统开发领域中一种放之四海而皆准的架构,它将系统中软件分为模型(Model)、视图(View)和控制器(Controller)三个部分并借此实现模型(数据)和视图(显示)的解耦合。由于模型和视图进行了分离,所以需要一个中间人将解耦合的模型和视图联系起来,扮演这个角色的就是控制器。稍具规模的软件都会使用MVC架构(或者是从MVC演化出的其他架构),DJango项目中我们称之为MTV,MTV中的M跟MVC中的M没有区别,就是代表数据模型,T代表网页模板(显示数据的视图),而V代表了视图函数,在Django框架中,视图函数和Django框架本身一起扮演了MVC中C的角色。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-daxDtFr1-1596964697157)(C:\Users\reserve\AppData\Roaming\Typora\typora-user-images\image-20200722144138618.png)]

RESTful架构

把软件(Software)、平台(Platform)、基础设施(Infrastructure)做成服务(Service)是很多IT企业一直在做的事情,这就是Sass(软件即服务)、Pass(平台即服务)和Lass(基础设施即服务)。实现面向服务的架构(SOA)有诸多方式,包括RPC(远程过程调用)、Web Service、REST等,在技术层面上,SOA是一种抽象的、松散耦合的粗粒度软件架构;在业务层面上,SOA的核心理念是“重用”和“互操作”,它将系统资源合成可操作的、标准的服务,使得这些资源能够被重新组合和应用。实现SOA的诸多方案中,REST被认为是最适合互联网应用的架构,符合REST规范的架构也常被称之为RESTful架构。

REST概述

REST这个词,是Roy Thomas Fieding在他2000年的博士论文中提出的,Roy是HTTP协议(1.0和1.1版)的主要设计者、Apache服务器软件主要作者、Apache基金会第一任主席。在他的博士论文中,Roy把他对互联网软甲的架构原则定名为REST,即REpresentation State Transfer的缩写,中文翻译为:“表现层状态转移”或“表述层状态转移

这个“表现层”其实指的是“资源”的“表现层”。所谓资源,就是网络上的一个实体,也可以称之为网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲或一种服务。我们可以用一个URL(统一资源定位符)指向资源,要获取到这个资源,访问它的URL即可,URL就是资源在互联网上的唯一标识。资源可以有多种外在表现形式。我们把资源具体呈现出来的形式,叫做它的“表现层”。比如,文本可以用text/plain格式表现,也可以用text/html格式、application/json格式表现,甚至可以采用二进制格式;图片可以用image/jepg格式表现,也可以用image/png格式表现。URL只代表资源的实体,不代表他的变现形式。严格地说,有些网址最后地.html后缀名是不必要的。因为这个后缀名表示格式,属于“表现层”范畴,而URL应该只代表“资源” 的位置,它的具体表现形式,应该在HTTP请求头的信息中用AcceptContent-type字段指定,这两个字段才是对“表现层”的描述。

访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。Web应用通常使用HTTP作为通讯协议,客户端想要操作服务器,必须通过HTTP请求头,让服务器端发生“状态”转移,而这种状态转移是建立在表现层之上的,所以就是“表现层状态转移”。客户端通过HTTP的动词GET、POST、PUT(或PATCH)、DELETE,分别对应对资源的四种基本操作,其中GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT(或PATCH)用来更新资源,DELETE用来删除资源。

当我们在设计Web应用是,如果需要想客户提供资源需求,就可以使用REST风格的URL,这是实现RESTful架构的第一步。当然,真正的RESTful架构并不只是URL符合REST风格,更重要的是“无状态”和“幂等性”两个词。

用户跟踪

前文我们已经提到过,HTTP协议是无状态的,一次请求结束断开连接,下次服务器再收到请求,它就不知道这个请求是哪个用户发过来的。但是对于一个Web应用而言,它是需要有状态管理的,这样才能让服务器知道HTTP请求来自哪个用户,从而判断是否允许该用户请求以及为用户提供更好的服务,这个过程就是常说的会话管理

在基于后端开发的Django项目中,可以通过用户登录成功之后,在服务器通过一个session对象保存用户的相关数据,然后把session的ID写入浏览器的cookie中,下一次请求时,HTTP请求头中会携带cookie数据,服务器从HTTP请求头中读取cookie中的sessionid,根据这个标识符找到对应的session对象,这样就能够获取到之前保存在session中的用户数据。我们刚才说过,REST架构是最适合互联网应用的架构,它强调了HTTP的无状态性,这样才能保证应用的水平扩展能力(当并发请求量增加时,可以通过增加新的服务器节点来为系统扩容)。显然,基于session实现用户跟踪方式需要服务器保存session对象,在做水平扩展增加新的服务器节点时,需要复制和同步session对象,这显然是非常麻烦的。解决这个问题有两种方案,一种是架设缓存服务器(如Redis),让多个服务器节点共享缓存服务并将session对象直接置于缓存服务器中;另一种方式放弃session的用户跟踪,使用基于token的用户跟踪

在整理需求的时候,我们需要先建立需求池,需求池中包含了用户故事(用户所能够执行的权限,不同级别的用户能够进行的操作,比如VIP用户的所有权限,SVIP用户的所有权限)。为了了解用户故事,此处根据安居客的住房系统搭建了思维导图:

安居客住房系统-基于Python-Django前后端分离开发(一)——初始化项目及ORM关系映射_第3张图片

【说明】每个叶子节点就是用户故事,需要放入需求池中

在整理需求的时候,我们需要先建立需求池,需求池中包含了用户故事(用户所能够执行的权限,不同级别的用户能够进行的操作,比如VIP用户的所有权限,SVIP用户的所有权限)。为了了解用户故事,此处根据安居客的住房系统搭建了思维导图:

安居客住房系统-基于Python-Django前后端分离开发(一)——初始化项目及ORM关系映射_第4张图片

【说明】每个叶子节点就是用户故事,需要放入需求池中


项目和代码持续更新中,项目源代码请参考我的码云:https://gitee.com/dcstempt_ping/izufang_rent
谢谢阅读❤

附录一

ORM映射模型api/models.py

# 在终端中执行下列代码进行模型到类的映射
python manage.py inspectdb > api/models.py

修改好的models.py如下:

# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
#   * Rearrange models' order
#   * Make sure each model has one field with primary_key=True
#   * Make sure each ForeignKey has `on_delete` set to the desired behavior.
#   * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table
# Feel free to rename the models, but don't rename db_table values or field names.
from django.db import models


class User(models.Model):
    """用户"""
    # 用户ID
    userid = models.AutoField(primary_key=True)
    # 用户名
    username = models.CharField(unique=True, max_length=20)
    # 用户密码(数据库中保存MD5摘要,登录时比较密码的签名)
    password = models.CharField(max_length=32)
    # 用户真实姓名
    realname = models.CharField(max_length=20)
    # 用户性别
    sex = models.BooleanField(default=True)
    # 用户电话
    tel = models.CharField(unique=True, max_length=20)
    # 用户邮箱
    email = models.CharField(unique=True, max_length=255, default='')
    # 用户注册日期
    regdate = models.DateTimeField(auto_now_add=True)
    # 用户积分(每天登录可以获得积分)
    point = models.IntegerField(default=0)
    # 用户最后登录日期时间
    lastvisit = models.DateTimeField(blank=True, null=True)
    # 用户角色
    roles = models.ManyToManyField(to='Role', through='UserRole')
    # role = models.OneToOneField(to='Role', on_delete=models.DO_NOTHING)

    class Meta:
        managed = False
        db_table = 'tb_user'


class District(models.Model):
    """地区"""
    # 地区ID
    objects = None
    distid = models.IntegerField(primary_key=True, verbose_name='地区编号')
    # 父级行政区域(省级行政区域的父级为None)
    parent = models.ForeignKey(to='self', on_delete=models.DO_NOTHING, db_column='pid', null=True)
    # 地区名称
    name = models.CharField(max_length=255)
    # 该地区是不是热门城市
    ishot = models.BooleanField(default=False)
    # 地区介绍
    intro = models.CharField(max_length=255, default='')

    class Meta:
        managed = False
        db_table = 'tb_district'


class Agent(models.Model):
    """经理人"""
    # 经理人ID
    objects = None
    agentid = models.AutoField(primary_key=True)
    # 经理人姓名
    name = models.CharField(max_length=255)
    # 经理人电话
    tel = models.CharField(max_length=20)
    # 经理人服务星级
    servstar = models.IntegerField()
    # 经理人提供的房源真实度
    realstar = models.IntegerField()
    # 经理人业务水平
    profstar = models.IntegerField()
    # 经理人是否持有专业认证
    certificated = models.BooleanField(default=False)
    # 经理人负责的楼盘
    estates = models.ManyToManyField(to='Estate', through='AgentEstate')

    class Meta:
        managed = False
        db_table = 'tb_agent'


class Estate(models.Model):
    """楼盘"""
    # 楼盘ID
    objects = None
    estateid = models.AutoField(primary_key=True)
    # 所属行政区域
    district = models.ForeignKey(to=District, on_delete=models.DO_NOTHING, db_column='distid')
    # 楼盘名称
    name = models.CharField(max_length=255)
    # 楼盘热度
    hot = models.IntegerField(default=0)
    # 楼盘介绍
    intro = models.CharField(max_length=511, default='')

    class Meta:
        managed = False
        db_table = 'tb_estate'


class AgentEstate(models.Model):
    """经理人楼盘中间实体"""
    agent_estate_id = models.AutoField(primary_key=True)
    # 经理人
    agent = models.ForeignKey(to=Agent, on_delete=models.DO_NOTHING, db_column='agentid')
    # 楼盘
    estate = models.ForeignKey(to=Estate, on_delete=models.DO_NOTHING, db_column='estateid')

    class Meta:
        managed = False
        db_table = 'tb_agent_estate'
        unique_together = (('agent', 'estate'), )


class HouseType(models.Model):
    """户型"""
    # 户型ID
    objects = None
    typeid = models.IntegerField(primary_key=True)
    # 户型名称
    name = models.CharField(max_length=255)

    class Meta:
        managed = False
        db_table = 'tb_house_type'


class HouseInfo(models.Model):
    """房源信息"""
    # 房源ID
    objects = None
    houseid = models.AutoField(primary_key=True)
    # 房源标题
    title = models.CharField(max_length=50)
    # 房源面积
    area = models.IntegerField()
    # 房源楼层
    floor = models.IntegerField()
    # 房源总楼层
    totalfloor = models.IntegerField()
    # 房源朝向
    direction = models.CharField(max_length=10)
    # 房源价格
    price = models.IntegerField()
    # 房源价格单位
    priceunit = models.CharField(max_length=10)
    # 房源详情
    detail = models.CharField(max_length=511, default='')
    # 房源图片(主图)
    mainphoto = models.CharField(max_length=255)
    # 房源发布日期
    pubdate = models.DateField(auto_now_add=True)
    # 房源所在街道
    street = models.CharField(max_length=255)
    # 是否有地铁
    hassubway = models.BooleanField(default=False)
    # 是否支持合租
    isshared = models.BooleanField(default=False)
    # 是否有中介费
    hasagentfees = models.BooleanField(default=False)
    # 房源户型
    type = models.ForeignKey(to=HouseType, on_delete=models.DO_NOTHING, db_column='typeid')
    # 房源发布者
    user = models.ForeignKey(to=User, on_delete=models.DO_NOTHING, db_column='userid')
    # 房源所属二级行政区域
    district_level2 = models.ForeignKey(to=District, on_delete=models.DO_NOTHING, related_name='+', db_column='distid2')
    # 房源所属三级行政区域
    district_level3 = models.ForeignKey(to=District, on_delete=models.DO_NOTHING, related_name='+', db_column='distid3')
    # 房源所属楼盘
    estate = models.ForeignKey(to=Estate, on_delete=models.DO_NOTHING, db_column='estateid', null=True)
    # 负责该房源的经理人
    agent = models.ForeignKey(to=Agent, on_delete=models.DO_NOTHING, db_column='agentid', null=True)
    # 房源标签
    tags = models.ManyToManyField(to='Tag', through='HouseTag')

    class Meta:
        managed = False
        db_table = 'tb_house_info'


class HousePhoto(models.Model):
    """房源的图片"""
    # 图片ID
    photoid = models.AutoField(primary_key=True)
    # 图片对应的房源
    house = models.ForeignKey(to=HouseInfo, on_delete=models.DO_NOTHING, db_column='houseid', null=True)
    # 图片资源路径
    path = models.CharField(max_length=255)
    # 是否主图
    ismain = models.BooleanField(default=False)

    class Meta:
        managed = False
        db_table = 'tb_house_photo'


class Tag(models.Model):
    """标签"""
    # 标签ID
    tagid = models.AutoField(primary_key=True)
    # 标签内容
    content = models.CharField(max_length=20)

    class Meta:
        managed = False
        db_table = 'tb_tag'


class HouseTag(models.Model):
    """房源标签中间实体"""
    house_tag_id = models.AutoField(primary_key=True)
    # 房源
    house = models.ForeignKey(to=HouseInfo, on_delete=models.DO_NOTHING, db_column='houseid')
    # 标签
    tag = models.ForeignKey(to=Tag, on_delete=models.DO_NOTHING, db_column='tagid')

    class Meta:
        managed = False
        db_table = 'tb_house_tag'
        unique_together = (('house', 'tag'), )


class Record(models.Model):
    """浏览记录"""
    # 浏览记录ID
    recordid = models.BigAutoField(primary_key=True)
    # 浏览的用户
    user = models.ForeignKey(to=User, on_delete=models.DO_NOTHING, db_column='userid')
    # 浏览的房源
    house = models.ForeignKey(to=HouseInfo, on_delete=models.DO_NOTHING, db_column='houseid')
    # 浏览日路日期
    recorddate = models.DateTimeField(auto_now=True)

    class Meta:
        managed = False
        db_table = 'tb_record'
        unique_together = (('user', 'house'), )


class LoginLog(models.Model):
    """登录日志"""
    # 日志ID
    logid = models.BigAutoField(primary_key=True)
    # 登录的用户
    user = models.ForeignKey(to=User, on_delete=models.DO_NOTHING, db_column='userid')
    # 登录的IP地址
    ipaddr = models.CharField(max_length=255)
    # 登录的日期
    logdate = models.DateTimeField(auto_now_add=True)
    # 登录的设备编码
    devcode = models.CharField(max_length=255, default='')

    class Meta:
        managed = False
        db_table = 'tb_login_log'


class Role(models.Model):
    """角色"""
    # 角色ID
    roleid = models.AutoField(primary_key=True)
    # 角色名称
    rolename = models.CharField(max_length=255)
    # 角色对应的权限
    privs = models.ManyToManyField(to='Privilege', through='RolePrivilege')

    class Meta:
        managed = False
        db_table = 'tb_role'


class Privilege(models.Model):
    """权限"""
    # 权限ID
    privid = models.AutoField(primary_key=True)
    # 权限对应的方法
    method = models.CharField(max_length=15)
    # 权限对应的URL
    url = models.CharField(max_length=1024)
    # 权限的描述
    detail = models.CharField(max_length=255, default='')

    class Meta:
        managed = False
        db_table = 'tb_privilege'


class UserRole(models.Model):
    """用户角色中间实体"""
    user_role_id = models.AutoField(primary_key=True)
    # 用户
    user = models.ForeignKey(to=User, on_delete=models.DO_NOTHING, db_column='userid')
    # 角色
    role = models.ForeignKey(to=Role, on_delete=models.DO_NOTHING, db_column='roleid')

    class Meta:
        managed = False
        db_table = 'tb_user_role'
        unique_together = (('user', 'role'), )


class RolePrivilege(models.Model):
    """角色权限中间实体"""
    role_priv_id = models.AutoField(primary_key=True)
    # 角色
    role = models.ForeignKey(to=Role, on_delete=models.DO_NOTHING, db_column='roleid')
    # 权限
    priv = models.ForeignKey(to=Privilege, on_delete=models.DO_NOTHING, db_column='privid')

    class Meta:
        managed = False
        db_table = 'tb_role_privilege'
        unique_together = (('role', 'priv'), )

你可能感兴趣的:(前后端分离开发,python,mysql,django)