# PythonDjango学习入门 **Repository Path**: suni1024/python-django-241001 ## Basic Information - **Project Name**: PythonDjango学习入门 - **Description**: 武沛齐 python、django 客户管理系统 - **Primary Language**: Python - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 7 - **Forks**: 1 - **Created**: 2024-10-01 - **Last Updated**: 2025-11-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: 学习完成, 案例学习 ## README # django ## 1、安装 ```tex pip install django ``` ```tex c:\python39 - python.exe - Scripts - pip.ext - django-admin.exe 【工具,创建django项目中的文件和文件夹】 - Lib - 内置模块 - site-packages - openpyxl - python-docx - flask - django 【框架源代码】 ``` ## 2、创建项目 > django中项目会有一些默认的文件和默认的文件夹 ### 使用命令行创建(标准的) ```tex C:\python\python3.10.3\Scripts\django-admin.exe startproject 项目名称 C:\python\python3.10.3\Scripts\django-admin.exe startproject day01 ``` ### 默认项目的文件介绍 ``` day01 manage.py 【项目的管理,启动项目,创建app、数据管理-不要动-经常用】 day01 __init__.py settings.py 【项目配置文件-经常修改】 urls.py 【URL和函数的对应关系-经常修改】 asgi.py 【接收网络请求-不改动】 wsgi.py 【接收网络请求-不改动】 ``` ### 知识点 #### django开发过程中的特殊文件夹 ##### 1、static 静态资源文件夹 ```tex css、js、项目图片资源等 ``` ##### 2、media 文件上传的文件夹 ``` 项目运行用户上传文件存放的资源文件夹 ``` ##### 如何启用media ```python # settings.py import os # 媒体文件的根目录 MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 媒体文件的 URL 前缀 MEDIA_URL = '/media/' # ulrs.py from django.conf import settings # 开发环境下的媒体文件访问配置,如果在线上也需要这种使用,if 判断就不要了 if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ``` ## 3、App的创建和说明 ``` -大项目规划 大功能拆分 - app,用户管理 【表结构、函数、HTML模板、CSS 可以独立拥有】 - app,订单管理 【表结构、函数、HTML模板、CSS 可以独立拥有】 - app,物流管理 【表结构、函数、HTML模板、CSS 可以独立拥有】 - app,企业官网 【表结构、函数、HTML模板、CSS 可以独立拥有】 - app,管理后台 【表结构、函数、HTML模板、CSS 可以独立拥有】 - app,api服务 【表结构、函数、HTML模板、CSS 可以独立拥有】 注意:我们开发比较简洁,用不上多个app,一般情况下,一个app即可 ``` ### 创建 ``` python manage.py startapp {{app名}} ``` ``` website __init__.py admin.py 【固定,不用动】【django默认提供的后台管理】 apps.py 【固定,不用动】 【app启动类】 migrations 【固定,不用动】【数据库变更记录】 __init__.py modles.py 【重要】【对数据库操作】 tests.py 【单元测试,固定,不用动】 views.py 【重要】【写函数的】 day01 manage.py 【项目的管理,启动项目,创建app、数据管理-不要动-经常用】 day01 __init__.py settings.py 【项目配置文件-经常修改】 urls.py 【URL和函数的对应关系-经常修改】 asgi.py 【接收网络请求-不改动】 wsgi.py 【接收网络请求-不改动】 ``` ![image-20240912204856039](https://suni1024-markdown.oss-cn-hangzhou.aliyuncs.com/markdown/202409122048097.png) ## 4、快速上手 ### 4-1、在运行django之前,确保app已注册 #### settings.py ![image-20240912210202348](https://suni1024-markdown.oss-cn-hangzhou.aliyuncs.com/markdown/202409122102410.png) ### 4-2、编辑URL与视图函数对应关系 #### urls.py ![image-20240912211121901](https://suni1024-markdown.oss-cn-hangzhou.aliyuncs.com/markdown/202409122111947.png) ### 4-3、编辑视图函数 #### views.py ![image-20240912211044646](https://suni1024-markdown.oss-cn-hangzhou.aliyuncs.com/markdown/202409122110690.png) ### 4-4、启动django项目 #### 4-4-1、命令行启动 ````js python manage.py runserver ```` #### 4-4-2、pycharm启动 ``` 添加 脚本形参 runserver 记得在Alt+F8 中 添加运行配置类型【python】,不然看不见控制台 ``` #### 4-4-3、写一个页面 ``` url=>函数 函数 ``` ![image-20240913141309986](https://suni1024-markdown.oss-cn-hangzhou.aliyuncs.com/markdown/202409131413155.png) #### 4-4-4、templates模板 > ```python > # 1、优先去项目根目录的templates中寻找,看第二张图,如果需要使用,需要配置 > # 2、去app目录下的templates目录中,找这个user_list.html (不是当前py所属app目录下去找,而是根据app的注册顺序,去找) > # settings.py文件 > INSTALLED_APPS = [ > 'django.contrib.admin', > 'django.contrib.auth', > 'django.contrib.contenttypes', > 'django.contrib.sessions', > 'django.contrib.messages', > 'django.contrib.staticfiles', > # 引入app > 'website.apps.WebsiteConfig' > ] > ``` ![image-20240913144217402](https://suni1024-markdown.oss-cn-hangzhou.aliyuncs.com/markdown/202409131442446.png) ##### 如果在项目的setting.py中,配置了DIRS,就会从项目根目录下的templates文件夹中去找 ![image-20240913144131515](https://suni1024-markdown.oss-cn-hangzhou.aliyuncs.com/markdown/202409131441575.png) ![image-20240913144357205](https://suni1024-markdown.oss-cn-hangzhou.aliyuncs.com/markdown/202409131443254.png) #### 4-4-5、static静态文件资源 ![image-20240913151009585](https://suni1024-markdown.oss-cn-hangzhou.aliyuncs.com/markdown/202409131510638.png) ```html {% load static %} Title

用户列表

``` ## 5、模板语法 ```python def tpl(request): name = "孙行者" roles = ["弼马温", "齐天大圣", "斗战胜佛"] user_info = {"name":"唐僧","age":"28","address":"东土大唐"} data_list = [ {"name":"唐僧","age":"28","address":"东土大唐"}, {"name": "孙悟空", "age": "208", "address": "花果山"}, {"name": "猪八戒", "age": "280", "address": "天庭"}, {"name": "沙悟净", "age": "280", "address": "天庭"}, {"name": "白龙马", "age": "280", "address": "西海"} ] return render(request, 'tpl.html', {"name": name, "roles": roles,"user_info":user_info,"data_list":data_list}) ``` ```html {% load static %} 模板语法

模板语法学习

{{ name }}

渲染列表


渲染字典

{{ user_info }}

属性的方式: {{ user_info.name }}


渲染列表里面套字典(JSON list)

{{data_list}}

if 条件语句

{% if name == "马楼精" %}

弼马温

{% elif name == "孙行者" %}

大师兄

{% else %}

孙猴子

