django开发

django开发

1. 创建项目

  • 创建后,删除settings.py 中BASE_DIR / 'templates'
  • 在删除templates文件

2. 创建app

python manage.py startapp myapp
  • 注册app
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp.apps.MyappConfig'
]	

3. 设计表结构

from django.db import models

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


class UserInfo(models.Model):
    """员工表"""
    name = models.CharField(max_length=16, verbose_name="姓名")
    password = models.CharField(max_length=64, verbose_name="密码")
    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 表示表中的哪一列
    # 在django中,数据表中的名称自动加上_id,也就是depart_id
    # on_delete=models.CASCADE 表示级联删除(删除部门,部门下的所有员工都会被删除)
    depart = models.ForeignKey(to="Department", to_field="id", on_delete=models.CASCADE, verbose_name="部门")
    # on_delete=models.SET_NULL, null=True, blank=True 表示置空(删除部门,部门下的所有员工的部门字段置为空)
    #depart = models.ForeignKey(to="Department", to_field="id", on_delete=models.SET_NULL, null=True, blank=True)

    gender_choices = (
        (1, "男"),
        (2, "女"),
    )
    gender = models.SmallIntegerField(choices=gender_choices,verbose_name="性别")

4. 在MySQL中生成表

  • 连接MySQL生成数据库

    create database department DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
    
  • 修改配置文件【settings.py】文件

    DATABASES = {
        'default':{
            'ENGINE':'django.db.backends.mysql',
            'NAME':'department',
            'USER':'root',
            'PASSWORD':'lzy13691870375',
            'HOST':'127.0.0.1',
            'PORT':'3306',
        }
    }
    
  • django命令生成数据库表

    python manage.py makemigrations
    python manage.py migrate
    
    mysql> desc myapp_department;
    +-------+-------------+------+-----+---------+----------------+
    | Field | Type        | Null | Key | Default | Extra          |
    +-------+-------------+------+-----+---------+----------------+
    | id    | bigint      | NO   | PRI | NULL    | auto_increment |
    | title | varchar(32) | NO   |     | NULL    |                |
    +-------+-------------+------+-----+---------+----------------+
    
    mysql> desc myapp_userinfo;
    +------------+---------------+------+-----+---------+----------------+
    | Field      | Type          | Null | Key | Default | Extra          |
    +------------+---------------+------+-----+---------+----------------+
    | id         | bigint        | NO   | PRI | NULL    | auto_increment |
    | name       | varchar(16)   | NO   |     | NULL    |                |
    | password   | varchar(64)   | NO   |     | NULL    |                |
    | age        | int           | NO   |     | NULL    |                |
    | account    | decimal(10,2) | NO   |     | NULL    |                |
    | creat_time | datetime(6)   | NO   |     | NULL    |                |
    | gender     | smallint      | NO   |     | NULL    |                |
    | depart_id  | bigint        | NO   | MUL | NULL    |                |
    +------------+---------------+------+-----+---------+----------------+
    

5. 静态文件管理

app文件下创建static文件和templates文件

6.部门管理

  • 添加
  • 删除
  • 编辑

编辑【urls.py】(动态url):

urlpatterns = [
    path('depart/list/', views.depart_list),
    path('depart/add/', views.depart_add),
    path('depart/delete/', views.depart_delete),
    path('depart/<int:nid>/edit/', views.depart_edit),
]

【view.py】

def depart_edit(request,nid):
    """部门编辑"""
    # 根据nid 编辑他的数据
    if request.method=="GET":
        row_object = models.Department.objects.filter(id=nid).first()
        return render(request,"depart_edit.html",{"row_object":row_object})
    if request.method == "POST":
        depart_title = request.POST.get("title")
        models.Department.objects.filter(id=nid).update(title=depart_title)

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

通过nid参数来编辑数据

<a class="btn btn-primary btn-sm" href="/depart/{{ obj.id }}/edit/">编辑</a>

7. 模板的继承

定义模板:

【layout.html】
<!-- 新建区域 -->
<div>
    {% block content %}{% endblock %}
</div>

继承模板

{% extends 'layout.html' %}

{% block content %}
	<h1>首页</h1>
{% endblock %}

8. 用户管理

mysql> desc myapp_userinfo;
+-------------+---------------+------+-----+---------+----------------+
| Field       | Type          | Null | Key | Default | Extra          |
+-------------+---------------+------+-----+---------+----------------+
| id          | bigint        | NO   | PRI | NULL    | auto_increment |
| name        | varchar(16)   | NO   |     | NULL    |                |
| password    | varchar(64)   | NO   |     | NULL    |                |
| age         | int           | NO   |     | NULL    |                |
| account     | decimal(10,2) | NO   |     | NULL    |                |
| create_time | datetime      | YES  |     | NULL    |                |
| gender      | smallint      | NO   |     | NULL    |                |
| depart_id   | bigint        | NO   | MUL | NULL    |                |
+-------------+---------------+------+-----+---------+----------------+
insert into myapp_userinfo(name,password,age,account,create_time,gender,depart_id) values("李云龙","123456",45,50000,"2020-03-24",1,1);
insert into myapp_userinfo(name,password,age,account,create_time,gender,depart_id) values("张三丰","123456",45,60000,"2021-03-24",1,1);
insert into myapp_userinfo(name,password,age,account,create_time,gender,depart_id) values("周杰伦","123456",45,70000,"2022-03-24",1,1);
mysql> select * from myapp_userinfo;
+----+--------+----------+-----+----------+---------------------+--------+-----------+
| id | name   | password | age | account  | create_time         | gender | depart_id |
+----+--------+----------+-----+----------+---------------------+--------+-----------+
|  4 | 李云龙 | 123456   |  45 | 50000.00 | 2020-03-24 00:00:00 |      1 |         1 |
|  5 | 张三丰 | 123456   |  45 | 60000.00 | 2021-03-24 00:00:00 |      1 |         1 |
|  6 | 周杰伦 | 123456   |  45 | 70000.00 | 2022-03-24 00:00:00 |      1 |         1 |
+----+--------+----------+-----+----------+---------------------+--------+-----------+
def user_list(request):

    # 获取所有用户列表
    user_data = UserInfo.objects.all()

    # 用 python 的语法获取数据
    """
    for obj in user_data:
        # obj.get_gender_display() 表示匹配 男/女,原始字段名为gender,obj.get_字段名称_display()
        # obj.create_time.strftime("%Y-%m-%d") 表示将时间格式转换成固定格式的字符串
        # obj.depart.title 表示获取depart_id对应的部门名称,因为我们在models中定义表时与另外一张表设置了级联关系,有外键
        print(obj.id, obj.name, obj.password, obj.age, obj.account, obj.get_gender_display(), obj.depart.title, obj.create_time.strftime("%Y-%m-%d"))
    """
    
    return render(request, "user_list.html", {"user_data": user_data})

注意: HTML 中获取数据的方式与 Python 中有些不同
例如:
1.HTML中引入函数不能带括号, obj.get_gender_display()
2.日期类型转字符串有Django自己的格式, obj.create_time|date:“Y-m-d”

{% for obj in user_data %}
<tr>
    <th scope="row">{{ obj.id }}</th>
    <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-sm" href="#">编辑</a>
        <a class="btn btn-danger btn-sm" href="#">删除</a>
    </td>
</tr>
{% endfor %}

8.1 新建用户:

  • 原始思路:

    • 数据校验较麻烦

    • 页面没有错误提示

    • 页面上每一个字段都需要重新写一遍

    • 关联的数据,需要手动获取并循环展示在页面

  • Django组件:

    • Form组件(简便)

    • ModelForm组件(最简便)

