【前端开发学习】7.Django

1 初识Django

1.1 django的安装

pip install django

1.2 创建django项目

  • cmd命令行创建
# 已经进行了环境变量配置
django-admin.exe startproject 项目名称
  • Pycharm 【需要专业版】

1.3 两种创建方式的对比

  • 命令行,创建的项目是比较标准的;
  • Pycharm,与命令行方式对比:
    • 增加了templates目录;【删除】
    • setting.py中,TEMPLATES = [{ ‘DIRS’: [’ ******* ']}] 【DIRS中的内容删除】

1.4 默认文件介绍

mysite

  • manage.py 【项目管理,启动项目、创建APP、数据管理】- 不修改,常用
  • mysite
    • __ init __.py
    • settings.py 【项目配置文件】- 常常操作
    • urls.py 【URL和函数的对应关系】- 常常操作
    • asgi.py 【接收网络请求「异步式」】- 不修改
    • wsgi.py 【接收网络请求「同步式」】- 不修改

1.5 APP的创建和说明

1. APP 的例子

  • 项目

    • app,用户管理 【表结构、函数、HTML模板、CSS】
    • app,订单管理 【表结构、函数、HTML模板、CSS】
    • app,后台管理 【表结构、函数、HTML模板、CSS】
    • app,网站 【表结构、函数、HTML模板、CSS】
    • app,API 【表结构、函数、HTML模板、CSS】

    注意,我们开发比较简洁,用不到多 APP ,通常情况下,项目创建一个 APP 即可。

2. 创建 APP,在终端输入:

python manage.py startapp app01

3. APP 默认文件介绍

  • app01
    • __ init __.py
    • admin.py【固定,不用动】 django默认提供了admin后台管理
    • apps.py 【固定,不用动】 app启动类
    • migrations 【固定,不用动】 数据库字段变更记录
      • __ init __.py
    • models.py重要】 对数据库进行操作
    • tests.py【固定,不用动】 单元测试
    • views.py重要】 函数

1.6 启动运行django

1. 注册 app 【setting.py】

在这里插入图片描述

2. 编写 URL 和视图函数的对应关系 【urls.py】

在这里插入图片描述

3. 编写视图函数 【views.py】

在这里插入图片描述

4. 启动 Django 项目

命令行启动

python manage.py runserver

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

1.7 模板和静态文件

1. templates 模板

在这里插入图片描述
在这里插入图片描述

2. 静态文件

在开发过程中,一般将 「图片、CSS、js」都会当作静态文件处理。

(1)在 app 目录下创建static文件夹

在这里插入图片描述

(2)引用静态文件
在这里插入图片描述

1.8 模板语法

1. 本质:在 HTML 中写一些占位符,由数据对这些占位符进行替换和处理。
在这里插入图片描述

2. 案例:伪联通新闻中心

(1)在 urls.py 创建函数的对应关系

在这里插入图片描述

(2)在 views 中编写函数

在这里插入图片描述

(3)在 templates 的 news.html 编写模板语法

在这里插入图片描述

(4)页面展示

在这里插入图片描述

1.9 请求和响应

1.

在这里插入图片描述

2. 案例:用户登录

(1)发生如下错误,只需要在 form 表单中伪造跨站请求 {% csrf_token %} 。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

(2)最终代码:

在这里插入图片描述

(3)效果展示

在这里插入图片描述

在这里插入图片描述

1.10 orm数据库操作

ORM

  • 创建、修改、删除数据库中的表;(不用写SQL语句)【无法创建数据库】
  • 操作表中的数据(不用写SQL语句)。

在这里插入图片描述

1. 安装第三方库

pip install mysqlclient==1.4.1

2. 创建数据库

  • 首先在终端启动 mysql:
net start mysql
  • 登录 mysql(已经将初始密码设为 0 )
mysql -u root -p
  • 创建数据库(其中 gx 是数据库名称):
create database gx DEFAULT CHARSET utf8 COLLATE utf8_general_ci;

3. django 连接数据库

在 settings.py 文件中进行配置和修改。

更改默认 DATABASES :

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # 引擎
        'NAME': 'gx', # 数据库名字
        'USER': 'root', # 用户名
        'PASSWORD': '123456', # 密码
        'HOST': '127.0.0.1', # 哪台机器安装了MySQL - 本机
        'PORT': '3306',
    }
}

4. django 创建表

(1)在 models.py 文件中:

在这里插入图片描述

等同于:

create table app01_userinfo(
    id bigint auto_increment primary key, # django自动添加
    name varchar(32),
    password varchar(64),
    age int
)

(2)在 pycharm 终端输入,注意, app 需要提前注册:

python manage.py makemigrations
python manage.py migrate

在这里插入图片描述

至此,表已经创建成功。

在这里插入图片描述
在这里插入图片描述

5. django 修改表

(1) 删除某个列:

  • 注释该列;
  • 在 pycharm 终端输入:
python manage.py makemigrations
python manage.py migrate

(2)添加某个列:
- 在 python 代码中输入该列;
- 在 pycharm 终端输入:

python manage.py makemigrations
python manage.py migrate

(3)但是,对于 添加列 需要注意,由于已存在的列可能已有数据,所以新增列必须要指定对应的数据:

  • 选择 1,手动输入一个值;
  • 选择 2,回到代码设置默认值;
age = models.IntegerField(default=2)
  • 允许为空;
data = models.IntegerField(null=True, blank=True)

总结:对表结构进行调整

  • 在 models.py 文件中进行操作类;
  • 输入命令:
    python manage.py makemigrations
    python manage.py migrate

6. 操作表中的数据

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

1.11 ORM 数据库案例:用户管理

1. 展示用户列表

  • url;
  • 函数
    • 获取所有用户信息;
    • HTML 渲染;

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2. 添加用户

  • url;
  • 函数
    • GET,看到页面,输入内容;
    • POST,提交,写入到数据库。

(1)在 urls.py 中添加 path:

在这里插入图片描述

(2)编写函数 info_add :

在这里插入图片描述

(3)编写 info_add.html ,注意要有 {% csrf_token %} ,使得跨站网址请求能够成功。

在这里插入图片描述
(4)效果展示

在这里插入图片描述在这里插入图片描述

3. 删除用户

  • url;
  • 函数

(1)在 urls.py 添加 path:

在这里插入图片描述

(2) 在 views.py 中编写相关代码:

在这里插入图片描述

(3)结果展示:(当然在 /info/list.html 中也做了修改)

在这里插入图片描述

2 案例

2.1 创建项目和 APP

  1. 在终端进入项目的存储路径,然后创建项目:

    E:\PycharmProjects>django-admin.exe startproject day16
    
  2. 创建APP:

    E:\PycharmProjects\day16>python manage.py startapp app01
    
  3. 注册 APP ,修改 setting.py :

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'app01.apps.App01Config',
    ]
    

