django学习 -- 可以感受到最原始的django框架是前后端不分离的 -- part.1

1.请求和响应

  • get post redirect

2.数据库操作

  • mysql+pymsql
  • ORM框架 -> 帮助我们处理sql语句 => pip install mysqlclient
    创建修改删除数据中的表(不用写sql语句)
    操作表中的数据
    => 创建数据库 django连接数据
    PS. 需要在setting.py进行设置

3.让django帮助创建数据库表

  • django会通过model中的类帮助生成sql创建表和字段
    类名代表表明 属性代表列名
class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    password = models.CharField(max_length=64)

4.执行命令

  • 需要提前注册app -> 在setting.py中注册
    python manage.py makemigrations
    python manage.py migrate

5.需要注意

  • 在已经有的model类中添加属性 可能会导致原来已经有的数据缺失新增加的列的数据
    django给出了两个选择,一是在命令行中默认添加输入,二是在新增加的属性字段中增加 default=字段,代表给原来没有的缺失值赋值。例如:
    age = models.CharField(defalut=18)
    或者设置可以为空
    age = models.CharField(null=True,blank=True)

6.通过django向表中插入数据

  • UserInfo.objects.create(name='cyanine',password="123456") 插入数据 需要类中的属性对应
    a = UserInfo.objects.all()获取所有数据 以列表返回,其中每一个元素都是一个对象 通过.属性获取具体的数据a.name,结合.filter()可以进行筛选查询
    .first()获取获取到的第一个对象

  • 删除数据
    .delete()
    可以添加filter进行筛选 例如UserInfo.objects.filter(name='cyanine').delete() 删除所有name为cyanine的数据
    使用.all()选择全部UserInfo.objects.all().delete()删除这张表中的所有数据

  • 更新数据
    UserInfo.objects.all().updata(password=123456)更新所有的password为123456
    还可以结合.filter()进行筛选

7.django表单如果不写action 那么默认提交就是向当前页面发送一个post请求

8.django中的url命名能避免对url的硬编码,

  • 例如修改一个url 的路径如果以前在html采用的是硬编码,直接写对应的会导致后面修改维护极为困难。
    但是如果采用url命名空间,那么修改的url并不会直接影响到,django会通过url的命名访问到,而不是硬编码。{% url 'url_name' other_param%} 这个url_name 就是在urls.py文件中的path('url/example', views.function,name = url_name)的name为url从的重命名,这样修改path对应的url,就不用在维护前端的页面了。

9.model添加约束 (数据库的约束)

  • 例如员工表和部门表之间的关系,让员工表和部门表关联,通过ForeignKry方法中的toto_field参数在django中设置。用途是可以方便对输入数据的合法性判断,例如员工不能属于一个不存在的部门,就可以很方便的进行判断。
  • 在员工表中
    department = models.ForeignKey(to = 'Department', to_field = 'id')
    这就将员工表和部门表进行了关联,员工的部门只能属于Depatment中的一个;to表示关联的表名,to_field表明关联的表的一个列名字段。要注意的是,django在储存这样的字段时默认命名方式是关联表名_id(例如这个就是depaetment_id)。
    于此对应,在获取的时候,如果直接通过字段/属性获取的话,例如obj.联表名_id,获取到的时储存在这张表中的对应关联表的数字,而不是我们想要的具体指代,因此django规定了如果直接.联表名就可以获取到关联表对应的那一行数据(封装为一个queryobj对象),然后就可以继续通过关联表内的属性进行查找。例如,部门表中一共有两个部门,字段为(id,部门名),内容为(1,主管部)(2,运行部),员工表关联
  • 级联删除 置空null
    现在设想一种情况,如果部门表中的一个部门取消掉,那么与之关联的员工应该如何处理呢?常用的就是两种操作:将他们一并删除(这就是级联删除);还有一种就是将他们设置为null。对应的这两种方式在django中需要这样设置:
    department = models.ForeignKey(to = 'Department', to_field = 'id',on_delete=models.CASCADE) 级联删除
    department = models.ForeignKey(to = 'Department', to_field = 'id',null=True,blank=True,on_delete=models.SET_NULL) 置空 需要注意的是 置空的前提就是这个字段可以为空null = True 这些都是django中的操作。

10.性别存储

  • model的choice 与数据库无关 是django的特性 可以方便的进行固定的选择储存。例如男女,单独为男女添加一个char字段会较为浪费空间,因此使用1/2这样的数字代替,但是数字不易读,因此django设计出了choice参数。
