Flask智能租房------前期准备 知识点详解
一、项目介绍
知识点:Django MVT 架构模式
Django 采用 MVT(Model-View-Template) 架构模式:
| 层次 | 说明 | 对应关系 |
|---|---|---|
| M (Model) | 数据层,负责数据库交互 | 对应 MVC 中的 Model |
| V (View) | 业务逻辑层,处理请求与响应 | 对应 MVC 中的 Controller |
| T (Template) | 模板层,负责页面展示 | 对应 MVC 中的 View |
请求处理流程:
用户请求 → urls.py(路由) → views.py(视图处理) → models.py(数据操作)
↓
template(模板渲染) → 返回HTML响应
python
# 案例:MVT 基本流程演示
# ========== 1. Model 层 (models.py) ==========
from django.db import models
# 定义房源模型类,对应数据库中的一张表
class House(models.Model):
# 房源标题,CharField用于存储短字符串,max_length为最大长度
title = models.CharField('房源标题', max_length=200)
# 租金,DecimalField用于存储精确的小数,max_digits=总位数,decimal_places=小数位数
rent = models.DecimalField('租金', max_digits=10, decimal_places=2)
# 房源描述,TextField用于存储大段文本
description = models.TextField('描述', blank=True, default='')
class Meta:
# 指定数据库表名为 'house'
db_table = 'house'
def __str__(self):
# 对象的字符串表示,便于在管理后台和调试中查看
return self.title
# ========== 2. View 层 (views.py) ==========
from django.shortcuts import render
from django.http import JsonResponse
from .models import House
# 视图函数:处理房源列表请求
def house_list(request):
# 从数据库中查询所有房源对象,返回QuerySet
houses = House.objects.all()
# 将查询结果传递给模板进行渲染
# render(请求对象, 模板路径, 上下文字典)
return render(request, 'house/list.html', {'houses': houses})
# ========== 3. Template 层 (templates/house/list.html) ==========
"""
<!DOCTYPE html>
<html>
<head><title>房源列表</title></head>
<body>
<!-- Django模板语言:for循环遍历房源列表 -->
{% for house in houses %}
<h2>{{ house.title }}</h2> <!-- 输出房源标题 -->
<p>租金:{{ house.rent }}元/月</p> <!-- 输出租金 -->
{% endfor %}
</body>
</html>
"""
# ========== 4. URL路由 (urls.py) ==========
from django.urls import path
from . import views
# 路由列表:将URL模式与视图函数绑定
urlpatterns = [
# path(路由路径, 视图函数, 路由名称)
path('houses/', views.house_list, name='house_list'),
]
二、项目开发模式与运行机制
知识点1:Django 项目的开发模式
Django 开发服务器是一个轻量级的 Python Web 服务器,用于开发和测试阶段。
python
# 启动开发服务器的命令(在终端执行)
# python manage.py runserver [ip:port]
# 默认监听 127.0.0.1:8000
# 指定IP和端口,允许外部访问
# python manage.py runserver 0.0.0.0:8000
知识点2:Django 的请求-响应生命周期
python
# 完整的请求-响应生命周期示例
# ========== settings.py 中的中间件配置 ==========
MIDDLEWARE = [
# 安全中间件:提供各种安全保护,如XSS防护、HTTPS重定向等
'django.middleware.security.SecurityMiddleware',
# 会话中间件:处理Session会话
'django.contrib.sessions.middleware.SessionMiddleware',
# 通用中间件:处理URL末尾的斜杠等
'django.middleware.common.CommonMiddleware',
# CSRF中间件:跨站请求伪造保护
'django.middleware.csrf.CsrfViewMiddleware',
# 认证中间件:处理用户认证
'django.contrib.auth.middleware.AuthenticationMiddleware',
# 消息中间件:处理一次性消息
'django.contrib.messages.middleware.MessageMiddleware',
# 点击劫持保护中间件
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# ========== views.py 中的请求与响应 ==========
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
# 示例1:返回纯文本响应
def index(request):
# request.method 获取请求方法(GET/POST/PUT/DELETE等)
if request.method == 'GET':
# HttpResponse 直接返回文本内容
return HttpResponse('欢迎使用智能租房平台')
# 示例2:返回JSON响应(常用于API接口)
def api_house(request):
data = {
'title': '阳光花园3室2厅',
'rent': 3500,
'status': 'success'
}
# JsonResponse 自动将字典转换为JSON格式,并设置Content-Type为application/json
return JsonResponse(data)
# 示例3:重定向响应
def old_url(request):
# redirect 将用户重定向到新的URL
# HTTP状态码302(临时重定向)
return redirect('/new-url/')
# 示例4:获取请求参数
def search(request):
# GET请求:通过 query_params 获取URL中的查询参数
# 例如:/search/?keyword=地铁&min_rent=2000
keyword = request.GET.get('keyword', '') # 获取查询参数,不存在则返回空字符串
min_rent = request.GET.get('min_rent', '0') # 获取最低租金参数
# POST请求:通过 body 或 POST 获取请求体中的参数
# 例如:表单提交
if request.method == 'POST':
city = request.POST.get('city', '') # 获取POST表单数据
area = request.POST.get('area', '') # 获取POST表单数据
return HttpResponse(f'搜索关键词:{keyword},最低租金:{min_rent}')
知识点3:WSGI 协议
python
# wsgi.py 文件内容
"""
WSGI(Web Server Gateway Interface)是Python Web服务器与Web应用之间的标准接口协议。
Django通过wsgi.py文件实现WSGI协议,使得Django应用可以部署在任何支持WSGI的服务器上。
"""
import os
# 从django.core模块导入WSGI应用处理函数
from django.core.wsgi import get_wsgi_application
# 设置Django项目的配置模块路径
# DJANGO_SETTINGS_MODULE 告诉Django使用哪个settings文件
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rent_project.settings')
# 获取WSGI应用对象,供WSGI服务器(如Gunicorn、uWSGI)调用
application = get_wsgi_application()
三、项目创建和配置
知识点1:创建 Django 项目
bash
# ==================== 创建项目的命令 ====================
# 使用 django-admin 创建新项目
# django-admin startproject 项目名称 [目标目录]
django-admin startproject rent_project
# 执行后生成的项目目录结构:
# rent_project/
# ├── manage.py # 项目管理脚本(启动服务器、创建应用、数据库迁移等)
# └── rent_project/ # 项目配置目录
# ├── __init__.py # Python包标识文件(将目录标记为Python包)
# ├── settings.py # 项目配置文件(数据库、应用、中间件等所有配置)
# ├── urls.py # 项目根路由配置文件
# ├── asgi.py # ASGI异步服务器接口(用于异步部署)
# └── wsgi.py # WSGI服务器接口(用于同步部署)
# ==================== 创建应用 ====================
# 进入项目目录
cd rent_project
# 创建名为 house 的应用
# python manage.py startapp 应用名称
python manage.py startapp house
# 执行后生成的应用目录结构:
# house/
# ├── __init__.py # Python包标识文件
# ├── admin.py # Django管理后台配置
# ├── apps.py # 应用配置类
# ├── migrations/ # 数据库迁移文件目录
# │ └── __init__.py
# ├── models.py # 数据模型定义
# ├── tests.py # 单元测试文件
# └── views.py # 视图函数定义
知识点2:注册应用
python
# ==================== settings.py ====================
INSTALLED_APPS = [
# Django自带的应用
'django.contrib.admin', # 管理后台站点
'django.contrib.auth', # 认证授权系统
'django.contrib.contenttypes', # 内容类型框架
'django.contrib.sessions', # 会话框架
'django.contrib.messages', # 消息框架
'django.contrib.staticfiles', # 静态文件管理
# ---- 注册自定义应用 ----
# 方式一:使用应用的名称(字符串形式)
'house',
# 方式二:使用应用配置类(推荐,更明确)
# 'house.apps.HouseConfig',
]
python
# ==================== house/apps.py ====================
from django.apps import AppConfig
class HouseConfig(AppConfig):
# 应用的默认主键字段类型
# Django 3.2+ 默认使用 BigAutoField(64位整数自增主键)
default_auto_field = 'django.db.models.BigAutoField'
# 应用的名称
name = 'house'
# 应用在管理后台等处显示的中文名称
verbose_name = '房源管理'
知识点3:使用配置信息(settings.py 详解)
python
# ==================== settings.py 核心配置详解 ====================
import os
from pathlib import Path
# ---- 1. 基础路径配置 ----
# BASE_DIR 指向项目的根目录(manage.py 所在的目录)
# Path(__file__) 是当前文件(settings.py)的路径
# .resolve() 将路径解析为绝对路径
# .parent.parent 向上两级目录
BASE_DIR = Path(__file__).resolve().parent.parent
# ---- 2. 安全配置 ----
# SECRET_KEY 是Django的安全密钥,用于加密Session、CSRF Token等
# 生产环境中必须保密,绝不能泄露或提交到版本控制系统
SECRET_KEY = 'django-insecure-your-secret-key-here'
# DEBUG 模式开关
# True:开发环境,显示详细的错误页面(包含代码和堆栈信息)
# False:生产环境,不显示敏感的调试信息
DEBUG = True
# ALLOWED_HOSTS:允许访问该站点的主机名/IP列表
# DEBUG=True 时,可以为空(允许localhost)
# DEBUG=False 时,必须配置,否则所有请求都会返回400错误
ALLOWED_HOSTS = ['*'] # 开发环境允许所有主机,生产环境应限制
# ---- 3. 应用注册 ----
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 注册智能租房应用
'house',
]
# ---- 4. 中间件配置 ----
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# ---- 5. 根URL配置 ----
# 指定项目的根URL配置文件路径
ROOT_URLCONF = 'rent_project.urls'
# ---- 6. 模板配置(后续详解) ----
# TEMPLATES = [...]
# ---- 7. WSGI应用配置 ----
WSGI_APPLICATION = 'rent_project.wsgi.application'
# ---- 8. 数据库配置 ----
DATABASES = {
# 'default' 是默认数据库连接
'default': {
# 数据库引擎:SQLite3(轻量级文件数据库,适合开发)
'ENGINE': 'django.db.backends.sqlite3',
# 数据库文件路径:项目根目录下的 db.sqlite3
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# ---- MySQL 数据库配置示例(生产环境推荐) ----
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.mysql', # MySQL引擎
# 'NAME': 'rent_db', # 数据库名称
# 'USER': 'root', # 数据库用户名
# 'PASSWORD': 'your_password', # 数据库密码
# 'HOST': '127.0.0.1', # 数据库主机地址
# 'PORT': '3306', # 数据库端口号
# 'OPTIONS': {
# 'charset': 'utf8mb4', # 字符集,支持emoji等
# 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
# },
# }
# }
# ---- 9. 密码验证 ----
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]
# ---- 10. 国际化与本地化 ----
# 语言代码:简体中文
LANGUAGE_CODE = 'zh-hans'
# 时区:中国标准时间
TIME_ZONE = 'Asia/Shanghai'
# 启用国际化(翻译)
USE_I18N = True
# 启用本地化(日期、数字格式本地化)
USE_L10N = True
# 启用时区感知(存储UTC时间,显示本地时间)
USE_TZ = True
# ---- 11. 默认主键字段类型 ----
# Django 3.2+ 新增配置,指定模型默认使用的主键类型
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
知识点4:配置前端静态文件
python
# ==================== settings.py 中的静态文件配置 ====================
# ---- 静态文件URL前缀 ----
# 在模板中使用 {% static 'css/style.css' %} 时,实际生成的URL为 /static/css/style.css
STATIC_URL = '/static/'
# ---- 静态文件收集目录(生产环境) ----
# 执行 python manage.py collectstatic 后,所有静态文件会被收集到此目录
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
# ---- 额外的静态文件目录 ----
# Django除了在每个应用的static目录中查找静态文件外,
# 还会在这些目录中查找(用于存放项目级别的公共静态文件)
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'), # 项目根目录下的 static 目录
]
# ---- 静态文件查找后端 ----
# Django查找静态文件的后端类列表
STATICFILES_FINDERS = (
# 在 INSTALLED_APPS 中注册的应用的 static/ 目录中查找
'django.contrib.staticfiles.finders.FileSystemFinder',
# 在 STATICFILES_DIRS 指定的目录中查找
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
)
bash
# ==================== 静态文件目录结构 ====================
# 项目根目录结构:
# rent_project/
# ├── static/ # 项目级静态文件目录(由STATICFILES_DIRS配置)
# │ ├── css/ # CSS样式文件
# │ │ ├── base.css # 全局基础样式
# │ │ └── house.css # 房源页面样式
# │ ├── js/ # JavaScript脚本文件
# │ │ ├── common.js # 公共脚本
# │ │ └── house.js # 房源页面脚本
# │ ├── images/ # 图片资源
# │ │ ├── logo.png # 网站Logo
# │ │ └── default_house.png # 默认房源图片
# │ └── fonts/ # 字体文件
# │ └── iconfont.ttf # 图标字体
# └── house/ # house应用
# └── static/ # 应用级静态文件目录
# └── house/ # 应用命名空间,防止文件名冲突
# ├── css/
# │ └── detail.css # 房源详情页样式
# └── js/
# └── detail.js # 房源详情页脚本
html
<!-- ==================== 在模板中使用静态文件 ==================== -->
{% load static %} <!-- 加载静态文件标签库,必须放在模板顶部 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>智能租房平台</title>
<!-- 引入项目级CSS文件 -->
<!-- {% static %} 模板标签会自动将路径转换为完整的URL -->
<link rel="stylesheet" href="{% static 'css/base.css' %}">
<!-- 引入应用级CSS文件 -->
<link rel="stylesheet" href="{% static 'house/css/detail.css' %}">
</head>
<body>
<!-- 引入项目级Logo图片 -->
<img src="{% static 'images/logo.png' %}" alt="智能租房平台">
<!-- 引入JavaScript文件 -->
<script src="{% static 'js/common.js' %}"></script>
<script src="{% static 'house/js/detail.js' %}"></script>
</body>
</html>
知识点5:配置模板文件
python
# ==================== settings.py 中的模板配置 ====================
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
TEMPLATES = [
{
# 模板引擎后端:Django自带的模板引擎
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# 模板文件的搜索目录列表(按优先级从高到低排列)
'DIRS': [
# 项目根目录下的 templates 目录(存放项目级公共模板)
os.path.join(BASE_DIR, 'templates'),
],
# 是否在已注册应用的 templates/ 目录中查找模板
# True 表示 Django 会在每个应用的 templates/ 子目录中搜索模板
'APP_DIRS': True,
# 模板引擎的选项配置
'OPTIONS': {
# 模板上下文处理器列表
# 上下文处理器会自动为每个模板添加特定的上下文变量
'context_processors': [
# 添加调试相关的上下文变量(DEBUG=True时生效)
'django.template.context_processors.debug',
# 添加 request 对象到模板上下文(可在模板中使用 {{ request }})
'django.template.context_processors.request',
# 添加用户认证相关的上下文变量({{ user }}, {{ perms }}等)
'django.contrib.auth.context_processors.auth',
# 添加消息框架相关的上下文变量({{ messages }})
'django.contrib.messages.context_processors.messages',
],
},
},
]
bash
# ==================== 模板文件目录结构 ====================
# rent_project/
# ├── templates/ # 项目级模板目录
# │ ├── base.html # 基础模板(其他模板继承此模板)
# │ ├── house/ # 房源相关的模板目录
# │ │ ├── index.html # 房源首页
# │ │ ├── list.html # 房源列表页
# │ │ ├── detail.html # 房源详情页
# │ │ └── search.html # 房源搜索页
# │ └── user/ # 用户相关的模板目录
# │ ├── login.html # 登录页
# │ └── register.html # 注册页
# └── house/ # house应用
# └── templates/ # 应用级模板目录(APP_DIRS=True时生效)
# └── house/ # 应用命名空间,防止模板名冲突
# └── my_partial.html # 应用专属的局部模板
html
<!-- ==================== 模板继承示例 ==================== -->
<!-- ========== templates/base.html (基础模板) ========== -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
{% load static %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- block标签定义可被子模板覆盖的区块 -->
<title>{% block title %}智能租房平台{% endblock %}</title>
<link rel="stylesheet" href="{% static 'css/base.css' %}">
<!-- 额外的CSS区块,子模板可以追加自己的样式 -->
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- 页面头部导航 -->
<header>
<nav>
<a href="{% url 'house:index' %}">首页</a>
<a href="{% url 'house:list' %}">房源列表</a>
</nav>
</header>
<!-- 主内容区域,子模板填充此区块 -->
<main>
{% block content %}
<!-- 子模板的内容将插入此处 -->
{% endblock %}
</main>
<!-- 页面底部 -->
<footer>
<p>© 2024 智能租房平台</p>
</footer>
<!-- 公共JavaScript -->
<script src="{% static 'js/common.js' %}"></script>
<!-- 额外的JS区块,子模板可以追加自己的脚本 -->
{% block extra_js %}{% endblock %}
</body>
</html>
<!-- ========== templates/house/list.html (子模板) ========== -->
<!-- extends标签继承基础模板,必须放在第一行 -->
{% extends 'base.html' %}
{% load static %}
<!-- 覆盖 title 区块 -->
{% block title %}房源列表 - 智能租房平台{% endblock %}
<!-- 覆盖 content 区块 -->
{% block content %}
<div class="house-list">
<!-- for标签:遍历房源列表 -->
{% for house in houses %}
<div class="house-card">
<!-- 模板变量:输出房源标题 -->
<h3>{{ house.title }}</h3>
<!-- 过滤器:format 格式化数字,添加千分位分隔符 -->
<p class="rent">租金:{{ house.rent|floatformat:0 }}元/月</p>
<p class="area">面积:{{ house.area }}㎡</p>
<!-- url标签:反向解析URL -->
<a href="{% url 'house:detail' house.id %}">查看详情</a>
</div>
{% empty %}
<!-- for...empty标签:列表为空时显示 -->
<p class="no-data">暂无房源信息</p>
{% endfor %}
</div>
{% endblock %}
<!-- 追加额外的CSS -->
{% block extra_css %}
<link rel="stylesheet" href="{% static 'house/css/list.css' %}">
{% endblock %}
四、数据准备
知识点1:数据表设计
python
# ==================== 数据库表设计 (models.py) ====================
"""
智能租房平台核心数据表设计:
1. 区域表 (Area):存储城市区域信息
2. 房源表 (House):存储房源详细信息
3. 房东表 (Landlord):存储房东用户信息
4. 房源图片表 (HouseImage):存储房源相关图片
5. 设施表 (Facility):存储房源配套设施
"""
from django.db import models
# ==================== 1. 区域模型 ====================
class Area(models.Model):
"""
区域表:存储城市的区域划分信息
例如:朝阳区、海淀区、西城区等
"""
# 区域名称,CharField适用于短文本字段
# verbose_name:字段的可读名称,用于管理后台和表单显示
# max_length:最大字符长度(必填参数)
name = models.CharField('区域名称', max_length=50)
class Meta:
# 指定数据库表名
db_table = 'area'
# 指定默认排序方式(按名称升序)
ordering = ['name']
# 在管理后台中显示的表名(中文)
verbose_name = '区域'
# 复数形式名称
verbose_name_plural = '区域'
def __str__(self):
"""对象的字符串表示"""
return self.name
# ==================== 2. 房东模型 ====================
class Landlord(models.Model):
"""
房东表:存储房东的基本信息
"""
# ---- 性别选项 ----
# choices参数:限定字段可选值的元组列表
# 每个选项格式为 (数据库存储值, 显示值)
GENDER_CHOICES = (
('M', '男'), # 数据库存储 'M',显示为 '男'
('F', '女'), # 数据库存储 'F',显示为 '女'
)
# 房东姓名
name = models.CharField('姓名', max_length=50)
# 性别,使用 choices 限定选项
gender = models.CharField(
'性别',
max_length=1, # 只需1个字符
choices=GENDER_CHOICES, # 可选值列表
default='M' # 默认值为 'M'(男)
)
# 手机号
phone = models.CharField(
'手机号',
max_length=11, # 手机号固定11位
unique=True # 唯一约束,不允许重复
)
# 身份证号
id_card = models.CharField(
'身份证号',
max_length=18, # 身份证号固定18位
unique=True, # 唯一约束
blank=True, # 表单验证时允许为空
null=True # 数据库允许NULL值
)
# 头像,ImageField用于存储图片文件
# upload_to:图片上传的子目录路径
avatar = models.ImageField(
'头像',
upload_to='landlord/avatar/%Y/%m/', # 按年/月组织上传目录
blank=True, # 允许为空
default='landlord/avatar/default.png' # 默认头像路径
)
# 创建时间,auto_now_add=True 在对象首次创建时自动设置
create_time = models.DateTimeField('创建时间', auto_now_add=True)
# 更新时间,auto_now=True 在对象每次保存时自动更新
update_time = models.DateTimeField('更新时间', auto_now=True)
# 是否删除(逻辑删除标志),BooleanField存储布尔值
is_delete = models.BooleanField('是否删除', default=False)
class Meta:
db_table = 'landlord'
ordering = ['-create_time'] # 按创建时间降序排列
verbose_name = '房东'
verbose_name_plural = '房东'
def __str__(self):
return self.name
# ==================== 3. 房源模型 ====================
class House(models.Model):
"""
房源表:存储房源的完整信息
这是系统的核心表,与其他表存在多对一和多对多关系
"""
# ---- 房屋类型选项 ----
HOUSE_TYPE_CHOICES = (
('whole', '整租'), # 整套出租
('shared', '合租'), # 合租单间
)
# ---- 装修情况选项 ----
DECORATION_CHOICES = (
('rough', '毛坯'),
('simple', '简装'),
('luxury', '精装'),
)
# ---- 付款方式选项 ----
PAYMENT_CHOICES = (
('monthly', '月付'),
('quarterly', '季付'),
('yearly', '年付'),
)
# 房源标题
title = models.CharField('房源标题', max_length=200)
# 房屋类型(整租/合租)
house_type = models.CharField(
'房屋类型',
max_length=10,
choices=HOUSE_TYPE_CHOICES,
default='whole'
)
# ---- 外键字段:多对一关系(多个房源对应一个区域) ----
# ForeignKey参数说明:
# - 第一个参数:关联的模型类
# - on_delete:删除关联对象时的行为
# CASCADE:级联删除(删除区域时,该区域下的房源也删除)
# PROTECT:保护模式(有房源引用时不允许删除区域)
# SET_NULL:设为NULL(删除区域时,房源的area字段设为NULL)
# SET_DEFAULT:设为默认值
area = models.ForeignKey(
Area,
on_delete=models.PROTECT, # 保护模式:有房源引用时不允许删除
verbose_name='所在区域',
related_name='houses' # 反向查询名称:area.houses.all()
)
# ---- 外键字段:多对一关系(多个房源对应一个房东) ----
landlord = models.ForeignKey(
Landlord,
on_delete=models.CASCADE, # 级联删除:房东删除时房源也删除
verbose_name='房东',
related_name='houses' # 反向查询名称:landlord.houses.all()
)
# 详细地址
address = models.CharField('详细地址', max_length=300)
# 租金(使用DecimalField确保精度,避免浮点数计算误差)
rent = models.DecimalField(
'租金(元/月)',
max_digits=10, # 总位数(含小数部分)
decimal_places=2 # 小数位数
)
# 房屋面积(平方米)
area_size = models.DecimalField(
'面积(㎡)',
max_digits=8,
decimal_places=2
)
# 房间数(如:3室2厅1卫中的3)
room_count = models.PositiveSmallIntegerField('房间数', default=1)
# 楼层
floor = models.PositiveSmallIntegerField('楼层', default=1)
# 总楼层
total_floor = models.PositiveSmallIntegerField('总楼层', default=1)
# 装修情况
decoration = models.CharField(
'装修',
max_length=10,
choices=DECORATION_CHOICES,
default='simple'
)
# 付款方式
payment = models.CharField(
'付款方式',
max_length=10,
choices=PAYMENT_CHOICES,
default='monthly'
)
# 房源描述(大段文本)
description = models.TextField('房源描述', blank=True, default='')
# 是否有电梯
has_elevator = models.BooleanField('有电梯', default=False)
# 是否有暖气
has_heating = models.BooleanField('有暖气', default=True)
# 是否可养宠物
pet_allowed = models.BooleanField('可养宠物', default=False)
# 可入住日期
available_date = models.DateField('可入住日期', null=True, blank=True)
# ---- 多对多关系:房源与设施的多对多关系 ----
# ManyToManyField创建多对多关系,Django自动创建中间表
facilities = models.ManyToManyField(
'Facility', # 关联的模型(字符串形式可以解决循环引用)
verbose_name='配套设施',
blank=True, # 允许不选择任何设施
related_name='houses' # 反向查询名称
)
# 浏览次数(用于热门房源排序)
view_count = models.PositiveIntegerField('浏览次数', default=0)
# 是否已出租
is_rented = models.BooleanField('已出租', default=False)
# 发布时间
create_time = models.DateTimeField('发布时间', auto_now_add=True)
# 最后更新时间
update_time = models.DateTimeField('更新时间', auto_now=True)
# 逻辑删除标志
is_delete = models.BooleanField('是否删除', default=False)
class Meta:
db_table = 'house'
# 复合排序:未出租的优先,然后按发布时间降序
ordering = ['is_rented', '-create_time']
verbose_name = '房源'
verbose_name_plural = '房源'
def __str__(self):
return self.title
# ==================== 4. 设施模型 ====================
class Facility(models.Model):
"""
设施表:存储房源的配套设施信息
例如:空调、冰箱、洗衣机、宽带、热水器等
"""
# 设施名称
name = models.CharField('设施名称', max_length=50, unique=True)
# 设施图标(可选,使用图标类名或图片路径)
icon = models.CharField('图标', max_length=100, blank=True, default='')
# 排序权重(数值越小排序越靠前)
sort_order = models.IntegerField('排序', default=0)
class Meta:
db_table = 'facility'
ordering = ['sort_order']
verbose_name = '设施'
verbose_name_plural = '设施'
def __str__(self):
return self.name
# ==================== 5. 房源图片模型 ====================
class HouseImage(models.Model):
"""
房源图片表:存储房源的展示图片
一个房源可以有多张图片
"""
# 外键关联房源表
house = models.ForeignKey(
House,
on_delete=models.CASCADE, # 级联删除
verbose_name='所属房源',
related_name='images' # 反向查询:house.images.all()
)
# 图片文件字段
# upload_to指定上传路径(相对于MEDIA_ROOT)
image = models.ImageField(
'图片',
upload_to='house/image/%Y/%m/' # 按年/月组织上传目录
)
# 图片描述/标题
caption = models.CharField('图片说明', max_length=200, blank=True, default='')
# 是否为封面图片
is_cover = models.BooleanField('是否封面', default=False)
# 排序
sort_order = models.IntegerField('排序', default=0)
class Meta:
db_table = 'house_image'
ordering = ['sort_order']
verbose_name = '房源图片'
verbose_name_plural = '房源图片'
def __str__(self):
return f'{self.house.title} - 图片{self.id}'
# ==================== 6. 媒体文件配置 (settings.py) ====================
"""
# 媒体文件URL前缀
# 在模板中使用 {{ house_image.image.url }} 时生成的URL前缀
MEDIA_URL = '/media/'
# 媒体文件的存储根目录
# 用户上传的文件将保存在此目录下
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
"""
知识点2:Django 模型字段类型大全
python
# ==================== Django 常用模型字段类型 ====================
class FieldTypesDemo(models.Model):
"""演示Django各种模型字段类型"""
# ---- 1. 字符串字段 ----
# CharField:短字符串字段,必须指定max_length
char_field = models.CharField('字符', max_length=100)
# TextField:长文本字段,无需指定max_length
text_field = models.TextField('长文本')
# SlugField:URL友好的短标签,只允许字母、数字、连字符和下划线
slug_field = models.SlugField('URL标签', max_length=50, allow_unicode=True)
# EmailField:邮箱字段,自动验证邮箱格式
email_field = models.EmailField('邮箱', max_length=254)
# URLField:URL字段,自动验证URL格式
url_field = models.URLField('网址', max_length=200)
# ---- 2. 数值字段 ----
# IntegerField:整数字段(-2147483648 到 2147483647)
int_field = models.IntegerField('整数')
# SmallIntegerField:小整数(-32768 到 32767)
small_int_field = models.SmallIntegerField('小整数')
# PositiveIntegerField:正整数(0 到 2147483647)
positive_int_field = models.PositiveIntegerField('正整数')
# PositiveSmallIntegerField:小正整数(0 到 32767)
positive_small_int = models.PositiveSmallIntegerField('小正整数')
# BigIntegerField:大整数(-9223372036854775808 到 9223372036854775807)
big_int_field = models.BigIntegerField('大整数')
# FloatField:浮点数字段
float_field = models.FloatField('浮点数')
# DecimalField:精确小数(用于金额等需要精确计算的场景)
# max_digits:总位数,decimal_places:小数位数
decimal_field = models.DecimalField('精确小数', max_digits=10, decimal_places=2)
# ---- 3. 布尔字段 ----
# BooleanField:布尔字段(True/False)
bool_field = models.BooleanField('布尔值', default=False)
# NullBooleanField:三态布尔(True/False/None)- Django 3.1+建议用BooleanField(null=True)
nullable_bool = models.BooleanField('可空布尔', null=True)
# ---- 4. 日期时间字段 ----
# DateField:日期字段(仅日期,无时间)
date_field = models.DateField('日期', auto_now=False, auto_now_add=False)
# TimeField:时间字段(仅时间,无日期)
time_field = models.TimeField('时间')
# DateTimeField:日期时间字段(日期+时间)
datetime_field = models.DateTimeField('日期时间', auto_now=False, auto_now_add=False)
# auto_now=True:每次保存对象时自动更新为当前时间
update_time = models.DateTimeField('更新时间', auto_now=True)
# auto_now_add=True:仅在创建对象时自动设置为当前时间
create_time = models.DateTimeField('创建时间', auto_now_add=True)
# DurationField:时间段字段,底层使用Python的timedelta
duration_field = models.DurationField('时间段')
# ---- 5. 文件字段 ----
# FileField:通用文件上传字段
file_field = models.FileField(
'文件',
upload_to='uploads/%Y/%m/', # 上传路径(相对于MEDIA_ROOT)
)
# ImageField:图片上传字段(需要安装Pillow库)
image_field = models.ImageField(
'图片',
upload_to='images/%Y/%m/',
)
# ---- 6. 关系字段 ----
# ForeignKey:外键(多对一关系)
# related_name:反向查询的属性名
foreign_key = models.ForeignKey(
Area,
on_delete=models.CASCADE,
related_name='demo_items'
)
# ManyToManyField:多对多关系
# Django会自动创建中间关系表
many_to_many = models.ManyToManyField(
Facility,
related_name='demo_items',
blank=True
)
# OneToOneField:一对一关系(类似ForeignKey + unique=True)
one_to_one = models.OneToOneField(
Landlord,
on_delete=models.CASCADE,
related_name='profile'
)
# ---- 7. 其他字段 ----
# UUIDField:通用唯一标识符字段
import uuid
uuid_field = models.UUIDField(
'UUID',
default=uuid.uuid4, # 默认生成随机UUID
editable=False, # 不允许在表单中编辑
unique=True # 唯一约束
)
# IPAddressField:IP地址字段(IPv4)
ip_field = models.GenericIPAddressField(
'IP地址',
protocol='both', # 支持IPv4和IPv6
null=True,
blank=True
)
# JSONField:JSON数据字段(Django 3.1+所有数据库均支持)
json_field = models.JSONField('JSON数据', default=dict, blank=True)
class Meta:
db_table = 'field_types_demo'
知识点3:模型 Meta 选项详解
python
# ==================== 模型 Meta 内部类详解 ====================
class House(models.Model):
title = models.CharField('标题', max_length=200)
rent = models.DecimalField('租金', max_digits=10, decimal_places=2)
area = models.ForeignKey(Area, on_delete=models.PROTECT, verbose_name='区域')
create_time = models.DateTimeField('创建时间', auto_now_add=True)
is_rented = models.BooleanField('已出租', default=False)
class Meta:
# ---- 数据库表名 ----
# 默认表名格式为:应用名_模型名(小写),如 house_house
# 可通过db_table自定义
db_table = 'house'
# ---- 排序 ----
# 默认排序字段,前面加"-"表示降序
# 可指定多个字段进行复合排序
ordering = ['is_rented', '-create_time']
# ---- 唯一约束 ----
# 联合唯一约束:确保多个字段的组合值唯一
unique_together = [('title', 'area')] # 同一区域下标题不能重复
# ---- 索引 ----
# 自定义索引,提升查询性能
indexes = [
# 单字段索引:加速按租金查询
models.Index(fields=['rent'], name='idx_rent'),
# 复合索引:加速按区域和租金的联合查询
models.Index(fields=['area', 'rent'], name='idx_area_rent'),
# 带条件的索引(仅索引未出租的房源)
models.Index(
fields=['create_time'],
condition=models.Q(is_rented=False),
name='idx_available_time'
),
]
# ---- 约束 ----
# 数据库级别的约束
constraints = [
# 检查约束:确保租金大于0
models.CheckConstraint(
check=models.Q(rent__gt=0),
name='rent_must_be_positive'
),
]
# ---- 显示名称 ----
verbose_name = '房源' # 单数形式名称
verbose_name_plural = '房源' # 复数形式名称(中文通常相同)
# ---- 抽象模型 ----
# abstract = True 表示这是一个抽象基类,不会创建数据库表
# abstract = False (默认)会创建数据库表
abstract = False
# ---- 可管理性 ----
# managed = False 表示Django不管理此表的创建和修改
# 适用于映射已存在的数据库表
managed = True
# ---- 默认排序 ----
# get_latest_by 指定用于 latest() 和 earliest() 方法的字段
get_latest_by = 'create_time'
知识点4:导入数据
python
# ==================== 方式一:使用 Django Shell 导入数据 ====================
"""
在终端中执行:
python manage.py shell
然后在交互式Shell中执行以下代码:
"""
# 导入模型类
from house.models import Area, Landlord, Facility, House, HouseImage
# ---- 1. 导入区域数据 ----
# bulk_create:批量创建,比逐条创建效率高得多
area_list = [
Area(name='朝阳区'),
Area(name='海淀区'),
Area(name='西城区'),
Area(name='东城区'),
Area(name='丰台区'),
Area(name='通州区'),
Area(name='大兴区'),
Area(name='昌平区'),
]
# bulk_create一次性将所有对象插入数据库,减少数据库交互次数
Area.objects.bulk_create(area_list)
print(f'成功导入 {len(area_list)} 个区域')
# ---- 2. 导入设施数据 ----
facility_data = [
{'name': '空调', 'icon': 'icon-ac', 'sort_order': 1},
{'name': '冰箱', 'icon': 'icon-fridge', 'sort_order': 2},
{'name': '洗衣机', 'icon': 'icon-washer', 'sort_order': 3},
{'name': '热水器', 'icon': 'icon-heater', 'sort_order': 4},
{'name': '宽带', 'icon': 'icon-wifi', 'sort_order': 5},
{'name': '天然气', 'icon': 'icon-gas', 'sort_order': 6},
{'name': '电视', 'icon': 'icon-tv', 'sort_order': 7},
{'name': '微波炉', 'icon': 'icon-microwave', 'sort_order': 8},
]
# 使用列表推导式创建模型对象列表
facility_list = [Facility(**item) for item in facility_data]
# **item 将字典解包为关键字参数,等价于:
# Facility(name='空调', icon='icon-ac', sort_order=1)
Facility.objects.bulk_create(facility_list)
print(f'成功导入 {len(facility_list)} 个设施')
# ---- 3. 导入房东数据 ----
landlord_list = [
Landlord(name='张三', gender='M', phone='13800000001', id_card='110105199001011234'),
Landlord(name='李四', gender='M', phone='13800000002', id_card='110105199002021234'),
Landlord(name='王芳', gender='F', phone='13800000003', id_card='110105199003031234'),
]
Landlord.objects.bulk_create(landlord_list)
print(f'成功导入 {len(landlord_list)} 个房东')
# ---- 4. 导入房源数据 ----
# 先获取已创建的区域和房东对象(通过名称查询)
chaoyang = Area.objects.get(name='朝阳区')
haidian = Area.objects.get(name='海淀区')
zhangsan = Landlord.objects.get(name='张三')
lisi = Landlord.objects.get(name='李四')
house1 = House.objects.create(
title='阳光花园精装修3室2厅', # 房源标题
house_type='whole', # 整租
area=chaoyang, # 外键关联:朝阳区
landlord=zhangsan, # 外键关联:张三
address='朝阳区建国路88号阳光花园小区', # 详细地址
rent=6500.00, # 月租金
area_size=120.50, # 面积
room_count=3, # 3室
floor=12, # 第12层
total_floor=28, # 总共28层
decoration='luxury', # 精装修
payment='quarterly', # 季付
description='精装修三居室,南北通透,采光好,交通便利,紧邻地铁站。',
has_elevator=True,
has_heating=True,
pet_allowed=False,
is_rented=False,
)
house2 = House.objects.create(
title='海淀中关村合租单间',
house_type='shared',
area=haidian,
landlord=lisi,
address='海淀区中关村大街100号',
rent=3200.00,
area_size=18.00,
room_count=1,
floor=5,
total_floor=6,
decoration='simple',
payment='monthly',
description='合租单间,近中关村地铁站,周边配套齐全。',
has_elevator=False,
has_heating=True,
pet_allowed=False,
is_rented=False,
)
# ---- 5. 设置多对多关系(房源与设施) ----
# 获取已创建的设施对象
ac = Facility.objects.get(name='空调')
wifi = Facility.objects.get(name='宽带')
washer = Facility.objects.get(name='洗衣机')
# add() 方法:向多对多关系中添加关联对象
# 可以一次添加多个对象
house1.facilities.add(ac, wifi, washer)
# 也可以使用列表添加
facilities_for_house2 = Facility.objects.filter(name__in=['空调', '宽带', '热水器'])
house2.facilities.set(facilities_for_house2) # set() 设置完整的关联集合
print('房源数据导入完成!')
python
# ==================== 方式二:使用 Django 管理命令导入数据 ====================
# house/management/commands/import_data.py
"""
目录结构:
house/
├── management/
│ ├── __init__.py
│ └── commands/
│ ├── __init__.py
│ └── import_data.py
"""
import json
from django.core.management.base import BaseCommand
from house.models import Area, Landlord, Facility, House
class Command(BaseCommand):
"""
自定义管理命令:python manage.py import_data
"""
# 命令的帮助说明(显示在 python manage.py help 中)
help = '导入智能租房平台的初始数据'
def add_arguments(self, parser):
"""
添加命令行参数
parser: argparse.ArgumentParser实例
"""
# 添加可选参数 --file,用于指定数据文件路径
parser.add_argument(
'--file', # 参数名称
type=str, # 参数类型
default='data.json', # 默认值
help='数据文件路径(JSON格式)' # 帮助说明
)
def handle(self, *args, **options):
"""
命令的主处理逻辑
*args: 位置参数
**options: 关键字参数(包含命令行参数的值)
"""
# 获取命令行参数值
file_path = options['file']
# 输出带样式的提示信息
# self.stdout.write() 输出到标准输出
# self.style.SUCCESS 使用绿色成功样式
self.stdout.write(self.style.SUCCESS(f'开始导入数据,数据文件:{file_path}'))
try:
# 打开并读取JSON数据文件
# encoding='utf-8' 确保中文正确读取
with open(file_path, 'r', encoding='utf-8') as f:
# json.load() 将JSON文件内容解析为Python对象
data = json.load(f)
# 导入区域数据
areas = data.get('areas', []) # 获取区域列表,不存在则返回空列表
for item in areas:
# get_or_create:如果对象已存在则获取,不存在则创建
# 返回值为 (object, created) 元组
# object: 模型对象实例
# created: 布尔值,True表示新创建,False表示已存在
area, created = Area.objects.get_or_create(
name=item['name'], # 查询条件
defaults={} # 创建时的额外字段值
)
if created:
self.stdout.write(f' 创建区域:{area.name}')
# 输出导入完成信息
self.stdout.write(self.style.SUCCESS('数据导入完成!'))
except FileNotFoundError:
# 文件不存在时输出错误信息
# self.style.ERROR 使用红色错误样式
self.stdout.write(self.style.ERROR(f'数据文件不存在:{file_path}'))
except json.JSONDecodeError as e:
# JSON格式错误时输出错误信息
self.stdout.write(self.style.ERROR(f'JSON格式错误:{e}'))
bash
# 执行自定义管理命令
python manage.py import_data --file=data/house_data.json
python
# ==================== 方式三:使用 Django 序列化工具导入数据 ====================
# ---- 导出数据(序列化) ----
from django.core import serializers
from house.models import House
# 将QuerySet序列化为JSON格式
# serialize(格式, QuerySet对象, indent=缩进空格数)
json_data = serializers.serialize('json', House.objects.all(), indent=2, ensure_ascii=False)
# 保存到文件
with open('houses.json', 'w', encoding='utf-8') as f:
f.write(json_data)
# ---- 导入数据(反序列化) ----
from django.core import serializers
# 从文件中读取JSON数据
with open('houses.json', 'r', encoding='utf-8') as f:
json_data = f.read()
# 反序列化:将JSON数据转换为Django对象
# deserialize() 返回一个迭代器,每个元素包含 .object 和 .deleted_object
for deserialized_object in serializers.deserialize('json', json_data):
# deserialized_object.object 是反序列化后的模型实例
# save() 方法将对象保存到数据库
deserialized_object.save()
print(f'导入成功:{deserialized_object.object}')
python
# ==================== 方式四:使用CSV文件导入数据 ====================
"""
使用Python内置的csv模块读取CSV文件并导入数据库
适用场景:从Excel导出的CSV文件导入数据
"""
import csv
from house.models import Area, House, Landlord
from decimal import Decimal # 导入Decimal用于精确小数运算
def import_from_csv(csv_file_path):
"""
从CSV文件导入房源数据
参数:
csv_file_path: CSV文件的路径
"""
# 计数器:记录成功导入的数据条数
success_count = 0
# 计数器:记录失败的数据条数
error_count = 0
# 打开CSV文件
# newline='' 防止在Windows系统上出现多余的空行
# encoding='utf-8-sig' 处理带BOM标记的UTF-8文件(如Excel导出的CSV)
with open(csv_file_path, 'r', encoding='utf-8-sig') as f:
# DictReader将CSV文件的每一行读取为字典
# 字典的键为CSV文件的第一行(表头)
reader = csv.DictReader(f)
for row in reader:
# row 是一个字典,例如:
# {'title': '阳光花园', 'area_name': '朝阳区', 'rent': '6500', ...}
try:
# 根据区域名称查找区域对象
area = Area.objects.get(name=row['area_name'])
# 根据房东姓名查找房东对象
landlord = Landlord.objects.get(name=row['landlord_name'])
# 创建房源对象
House.objects.create(
title=row['title'],
area=area,
landlord=landlord,
address=row['address'],
# Decimal() 将字符串转换为精确小数
rent=Decimal(row['rent']),
area_size=Decimal(row['area_size']),
room_count=int(row['room_count']), # int() 将字符串转为整数
floor=int(row['floor']),
total_floor=int(row['total_floor']),
)
success_count += 1
except Area.DoesNotExist:
# 区域不存在的异常处理
print(f'区域不存在:{row["area_name"]},跳过此条数据')
error_count += 1
except Landlord.DoesNotExist:
# 房东不存在的异常处理
print(f'房东不存在:{row["landlord_name"]},跳过此条数据')
error_count += 1
except (ValueError, KeyError) as e:
# 数据格式错误或缺少必要字段的异常处理
print(f'数据错误:{e},跳过此条数据')
error_count += 1
print(f'导入完成:成功 {success_count} 条,失败 {error_count} 条')
# 调用函数
# import_from_csv('data/houses.csv')
知识点5:创建模型类------模型方法详解
python
# ==================== 模型自定义方法 ====================
from django.db import models
from django.urls import reverse
class House(models.Model):
title = models.CharField('房源标题', max_length=200)
rent = models.DecimalField('租金', max_digits=10, decimal_places=2)
area_size = models.DecimalField('面积', max_digits=8, decimal_places=2)
room_count = models.PositiveSmallIntegerField('房间数', default=1)
floor = models.PositiveSmallIntegerField('楼层', default=1)
total_floor = models.PositiveSmallIntegerField('总楼层', default=1)
is_rented = models.BooleanField('已出租', default=False)
view_count = models.PositiveIntegerField('浏览次数', default=0)
area = models.ForeignKey('Area', on_delete=models.PROTECT, verbose_name='区域')
create_time = models.DateTimeField('发布时间', auto_now_add=True)
class Meta:
db_table = 'house'
verbose_name = '房源'
verbose_name_plural = '房源'
# ---- 1. __str__ 方法 ----
# 对象的字符串表示,用于admin后台、print()、交互式Shell等场景
def __str__(self):
return self.title
# ---- 2. 自定义业务方法 ----
# 计算每平方米的单价
def get_price_per_sqm(self):
"""
计算每平方米的租金单价
使用 Decimal 确保精度,避免浮点数误差
返回:四舍五入到两位小数的单价
"""
if self.area_size and self.area_size > 0:
# Decimal 类型的除法,结果仍然是 Decimal
price = self.rent / self.area_size
# quantize:四舍五入到指定精度
from decimal import Decimal, ROUND_HALF_UP
return price.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
# 面积为0或None时返回0
return 0
# ---- 3. 获取绝对URL ----
# Django约定方法,用于获取对象的唯一URL
# 在模板中可通过 {{ house.get_absolute_url }} 调用
def get_absolute_url(self):
# reverse():反向解析URL
# 'house:detail':URL名称(应用名:URL名称)
# kwargs={'pk': self.id}:传递URL参数
return reverse('house:detail', kwargs={'pk': self.id})
# ---- 4. 增加浏览次数 ----
def increase_view_count(self):
"""
原子操作:使用F表达式实现数据库级别的字段自增
比 self.view_count += 1 更安全,避免并发竞争问题
"""
from django.db.models import F
# F('view_count') 表示数据库中的 view_count 字段
# update() 直接在数据库中执行SQL更新,无需加载整个对象
House.objects.filter(pk=self.pk).update(view_count=F('view_count') + 1)
# 刷新当前对象的字段值
self.refresh_from_db()
# ---- 5. 类方法(类似静态方法,通过类名调用) ----
@classmethod
def get_available_houses(cls):
"""
获取所有可租房源
@classmethod 修饰器使得该方法可以通过类名直接调用:
House.get_available_houses()
cls 参数指向当前类(House)
"""
# filter() 方法返回符合条件的QuerySet
return cls.objects.filter(is_rented=False, is_delete=False)
@classmethod
def get_hot_houses(cls, limit=10):
"""
获取热门房源(按浏览次数排序)
参数:
limit: 返回的房源数量,默认10条
"""
# order_by('-view_count') 按浏览次数降序排列
# [:limit] 对QuerySet进行切片,限制返回数量
# 注意:不能对已切片的QuerySet再次切片或过滤
return cls.objects.filter(
is_rented=False, # 未出租
is_delete=False # 未删除
).order_by('-view_count')[:limit]
# ---- 6. 属性方法 ----
# 使用 @property 装饰器将方法变为属性,可直接用 house.display_title 访问
@property
def display_title(self):
"""
生成显示用的标题,如果已出租则加上标识
"""
if self.is_rented:
return f'[已租] {self.title}'
return self.title
@property
def is_hot(self):
"""判断是否为热门房源(浏览次数超过100)"""
return self.view_count > 100
@property
def floor_info(self):
"""格式化楼层信息"""
return f'{self.floor}/{self.total_floor}层'
知识点6:数据库迁移
python
# ==================== 数据库迁移操作 ====================
"""
数据库迁移是Django管理数据库模式变更的机制。
通过迁移文件,Django可以自动创建、修改数据库表结构。
"""
# ---- 步骤1:生成迁移文件 ----
# 在终端中执行以下命令:
# python manage.py makemigrations [应用名]
# 执行后在 house/migrations/ 目录下生成迁移文件,例如:
# house/migrations/0001_initial.py
"""
迁移文件内容示例(Django自动生成):
"""
# house/migrations/0001_initial.py
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
"""
初始迁移:创建所有数据表
"""
# 初始迁移标记
initial = True
# 依赖的其他迁移(确保按正确顺序执行)
dependencies = [
]
# 迁移操作列表
operations = [
# CreateModel:创建新数据表
migrations.CreateModel(
name='Area',
fields=[
# AutoField:自增主键字段
('id', models.BigAutoField(
auto_created=True, # 自动创建
primary_key=True, # 设为主键
serialize=False, # 不序列化
verbose_name='ID' # 显示名称
)),
('name', models.CharField(max_length=50, verbose_name='区域名称')),
],
options={
'verbose_name': '区域',
'verbose_name_plural': '区域',
'db_table': 'area',
'ordering': ['name'],
},
),
# CreateModel:创建房东表
migrations.CreateModel(
name='Landlord',
fields=[
('id', models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID'
)),
('name', models.CharField(max_length=50, verbose_name='姓名')),
('gender', models.CharField(
choices=[('M', '男'), ('F', '女')],
default='M',
max_length=1,
verbose_name='性别'
)),
('phone', models.CharField(max_length=11, unique=True, verbose_name='手机号')),
('id_card', models.CharField(
blank=True, max_length=18, null=True, unique=True, verbose_name='身份证号'
)),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('is_delete', models.BooleanField(default=False, verbose_name='是否删除')),
],
options={
'verbose_name': '房东',
'verbose_name_plural': '房东',
'db_table': 'landlord',
},
),
# CreateModel:创建房源表(含外键字段)
migrations.CreateModel(
name='House',
fields=[
('id', models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID'
)),
('title', models.CharField(max_length=200, verbose_name='房源标题')),
('rent', models.DecimalField(
decimal_places=2, max_digits=10, verbose_name='租金(元/月)'
)),
# ForeignKey字段:外键关联
('area', models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, # 保护模式
to='house.area', # 关联的目标表
verbose_name='所在区域'
)),
('landlord', models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to='house.landlord',
verbose_name='房东'
)),
],
),
]
# ---- 步骤2:执行迁移 ----
# 在终端中执行以下命令:
# python manage.py migrate [应用名]
# 此命令会执行所有未执行的迁移文件,创建/修改数据库表
bash
# ==================== 常用迁移管理命令 ====================
# 查看所有迁移文件及其执行状态
python manage.py showmigrations
# 查看某个迁移文件对应的SQL语句(不实际执行)
# 用于检查Django将要执行什么SQL操作
python manage.py sqlmigrate house 0001
# 回滚迁移:撤销指定迁移文件的变更
# 将数据库表结构恢复到该迁移之前的状态
python manage.py migrate house 0001
# 回滚到初始状态(删除所有表)
python manage.py migrate house zero
# 创建空的迁移文件(用于手动编写数据迁移)
python manage.py makemigrations --empty house
# 检查模型与迁移文件是否一致(不修改任何内容)
python manage.py makemigrations --check --dry-run
知识点7:QuerySet 核心查询 API
python
# ==================== QuerySet 查询操作详解 ====================
from house.models import House, Area, Landlord
from django.db.models import Q, F, Count, Avg, Sum, Max, Min
from django.db.models.functions import Concat
from django.db.models import Value
# ==================== 1. 基础查询 ====================
# 获取所有记录
# SELECT * FROM house;
all_houses = House.objects.all()
# 获取单条记录
# 主键查询:SELECT * FROM house WHERE id = 1;
house = House.objects.get(pk=1) # pk 是 primary_key 的简写
# filter() 过滤查询:返回满足条件的QuerySet
# SELECT * FROM house WHERE is_rented = 0 AND is_delete = 0;
available = House.objects.filter(is_rented=False, is_delete=False)
# exclude() 排除查询:返回不满足条件的QuerySet
# SELECT * FROM house WHERE is_rented != 1;
not_rented = House.objects.exclude(is_rented=True)
# count() 计数
# SELECT COUNT(*) FROM house WHERE is_rented = 0;
count = House.objects.filter(is_rented=False).count()
# exists() 判断是否存在
# SELECT 1 FROM house WHERE rent < 2000 LIMIT 1;
has_cheap = House.objects.filter(rent__lt=2000).exists()
# first() / last() 获取第一条/最后一条记录
first_house = House.objects.first() # ORDER BY id ASC LIMIT 1
last_house = House.objects.last() # ORDER BY id DESC LIMIT 1
# ==================== 2. 字段查找(双下划线语法) ====================
# ---- 精确匹配 ----
# exact / 精确匹配(等价于 = )
houses = House.objects.filter(rent__exact=3000) # WHERE rent = 3000
houses = House.objects.filter(rent=3000) # 简写形式,等价于上面
# iexact:不区分大小写的精确匹配
houses = House.objects.filter(title__iexact='sunny garden')
# ---- 模糊匹配 ----
# contains:包含(区分大小写)--- LIKE BINARY
houses = House.objects.filter(title__contains='花园') # WHERE title LIKE BINARY '%花园%'
# icontains:包含(不区分大小写)--- LIKE
houses = House.objects.filter(title__icontains='garden') # WHERE title LIKE '%garden%'
# startswith / istartswith:以...开头
houses = House.objects.filter(title__startswith='阳光') # WHERE title LIKE BINARY '阳光%'
# endswith / iendswith:以...结尾
houses = House.objects.filter(title__endswith='厅') # WHERE title LIKE BINARY '%厅'
# ---- 比较查询 ----
# gt:大于 (>) gte:大于等于 (>=)
houses = House.objects.filter(rent__gt=5000) # WHERE rent > 5000
houses = House.objects.filter(rent__gte=3000) # WHERE rent >= 3000
# lt:小于 (<) lte:小于等于 (<=)
houses = House.objects.filter(rent__lt=2000) # WHERE rent < 2000
houses = House.objects.filter(rent__lte=5000) # WHERE rent <= 5000
# ---- 范围查询 ----
# range:闭区间 [a, b]
houses = House.objects.filter(rent__range=(2000, 5000)) # WHERE rent BETWEEN 2000 AND 5000
# ---- 集合查询 ----
# in:在列表中
houses = House.objects.filter(rent__in=[2000, 3000, 5000]) # WHERE rent IN (2000, 3000, 5000)
# ---- 空值查询 ----
# isnull:判断是否为NULL
houses = House.objects.filter(description__isnull=True) # WHERE description IS NULL
houses = House.objects.filter(description__isnull=False) # WHERE description IS NOT NULL
# ---- 日期查询 ----
# year / month / day:按日期部分查询
houses = House.objects.filter(create_time__year=2024) # WHERE YEAR(create_time) = 2024
houses = House.objects.filter(create_time__month=6) # WHERE MONTH(create_time) = 6
# date:精确日期匹配
from datetime import date
houses = House.objects.filter(create_time__date=date(2024, 6, 1))
# 时间范围查询
from datetime import datetime
start = datetime(2024, 1, 1)
end = datetime(2024, 12, 31, 23, 59, 59)
houses = House.objects.filter(create_time__range=(start, end))
# ==================== 3. Q对象(复杂条件查询) ====================
"""
Q对象用于构建复杂的查询条件,支持 &(AND)、|(OR)、~(NOT)逻辑运算。
"""
# OR 查询:租金低于2000 OR 面积大于100
# Django的filter()默认是AND关系,要实现OR必须使用Q对象
houses = House.objects.filter(
Q(rent__lt=2000) | Q(area_size__gt=100)
)
# SQL: WHERE rent < 2000 OR area_size > 100
# AND 查询(等价于普通filter,但展示Q的用法)
houses = House.objects.filter(
Q(rent__gte=2000) & Q(rent__lte=5000)
)
# SQL: WHERE rent >= 2000 AND rent <= 5000
# NOT 查询:排除已出租的房源
houses = House.objects.filter(~Q(is_rented=True))
# SQL: WHERE NOT (is_rented = 1)
# 复杂组合查询:(租金<3000 且 面积>50)或 有电梯
houses = House.objects.filter(
(Q(rent__lt=3000) & Q(area_size__gt=50)) | Q(has_elevator=True)
)
# SQL: WHERE (rent < 3000 AND area_size > 50) OR has_elevator = 1
# Q对象可以存储为变量,方便动态构建查询条件
q_condition = Q()
# 假设用户提交的搜索条件
keyword = '花园'
min_rent = 2000
max_rent = 8000
area_name = '朝阳区'
if keyword:
# &= 相当于 q_condition = q_condition & Q(...)
q_condition &= Q(title__icontains=keyword)
if min_rent:
q_condition &= Q(rent__gte=min_rent)
if max_rent:
q_condition &= Q(rent__lte=max_rent)
if area_name:
q_condition &= Q(area__name=area_name)
# 只有在有搜索条件时才进行过滤
if q_condition:
results = House.objects.filter(q_condition)
else:
results = House.objects.all()
# ==================== 4. F对象(字段间比较) ====================
"""
F对象用于引用模型字段的值,实现字段之间的比较和计算。
"""
# 字段间比较:楼层大于总楼层的一半(高层房源)
from django.db.models import F
houses = House.objects.filter(floor__gt=F('total_floor') / 2)
# 字段运算:浏览次数加1(原子操作,线程安全)
House.objects.filter(pk=1).update(view_count=F('view_count') + 1)
# 跨表字段引用:房源面积大于其所在区域平均面积的房源
from django.db.models import Avg
avg_size = House.objects.aggregate(avg=Avg('area_size'))['avg']
houses = House.objects.filter(area_size__gt=avg_size)
# ==================== 5. 聚合查询 ====================
"""
聚合函数:对QuerySet进行统计计算,返回字典而非QuerySet。
"""
from django.db.models import Count, Avg, Sum, Max, Min
# aggregate():对整个QuerySet进行聚合
stats = House.objects.aggregate(
total_count=Count('id'), # 总房源数
total_rent=Sum('rent'), # 租金总和
avg_rent=Avg('rent'), # 平均租金
max_rent=Max('rent'), # 最高租金
min_rent=Min('rent'), # 最低租金
avg_area=Avg('area_size'), # 平均面积
)
print(stats)
# 输出示例:
# {'total_count': 150, 'total_rent': Decimal('450000'), 'avg_rent': Decimal('3000.00'),
# 'max_rent': Decimal('15000'), 'min_rent': Decimal('800'), 'avg_area': Decimal('65.50')}
# annotate():为每个对象添加聚合字段(分组统计)
# 统计每个区域的房源数量和平均租金
area_stats = Area.objects.annotate(
house_count=Count('houses'), # 统计关联的房源数量
avg_rent=Avg('houses__rent'), # 计算关联房源的平均租金
total_rent=Sum('houses__rent'), # 计算关联房源的租金总和
).order_by('-house_count') # 按房源数量降序排列
for area in area_stats:
print(f'{area.name}: {area.house_count}套, 均价{area.avg_rent}元')
# ==================== 6. 排序 ====================
# order_by():指定排序字段
# '-' 前缀表示降序
houses = House.objects.order_by('-create_time') # 按发布时间降序
houses = House.objects.order_by('rent', '-area_size') # 先按租金升序,再按面积降序
# 取消排序
houses = House.objects.order_by() # 清除默认排序
# 使用关联表字段排序
houses = House.objects.order_by('area__name', '-rent') # 按区域名称升序,租金降序
# ==================== 7. 切片 ====================
# 限制返回数量(LIMIT)
houses = House.objects.all()[:10] # LIMIT 10(前10条)
# 偏移量(OFFSET + LIMIT)
houses = House.objects.all()[10:20] # LIMIT 10 OFFSET 10(第11到20条)
# 注意:
# 1. 切片后的QuerySet不能再调用filter()、exclude()等方法
# 2. 不支持负索引(如 [-1])
# 3. step(步长)参数不被支持
# ==================== 8. 关联查询 ====================
# ---- 正向查询(通过外键字段) ----
house = House.objects.get(pk=1)
area_name = house.area.name # 通过外键访问关联的区域名称
landlord_name = house.landlord.name # 通过外键访问关联的房东名称
# 使用双下划线跨表查询(JOIN查询)
# 查询朝阳区的所有房源
houses = House.objects.filter(area__name='朝阳区')
# SQL: SELECT house.* FROM house
# INNER JOIN area ON house.area_id = area.id
# WHERE area.name = '朝阳区'
# 查询姓张的房东的所有房源
houses = House.objects.filter(landlord__name__startswith='张')
# ---- 反向查询(通过 related_name) ----
area = Area.objects.get(pk=1)
# 使用 related_name='houses' 进行反向查询
area_houses = area.houses.all() # 获取该区域的所有房源
area_count = area.houses.count() # 获取该区域的房源数量
# 也可以使用默认的反向名称:模型名小写_set
area_houses = area.house_set.all() # 如果没有设置related_name
# ==================== 9. select_related 与 prefetch_related ====================
"""
优化关联查询,避免 N+1 查询问题。
"""
# ---- select_related:用于 ForeignKey / OneToOneField ----
# 不使用优化:会产生 N+1 查询
houses = House.objects.all()[:10]
for house in houses:
# 每次循环都会查询一次数据库获取区域信息
print(house.area.name) # 共执行 1 + 10 = 11 次SQL查询
# 使用 select_related:仅1次查询(JOIN查询)
houses = House.objects.select_related('area', 'landlord').all()[:10]
for house in houses:
# 已经在第一次查询中通过JOIN获取了area和landlord的信息,不再额外查询
print(house.area.name)
# SQL: SELECT house.*, area.*, landlord.*
# FROM house
# INNER JOIN area ON house.area_id = area.id
# INNER JOIN landlord ON house.landlord_id = landlord.id
# LIMIT 10
# ---- prefetch_related:用于 ManyToManyField / 反向ForeignKey ----
# 使用 prefetch_related:2次查询(先查主表,再查关联表)
houses = House.objects.prefetch_related('facilities').all()
for house in houses:
# 已经预取了设施信息,不会额外查询
for facility in house.facilities.all():
print(facility.name)
# SQL 1: SELECT * FROM house
# SQL 2: SELECT * FROM facility
# INNER JOIN house_facility ON facility.id = house_facility.facility_id
# WHERE house_facility.house_id IN (1, 2, 3, ...)
# ==================== 10. values 与 values_list ====================
# values():返回字典列表(指定字段)
houses = House.objects.values('title', 'rent')
# <QuerySet [{'title': '阳光花园', 'rent': Decimal('3000')}, ...]>
# values_list():返回元组列表
houses = House.objects.values_list('title', 'rent')
# <QuerySet [('阳光花园', Decimal('3000')), ...]>
# flat=True:单字段时返回扁平列表(而非元组列表)
titles = House.objects.values_list('title', flat=True)
# <QuerySet ['阳光花园', '中关村小区', ...]>
# named=True:返回命名元组,可以通过属性名访问字段
houses = House.objects.values_list('title', 'rent', named=True)
for house in houses:
print(house.title, house.rent) # 可以通过属性名访问
# ==================== 11. 数据操作 ====================
# ---- 创建对象 ----
# 方式一:create() 一步完成创建和保存
house = House.objects.create(
title='新上架房源',
rent=3000,
area_id=1, # 直接使用外键ID
landlord_id=1,
)
# 方式二:先实例化,再save()
house = House(title='新房源', rent=4000, area_id=1, landlord_id=1)
house.save() # 必须调用save()才能写入数据库
# ---- 更新对象 ----
# 方式一:获取对象后修改属性,再save()
house = House.objects.get(pk=1)
house.rent = 3500 # 修改租金
house.is_rented = True # 标记为已出租
house.save() # 保存修改到数据库
# 方式二:update() 批量更新(不触发save信号,效率更高)
# UPDATE house SET is_rented = 1 WHERE area_id = 1;
House.objects.filter(area_id=1).update(is_rented=True)
# 使用F表达式原子更新
House.objects.filter(pk=1).update(view_count=F('view_count') + 1)
# ---- 删除对象 ----
# 物理删除(从数据库中彻底删除)
house = House.objects.get(pk=1)
house.delete() # 删除单条记录
# 批量删除
House.objects.filter(is_rented=True).delete()
# 逻辑删除(推荐,数据不真正删除,仅标记)
House.objects.filter(pk=1).update(is_delete=True)
# ---- get_or_create ----
# 如果对象存在则获取,不存在则创建
# 返回 (object, created) 元组
house, created = House.objects.get_or_create(
title='特定房源', # 查询条件
defaults={ # 创建时的额外参数
'rent': 5000,
'area_id': 1,
'landlord_id': 1,
}
)
print(f'是否新创建:{created}')
# ---- update_or_create ----
# 如果对象存在则更新,不存在则创建
house, created = House.objects.update_or_create(
title='特定房源', # 查询条件
defaults={ # 创建或更新的字段值
'rent': 6000, # 如果已存在,则更新rent为6000
}
)
五、项目完整配置汇总
python
# ==================== settings.py 完整配置文件 ====================
"""
Django settings for rent_project project.
智能租房平台 - 项目配置文件
Generated by 'django-admin startproject' using Django 4.2.
"""
import os
from pathlib import Path
# ==================== 基础路径 ====================
# 项目根目录
BASE_DIR = Path(__file__).resolve().parent.parent
# ==================== 安全配置 ====================
# 生产环境中应从环境变量读取:os.environ.get('DJANGO_SECRET_KEY')
SECRET_KEY = 'django-insecure-change-this-in-production'
DEBUG = True
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
# ==================== 应用注册 ====================
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 自定义应用
'house.apps.HouseConfig',
]
# ==================== 中间件 ====================
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# ==================== URL配置 ====================
ROOT_URLCONF = 'rent_project.urls'
# ==================== 模板配置 ====================
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# ==================== WSGI ====================
WSGI_APPLICATION = 'rent_project.wsgi.application'
# ==================== 数据库配置 ====================
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# ==================== 密码验证 ====================
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]
# ==================== 国际化 ====================
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_TZ = True
# ==================== 静态文件 ====================
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
# ==================== 媒体文件(用户上传) ====================
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# ==================== 默认主键类型 ====================
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# ==================== 日志配置 ====================
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {message}',
'style': '{',
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
},
'loggers': {
'django': {
'handlers': ['console'],
'level': 'INFO',
},
'house': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
},
}
六、本章小结
第6章核心知识点总结:
├── 1. 项目架构
│ ├── MVT模式:Model(数据层)、View(逻辑层)、Template(展示层)
│ ├── 请求流程:URL → View → Model → Template → Response
│ └── WSGI协议:Python Web应用的标准接口
│
├── 2. 项目创建与配置
│ ├── django-admin startproject:创建项目
│ ├── manage.py startapp:创建应用
│ ├── INSTALLED_APPS:注册应用
│ ├── settings.py:项目核心配置(数据库、静态文件、模板、国际化等)
│ └── manage.py:项目管理工具(迁移、运行服务器、Shell等)
│
├── 3. 静态文件配置
│ ├── STATIC_URL:URL前缀
│ ├── STATICFILES_DIRS:搜索目录
│ ├── STATIC_ROOT:收集目录(生产环境)
│ └── {% load static %} + {% static 'path' %}:模板中引用
│
├── 4. 模板配置
│ ├── DIRS:模板搜索目录
│ ├── APP_DIRS:是否搜索应用目录
│ ├── 模板继承:{% extends %} + {% block %}
│ └── 模板语言:变量{{ }}、标签{% %}、过滤器|filter
│
├── 5. 数据表设计
│ ├── 字段类型:CharField、IntegerField、DecimalField、ForeignKey等
│ ├── 字段选项:max_length、choices、default、null、blank等
│ ├── 关系映射:ForeignKey(多对一)、ManyToManyField(多对多)、OneToOneField(一对一)
│ └── Meta选项:db_table、ordering、verbose_name、indexes等
│
├── 6. 数据导入
│ ├── Django Shell:交互式导入
│ ├── 自定义命令:manage.py import_data
│ ├── 序列化工具:serializers.serialize / deserialize
│ └── CSV导入:csv.DictReader + models.objects.create
│
└── 7. 数据库迁移
├── makemigrations:生成迁移文件
├── migrate:执行迁移
├── showmigrations:查看迁移状态
└── sqlmigrations:预览SQL语句