2.2 表结构的创建

在这里插入图片描述

models.py

from django.db import models

# Create your models here.

class Department(models.Model):
    """部门表"""
    title = models.CharField(verbose_name='标题', max_length=32)

class UserInfo(models.Model):
    """员工表"""
    name = models.CharField(verbose_name="姓名", max_length=16)
    password = models.CharField(verbose_name="密码", max_length=64)
    age = models.IntegerField(verbose_name="年龄")
    account = models.DecimalField(verbose_name="账户余额", max_digits=10, decimal_places=2, default=0)
    create_time = models.DateTimeField(verbose_name="入职时间")

    # 1. 有约束
    # - to,与哪张表关联
    # - to_field,表中的哪一列关联
    # 2. django自动
    # - 写的 depart
    # - 实际生成 depart_id
    # 3. 部门表被删除
    # 3.1 级联删除
    depart = models.ForeignKey(to="Department", to_field="id", on_delete=models.CASCADE)
    # 3.2 置空
    # depart = models.ForeignKey(to="Department", to_field="id", null=True, blank=True, on_delete=models.SET_NULL)
    gender_choices = (
        (1, "男"),
        (2, "女"),
    )
    gender = models.SmallIntegerField(verbose_name="性别", choices=gender_choices)

2.3 生成数据库

  1. cmd 终端创建数据库
create database day16 DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
  1. 在 setting.py 中修改 DATABASES
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # 引擎
        'NAME': 'day16', # 数据库名字
        'USER': 'root', # 用户名
        'PASSWORD': 'root', # 密码
        'HOST': '127.0.0.1', # 哪台机器安装了MySQL - 本机
        'PORT': '3306',
    }
}
  1. django 命令生成表
python manage.py makemigrations
python manage.py migrate

表结构创建成功:
在这里插入图片描述

2.4 部门管理

在这里插入图片描述
depart_list.html

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
</head>
<body>
<nav class="navbar navbar-default">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">联通用户管理系统</a>
        </div>

        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li><a href="depart/list">部门管理</a></li>
                <li><a href="#">用户管理</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                <li><a href="#">登录</a></li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">rice <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="#">个人资料</a></li>
                        <li><a href="#">用户信息</a></li>
                        <li><a href="#">注销</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">Separated link</a></li>
                    </ul>
                </li>
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>
<div>
    <div class="container">
        <div style="margin-bottom: 10px">
            <a class="btn btn-success" href="#">新建部门</a>
        </div>
        <div class="panel panel-default">
            <div class="panel-heading">
                <span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
                部门列表
            </div>
            <table class="table table-bordered">
                <thead>
                <tr>
                    <th>ID</th>
                    <th>部门名称</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                <tr>
                    <td>1</td>
                    <td>销售部</td>
                    <td>
                        <a class="btn btn-primary btn-xs">编辑</a>
                        <a class="btn btn-danger btn-xs">删除</a>
                    </td>
                </tr>
                </tbody>
            </table>

        </div>

    </div>
</div>
</div>
<script src="{% static 'jquery/jquery.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}"></script>
</body>
</html>

2.5 数据库数据

在这里插入图片描述

views.py

from django.shortcuts import render
from app01 import models
# Create your views here.

def depart_list(request):
    """部门列表"""

    # 去数据库中获取所有部门列表
    # [对象, 对象, 对象]
    queryset = models.Department.objects.all()

    return render(request, 'depart_list.html', {'queryset': queryset})

depart_list.html

                <tbody>
                {% for obj in queryset %}
                <tr>
                    <td>{{ obj.id }}</td>
                    <td>{{ obj.title }}</td>
                    <td>
                        <a class="btn btn-primary btn-xs">编辑</a>
                        <a class="btn btn-danger btn-xs">删除</a>
                    </td>
                </tr>
                {% endfor %}
                </tbody>

2.6 添加部门页面

在这里插入图片描述

views.py

def depart_add(request):
    """添加部门"""
    return render(request, 'depart_add.html')

depart_list.html

<div style="margin-bottom: 10px">
    <a class="btn btn-success" href="/depart/add/" >新建部门</a>
</div>

depart_add.html

<div class="container">
    <div class="panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">部门列表</h3>
        </div>
        <div class="panel-body">
            <form>
                <div class="form-group">
                    <label>标题</label>
                    <input type="text" class="form-control" placeholder="标题" name="title" />
                </div>
                <div class="form-group">
                    <div class="col-sm-offset-2 col-sm-10">
                        <button type="submit" class="btn btn-primary">保 存</button>
                    </div>
                </div>
            </form>
        </div>
    </div>

2.7 部门:添加

views.py