1 初识Form

1. views.py
class MyForm(Form):
	user = forms.CharField(widget=forms.Input)
	pwd = forms.CharField(widget=forms.Input)
	email = forms.CharField(widget=forms.Input)

def user_add(request):
	if request.method == "GET":
	form = MyForm()
		return render(request, "user_add.html", {"form": form})
2. user_add.html
<form method="post">
	{{ form.user }}
	{{ form.pwd }}
	{{ form.email }}
</form>
<form method="post">
	{% for field in form %}
		{{ field }}
	{% endfor %}
</form>

2. ModelForm

0. 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,verbose_name="部门id")

    #在django中做约束
    gender_choices = (
        (1,"男"),
        (2,"女"),
    )
    gender = models.SmallIntegerField(verbose_name="性别",choices=gender_choices)

1. views.py
class MyForm(ModelForm):
	class Meta:
        model = UserInfo
		field = ["name", "password", "age"]

def user_add(request):
	if request.method == "GET":
	form = MyForm()
		return render(request, "user_add.html", {"form": form})
2. user_add.html
<form method="post">
	{{ form.user }}
	{{ form.pwd }}
	{{ form.email }}
</form>
<form method="post">
	{% for field in form %}
		{{ field }}
	{% endfor %}
</form>

8.2 用户添加(ModelForm)

【views.py】
#################################### Model Form演示 ###############################
from django import forms
from myapp.models import UserInfo

class UserModelForm(forms.ModelForm):
    class Meta:
        model = UserInfo
        fields = ["name", "password", "age", "account", "create_time", "gender", "depart"]
        # 逐一控制标签的样式
        # widgets = {
        #     "name": forms.TextInput(attrs={"class": "form-control"}),
        #     "password": forms.PasswordInput(attrs={"class": "form-control"}),
        # }
    # 循环找到所有的插件,添加 "class": "form-control"
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name, field in self.fields.items():
            # 可以排除指定的字段
            if name == "create_time":
                continue
            print(name, field)
            field.widget.attrs = {"class": "form-control"}

def user_add(request):
    """新建用户(ModelForm版本)"""
    if request.method == "GET":
        form = UserModelForm()
        return render(request, "user_add.html",{"form":form})
    elif request.method == "POST":
        form = UserModelForm(data=request.POST)
        if form.is_valid():
            print(form.cleaned_data)
            # 直接保存到数据库
            form.save()
            return redirect("/user/list/")
        # 校验失败 返回页面错误信息
        return render(request,"user_add.html",{"form":form})
  • 展示部门名字,需要定义__str__函数,返回名称
【models.py】
class Department(models.Model):
    """部门表"""
    title = models.CharField(max_length=32, verbose_name='标题')

    def __str__(self):
        return self.title

8.3 编辑用户

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

【views.py】
def user_edit(request , nid):
    """ 编辑用户 """
    #根据id去数据库获取要编辑的那一行(对象)
    row_object = models.UserInfo.objects.filter(id=nid).first()
    
    if request.method == "GET":
        form = UserModelForm(instance=row_object)
        return render(request,"user_edit.html",{'form':form})

    elif request.method == "POST":

        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})

使用form = UserModelForm(instance=row_object) instance 参数实现ModelForm展示

8.4 删除用户

和之前一样

9. 靓号管理

9.1 表结构

# models.py
class PrettyNum(models.Model):
    """ 靓号表 """
    mobile = models.CharField(verbose_name="手机号" , max_length=11)
    #想要允许为空 null=True , blank=True
    price = models.IntegerField(verbose_name="价格")

    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)

自己模拟语句:

insert into myapp_prettynum(mobile,price,level,status)values("13333333333",19,1,2);

9.2 靓号列表

def pretty_list(request):
    """ 靓号列表 """
    # select * from 表 order by level desc
    prettyNum = models.PrettyNum.objects.all().order_by("-level")
    return render(request,"pretty_list.html",{"prettyNum":prettyNum})

9.3 新建靓号

  • 使用ModelForm实现
# 导入正则表达式
from django.core.validators import RegexValidator
""" 自定义ModelForm """
class PrettyModelForm(forms.ModelForm):
	# 验证方式 1:
    mobile = forms.CharField(
        label = "手机号",
        validators=[RegexValidator(r'^1[3-9]\d{9}$','手机号格式错误')]
    )
    class Meta:
        model = models.PrettyNum
        # fields = "__all__"    选择所有字段
        fields = ["mobile", "price", "level", "status"]
        # exclude = ['level']   排除某个字段

    # 循环找到所有的插件,添加 "class": "form-control"
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name, field in self.fields.items():
            # 可以排除指定的字段
            # if name == "create_time":
            #     continue
            print(name, field)
            field.widget.attrs = {"class": "form-control"}

def pretty_add(request):
    """ 新建靓号 """
    if request.method == "GET":
        form = PrettyModelForm()
        return render(request,"pretty_add.html",{"form": form})
    elif request.method == "POST":
        form = PrettyModelForm(data= request.POST)
        if form.is_valid():
            # 直接保存数据库
            form.save()
            return redirect("/pretty/list/")
        #校验失败
        return render(request,"pretty_add.html",{"form":form})
  • 点击提交

    • 数据校验

      # 验证方式 2:
      def clean_mobile(self):
          txt_mobile = self.cleaned_data["mobile"]
          # 验证不通过
          if len(txt_mobile)!= 11:
              raise ValidationError("长度不为11!")
          #验证通过 返回值
          return txt_mobile
      
    • 保存数据库

    • 跳回靓号列表

9.4 编辑靓号

class PrettyEditModelForm(forms.ModelForm):
    # 验证方式 1:
    mobile = forms.CharField(
        label = "手机号",
        validators=[RegexValidator(r'^1[3-9]\d{9}$','手机号格式错误')]
    )
    # 不给用户编辑手机号
    # mobile = forms.CharField(disabled=True,label="手机号")

    class Meta:
        model = models.PrettyNum
        # fields = "__all__"    选择所有字段
        fields = ["mobile", "price", "level", "status"]
        # exclude = ['level']   排除某个字段

    # 循环找到所有的插件,添加 "class": "form-control"
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name, field in self.fields.items():
            # 可以排除指定的字段
            # if name == "create_time":
            #     continue
            print(name, field)
            field.widget.attrs = {"class": "form-control"}

    # 钩子函数 判断手机号是否存在
    def clean_mobile(self):
        txt_mobile = self.cleaned_data['mobile']

        if len(txt_mobile) != 11:
            # 验证不通过
            raise ValidationError('格式错误')

        # exclude 表示排除哪一个数据
        # self.instance.pk 表示当前编辑的哪一行 id
        exists_data = models.PrettyNum.objects.exclude(id=self.instance.pk).filter(mobile=txt_mobile).exists()
        if exists_data:
            raise ValidationError("手机号已存在")
        # 验证通过
        return txt_mobile


def pretty_edit(request,nid):
    """ 编辑靓号 """
    row_object = models.PrettyNum.objects.filter(id=nid).first()
    if request.method=="GET":
        form = PrettyEditModelForm(instance=row_object)
        return render(request,"pretty_edit.html",{"form": form})

    elif request.method=="POST":
        form = PrettyEditModelForm(data = request.POST,instance=row_object)
        if form.is_valid():
            form.save()
            return redirect("/pretty/list/")
        return render(request,"pretty_edit.html",{"form":form})

不允许手机号重复:

  • 添加: 如果手机号已存在,提示"手机号已存在"
