Skip to content

Django

快速开始

  • 安装Django
bash
# 全局安装django
pip install django

# 使用 pipenv 创建虚拟环境并安装 Django
mkdir startdjango && cd startdjango   # 创建虚拟环境
pipenv install django   # 在虚拟环境中安装django
pipenv shell  # 激活虚拟环境
  • 创建项目
bash
django-admin startproject [项目名称]  # 新建一个django项目

django-admin startproject [项目目录] . # 在当前目录创建django项目
  • 创建APP
bash
python manage.py startapp [APP名称]
  • 运行项目
bash
python manage.py runserver

项目文件架构

markdown
django_project           → 项目根目录
├── manage.py            → 项目的管理,启动项目、创建app、数据管理(常使用)
└── django_project       → 项目配置目录(与项目同名)
    ├── __init__.py      → 标识为Python包
    ├── settings.py      → 全局配置(常修改)
    ├── urls.py          → 主路由配置(常修改)
    ├── asgi.py          → ASGI服务器配置, 异步接收网络请求
    └── wsgi.py          → WSGI服务器配置, 同步接收网络请求

APP

在 Django 中,APP(应用) 是一个可重用的功能模块,用于实现特定的业务逻辑。它是 Django 项目的基本组成单元,遵循 "一个APP只做一件事" 的设计原则。

  • 独立性: 每个APP是一个独立的Python包,包含完整的功能闭环(模型、视图、模板等)
  • 低耦合:APP之间应通过公共接口通信,尽量减少直接依赖
  • 可复用:每个APP可以单独摘出来跨项目复用
  • 配置分离:敏感配置应放在项目settings中,而非APP内
markdown
- 项目
  - 业务APP: 用户管理、订单系统等(需自行开发)
  - 通用APP: 评论系统、API接口等(可第三方安装)
  - 内置APP: admin(后台)、auth(认证)、sessions 等
  • 创建APP
    在项目根目录下执行

    bash
    python manage.py startapp blog

    会在此项目文件夹下创建新app如下:

    markdown
    blog                         # APP名称(Python包)
    ├── migrations/                # 数据库变更记录(自动生成)
    │   ├── __init__.py   
    
    ├── templates/                 # 专属模板目录(html文件)
    │   └── blog/                  # 推荐加APP名前缀避免命名冲突
    │       ├── base.html          
    │       ├── post_list.html     
    │       └── post_detail.html   
    
    ├── static/                    # 专属静态文件(图片、css、js等文件)
    │   └── blog/
    │       ├── js/
    │       ├── plugins/                  # 推荐加APP名前缀避免命名冲突
    │       ├── css/
    │       │   └── style.css      
    │       └── images/
    │           └── logo.png     
    
    ├── __init__.py                
    ├── admin.py                   # django默认提供了admin后台管理
    ├── apps.py                    # APP配置类(自动生成)
    ├── models.py                  # 数据模型定义(重要,数据库操作相关)
    ├── tests.py                   # 单元测试
    ├── urls.py                    # 子路由配置(需手动创建)
    └── views.py                   # 视图逻辑(重要,函数)
  • 注册APP
    将新创建的app添加到项目同名目录下的settings.py文件中的INSTALLED_APPS列表中,格式为'app_name.apps.App_nameConfig'

    python
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'blog.apps.BlogConfig',  # 注册新创建的app
    ]

URL与视图(View)

URL介绍

URL是Uniform Resource Locator的简写,即统一资源定位符
一个URL由以下几个部分组成:

URL组成部分

  • Scheme:访问协议,一般是http或者https或ftp等
  • Domain Name:域名或ip地址,比如www.baidu.com
  • Port:端口号,http协议是80端口,https443端口
  • Path to the file:用来指定资源的路径
  • Parameters:查询字符串,比如: www.baidu.com/s?wd=python, 后面的wd=python就是查询字符串
  • Anchor:锚点,用于前端做页面定位

注意:URL中所有字符都基于ASCII字符集,浏览器会对中文字符先进行编码再传输

路由模块化

django可以在app内部写自己的urls.py, 而在项目的urls.py中包含这个appurls.py

python
# mysite/mysite/urls.py
from django.urls import path, include
urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls')),  # 将blog/的请求交给blog/urls.py处理
]
python
# mysite/blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
  path('articles/', views.article_list),  # 显示所有文章列表
  path('article/<int:id>/', views.article_detail),
]
python
# mysite/blog/views.py
from django.shortcuts import render, HttpResponse

def article_list(request):
  # 示例1:返回HTTP页面
  return render(request, "article_list.html")