# 导入redirect
from django.shortcuts import render, redirect
def depart_add(request):
    """添加部门"""
    if request.method == "GET":
        return render(request, 'depart_add.html')

    # 获取用户POST提交过来的数据
    title = request.POST.get("title")

    # 保存到数据库
    models.Department.objects.create(title=title)

    # 重定向到部门列表
    return redirect('/depart/list')

depart_add.html

        <div class="panel-body">
            <form method="post">
                {% csrf_token %}
                <div class="form-group">
                    <label>标题</label>
                    <input type="text" class="form-control" placeholder="标题" name="title"/>
                </div>
                <div class="form-group">
                    <button type="submit" class="btn btn-primary">提交</button>
                </div>
            </form>

2.8 部门:删除

views.py

def depart_delete(request):
    """删除部门"""
    # 获取ID
    nid = request.GET.get('nid')

    # 删除
    models.Department.objects.filter(id=nid).delete()

    # 重定向回部门列表
    return redirect("/depart/list/")

depart_list.html

<a class="btn btn-danger btn-xs" href="/depart/delete/?nid= {{ obj.id }}">删除</a>

2.9 部门:编辑

在这里插入图片描述

urls.py

/< 正则表达式 >/

urlpatterns = [
    # http://127.0.0.1:8000/depart/1/edit/
    path('depart/<int:nid>/edit/', views.depart_edit),
]

views.py

def depart_edit(request, nid):
    """修改部门"""
    if(request.method == "GET"):
        # 根据nid 获取它的数据 [object,]
        row_object = models.Department.objects.filter(id=nid).first()
        return render(request, 'depart_edit.html', {"title": row_object.title})

    # 获取用户提交的数据
    title = request.POST.get("title")
    # 更新数据
    models.Department.objects.filter(id=nid).update(title=title)
    # 重定向回部门列表界面
    return redirect("/depart/list/")

depart_edit.html

<input type="text" class="form-control" placeholder="标题" name="title" value="{{ title }}"/>

2.10 模板继承

  1. 定义模板:

layout.html

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
</head>
<body>
<div>
    {% block content %}
    {% endblock%}
</div>
<script src="{% static 'jquery/jquery.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}"></script>
</body>
</html>
  1. 在别的 html 界面继承模板:

children.html

{% extends 'layout.html' %}

{% block content %}

<!--新增内容-->

{% endblock %}

当然, 也可以定义多个模板:

layout.html

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    {% block css %}
    {% endblock%}
</head>
<body>
<div>
    {% block content %}
    {% endblock%}
</div>
    {% block js %}
    {% endblock%}
</body>
</html>

children.html

{% extends 'layout.html' %}

{% block css %}
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
{% endblock%}

{% block content %}
<!--新增内容-->
{% endblock %}

{% block js %}
    <script src="{% static 'jquery/jquery.min.js' %}"></script>
{% endblock%}

2.11 用户管理:列表

  1. 在 mysql 插入数据
    insert into app01_userinfo(name, password, age, account, create_time, gender, depart_id) values("rice", "123", 23, 100.68, "2020-01-11", 2, 1);
    
    insert into app01_userinfo(name, password, age, account, create_time, gender, depart_id) values("candy", "123", 26, 100.68, "2021-01-11", 1, 2);
    
    insert into app01_userinfo(name, password, age, account, create_time, gender, depart_id) values("romance", "123", 25, 100.68, "2020-01-11", 2, 4);
    

  1. urls.py

    path('user/list/', views.user_list),
    
  2. views.py

    def user_list(request):
        """用户管理"""
    
        # 获取所有用户信息
        queryset = models.UserInfo.objects.all()
        # python 写法
        # for obj in queryset:
        #     id = obj.id
        #     name = obj.name
        #     password = obj.password
        #     age = obj.age
        #     account = obj.account
        #     create_time = obj.create_time.strftime("%Y-%m-%d")
        #     gender = obj.get_gender_display()
        #     title = obj.depart.title # 根据id自动去关联的表中获取哪一行的数据depart对象
        return render(request, 'user_list.html', {"queryset": queryset})
    
    1. user_list.html
    <table class="table table-bordered">
        <thead>
        <tr>
            <th>ID</th>
            <th>姓名</th>
            <th>密码</th>
            <th>年龄</th>
            <th>余额</th>
            <th>入职时间</th>
            <th>性别</th>
            <th>所属部门</th>
            <th>操作</th>
        </tr>
        </thead>
        <tbody>
        {% for obj in queryset %}
        <tr>
            <td>{{ obj.id }}</td>
            <td>{{ obj.name }}</td>
            <td>{{ obj.password }}</td>
            <td>{{ obj.age }}</td>
            <td>{{ obj.account }}</td>
            <td>{{ obj.create_time|date:"Y-m-d" }}</td>
            <td>{{ obj.get_gender_display }}</td>
            <td>{{ obj.depart.title }}</td>
            <td>
                <a class="btn btn-primary btn-xs" href="#">编辑</a>
                <a class="btn btn-danger btn-xs" href="#">删除</a>
            </td>
        </tr>
        {% endfor %}
        </tbody>
    </table>
    
  3. 效果展示

在这里插入图片描述

2.12 用户管理:添加

1. 思路

  • 原始方法:比较复杂,因此最终不采用;

    缺点

    • 用户提交数据没有校验;
    • 发生错误的时候,页面没有错误提示;
    • 页面上,每一个字段都需要我们重新写一遍;
    • 关联数据需要手动获取并通过循环才能展示在页面。
  • Django 组件

    • Form 组件 【简便】
    • ModelForm 组件 【最简便】【最推荐】