# 验证方式 2  验证是否存在:
def clean_mobile(self):
    txt_mobile = self.cleaned_data["mobile"]
    # 验证是否存在
    exist = models.PrettyNum.objects.filter(mobile=txt_mobile).exists()
    if exist:
        raise ValidationError("手机号已存在")
    #验证通过 返回值
    return txt_mobile
  • 编辑: 如果手机号除了当前手机号以外已存在,提示"手机号已存在"
def clean_mobile(self):
    txt_mobile = self.cleaned_data['mobile']

    if len(txt_mobile) != 11:
        # 验证不通过
        raise ValidationError('格式错误')

    # exclude 表示排除哪一个数据
    # self.instance.pk 表示当前编辑的哪一行 id
    exists_data = models.PrettyNum.objects.exclude(id=self.instance.pk).filter(mobile=txt_mobile).exists()
    if exists_data:
        raise ValidationError("手机号已存在")
    # 验证通过
    return txt_mobile

9.5 搜索手机号

query1 = PrettyNum.objects.filter(mobile=15576547933, id=4)
print(query1)
# 如果是空字典,表示获取所有
query_dict = {"mobile": "15576547933", "id": 4}
query2 = PrettyNum.objects.filter(**query_dict)
print(query2)
PrettyNum.objects.filter(id=4)			# 等于4
PrettyNum.objects.filter(id__gt=4)		# 大于4
PrettyNum.objects.filter(id__gte=4)		# 大于等于4
PrettyNum.objects.filter(id__lt=4)		# 小于4
PrettyNum.objects.filter(id__lte=4)		# 小于等于4

PrettyNum.objects.filter(mobile__startswith="1999")		# 筛选出以"1999"开头的
PrettyNum.objects.filter(mobile__endswith="1999")		# 筛选出以"1999"结尾的
PrettyNum.objects.filter(mobile__contains="1999")		# 筛选出包含"1999"开头的

使用get请求来获取用户输入内容,然后来查询

# views.py
def pretty_list(request):
    """ 靓号列表 """
    data_dict={}
    search_data = request.GET.get("q","")
    if search_data:
        data_dict["mobile__contains"] = search_data

    # select * from 表 order by level desc
    prettyNum = models.PrettyNum.objects.filter(**data_dict).order_by("-level")
    return render(request,"pretty_list.html",{"prettyNum":prettyNum,"search_data":search_data})

9.6 分页

def pretty_list(request):
    # for i in range(100,1000):
    #     models.PrettyNum.objects.create(mobile="18888888{}".format(str(i)),price=88,level=2,status=2)

    """ 靓号列表 """
    data_dict = {}
    search_data = request.GET.get("q", "")
    if search_data:
        data_dict["mobile__contains"] = search_data

    # 1.根据用户访问的页码,计算起始位置和末尾位置
    page = int(request.GET.get("page", 1))
    page_size = 8
    start = (page - 1) * page_size
    end = page * page_size

    # 配置页码页面
    # select * from 表 order by level desc
    prettyNum = models.PrettyNum.objects.filter(**data_dict).order_by("-level")[start:end]

    # 数据总条数
    total_count = models.PrettyNum.objects.filter(**data_dict).order_by("-level").count()

    # 计算总页码
    total_page, div = divmod(total_count, page_size)
    if div:
        total_page += 1

    # 计算出当前页的前5页和后5页
    plus = 5

    if total_page <= 2 * plus + 1:  # 判断总页数是否大于11,数据库数据较少的情况
        start_page = 1
        end_page = total_page
    else:
        # 数据库数据大于11页
        # 当前页小于等于5时
        if page <= plus:
            start_page = 1
            end_page = 2 * plus + 1
        # 当前页
        elif page + plus >= total_page:
            start_page = total_page - 2 * plus
            end_page = total_page
        else:
            start_page = page - plus
            end_page = page + plus + 1

    page_str_list = []

    #首页
    page_str_list.append('<li class="page-item "><a class="page-link" href="/pretty/list/?page=1">首页</a></li>')
    # 上一页
    if page > 1:
        prev = '<li class="page-item "><a class="page-link" href="/pretty/list/?page={}">上一页</a></li>'.format(page - 1)
    else:
        prev = '<li class="page-item "><a class="page-link" href="/pretty/list/?page={}">上一页</a></li>'.format(1)
    page_str_list.append(prev)


    for i in range(start_page, end_page + 1):
        if i == page:
            ele = '<li class="page-item active"><a class="page-link" href="/pretty/list/?page={}">{}</a></li>'.format(i,
                                                                                                                      i)
        else:
            ele = '<li class="page-item"><a class="page-link" href="/pretty/list/?page={}">{}</a></li>'.format(i, i)
        page_str_list.append(ele)

    # 下一页
    if page < total_page:
        prev = '<li class="page-item "><a class="page-link" href="/pretty/list/?page={}">下一页</a></li>'.format(page + 1)
    else:
        prev = '<li class="page-item "><a class="page-link" href="/pretty/list/?page={}">下一页</a></li>'.format(total_page)

    # 尾页

    page_str_list.append('<li class="page-item "><a class="page-link" href="/pretty/list/?page={}">尾页</a></li>'.format(total_page))

    page_string = mark_safe("".join(page_str_list))

    return render(request, "pretty_list.html",
                  {"prettyNum": prettyNum, "search_data": search_data, "page_string": page_string})

9.7 时间插件

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

9.8 ModelForm 和 BootStrap

  • ModelForm 可以帮我们生成HTML标签

    class UserModelForm(forms.ModelForm):
        class Meta:
            model = UserInfo
            fields = ["name", "password", "age", "account", "create_time", "gender", "depart"]
            
    form = UseModelForm()
    
    {{form.name}}	普通的input 框
    
  • 定义插件

    class UserModelForm(forms.ModelForm):
        class Meta:
            model = UserInfo
            fields = ["name", "password", "age", "account", "create_time", "gender", "depart"]
            # 逐一控制标签的样式
            widgets = {
                 "name": forms.TextInput(attrs={"class": "form-control"}),
                 "password": forms.PasswordInput(attrs={"class": "form-control"}),
             }
    
    class UserModelForm(forms.ModelForm):
        name = forms.CharField(
            min_length = 3,
            label = "用户名",
            widget = forms.TextInput(attrs={"class":"form-control"})
        )
        class Meta:
            model = UserInfo
            fields = ["name", "password", "age", "account", "create_time", "gender", "depart"]
            # 逐一控制标签的样式
         
    
  • 重新定义的init方法,批量设置

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name, field in self.fields.items():
            # 可以排除指定的字段
            # if name == "create_time":
            #     continue
            print(name, field)
            field.widget.attrs = {"class": "form-control"}
    

10. 管理员操作

添加管理员

新增确认密码字段:

class AdminModelForm(forms.ModelForm):
    confirm_password = forms.CharField(
        label="确认密码",
        widget=forms.PasswordInput
    )

    class Meta:
        model = models.Guanly
        fields = ["name" , "password", "confirm_password"]
        widgets={
            "password":forms.PasswordInput
        }


    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name, field in self.fields.items():
            # 可以排除指定的字段
            # if name == "create_time":
            #     continue
            print(name, field)
            field.widget.attrs = {"class": "form-control"}

验证确认密码是否一致(钩子函数):

def clean_confirm_password(self):
    pwd = self.cleaned_data.get("password")
    confirm = self.cleaned_data.get("confirm_password")
    if confirm!=pwd:
        raise ValidationError("密码不一致,请重新输入")

密码和确认密码报错后不想让它删除:加上关键字 render_value = True