{% endif %} ``` ![image-20240913161920319](https://suni1024-markdown.oss-cn-hangzhou.aliyuncs.com/markdown/202409131619405.png) ## 6、请求和响应 ```python from django.shortcuts import render, HttpResponse,redirect import requests def index(request): return HttpResponse("hello python django 玩家") def user_list(request): # return HttpResponse("用户列表") # 去app目录下的templates目录中,找这个user_list.html # 备注:不是当前py所属app目录下去找,而是根据app的注册顺序,去找 return render(request, 'user_list.html') def user_add(request): return render(request, 'user_add.html') def tpl(request): name = "孙行者" roles = ["弼马温", "齐天大圣", "斗战胜佛"] user_info = {"name": "唐僧", "age": "28", "address": "东土大唐"} data_list = [ {"name": "唐僧", "age": "28", "address": "东土大唐"}, {"name": "孙悟空", "age": "208", "address": "花果山"}, {"name": "猪八戒", "age": "280", "address": "天庭"}, {"name": "沙悟净", "age": "280", "address": "天庭"}, {"name": "白龙马", "age": "280", "address": "西海"} ] return render(request, 'tpl.html', {"name": name, "roles": roles, "user_info": user_info, "data_list": data_list}) def news(request): res = requests.get('https://jsonplaceholder.typicode.com/posts') data_list = res.json() print(data_list) return render(request, 'news.html', {"data_list": data_list}) def something(request): # request 是一个对象,封装了用户通发送过来的所有请求相关数据 print(request) # 1、获取请求方式 GET | POST print(request.method) # 2、在URL上传递值 /something/?id=1&search=1024 print(request.GET) # 3、在请求体中提交的数据 print(request.POST) # 4、【响应】HttpResponse 返回字符串给请求者 return HttpResponse("something") # 5、【响应】或者返回一个页面 return render(request, 'something.html' ) # 6、【响应】浏览器重定向到其他页面 return redirect("https://www.baidu.com") def login(request): if request.method == 'GET': return render(request,'login.html') else: username = request.POST.get('user') password = request.POST.get('pwd') if username == 'root' and password == '123456': return redirect("https://www.baidu.com") else: return render(request,"login.html",{"error_message":"用户名或密码错误"}) ``` ### login.html ```html Title
{% csrf_token %} {{ error_message }}
``` ## 7、数据库操作 > 使用Django开发操作数据库更简单,内部提供了ORM框架 ### 7.1 安装第三方模块 ```python pip install mysqlclient ``` ### 7.2 创建并连接数据库 > 我自己电脑本地安装了docker服务,并创建mysql容器 > 主机名 127.0.0.1 端口号 3306 > 用户名 root 密码 123456a #### django连接数据库 ##### 修改settings.py ```python DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": "day15", # 数据库名称 "USER": "root", # 用户名 "PASSWORD": "123456a", # 密码 "HOST": "127.0.0.1", # 主机 "PORT": "3306", # 端口号 } } ``` ### 7.3 django 操作表 - 创建表 - 删除表 - 修改表 ##### 在models.py文件中: ```python from django.db import models # Create your models here. class UserInfo(models.Model): name = models.CharField(max_length=32) password = models.CharField(max_length=64) age = models.IntegerField() content = models.TextField() ``` ##### 执行命令-创建表 ```python python manage.py makemigrations python manage.py migrate ``` ![image-20240917162358219](https://suni1024-markdown.oss-cn-hangzhou.aliyuncs.com/markdown/202409171623354.png) ![image-20240917162500970](https://suni1024-markdown.oss-cn-hangzhou.aliyuncs.com/markdown/202409171625019.png) ##### 修改表与删除字段 ````python from django.db import models # Create your models here. class UserInfo(models.Model): name = models.CharField(max_length=32) password = models.CharField(max_length=64) age = models.IntegerField() # 不需要使用的字段,如果要废弃 or 删除 可以注释掉,表也是一样 content = models.TextField(max_length=255) title = models.CharField(max_length=255,default=True,blank=True ) # 设置默认值为空 class SystemDept(models.Model): name = models.CharField(max_length=64) class SystemRole(models.Model): name = models.CharField(max_length=64) ```` 执行命令 ``` python manage.py makemigrations python manage.py migrate ``` #### 增删改查 ```python # from website.models import SystemDept,UserInfo,SystemRole # UserInfo.objects.create(name="刘备",age=25,title="刘皇叔") from website import models from django.core.serializers import serialize def orm(request): # 【新建操作】 models.UserInfo.objects.create(name="刘备",age=25,title="刘皇叔") models.UserInfo.objects.create(name="关羽", age=25, title="关二哥") models.UserInfo.objects.create(name="张飞", age=25, title="燕人张飞") models.UserInfo.objects.create(name="江东杰瑞", age=25, title="江东杰瑞") models.SystemDept.objects.create(name="技术部") models.SystemDept.objects.create(name="财务部") models.SystemDept.objects.create(name="人事部") # 【删除操作】 models.SystemDept.objects.filter(id=1).delete() # 删除指定id 3的数据 models.SystemDept.objects.all().delete() # 删除这张表中的全部记录数据 # 【查询数据】 # 得到的是 [{},{},{}] QuerySet类型 data_list = models.SystemDept.objects.all() for item in data_list: print(item.name) # 查询数据,得到的是 [{}] QuerySet类型 data_list = models.SystemDept.objects.filter(id=6) print(data_list) # 查询数据,知道只会有1行结果返回时,可以使用first() row_obj = models.SystemDept.objects.filter(id=6).first() #【更新数据】 # 批量更新 UserInfo 表的 content 字段 models.UserInfo.objects.all().update(content="蜀国阵营") # 更新 UserInfo 表的 指定id 记录 content 字段 models.UserInfo.objects.filter(id=4).update(content="江东鼠辈,白衣渡江") return HttpResponse("数据插入成功") ``` ## 案例数据展示 ### 1、用户列表展示 ```html Title

三国群英

添加 {% for item in data_list %} {% endfor %}
默认标题
id 姓名 称谓 阵营 操作
{{item.id}} {{item.name}} {{item.title}} {{item.content}} 删除
``` ```python def info_list(request): data_list = models.UserInfo.objects.all() return render(request,'info_list.html',{"data_list":data_list}) ``` ### 2、添加用户 ```html Title

添加用户

