django+xadmin学习笔记

第1章 强力django+杀手级xadmin 打造上线标准的在线教育平台-导学

课程简介和学习安排

1-1 强力django+杀手级xadmin 打造上线标准的在线教育平台


第2章 开发环境搭建-linux

本章节将会带领大家在windows上通过虚拟机安装linux,并在linux上安装python、pycharm、navicat、python和虚拟环境等课程必须的软件

2-1 课程中会用到的开发环境介绍 (03:06)

  • IDE: pycharm
  • 数据库: mysql, navicat
  • 编程语言: python3.7
  • 虚拟环境: virtualenvwrapper

2-2 如何在windows上安装linux-上 (13:39)

2-3 如何在windows上安装linux-下 (14:24)

  • 安装的是 优麒麟 https://www.ubuntukylin.com/

2-4 python的安装和配置 (12:30)

  • python的依赖包 http://projectsedu.com/2019/11/15/centos7-%E4%B8%8B%E9%80%9A%E8%BF%87nginx-uwsgi%E9%83%A8%E7%BD%B2django%E5%BA%94%E7%94%A8/

2-5 虚拟环境的安装和配置 (15:07)

  • 安装虚拟环境 http://projectsedu.com/2019/11/15/centos7-%E4%B8%8B%E9%80%9A%E8%BF%87nginx-uwsgi%E9%83%A8%E7%BD%B2django%E5%BA%94%E7%94%A8/
- 安装 virtualenvwrapper, 后面的中括号,加上之后是使用的是豆瓣源,速度更快.
pip3 install virtualenvwrapper [-i https://pypi.douban.com/simple]
- 查找位置
sudo find / -name virtualenvwrapper.sh 查找位置
sudo find / -name python3  查找位置
- 加到 .bashrc 最后
VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
export WORKON_HOME=$HOME/.virtualenv
source /home/zq/.local/bin/virtualenvwrapper.sh
- 进入虚拟环境
source .bashrc 
mkvirtualenv test # 新建虚拟环境
workon # 列出所有虚拟环境
workon 虚拟环境名称 # 进入虚拟环境
deactivate # 退出虚拟环境
rmvirtualenv 虚拟环境名称  # 删除虚拟环境

2-6 mysql和navicat的安装和配置 (19:24)

  • 安装mysql, Ubuntu下

sudo apt-get install mysql-server

安装好之后 sudo vim /etc/mysql/debian.cnf

在这里插入图片描述
拿到 userpassword 来登录数据库.

mysql -udebian-sys-maint -pBZyX3QSuTEsVyqxQ 可以进入.

2-7 pycharm的安装和配置 (07:46)


第3章 开发环境搭建 -windows

本章节将会带领大家在windows上上安装python、pycharm、navicat、python和虚拟环境等课程必须的软件

3-1 课程中会用到的开发环境介绍 (03:13)

3-2 python、mysql、navicat和pycharm的安装和配置 (23:07)


第4章 开发环境搭建-mac

本章节通过文档的形式详细讲解如何在mac上安装必须的软件:python、pycharm、navicat、python和虚拟环境

4-1 开发环境搭建指南-mac


第5章 Navicat和Pycharm的基础

课程的整个代码开发都是在pycharm中完成的,所以这里会用单独的章节专门讲解pycharm的使用,也会介绍navicat的简单使用

5-1 navicat的简单使用 (11:13)

5-2 pycharm简单介绍(很重要!!!) (11:29)

5-3 如何在pycharm中调试代码 (07:42)

5-4 pycharm中常用的快捷键(很重要!!!) (11:06)


第6章 留言板快速开发【用一个小项目巩固Django基础知识】

通过django简单实现一个留言板功能来回顾django的基础知识, 包括settings的配置、 url配置、 view逻辑、 model设计和templates的显示

6-1 django目录结构解析-1 (12:32)

  • 进入虚拟环境之后安装 pip install django==2.2 -i https://pypi.douban.com/simple

6-2 django目录结构解析-2 (11:44)

在这里插入图片描述

如上,在原来的基础上,新加了一些 目录

  • apps 这个目录是自己新建的,把自己编写的内部的应用都放在这个目录中
  • djangotest 主目录
  • extra_apps 自己新建的,用来放后期引入的外部类
  • media 自己新建,放一些上传类的文件
  • static 自己新建,放一些静态文件
  • requirements.txt 自己新建, 里面写所依赖的包的版本号

6-3 配置url和静态文件 (21:30)

  1. apps/message_form下新建目录templates, 再在templates下新建message_form 目录,加入 message_form.html

  2. 现在应用 apps/message_form/views.py 中加一个函数

from django.shortcuts import render


# Create your views here.

def message_form(request):
    return render(request, "message_form/message_form.html")

  1. apps/message_form 下新加一个 urls.py, 加入如下代码
from django.urls import path
from . import views

app_name = "message_form"
urlpatterns = [
    path("", views.message_form)
]

  1. 在 主目录 Message/urls.py 中修改如下
from django.contrib import admin
from django.urls import path,include


urlpatterns = [
    path('admin/', admin.site.urls),
    path('message_form/', include('apps.message_form.urls'))
]

  1. 在浏览器中, 打开 http://127.0.0.1:8000/message_form/, 即可看到页面.

注意静态文件 static 的位置

  1. 如果用默认的 static,则直接在 应用 message_form 下新建一个 static 即可.
  2. 如果是放在外层统一管理, 则需要在 Message/settings.py 下, 最后行, 加入 STATICFILES_DIRS, 这个参数,是告诉系统,静态文件在那里查找. BASE_DIR 是之前定义的项目所在文件夹.
STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static')
]

6-4 orm和model表设计-1 (11:45)

  1. 按照 mysqlclient pip install mysqlclient
  2. 修改 Message/settings.py 的 数据库配置
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mxonline',
        'USER': 'root',
        'PASSWORD': 'root',
        'HOST': '127.0.0.1'
    }
}
  1. 在目录下运行命令 python3 manage.py makemigrations
  2. 继续 python3 manage.py migrate, 这样, django默认的表就自动导入到 mysqlmxonline数据库中了