class AdminModelForm(forms.ModelForm):
    confirm_password = forms.CharField(
        label="确认密码",
        widget=forms.PasswordInput(render_value=True)
    )

    class Meta:
        model = models.Guanly
        fields = ["name" , "password", "confirm_password"]
        widgets={
            "password":forms.PasswordInput(render_value=True)
        }

md5 加密

from django.conf import settings
import hashlib
def md5(data_string):
    obj = hashlib.md5(settings.SECRET_KEY.encode('utf-8'))
    obj.update(data_string.encode('utf-8'))
    return obj.hexdigest()
# 加密数据
def clean_password(self):
    pwd = self.cleaned_data.get("password")
    return md5(pwd)

重置密码

使新密码不与旧密码相同:self.instance.pk 获取当前对象的id(主键)

def clean_password(self):
    pwd = self.cleaned_data.get("password")
    md5_pwd = md5(pwd)

    # 去数据库校验当前密码是否一致
    exists = models.Guanly.objects.filter(id=self.instance.pk,password=md5_pwd).exists()
    if exists:
        raise ValidationError("密码不能与之前的相同")
    return md5(pwd)

用户登入

  • 无状态 & 短连接

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M6fLIQLA-1692900549258)(C:\Users\camel\Documents\WeChat Files\wxid_hfsdabob3o0j22\FileStorage\Temp\1692000273292.png)]

cookie 和 session

http://127.0.0.1:8000/admin/list/
https://127.0.0.1:8000/admin/list/

用户认证

  • 验证输入是否正确
  • 验证用户名和密码是否正确(去数据库校验)
class LoginForm(forms.Form):
    name = forms.CharField(
        label = "用户名",
        widget=forms.TextInput(attrs={"class":"form-control","id":"exampleInputEmail1"," aria-describedby":"emailHelp", "placeholder":"请输入用户名"})
    )
    password = forms.CharField(
        label="密码",
        widget=forms.PasswordInput(render_value=True,attrs={"class":"form-control","id":"exampleInputPassword1", "placeholder":"请输入密码"})
    )

    def clean_password(self):
        pwd = self.cleaned_data.get("password")
        return md5(pwd)

def account_login(request):
    """ 登入 """
    if request.method == "GET":
        form = LoginForm()
        return render(request,"login.html",{"form":form})
    elif request.method == "POST":
        form = LoginForm(data = request.POST)
        if form.is_valid():
            # 验证成功 获取用户名和密码 form.cleaned_data
            # 去数据库校验用户名和密码是否正确
            print(form.cleaned_data)
            # 去数据库校验 用户名和密码是否正确,获取用户对象
            # admin_object = models.Guanly.objects.filter(username = "xxx",password = "xxx").first()
            admin_object = models.Guanly.objects.filter(**form.cleaned_data).first()
            # 输入错误
            if not admin_object:
                form.add_error("password","用户名或者密码错误")
                return render(request,'login.html',{"form":form})

            # 输入正确
            # 网站生成随机字符串; 写到用户浏览器cookie中; 再写入到session中;
            request.session['info'] = {'id':admin_object.id,'name':admin_object.name}
            return redirect('/admin/list/')
        return render(request,"login.html",{"form":form})
  • 登入成功才能有后台管理,没有登入的用户看不到内容,返回登入窗口

    • cookie,随机字符串
    • session,用户信息

    在其他需求登入才能访问的页面中。都需要加入这段代码(也可以用中间件实现)

def index(request):
    info = request.session.get('info')
    if not info:
        return redirect('/login/')
    ...

中间件

  • 定义中间件,新建个目录middleware 目录下新建auth.py

    class M1(MiddlewareMixin):
        """ 中间件1 """
    
        def process_request(self, request):
            print("M1 进来了")
    
        def process_response(self, request, response):
            print("M1 走了")
            return response
    
    class M2(MiddlewareMixin):
        """ 中间件2 """
    
        def process_request(self, request):
            print("M2 进来了")
    
        def process_response(self, request, response):
            print("M2 走了")
            return response
    
    
  • 应用中间件settings.py

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    
        'myapp.middleware.auth.M1',
        'myapp.middleware.auth.M2',
    ]
    
  • 中间件的process_request 方法

    方法中没有返回值则继续往后走
    有返回值 HttpResponse render redirect,则不再继续向后执行
    

中间件实现登入校验

  • 定义中间件,新建个目录middleware 目录下新建auth.py

    class AuthMiddleware(MiddlewareMixin):
        """ 中间件 """
        def process_request(self, request):
            # 排除不需要登入就能访问的页面    获取当前用户URL:request.path_info
            if request.path_info == "/account/login/":
                return
            # 读取当前访问用户的session信息,若能读到,说明登入过,就可以向后走
            info_dict = request.session.get("info")
            print(info_dict)
            # 如果登入过
            if info_dict:
                return
            # 没有登入过
            return  redirect('/account/login/')
    
        def process_response(self, request, response):
            print("M1 走了")
            return response
    
    
  • 应用中间件settings.py

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
        'myapp.middleware.auth.AuthMiddleware',
    ]
    
  • 中间件的process_request 方法

    方法中没有返回值则继续往后走
    有返回值 HttpResponse render redirect,则不再继续向后执行
    

注销

def account_logout(request):
    """ 注销 """
    request.session.clear()
    return redirect('/account/login/')
HTML中使用{{ request.session.info.name }} 显示用户名称

图片验证码

生成图片

pip install pillow
  • 生成图片验证码函数:
import random
from PIL import Image,ImageDraw,ImageFont,ImageFilter

"""
生成随机图片验证码
"""
def check_code(width=120, height=30, char_length=5, font_file='kumo.ttf', font_size=28):
    """
    生成随机图片验证码
    """
    code = []
    img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
    draw = ImageDraw.Draw(img, mode='RGB')

    def rndChar():
        """
        生成随机字母
        :return:
        """
        return chr(random.randint(65, 90))

    def rndColor():
        """
        生成随机颜色
        :return:
        """
        return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))

    # 写文字
    font = ImageFont.truetype(font_file, font_size)
    for i in range(char_length):
        char = rndChar()
        code.append(char)
        h = random.randint(0, 4)
        draw.text([i * width / char_length, h], char, font=font, fill=rndColor())

    # 写干扰点
    for i in range(40):
        draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())

    # 写干扰圆圈
    for i in range(40):
        draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())

    # 画干扰线
    for i in range(5):
        x1 = random.randint(0, width)
        y1 = random.randint(0, height)
        x2 = random.randint(0, width)
        y2 = random.randint(0, height)

        draw.line((x1, y1, x2, y2), fill=rndColor())

    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
    return img, ''.join(code)
  • 生成图片:
from io import BytesIO
def image_code(request):
    """ 生成图片验证码 """
    # 调用函数
    img,code_string = check_code()
    print(code_string)
    # 写入session中(以便今后获取验证码校验)
    request.session['image_code'] = code_string
    # 给session设置60秒超时
    request.session.set_expiry(60)

    print(img,code_string)
    # 使用内存文件
    stream = BytesIO()
    img.save(stream,'png')
    return HttpResponse(stream.getvalue())
  • 验证码校验:
def account_login(request):
    """ 登入 """
    if request.method == "GET":
        form = LoginForm()
        return render(request,"login.html",{"form":form})

    elif request.method == "POST":
        form = LoginForm(data = request.POST)
        if form.is_valid():
            # 验证码校验
            user_input_code = form.cleaned_data.pop('image_code') # 获取用户填写的code 并且剔除去
            code = request.session.get('image_code',"")

            if code.upper()!=user_input_code.upper():      # 验证码填写错误
                form.add_error("image_code", "验证码错误")
                return render(request, 'login.html', {"form": form})
  • 设置session保存7天