gender_choice = (
    (1,'男'),
    (2,'女'),
) #注意是元组套元组
gender = models.SmallIntegerField(verbose_name='性别',choices = gender_choice) #verbose_name就是对列名(属性)的注解 可写可不写 写上为好
#SmallIntegerField是小整数字段
  • 这样字段的获取:如果直接obj.gender获取的就是数据库中对应的数字,但是django封装了一个判断方法,可以很方便的帮我们判断,然后返回的是我们希望现实的字段:obj.get_gender_display()。其中函数名字组成方式为get_属性名字/字段名字_display()

11.Form和ModelForm

  • 11.1 form类 自动生成表单
#views.py
class MyForm(Form):
    user = forms.CharField(widget=forms.Input)
    password = forms.CharField(widget=forms.Input)
    email = forms.CharField(widget=forms.Input)
def user_add(request):
    form = MyForm() #实力化一个form表单类
    return render(request,'user_add.html',{'form':form}
<!-- user_add.html -->
<form method='post'>
{% for i in form %}
{{ i }}
{% endfor %}
<!-- 或者也可以一个一个自己写出来 比如{{form.user}} {{form.password}} -->
</form>
  • 11.2 ModelForm组件
    • 实现数据库表单字段和前端form表单字段的自动生成
    • 对连接表/外键限制自动生成选择框
    • 定义models类中的__str__方法实现对连接表属性的直接展示
    • 定义生成表单样式的属性
    • {{ obj.label }}可以遍历出在modelform定义字段中的label参数
      接下来是具体的代码展示 有点太多了...
      记得添加了路由!!!(这个就不展示在代码中了,)
#views.py
#根据staff表单生成modelform类
class staffMdoelForm(ModelForm):
    class Meta:
        model = Staff
        fields = ['name','age','gender','phonenumber','create_time']

#添加员工
def staff_add(request):
    form = staffMdoelForm() #实例化modelform类 
    return render(request,'staff_add.html',{
        'form':form
        })
<div style="width:50%;margin-left: 25%;margin-top: 25px;">
    <div class="container" >
        <form method="post">
            <h2>添加新的员工信息</h2>
            {% csrf_token %}
            {% for field in form %}
            <div class="mb-3">
                <label class="form-label">{{field.label}}</label>
                {{ field }}
            </div>
            {% endfor %}
            <button type="submit" class="btn btn-primary">确认添加</button>
        </form>
    </div>    
</div>
#连接表modelform处理(html不用做太多改变)
#在models.py中的从表添加__str__方法 让调用这个类的时候返回的不再是一个queryobject而是__str__定义好的返回值
class Staff(models.Model):
    name = models.CharField(verbose_name='员工姓名',max_length=32)
......
    def __str__(self):
        return self.name
#在views.py中定义modelform类的时候 字段选择直接写定义的而不是在数据库表单中的字段
class userModelForm(ModelForm):
    class Meta:
        model = UserInfo #注 Userinfo表单中的identify是引用了Staff表单中的id作为外键
        fields = ['name','password','identify']

12.重定向

  • from django.shortcuts import redirect或者from django.http import HttpResponseRedirect
    return一个重定向url的实例即可

13.前端url实现删除请求

  • 通过前端发送一个带有删除目标信息的GET请求,然后后端获取删除即可,但是这个带有删除目标信息的url该如何组装呢?通过url的组成规律,添加/?id=信息传递,代码如下:
    <a href="/login/manage/delete/?id={{user.0}}">删除</a> #这其中的{{id.0}}通过django的模板语法动态赋予目标的id信息
    对应处理函数(记得注册url)
def delete_user_info(request):
    id = request.GET.get('id')
    UserInfo.objects.filter(id = id).delete()
    return HttpResponseRedirect('/login/manage/')

14.django的模板语法

  • 其中最开始的{% extends 'nav.html'%}必须处于子页面的第一个加载位置,意味着如果前面有哪怕{% load static %}也会报错。

15.django正则匹配url

  • 这样就不用前端写带有get参数的url,后端也不需要get获取,而是直接作为一个解析好的参数传递给视图进行处理,二是直接及进行请求和解析。同时也要注意,在url中添加的正则顺序不影响在命名url中的参数顺序
  • 具体如下:
#html中
<a href="{% url 'edit' user.0 %}">操作...</a>
#url中
path('login/manage/<int:id>/edit/',views.edit_user_info,name = 'edit')
#views中
def edit_user_info(request,id):
    default_userinfo = UserInfo.objects.filter(id = id).first()
    ....

可以看到<int:id>是在/edit/之前的,但是在html的url中这个顺序不影响。同时在views中对应的函数可以直接接收到url中正则匹配到的参数,而不用再通过解析get请求获取。

16.在django的模板中,可以通过obj.get_联表名_display获取到和后端get_联表名_display()的效果,直接返回对照过后的结果而不是对应的关联id。

17.django的模板可以进行的额外操作:

  • {{number : add 1}}就是python的+=1操作,{{datetime | date :'Y-m-d}}就是python中的strftime()函数(要注意的是里面的格式不需要带%)

18.modelform的字段校验函数

  • is_valid()检验提交post请求中的form表单中的字段是否为空,具体的字段就是在modelformfields列表定义的那些.
  • 同时还可以在modelforms类中进行字段校验 错误的信息会保存在用post请求实例化的modelform中。
class staffMdoelForm(ModelForm):
    phonenumber = forms.CharField(label='电话号码',max_length=11,min_length=11)#进行的字段校验 
    #规定电话号码的长度为11位
    #还可以通过validators参数进行正则筛选
    class Meta:
        model = Staff
        fields = ['name','age','gender','phonenumber','create_time']
    phonenumber = forms.CharField(label='电话号码',max_length=11,min_length=11)
    #规定电话号码的长度为11位
    #还可以通过validators参数进行正则筛选

19.调整返回的提示信息为汉语

  • 通过修改setting.py文件中的语言项
#setting.py
# LANGUAGE_CODE = "en-us" 注释掉
LANGUAGE_CODE = "zh-hans" #添加

20.modelform中instance参数

  • 通过设置单条数据对表单进行填充,例如:
#views.py中的一个方法
default_userinfo = UserInfo.objects.filter(id = id).first() #根据id在数据库中获取对应的一条数据
userform = userModelForm(instance=default_userinfo) # 通过instance参数设定一个实例化的表单 让他在前端渲染的时候自动在value上显示从数据库中拿到的默认值
  • 通过instance参数和data参数可以实现数据库数据的更新
#在views.py中的某个函数
default_userinfo = UserInfo.objects.filter(id = id).first()
userform = userModelForm(data=request.POST,instance=default_userinfo)
if userform.is_valid():
    userform.save()
    return redirect('/login/manage/')
else:
    return render(request,'edit_info.html',{
        'userform': userform,
    })
  • 如果想在用户输入以外的字段进行更新,那么在modelform实例化之后通过instacne属性可以修改:
userform = userModelForm(data=request.POST,instance=default_userinfo)
# useform.instance.字段 =  值

21.django查询排序

  • obj.objects.all().order_by('属性名')
    如果给属性名前面添加减号 代表逆向(高到低)排序

22.给原有的表中添加新的字段产生错误"django.core.exceptions.ValidationError" error

  • 产生原因:大概率是因为追加新的字段设置默认值的时候产生的格式错误,例如datetime格式但是没有设置默认或者默认格式错误,造成这样的原因。
  • 解决方法:重置所有的migrations
  • 操作:删除migrations文件夹下除了__init__.py文件以外所有的文件,然后重新执行数据库迁移命令:python manage.py makemigrations, python manage.py migrate
  • 如果还不成功可能是因为数据库中原本存在的数据导致,将数据库清库(所有表删除),然后在执行即可。

23.django报错'orderForm' object has no attribute 'get'

  • 原因是在设置input标签的样式的时候重写的父类方法出错,参数多写了一个self
#错误源码
    def __init__(self,*arg,**kwargs):
        super().__init__(self,*arg,**kwargs)
        for name, field in self.fields.items():
            # print(name,field) #这个是展示 self.fields.items()可以直接拿到定义在Meta中的fields字段
            field.widget.attrs = {'class' : 'form-control'} #为生成的表单input赋予css属性 
  • 解决方法 : 去掉super().__init__(self,*arg,**kwargs)这一行中的self即可,正确写法:
    super().__init__(*arg,**kwargs)
  • 探究一下为什么super不需要self作为方法的第一个参数:
    因为super()是一个类 reference -- 感觉好多...暂时搁置一下...😖

24.关闭浏览器校验

在form表单添加novalidate

<form method="post" novalidate >

25.关于models中null和black字段

  • 需要同时开启,但是数据库更推荐默认值为空值,因此设置default=None而不是null
    如果设置blank=True但是不开启null=True,那么在插入的时候,如果传回来的数据为空,插入会报错不能为null字段...总之还是很迷惑的报错....但是解决方法还是最好设置两个都为True并且设置默认字段为None。如果为null,那么在数据传入到前端的时候会默认输出null或者none,而不是将那片区域置空,对于前端的用户输入逻辑不太友好。

26.modelforms便捷写法

  • 在添加fields的时候,可以用__all__一次性获取全部,exclude = [ 排除的字段 ]排除掉不要的字段。
class orderForm(ModelForm):
    class Meta():
        model = Orderform
        fields = "__all__" #注意要引号
        # exclude = ['字段1','字段二', .... ]

27.后端表单正则校验

  • 在modelforms类中声明字段,然后通过validators参数设置正则表达式:
from django.core.validators import RegexValidator #需要导入正则类
class exampleModelForms(forms.ModelForm):
    moblie = forms.CharField(
        label = '手机号码',
        validactors = [RegexValidator(r'^1[3-9]\d{9}$','手机号格式错误')]       
        #校验失败返回后面的提示信息而不是默认的了~ 
    )

28.钩子方法进行字段校验

  • 详细的
  • 这里摘列出常用的代码结果
class exampleModelForms(forms.ModelForm):
   def clean_字段名(self): #这个需要在一个modelform类中 这个函数名modelform会自动生成对应的方法
           pass 
           name = self.cleaned_data.get('name')
           if name=='admin':
               raise ValidationError('admin是超级管理员,不能注册!')#这个错误会直接扔进该字段的错误类别中:name.errors
           return self.cleaned_data.get('name')

29.规定modelform中不可编辑的部分

在modelform中字段中设置参数disabled= True即可,这样在前端页面渲染出来的这一条字段是不可以更改的。

30.通过modelform判断要添加的数据是否重复

  • order_edit_form = orderEditForm().filter(id=你要查询的).exist() 返回True/False
    这个方法需要添加在modelform中的钩子方法中。
  • 排除自己以外的是否存在重复?(主要是在修改数据的时候)
    通过exclude排除自己后判断:order_edit_form = orderEditForm().exclude(id='自己的id' ).filter(id=你要查询的).exist() 返回True/False

总结 编辑和添加的不同

31.查询(根据某个字的一部分进行)

  • filter()还支持传入字典:
order_edit_form = orderEditForm().filter(id=你要查询的,other=你要查询的)
#等于
query_dict = { 'id' : 要查询的, 'other' : 要查询的 }
order_edit_form = orderEditForm().filter(**query_dict)
 #需要注意传入字典的时候需要两个**

PS. 通常我们再变量前加一个星号(*)表示这个变量是元组/列表,加两个星号表示这个参数是字典

32.问题记录:modelform不能更新一行数据的部分字段

  • 解决方法
    这里的解决方法很多 包括自定义钩子函数对post数据进行清晰、定义一个工厂函数处理每次不同的字段等,不过都太高端了,部分概念我还没有理解清楚😭
  • 因此我打算采取最简单的 -- 一个字段一个字段的去update😳。 或者采用dict的模式,处理不变的字段和更新后的字段 组合成为一个更新后的字典然后通过modelform保存..
  • 最后发现是我实例化出错了的问题....(真诚的眼瞎😭)
    row_object = Orderform.objects.filter(id = id).first() #注意这里实例化的是一个form类 之前错误的原因是这里也写成了一个modelform类 这就导致下面instance的时候会报错 而且默认不更新的前端数据实际上是会传递回来的 作为表单的默认值
    order_edit_form = orderEditForm(data=request.POST,instance=row_object) 
    #这里实例化的是一个modelforms类

33.django的条件查询

除了固定的等于(=)查询外,还可以通过给字段/属性添加下划线的方式规定范围查询

  • 数字方面:gt、gte、lt、lte:大于、大于等于、小于、小于等于。
    list = BookInfo.objects.filter(id__gt=3) 代表id大于3的数据
    PS. 不等于可以通过exclude过滤
  • 字符串方面:startswith、endswith:以指定值开头或结尾;contains:是否包含。
    list = BookInfo.objects.filter(btitle__endswith='部') 查询是否数据中btitle字段以‘部’这个字结尾的一行数据。
  • 日期查询: year、month、day、week_day、hour、minute、second:对日期时间类型的属性进行运算。
    list = BookInfo.objects.filter(bpub_date__year=1980) 查询1980年发表的图书
    list = BookInfo.objects.filter(bpub_date__gt=date(1990,1,1)) 查询1980年1月1日后发表的图书。
  • 还有很多查询: 例如比较同一行数据两个字段之间的关系(如阅读数量>=评论数量)、与或非查询、聚合等... 具体参考reference或者官网

PS. 1. filter可以连续使用 :list=BookInfo.objects.filter(bread__gt=20).filter(id__lt=3) 2. filter可以传入字典

34.设置get默认值

  • request.GET.get('page', '1') 获取get请求,如果get没有附带page参数,那么默认是1.