6-5 orm和model表设计-2 (10:41)

  1. message_form 下的models.py 中新建model
from django.db import models

# Create your models here.

class Message(models.Model):
    name = models.CharField(max_length=20, verbose_name="姓名")
    email = models.EmailField(verbose_name="邮箱")
    address = models.CharField(max_length=100, verbose_name="联系地址")
    message = models.TextField(verbose_name="留言信息")

    class Meta:
        verbose_name = "留言信息"
        verbose_name_plural = verbose_name
        # db_table = "my_message"

  1. 这个 app 一定要在 Messagesettings.py 中的 INSTALLED_APPS 中注册一下. 不然无法生成表
INSTALLED_APPS = [
    'apps.message_form.apps.MessageFormConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
  1. python3 manage.py makemigrations
  2. python3 manage.py migrate 生成表

文档: https://docs.djangoproject.com/zh-hans/2.0/ref/models/fields/

6-6 model进行增、删、改、查-1 (12:20)

  1. message_form 下的 views.py 中, 取到 models.py 中的数据
from django.shortcuts import render


# Create your views here.
from apps.message_form.models import Message  # 导入 models
def message_form(request):
    all_messages = Message.objects.all()  # 获取所有数据
    for message in all_messages:
        print(message.name)
    return render(request, 'message_form/message_form.html')
  1. all_messages = Message.objects.all() 返回的是一个 querset 对象, 可以进行for循环切片操作, 但是 querset 本身并没有执行sql操作.
  2. 测试输出 sql语句, 这里是查找所有字段
all_messages = Message.objects.all()
sliced_query = Message.objects.all()[:1]  #切片操作
print(all_messages.query)
print(sliced_query.query)
SELECT `message`.`id`, `message`.`name`, `message`.`email`, `message`.`address`, `message`.`message` FROM `message`
SELECT `message`.`id`, `message`.`name`, `message`.`email`, `message`.`address`, `message`.`message` FROM `message`  LIMIT 1

6-7 model进行增、删、改、查-2 (12:04)

  1. Message.objects.all() 查询数据库中的所有字段
  2. filter 来输入条件查找
from django.shortcuts import render


# Create your views here.
from apps.message_form.models import Message
def message_form(request):

    #2. filter
    all_messages = Message.objects.filter(name="bobby1") 
    print(all_messages.query)

    for message in all_messages:
        print(message.name)
    return render(request, 'message_form/message_form.html')
  1. get 来查找数据. get 返回的是一个对象,数据不存在或者多条数据存在会抛出异常
from django.shortcuts import render

# Create your views here.
from apps.message_form.models import Message

def message_form(request):
    # get 返回的是一个对象,数据不存在或者多条数据存在会抛出异常
    try:
        message = Message.objects.get(name="bobby1")
        print(message.name)
    except Message.DoesNotExist as e:
        pass
    except Message.MultipleObjectsReturned as e:
        pass

    return render(request, 'message_form/message_form.html')
  1. 删除数据 delete 方法
all_messages = Message.objects.filter(name="bobby")
all_messages.delete()  # 删除所有数据
  1. save 插入数据, ①如果主键存在则更新,②如果不存在则插入.
# 部分核心代码

    # save 插入数据
    message = Message()
    message.name = "bobby"
    message.email = "bobby@imooc.com"
    message.address = "北京市"
    message.message = "留言"

    message.save()
    

6-8 从前端html页面提取出数据并保存到数据库中 (14:08)

  1. formaction改成我们自己的地址 action='message_form'
  2. 提交之后,报错 Forbidden (403) CSRF verification failed. Request aborted.
  3. 需要在 htmlform 下加一行代码
<!-- 部分代码 -->
...
...
        <input type="submit" class="button" value="提交"/>
    </label>
    {% csrf_token %}
</form>
  1. 在一个方法中, 区别是 post 还是 get , 然后加入数据库
# get 展示页面, post 获取数据
def message_form(request):
    if request.method == "POST":
        # 从html中提取数据保存到数据库中
        name = request.POST.get("name", "")
        email = request.POST.get("email", "")
        address = request.POST.get("address", "")
        messagecontent = request.POST.get("message", "")

        message = Message()
        message.name = name
        message.email = email
        message.address = address
        message.message = messagecontent

        message.save()

    return render(request, 'message_form/message_form.html')

6-9 django的template数据展示 (18:49)

  1. 获取值再给到模板
	#部分代码
    if request.method == "GET":
        all_message = Message.objects.all()
        if all_message:
            message = all_message[0]
        return render(request, 'message_form/message_form.html',{
            "message": message
        })
  1. 这里使用 {'message': message}, 也有一种方法, 可以一步把所有的都给到页面. return render(request, "message_form", locals()", 不建议这么写.
  2. 展示到 模板<input id="name" value="{{ message.name }}" />, 用 {{ message.name }} 的方法
  3. 第一步 代码优化
    if request.method == "GET":
        var_dict = {}
        all_message = Message.objects.all()
        if all_message:
            message = all_message[0]
            var_dict = {
                "message": message
            }
        return render(request, 'message_form/message_form.html', var_dict)
  1. views.py 的方法中,最后必须返回一个 return, 所以上面的 post 最后还要优化一下, 最后加上 return
    if request.method == "POST":
        # 从html中提取数据保存到数据库中
        name = request.POST.get("name", "")
        email = request.POST.get("email", "")
        address = request.POST.get("address", "")
        message_text = request.POST.get("message", "")

        message = Message()
        message.name = name
        message.email = email
        message.address = address
        message.message = message_text
        message.save()
        return render(request, 'message_form/message_form.html', {
            "message": message
        })
  1. 模板的文档 https://docs.djangoproject.com/zh-hans/2.0/ref/templates/builtins/
  2. 模板的 if 用法 <input value="{% if message.name == 'bobby' %}bobbytest{% endif %}" type="text" />
  3. ifequal方法 <input value="{% ifequal message.name|slice:'2' 'bo' %}bobbytest{% endifequal %}" type="text" /> 如果前面2个字母是 bo 的话,则显示 bobbytest, 不然则不显示.

第7章 需求分析和表结构设计–开始搞一个大项目

对系统进行需求分析, 然后设计出django app, 然后对每个app设计相应的django model数据表。系统共有四个app, users处理用户相关;courses处理课程相关;organization处理课程机构相关;operation处理用户操作相关

7-1 需求分析和app设计 (15:48)

  • users 用户相关

  • courses 课程相关

  • organization 机构相关

  • operation 用户操作相关

  • 目录结构和 requirements.txt
    在这里插入图片描述

  • 数据库配置 和 静态文件配置

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mxonline',
        'USER': 'root',
        'PASSWORD': 'root',
        'HOST': '127.0.0.1'
    }
}


STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static')
]

7-2 新建项目和apps (07:22)

  • python3 manage.py startapp users
  • python3 manage.py startapp course
  • python3 manage.py startapp organization
  • python3 manage.py startapp operation

配置到 settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'apps.course.apps.CourseConfig',
    'apps.usrs.apps.UsrsConfig',
    'apps.operation.apps.OperationConfig',
    'apps.organization.apps.OrganizationConfig'
]

7-3 自定义userprofile表覆盖默认的user表 (20:48)

系统自带的用户表无法满足我们项目的需要, 这里需要写方法重载之前的用户表

  • Users.py 下的 models.py 中定义 UserProfile
class UserProfile(AbstractUser):
    nick_name = models.CharField(max_length=50, verbose_name="昵称", default="")
    birthday = models.DateField(verbose_name="生日", null=True, blank=True)
    gender = models.CharField(verbose_name="性别", choices=GENDER_CHOICES, max_length=6)
    address = models.CharField(max_length=100, verbose_name="地址", default="")
    mobile = models.CharField(max_length=11, unique=True, verbose_name="手机号码")
    image = models.ImageField(upload_to="head_image/%Y/%m", default="default.jpg")

    class Meta:
        verbose_name = "用户信息"
        verbose_name_plural = verbose_name

    def __str__(self):
        if self.nick_name:
            return self.nick_name
        else:
            return self.username

我们重载完了 原来的auth_user 表, 要告诉系统, 以后要用我们自己定义的表, 而不是系统默认的 auth_user 表. 在 Mxonline/settings.py 总配置下, 新加一个字段

# 111行 
AUTH_USER_MODEL = "user.UserProfile"
  • 删除之前数据库中的所有表

  • python3 manage.py makemigrations

  • python3 manage.py migrate

因为里面有 图片的ImageField, 所以系统提示要安装 pip install pillow -i https://pypi.douban.com/simple

  • python3 manage.py makemigrations
  • python3 manage.py migrate

7-4 如何避免循环import不同apps中的model (05:15)

在这里插入图片描述

  • 下一层不能导入上一层

7-5 course相关的表结构设计 - 1 (17:23)

courses models.py 又引入其他4个实体

  • Course 课程基本信息
  • Lesson 章节信息
  • Video 视频
  • CourseResource 课程资源

在建立 模型的时候,发现有一些字段是每个模型都需要的,例如 添加时间 等.所以, 可以写一个基本类,来继承 models.Model, 然后让我们的其他模型都继承这个类. 这样就可以实现代码的复用. 这里根据 7-4 的层级, 把这个父类放在 Users下面的models里面

# 基础类
class BaseModel(models.Model):
    add_time = models.DateField(default=datetime.now, verbose_name="添加时间")

    class Meta:
        abstract = True

然后在其他模型中,导入这个父类.

from apps.users.models import BaseModel
...
class Course(BaseModel):
	pass
...

7-6 course相关的表结构设计 - 2 (16:32)

course 课程基本信息 model 设计

class Course(BaseModel):
    name = models.CharField(verbose_name="课程名", max_length=50)
    desc = models.CharField(verbose_name="课程描述", max_length=300)
    learn_times = models.IntegerField(default=0, verbose_name="学习时长(分钟数)")
    degree = models.CharField(verbose_name="难度", choices=(("cj", "初级"), ("zj", "中级"), ("gj", "高级")), max_length=2)
    students = models.IntegerField(default=0, verbose_name="学习人数")
    fav_nums = models.IntegerField(default=0, verbose_name="收藏人数")
    click_nums = models.IntegerField(default=0, verbose_name="点击数")
    category = models.CharField(default=u"后端开发", max_length=20, verbose_name="课程类别")
    tag = models.CharField(default="", verbose_name="课程标签", max_length=10)
    youneed_know = models.CharField(default="", max_length=300, verbose_name="课程须知")
    teachar_tell = models.CharField(default="", max_length=300, verbose_name="老师告诉你")
    detail = models.TextField(verbose_name="课程详情")
    image = models.ImageField(upload_to="courses/%Y/%m", verbose_name="封面图", max_length=100)

    class Meta:
        verbose_name = "课程信息"
        verbose_name_plural = verbose_name

Lesson 章节信息 model 设计

外键数据的删除情况 course = models.ForeignKey(Course, on_delete=models.CASCADE)
这里的 on_delete 分二种常见的情况
on_delete=models.CASCADE 这里表示如果 课程被删除了, 那么自己这个课程章节也删除.
on_delete=models.SET_NULL, 这种就是 对应的外键数据删除了, 自己的数据不动, 而且后面也要加上 on_delete=models.SET_NULL, null=True, blank=True
例如: course = models.ForeignKey(Course, on_delete=models.SET_NULL, null=True, blank=True)

# 课程章节
class Lesson(BaseModel):
    # on_delete 表示对应的外键数据被删除后, 当前的数据应该怎么办.
    # on_delete=models.CASCADE 这里表示如果 课程被删除了, 那么自己这个课程章节也删除.
    # 还有一种 on_delete=models.SET_NULL, 这种就是 对应的外键数据删除了, 自己的数据不动
    course = models.ForeignKey(Course, on_delete=models.CASCADE)
    name = models.CharField(max_length=100, verbose_name=u"章节名")
    learn_times = models.IntegerField(default=0, verbose_name=u"学习时长(分钟数)")
    

    class Meta:
        verbose_name = "课程章节"
        verbose_name_plural = verbose_name