# session 保存7天
request.session.set_expiry(60 * 60 * 24 * 7)

11. Ajax 请求

浏览器向网站发送请求时,URL 和 表单的形式提交

  • GET
  • POST

特点:页面刷新。

Ajax可以实现向后台偷偷发送请求

  • 依赖jQuery

  • 编写ajax代码

    $.ajax({
    	url:"发送的地址",
    	type: "get",
    	data:{
    		n1:123,
    		n2:456
    	},
    	success:function(res){
    		console.log(res);
    	}
    })
    

11.1 get请求

<script type="text/javascript">
    function clickMe(){
        $.ajax({
            url:'/task/ajax/',
            type:'get',
            data:{
                n1:123,
                n2:456,
            },
            success:function(res){
                console.log(res)
            }
        })
    }
</script>
path('task/ajax/', task.task_ajax),
def task_ajax(request):
    print(request.GET)
    return HttpResponse("成功了")

11.2 POST请求

添加 @csrf_exempt 可以免除 {% csrf_token %}

from django.shortcuts import render,HttpResponse
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def task_ajax(request):
    print("get请求: ", request.GET)
    print("post请求: ", request.POST)
    return HttpResponse("成功了")

11.3 绑定事件

<script type="text/javascript">
    $(function(){
        // 页面加载完成后代码自动执行
        bindBtn1Event();
    })

    function bindBtn1Event()
    {
        $("#btn1").click(function () {
            $.ajax({
                url: '/task/ajax/',
                type: 'get',
                data: {
                    n1: 123,
                    n2: 456,
                },
                success: function (res) {
                    console.log(res)
                }
            })
        })
    }

11.4 ajax请求的返回值

以JSON的方式返回数据

  • 前端需要加上 dataType:“JSON”
<script type="text/javascript">
    $(function(){
        // 页面加载完成后代码自动执行
        bindBtn1Event();
    })

    function bindBtn1Event()
    {
        $("#btn1").click(function () {
            $.ajax({
                url: '/task/ajax/',
                type: 'get',
                data: {
                    n1: 123,
                    n2: 456,
                },
                dataType:"JSON",
                success: function (res) {
                    console.log(res);
                    console.log(res.status);
                    console.log(res.data);
                }
            })
        })
    }
</script>
  • 后端可以通过 JsonResponse(data_dict) 返回json数据
@csrf_exempt
def task_ajax(request):
    print("get请求: ", request.GET)
    print("post请求: ", request.POST)

    data_dict = {"status":True,"data":[11,22,33,44]}
    # return JsonResponse(data_dict)

    return HttpResponse(json.dumps(data_dict))
  • 返回的三种方式
{% extends 'layout.html' %}
{% block content %}
    <h1>任务列表</h1>
    <h3> 实例 1</h3>
    <input type="button" value="点击我" id="btn1" onclick="clickMe()">

    <h3> 实例 2</h3>
    <input type="text" id="txtUser" placeholder="姓名">
    <input type="text" id="txtAge" placeholder="年龄">
    <input type="button" id="btn2" value="点击">

    <h3> 实例 3</h3>
    <form id="form3">
        <input type="text" name="User" placeholder="姓名">
        <input type="text" name="Age" placeholder="年龄">
        <input type="text" name="pwd" placeholder="密码">
        <input type="text" name="mobile" placeholder="年龄">

    </form>
    <input type="button" id="btn3" value="点击">

{% endblock %}

{% block js %}
    <script type="text/javascript">
        $(function () {
            // 页面加载完成后代码自动执行
            bindBtn1Event();
            bindBtn2Event();
            bindBtn3Event();
        })

        function bindBtn1Event() {
            $("#btn1").click(function () {
                $.ajax({
                    url: '/task/ajax/',
                    type: 'get',
                    data: {
                        n1: 123,
                        n2: 456,
                    },
                    dataType: "JSON",
                    success: function (res) {
                        console.log(res);
                        console.log(res.status);
                        console.log(res.data);
                    }
                })
            })
        }


        function bindBtn2Event() {
            $("#btn2").click(function () {
                $.ajax({
                    url: '/task/ajax/',
                    type: 'get',
                    data: {
                        name: $("#txtUser").val(),
                        age: $("#txtAge").val(),
                    },
                    dataType: "JSON",
                    success: function (res) {
                        console.log(res);
                        console.log(res.status);
                        console.log(res.data);
                    }
                })
            })
        }

        function bindBtn3Event() {
            $("#btn3").click(function () {
                $.ajax({
                    url: '/task/ajax/',
                    type: 'get',
                    data: $("#form3").serialize(),
                    dataType: "JSON",

                    success: function (res) {
                        console.log(res);
                        console.log(res.status);
                        console.log(res.data);
                    },
                })
            })
        }

    </script>
{% endblock %}

11.5 ajax实现添加数据

@csrf_exempt
def task_add(request):
    print(request.POST)
    #1. 用户发来的数据校验
    form = TaskModelForm(data = request.POST)
    # 成功
    if form.is_valid():
        form.save()
        data_dict = {"status": True}	# 传入后台status为True
        return HttpResponse(json.dumps(data_dict))
    # 失败
    else:
        data_dict = {"status": False,'error':form.errors}	# 传入后台status为False
        return HttpResponse(json.dumps(data_dict,ensure_ascii=False))
  • js代码:

    1. 先滞空所有报错信息

    2. 如果status为True 则显示添加成功

    3. 如果为False,通过一下代码

      $.each(res.error,function (name,data){
          // console.log(name,data);
          $("#id_" + name).next().text(data[0]);
      })
      

      遍历所有错误信息,并且展现出来

<script type="text/javascript">
    $(function () {
        // 页面加载完成后代码自动执行
        bindBtn1Event();
        bindBtn2Event();
        bindBtn3Event();
        bindBtnAddEvent();
    })
    function bindBtnAddEvent() {
        $("#btnAdd").click(function () {
            $(".error-msg").text("")    //先滞空

            $.ajax({
                url: '/task/add/',
                type: 'post',
                data: $("#addForm").serialize(),
                dataType: "JSON",

                success: function (res) {
                    if (res.status){
                        alert("添加成功!")
                    }else{
                        console.log(res.error)
                        $.each(res.error,function (name,data){
                            // console.log(name,data);
                            $("#id_" + name).next().text(data[0]);
                        })

                    }
                },
            })
        })
    }

</script>
  • 使用JS代码 location.reload() 刷新页面

11.6 模态框保存

设置有些数据不需要用户填,但是需要设置默认数据:

# 生成订单号
form.instance.oid = datetime.now().strftime("%Y%m%d%H%M%S") + str(random.randint(1000,9999))
# 设置管理员id   当前登入系统的管理员
form.instance.admin_id = request.session["info"]["id"]

js 详细代码:

<script type="text/javascript">
    $(function () {
        bindBtnSaveEvent()
    })

    function bindBtnSaveEvent() {
        $("#btnSave").click(function () {

            // 清楚错误信息
            $(".error-msg").text("")
            // 向后台发送请求
            $.ajax({
                url: "/order/add/",
                type: "post",
                data: $("#addForm").serialize(),
                dataType: "JSON",
                success: function (res) {
                    if (res.status) {
                        alert("创建成功!");
                        // 清空表单
                        $("#addForm")[0].reset();

                        // 关闭对话框
                        $("#MyModal").modal('hide');

                        // 刷新页面
                        location.reload()

                    } else {
                        // 把错误信息放在对话框中
                        $.each(res.error, function (name, errorList) {
                            $("#id_" + name).next().text(errorList[0])
                        })
                    }
                }
            })
        });
    }