def article_detail(request, id)
  # 示例2:返回文本内容
  return HttpResponse(f"文章ID:{id}")

path 和 re_path 方法

Django提供了两种设计URL的方法:pathre_path, 它们均支持视图函数或类传递参数。
pathre_path支持传递的数据类型只有 str, int, slug, uuid四种。一般来说 re_path 更强大,但写起来更复杂一些:

  • path方法
    python
    path(route, view, name=None, kwargs = None)
    # 参数说明:
    # route:url的匹配规则。这个参数中可以指定url中需要传递的参数。
    #        传递参数是通过<>来指定的,并且可以指定以下几种常用类型:
    #        str:默认类型,非空字符串,字符串内容不能包含斜杠'/'
    #        int: 一般整形
    #        slug:由横杠'-'或下划线'_'连接英文字符或数字组成的字符串,如:abc-122_dasd
    #        uuid: 匹配uuid字符串
    #        path:可用来传递文件路径等,如:'C:/Document/demo.txt'
    # view: 可以是视图函数、类视图或者是django.urls.include()函数的返回值
    # name: url别名,当项目庞大,url较多时,需要对每个url起别名
    python
    # 使用<变量类型:变量名> or <变量名>传递, 例如 <int:id> 或 <username>
    urlpatterns = [
      path('blog/articles/<int:id>/', views.article_detail),
    ]
  • re_path方法
    python
    # `re_path`方法是采用正则表达式regex匹配
    # 采用命名组(?P<变量名>表达式)的方式传递参数
    # 使用re_path时不一定总是以$结尾
    urlpatterns = [
      re_path(r'^blog/articles/(?P<id>\d+)/$', views.article_detail),
    ]
    示例
    python
    # 示例一,path方法
    from django.urls import path
    from . import views
    
    urlpatterns = [
        path('articles/2003/', views.special_case_2003),
        path('articles/<int:year>/', views.year_archive),
        path('articles/<int:year>/<int:month>/', views.month_archive),
        path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail),
    ]
    
    # 示例二:re_path方法,结果与上例等同
    from django.urls import path, re_path
    from . import views
    
    urlpatterns = [
        path('articles/2003/', views.special_case_2003),
        re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
        re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
        re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-]+)/$', views.article_detail),
    ]

URL的命名与命名空间

  • url的命名

    为url命名可以根据path.name反向读取路径

    python
    # mysite/blog/urls.py
    from django.urls import path, re_path
    from . import views
    
    app_name = 'blog' # 命名空间
    urlpatterns = [
      path('articles/', views.article_list, name = 'article_list'),
      path('articles/<int:id>/', views.article_detail, name = 'article_detail'),
    ]
  • 命名空间

    1. 在项目的主 urls.py 中定义命名空间
    python
    # 项目根 urls.py
    from django.urls import include, path
    # 通过 include() 指定 namespace
    urlpatterns = [
        path('blog/', include('blog.urls', namespace='blog')),  # 命名空间 blog
        path('news/', include('news.urls', namespace='news')),  # 命名空间 news
    ]
    1. 在应用的 urls.py 中定义 app_name
    python
    # blog/urls.py
    from django.urls import path
    from . import views
    
    app_name = 'blog'  # 应用命名空间(必须与主 urls.py 的 namespace 一致)
    
    urlpatterns = [
        path('detail/<int:id>/', views.article_detail, name='detail'),  
        # 实际 URL: /blog/articles/1/
    ]

URL的反向解析

当知道path.name,可以通过reverse()反向解析出对应path路径

  • 在视图函数中:

    python
    from django.urls import reverse
    
    def my_view(request):
        url_1 = reverse('article_list')  # 生成 /blog/articles/
        url_2 = reverse('article_detail', kwargs={'id': 1})  # 生成 /blog/articles/1/

    如果有应用命名空间,那么反向解析时应加上命名空间:

    python
    from django.urls import reverse
    def my_view(request):
        url_1 = reverse('blog:article_list')  # 生成 /blog/articles/
        url_2 = reverse('blog:article_detail', kwargs={'id': 1})  
        # 生成 /blog/articles/1/
  • 在模板中:

    html
    <a href="{% url 'article_list' %}">查看文章列表</a>
    <a href="{% url 'article_detail' id=1 %}">查看文章1</a>
  • 支持位置参数或关键字参数:

    python
    reverse('article-detail', args=[1])          # 位置参数
    reverse('article-detail', kwargs={'pk': 1})  # 关键字参数

