作者:代昌松
项目详情代码请参加我的代码仓库:https://gitee.com/dcstempt_ping/izufang_rent
所有的自由都源于自信,所有的自信都源于自律!!!
项目可行性分析 ----> 可行性报告
需求分析 ----> 需求规格说明数 / 产品界面原型图
概要设计和详细设计 ----> 数据库 / 面向对象模型
编码 / 调试 / 测试
部署 / 交付 / 维护
瀑布模型一层一层往下递推,每一个环节需要等待上一个环节完成后才能开始
缺点:开发环节无法并行,无法拥抱需求变化,开发团队士气低落
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模型代码请参考附录一
【说明】
省市区需要形成三级联动,由与python是面向对象式编程,需要将pid
修改为parent,并且修改为ForeignKey
,表示多对一关系,一个地区只能有一个上一级地区,但是一个地区可以有多个次级区域,并且此处需要自参照,需要用to=self
。
表示真或假的是数据,都修改为BooleanField
,并且默认为False
。
楼盘Estate
需要关联三级行政区域,需要将distid
修改为ForeignKey
可以拿到对应地区的对象。
经理人和楼盘是多对多的关系,但是在数据库中只有两张表是无法形成多对多关系,假如需要从经理人查询到楼盘信息,需要在添加estates = models.ManyToManyField(to='Estate', through='AgentEstate')
,通过through
来指定中间实体,所以这时需要建立一个中间实体AgentEstate
来建立经理和楼盘之间的关系。
在经理人和楼盘的中间实体中,由于经理对于楼盘可以是一对多,楼盘对于经理也是一对多,所以在agnet
和estate
字段都修改为foreign
。
在AgentEstate
类中国,有一个unique_together
,表示一个经理对一个楼盘是唯一索引。
用户和角色,用户和权限,房源和标签同样是多对多关系,他们之间都需要模型中间体,与经理人和楼兰修改方法相同,此处不再赘述。
在用户名和邮箱中添加unique
属性保证其唯一性。
在User
中为什么不适用用户名Username
做主键?用户名中可能包含非英文的字符,需要通过百分号编码quote
转换。URL和HTTP请求行、请求头中都不能出现非URL字符,所有的非URL字符都要处理为百分号编码。所以处理起来比较麻烦,需要用没有实际意义的字段作为主键,例如userid
.
用户密码需要转换成哈希摘要保存在数据库中:例如MD5,SHA1,SH256都是单向哈希函数,只能通过对象转换为标识符,但是不能通过标识符反解出对象。通过标识符就可以识别出对象是否被伪造篡改,因此可以验证密码的唯一性。这个标识符也被称为数字签名或指纹。
on_delete
表示:保证数据完整性(一般在程序中保证数据完整性,而在数据库中不添加外键约束,从而提高数据库的性能),在删除数据的时候,选择DO_NOTHING
表示不做任何操作。
关系型数据库如何保证数据完整性:
数据一致性问题?
若想自定义一个父类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`可以新增加创建时间、更新时间和删除时间属性
在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())
在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)]
把软件(Software)、平台(Platform)、基础设施(Infrastructure)做成服务(Service)是很多IT企业一直在做的事情,这就是Sass(软件即服务)、Pass(平台即服务)和Lass(基础设施即服务)。实现面向服务的架构(SOA)有诸多方式,包括RPC(远程过程调用)、Web Service、REST等,在技术层面上,SOA是一种抽象的、松散耦合的粗粒度软件架构;在业务层面上,SOA的核心理念是“重用”和“互操作”,它将系统资源合成可操作的、标准的服务,使得这些资源能够被重新组合和应用。实现SOA的诸多方案中,REST被认为是最适合互联网应用的架构,符合REST规范的架构也常被称之为RESTful架构。
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请求头的信息中用Accept
和Content-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用户的所有权限)。为了了解用户故事,此处根据安居客的住房系统搭建了思维导图:
【说明】每个叶子节点就是用户故事,需要放入需求池中
在整理需求的时候,我们需要先建立需求池,需求池中包含了用户故事(用户所能够执行的权限,不同级别的用户能够进行的操作,比如VIP用户的所有权限,SVIP用户的所有权限)。为了了解用户故事,此处根据安居客的住房系统搭建了思维导图:
【说明】每个叶子节点就是用户故事,需要放入需求池中
项目和代码持续更新中,项目源代码请参考我的码云: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'), )