2. 原始方法

  1. views.py

    def user_add(request):
        """添加用户"""
        if request.method == "GET":
            context = {
                'gender_choices': models.UserInfo.gender_choices,
                'depart_list': models.Department.objects.all()
            }
            return render(request, 'user_add.html', context)
    
        # 获取用户提交的数据
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        age = request.POST.get('age')
        account = request.POST.get('ac')
        ctime = request.POST.get('ctime')
        gender = request.POST.get('gd')
        depart_id = request.POST.get('dp')
    
        # 添加到数据库中
        models.UserInfo.objects.create(name=user, password=pwd, age=age,
                                       account=account, create_time=ctime,
                                       gender=gender, depart_id=depart_id)
        # 返回到用户列表界面
        return redirect("/user/list")
    
  2. user_add.html

    <div class="container">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title">用户列表</h3>
            </div>
            <div class="panel-body">
                <form method="post">
                    {% csrf_token %}
                    <div class="form-group">
                        <label>姓名</label>
                        <input type="text" class="form-control" placeholder="姓名" name="user"/>
                    </div>
                    <div class="form-group">
                        <label>密码</label>
                        <input type="text" class="form-control" placeholder="密码" name="pwd"/>
                    </div>
                    <div class="form-group">
                        <label>年龄</label>
                        <input type="text" class="form-control" placeholder="年龄" name="age"/>
                    </div>
                    <div class="form-group">
                        <label>余额</label>
                        <input type="text" class="form-control" placeholder="余额" name="ac"/>
                    </div>
                    <div class="form-group">
                        <label>入职时间</label>
                        <input type="text" class="form-control" placeholder="入职时间" name="ctime"/>
                    </div>
                    <div class="form-group">
                        <label>性别</label>
                        <select class="form-control" name="gd">
                            {% for gender in gender_choices %}
                            <option value="{{ gender.0 }}">{{ gender.1 }}</option>
                            {% endfor %}
                        </select>
                    </div>
                    <div class="form-group">
                        <label>部门</label>
                        <select class="form-control" name="dp">
                            {% for item in depart_list %}
                            <option value="{{ item.id }}">{{ item.title }}</option>
                            {% endfor %}
                        </select>
                    </div>
                    <div class="form-group">
                        <button type="submit" class="btn btn-primary">提交</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
    

3.Form

  1. views.py

    class MyForm(Form):
    	user = forms.CharField(wiget=forms.Input)
    	pwd = form.CharField(wiget=forms.Input)
    	age= form.CharField(wiget=forms.Input)
    	account = form.CharField(wiget=forms.Input)
    	create_time = form.CharField(wiget=forms.Input)
    	gender = form.CharField(wiget=forms.Input)
    	depart_id = form.CharField(wiget=forms.Input)
    
    def user_add(request):
    	form = MyForm() # 实例化
    	return render(request, 'user_add.html', {"form": form})
    
  2. user_add.html

    form.user 相当于 input user

    <form method="post">
    	{{ form.user }}
    	{{ form.pwd }}
    <!--<input type="text" class="form-control" placeholder="姓名" name="user"/>-->
    
    </form>
    

    进一步优化,使用循环:

    <form method="post">
    	{ for field in form %}
    		{{ field }}
    	{% endfor %}
    </form>
    

4. ModelForm

  1. models.py

    class UserInfo(models.Model):
        """员工表"""
        name = models.CharField(verbose_name="姓名", max_length=16)
        password = models.CharField(verbose_name="密码", max_length=64)
        age = models.IntegerField(verbose_name="年龄")
        account = models.DecimalField(verbose_name="账户余额", max_digits=10, decimal_places=2, default=0)
        create_time = models.DateTimeField(verbose_name="入职时间")
        depart = models.ForeignKey(to="Department", to_field="id", on_delete=models.CASCADE)
        gender_choices = (
            (1, "男"),
            (2, "女"),
        )
        gender = models.SmallIntegerField(verbose_name="性别", choices=gender_choices)
    
  2. views.py

    class MyForm(ModelForm):
    	class Meta:
    		model = UserInfo
    		fields = ["name", "password", "age"]
    def user_add(request):
    	form = MyForm() # 实例化
    	return render(request, 'user_add.html', {"form": form})
    
  3. user_add.html

    form.user 相当于 input user

    <form method="post">
    	{{ form.user }}
    	{{ form.pwd }}
    <!--<input type="text" class="form-control" placeholder="姓名" name="user"/>-->
    
    </form>
    

    进一步优化,使用循环:

    <form method="post">
    	{ for field in form %}
    		{{ field }}
    	{% endfor %}
    </form>
    

5. 使用 ModelForm 添加用户

  1. views.py

    from django import forms
    
    class UserModelForm(forms.ModelForm):
        class Meta:
            model = models.UserInfo
            fields = ["name", "password", "age", "account", "create_time", "depart", "gender"]
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            # 循环找到所有插件,添加class="form-control"
            for name, field in self.fields.items():
                field.widget.attrs = {"class": "form-control", "placeholder": field.label}
    
    def user_model_form_add(request):
        """添加用户 ModelForm版本"""
        form = UserModelForm()
        return render(request, 'user_model_form_add.html', {"form": form})
    
  2. models.py

    重写字段,保证 depart 作为用户列表的外键能够以字符形式显示。

    class Department(models.Model):
        """部门表"""
        title = models.CharField(verbose_name='标题', max_length=32)
        def __str__(self):
            return self.title
    
  3. user_model_form_add.html

    {% extends 'layout.html' %}
    
    {% block content %}
    <div class="container">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title">用户列表</h3>
            </div>
            <div class="panel-body">
                <form method="post">
                    {% csrf_token %}
    
                    {% for field in form %}
                    <div class="form-group">
                        <label>{{ field.label }}</label>
                        {{ field }}
                    </div>
                    {% endfor %}
                    
                    <div class="form-group">
                        <button type="submit" class="btn btn-primary">提交</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
    {% endblock %}
    
  4. 效果图

    在这里插入图片描述

