Flask入门学习教程,从入门到精通,Flask智能租房——前期准备 知识点详解(5)

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>&copy; 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语句
相关推荐
JOJO数据科学13 分钟前
JupyterLab Electron 鸿蒙 PC 适配全记录:从 Python 原生崩溃到 node-static 本地工作台
python·electron·harmonyos
xufengzhu19 分钟前
第三方 Python 库 redis-py + hiredis 的使用
开发语言·redis·python
llxxyy卢1 小时前
polar夏季赛部分题目
开发语言·python
闵孚龙1 小时前
PyTorch 系列 之 nn.Module:所有模型的骨架
人工智能·pytorch·python
AI玫瑰助手1 小时前
Python模块:from...import...导入指定内容
开发语言·python·信息可视化
小森林之主1 小时前
Python re 模块速查:从实战对比中掌握正则表达式
python·正则表达式·性能测试·re模块·编程实战
郭wes代码1 小时前
Win10 拒绝访问、长期关机自动维护与声音图标灰色故障解决记录
windows·python·开源
solicitous1 小时前
学习了解充电桩协议OCPP——J规范
学习
伊布拉西莫2 小时前
LangChain LCEL源码深度剖析
python·langchain
用心_承载未来2 小时前
从“复制链接→打开APP“到“一键解析“:我做了个短视频去水印工具
python·去水印·短视频去水印