Video 视频 model 设计

class Video(BaseModel):
    lesson = models.ForeignKey(Lesson, verbose_name="章节", on_delete=models.CASCADE)
    name = models.CharField(max_length=100, verbose_name=u"视频名")
    learn_times = models.IntegerField(default=0, verbose_name=u"学习时长(分钟数)")
    url = models.CharField(max_length=200, default="", verbose_name=u"访问地址")

    class Meta:
        verbose_name = "视频"
        verbose_name_plural = verbose_name

CourseResource 课程资源 model 设计

# 课程资源
class CourseResource(BaseModel):
    course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name="课程")
    name = models.CharField(max_length=100, verbose_name=u"名称")
    file = models.FileField(upload_to="course/resourse/%Y/%m", verbose_name="下载地址", max_length=200)

    class Meta:
        verbose_name = "课程资源"
        verbose_name_plural = verbose_name

7-7 课程机构相关的表结构设计 (12:52)

# 城市
class City(BaseModel):
    name = models.CharField(max_length=20, verbose_name=u"城市")
    desc = models.CharField(max_length=200, verbose_name=u"描述")

    class Meta:
        verbose_name = "城市"
        verbose_name_plural = verbose_name


# 课程机构
class CourseOrg(BaseModel):
    name = models.CharField(max_length=50, verbose_name="机构名称")
    desc = models.TextField(verbose_name="描述")
    tag = models.CharField(default="全国知名", max_length=10, verbose_name="机构标签")
    category = models.CharField(default="pxjg", verbose_name=u"机构类别", max_length=4,
                                choices=(("pxjg", "培训机构"), ("gr", "个人"), ("gx", "高校")))
    click_nums = models.IntegerField(default=0, verbose_name=u"点击数")
    fav_nums = models.IntegerField(default=0, verbose_name=u"收藏数")
    image = models.ImageField(upload_to="org/%Y/%m", verbose_name=u"logo", max_length=100)
    address = models.CharField(max_length=150, verbose_name=u"机构地址")
    students = models.IntegerField(default=0, verbose_name=u"学习人数")
    course_nums = models.IntegerField(default=0, verbose_name=u"课程数")
    city = models.ForeignKey(City, on_delete=models.CASCADE, verbose_name=u"所在城市")

    class Meta:
        verbose_name = "课程机构"
        verbose_name_plural = verbose_name

# 课程讲师
class Teacher(BaseModel):
    org = models.ForeignKey(CourseOrg, on_delete=models.CASCADE, verbose_name="所属机构")
    name = models.CharField(max_length=50, verbose_name=u"教师名")
    work_years = models.IntegerField(default=0, verbose_name="工作年限")
    work_company = models.CharField(max_length=50, verbose_name="就职公司")
    work_position = models.CharField(max_length=50, verbose_name="公司职位")
    points = models.CharField(max_length=50, verbose_name="教学特点")
    click_nums = models.IntegerField(default=0, verbose_name="点击数")
    fav_nums = models.IntegerField(default=0, verbose_name="收藏数")
    age = models.IntegerField(default=18, verbose_name="年龄")
    image = models.ImageField(upload_to="teacher/%Y/%m", verbose_name="头像", max_length=100)

    class Meta:
        verbose_name = "讲师"
        verbose_name_plural = verbose_name

此时,回到 课程course/models.py 中, 给 课程信息 Course 加上讲师的外键. 因为每个课程都有一个讲师.

from apps.organization.models import Teacher
...
class Course(BaseModel):
    teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE, verbose_name="讲师")
    ...

7-8 operations相关表结构设计 (16:32)

from django.db import models

from apps.users.models import BaseModel

from django.contrib.auth import get_user_model
from apps.course.models import Course

UserProfile = get_user_model()


# 用户咨询
class UserAsk(BaseModel):
    name = models.CharField(max_length=20, verbose_name="姓名")
    mobile = models.CharField(max_length=11, verbose_name="手机")
    course_name = models.CharField(max_length=50, verbose_name="课程名")

    class Meta:
        verbose_name = "用户咨询"
        verbose_name_plural = verbose_name


# 课程评论
class CourseComments(BaseModel):
    user = models.ForeignKey(UserProfile, verbose_name="用户")
    course = models.ForeignKey(Course, verbose_name="课程")
    comments = models.CharField(200, verbose_name="评论内容")

    class Meta:
        verbose_name = "课程评论"
        verbose_name_plural = verbose_name


# 用户收藏
class UserFavorite(BaseModel):
    user = models.ForeignKey(UserProfile, verbose_name="用户")
    fav_id = models.IntegerField(verbose_name="数据id")
    fav_type = models.IntegerField(choices=((1, "课程"), (2, "课程机构"), (3, "讲师")), default=1, verbose_name="收藏类型")

    class Meta:
        verbose_name = "用户收藏"
        verbose_name_plural = verbose_name


# 用户消息
class UserMessage(BaseModel):
    user = models.ForeignKey(UserProfile, verbose_name="用户")
    message = models.CharField(max_length=200, verbose_name="消息内容")
    has_read = models.BooleanField(default=False, verbose_name="是否已读")

    class Meta:
        verbose_name = "用户消息"
        verbose_name_plural = verbose_name


# 用户和课程之间的关系
class UserCourse(BaseModel):
    user = models.ForeignKey(UserProfile, verbose_name="用户")
    course = models.ForeignKey(Course, verbose_name="课程")

    class Meta:
        verbose_name = "用户课程"
        verbose_name_plural = verbose_name

7-9 通过migrate生成表和本章小结 (07:23)

python3 manage.py makemigrations
python3 manage.py migrate


第8章 xadmin快速搭建后台管理系统

通过xadmin结合第4章设计的model快速的搭建一套完整的后台管理系统;本章首先介绍django admin的简单使用, 然后引出xadmin,在安装xadmin之后将model注册到xadmin中, 最后完成xadmin的全局配置

8-1 通过django的admin快速搭建后台管理系统 (25:19)

  • 权限管理
  • 少前端样式
  • 快速开发