6. 添加的错误提示

  1. views.py

    重写字段 name ,用于数据验证(name 字段至少为 3 个字符),
    name = forms.CharField(min_length=3, label="用户名")

    class UserModelForm(forms.ModelForm):
    
        # 重写字段
        name = forms.CharField(min_length=3, label="用户名")
    
        class Meta:
            model = models.UserInfo
            fields = ["name", "password", "age", "account", "create_time", "depart", "gender"]
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            # 循环找到所有插件,添加class="form-control"
            for name, field in self.fields.items():
                field.widget.attrs = {"class": "form-control", "placeholder": field.label}
    
    def user_model_form_add(request):
        """添加用户 ModelForm版本"""
        if request.method == "GET":
            form = UserModelForm()
            return render(request, 'user_model_form_add.html', {"form": form})
    
        # 用户POST提交数据,数据校验
        form = UserModelForm(data=request.POST)
        if form.is_valid():
            # 如果数据合法,保存在数据库
            form.save()
            return redirect('/user/list/')
    
        # 校验失败(在页面上显示错误信息)
        else:
            return render(request, 'user_model_form_add.html', {"form": form})
    
  2. user_model_form_add.html

    注意,网页自带的验证先取消 novalidate ,方便展示我们代码实现数据验证的效果。
    field.errors.0 显示当前的第一个错误。

    <form method="post" , novalidate>
        {% csrf_token %}
    
        {% for field in form %}
        <div class="form-group">
            <label>{{ field.label }}</label>
            {{ field }}
            <span style="color: red">{{ field.errors.0 }}</span>
        </div>
        {% endfor %}
        <div class="form-group">
            <button type="submit" class="btn btn-primary">提交</button>
        </div>
    </form>
    
  3. 效果图:

    在这里插入图片描述

2.13 用户管理:编辑

  • 点击编辑, 跳转到编辑界面(将编辑行的 ID 携带过去);
  • 编辑页面(默认数据,根据ID获取并设置到页面中)
  • 提交
    • 错误提示
    • 数据校验
    • 数据库更新
  1. user_list.html

    <a class="btn btn-primary btn-xs" href="/user/{{ obj.id }}/edit/">编辑</a>
    
  2. urls.py
    编辑用户需要获取用户的id

    path('user/<int:nid>/edit/', views.user_edit),
    
  3. views.py

    def user_edit(request, nid):
        """编辑用户"""
        # 根据id去数据库获取编辑行的数据
        row_object = models.UserInfo.objects.filter(id=nid).first()
    
        if request.method == "GET":
            # 加入instance参数,原有的值会显示在输入框
            form = UserModelForm(instance=row_object)
            return render(request, 'user_edit.html', {'form': form})
    
        form = UserModelForm(data=request.POST, instance=row_object)
        if form.is_valid():
            form.save()
            return redirect('/user/list/')
        return render(request, 'user_edit.html', {"form":form})
    
  4. 效果图

    在这里插入图片描述

2.14 用户管理:删除

  1. user_list.html

    修改删除标签

    <a class="btn btn-danger btn-xs" href="/user/{{ obj.id }}/delete/">删除</a>
    
  2. urls.py

    path('user/<int:nid>/delete/', views.user_delete)
    
  3. views.py

    def user_delete(request, nid):
        models.UserInfo.objects.filter(id=nid).delete()
        return redirect('/user/list/')
    

3 案例:靓号管理

3.1 表结构

在这里插入图片描述

class PrettyNum(models.Model):
    """靓号表"""
    mobile = models.CharField(verbose_name="手机号", max_length=11)
    # 想要允许为空 null=True, blank=True
    price = models.IntegerField(verbose_name="价格", default=0)
    level_choices = (
        (1, "1级"),
        (2, "2级"),
        (3, "3级"),
        (4, "4级"),
    )
    level = models.SmallIntegerField(verbose_name="级别", choices=level_choices, default=1)
    status_choices = (
        (1, "已占用"),
        (2, "未使用"),
    )
    status = models.SmallIntegerField(verbose_name="状态", choices=status_choices, default=2)

3.2 靓号列表

  • URL;
  • 函数
    • 获取所有靓号;
    • 结合 html + render 将所有靓号展现出来;
  1. layout.html

    <li><a href="/pretty/list/">靓号管理</a></li>
    
  2. urls.py

    path('pretty/list/', views.pretty_list),
    
  3. views.py

    def pretty_list(request):
        """靓号管理"""
    
        # order_by :降序
        queryset = models.PrettyNum.objects.all().order_by("-level")
        return render(request, 'pretty_list.html', {'queryset': queryset})
    
  4. pretty.html

    {% extends 'layout.html' %}
    {% block content %}
    
    <div class="container">
        <div style="margin-bottom: 10px">
            <a class="btn btn-success" href="#">新建靓号</a>
        </div>
        <div class="panel panel-default">
            <div class="panel-heading">
                <span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
                用户列表
            </div>
            <table class="table table-bordered">
                <thead>
                <tr>
                    <th>ID</th>
                    <th>手机号</th>
                    <th>价格</th>
                    <th>级别</th>
                    <th>状态</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                {% for obj in queryset %}
                <tr>
                    <td>{{ obj.id }}</td>
                    <td>{{ obj.mobile }}</td>
                    <td>{{ obj.price }}</td>
                    <td>{{ obj.get_level_display }}</td>
                    <td>{{ obj.get_status_display }}</td>
                    <td>
                        <a class="btn btn-primary btn-xs" href="#">编辑</a>
                        <a class="btn btn-danger btn-xs" href="#">删除</a>
                    </td>
                </tr>
                {% endfor %}
                </tbody>
            </table>
        </div>
    
    </div>
    {% endblock %}
    