注意:reverse反转url时不区分GET请求和POST请求,因此不能在反转的时候query-srting参数。如果想要添加query-srting参数,只能手动添加:

python
login_url = reverse('article_by_year') + '?year=2023'
> /articles?year=2023

URL传参方式

  1. 路径传参
    这是最常用的方式,直接在URL路径中包含参数如: http://127.0.0.1:8000/articles/2008/3/
python
# urls.py
from django.urls import path
from . import views

urlpatterns = [
  # 需定义参数的类型,如果类型不匹配就跳转404页面
  path('articles/<int:year>/<int:month>/', views.month_archive),
]
python
# views.py
def month_archive(request, year, month):
  return HttpResponse(f"您查找的文章日期为:{year}{month}月")
  1. Query-String
    如:https://127.0.0.1:8000/articles?year=2008&month=12
    python
    # urls.py
    from django.urls import path
    from . import views
    
    urlpatterns = [
      # 使用query-string传参时路径没有'/'
      path('articles', views.year_archive, name='article_by_year'),
    ]
    python
    # views.py
    def article_list(request):
      year = request.GET.get('year')
      month = request.GET.get('month')
      return HttpResponse(f"您查找的文章日期为:{year}{month}月")

模板(Template)

Django自带的模板引擎是DTL(Django Tamplate Language),当然Django也支持Jinja2等其他模板引擎。

官方文档参考:Django模板

模板的渲染

  1. render_to_string: 找到模板,将模板编译后渲染成Python字符串格式,然后通过HttpResponse类包装成一个HttpResponse对象返回。
python
from django.template.loader import render_to_string
from django.http import HttpResponse
def article_detail(request, book_id):
  html = render_to_string('detail.html')
  return HttpResponse(html)
  1. 使用**render**一步到位完成渲染成字符串 + 包装成HttpResponse对象:
python
# 使用render.context向模板传递参数,context是字典类型
from django.shortcuts import render
def article_list(request):
  return render(request, 'list.html', context={
    'username': '张三',
    'parent': ['father', 'mother'],
    'age': 10
    })