</script>

11.7 删除数据(ajax)

点击删除后,弹出提示框,确认后再删除

后端代码:

path('order/delete/', order.order_delete),
def order_delete(request):
    """ 删除订单 """
    uid = request.GET.get('uid')
    exists = models.Order.objects.filter(id=uid).exists()
    if not exists:
        return JsonResponse({"status":False,"error":"删除失败,数据不存在"})
    else:
        models.Order.objects.filter(id=uid).delete()
        return JsonResponse({"status": True})

前端js代码:

<script type="text/javascript">
    var DELETE_ID;

    $(function () {
        bindBtnSaveEvent()
        bindBtnDeleteEvent()
        bindBtnConfirmDeleteEvent()
    })

    function bindBtnDeleteEvent() {
        $(".btn-delete").click(function () {
            // 展示模态框
            $("#deleteModal").modal("show");
            // 获取当前行的ID并且赋值给全局变量
            DELETE_ID = $(this).attr("uid");

        });
    }

    function bindBtnConfirmDeleteEvent(){
        $("#btnConfirmDelete").click(function (){
           // 点击删除按钮,将全局变量中的ID发送到后台
            $.ajax({
                url:"/order/delete/",
                type:"GET",
                data:{
                    uid : DELETE_ID
                },
                dataType:"JSON",
                success:function (res){
                    if(res.status){
                        // 删除成功
                        // alert("删除成功");
                        // 隐藏删除框
                        $("#deleteModal").modal("hide");
                        // 要删除的id至空
                        DELETE_ID = 0;
                        // 刷新页面
                        location.reload()
                    }else{
                        // 删除失败
                        alert(res.error);
                    }
                }
            })
        });
    }
</script>

11.8 编辑数据

数据库中获取对象时

# 对象,当前行的所有数据
row_object = models.Order.objects.filter(id=uid).first()
row_object.id
# 字典,{"id":1,"title":}
row_object = models.Order.objects.filter(id=uid).values('id','title').first()
# queryset = [obj,obj,obj]
queryset = models.Order.objects.all()

# queryset = [{"id":1,"title":"xx"},{"id":2,"title":"xx"}]
queryset = models.Order.objects.all().values('id','title')

# queryset = [(1,"xx"),(2,"xx")]
queryset = models.Order.objects.all()values_list('id','title')

后端代码:

path('order/detail/', order.order_detail),
def order_detail(request):
    """ 根据ID获取订单数据 """
    # 方式一
    """
    uid = request.GET.get("uid")
    row_object = models.Order.objects.filter(id=uid).first()
    if not row_object:
        return JsonResponse({"status":False,"error":"删除失败,数据不存在"})

    # 从数据库获取一个对象 row_object
    result = {
        "status": True,
        "data":{
            "title": row_object.title,
            "price": row_object.price,
            "status": row_object.status,
        }
    }
    return JsonResponse({"status":True,"data":result})
    """

    uid = request.GET.get("uid")
    # 字典
    row_dict = models.Order.objects.filter(id=uid).values("title","price","status").first()
    if not row_dict:
        return JsonResponse({"status":False,"error":"删除失败,数据不存在"})

    # 从数据库获取一个对象 row_object
    result = {
        "status": True,
        "data":row_dict
    }
    # JSON序列化只能基本数据类型 其他对象类型序列化不了
    return JsonResponse(result)

前端js代码:

<script type="text/javascript">
    $(function () {
        bindBtnSaveEvent()
        bindBtnDeleteEvent()
        bindBtnConfirmDeleteEvent()
        bindBtnEditEvent()
    })
    function bindBtnEditEvent() {
        $(".btn-edit").click(function () {
            // 清空对话框的数据
            $("#addForm")[0].reset();
            
            var uid = $(this).attr("uid");

            // alert("点击了编辑");

            // 发送ajax去获取当前行的数据
            $.ajax({
                url: "/order/detail/",
                type: "get",
                data: {
                    uid: uid
                },
                dataType: "JSON",
                success: function (res) {
                    if (res.status) {
                        console.log(res.data)
                        // 将数据赋值到对话框标签中
                        $.each(res.data,function (name,value){
                            $("#id_" + name).val(value)
                        })

                        // 修改对话框标题
                        $("#ModalLabel").text("编辑");

                        // 点击编辑模态对话框出来
                        $("#MyModal").modal("show");

                    } else {
                        alert(res.error);
                    }
                }
            })

            // 在对话框中默认让用户看到
        })
    }
</script>

11.9 总结

  • 后端代码:
from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from myapp import models
from django import forms
from django.views.decorators.csrf import csrf_exempt
from datetime import datetime
import random
import json

from myapp.utils.pagination import Pagination

class OrderModelForm(forms.ModelForm):
    class Meta:
        model = models.Order
        exclude = ["oid","admin"]

    # 循环找到所有的插件,添加 "class": "form-control"
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name, field in self.fields.items():
            # 可以排除指定的字段
            # if name == "create_time":
            #     continue

            field.widget.attrs = {"class": "form-control"}
def order_list(request):
    queryset = models.Order.objects.all().order_by("-id")
    page_object = Pagination(request, queryset)
    form = OrderModelForm()
    context ={
        "form":form,
        "queryset": page_object.page_queryset,  #分完页的数据
        "page_string": page_object.html(),  #生成的页码
    }
    return render(request,"order_list.html",context)

@csrf_exempt
def order_add(request):
    """ 新建订单(Ajax请求) """
    form = OrderModelForm(data=request.POST)
    if form.is_valid():
        # 生成订单号
        form.instance.oid = datetime.now().strftime("%Y%m%d%H%M%S") + str(random.randint(1000,9999))
        # 设置管理员id   当前登入系统的管理员
        form.instance.admin_id = request.session["info"]["id"]

        # 保存到数据库中
        form.save()
        # return HttpResponse(json.dumps({"status":True}))
        return JsonResponse({"status":True})
    else:
        return JsonResponse({"status":False,"error":form.errors})

def order_delete(request):
    """ 删除订单 """
    uid = request.GET.get('uid')
    exists = models.Order.objects.filter(id=uid).exists()
    if not exists:
        return JsonResponse({"status":False,"error":"删除失败,数据不存在"})
    else:
        models.Order.objects.filter(id=uid).delete()
        return JsonResponse({"status": True})

def order_detail(request):
    """ 根据ID获取订单数据 """
    # 方式一
    """
    uid = request.GET.get("uid")
    row_object = models.Order.objects.filter(id=uid).first()
    if not row_object:
        return JsonResponse({"status":False,"error":"删除失败,数据不存在"})

    # 从数据库获取一个对象 row_object
    result = {
        "status": True,
        "data":{
            "title": row_object.title,
            "price": row_object.price,
            "status": row_object.status,
        }
    }
    return JsonResponse({"status":True,"data":result})
    """

    uid = request.GET.get("uid")
    # 字典
    row_dict = models.Order.objects.filter(id=uid).values("title","price","status").first()
    if not row_dict:
        return JsonResponse({"status":False,"error":"删除失败,数据不存在"})

    # 从数据库获取一个对象 row_object
    result = {
        "status": True,
        "data":row_dict
    }
    # JSON序列化只能基本数据类型 其他对象类型序列化不了
    return JsonResponse(result)