3.3 新建靓号

  • 列表点击跳转:/pretty/add/
  • URL
  • ModelForm 类
  • 函数
    • 实例化类对象;
    • 通过 render 将对象传入到 HTML 中;
    • 模板循环展示所有对象;
  • 点击提交
    • 数据校验; 【两种方法】
    • 保存到数据库;
    • 跳转回靓号列表;
  1. urls.py

    path('pretty/add/', views.pretty_add),
    
  2. views.py

    from django.core.validators import RegexValidator
    from django.core.validators import ValidationError
    class PrettyModelForm(forms.ModelForm):
        # 验证:方式1 字段+正则
        # mobile = forms.CharField(
        #     label="手机号",
        #     validators=[RegexValidator(r'^1[3-9]\d{9}$', '手机号格式错误')],
        # )
        class Meta:
            model = models.PrettyNum
            # fields = ["mobile", "price", "level", "status"]
            fields = "__all__"
            # exclude = ['level']
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            for name, field in self.fields.items():
                field.widget.attrs = {"class": "form-control", "placeholder": field.label}
        # 验证:方式2 钩子方法 
        def clean_mobile(self):
            txt_mobile = self.cleaned_data['mobile']
            if len(txt_mobile) != 11:
                # 验证不通过
                raise ValidationError("格式错误")
            # 验证通过
            return txt_mobile
    
    def pretty_add(request):
        """添加靓号"""
        if request.method == "GET":
            form = PrettyModelForm()
            return render(request, 'pretty_add.html', {'form': form})
    
        # POST提交数据,数据校验
        form = PrettyModelForm(data=request.POST)
        if form.is_valid():
            form.save()
            return redirect('/pretty/list/')
    
        # 校验失败
        else:
            return render(request, 'pretty_add.html', {'form': form})
    
  3. pretty_list.html

    <a class="btn btn-success" href="/pretty/add/">新建靓号</a>
    
  4. pretty_add.html

{% extends 'layout.html' %}
{% block content %}
<div class="container">
    <div class="panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">靓号列表</h3>
        </div>
        <div class="panel-body">
            <form method="post" novalidate>
                {% csrf_token %}

                {% for field in form %}
                <div class="form-group">
                    <label>{{ field.label }}</label>
                    {{ field }}
                    <span style="color: red">{{ field.errors.0 }}</span>
                </div>
                {% endfor %}
                <div class="form-group">
                    <button type="submit" class="btn btn-primary">提交</button>
                </div>
            </form>
        </div>
    </div>
</div>


{% endblock%}
  1. 效果图
    在这里插入图片描述

  2. 数据校验的补充:数据库的手机号需要唯一

    使用正则表达式判断 「手机号是否已经存在」

    # True/False
    exists = models.PrettyNum.objects.ilter(mobile="100000000").exists()
    

    效果图:

    在这里插入图片描述
    views.py

    class PrettyModelForm(forms.ModelForm):
        class Meta:
            model = models.PrettyNum
            fields = "__all__"
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            for name, field in self.fields.items():
                field.widget.attrs = {"class": "form-control", "placeholder": field.label}
        # 验证:方式2
        def clean_mobile(self):
            txt_mobile = self.cleaned_data['mobile']
    			
    		# 手机号是否唯一的判断 
            exists = models.PrettyNum.objects.filter(mobile=txt_mobile).exists()
            if exists:
                raise ValidationError("手机号已存在")
                
            return txt_mobile
    

3.4 编辑靓号

  1. urls.py

    path('pretty/<int:nid>/edit/', views.pretty_edit),
    
  2. views.py

    class PrettyEditModeleForm(forms.ModelForm):
        # disabled参数,该字段不可以编辑
        # mobile = forms.CharField(disabled=True, label="手机号")
        class Meta:
            model = models.PrettyNum
            #fields = ["price", "level", "status"]
            fields = "__all__"
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            for name, field in self.fields.items():
                field.widget.attrs = {"class": "form-control", "placeholder": field.label}
        # 验证:方式2
        def clean_mobile(self):
    
            # 当前编辑行的id
            # self.instance.pk
    
            txt_mobile = self.cleaned_data['mobile']
    
    		# 手机号是否唯一的判断
            exists = models.PrettyNum.objects.exclude(id=self.instance.pk).filter(mobile=txt_mobile).exists()
            if exists:
                raise ValidationError("手机号已存在")
            
            if len(txt_mobile) != 11:
                # 验证不通过
                raise ValidationError("格式错误")
            # 验证通过
            return txt_mobile
    
    
    def pretty_edit(request, nid):
        """编辑靓号"""
    
        # 根据id去数据库获取编辑行的数据
        row_object = models.PrettyNum.objects.filter(id=nid).first()
    
        if request.method == "GET":
            form = PrettyEditModeleForm(instance=row_object)
            return render(request, 'pretty_edit.html', {'form': form})
    
        form = PrettyEditModeleForm(data=request.POST, instance=row_object)
        if form.is_valid():
            form.save()
            return redirect('/pretty/list/')
    
        return render(request, 'pretty_edit.html', {'form': form})
    
  3. pretty_list.html

    <a class="btn btn-primary btn-xs" href="/pretty/{{ obj.id }}/edit/">编辑</a>
    
  4. pretty_edit.html

    {% extends 'layout.html' %}
    {% block content %}
    
    <div class="container">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title">编辑靓号</h3>
            </div>
            <div class="panel-body">
                <form method="post" novalidate>
                    {% csrf_token %}
    
                    {% for field in form %}
                    <div class="form-group">
                        <label>{{ field.label }}</label>
                        {{ field }}
                        <span style="color: red">{{ field.errors.0 }}</span>
                    </div>
                    {% endfor %}
                    <div class="form-group">
                        <button type="submit" class="btn btn-primary">提交</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
    
    {% endblock %}
    
  5. 效果图

    在这里插入图片描述

  6. 数据校验:确保手机号的唯一性。

    这和新建靓号中的校验不太一样。新建靓号的时候,对于新输入的手机号,是和数据库中的手机号相比判断的。

    编辑靓号中,如果你更改的是手机号除外的信息,此时也没办法通过,因为你编辑后提交的手机号在数据库中已经存在了,因此在进行校验的时候需要排除自身的手机号再判断唯一性。

    views.py

    txt_mobile = self.cleaned_data['mobile']
    
    # 当前编辑行的id
    # self.instance.pk
    exists = models.PrettyNum.objects.exclude(id=self.instance.pk).filter(mobile=txt_mobile).exists()
    
    if exists:
    	raise ValidationError("手机号已存在")
    

    效果图,编辑当前手机号的级别:

    在这里插入图片描述