变量

  • 变量被包围在双层{中,就像这样:

    html
    <p>My first name is {{ first_name }}. My last name is {{ last_name }}.</p>
  • 字典查找属性查找列表索引查找都使用点表示法实现:

    html
    <p>{{ my_dict.key }}</p>
    <p>{{ my_object.attribute }}</p>
    <p>{{ my_list.0 }}</p>
  • 变量可以渲染对象:

    python
    # views.py
    from django.shortcuts import render
    class Person:
      def __init__(self, name):
        self.name = name
    
    def info(request):
      return render(request, 'info.html', context={'person': Person('张三')})
    html
    # info.html
    <p>实例化一个Person对象:{{person.name}} </p>

模板常用标签

标签被包围在 {% 和 %} 中,就像这样:

html
{% csrf_token %}
  • if 标签: if-elif-else-endif. if标签中可以使用==, !=, <, <=, >, >=, in, not in, is, is not等判断运算符

    html
    {% if age >= 18 %}
        <p>成年</p>  
    {% else %}
        <p>未成年</p> 
    {% endif %}
  • for ... in ... 标签:类似于 Python 中的 for ... in .... 可以遍历列表、元组、字符串、字典等一切可遍历对象

    html
    {% for person in persons %}
        <p>{{ person.name }}</p>
    {% endfor %}

    如果需要反向遍历,需要加上 reversed

    html
    {% for person in persons reversed %}
        <p>{{ forloop.counter0 }} {{ person.name }}</p>
    {% endfor %}

    for循环中,DTL还提供了一些变量可供使用:

    • forloop.counter:当前循环的下标,从1开始
    • forloop.counter0:当前循环的下标,从0开始
    • forloop.revcounter:当前循环的反向下标,从n->1
    • forloop.revcounter0:反向下标,从n->0
    • forloop.first:是否第一次遍历
    • forloop.last:是否最后一次遍历
  • for ... in ... empty ...:与for ... in ... 的区别在于当遍历的对象如果为空时,执行empty中的内容

    html
    {% for person in persons %}
        <p>{{ person.name }}</p>
    {% empty %}
        <p>暂时没有任何人</p>
    {% endfor %}
  • with标签:在模板中定义变量。有时候对一个变量访问比较繁琐,可以先将这个变量缓存到新建的一个变量上进行使用

html
context = {"persons": ["张三", "李四"]}

{% with Li=persons.1 %}
  <p>{{ Li }}</p>
{% endwith %}
  • url标签:它的主要作用是根据urls.py中的名称(name)动态生成URL,而不是硬编码URL路径,这使得项目更易于维护
html
<a herf="{% url 'blog:article_list' %}">文章列表</a>

如果url反转的时候需要传递参数,可以传位置参数和关键字参数,但是两种参数不能同时使用, 多个参数间使用空格分隔:

python
# url.py
path('article_detail/<int:id>/', views.article_detial, name='article_detail')
html
# 位置参数
<a herf="{% url 'blog:article_detail' 1 %}">图书详情页面</a>
# 关键字参数
<a herf="{% url 'blog:article_detail' id=1 %}">图书详情页面</a>

# 需手动添加query-string
<a herf="{% url 'blog:article_detail' id=1 %}?page=1">图书详情页面</a>

模板常用过滤器

过滤器就是模板的函数,有时需要对一些数据进行处理后才能使用,因此需要模板过滤器,常用的有这些

  • add:将右边的参数累加到左边的值。这个过滤器会尝试将值和参数转换成整型再相加。如果转换成整型失败,那么会将值和参数进行拼接。如果是值是字符串,则会拼接字符串;如果值是列表,则会拼接成一个新列表

    html
    {{ value|add:"2" }}
  • cut:移除值中所有指定的字符串。类似于pyhton的replace(args, " ")

    html
    {{ value|cut:" " }}
  • date:将一个日期按照指定的格式,格式化成字符串

    python
    context = {
      "birthday": datetime.now()
    }
    html
      <p>{{birthday|date:"Y/m/d"}}</p>
  • default:如果值是[], "", {}, None等在if判断中为False的值, 则使用default过滤器提供的默认值:

    html
      <p>{{value|default:"nothing"}}</p>
  • floatformat:使用四舍五入的方式格式化一个浮点类型。如果这个过滤器没有传递参数则默认保留一位小数,如果小数点后面全是0则只会保留整数。参数值代表要保留几位小数

    html
    # 假设value = 3.1415
    
    {{value|floatformat}} -> 3.1
    {{value|floatformat:2}} -> 3.14
  • join:类似Python中的join,将列表\元组\字符串用指定的字符进行拼接

    html
    {{value|join:"-"}}
  • length: 获取一个列表\元组\字符串\字典的长度

    html
    {{value|length}}
  • safe:标记一个字符串是安全的,即关掉字符串的自动转义
    当值是html语句时,可以自动转义成网页效果

    html
    # 假设value = <h1>标题 1</h1>
    {{value|safe}}
  • slice:切片过滤器,类似Python中的切片操作。可以对列表\元组\字符串进行切片操作

    html
    {{value|slice:":3"}} # 取前3个元素
    {{value|slice:"2:4"}} # 取第2到第4个元素
  • striptags:移除字符串中的html标签

    html
    {{value|striptags}}

模板结构

  • include标签{% include %} 标签用于在当前模板中包含另一个模板的内容,实现模板的模块化和代码复用。
    应用场景:
    • 将页面的公共部分(如页眉、页脚、导航栏)提取为单独文件
    • 包含重复使用的UI组件
    • 动态加载不同部分的模版
      示例如下:
html
<!-- parent.html -->
{% with secret_key="12345" public_info="Welcome" %}
    <!-- 包含子模板,不使用 only -->
    {% include "child.html" with message="Hello" %}
{% endwith %}

<!-- child.html -->
<p>消息: {{ message }}</p>          {# 能访问显式传递的 message #}
<p>公共信息: {{ public_info }}</p>  {# 能访问父模板的 public_info #}
<p>密钥: {{ secret_key }}</p>      {# 意外暴露了 secret_key #}

输出结果:

html
消息: Hello
公共信息: Welcome
密钥: 12345  <!-- 这可能是安全隐患! -->

使用only控制子模版只能访问传给他的变量,不能访问上下文变量:

html
<!-- parent.html -->
{% with secret_key="12345" public_info="Welcome" %}
    <!-- 使用 only 限制访问 -->
    {% include "child.html" with message="Hello" only %}
{% endwith %}

<!-- child.html -->
<p>消息: {{ message }}</p>          {# 能访问显式传递的 message #}
<p>公共信息: {{ public_info }}</p>  {# 无法访问,变量不存在 #}
<p>密钥: {{ secret_key }}</p>      {# 无法访问,变量不存在 #}

输出结果:

html
消息: Hello
公共信息:
密钥:
  • 模版继承 模板继承是 Django 模板系统中最强大的部分,允许你构建一个基础"骨架"模板,然后子模板可以覆盖骨架中的特定部分,实现代码复用和统一布局。
    应用场景:

    • 为整个网站创建统一布局
    • 根据不同应用或页面类型创建不同的基础模版
    • 实现多级继承(如全站基础模版->应用基础模版->具体页面模版)
    1. 基础模版(base.html)
    html
    <!DOCTYPE html>
    <html>
    <head>
        <title>{% block title %}默认标题{% endblock %}</title>
    </head>
    <body>
        {% block content %}这是父模版的body{% endblock %}
    </body>
    </html>
    1. 子模版(index.html)
    html
    {% extends "base.html" %}
    
    {% block title %}我的页面标题{% endblock %}
    
    {% block content %}
        {{ block.super }}    {# 包含父模板中的内容: "这是父模版的body" #}
        <p>这是子模版的部分...</p>
    {% endblock %}

加载静态文件

1. 首先确保 django.contrib.staticfiles 已经添加到 settings.INSTALLED_APPS 中.
2. 确保在 settings.py 中设置了 STATIC_URL, 比如:

python
STATIC_URL = "static/"

3.app01文件夹下创建static文件夹,在static文件夹下创建app01同名文件夹用于存放静态文件。这样做的目的是避免多个app下具有同名静态文件的路径产生混淆。
4. 工程可能包含未与任何app绑定的静态资源。除了在 my_app 中使用static/目录,还可以在setting.py中定义一个目录列表(STATICFILES_DIRS) ,Django 会从中寻找静态文件。

python
STATICFILES_DIRS = [
  BASE_DIR / "static",
  "/var/www/static/",
]

5. 在模版中使用load标签加载static标签,示例代码如下:

html
{% load static %}

<!-- CSS 文件 -->
<link rel="stylesheet" href="{% static 'css/style.css' %}">

<!-- JavaScript 文件 -->
<script src="{% static 'js/main.js' %}"></script>

<!-- 图片 -->
<img src="{% static 'images/logo.png' %}" alt="Logo">

数据库的配置和使用

配置数据库

以MySQL为例,首先需要安装pymysql库(需要先安装mysql数据库),可以使用以下命令安装:

bash
pip install pymysql

然后在Django项目的settings.py文件中配置数据库连接信息。以下是一个MySQL数据库的配置示例:

python
# project_demo/settings.py
DATABASES = {
  'default': {
    # 数据库引擎(是mysql还是oracle等)
    'ENGINE': 'django.db.backends.mysql',
    # 数据库名
    'NAME': 'database_demo ',
    # 连接数据库的用户名
    'USER': 'root',
    # 连接数据库的密码
    'PASSWORD': '123456qwe',
    # 数据库的主机地址
    'HOST': '127.0.0.1',
    # 数据库的端口号
    'PORT': '3306',
  }
}

原生SQL操作数据库

使用原生 sql 语句操作数据库其实是使用 python db api来操作的,比如mysql使用的 apipymysql(需要pip install pymysql) 示例代码:

python
# connection对象会自动读取setting.py中数据库的配置信息
from django.db import connection

# 获取游标对象
cursor = connection.cursor()
# 执行SQL语句
cursor.execute("SELECT * FROM student")
# 获取所有的数据
rows = cursor.fetchall()
# 遍历查询到的数据
for row in rows:
  print(row)
  • 游标对象常用api
    • 执行sql语句
      python
      # 执行一次sql语句
      cursor. execute(sql, params=None) # params可以是列表、元组或字典
      # 示例
      cursor.exexute("SELECT * FROM student WHERE id = %s", [1])
      cursor.execute(
      "SELECT * FROM myapp_mymodel WHERE id = %(id)s AND status = %(status)s",
      {'id': 1, 'status': 'active'})
      python
      # 多次执行同一sql语句,每次使用不同参数
      cursor.executemany(sql, param_list) # param_list 为列表类型
      # 示例
      data = [(1, 'John'), (2, 'Jane'), (3, 'Mike')]
      cursor.executemany("INSERT INTO student (id, name) VALUES (%s, %s)", data)
    • 获取查询结果的一行或多行
      python
      # 获取查询结果的下一行
      row = cursor.fetchone()
      # 获取查询结果的多行
      rows = cursor.fetchmany(size=None) # size: 行数
      # 获取查询结果的所有剩余行
      all_rows = cursor.fetchall()
    • 关闭游标
      python
      # 注意:关闭游标后此游标不能再次使用
      cursor.close()

<后续Django-ORM...>

Last Updated:

Released under the MIT License.