@csrf_exempt
def order_edit(request):
    """ 编辑订单 """
    uid = request.GET.get("uid")
    row_object = models.Order.objects.filter(id = uid).first()
    if not row_object:
        return JsonResponse({"status":False,"tips":"数据不存在,请刷新重试"})
    else:
        form = OrderModelForm(data=request.POST,instance=row_object)
        if form.is_valid():
            form.save()
            return JsonResponse({"status": True})
        return JsonResponse({"status": False, "error": form.errors})

  • 前端js代码:
{% block js %}

    <script type="text/javascript">
        var DELETE_ID;
        var EDIT_ID;

        $(function () {
            bindBtnAddEvent()
            bindBtnSaveEvent()
            bindBtnDeleteEvent()
            bindBtnConfirmDeleteEvent()
            bindBtnEditEvent()
        })

        function bindBtnAddEvent() {
            $("#btnAdd").click(function () {
                // 将正在编辑的ID设置为空
                EDIT_ID = undefined;

                // 清空对话框的数据
                $("#addForm")[0].reset();

                // 修改对话框标题
                $("#ModalLabel").text("新建");

                // 点击新建按钮,显示对话框
                $("#MyModal").modal('show');
            });
        }

        function bindBtnSaveEvent() {
            $("#btnSave").click(function () {

                // 清楚错误信息
                $(".error-msg").text("")

                if (EDIT_ID) {
                    // 编辑
                    doEdit();
                } else {
                    // 添加
                    doAdd();
                }


            });
        }

        function doAdd() {
            // 向后台发送请求(添加ajax请求)
            $.ajax({
                url: "/order/add/",
                type: "post",
                data: $("#addForm").serialize(),
                dataType: "JSON",
                success: function (res) {
                    if (res.status) {
                        alert("创建成功!");
                        // 清空表单
                        $("#addForm")[0].reset();

                        // 关闭对话框
                        $("#MyModal").modal('hide');

                        // 刷新页面
                        location.reload()

                    } else {
                        // 把错误信息放在对话框中
                        $.each(res.error, function (name, errorList) {
                            $("#id_" + name).next().text(errorList[0])
                        })
                    }
                }
            })
        }

        function doEdit() {
            // 向后台发送请求(添加ajax请求)
            $.ajax({
                url: "/order/edit/" + "?uid=" + EDIT_ID,
                type: "post",
                data: $("#addForm").serialize(),
                dataType: "JSON",
                success: function (res) {
                    if (res.status) {

                        // 清空表单
                        $("#addForm")[0].reset();

                        // 关闭对话框
                        $("#MyModal").modal('hide');

                        // 刷新页面
                        location.reload()

                    } else {
                        if (res.tips) {
                            alert(res.tips);
                        } else {
                            // 把错误信息放在对话框中
                            $.each(res.error, function (name, errorList) {
                                $("#id_" + name).next().text(errorList[0])
                            })
                        }

                    }
                }
            })
        }

        // id = btnConfirmDelete
        function bindBtnDeleteEvent() {
            $(".btn-delete").click(function () {
                // 展示模态框
                $("#deleteModal").modal("show");

                // 获取当前行的ID并且赋值给全局变量
                DELETE_ID = $(this).attr("uid");

            });
        }

        function bindBtnConfirmDeleteEvent() {
            $("#btnConfirmDelete").click(function () {
                // 点击删除按钮,将全局变量中的ID发送到后台
                $.ajax({
                    url: "/order/delete/",
                    type: "GET",
                    data: {
                        uid: DELETE_ID
                    },
                    dataType: "JSON",
                    success: function (res) {
                        if (res.status) {
                            // 删除成功
                            // alert("删除成功");

                            // 隐藏删除框
                            $("#deleteModal").modal("hide");
                            // 要删除的id至空
                            DELETE_ID = 0;
                            // 刷新页面
                            location.reload()


                        } else {
                            // 删除失败
                            alert(res.error);
                        }
                    }
                })
            });
        }

        function bindBtnEditEvent() {
            $(".btn-edit").click(function () {
                // 清空对话框的数据
                $("#addForm")[0].reset();

                var uid = $(this).attr("uid");
                EDIT_ID = uid;

                // alert("点击了编辑");

                // 发送ajax去获取当前行的数据
                $.ajax({
                    url: "/order/detail/",
                    type: "get",
                    data: {
                        uid: uid
                    },
                    dataType: "JSON",
                    success: function (res) {
                        if (res.status) {
                            console.log(res.data)
                            // 将数据赋值到对话框标签中
                            $.each(res.data, function (name, value) {
                                $("#id_" + name).val(value)
                            })

                            // 修改对话框标题
                            $("#ModalLabel").text("编辑");

                            // 点击编辑模态对话框出来
                            $("#MyModal").modal("show");

                        } else {
                            alert(res.error);
                        }
                    }
                })

                // 在对话框中默认让用户看到
            })
        }


    </script>
{% endblock %}

12. 图表

  • highchart 国外
  • echarts 国内

动态数据库获取数据形成柱状图

  • 后端代码

    def chart_bar(request):
        """ 构造柱状图数据 """
        # 数据可以从数据库获取
        legend = ['销量', '业绩']
        series_list = [
            {
                "name": '销量',
                "type": 'bar',
                "data": [5, 20, 36, 10, 10, 20]
            },
            {
                "name": '业绩',
                "type": 'bar',
                "data": [70, 200, 36, 66, 15, 10]
            }
        ]
        x_axis = ['1月', '2月', '3月', '4月', '5月', '6月']
    
        result = {
            "status":True,
            "data":{
                "legend":legend,
                "series_list":series_list,
                "x_axis":  x_axis,
            }
        }
        return JsonResponse(result)
    
  • 前端代码

    <body>
        <div id="m1" style="width: 100%;height:400px;">
        </div>
    </body>
    
    <script type="text/javascript">
        $(function () {
            initBar();
        })
    
        /**
         * 初始化柱状图
         */
        function initBar() {
            // 基于准备好的dom,初始化echarts实例
            var myChart = echarts.init(document.getElementById('m1'));
    
            // 指定图表的配置项和数据
            var option = {
                title: {
                    text: '柱状图',
                    textAlign: "auto",
                    left: "center"
    
                },
                tooltip: {},
                legend: {
                    data: [],   // 后台获取
                    bottom: 0,
                },
                xAxis: {
                    data: [], // 后台获取
                },
                yAxis: {},
                series: []  // 后台获取
            };
    		// 通过ajax向后台获取数据
            $.ajax({
                url: "/chart/bar/",
                type: "get",
                dataType: "JSON",
                success: function (res) {
                    // 将后台返回的数据,更新到option中
                    if(res.status){
                        option.legend.data = res.data.legend;
                        option.xAxis.data = res.data.x_axis;
                        option.series = res.data.series_list;
                    }
                    // 使用刚指定的配置项和数据显示图表。
                    myChart.setOption(option);
                }
            })
    
        }
    
    </script>
    

13. 关于文件上传

13.1 基本操作

  • 前端:
{% extends "layout.html" %}
{% block content %}
    <div class="container">
    <form method="post" enctype="multipart/form-data">
         {% csrf_token %}
        <input type="text" name="name">
        <input type="file" name="avatar">
        <input type="submit" value="提交">
    </form>
    </div>
{% endblock %}
  • 后端
def upload_list(request):
    if request.method == "GET":
        return render(request,"upload_list.html")
    elif request.method == "POST":
        # print(request.FILES)
        # 声明图片对象
        file_object = request.FILES.get("avatar")

        # 分块存储图片
        f = open(file_object.name,'wb')
        for chunk in file_object.chunks():
            f.write(chunk)
        f.close()
        return HttpResponse("上传图片成功")

案例: 批量上传数据

  • 前端