3.5 删除靓号

  1. pretty_list.html

    修改删除标签

    <a class="btn btn-danger btn-xs" href="/pretty/{{ obj.id }}/delete/">删除</a>
    
  2. urls.py

    path('pretty/<int:nid>/delete/', views.pretty_delete),
    
  3. views.py

    def pretty_delete(request, nid):
    	models.PrettyNum.objects.filter(id=nid).delete()
    	return redirect('/pretty/list/')
    

3.6 靓号管理:手机号搜索

  1. 数据库搜索相关知识补充

    models.PrettyNum.objects.filter(mobile="12345678920", id=5)
        
    # 以字典方式传入 必须要加 ** 
    data_list = {"mobile": "12345678920", "id": 5}
    models.PrettyNum.objects.filter(**data_list)
    
    models.PrettyNum.objects.filter(id=5)       # 等于5
    models.PrettyNum.objects.filter(id__gt=5)    # 大于5
    models.PrettyNum.objects.filter(id__gte=5)   # 大于等于5
    models.PrettyNum.objects.filter(id__lt=5)    # 小于5
    models.PrettyNum.objects.filter(id__lte=5)   # 小于等于5
    
    models.PrettyNum.objects.filter(mobile="123")               # 等于
    models.PrettyNum.objects.filter(mobile__startswith="123")    # 以123开头
    models.PrettyNum.objects.filter(mobile__endswith="123")      # 以123结尾
    models.PrettyNum.objects.filter(mobile__contains="123")      # 包含123
    
  2. 手机号搜索实现

    本质:

    在用户列表界面增加一个输入框,用来输入查询的手机号。

    我们希望进行搜索时,条件会包含在网址里,即网址是 http://127.0.0.1:8000/pretty/list/?q=456 ,那么需要通过 get 方式提交,即 <form method="get"> 。那么后端会通过 request.GET.get 获取到 name="q" 的输入,将其放入字典 data_list ,作为数据库搜索的条件 filter(**data_list)

    要想让输入框能够显示查询的手机号,需要把输入值 search_data 传入前端,放在<input> 标签的 value 字段。

views.py

def pretty_list(request):
    """靓号管理"""
    data_list = {}
    search_data = request.GET.get('q', "")
    if search_data:
         data_list["mobile__contains"] = search_data

    queryset = models.PrettyNum.objects.filter(**data_list).order_by("-level")
    return render(request, 'pretty_list.html', {'queryset': queryset, "search_data": search_data})

pretty_list.html

    <div style="margin-bottom: 10px" class="clearfix">
        <a class="btn btn-success" href="/pretty/add/">新建靓号</a>
        <div style="float: right; width:300px">
            <form method="get">
                <div class="input-group">
                    <input type="text" name="q" class="form-control" placeholder="Search for..."
                           value="{{ search_data }}">
                    <span class="input-group-btn">
                    <button class="btn btn-default" type="submit">
                        <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
                    </button>
                </span>
                </div><!-- /input-group -->
            </form>
        </div>
    </div>

效果图:

在这里插入图片描述

3.7 靓号管理:分页