创建一个超级用户 python3 manage.py createsuperuser

http://127.0.0.1:8000/admin/ 进入后台之后, 将语言设置为中文
settings.py 中 将 LANGUAGE_CODE = 'en-us' 修改为 LANGUAGE_CODE = 'zh-hans'
再把 USE_TZ = True 修改为 USE_TZ = False
修改时区 TIME_ZONE = 'Asia/Shanghai'

把我们自己写的models注册到后台中, 在 users 下面的 admin.py

from django.contrib import admin

from apps.users.models import UserProfile

class UserProfileAdmin(admin.ModelAdmin):
    pass
    
admin.site.register(UserProfile, UserProfileAdmin)

后台如下显示

在这里插入图片描述

把上面的红字部分变成中文,需要再 users下面的 apps.py

from django.apps import AppConfig

class UsersConfig(AppConfig):
    name = 'apps.users'
    verbose_name = "用户"

在这里插入图片描述

在后台自己添加管理员的时候, 再登录,发现登录不上,原因是,我们自己添加的管理员的密码,数据库中是没有加密的.
为此, 在 users 下面的 admin.py 中导入 from django.contrib.auth.admin import UserAdmin, 再关联 admin.site.register(UserProfile, UserAdmin). 代码如下


from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from apps.users.models import UserProfile


class UserProfileAdmin(admin.ModelAdmin):
    pass


admin.site.register(UserProfile, UserAdmin)

8-2 更加强大的后台管理系统-xadmin的配置 (16:55)

把准备好的 xadmin 拷贝到目录下.

安装xadmin步骤

  1. 下载xadmin源码
  2. 在settings的INSTALLED_APPS中添加
    crispy_forms 和 xadmin
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'apps.course.apps.CourseConfig',
    'apps.users.apps.UsersConfig',
    'apps.operation.apps.OperationConfig',
    'apps.organization.apps.OrganizationConfig',
    'crispy_forms',
    'xadmin.apps.XAdminConfig',  
]
  1. 安装xadmin的依赖包

我们再 xadmin 文件夹下有一个 requirements.txt文件夹, 可以进入虚拟环境,然后进入目录,运行 pip install -r requirements.txt 来批量安装插件.

  1. 通过migrate生成xadmin需要的表

在之前, 先复制 DjangoUeditor 按照要求来操作. 最后配置变量没有做,但是没有报错了.

python3 manage.py makemigrations xadmin 同步xadmin
python3 manage.py migrate

在这里插入图片描述

  1. 配置一个访问地址 settings.py
from django.contrib import admin
from django.urls import path

import xadmin

urlpatterns = [
    path('admin/', admin.site.urls),
    path('xadmin/', xadmin.site.urls),

]

http://127.0.0.1:8000/xadmin/
在这里插入图片描述

8-3 解决xadmin新建用户出现手机号码重复的问题 (07:36)

users.py 下面的 models.pymobile = models.CharField(max_length=11, verbose_name="手机号码")unique=True, 去掉.

密码验证的配置, 在 settings.py 中的 AUTH_PASSWORD_VALIDATORS 里面的四个. 可以注释掉. 之后重启服务.

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',
    # },
]

8-4 xadmin快速配置列表、搜索、过滤等功能 (16:52)

定义 xadmin.py

import xadmin

from apps.organization.models import Teacher, CourseOrg, City


class TeacherAdmin(object):
    pass


class CourseOrgAdmin(object):
    pass


class CityAdmin(object):
    pass


xadmin.site.register(Teacher, TeacherAdmin)
xadmin.site.register(CourseOrg, CourseOrgAdmin)
xadmin.site.register(City, CityAdmin)

在城市信息中, 添加了1条数据,如下所示,这种样式 City object(1) 需要修改

在这里插入图片描述

如上,需要修改,是修改 models.py 下面的 __str__ 函数.

# 城市
class City(BaseModel):
    name = models.CharField(max_length=20, verbose_name=u"城市")
    desc = models.CharField(max_length=200, verbose_name=u"描述")

    class Meta:
        verbose_name = "城市"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

修改好之后, 页面如下显示

在这里插入图片描述

1. 配置列表显示项目

xadmin.py

class CityAdmin(object):
    list_display = ["id", "name", "desc"]

页面如下显示

在这里插入图片描述

2. 配置搜索栏

class CityAdmin(object):
    list_display = ["id", "name", "desc"]
    search_fields = ["name", "desc"]

在这里插入图片描述

如上, 搜索的话,会搜索我们配置好的 namedesc

3. 配置 过滤器

class CityAdmin(object):
    list_display = ["id", "name", "desc"]  # 列表显示项
    search_fields = ["name", "desc"]  # 模糊搜索的字段
    list_filter = ["name", "desc", "add_time"] # 过滤字段

在这里插入图片描述

4. 直接编辑

class CityAdmin(object):
    list_display = ["id", "name", "desc"]  # 列表显示项
    search_fields = ["name", "desc"]  # 模糊搜索的字段
    list_filter = ["name", "desc", "add_time"]  # 过滤字段
    list_editable = ["name", "desc"] # 直接编辑

在这里插入图片描述
5. 在外键上面加上过滤条件 course__name

class LessonAdmin(object):
    list_display = ["course", "name", "add_time"]  # 列表显示项
    search_fields = ["course", "name"]  # 模糊搜索的字段
    list_filter = ["course__name", "name", "add_time"]  # 过滤字段

在这里插入图片描述

8-5 快速注册model到xadmin中 (18:40)

8-6 xadmin全局配置和本章总结 (08:48)

1. 配置左上角的文件,和右下角的文字

在任意的 xadmin.py 文件中, 编写配置, 再绑定, 下面的GlobalSettings 名字自己取得, 最后绑定上去,所以叫什么名字都可以.

class GlobalSettings(object):
    site_title = "慕学后台管理系统"
    site_footer = "慕课网"
   
xadmin.site.register(xadmin.views.CommAdminView, GlobalSettings)

在这里插入图片描述
在这里插入图片描述
2. 配置后台多皮肤

class BaseSettings(object):
    enable_themes = True
    use_bootswatch = True
   