{% csrf_token %}
``` ```python def info_add(request): if request.method == 'GET': return render(request, 'info_add.html') else: name = request.POST.get('name') title = request.POST.get('title') content = request.POST.get('content') models.UserInfo.objects.create(name=name,title=title,content=content) return redirect("/info/list") ``` ## 开始项目 ### 1、建表 #### 用户表存储名称?ID? > 存储部门id,都是数据库范式理论知识,大多如此,节省存储开销。 > 存储部门name,大公司常见,因为查询的次数非常多,链表操作查询耗时,为了加速查找,运行数据冗余 #### 部门ID是否需要约束? > 只能是部门表中已存在的ID #### 当部门被删除、关联的用户怎么办? > 1、同步删除用户【级联删除】 > 2、对应用户记录的部门id字段重置为空 ```python from datetime import timezone from django.db import models # Create your models here. class Deptartment(models.Model): """部门表""" title = models.CharField(verbose_name="部门 ",max_length=48) class UserInfo(models.Model): """员工表""" name = models.CharField(verbose_name='姓名',max_length=48) password = models.CharField(verbose_name='密码',max_length=64) age = models.IntegerField(verbose_name='年龄') account = models.DecimalField(verbose_name='账户余额',max_digits=12,decimal_places=2,default=0) create_time = models.DateTimeField(verbose_name='入职时间') # 无约束,如果插入的id,不存在于部门表中,那么这条记录根本查不到 # depart_id = models.BigIntegerField(verbose_name='部门ID') # 1、有约束 # - to,与哪张表关联 # - to_field 表中的哪一列关联 # 2、django自动 # - 写的是depart,最后生成的字段是depart_id,django会帮我们自动处理好 # depart = models.ForeignKey(to='Deptartment',to_field='id',on_delete=models.CASCADE) # 3、当部门表删除动作 # - 3.1 用户表数据级联删除 depart = models.ForeignKey(to='Deptartment', to_field='id', on_delete=models.CASCADE) # - 3.2 用户表数据置空 # depart = models.ForeignKey(to='Deptartment', to_field='id',null=True,blank=True, on_delete=models.SET_NULL) # 在django中做约束 gender_choices = ( (0,'女'), (1,'男') ) gender = models.SmallIntegerField(verbose_name='性别',choices=gender_choices) ``` ### 2、部门管理 > 体验,原始做法 > django中提供Form和ModleForm组件 #### 部门列表 ### 3、模板继承 #### layout.html 母版页 ```html {% load static %} 部门列表 {% block css %} {% comment %} 继承css样式 或者JS,当我们自己的页面,不要使用特殊的样式或者js时 {% endcomment %} {% endblock %}
{% block content %} {% endblock %}
{% block js %} {% comment %} 继承css样式 或者JS,当我们自己的页面,不要使用特殊的样式或者js时 {% endcomment %} {% endblock %}
``` #### depart_list.html ```html {% extends "layout.html" %} {% load static %} {% block content %}
新建部门
{% for item in data_list %} {% endfor %}
ID 名称 操作
{{item.id}} {{item.title}}
{% endblock %} ``` ### 4、用户管理 > 最原始的方式,基本不用(麻烦) > 1、没有数据校验,非空判断。前端提交过来的数据,有可能是空的,或者 xss攻击注入,前端传递的参数是不可信的 > 2、前端页面上,错误提示没有,每个字段都需要定义一次 > 3、页面上渲染的关联数据,需要手动写SQL获取并传递展示 > > django组件 > 1、Form (小简便) = 原始方式(1、2)+3 > 2、ModelForm 组件 (最简便) 针对数据库中的某个表 #### 4.1 原始做法 ```python def user_add(request): """添加用户""" if request.method == 'GET': """原始做法""" context = { "gender_choices": models.UserInfo.gender_choices, "depart_list": models.Deptartment.objects.all() } return render(request, 'user_add.html', context) # 获取用户post提交过来的数据 name = request.POST.get('name') password = request.POST.get('password') age = request.POST.get('age') account = request.POST.get('account') create_time = request.POST.get('create_time') gender = request.POST.get('gender') depart_id = request.POST.get('depart_id') models.UserInfo.objects.create(name=name, password=password, age=age, account=account, create_time=create_time, gender=gender, depart_id=depart_id) return redirect('/user/list/') ``` ```html {% extends "layout.html" %} {% block content %}
新增用户
{% csrf_token %}
{% endblock %} ``` #### 4.2 ModelForm ```html {% extends "layout.html" %} {% block content %}
新增用户使用ModelForm
{% csrf_token %} {% for field in form %}
{{field}} {{field.errors.0}}
{% endfor %}
{% endblock %} ``` ```python class UserModelForm(forms.ModelForm): """定义一个类去继承forms.ModelForm""" name = forms.CharField(label='姓名',min_length=3,max_length=25) # 设置最小字符,最大字符,可以个性化配置,覆盖写入 class Meta: model = models.UserInfo fields = ['name', 'password', 'age', 'account', 'create_time', 'gender', 'depart'] # 字段名 # widgets = { # # 标签,控制字段渲染时展示的 类型 样式 # "name": forms.TextInput(attrs={"class": "layui-input", "placeholder": "请输入姓名", "lay-verify": "required", "lay-reqtext": "请填写用户名", "lay-affix": "clear"}), # "password": forms.PasswordInput(attrs={"class": "layui-input", "placeholder": "请输入密码", "lay-verify": "required", "lay-reqtext": "请输入密码", "lay-affix": "eye"}), # "age": forms.TextInput( # attrs={"class": "layui-input", "placeholder": "请输入年龄", "lay-verify": "required", # "lay-reqtext": "请输入年龄", "lay-affix": "clear"}), # "account": forms.TextInput( # attrs={"class": "layui-input", "placeholder": "请输入账户余额", "lay-verify": "required", # "lay-reqtext": "请输入账户余额", "lay-affix": "clear"}), # } # 执行init时,重新定义父类的init方法 def __init__(self,*args,**kwargs): super(UserModelForm, self).__init__(*args, **kwargs) # 正确地调用父类的构造函数 for name, field in self.fields.items(): field.widget.attrs = {"class": "layui-input","placeholder": field.label,"lay-affix": "clear"} def user_model_form_add(request): """添加用户使用ModelForm组件""" if request.method == 'GET': form = UserModelForm() return render(request, 'user_model_form_add.html', {"form": form}) # 用户post提交数据,开始数据校验 form = UserModelForm(data=request.POST) if form.is_valid(): # print(form.cleaned_data) # 如果数据校验通过,就会在这里执行 form.save() # 数据保存 return redirect('/user/list/') else: # 数据校验失败,就会在这里执行 print(form.errors) return render(request, 'user_model_form_add.html', {"form": form}) ``` #### 编辑、删除 ```python def user_edit(request,user_id): """编辑用户使用ModelForm组件""" row_obj = models.UserInfo.objects.filter(id=user_id).first() # 查询对应数据 if request.method == 'GET': form = UserModelForm(instance=row_obj)#让实例等于查询的结果,类似于双向绑定 return render(request, 'user_edit.html', {"form": form}) # 开始数据校验 form = UserModelForm(data=request.POST,instance=row_obj) # 这样写就是,我拿到对应id的数据,然后使用MoelForm来更新 if form.is_valid(): # print(form.cleaned_data) # 默认保存的是用户输入的所有数据,如果表里有个特殊字段,我们要写入数据可以 这样 # form.instance.字段名 = 字段值 # 如果数据校验通过,就会在这里执行 form.save() # 数据保存 return redirect('/user/list/') else: # 数据校验失败,就会在这里执行 print(form.errors) return render(request, 'user_edit.html', {"form": form}) ``` ```html {% extends "layout.html" %} {% block content %}
编辑用户使用ModelForm
{% csrf_token %} {% for field in form %}
{{field}} {{field.errors.0}}
{% endfor %}
{% endblock %} ``` ### 5、靓号管理(有东西值得学习) #### 5.1 台账 ```html {% extends "layout.html" %} {% block content %}
新建部门
{% for item in data_list %} {% endfor %}
ID 名称 操作
{{item.id}} {{item.title}}
{% endblock %} ``` ```py def pretty_list(request): """靓号列表""" data_list = models.PrettyNum.objects.all().order_by('-level') # -id desc (倒序) +id asc(正序) return render(request, 'pretty_list.html', {"data_list": data_list}) ``` #### 5.2 新增 ```html {% extends "layout.html" %} {% block content %}
新增靓号使用ModelForm
{% csrf_token %} {% for field in form %}
{{field}} {{field.errors.0}}
{% endfor %}
{% endblock %} ``` ```python from django.core.validators import RegexValidator from django.core.exceptions import ValidationError class PrettyModelForm(forms.ModelForm): """定义一个类去继承forms.ModelForm""" """验证数据方式1""" mobile = forms.CharField( label='靓号号码', min_length=11, max_length=11, validators=[ RegexValidator( regex=r'^1[3-9]\d{9}$', message='手机号格式错误' ), ] ) class Meta: model = models.PrettyNum # fields = ['mobile', 'price', 'level', 'status'] # 自定义字段名 fields = "__all__" # 取全部字段 # exclude = ["status"] # 排除某个字段 # 执行init时,重新定义父类的init方法 def __init__(self,*args,**kwargs): super(PrettyModelForm, self).__init__(*args, **kwargs) # 正确地调用父类的构造函数 for name, field in self.fields.items(): field.widget.attrs = {"class": "layui-input","placeholder": field.label,"lay-affix": "clear"} """ 验证数据方式2 我这里字段有mobile,price,level,status,就可以写钩子方法 def clean_字段名 我这里可以组合使用,例如我先校验手机号是否可以通过正则校验,我在校验是否已存在库中 """ def clean_mobile(self): """构子方法""" txt_mobile = self.cleaned_data['mobile'] # 检查手机号是否满足11位 if len(txt_mobile) != 11: raise ValidationError('手机号码格式错误') # 检查数据库中是否存在相同的手机号 if models.PrettyNum.objects.filter(mobile=txt_mobile).exists(): raise ValidationError('此手机号已存在') # 校验通过 return txt_mobile def pretty_add(request): """新建靓号"使用ModelForm组件""" if request.method == 'GET': form = PrettyModelForm() return render(request, 'pretty_model_form_add.html', {"form": form}) # 用户post提交数据,开始数据校验 form = PrettyModelForm(data=request.POST) if form.is_valid(): # print(form.cleaned_data) # 如果数据校验通过,就会在这里执行 form.save() # 数据保存 return redirect('/pretty/list/') else: # 数据校验失败,就会在这里执行 print(form.errors) return render(request, 'pretty_model_form_add.html', {"form": form}) ``` #### 5.3 编辑 ```html {% extends "layout.html" %} {% block content %}
编辑靓号使用ModelForm
{% csrf_token %} {% for field in form %}
{{field}} {{field.errors.0}}
{% endfor %}
{% endblock %} ``` ```python class PrettyModelFormEdit(forms.ModelForm): """定义一个类去继承forms.ModelForm,我这里编辑,只让他修改等级之类,手机号码不能修改 """ mobile = forms.CharField(label='手机号',disabled=True) class Meta: model = models.PrettyNum fields = ['mobile','price', 'level', 'status'] # 自定义字段名 # exclude = ["'mobile'"] # 排除某个字段 # 执行init时,重新定义父类的init方法 def __init__(self,*args,**kwargs): super(PrettyModelFormEdit, self).__init__(*args, **kwargs) # 正确地调用父类的构造函数 for name, field in self.fields.items(): field.widget.attrs = {"class": "layui-input","placeholder": field.label,"lay-affix": "clear"} """ 验证数据方式2 我这里字段有mobile就可以写钩子方法 def clean_字段名 编辑业务状态下,如果我没有修改手机号,但是我有需求设置了手机号不允许重复怎么办? """ def clean_mobile(self): """构子方法 self.instance.pk 就是当前编辑数据的id """ print(self.instance.pk) txt_mobile = self.cleaned_data['mobile'] if models.PrettyNum.objects.exclude(id=self.instance.pk).filter(mobile=txt_mobile).exists(): # 排除我自己的数据,我去库里找一圈是否还存在手机号相同的情况,如果没有就表示手机号是可以被插入的 raise ValidationError('此手机号已存在') # 校验通过 return txt_mobile def pretty_edit(request,pretty_id): """编辑靓号使用ModelForm组件""" row_obj = models.PrettyNum.objects.filter(id=pretty_id).first() # 查询对应数据 if request.method == 'GET': form = PrettyModelFormEdit(instance=row_obj)#让实例等于查询的结果,双向绑定 return render(request, 'pretty_model_form_edit.html', {"form": form}) # 开始数据校验 form = PrettyModelFormEdit(data=request.POST,instance=row_obj) # 这样写就是,我拿到对应id的数据,然后使用MoelForm来更新 if form.is_valid(): # print(form.cleaned_data) # 默认保存的是用户输入的所有数据,如果表里有个特殊字段,我们要写入数据可以 这样 # form.instance.字段名 = 字段值 # 如果数据校验通过,就会在这里执行 form.save() # 数据保存 return redirect('/pretty/list/') else: # 数据校验失败,就会在这里执行 print(form.errors) return render(request, 'pretty_model_form_edit.html', {"form": form}) ``` #### 5.4 搜索手机号 ##### sql条件查询 ```python models.PrettyNum.objects.filter(id=1,mobile='13116836021') # 写法1 # 写法2 search_dict = {"id":1,"mobile":"13116836021"} models.PrettyNum.objects.filter(**search_dict) ``` ##### id条件查询 ``` python models.PrettyNum.objects.filter(id=12) # 等于 12 models.PrettyNum.objects.filter(id__gt=12) # 大于 12 models.PrettyNum.objects.filter(id__gte=12) # 大于等于 12 models.PrettyNum.objects.filter(id__lt=12) # 小于 12 models.PrettyNum.objects.filter(id__lte=12) # 小于等于 12 search_dict = {"id__gt":12} models.PrettyNum.objects.filter(**search_dict) ``` ##### 字符串搜索 ```python models.PrettyNum.objects.filter(mobile__startswith="13116836021") # 筛选以指定字符开头 models.PrettyNum.objects.filter(mobile__endswith="13116836021") # 筛选以指定字符结尾 models.PrettyNum.objects.filter(mobile__contains="13116836021") # 筛选包含字符串的数据 search_dict = {"mobile__contains":"13116836021"} models.PrettyNum.objects.filter(**search_dict) ``` #### 5.5 分页 ```python models.PrettyNum.objects.all() #搜索全部 modles.PrettyNum.objects.all()[0:10] # 前10条 modles.PrettyNum.objects.filter(id=1)[0:10] # 符合条件 第1页 前10条 modles.PrettyNum.objects.filter(id=1)[10:20] # 符合条件 第2页 ``` ```python def pretty_list(request): """靓号列表""" # 一次性插入300条数据 # for i in range(300): # models.PrettyNum.objects.create(mobile="1311683021",price=1024,level=4,status=2) search_dict = {} search_value = request.GET.get('search') if search_value: search_dict['mobile__contains'] = search_value # 根据用户想要访问的页码,计算出起止位置 page = int(request.GET.get('page',1)) # 默认页码1 limit = int(request.GET.get('limit',10)) # 默认10条 start_index = (page-1) * limit end_index = page * limit totals = models.PrettyNum.objects.filter(**search_dict).count() # 可以获取到符合条件的数据总数 data_list = models.PrettyNum.objects.filter(**search_dict).order_by('-level')[start_index:end_index] # -id desc (倒序) +id asc(正序) # models.PrettyNum.objects.filter(id=1, mobile='13116836021') # 写法1 # # 写法2 # search_dict = {"id": 1, "mobile": "13116836021"} # models.PrettyNum.objects.filter(**search_dict) return render(request, 'pretty_list.html', {"data_list": data_list,"search_value":search_value}) ``` ##### 封装与优化 ###### 分页 Pagination.js ```js function initPagination(elementId, totalRecords, currentPage, pageSize) { layui.use(['laypage'], function(){ var laypage = layui.laypage; // 从URL中获取当前的page和limit参数 var urlParams = new URLSearchParams(window.location.search); currentPage = parseInt(urlParams.get('page')) || currentPage; pageSize = parseInt(urlParams.get('limit')) || pageSize; // 计算实际的页数和每页显示数量 pageSize = Math.min(pageSize, totalRecords); let totalPages = Math.max(1, Math.ceil(totalRecords / pageSize)); // 确保当前页不超过总页数 currentPage = Math.min(Math.max(1, currentPage), totalPages); laypage.render({ elem: elementId, count: totalRecords, curr: currentPage, limit: pageSize, limits: [10, 20, 30, 50, 60, 70, 80, 90, 100], layout: ['count', 'prev', 'page', 'next', 'limit', 'refresh', 'skip'], jump: function(obj, first){ if(!first){ var url = new URL(window.location.href); url.searchParams.set('page', obj.curr); url.searchParams.set('limit', obj.limit); window.location.href = url.toString(); } } }); }); } ``` ###### html中引入使用 ```html {% extends "layout.html" %} {% load static %} {% block css %} {% endblock %} {% block content %}
新增靓号
{% for item in data_list %} {% endfor %}
ID 靓号号码 价格 级别 状态 操作
{{item.id}} {{item.mobile}} {{item.price}} {{item.get_level_display}} {{item.get_status_display}} 编辑 删除
{% endblock %} {% block js %} {% endblock %} ``` ###### views.py ```python def pretty_list(request): """靓号列表""" search_dict = {} search_value = request.GET.get('search', '').strip() if search_value: search_dict['mobile__contains'] = search_value # 获取并验证分页参数 try: page = max(1, int(request.GET.get('page', 1))) except ValueError: page = 1 try: limit = max(1, min(100, int(request.GET.get('limit', 10)))) except ValueError: limit = 10 # 查询数据并排序 queryset = models.PrettyNum.objects.filter(**search_dict).order_by('-level') # 使用Django的Paginator进行分页 paginator = Paginator(queryset, limit) try: data_list = paginator.page(page) except PageNotAnInteger: data_list = paginator.page(1) except EmptyPage: data_list = paginator.page(paginator.num_pages) context = { 'data_list': data_list, 'search_value': search_value, 'totals': paginator.count, 'current_page': data_list.number, 'page_size': limit, 'total_pages': paginator.num_pages, } return render(request, 'pretty/list.html', context) ``` ### 6、ModelForm与layui > ModelForm可以帮助我们创建HTML标签 ```python class PrettyModelForm(forms.ModelForm): """定义一个类去继承forms.ModelForm""" class Meta: model = models.PrettyNum fields = ['name','password','mobile', 'price', 'level', 'status'] # 自定义字段名 form = PrettyModelForm() ``` ```tex {{form.name}} 普通的input输入框 {{form.password}} 普通的input输入框 ``` ##### 新建一个公共类 名字 layUI.py ```python # encoding: utf-8 # 项目名称: day02 # @File : layUI.py # @Author: sun hao # @Desc : # @Date : 19:25 2024/10/02 from django import forms class LayUiFormMixin: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.add_layui_attrs() def add_layui_attrs(self): for name, field in self.fields.items(): new_attrs = { "class": "layui-input", "placeholder": "请输入"+field.label, "autocomplete": "off" } if field.widget.attrs: field.widget.attrs.update(new_attrs) else: field.widget.attrs = new_attrs class LayUiModelForm(LayUiFormMixin, forms.ModelForm): pass class LayUiForm(LayUiFormMixin, forms.Form): pass ``` ##### 导入使用 ```python # encoding: utf-8 # 项目名称: day02 # @File : account.py # @Author: sun hao # @Desc : 用户登录模块 # @Date : 16:04 2024/10/02 from django import forms from django.shortcuts import HttpResponse, render, redirect from app01 import models from app01.utils.encrypt import md5 from app01.utils.layUI import LayUiForm class LoginForm(LayUiForm): # 这里我直接写个类,让LoginForm 去继承一下 # 这样一些基础样式、属性就可以直接继承那个类了,我这里只要写一些需要自定义的输入框 样式属性即可 username = forms.CharField( label="用户名", widget=forms.TextInput( attrs={ "lay-affix": "clear", } ), required=True, error_messages={ 'required': '请输入用户名', } ) password = forms.CharField( label="密码", widget=forms.PasswordInput( render_value=True, # 记住密码页面数据回显,当用户密码校验不通过时 attrs={ "lay-affix": "eye", } ), required=True, error_messages={ 'required': '请输入密码', } ) def clean_password(self): # 使用钩子方法,拿到输入的密码,md5 加密一下,这样在后面的步骤中可以直接去库里校验匹配了 pwd = self.cleaned_data.get("password") return md5(pwd) def login(request): """ 用户登录 """ if request.method == 'GET': form = LoginForm() return render(request, 'login/login.html', {"form":form}) form = LoginForm(data=request.POST) if form.is_valid(): """ 数据校验通过,可以拿到输入的用户名密码 { username:"xxx",password:"xxx" } 因为我这里使用的Form 只能使用form.cleaned_data 如果是ModelForm,就可以使用form.save """ print(form.cleaned_data) # 拿到用户名密码,去库里校验 search_dict = form.cleaned_data # 这里 我接收到的字段名跟库里的表字段名一样,可以直接这样写 login_row = models.Admin.objects.filter(**search_dict).first() if not login_row: # 没有这个账号密码 form.errors.clear() # 清除上一次的错误信息 form.add_error("username","用户名或密码错误") form.add_error("password","用户名或密码错误") return render(request, 'login/login.html', {"form": form}) # 用户名,密码匹配上了 # 网站生成随机字符串,写到用户浏览器的cookie中,在写入到session中 # 页面刷一下,或者 请求一个接口。浏览器可以看到cookie那里有个sessionid 与 库里 django_session 是对应的 request.session["login_info"] = {"id":login_row.id,"name":login_row.username} return redirect("/admin/list/") # return HttpResponse("登录成功") return render(request, 'login/login.html', {"form":form}) ``` ##### 登录页面 login.html ```html {% load static %} 用户登录
用户登录
{% csrf_token %}
``` ### 7、管理员模块 > 在做管理员模块时,发现 新增、编辑 基本都是类似功能。如果每个模块,每个动作都要写个html后面维护是很难受的,当让遇上恶心的需求方时,单独页面才是最优解 > 我这里写了一个public公共文件夹 里面有个add.html 单纯的用来做 新增 编辑表单渲染页面。 #### admin.py ```python from cProfile import label from dataclasses import field from django import forms from django.shortcuts import HttpResponse, render, redirect from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.validators import RegexValidator from django.core.exceptions import ValidationError from django.template.defaultfilters import title from app01 import models from app01.utils.encrypt import md5 """ 管理员管理模块 """ def admin_list(request): """管理员数据列表""" search_dict = {} search_value = request.GET.get('search', '').strip() if search_value: search_dict['username'] = search_value # 获取并验证分页参数 try: page = max(1, int(request.GET.get('page', 1))) except ValueError: page = 1 try: limit = max(1, min(100, int(request.GET.get('limit', 10)))) except ValueError: limit = 10 # 查询数据并排序 queryset = models.Admin.objects.filter(**search_dict).order_by('id') # 使用Django的Paginator进行分页 paginator = Paginator(queryset, limit) try: data_list = paginator.page(page) except PageNotAnInteger: data_list = paginator.page(1) except EmptyPage: data_list = paginator.page(paginator.num_pages) context = { 'data_list': data_list, 'search_value': search_value, 'totals': paginator.count, 'current_page': data_list.number, 'page_size': limit, 'total_pages': paginator.num_pages, } return render(request, 'admin/list.html', context) class AdminModelForm(forms.ModelForm): """定义一个类去继承forms.ModelForm""" confirm_password = forms.CharField(label="确认密码", widget=forms.PasswordInput(render_value=True)) class Meta: model = models.Admin fields = ['username', 'password'] # 字段名 widgets = { 'password': forms.PasswordInput(render_value=True) } # 执行init时,重新定义父类的init方法 def __init__(self, *args, **kwargs): super(AdminModelForm, self).__init__(*args, **kwargs) # 正确地调用父类的构造函数 for name, field in self.fields.items(): field.widget.attrs = {"class": "layui-input", "placeholder": field.label, "lay-affix": "clear", "autocomplete": "off"} def clean_password(self): # 我这里拿到password后,直接加密了 pwd = self.cleaned_data.get("password") return md5(pwd) # 加密存储 def clean_confirm_password(self): # 所以,这里拿到的 password 其实已经是加密后的 print(self.cleaned_data) pass_word_str = self.cleaned_data['password'] confirm_pass_word_str = md5(self.cleaned_data['confirm_password']) if confirm_pass_word_str != pass_word_str: raise ValidationError('密码不一致,请确认密码') return confirm_pass_word_str # 返回什么,库里就存什么 def admin_add(request): """添加用户使用ModelForm组件""" if request.method == 'GET': form = AdminModelForm() return render(request, 'public/add.html', {"title": "新建管理员", "form": form}) # 用户post提交数据,开始数据校验 form = AdminModelForm(data=request.POST) if form.is_valid(): form.save() # 数据保存 return redirect('/admin/list/') else: # 数据校验失败,就会在这里执行 print(form.errors) return render(request, 'public/add.html', {"title": "新建管理员", "form": form}) class AdminEditModelForm(forms.ModelForm): """定义一个类去继承forms.ModelForm""" class Meta: model = models.Admin fields = ['username'] # 字段名 # 执行init时,重新定义父类的init方法 def __init__(self, *args, **kwargs): super(AdminEditModelForm, self).__init__(*args, **kwargs) # 正确地调用父类的构造函数 for name, field in self.fields.items(): field.widget.attrs = {"class": "layui-input", "placeholder": field.label, "lay-affix": "clear", "autocomplete": "off"} def admin_edit(request, id): """管理员账号编辑""" """编辑账号使用ModelForm组件""" row_obj = models.Admin.objects.filter(id=id).first() # 查询对应数据 if not row_obj: return render(request, 'error-page/error.html', {"msg": "数据不存在"}) if request.method == 'GET': form = AdminEditModelForm(instance=row_obj) # 让实例等于查询的结果,双向绑定 return render(request, 'public/add.html', {"form": form, "title": "编辑用户信息"}) # 开始数据校验 form = AdminEditModelForm(data=request.POST, instance=row_obj) # 这样写就是,我拿到对应id的数据,然后使用MoelForm来更新 if form.is_valid(): # print(form.cleaned_data) # 默认保存的是用户输入的所有数据,如果表里有个特殊字段,我们要写入数据可以 这样 # form.instance.字段名 = 字段值 # 如果数据校验通过,就会在这里执行 form.save() # 数据保存 return redirect('/admin/list/') else: # 数据校验失败,就会在这里执行 print(form.errors) return render(request, 'public/add.html', {"form": form,"title": "编辑用户信息"}) def admin_delete(request): """管理员账号""" id = request.GET.get('id') models.Admin.objects.filter(id=id).delete() return redirect('/admin/list/') class AdminResetModelForm(forms.ModelForm): """定义一个类去继承forms.ModelForm""" confirm_password = forms.CharField(label="确认密码", widget=forms.PasswordInput(render_value=True)) class Meta: model = models.Admin fields = ['password'] # 字段名 widgets = { 'password': forms.PasswordInput(render_value=True) } # 执行init时,重新定义父类的init方法 def __init__(self, *args, **kwargs): super(AdminResetModelForm, self).__init__(*args, **kwargs) # 正确地调用父类的构造函数 for name, field in self.fields.items(): field.widget.attrs = {"class": "layui-input", "placeholder": field.label, "lay-affix": "clear", "autocomplete": "off"} def clean_password(self): # 我这里拿到password后,直接加密了 pwd = self.cleaned_data["password"] return md5(pwd) # 加密存储 def clean_confirm_password(self): # 所以,这里拿到的 password 其实已经是加密后的 print(self.cleaned_data) pass_word_str = self.cleaned_data['password'] confirm_pass_word_str = md5(self.cleaned_data['confirm_password']) # 去数据库校验一下,当前id记录的密码md5 后 与新设置的密码md5 是否一样 exists = models.Admin.objects.filter(id=self.instance.pk,password=pass_word_str).exists() if exists: raise ValidationError('新密码不能与旧密码一样,请重新设置新密码') if confirm_pass_word_str != pass_word_str: raise ValidationError('密码不一致,请确认密码') return confirm_pass_word_str # 返回什么,库里就存什么 def admin_reset(request,id): """管理员账号密码重置""" row_obj = models.Admin.objects.filter(id=id).first() # 查询对应数据 if not row_obj: return render(request, 'error-page/error.html', {"msg": "该账号不存在"}) # 最简单的版本 S # password = md5('123456a') # models.Admin.objects.filter(id=id).update(password=password) # return redirect('/admin/list/') # 最简单的版本 E title = "重置密码-{}".format(row_obj.username) if request.method == 'GET': form = AdminResetModelForm() # 让实例等于查询的结果,双向绑定 return render(request, 'public/add.html', {"title": title, "form": form}) # 用户post提交数据,开始数据校验 form = AdminResetModelForm(data=request.POST,instance=row_obj) if form.is_valid(): form.save() # 数据保存 return redirect('/admin/list/') else: # 数据校验失败,就会在这里执行 print(form.errors) return render(request, 'public/add.html', {"title": title, "form": form}) ``` ```html {% extends "layout.html" %} {% load static %} {% block css %} {% endblock %} {% block content %}
新增管理员
{% for item in data_list %} {% endfor %}
ID 用户名 密码 操作
{{item.id}} {{item.username}} {{item.password}} 重置密码 编辑 删除
{% endblock %} {% block js %} {% endblock %} ``` ### 8、用户登录 #### #### 1、登录 ```python # encoding: utf-8 # 项目名称: day02 # @File : account.py # @Author: sun hao # @Desc : 用户登录模块 # @Date : 16:04 2024/10/02 from django import forms from django.shortcuts import HttpResponse, render, redirect from app01 import models from app01.utils.encrypt import md5 from app01.utils.layUI import LayUiForm class LoginForm(LayUiForm): # 这里我直接写个类,让LoginForm 去继承一下 username = forms.CharField( label="用户名", widget=forms.TextInput( attrs={ "lay-affix": "clear", } ), required=True, error_messages={ 'required': '请输入用户名', } ) password = forms.CharField( label="密码", widget=forms.PasswordInput( render_value=True, # 记住密码页面数据回显,当用户密码校验不通过时 attrs={ "lay-affix": "eye", } ), required=True, error_messages={ 'required': '请输入密码', } ) def clean_password(self): # 使用钩子方法,拿到输入的密码,md5 加密一下,这样在后面的步骤中可以直接去库里校验匹配了 pwd = self.cleaned_data.get("password") return md5(pwd) def login(request): """ 用户登录 """ if request.method == 'GET': form = LoginForm() return render(request, 'login/login.html', {"form":form}) form = LoginForm(data=request.POST) if form.is_valid(): """ 数据校验通过,可以拿到输入的用户名密码 { username:"xxx",password:"xxx" } 因为我这里使用的Form 只能使用form.cleaned_data 如果是ModelForm,就可以使用form.save """ print(form.cleaned_data) # 拿到用户名密码,去库里校验 search_dict = form.cleaned_data # 这里 我接收到的字段名跟库里的表字段名一样,可以直接这样写 login_row = models.Admin.objects.filter(**search_dict).first() if not login_row: # 没有这个账号密码 form.add_error("username","用户名或密码错误") form.add_error("password","用户名或密码错误") return render(request, 'login/login.html', {"form": form}) # 用户名,密码匹配上了 # 网站生成随机字符串,写到用户浏览器的cookie中,在写入到session中 # 页面刷一下,或者 请求一个接口。浏览器可以看到cookie那里有个sessionid 与 库里 django_session 是对应的 request.session["login_info"] = {"id":login_row.id,"name":login_row.username} return redirect("/admin/list/") # return HttpResponse("登录成功") return render(request, 'login/login.html', {"form":form}) ``` ```html {% load static %} 用户登录
用户登录
{% csrf_token %}
``` #### 2、cookie 与 session > 登录成功后 > cookie 随机字符串 > session 用户信息 在需要用户票据才能访问的页面中,都需要加入 ```python def index(request): login_info = request.session.get("login_info") if not login_info: return redirect("/login/") ``` #### 3、用户登录在上一步基本完成 ##### 1、中间件 middleware > vue的路由守卫一个道理,某些页面只有用户登录了,才能访问 ##### 2、没梦想的咸鱼做法 ```python """ 管理员管理模块 """ def admin_list(request): """管理员数据列表""" # 尝试获取session中的用户登录信息,如果获取不到就是没有登录的状态,让它去登录页面 # 如果一个模块 10个接口,1个项目10个模块,都需要需要重复写,这是不对的,只有没梦想的咸鱼才这样干 login_info = request.session.get("login_info") print(login_info) if not login_info: return redirect("/login/") search_dict = {} search_value = request.GET.get('search', '').strip() if search_value: search_dict['username'] = search_value # 获取并验证分页参数 try: page = max(1, int(request.GET.get('page', 1))) except ValueError: page = 1 try: limit = max(1, min(100, int(request.GET.get('limit', 10)))) except ValueError: limit = 10 # 查询数据并排序 queryset = models.Admin.objects.filter(**search_dict).order_by('id') # 使用Django的Paginator进行分页 paginator = Paginator(queryset, limit) try: data_list = paginator.page(page) except PageNotAnInteger: data_list = paginator.page(1) except EmptyPage: data_list = paginator.page(paginator.num_pages) context = { 'data_list': data_list, 'search_value': search_value, 'totals': paginator.count, 'current_page': data_list.number, 'page_size': limit, 'total_pages': paginator.num_pages, } return render(request, 'admin/list.html', context) ``` ##### 3、auth.py ```python # encoding: utf-8 # 项目名称: day02 # @File : auth.py # @Author: sun hao # @Desc : 中间件 auth.py # @Date : 16:30 2024/10/05 from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse class M1(MiddlewareMixin): """ 中间件1 """ def process_request(self, request): print("M1,进来了") # 如果没有返回值,或者 返回None 它可以继续走下去 # 如果有返回值,直接 HttpResponse,后面就不执行了 return HttpResponse("无权访问") # 这里一旦返回了 就是 M1,进来了,M2,进来了 无权访问 def process_response(self, request, responses): print("M1,走了") return responses class M2(MiddlewareMixin): """ 中间件2 """ def process_request(self, request): print("M2,进来了") def process_response(self, request, responses): print("M2,走了") return responses ``` ###### setting.py ```python 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', 'app01.middleware.auth.M1', # 引入使用 'app01.middleware.auth.M2', # 引入使用 ] ``` ###### 输出结果 ```\ M1,进来了 M2,进来了 {'id': 11, 'name': 'sunhao'} M2,走了 M1,走了 ``` ##### 4、最终实现 ```python # encoding: utf-8 # 项目名称: day02 # @File : auth.py # @Author: sun hao # @Desc : 中间件 auth.py # @Date : 16:30 2024/10/05 from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse, redirect class CheckAuthMiddleware(MiddlewareMixin): """ 检查当前访问用户的session信息 """ def process_request(self, request): # 排除 登录页面 login,它不需要用户session可以直接访问,如果不加上,它会一直302重定向让程序崩溃 if request.path_info == "/login/": return # 1、尝试读取访问用户的session信息,如果有 下一步 如果没有 请先去登录 login_info = request.session.get("login_info") if not login_info: # 没有session信息,去登录 # return HttpResponse("请先登录") return redirect("/login/") # 有 session信息 下一步 return ``` #### 8、用户注销 ```python def logout(request): """ 用户注销 """ request.session.clear() return redirect("/login/") ``` #### 9、图片验证码 - ##### 生成图片 ```python pip install pillow ``` - ##### 完整代码 code.py ```python # encoding: utf-8 # 项目名称: day02 # @File : code.py.py # @Author: sun hao # @Desc : 生成图片验证码 # @Date : 19:55 2024/10/05 import random from PIL import Image, ImageDraw, ImageFont, ImageFilter def check_code(width=120, height=30, char_length=5, font_file='Monaco.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) # if __name__ == '__main__': # img,code_tr = check_code() # print(code_tr) ``` account.py ```python # encoding: utf-8 # 项目名称: day02 # @File : account.py # @Author: sun hao # @Desc : 用户登录模块 # @Date : 16:04 2024/10/02 from django import forms from django.shortcuts import HttpResponse, render, redirect from app01 import models from app01.utils.encrypt import md5 from app01.utils.layUI import LayUiForm from app01.utils.code import check_code from io import BytesIO class LoginForm(LayUiForm): # 这里我直接写个类,让LoginForm 去继承一下 username = forms.CharField( label="用户名", widget=forms.TextInput( attrs={ "lay-affix": "clear", } ), required=True, error_messages={ 'required': '请输入用户名', } ) password = forms.CharField( label="密码", widget=forms.PasswordInput( render_value=True, # 记住密码页面数据回显,当用户密码校验不通过时 attrs={ "lay-affix": "eye", } ), required=True, error_messages={ 'required': '请输入密码', } ) code = forms.CharField( label="验证码", widget=forms.TextInput( attrs={ "lay-affix": "clear", } ), required=True, error_messages={ 'required': '请填写验证码', } ) def clean_password(self): # 使用钩子方法,拿到输入的密码,md5 加密一下,这样在后面的步骤中可以直接去库里校验匹配了 pwd = self.cleaned_data.get("password") return md5(pwd) def login(request): """ 用户登录 """ if request.method == 'GET': form = LoginForm() return render(request, 'login/login.html', {"form": form}) print(request.POST) form = LoginForm(data=request.POST) if form.is_valid(): """ 数据校验通过,可以拿到输入的用户名密码 { username:"xxx",password:"xxx" } 因为我这里使用的Form 只能使用form.cleaned_data 如果是ModelForm,就可以使用form.save """ # print(form.cleaned_data) # 验证码的校验比较,统一转大写比较 user_input_code = form.cleaned_data.pop('code') # 用户提交过来的code session_image_code = request.session.get('image_code',"") # 服务器事先存储下的code,可能会为空 if session_image_code.upper() != user_input_code.upper(): form.errors.clear() # 清除上一次的错误信息 form.add_error("code", "验证码错误") return render(request, 'login/login.html', {"form": form}) # 这里为什么使用pop,来干掉code字段呢, # Admin 表中没有code字段,如果不去掉,code就会参与sql的检索,会导致程序崩溃 # 拿到用户名密码,去库里校验 search_dict = form.cleaned_data # 这里 我接收到的字段名跟库里的表字段名一样,可以直接这样写 login_row = models.Admin.objects.filter(**search_dict).first() if not login_row: # 没有这个账号密码 form.errors.clear() # 清除上一次的错误信息 form.add_error("username", "用户名或密码错误") form.add_error("password", "用户名或密码错误") return render(request, 'login/login.html', {"form": form}) # 用户名,密码匹配上了 # 网站生成随机字符串,写到用户浏览器的cookie中,在写入到session中 # 页面刷一下,或者 请求一个接口。浏览器可以看到cookie那里有个sessionid 与 库里 django_session 是对应的 request.session["login_info"] = {"id": login_row.id, "name": login_row.username} request.session.set_expiry(60*60*24*7) # 重新设置有效时间7天有效。为啥要这么做?之前设置图片验证码60秒有效,需要覆盖掉。 return redirect("/admin/list/") # return HttpResponse("登录成功") return render(request, 'login/login.html', {"form": form}) def logout(request): """ 用户注销 """ request.session.clear() return redirect("/login/") # def image_code(request): # """ # 图片验证码 # """ # img,code_str = check_code() # # 把验证码写到session中,我们在登录接口那边拿到 账号密码后,校验验证码大小写对不对 # request.session["image_code"] = code_str # request.session.set_expiry = 60 # 验证码60秒以内有效 # print("图片验证码:"+code_str) # # 3. img 写入内存(Python3) # stream = BytesIO() # img.save(stream, 'png') # return HttpResponse(stream.getvalue()) def image_code(request): """ 图片验证码 """ img, code_str = check_code() # 把验证码写到session中,我们在登录接口那边拿到 账号密码后,校验验证码大小写对不对 request.session["image_code"] = code_str request.session.set_expiry(60) # 验证码60秒以内有效 print("图片验证码:" + code_str) # 使用上下文管理器来确保 BytesIO 正确关闭 with BytesIO() as stream: img.save(stream, 'png') return HttpResponse(stream.getvalue(), content_type='image/png') ``` login.html ```html {% load static %} 用户登录
用户登录
{% csrf_token %}
``` #### 10、axios ajax ##### get ```python # encoding: utf-8 # 项目名称: day02 # @File : task.py # @Author: sun hao # @Desc : # @Date : 19:58 2024/10/08 from django import forms from django.shortcuts import HttpResponse, render, redirect from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.validators import RegexValidator from django.core.exceptions import ValidationError from app01 import models def task_list(request): """ 任务列表 """ return render(request, 'task/list.html') def task_ajax(request): print(request.GET) return HttpResponse('请求成功') ``` ```html {% extends "layout.html" %} {% load static %} {% block css %} {% endblock %} {% block content %} 任务列表 {% endblock %} {% block js %} {% endblock %} ``` ##### post > 需要 免除 csrf 的限制 ```python # encoding: utf-8 # 项目名称: day02 # @File : task.py # @Author: sun hao # @Desc : # @Date : 19:58 2024/10/08 from django import forms from django.shortcuts import HttpResponse, render, redirect from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.validators import RegexValidator from django.core.exceptions import ValidationError from django.views.decorators.csrf import csrf_exempt from app01 import models def task_list(request): """ 任务列表 """ return render(request, 'task/list.html') @csrf_exempt def task_ajax(request): """ 使用 csrf_exempt 免除接口post限制,不然提交时会提示403的控制 """ print(request.GET) return HttpResponse('请求成功') ``` ```html {% extends "layout.html" %} {% load static %} {% block css %} {% endblock %} {% block content %} 任务列表 {% endblock %} {% block js %} {% endblock %} ``` #### 案例代码 ````html {% extends "layout.html" %} {% load static %} {% block css %} {% endblock %} {% block content %}
表单
{% for field in form %}
{{field}}
{% endfor %}