pass (笔记未整理)

  1. pagination.py

    """
    自定义分页组件
    如果以后想要使用这个分页组件,需要做如下几件事:
    
    在视图函数中:
        def pretty_list(request):
    
            # 1. 根据自己的情况去筛选数据
            queryset = models.PrettyNum.objects.filter(**data_list).order_by("-level")
    
            # 2. 实例化分页对象
            page_objects = Pagination(request, queryset) # 封装成组件
    
            context = {
                'search_data': search_data,
                'queryset': page_objects.page_queryset,  # 分完页的数据
                "page_string": page_objects.html() # 生成的页码
            }
            return render(request, 'pretty_list.html', context)
    
    # 在HTML中:
        {% for obj in queryset %}
            {{ obj.xx }}
        {% endfor %}
    
        <ul class="pagination">
            {{ page_string }}
        </ul>
    """
    from django.utils.safestring import  mark_safe
    
    
    class Pagination(object):
    
        def __init__(self, request, queryset, page_size=10, page_param="page", plus=5):
            """
            :param request:     请求的对象
            :param queryset:    符合条件的数据 (根据这个数据给他进行分页处理)
            :param page_size:   每页显示多少条数据
            :param page_param:  在URL中传递的获取分页的参数 例如:pretty/list/?page=1
            :param plus:        显示当前页的前后几页(页码)
            """
            import copy
            query_dict = copy.deepcopy(request.GET)
            query_dict.mutable = True
            self.query_dict = query_dict
    
            self.page_param = page_param
            page = int(request.GET.get(page_param, "1"))
            self.page = page
            self.page_size = page_size
            self.start = (page - 1) * page_size
            self.end = page * page_size
            self.page_queryset = queryset[self.start:self.end]
    
            total_count = queryset.count()
            # 总页码数
            total_page_count, div = divmod(total_count, page_size)
            if div:
                total_page_count += 1
            self.total_page_count = total_page_count
            self.plus = plus
    
    
        def html(self):
            # 计算当前页的前5页与后5页
            if self.total_page_count <= 2 * self.plus + 1:
                start_page = 1
                end_page = self.total_page_count + 1
            else:
                if self.page <= self.plus:
                    start_page = 1
                    end_page = 2 * self.plus + 1
                else:
                    if (self.page + self.plus) > self.total_page_count:
                        start_page = self.total_page_count - 2 * self.plus
                        end_page = self.total_page_count
                    else:
                        start_page = self.page - self.plus
                        end_page = self.page + self.plus
    
            page_str_list = []
    
            # 首页
            self.query_dict.setlist(self.page_param, [1])
            page_str_list.append('<li><a href="?{}">首页</a></li>'.format(self.query_dict.urlencode()))
    
            # 上一页
            if self.page > 1:
                self.query_dict.setlist(self.page_param, [self.page - 1])
                prev = '<li><a href="?{}">上一页</a></li>'.format(self.query_dict.urlencode())
            else:
                self.query_dict.setlist(self.page_param, [1])
                prev = '<li><a href="?{}">上一页</a></li>'.format(self.query_dict.urlencode())
            page_str_list.append(prev)
    
            # 生成页码
            for i in range(start_page, end_page):
                self.query_dict.setlist(self.page_param, [i])
                if i == self.page:
                    ele = '<li class="active"><a href="?{}">{}</a></li>'.format(self.query_dict.urlencode(), i)
                else:
                    ele = '<li><a href="?{}">{}</a></li>'.format(self.query_dict.urlencode(), i)
                page_str_list.append(ele)
    
            # 下一页
            if self.page < self.total_page_count:
                self.query_dict.setlist(self.page_param, [self.page + 1])
                prev = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
            else:
                self.query_dict.setlist(self.page_param, [self.total_page_count])
                prev = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
    
            page_str_list.append(prev)
    
            # 尾页
            self.query_dict.setlist(self.page_param, [self.total_page_count])
            page_str_list.append('<li><a href="?{}">尾页</a></li>'.format(self.query_dict.urlencode()))
    
            page_string = mark_safe("".join(page_str_list))
            return page_string
    
  2. views.py

    from app01.utils.pagination import Pagination
    
    def pretty_list(request):
        """靓号管理"""
        data_list = {}
        search_data = request.GET.get('q', "")
        if search_data:
             data_list["mobile__contains"] = search_data
    
        queryset = models.PrettyNum.objects.filter(**data_list).order_by("-level")
    
        page_objects = Pagination(request, queryset) # 封装成组件
    
        context = {
            'search_data': search_data,
            'queryset': page_objects.page_queryset,  # 分完页的数据
            "page_string": page_objects.html() # 生成的页码
        }
        return render(request, 'pretty_list.html', context)
    
  3. pretty_list.html

        <div class="clearfix">
            <ul class="pagination">
                {{page_string}}
                <li>
                    <form method="get">
                        <div class="input-group" style="width: 200px">
                            <input type="text" name="page" class="form-control" placeholder="页码">
                            <span class="input-group-btn">
                    <button class="btn btn-default" type="submit">跳转</button>
                </span>
                        </div>
                    </form>
                </li>
            </ul>
        </div>
    

视频3-10还有一半未看。

4 时间选择组件

  1. 效果展示:
![在这里插入图片描述](https://img-blog.csdnimg.cn/38585175e498498b90b21bbcc6371528.png)
  1. 引入 bootstrap-datepicker

    {% block css %}
    	<link rel="stylesheet" href="{% static '/plugins/bootstrap-datepicker-master/dist/css/bootstrap-datepicker.min.css' %}">
    {% endblock %}
    
    {% block js %}
        <script src="{% static '/plugins/bootstrap-datepicker-master/dist/js/bootstrap-datepicker.min.js' %}"></script>
        <script src="{% static '/plugins/bootstrap-datepicker-master/dist/locales/bootstrap-datepicker.zh-CN.min.js' %}"></script>
    {% endblock %}
    
  2. user_add.html 时间函数 ,其中 #dt 对应入职时间的ID

    <input type="text" id="dt" class="form-control" placeholder="入职时间" name="ctime"/>
    
        <script>
            $(function() {
                $('#dt').datepicker({
                    format: 'yyyy-mm-dd',
                    startDate: '0',
                    language: "zh-CN",
                    autoclose: true
                })
            })
        </script>
    
  3. user_model_form_add.html 时间函数 ,modelform中的ID默认为 ID_字段名,即 #id_create_time

        <script>
            $(function() {
                $('#id_create_time').datepicker({
                    format: 'yyyy-mm-dd',
                    startDate: '0',
                    language: "zh-CN",
                    autoclose: true
                })
            })
        </script>
    

5 BootStrap 样式父类

  1. 重复的内容可以集成为父类, 比如样式设置:

    在这里插入图片描述

    from django import forms
    
    class BootStrapModelForm(forms.ModelForm):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            # 循环找到所有插件,添加class="form-control"
            for name, field in self.fields.items():
                # 字段中有属性,保留原有属性,没有属性,才增加
                if field.widget.attrs:
                    field.widget.attrs["class"] = "form-control"
                    field.widget.attrs["placeholder"] = field.label
                else:
                    field.widget.attrs = {
                        "class": "form-control",
                        "placeholder": field.label
                    }
    
  2. 之后,所有涉及到样式设置的 ModelForm 可以直接继承 BootStrap ,比如:

    class UserModelForm(BootStrapModelForm):
    

6 views 函数拆分

由于已经实现了许多功能,原本的 views 函数显得复杂,此时可以新建一个 views 目录,将各个功能拆分成若干个 .py 文件。

注意,原本的 views.py 需要删除。

在这里插入图片描述

urls.py 也需要修改。

在这里插入图片描述

注意, models.py 函数不能像这样拆分。

7 案例:管理员

7.1


参考资料:

  1. Windows安装mysql详细步骤(通俗易懂,简单上手)
  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值