xadmin.site.register(xadmin.views.BaseAdminView, BaseSettings)

在这里插入图片描述
3. 左侧栏木树效果 和 第一个效果在一起配置

class GlobalSettings(object):
    site_title = "慕学后台管理系统"
    site_footer = "慕课网"
    menu_style = "accordion"
xadmin.site.register(xadmin.views.CommAdminView, GlobalSettings)

在这里插入图片描述


第9章 登录和注册功能开发(短信动态验证码登录)

完成用户注册相关的功能, 包括登录、注册等功能, 本章会深入session和cookie的机制以及通过django form对表单进行验证。注册和验证码登录环境会通过图片验证码防止网络攻击

9-1 配置首页和登录页面 (22:55)

1. 配置首页

settings.py 中 配置

from django.views.generic import TemplateView

import xadmin

urlpatterns = [
	# 下面的方法直接指定 模板.
    path('', TemplateView.as_view(template_name="index.html")),

2. 配置登录页 下面的TemplateView 只能简单的返回html页面,没有办法编写逻辑.

path('login/', TemplateView.as_view(template_name="login.html"), name="login"),

html 中写法, 2种写法都可以

<a style="color:white" class="fr loginbtn" href="/login/">登录</a>
<a style="color:white" class="fr loginbtn" href="{% url 'login' %}">登录</a>

href="{% url 'login' %}" 这里的 login 就是 url 中配置的name

9-2 通过django内置的login完成登录 (27:03)

  1. CBV (class base view) 有利于代码重用,利于维护,后续使用这种方式
  2. FBV(function base view)

1. 在 Users 下面的 views.py 中, 代码如下

from django.shortcuts import render
from django.views.generic.base import View


# Create your views here.
class LoginView(View):
    # 处理get请求
    def get(self, request, *args, **kwargs):
        return render(request, 'login.html')

    # 处理post请求
    def post(self, request, *args, **kwargs):
        pass

2. 在 settins.py 中

from apps.users.views import LoginView

urlpatterns = [
	...
    path('login/', LoginView.as_view(), name="login"),
	...
]

3. 重定向

from django.http import HttpResponseRedirect
from django.urls import reverse

...
# 登录成功之后应该怎么返回页面, 跳转到首页
return HttpResponseRedirect(reverse("index"))

如上 indexurls.py 中定义路径时候的 name

4. 内置方法验证用户是否存在

from django.contrib.auth import authenticate, login  # 通过用户名和密码查询用户是否存在

# 通过用户名和密码查询用户是否存在
user = authenticate(username=user_name, password=password)
if user is not None:
    # 查询到用户
    login(request, user)
    # 登录成功之后应该怎么返回页面, 跳转到首页
    return HttpResponseRedirect(reverse("index"))
else:
    # 未查询到用户
    return render(request, "login.html", {"msg": "用户名或密码错误!"})

9-3 登录成功之后的思考 (10:15)

1. 根据内置方法, 模板中判断用户是否登录

{% if request.user.is_authenticated %}
     <div>
       	已经登录
    </div>
    {% else %}
    <div>未登录</div>
{% endif %}

9-4 通过form表单对登录框进行验证 (24:49)

  1. 在页面上留一个 HTML , <div>{{ msg }}</div>
  2. 逻辑 中, return render(request, "login.html", {"msg": "请输入密码"}), 这样就可以显示对应的消息信息.
# 数据验证
if not user_name:
    return render(request, "login.html", {"msg": "请输入用户名"})

if not password:
    return render(request, "login.html", {"msg": "请输入密码"})

if len(password) < 3:
    return render(request, "login.html", {"msg": "密码格式不正确"})

简化上面代码

  1. users 目录下, 新建1个 forms.py 文件.

在这里插入图片描述

  1. 代码 如下
from django import forms


class LoginForm(forms.Form):
    # 名称要和 表单提交时字段名称一致
    username = forms.CharField(required=True, min_length=2)
    password = forms.CharField(required=True, min_length=3)
  1. views.py 中调用
from apps.users.forms import LoginForm  # 表单验证类

# 处理post请求
def post(self, request, *args, **kwargs):

    # 表单验证
    login_form = LoginForm(request.POST)
    if login_form.is_valid():
    	//... 成功验证后的代码
    else:
    	//... 验证失败后的代码
  1. form 除了做验证,还可以做一些数据处理,例如, 从form中获取值
# 数据的获取, 从form中获取.
user_name = login_form.cleaned_data['username']
password = login_form.cleaned_data['password']
  1. 目前是,点击表单,如果错误,则表单之前填入的数据都没有了,可以用 form来完善这个问题.
return render(request, "login.html", {"login_form": login_form})
...
模板中
value="{{ login_form.username.value }}"

9-5 退出登录接口开发 (09:55)

  1. 判断用户是否登录 request.user.is_authenticated, 登录之后,直接跳转到 首页.
# 判断用户是否登录
if request.user.is_authenticated:
    # 登录之后直接跳转到首页
    return HttpResponseRedirect(reverse("index"))
return render(request, 'login.html')
  1. 退出逻辑 django.contrib.auth 下面的 logout
from django.contrib.auth import logout  # 通过用户名和密码查询用户是否存在
...
# 退出方法
class LogoutView(View):
    # 处理get请求
    def get(self, request, *args, **kwargs):
        logout(request)
        # 退出之后重定向到首页
        return HttpResponseRedirect(reverse("index"))
  1. urls.py 中配置 url
from apps.users.views import LogoutView
...
urlpatterns = [
	...
    path('logout/', LogoutView.as_view(), name="logout"),
	...
]
  1. 配置到 模板中
<a class="fr" href="{% url 'logout' %}">退出</a>

9-6 通过云片网发送短信验证码 (20:32)

云片网 https://www.yunpian.com

  1. apps 下新建一个 python文件夹 utils, 再建立1个 YunPian.py
import requests


def send_simgle_sms(apikey, code, mobile):
    # 发送单条短信
    url = "https://sms.yunpian.com/v2/sms/single_send.json"
    text = "[测试]您的验证码是{}.如非本人操作,请忽略本短信.".format(code)

    res = requests.post(url, data={
        "apikey": apikey,
        "mobile": mobile,
        "text": text
    })

    return res


if __name__ == "__main__":
    res = send_simgle_sms("d4jiljisdf323slidjflw", "123456", "18812345678")
    import json

    res_json = json.loads(res.text)
    code = res_json["code"]
    msg = res_json["msg"]

    if code == 0:
        print("发送成功")
    else:
        print("发送失败:{}".format(msg))
    print(res.text)

9-7 通过django-captcha-simple显示图片验证码 (13:52)

  1. 文档 https://django-simple-captcha.readthedocs.io/en/latest/usage.html#installation
  2. 虚拟环境中安装 pip install django-simple-captcha
  3. settings.py 中加入到 INSTALLED_APPS
INSTALLED_APPS = [
	...
    'DjangoUeditor',
    'captcha'
]
  1. python3 manage.py migrate
  2. 配置 urls.py
from django.conf.urls import url, include
...
path('captcha/', include('captcha.urls')),
...
  1. 安装依赖包 apt-get -y install libz-dev libjpeg-dev libfreetype6-dev python-dev 安装过可不装
  2. users文件夹下面的 forms.py
from captcha.fields import CaptchaField

class DynamicLoginForm(forms.Form):
    captcha = CaptchaField()
  1. users文件夹下面的 views.py
from apps.users.forms import LoginForm, DynamicLoginForm
...
# Create your views here.
class LoginView(View):
    # 处理get请求
    def get(self, request, *args, **kwargs):
        # 判断用户是否登录
        if request.user.is_authenticated:
            # 登录之后直接跳转到首页
            return HttpResponseRedirect(reverse("index"))

        login_form = DynamicLoginForm()
        return render(request, 'login.html',
                      {
                          'login_form': login_form
                      })
  1. 在模板html中, 页面如下显示
<div class="form-group marb20 blur" id="jsRefreshCode">
	{{ login_form.captcha }}
</div>

在这里插入图片描述

9-8 图片验证码是如何显示在前端页面中的 (08:08)

9-9 ajax方式完成短信验证码的发送 - 1 (15:30)

  1. 让某一个 url 不验证 csrf, 在 urls.py 中如下
from django.views.decorators.csrf import csrf_exempt

urlpatterns = [
	...
    # 短信验证接口
    path('send_sms/', csrf_exempt(SendSmsView.as_view()), name="send_sms"),
	...
]
  1. 把需要的配置都写在 settins.py 中, 之后哪里用到就引用,然后调用
# 云片网相关设置
yp_apikey = "sdfsfasdfsd"

# 调用
from Mxonline.settings import yp_apikey

# print(yp_apikey)

  1. 把 dict 转成 json 传递给浏览器
from django.http import JsonResponse

...
return JsonResponse({})

9-10 ajax方式完成短信验证码的发送 - 2 (16:27)

9-11 通过redis记录发送的验证码 (20:08)

  1. redis k-v 数据库,出入值的时候,可以设定一个时间,如果过期了,就会被 redis自动清理掉
  2. 安装 redis

ubuntu
sudo apt-get install redis-server
sudo apt-get install redis-cli

mac
brew install redis@3.2

配置环境变量 .bash_profile

> # redis环境变量
> export PATH=$PATH:/usr/local/opt/redis@3.2/bin

启动 brew services start redis@3.2
连接 redis-cli
退出 quit

常用的操作
flushdb 清空数据
keys * 查看key
set "mobile" "18888888888" 把 mobile 设置为 18888888888
get "mobile" 获取 mobile的值

安装 python redis 的驱动

  1. https://github.com/andymccurdy/redis-py 安装文档
  2. pip install redis
  3. 在目录下建立一个 python 目录, 新建 redis_test.py 文件
import redis

r = redis.Redis(host='localhost', port=6379, db=0, charset='utf8', decode_responses=True)

r.set("mobile", "123")
r.expire("mobile", 1)  # 1秒之后过期

# 延迟1秒
import time

time.sleep(1)
print(r.get("mobile"))  # None
  1. 如上, r.expire("mobile", 1) # 1秒之后过期 这一条是设置过期时间, 过了时间就到期了.

项目中使用 redis

  1. settings.py 配置文件中, 写入 redis的配置
# redis相关配置
REDIS_HOST = "127.0.0.1"
REDIS_PORT = 6379
  1. 在项目中
import redis
from MxOnline.settings import REDIS_HOST,REDIS_PORT

...
r = redis.Redis(host=REDIS_HOST', port=REDIS_PORT, db=0, charset='utf8', decode_responses=True)
...
r.set(str(mobile), code)
r.expire(str(mobile), 60*5)  # 设置验证码五分钟过期

9-12 手机验证码动态登录 - 1 (20:24)

编写一个 view 的几个步骤

  1. 编写 view 代码
  2. 配置 url
  3. 修改 html 页面中相关的地址

forms.py中对某一个字段进行验证 clean_字段名

class DynamicLoginPostForm(forms.Form):
	...
    code = forms.CharField(required=True, min_length=4, max_length=4)
	...
    def clean_code(self):
        pass

9-13 手机验证码动态登录 - 2 (17:38)

9-14 手机注册功能 - 1 (18:54)

  1. 配置 urls.py
from apps.users.views import RegisterView
...
# 注册页面
path('register/', RegisterView.as_view(), name="register"),

  1. 编写 view
class RegisterView(View):
    def get(self, request, *args, **kwargs):
        return render(request, "register.html")

    def post(self, request, *args, **kwargs):
        return render(request, "register.html")
  1. 编写一个 form
class RegisterGetForms(forms.Form):
    captcha = CaptchaField()
  1. form 改写 view
class RegisterView(View):
    def get(self, request, *args, **kwargs):
        register_get_form = RegisterGetForms()
        return render(request, "register.html", {
            "register_get_form": register_get_form
        })

    def post(self, request, *args, **kwargs):
        return render(request, "register.html")
  1. 改写 html
<div> {{register_get_form.captcha}} </div>

9-15 手机注册功能 - 2 (12:25)

9-16 cookie和session的登录原理和区别 (24:04)


第10章 课程机构相关功能开发

完成课程机构的相关功能, 本章会开始django的templates模板继承机制实现模板的重用。 本章包括分页、筛选、收藏等功能, 会讲到如何通过modelform对表单进行验证和保存。

10-1 使用template的static重新引入静态文件 (10:29)

10-2 通过django的template继承机制重构html页面 (22:43)

10-3 显示课程机构列表页数据 - 1 (15:31)

10-4 显示课程机构列表页数据 - 2 (12:45)

10-5 课程机构经典课程展示- 通过model反向去外键关联数据 (22:12)

10-6 课程机构分页 (19:17)

10-7 课程机构的筛选 (20:59)

10-8 通过order_by对课程机构排序 (06:08)

10-9 授课机构排名 - 通过forloop显示索引 (06:36)

10-10 通过url的include机制重新设计url (09:20)

10-11 通过modelform完成用户咨询提交…1 (27:21)

10-12 课程机构详情页 (20:43)

10-13 课程机构详情页2 (15:48)

10-14 机构讲师列表 (09:32)

10-15 机构课程和机构介绍页面开发 (16:12)

10-16 课程机构收藏 - 1 (19:16)

10-17 课程机构收藏 - 2 (17:13)


第11章 课程相关功能开发

11-1 课程列表页开发 - 1 (14:27)

11-2 课程列表页开发 - 2 (11:25)

11-3 热门课程推荐 (12:00)

11-4 课程详情页面显示 (22:46)

11-5 课程详情页的收藏和相关课程推荐 - 1 (19:08)

11-6 课程详情页的收藏和相关课程推荐 - 2 (10:44)

11-7 课程章节信息展示 (20:15)

11-8 如何控制一个view必须登录之后才能访问 (16:55)

11-9 学过该课程的同学还学习过的课程 (09:27)

11-10 课程评论页面开发 - 1 (14:49)

11-11 课程评论页面开发 - 2 (14:16)

11-12 视频播放 (21:51)


第12章 讲师相关功能开发

实现授课讲师的列表页和详情页讲师信息的展示

12-1 讲师列表页开发 (23:16)

12-2 讲师详情页面开发 (20:55)


第13章 个人中心相关功能开发

个人中心功能包括个人信息的展示和修改、 头像修改、密码修改、手机号码修改需要通过短信验证才能修改。 用户学习的课程展示、 用户的收藏展示以及删除收藏功能,最后是用户的个人消息展示

13-1 个人信息显示 (25:35)

13-2 通过django的modelform处理头像修改 (19:29)

13-3 修改个人信息 (09:53)

13-4 修改密码 (19:35)

13-5 修改手机号码 (17:50)

13-6 多种方式实现我的课程页面 (21:36)

13-7 我的收藏 - 课程机构 (18:37)

13-8 我的收藏 - 授课讲师 (10:17)

13-9 我的收藏 - 公开课程 (11:46)

13-10 全局消息提示和个人消息中心 (16:19)


第14章 首页、全局搜索和全局错误页面配置

本章主要是完成首页开发和全局搜索功能的实现,最后配置系统的全局404、403和500页面

14-1 首页 -1 (16:29)

14-2 首页 - 2 (14:53)

14-3 全局搜索功能 - 副本 (16:47)

14-4 如何快速找到所有的连接并快速的配置 (17:29)

14-5 课程详情页显示学习用户 (03:20)

14-6 自定义用户验证模块 (06:57)

14-7 自定义404、500页面 (10:53)


第15章 常见web攻击

本章介绍最常见的sql注入攻击、 xss攻击和csrf攻击的原理以及防护

15-1 sql注入攻击 (17:13)

15-2 xss攻击原理及防范 (10:12)

15-3 csrf攻击与防范 (08:38)


第16章 xadmin更进阶的开发

介绍xadmin更进阶的开发, 加深对xadmin的理解, 让整个后台管理系统完成更加细节的定制, 包括自定义详情页布局、权限的配置和管理、图片的列表页显示、ueditor富文本编辑、数据的导入和导出功能、inline的多表编辑功能等大量的配置功能。

16-1 如何修改编辑页面的布局 (16:14)

16-2 django的组和权限管理配置 (13:44)

16-3 如何定义编辑页面和新增页面的表单 (04:34)

16-4 如何让讲师可以登录xadmin并过滤列表页数据 (09:32)

16-5 重载save_models方法控制保存和修改数据的逻辑 (08:40)

16-6 同一张表的不同数据使用不同的管理器进行管理 (05:50)

16-7 通过在model中定义方法将图片显示在列表页 (07:20)

16-8 配置只读字段、排除字段和默认的排序 (06:20)

16-9 通过model_icon修改model的图标 (07:26)

16-10 通过inline配置多张表的一次性编辑 (09:27)

16-11 集成ueditor富文本编辑器到xadmin中 (27:19)

16-12 数据的导入和导出配置 (12:32)


第17章 生产环境部署-阿里云

本章主要讲解 1. nginx+uwsgi完成线上生成环境的原理 2. mysql的访问权限以及端口绑定配置,以及将本地数据库直接传输到生成环境 3. nginx配置一个虚拟主机,及完成域名和ip地址的转发、 nginx的静态文件代理 4. uwsgi的配置文件的基本配置 5. 代码变更的时候实现uwsgi服务重启

17-1 为什么我们需要云服务器部署 (05:38)

17-2 如何购买阿里云服务器和连接到阿里云服务器 (13:46)

17-3 uwsgi nginx组合介绍 以及python的安装和配置 (10:14)

17-4 mariadb和redis的安装与配置 (14:47)

17-5 nginx和virtualenvwrapper的安装和配置 (06:35)

17-6 如何同步本地代码到阿里云服务器 (13:46)

17-7 uwsgi和nginx配置 (23:47)

17-8 uwsgi和nginx配置 (23:47)

17-9 配置域名和服务器之间的映射 (08:54)

17-10 部署后需要注意的事项以及如何排查日志错误 (11:29)

17-11 centos7 下通过uwsgi,nginx部署django应用

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值