任务列表

{% endblock %} {% block js %} {% endblock %} ```` # 订单管理模块 > 想要在数据库中获取数据时:对象 / 字典 ## 知识点 ```python # queryset = [obj,obj,obj] queryset = models.Order.objects.all() ``` ``` python # queryset = [{"id":1,"title":"xx"},{"id":2,"title":"xx"},{"id":3,"title":"xx"}] queryset = models.Order.objects.all().values("id","title") ``` ```python # queryset = [(1,"xxx"),(2,"xxx"),(3,"xxx")] queryset = models.Order.objects.all().values_list("id","title") # 输出元组 ``` ## 获取订单详情 通过axios ### 方式1 不推荐(对象) ```python def order_details(request): """ 获取对应订单的具体信息,通过id """ order_id = request.GET.get('order_id') row_obj = models.Order.objects.filter(id=order_id).first() # 返回一个对象,当前行的所有数据 print(row_obj) #得到是它 Order object (16) if not row_obj: data_dict = {"status": False, "code": "500", "message": "该订单记录不存在"} return JsonResponse(data_dict) # 方式1 S # 获取到对象,没法序列化返回给浏览器,不能直接给 row_obj data_dict = {"status": True, "code": "200", "data": { "title": row_obj.title, "price": row_obj.price, "status": row_obj.status, }} return JsonResponse(data_dict) # 方式1 E ``` ### 方式2 推荐 (字典) ```python def order_details(request): """ 获取对应订单的具体信息,通过id """ order_id = request.GET.get('order_id') row_dict = models.Order.objects.filter(id=order_id).values("title","price","status").first() # 返回一个字典,当前行的所有数据 print(row_dict) #得到 {'title': '通义千问', 'price': Decimal('4789.00'), 'status': 2} if not row_dict: data_dict = {"status": False, "code": "500", "message": "该订单记录不存在"} return JsonResponse(data_dict) # 方式1 S # 获取到对象,没法序列化返回给浏览器,不能直接给 row_obj # data_dict = {"status": True, "code": "200", "data": { # "title": row_obj.title, # "price": row_obj.price, # "status": row_obj.status, # }} # return JsonResponse(data_dict) # 方式1 E # 方式2 S data_dict = {"status": True, "code": "200", "data":row_dict } return JsonResponse(data_dict) # 方式2 E ``` # 图表不用看 # 文件上传 > 所有的静态文件都是放在static目录下 ## 基本操作 ### 单个图片上传 ```html {% extends "layout.html" %} {% load static %} {% block css %} {% endblock %} {% block content %}
{% csrf_token %}
{% endblock %} ``` ```python # views.py import os import datetime from django.core.files.storage import FileSystemStorage from django.http import HttpResponse from django.shortcuts import render from django.conf import settings from urllib.parse import urljoin from django.views.decorators.csrf import csrf_exempt @csrf_exempt def upload_list(request): """文件上传""" if request.method == 'GET': return render(request, 'upload/list.html') if request.method == 'POST': file_obj = request.FILES.get('files') print(file_obj.name) # 文件名 xxx.png # 获取当前日期和时间 now = datetime.datetime.now() date_str = now.strftime('%Y-%m-%d') timestamp_str = now.strftime('%Y%m%d%H%M%S') # 创建日期文件夹 date_folder = os.path.join(settings.MEDIA_ROOT, date_str) os.makedirs(date_folder, exist_ok=True) # 使用FileSystemStorage来存储文件 fs = FileSystemStorage(location=date_folder) # 安全处理文件名并加上时间戳 file_name = os.path.basename(file_obj.name) timestamped_file_name = f"{timestamp_str}_{file_name}" filename = fs.save(timestamped_file_name, file_obj) # 保存文件 file_url = fs.url(filename) # 使用 FileSystemStorage 获取文件的访问 URL # 获取当前项目的基地址 base_url = request.build_absolute_uri('/') # 生成完整的文件访问地址 full_url = urljoin(base_url, file_url) # 返回上传成功的文件访问地址 response_content = f'文件上传成功,访问地址: {full_url}' return HttpResponse(response_content) return HttpResponse('仅支持GET和POST请求') ``` ### 多个图片上传 ```html {% extends "layout.html" %} {% load static %} {% block css %} {% endblock %} {% block content %}
{% csrf_token %}
{% endblock %} ``` ```python # views.py import os import datetime from django.core.files.storage import FileSystemStorage from django.http import HttpResponse from django.shortcuts import render from django.conf import settings from urllib.parse import urljoin from django.views.decorators.csrf import csrf_exempt @csrf_exempt def upload_list(request): """文件上传""" if request.method == 'GET': return render(request, 'upload/list.html') if request.method == 'POST': # 获取上传的文件列表 files = request.FILES.getlist('files') if not files: return HttpResponse('没有文件上传') # 获取当前日期和时间 now = datetime.datetime.now() date_str = now.strftime('%Y-%m-%d') timestamp_str = now.strftime('%Y%m%d%H%M%S') # 创建日期文件夹 date_folder = os.path.join(settings.MEDIA_ROOT, date_str) os.makedirs(date_folder, exist_ok=True) # 使用FileSystemStorage来存储文件 fs = FileSystemStorage(location=date_folder) # 存储每个文件并记录文件的访问 URL uploaded_files_urls = [] for file_obj in files: # 安全处理文件名并加上时间戳 file_name = os.path.basename(file_obj.name) timestamped_file_name = f"{timestamp_str}_{file_name}" filename = fs.save(timestamped_file_name, file_obj) # 生成文件的访问 URL file_url = os.path.join(settings.MEDIA_URL, date_str, filename).replace('\\', '/') # 获取当前项目的基地址 base_url = request.build_absolute_uri('/') # 生成完整的文件访问地址 full_url = urljoin(base_url, file_url) uploaded_files_urls.append(full_url) # 返回上传成功的文件列表 response_content = '
'.join([f'文件上传成功,访问地址: {url}' for url in uploaded_files_urls]) return HttpResponse(response_content) return HttpResponse('仅支持GET和POST请求') ``` ## Excel操作 ```python pip install openpyxl ``` ### 批量新增部门 ```html
{% block js %} {% endblock %} ``` ```python @csrf_exempt def depart_multi(request): """ 基于EXCEL文件批量上传,创建部门 """ if request.method == 'GET': data_list = models.Deptartment.objects.all() return render(request, 'depart/list.html', {"data_list": data_list}) if request.method == 'POST': # 1、获取用户上传的文件对象 file_obj = request.FILES.get('files') print(type(file_obj)) # 通过 这样可用知道 它是用了什么 # from django.core.files.uploadedfile import InMemoryUploadedFile print(file_obj.name) # 文件名 xxx.xlsx # 2、传递文件对象,给openpyxl 打开Excel并读取内容 from openpyxl import load_workbook wb = load_workbook(file_obj) sheet = wb.worksheets[0] cell = sheet.cell(row=1, column=1) # 第1行第1列 print(cell.value) # 3、循环获取每一行数据,从第2行开始,第1行是标题 for row in sheet.iter_rows(min_row=2): # print(row) # (,) # print(row[0].value)# xx部门 dept_name = row[0].value; is_exists = models.Deptartment.objects.filter(title=dept_name).exists() if not is_exists: models.Deptartment.objects.create(title=dept_name) print(f'{dept_name},已创建') else: print(f'{dept_name},已经存在') return HttpResponse('上传文件') ``` # 混合数据 Form 提交页面时,用户输入数据+文件(输入不能为空、报错) - Form生成HTML标签 type = file - 表单的验证 - form.cleaned_data 获取 数据 + 文件对象 ### html文件 ```html {% extends "layout.html" %} {% load static %} {% block css %} {% endblock %} {% block content %}
{{title}}
{% csrf_token %} {% for field in form %}
{{field}} {{field.errors.0}}
{% endfor %}
{% endblock %} ``` ### upload.py ```python def upload_form(request): title = "Form上传" if request.method == 'GET': form = UpLoadForm() return render(request, 'upload/form.html', {"form": form, "title": title}) form = UpLoadForm(data=request.POST, files=request.FILES) if form.is_valid(): print( form.cleaned_data) # 读取到的内容,自己开始处理 {'name': '路人甲', 'age': 1024, 'avatar': } # 1、读取图片内容,写入到文件夹中,并返回文件路径并存储文件路径到库中 S file_obj = form.cleaned_data.get("avatar") # 获取当前日期和时间 now = datetime.datetime.now() date_str = now.strftime('%Y-%m-%d') timestamp_str = now.strftime('%Y%m%d%H%M%S') # 创建日期文件夹 date_folder = os.path.join(settings.MEDIA_ROOT, date_str) os.makedirs(date_folder, exist_ok=True) # 使用FileSystemStorage来存储文件 fs = FileSystemStorage(location=date_folder) # 安全处理文件名并加上时间戳 file_name = os.path.basename(file_obj.name) timestamped_file_name = f"{timestamp_str}_{file_name}" filename = fs.save(timestamped_file_name, file_obj) # 保存文件 file_url = os.path.join(settings.MEDIA_URL, date_str, filename).replace('\\', '/') # 1、读取图片内容,写入到文件夹中,并返回文件路径并存储文件路径到库中 E models.Boss.objects.create( name=form.cleaned_data['name'], age=form.cleaned_data['age'], avatar=file_url ) return HttpResponse('......') return render(request, 'upload/form.html', {"form": form, "title": title}) ``` # 混合数据 ModelForm ## models.py ```python class City(models.Model): """ 城市=>ModelForm """ name = models.CharField(verbose_name="城市名称", max_length=64, db_comment="城市名称") count = models.IntegerField(verbose_name="人口", db_comment="人口") # 本质上在数据库中,也是CharField 字符串 # 使用 FileField 会帮你自动保存数据 # upload_to 它会帮你存到这里 logo = models.FileField(verbose_name="Logo", max_length=64, db_comment="城市Logo",upload_to='city/%Y/%m/%d/') ``` ## 定义ModelForm ```python class UpLoadModelForm(LayUiModelForm): layui_exclude_fields = ['logo'] class Meta: model = models.City fields = '__all__' ``` ## html ```html {% extends "layout.html" %} {% load static %} {% block css %} {% endblock %} {% block content %}
{{title}}
{% csrf_token %} {% for field in form %}
{{field}} {{field.errors.0}}
{% endfor %}
{% endblock %} ``` ## py ```python class UpLoadModelForm(LayUiModelForm): layui_exclude_fields = ['logo'] class Meta: model = models.City fields = '__all__' def upload_model_form(request): """ 文件上传,基于ModelForm上传 """ title = "ModelForm上传" if request.method == 'GET': form = UpLoadModelForm() return render(request, 'upload/model_form.html', {"form": form, "title": title}) form = UpLoadModelForm(data=request.POST, files=request.FILES) if form.is_valid(): form.save() return HttpResponse('新建成功') return render(request, 'upload/model_form.html', {"form": form, "title": title}) ``` ## 小结 - 自己手动写-参考 depart_multi 部门管理中的批量上传新建部门功能 - Form组件 ( 表单验证 ) ```python request.POST file_obj = request.FILES.get('avatar') # 具体文件操作还是需要自己手动 ``` - ModelForm(表单验证、自动保存数据、自动保存文件) ```python - 配置 media - models.py 定义类文件要 class City(models.Model): """ 城市=>ModelForm """ name = models.CharField(verbose_name="城市名称", max_length=64, db_comment="城市名称") count = models.IntegerField(verbose_name="人口", db_comment="人口") # 本质上在数据库中,也是CharField 字符串 # 使用 FileField 会帮你自动保存数据 # upload_to 它会帮你存到这里 logo = models.FileField(verbose_name="Logo", max_length=64, db_comment="城市Logo",upload_to='city/%Y/%m/%d/') ``` # 后续未来开发之路 ## 1、python基础 ```python https://www.bilibili.com/video/BV1m54y1r7zE/?spm_id_from=333.999.0.0&vd_source=51dad4119df698fa0d36140c5d0c9e59 ``` ## 2、并发编程(进程线程协程) ```python https://www.bilibili.com/video/BV1Ev411G7i3/?spm_id_from=333.999.0.0&vd_source=51dad4119df698fa0d36140c5d0c9e59 # 不建议小白学习 https://www.bilibili.com/video/BV1NA411g7yf/?spm_id_from=333.999.0.0 ``` ## 3、MySql ```python https://www.bilibili.com/video/BV15R4y1b7y9/?spm_id_from=333.999.0.0&vd_source=51dad4119df698fa0d36140c5d0c9e59 ``` ## 4、django 开发知识点 ```python https://www.bilibili.com/video/BV1zE411x7LG/?spm_id_from=333.999.0.0 https://www.bilibili.com/video/BV1JE411V7xk/?spm_id_from=333.999.0.0 ``` ## 5、项目开发 bug任务平台 ```python https://www.bilibili.com/video/BV1uA411b77M/?spm_id_from=333.999.0.0 ``` ## 6、进阶项目(增删改查、权限控制) ``` 【CRM项目实战第一部分:rbac权限组件】https://www.bilibili.com/video/BV1DE41147b9?vd_source=e242bc058a7d956ac24d84227113e3e3 ``` ## 7、前后端分离的项目 ```python https://www.bilibili.com/video/BV1w94y1W7Sx/?spm_id_from=333.999.0.0 ``` ## 8、微信小程序 ```python https://www.bilibili.com/video/BV1jC4y1s7QD/?spm_id_from=333.999.0.0 ```