<form method="post" enctype="multipart/form-data" action="/depart/multi" style="margin-top: 5px;">
    {% csrf_token %}
    <input type="file" name="exc">
    <a class="btn btn-success btn-sm" href="/depart/add" style="margin-top: 10px;margin-bottom: 10px">
        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
             class="bi bi-file-earmark-plus" viewBox="0 0 16 16">
            <path d="M8 6.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V9.5H6a.5.5 0 0 1 0-1h1.5V7a.5.5 0 0 1 .5-.5z"/>
            <path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
        </svg>
        批量新建部门
    </a>
</form>
  • 后端
def depart_multi(request):
    """ 批量添加部门(Excel) """
    from openpyxl import load_workbook
    # 获取上传的excel文件
    file_object = request.FILES.get('exc')

    #打开Excel文件读取内容
    wb = load_workbook(file_object)
    sheet = wb.worksheets[0]

    #循环获取每一行代码
    for row in sheet.iter_rows(min_row=2):
        exc_title = row[0].value
        # 如果表格数据不存在数据库中,则可以创建
        if not models.Department.objects.filter(title=exc_title).exists():
            models.Department.objects.create(title=exc_title)
    # 重定向网页
    return redirect("/depart/list/")

案例:混合数据(Form)

提交页面时:用户输入数据 + 文件 (输入不能为空)

  • 后端:
class UpForm(forms.Form):
    name = forms.CharField(label="姓名")
    age = forms.IntegerField(label="年龄")
    img = forms.FileField(label="头像")

    # 循环找到所有的插件,添加 "class": "form-control"
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        exclude_fields = ["img"]
        for name, field in self.fields.items():
            # 可以排除指定的字段
            # if name == "create_time":
            #     continue
            if name in exclude_fields:
                continue
            field.widget.attrs = {"class": "form-control"}
            
def upload_form(request):
    """ 上传混合数据 """
    if request.method == "GET":
        form = UpForm()
        return render(request, 'upload_form.html', {"form": form})

    elif request.method == "POST":
        form = UpForm(data=request.POST, files=request.FILES)
        # {'name': '张三', 'age': 21, 'img': <InMemoryUploadedFile: code.png (image/png)>}
        # 1.读取图片内容,写入到文件夹中
        if form.is_valid():

            image_object = form.cleaned_data.get("img")
            # 路径
            db_file_path = os.path.join("static", "img", image_object.name)

            file_path = os.path.join("myapp", db_file_path)
            print(file_path)
            f = open(file_path, mode='wb')
            for chunk in image_object.chunks():
                f.write(chunk)
            f.close()

            # 2.将图片文件路径写到数据库
            models.Boss.objects.create(
                name=form.cleaned_data['name'],
                age = form.cleaned_data['age'],
                img = db_file_path,
            )
            return HttpResponse("成功")
        return render(request, 'upload_form.html', {"form": form})

启用media

django开发过程两个特殊文件:

  • static,存放静态文件的路径,包括:CSS JS 项目图片。
  • media,用户上传的数据的目录。

url.py 中配置:

from django.urls import path, re_path
from django.conf import settings
from django.views.static import serve
urlpatterns = [
	re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}, name='media'),
]

setting.py 中配置

import os
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = '/media/'

后端获取路径:

# media_path = os.path.join(settings.MEDIA_ROOT,image_object.name)   绝对路径
media_path = os.path.join("media", image_object.name)

案例:混合数据(ModelForm)

  • models.py
class Player(models.Model):
    """ 游戏人物 """
    name = models.CharField(verbose_name="游戏ID", max_length=32)
    level_choices = (
        (1, "D"),
        (2, "D+"),
        (3, "C"),
        (4, "C+"),
        (5, "B"),
        (6, "B+"),
        (7, "A"),
        (8, "A+"),
        (9, "S"),
    )
    level = models.SmallIntegerField(verbose_name="段位",choices=level_choices,default=1)
    # 本质上数据库也是CharField,自动保存数据
    img = models.FileField(verbose_name="头像", max_length=128, upload_to='player/')
  • 后端代码
class PlayerModelForm(forms.ModelForm):
    class Meta:
        model = models.Player
        fields = "__all__"

    # 循环找到所有的插件,添加 "class": "form-control"
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        exclude_fields = ["img"]
        for name, field in self.fields.items():
            # 可以排除指定的字段
            # if name == "create_time":
            #     continue
            if name in exclude_fields:
                continue
            field.widget.attrs = {"class": "form-control"}
            
def player_add(request):
    """ 新建玩家"""
    if request.method == "GET":
        form = PlayerModelForm()
        return render(request,"player_add.html",{"form":form})
    elif request.method == "POST":
        form = PlayerModelForm(data=request.POST,files = request.FILES)
        if form.is_valid():
            # 保存到数据库
            form.save()
            return redirect("/player/list/")
        return render(request,"player_add.html",{"form":form})

小节

  • 自己手动写

    file_object = request.FILES.get("exc")
    
  • Form组件

    request.POST
    file_object = request.FILES.get("exc")
    具体文件操作还是要自己写
    
  • MediaForm组件

    - media 文件夹
    - Models.py 中定义类文件要:
    	img = models.FileField(verbose_name = "LOGO", max_length = 128,upload_to = "city/")
    

c,存放静态文件的路径,包括:CSS JS 项目图片。

  • media,用户上传的数据的目录。

url.py 中配置:

from django.urls import path, re_path
from django.conf import settings
from django.views.static import serve
urlpatterns = [
	re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}, name='media'),
]

setting.py 中配置

import os
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = '/media/'

后端获取路径:

# media_path = os.path.join(settings.MEDIA_ROOT,image_object.name)   绝对路径
media_path = os.path.join("media", image_object.name)

案例:混合数据(ModelForm)

  • models.py
class Player(models.Model):
    """ 游戏人物 """
    name = models.CharField(verbose_name="游戏ID", max_length=32)
    level_choices = (
        (1, "D"),
        (2, "D+"),
        (3, "C"),
        (4, "C+"),
        (5, "B"),
        (6, "B+"),
        (7, "A"),
        (8, "A+"),
        (9, "S"),
    )
    level = models.SmallIntegerField(verbose_name="段位",choices=level_choices,default=1)
    # 本质上数据库也是CharField,自动保存数据
    img = models.FileField(verbose_name="头像", max_length=128, upload_to='player/')
  • 后端代码
class PlayerModelForm(forms.ModelForm):
    class Meta:
        model = models.Player
        fields = "__all__"

    # 循环找到所有的插件,添加 "class": "form-control"
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        exclude_fields = ["img"]
        for name, field in self.fields.items():
            # 可以排除指定的字段
            # if name == "create_time":
            #     continue
            if name in exclude_fields:
                continue
            field.widget.attrs = {"class": "form-control"}
            
def player_add(request):
    """ 新建玩家"""
    if request.method == "GET":
        form = PlayerModelForm()
        return render(request,"player_add.html",{"form":form})
    elif request.method == "POST":
        form = PlayerModelForm(data=request.POST,files = request.FILES)
        if form.is_valid():
            # 保存到数据库
            form.save()
            return redirect("/player/list/")
        return render(request,"player_add.html",{"form":form})

小节

  • 自己手动写

    file_object = request.FILES.get("exc")
    
  • Form组件

    request.POST
    file_object = request.FILES.get("exc")
    具体文件操作还是要自己写
    
  • MediaForm组件

    - media 文件夹
    - Models.py 中定义类文件要:
    	img = models.FileField(verbose_name = "LOGO", max_length = 128,upload_to = "city/")