0、批量插入数据和choices参数
rom app01 import models
def index(request):
# 批量插入数据
# 方式一(不推荐)
for i in range(100):
models.Book.objects.create(name='书籍%s'%i,price=i+1) # 写了100次数据库
# 方式二
ll = []
for i in range(100):
book = models.Book(name='书籍%s' % i, price=i + 1)
ll.append(book)
# 打印原生sql
models.Book.objects.bulk_create(ll, 10)
return render(request, 'index.html')
"""
在设计表的时候 针对可以列举完全的可能性字段
一般都是用choices参数
"""
gender_choices = (
(1,'male'),
(2,'female'),
(3,'others')
)
gender = models.IntegerField(choices=gender_choices)
# 针对具有choices参数的字段 存储数据的时候还是按照字段本身的数据类型存储没有其他的约束,但是如果你存的字段在你列举的范围内 那么可以自动获取对应关系
user_obj.gender # 数字
user_obj.get_gender_display() # 固定格式 get_choices参数字段名_display()
"""有对应关系就拿对应关系,没有则还是数据本身不会报错"""
# 自己看看一些模型表的设计 体会choices参数的使用
0.5 多对多变关系的三种建立方式
# 全自动:利用orm自动帮我们创建第三张关系表
class Book(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author')
class Author(models.Model):
name = models.CharField(max_length=32)
"""
优点:代码不需要你写 非常的方便 还支持orm提供操作第三张关系表的方法...
不足之处:第三张关系表的扩展性极差(没有办法额外添加字段...)
"""
# 纯手动
class Book(models.Model):
name = models.CharField(max_length=32)
class Author(models.Model):
name = models.CharField(max_length=32)
class Book2Author(models.Model):
book_id = models.ForeignKey(to='Book')
author_id = models.ForeignKey(to='Author')
'''
优点:第三张表完全取决于你自己进行额外的扩展
不足之处:需要写的代码较多,不能够再使用orm提供的简单的方法
不建议你用该方式
'''
# 半自动
class Book(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author',
through='Book2Author',
through_fields=('book','author')
)
class Author(models.Model):
name = models.CharField(max_length=32)
# books = models.ManyToManyField(to='Book',
# through='Book2Author',
# through_fields=('author','book')
# )
class Book2Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')
"""
through_fields字段先后顺序
判断的本质:
通过第三张表查询对应的表 需要用到哪个字段就把哪个字段放前面
你也可以简化判断
当前表是谁 就把对应的关联字段放前面
半自动:可以使用orm的正反向查询 但是没法使用add,set,remove,clear这四个方法
"""
# 总结:你需要掌握的是全自动和半自动 为了扩展性更高 一般我们都会采用半自动(写代码要给自己留一条后路)
1、分页器组件介绍
1 项目数据量大了以后,比如涉及到分页,一页一页的加载显示
2 django中分页器组件,把分页常用的东西,封装到一个类中
3 实例化得到一个对象,对象里有属性和方法
2、分页器的简单使用
from django.core.paginator import Paginator
#######1 Paginator对象的属性和方法
book_list=models.Book.objects.all()
# 实例化得到对象
# 第一个参数:要分页的数据,book_list
# 第二个参数:每一页的条数10
paginator=Paginator(book_list,10)
# Paginator对象的属性和方法
print(paginator.per_page) # 每页显示的条数
print(paginator.count) # 总条数,总共要分页多少条数据
print(paginator.num_pages) # 总页码数
print(paginator.page_range) # 页码的生成器 [1,2,3,4,5,6,7,8,9,10]
######3 Page对象的属性和方法
# Page类 的对象
page=paginator.page(1) # 第一页的对象
# 每一页的对象,属性和方法
print(page.has_next()) # 有没有下一页
print(page.next_page_number()) # 下一页页码
print(page.has_previous()) # 是否有上一页
print(page.previous_page_number()) # 上一页页面 (当前页如果是第一页,没有上一页)
print(page.object_list) # 当前页的所有数据
print(page.number) # 当前页的页码数
##### 4 表模型中默认以id排序
class Meta:
ordering=('id', ) # 默认以id排序
2、1 视图
def index(request):
# 需要的第三个参数
page_num_int=int(request.GET.get('page',1))
book_list = models.Book.objects.all()
paginator = Paginator(book_list, 10)
# 需要的第一个参数:页码的生成器 [1,2,3,4,5,6,7,8,9,10]
page_range = paginator.page_range
# 需要的第二个参数,去到某一页的page对象
page = paginator.page(page_num_int)
return render(request, 'index.html', {'page_range':page_range,'page':page,'page_num_int':page_num_int})
2、2 模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<title>Title</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<table class="table">
<thead>
<tr>
<th>id</th>
<th>名字</th>
<th>价格</th>
</tr>
</thead>
<tbody>
{% for book in page.object_list %}
<tr>
<td>{{ book.id }}</td>
<td>{{ book.name }}</td>
<td>{{ book.price }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="text-center">
<nav aria-label="Page navigation">
<ul class="pagination">
{% if page.has_previous %}
<li>
<a href="/?page={{ page.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% else %}
<li class="disabled">
<a href="" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% endif %}
{% for page_num in page_range %}
{% if page_num_int == page_num %}
<li class="active"><a href="/?page={{ page_num }}">{{ page_num }}</a></li>
{% else %}
<li><a href="/?page={{ page_num }}">{{ page_num }}</a></li>
{% endif %}
{% endfor %}
{% if page.has_next %}
<li>
<a href="/?page={{ page.next_page_number }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% else %}
<li class="disabled">
<a href="" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
</div>
</div>
</body>
</html>
3、分页器的进阶使用
# 最多显示前5 后5 和当前,总共11个页码,如果少于11,全部显示出来
#逻辑分析
显示左5,右5,总共11个页,
1 如果总页码大于11
1.1 if 当前页码减5小于1,要生成1到11的列表(顾头不顾尾,共11个页码)
page_range=range(1,12)
1.2 elif 当前页码+5大于总页码,生成当前页码减10,到当前页码加1的列表(顾头不顾尾,共11个页码)
page_range=range(paginator.num_pages-10,paginator.num_pages+1)
1.3 else 生成当前页码-5,到当前页码+6的列表
page_range=range(current_page_num-5,current_page_num+6)
2 其它情况,生成的列表就是pageinator的page_range
page_range=paginator.page_range
3、1 视图
def index(request):
# 需要的第三个参数
page_num_int = int(request.GET.get('page', 1))
book_list = models.Book.objects.all()
paginator = Paginator(book_list, 10)
# 需要的第一个参数:页码的生成器 [1,2,3,4,5,6,7,8,9,10]
# page_range = paginator.page_range
if paginator.num_pages > 11:
# 当前条件符合了以后,有三种情况
if page_num_int - 5 < 1:
page_range = range(1, 12)
elif page_num_int + 5 > paginator.num_pages:
page_range = range(paginator.num_pages - 10, paginator.num_pages + 1)
else:
page_range = range(page_num_int - 5, page_num_int + 6)
else:
page_range = paginator.page_range
# 需要的第二个参数,去到某一页的page对象
page = paginator.page(page_num_int)
return render(request, 'index.html', {'page_range': page_range, 'page': page, 'page_num_int': page_num_int})
# 我的
def index(request):
book_list = models.Book.objects.all()
page_num = int(request.GET.get('page', 1)) # 当前页码
page_obj_all = Paginator(book_list, 10) # 所有的图书分页对象
page_all_num = page_obj_all.num_pages # 总页码数
if request.method == 'POST':
page_num = request.POST.get('search_page')
if not page_num:
page_num = '1'
if page_num.isdigit:
page_num = int(page_num) # 当前页码
if page_num > page_all_num:
page_num = page_all_num
else:
page_num = 1 # 当前页码
page_size = request.POST.get('page_size')
if not page_size:
page_size = '10'
if page_size.isdigit:
page_size = int(page_size)
if page_size > page_all_num:
page_size = page_all_num
else:
page_size = 10
page_obj_all = Paginator(book_list, page_size)
if page_all_num > 11:
if page_num - 5 < 1:
page_range = range(1, 12)
elif page_num + 5 > page_all_num:
page_range = range(page_all_num - 10, page_all_num + 1)
else:
page_range = range(page_num - 5, page_num + 6)
else:
page_range = page_obj_all.page_range
page_obj = page_obj_all.page(page_num) # 当前页的所有图书对象
return render(request, 'index.html', {'page_range': page_range, 'page_obj': page_obj, 'page_num': page_num})
3、2 模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<title>Title</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<table class="table">
<thead>
<tr>
<th>id</th>
<th>名字</th>
<th>价格</th>
</tr>
</thead>
<tbody>
{% for book in page.object_list %}
<tr>
<td>{{ book.id }}</td>
<td>{{ book.name }}</td>
<td>{{ book.price }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="text-center">
<nav aria-label="Page navigation">
<ul class="pagination">
{% if page.has_previous %}
<li>
<a href="/?page={{ page.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% else %}
<li class="disabled">
<a href="" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% endif %}
{% for page_num in page_range %}
{% if page_num_int == page_num %}
<li class="active"><a href="/?page={{ page_num }}">{{ page_num }}</a></li>
{% else %}
<li><a href="/?page={{ page_num }}">{{ page_num }}</a></li>
{% endif %}
{% endfor %}
{% if page.has_next %}
<li>
<a href="/?page={{ page.next_page_number }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% else %}
<li class="disabled">
<a href="" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
</div>
</div>
</body>
</html>
3、3 Jason版自定义分页器
class Pagination(object):
def __init__(self, current_page, all_count, per_page_num=2, pager_count=11):
"""
封装分页相关数据
:param current_page: 当前页
:param all_count: 数据库中的数据总条数
:param per_page_num: 每页显示的数据条数
:param pager_count: 最多显示的页码个数
"""
try:
current_page = int(current_page)
except Exception as e:
current_page = 1
if current_page < 1:
current_page = 1
self.current_page = current_page
self.all_count = all_count
self.per_page_num = per_page_num
# 总页码
all_pager, tmp = divmod(all_count, per_page_num)
if tmp:
all_pager += 1
self.all_pager = all_pager
self.pager_count = pager_count
self.pager_count_half = int((pager_count - 1) / 2)
@property
def start(self):
return (self.current_page - 1) * self.per_page_num
@property
def end(self):
return self.current_page * self.per_page_num
def page_html(self):
# 如果总页码 < 11个:
if self.all_pager <= self.pager_count:
pager_start = 1
pager_end = self.all_pager + 1
# 总页码 > 11
else:
# 当前页如果<=页面上最多显示11/2个页码
if self.current_page <= self.pager_count_half:
pager_start = 1
pager_end = self.pager_count + 1
# 当前页大于5
else:
# 页码翻到最后
if (self.current_page + self.pager_count_half) > self.all_pager:
pager_end = self.all_pager + 1
pager_start = self.all_pager - self.pager_count + 1
else:
pager_start = self.current_page - self.pager_count_half
pager_end = self.current_page + self.pager_count_half + 1
page_html_list = []
# 添加前面的nav和ul标签
page_html_list.append('''
<nav aria-label='Page navigation>'
<ul class='pagination'>
''')
first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
page_html_list.append(first_page)
if self.current_page <= 1:
prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
else:
prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)
page_html_list.append(prev_page)
for i in range(pager_start, pager_end):
if i == self.current_page:
temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
else:
temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
page_html_list.append(temp)
if self.current_page >= self.all_pager:
next_page = '<li class="disabled"><a href="#">下一页</a></li>'
else:
next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
page_html_list.append(next_page)
last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
page_html_list.append(last_page)
# 尾部添加标签
page_html_list.append('''
</nav>
</ul>
''')
return ''.join(page_html_list)
后端
def get_book(request):
book_list = models.Book.objects.all()
current_page = request.GET.get("page",1)
all_count = book_list.count()
page_obj = Pagination(current_page=current_page,all_count=all_count,per_page_num=10)
page_queryset = book_list[page_obj.start:page_obj.end]
return render(request,'booklist.html',locals())
前端
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
{% for book in page_queryset %}
<p>{{ book.title }}</p>
{% endfor %}
{{ page_obj.page_html|safe }}
</div>
</div>
</div>
4、forms组件介绍
1 注册功能,登录功能,前端需要校验(字段长度,邮箱是否合法。。。)
2 前端校验可以没有,后端校验是必须的,使用传统方式 if判断写的很多
3 借助于forms组件,可以快速实现字段的校验
from django.forms import Form
4 禁止浏览器自动检测数据,在form中填写novalidate
5、forms校验字段功能
1、在app目录下创建一个utils的py文件
### 1 写一个类,类里写要校验的字段
from django import forms
# Create your models here.
class Myform(forms.Form):
name = forms.CharField(max_length=10, min_length=3, widget=forms.widgets.TextInput(attrs={'class': 'form-control'}),
error_messages={'max_length': '不能多余10位',
'min_length': '不能小于3位',
'required': '必须填写'
},
label='用户名')
password = forms.CharField(max_length=10, min_length=3,
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}),
error_messages={'max_length': '不能多余10位',
'min_length': '不能小于3位',
'required': '必须填写'
},
label='密码')
re_password = forms.CharField(max_length=10, min_length=3,
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}),
error_messages={'max_length': '不能多余10位',
'min_length': '不能小于3位',
'required': '必须填写'
},
label='确认密码')
email = forms.EmailField(widget=forms.widgets.EmailInput(attrs={'class': 'form-control'}),
error_messages={'invalid': '邮箱格式不正确',
'required': '必须填写'
},
label='邮箱')
date = forms.DateField(required=False, widget=forms.widgets.DateInput(attrs={'class': 'form-control'}),
label='出生日期', error_messages={'invalid': '邮箱格式不正确',
'required': '必须填写'})
gender = forms.ChoiceField(choices=((1, '男'), (2, '女'), (3, '保密')), initial=3, widget=forms.widgets.RadioSelect(),
label='性别',
)
text = forms.CharField(
widget=forms.widgets.Textarea(attrs={'class': 'form-control', 'cols': 10, 'rows': 10, 'style': 'resize: none'}),
label='个人简介', error_messages={'required': '必须填写'}, )
### 2 视图函数中使用
def register(request):
# 数据可以是从前端传过来的,也可以是自己后台的数据
# 我现在有以下数据
data={'name':'wx','email':'33333@qq.com','age':900}
# data={'email':'33333@qq.com','age':100}
# data={'age':100}
# 校验数据是否合法
# 实例化得到form对象,把要校验的数据传入
form=myforms.MyForm(data)
# 校验数据:form.is_valid() 返回布尔类型
if form.is_valid():
print('校验通过')
# 校验通过的数据
print(form.cleaned_data) # 不一定是上面传入的数据
else:
print(form.cleaned_data)
print('校验失败')
# 哪个字段失败了?失败的原因是什么
print(form.errors)
print(type(form.errors))
from django.forms.utils import ErrorDict
#### 重写了__str__
print(form.errors.as_json())
print(form.errors.as_data())
# form.errors.as_ul() # 是为了渲染模板
return HttpResponse('ok')
6、forms渲染模板功能
## 视图函数
def register(request):
if request.method=='GET':
form=myforms.MyForm()
return render(request,'register.html',{'form':form})
elif request.method=='POST':
# 数据校验
form=myforms.MyForm(request.POST)
if form.is_valid():
print('校验通过,存数据库')
else:
print(form.errors.as_data())
print('校验失败,返回错误')
return HttpResponse('ok')
## 模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<hr>
<h1>手动创建模板</h1>
<form action="" method="post">
<p>用户名:<input type="text" name="name"></p>
<p>邮箱:<input type="text" name="email"></p>
<p>年龄:<input type="text" name="age"></p>
<p><input type="submit" value="提交"></p>
</form>
<hr>
<h1>半自动渲染模板1</h1>
<form action="" method="post">
<p>用户名:{{ form.name }}</p>
<p>邮箱:{{ form.email }}</p>
<p>年龄:{{ form.age }}</p>
<p><input type="submit" value="提交"></p>
</form>
<h1>半自动渲染模板2(用的最多)</h1>
<form action="" method="post">
<p>{{ form.name.label }}--{{ form.name }}</p>
<p>{{ form.email.label }}---{{ form.email }}</p>
<p>{{ form.age.label }}---{{ form.age }}</p>
<p><input type="submit" value="提交"></p>
</form>
<h1>半自动渲染模板3(用的最多)</h1>
<form action="" method="post">
{% for foo in form %}
<p>{{ foo.label }} :{{ foo }}</p>
{% endfor %}
<p><input type="submit" value="提交"></p>
</form>
<h1>全自动(了解)</h1>
<form action="" method="post">
{# {{ form.as_ul }}#}
{{ form.as_p }}
{# <table>#}
{# {{ form.as_table }}#}
{# </table>#}
<p><input type="submit" value="提交"></p>
</form>
</body>
</html>
1、froms渲染错误信息
1 form对象.errors 字典,所有的错误信息
2 name对象.errors 当前字段的错误信息
3 校验数据只校验类中出现的字段 多传不影响 多传的字段直接忽略
4 校验数据 默认情况下 类里面所有的字段都必须传值
方式一:不保留上次输入的结果
## 视图函数
def register(request):
if request.method =='GET':
form = myforms.MyForm()
return render(request, 'register.html',{'form':form})
else:
form = myforms.MyForm(request.POST)
if form.is_valid():
return redirect('http://www.baidu.com')
else:
return render(request, 'register.html',{'form':form})
## 模板
<form action="" method="post" novalidate> # novalidate参数,禁止浏览器自动校验
{% for foo in form %}
<div class="form-group">
<label for="">{{ foo.label }}</label>
{{ foo }}
<span class="text-danger pull-right">{{ foo.errors.0 }}</span>
</div>
{% endfor %}
<div class="text-center">
<input type="submit" value="提交" class="btn btn-danger">
</div>
</form>
方式二:保留上一次输入的参数
def index(request):
form_obj = MyForm()
if request.method == 'POST':
form_obj = MyForm(request.POST)
if form_obj.is_valid():
return HttpResponse('OK')
return render(request,'index.html',{‘form’: from_obj})
"""
1.必备的条件 get请求和post传给html页面对象变量名必须一样
2.forms组件当你的数据不合法的情况下 会保存你上次的数据 让你基于之前的结果进行修改更加的人性化
"""
2、forms组件参数配置
from django.forms import widgets
# 定制模板中的显示样式,及配置类
widget=widgets.PasswordInput(attrs={'class': 'form-control'})
# 错误信息中文显示
error_messages={'min_length': '太短了小伙子'}
class MyForm(forms.Form):
# 校验这个字段,最大长度是32,最小长度是3
name = forms.CharField(required=False, max_length=32, min_length=3, label='用户名',
widget=widgets.TextInput(attrs={'class': 'form-control'}),
error_messages={'min_length': '太短了小伙子',
'max_length': '太长了小伙子'
})
password = forms.CharField(required=False, max_length=32, min_length=3, label='密码',
widget=widgets.PasswordInput(attrs={'class': 'form-control'}),
error_messages={'min_length': '太短了小伙子',
'max_length': '太长了小伙子'
})
re_password = forms.CharField(required=False, max_length=32, min_length=3,
label='确认密码',
widget=widgets.PasswordInput(attrs={'class':'form-control'}),
error_messages={'min_length': '太短了小伙子',
'max_length': '太长了小伙子'
})
email = forms.EmailField(label='邮箱', error_messages={'required': '小伙子,这个必填',
'invalid':'邮箱格式不正确',
},
widget=widgets.TextInput(attrs={'class': 'form-control'}))
age = forms.IntegerField(max_value=200, min_value=0, label='年龄',
error_messages={'required': '小伙子,这个必填',
'min_value': '太小了,小伙子',
'max_value': '太大了,小伙子',
},
widget=widgets.TextInput(attrs={'class': 'form-control'}))
text = forms.CharField(label='个人简介',
error_messages={'required': '小伙子,这个必填'},
widget=widgets.Textarea(attrs={'class': 'form-control'}))
date = forms.CharField(label='出生日期',
error_messages={'required': '小伙子,这个必填'},
widget=widgets.DateInput(attrs={'class': 'form-control'}))
3、局部钩子和全局钩子(HOOK)
"""
钩子函数在forms组件中就类似于第二道关卡,能够让我们自定义校验规则
## 局部钩子的使用
# 1 在自定义的Form类中写 clean_字段名
# 2 取出字段的真正值,name=self.cleaned_data.get('name')
# 3 判断自己的规则,如果判断失败,抛出ValidationError
# 4 如果通过,return 字段名
"""
from django.forms import ValidationError
# 局部钩子
def clean_name(self):
# name对应的值,如何取到?
name = self.cleaned_data.get('name')
if name.startswith('sb'):
# 不让校验通过
raise ValidationError('不能以sb开头')
# self.add_error('name','不能以sb开头')
else:
# 校验通过,返回name
return name
# 全局钩子
def clean(self):
# name=self.cleaned_data.get('name')
# print(name)
password = self.cleaned_data.get('password')
re_password = self.cleaned_data.get('re_password')
if password == re_password:
return self.cleaned_data
# return {'lqz':"nb"}
else:
raise ValidationError('两次密码不一致')
# self.add_error('re_password','两次密码不一致')
4、forms组件其他参数及补充知识点
label 字段名
error_messages 自定义报错信息
initial 默认值
required 控制字段是否必填
"""
1.字段没有样式
2.针对不同类型的input如何修改
text
password
date
radio
checkbox
...
"""
widget=forms.widgets.PasswordInput(attrs={'class':'form-control c1 c2'})
# 多个属性值的话 直接空格隔开即可
# 第一道关卡里面还支持正则校验
validators=[
RegexValidator(r'^[0-9]+$', '请输入数字'),
RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
]
"""其他类型form的渲染"""
# 单选radio
gender = forms.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3,
widget=forms.widgets.RadioSelect()
)
# 单选select
hobby = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球")),
label="爱好",
initial=3,
widget=forms.widgets.Select()
)
# 多选select
hobby1 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球")),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
# 单选checkbox
keep = forms.ChoiceField(
label="是否记住密码",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
# 多选checkbox
hobby2 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球")),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
5、cookie session token
1 https://www.cnblogs.com/liuqingzheng/articles/8990027.htmlp
2 cookie:客户端浏览器上的键值对
3 session:存在服务端的键值对
4 token:加密的键值对,如果放在客户端浏览器上,它就叫cookie, 服务端签发的加密字符串
head.{name:wuxi,age:18}.eseetsweasdca
base64加码:
asdfasfd.asdfasdf.asdfasdfaeraew
后端校验:
用这个token去查我的账户余额,向银行发请求,银行校验通过,是银行给你的,---》返回你的余额
head.{name:wuxi,age:18}.eseetsweasdca
head.{name:wen,age:18}.eseetsweasdca
6、django中cookie的使用
def cookie_test(request):
# 浏览器向我这个地址发一个请求,就在浏览器写入 name = wuxi
obj=HttpResponse('ok')
obj.set_cookie('name','wuxi') # 写入到浏览器了,在http响应头里:cookie: name=wen
obj.set_cookie('age','19')
return obj
def get_cookie(request):
print(request.COOKIES)
print(request.COOKIES.get('name'))
return HttpResponse('我拿了你传过来的cookie')
def delete_cookie(request):
obj=HttpResponse('我删掉了你 name 这个cookie ')
obj.delete_cookie('name')
return obj
三、拓展
https://www.cnblogs.com/liuqingzheng/articles/8980355.html
企业级什么意思
Apache协议
https://www.runoob.com/w3cnote/open-source-license.html
Apache License(Apache许可证),是Apache软件基金会发布的一个自由软件许可证。
Apache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,同样鼓励代码共享和最终原作者的著作权,同样允许源代码修改和再发布。但是也需要遵循以下条件:
需要给代码的用户一份Apache Licence。
如果修改了代码,需要再被修改的文件中说明。
在衍生的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议,商标,专利声明和其他原来作者规定需要包含的说明。
如果再发布的产品中包含一个Notice文件,则在Notice文件中需要带有Apache Licence。你可以再Notice中增加自己的许可,但是不可以表现为对Apache Licence构成更改。
Apache Licence也是对商业应用友好的许可。使用者也可以再需要的时候修改代码来满足并作为开源或商业产品发布/销售。
使用这个协议的好处是:
永久权利 一旦被授权,永久拥有。
全球范围的权利 在一个国家获得授权,适用于所有国家。假如你在美国,许可是从印度授权的,也没有问题。
授权免费 无版税, 前期、后期均无任何费用。
授权无排他性 任何人都可以获得授权
授权不可撤消 一旦获得授权,没有任何人可以取消。比如,你基于该产品代码开发了衍生产品,你不用担心会在某一天被禁止使用该代码
0、form组件校验源码
1 读的入口是:
form.is_valid()--->self.errors(BaseForm类)---》self.full_clean()(BaseForm类)--》
-self._clean_fields(局部数据校验)和self._clean_form(全局数据校验)
2 self._clean_fields(BaseForm类)
for name, field in self.fields.items():
try:
# 字段自己的校验(最大值,最小值,是不是邮箱格式)
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name): # 反射判断有没有clean_字段名
value = getattr(self, 'clean_%s' % name)() # 必须返回字段值的原因,是这里要赋值
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)
3 self._clean_form(BaseForm类) 全局钩子
try:
cleaned_data = self.clean() # self.clean执行的是自己类的clean方法
except ValidationError as e:
self.add_error(None, e) # 全局的错误可以用get(__all__)获取
面向切面编程(AOP OOP:面向对象编程)
1、django中cookie的使用
# 0 会话跟踪,会话保持
# 1 cookie规范
-记住:当前网站在浏览器上cookie个数和大小有限制
-Cookie大小上限为4KB;
-一个服务器最多在客户端浏览器上保存20个Cookie;
-一个浏览器最多保存300个Cookie;
# 2 django中操作cookie
-增:obj.set_cookie('key','value')
-删: obj.delete_cookie('key') # 本质是将浏览器的cookie设置过期
-查: request.COOKIES.get('key')
-改: obj.set_cookie('key','value1')
# 3 带签名的cookie(加盐,加密)
-增:obj.set_signed_cookie('name','wuxi','123')
-删: obj.delete_cookie('name') # 设置过期
-查: request.get_signed_cookie('name',salt='123')
-改: obj.set_signed_cookie('name','wuxi2','123')
2、cookie版登陆校验
路由
# cookie版登录
path('login/', views.login),
path('order/', views.order),
path('logout/', views.logout),
path('userinfo/', views.userinfo),
视图函数
## 登录认证装饰器
def login_auth(func):
def inner(request, *args, **kwargs):
# 登录校验
name = request.COOKIES.get('name')
if name:
res = func(request, *args, **kwargs)
return res
else:
path = request.get_full_path() # 上一次登录的URL
return redirect('/login/?returnUrl=%s' % path)
return inner
### cookie版登录
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
else:
name = request.POST.get('name')
password = request.POST.get('password')
if name == 'wuxi' and password == '123':
# 写入cookie
# 登录成功,重定向
path = request.GET.get('returnUrl')
if path:
obj = redirect(path)
else:
obj = redirect('/index/')
obj.set_cookie('name', name)
return obj
else:
return HttpResponse('用户名或密码错误')
# def order(request):
# name = request.COOKIES.get('name')
# if name:
# return render(request,'order.html')
# else:
# return redirect('/login')
## 装饰器版本(只要加了装饰器,一旦进入这个视图函数,就表明登录成了)
@login_auth
def order(request):
return render(request, 'order.html')
@login_auth
def userinfo(request):
return render(request, 'userinfo.html')
def logout(request):
obj = HttpResponse('退出登录成功')
obj.delete_cookie('name')
return obj
模板
login.html
<form action="" method="post">
<p>用户名:<input type="text" name="name"></p>
<p>密码:<input type="password" name="password"></p>
<p><input type="submit" value="提交"></p>
</form>
order
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="/logout/">点我退出</a>
</body>
</html>
userinfo.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户信息页面</h1>
</body>
</html>
3、django中session的使用
1 存在于服务端的键值对
2 同一个浏览器不允许登录多个账户,不同浏览器可以登录同一个账户
3 session的使用(必须迁移数据),当做字典来处理
-增:request.session['name']='wuxi'
-查:request.session['name']
-改:request.session['name']='WUXI'
-删:del request.session['name']
-设置过期时间:request.session.set_expiry(10)
4 session的其它使用
-request.session.setdefault('k1',123)
-request.session.get('name',None)
-del request.session['k1']
-request.session.keys()
-request.session.values()
-request.session.items()
-request.session.session_key # 获取那个随机字符串,django_session表中session_key字段
-request.session.clear_expired() # 清除django_session表中,过期的session
-request.session.exists("session_key") # 判断这个随机字符串(session_key字段),有没有数据
-request.session.delete() # 删除所有的值,django_session表中删除当前登录者的这条记录
-request.session.flush() # 干了上面那个事,把cookie设置为过期
4、django中session的配置
def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
domain=None, secure=False, httponly=False)
# key
# value
# max_age:传个数字,以秒计,过期时间,有默认值 (6天后过期:60*60*24*5)
---了解
# expires:传时间对象,date=datetime.timedelta(days=5) 5天后过期
# path:默认 / 表示当前域下的所有路径 http://127.0.0.1:8000/wuxi/dfd/
# domain:在那个域下有效
# secure:是否Https传输cookie
# httponly:cookie只支持http传输
1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)
2. 缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎
SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎
SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()
4. 缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎
5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎
其他公用设置项:
SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默认)***记住
---了解
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认)
SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认)
SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认)
5、cbv加装饰器
from django.views import View
from django.utils.decorators import method_decorator
# 使用登录认证装饰器
# 用法一
@method_decorator(login_auth,name='get')
@method_decorator(login_auth,name='post')
class UserInfo(View):
# 用法二
@method_decorator(login_auth)
def get(self, request, *args, **kwargs):
return HttpResponse('userinfo get')
# 用法三
@methed_decorator(login_auth)
def dispatch(self,request, *args, **kwargs):
return super(UserInfo, self).dispatch(request, *args, **kwargs)
# 总结:三种用法
-加在类上:@method_decorator(login_auth,name='get')
-加在方法上:@method_decorator(login_auth)
-加在dispatch方法上,以下所有的类都会装饰上
6、中间件简介
# 中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出。因为改变的是全局,所以需要谨慎实用,用不好会影响到性能
# django内置中间件
'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',
2、自定义中间件
1 自定义步骤:
-写一个类,继承MiddlewareMixin
-里面写方法process_request(请求来了,一定会触发它的执行)
-在setting中配置(注意,放在前和放在后)
MIDDLEWARE = [
...
'app01.mymiddle.MyMiddleware1',
...
]
3、process_request,process_response,process_view,process_exception
1 process_request(self, request对象)
2 process_response(self, request对象, response对象)
3 多个中间件,执行顺序是什么?
# 请求来的时候从上往下执行:process_request
# 请求走的时候,从下往上执行:process_response
4 process_request可以干什么?
-写一个中间件,不管前端用什么编码,在requset.data中都有post的数据
-频率限制(限制某个ip地址,一分钟只能访问5次)
-登录认证(只要没登录,重定向到login路径)、
-记录用户访问日志(ip,时间,访问路径)
-可以return值,但当前请求会直接返回
5 process_response可以干什么?内部有response对象
-统一给所有(某几个路径)加cookie
-统一给所有(某几个路径)加响应头
"""
process_request
1.请求来的时候需要经过每一个中间件里面的process_request方法
结果的顺序是按照配置文件中注册的中间件从上往下的顺序依次执行
2.如果中间件里面没有定义该方法,那么直接跳过执行下一个中间件
3.如果该方法返回了HttpResponse对象,那么请求将不再继续往后执行
而是直接原路返回(校验失败不允许访问...)
process_request方法就是用来做全局相关的所有限制功能
process_response
1.响应走的时候需要结果每一个中间件里面的process_response方法
该方法有两个额外的参数request,response
2.该方法必须返回一个HttpResponse对象
1.默认返回的就是形参response
2.你也可以自己返回自己的
3.顺序是按照配置文件中注册了的中间件从下往上依次经过
如果你没有定义的话 直接跳过执行下一个
研究如果在第一个process_request方法就已经返回了HttpResponse对象,那么响应走的时候是经过所有的中间件里面的process_response还是有其他情况
是其他情况
就是会直接走同级别的process_reponse返回
flask框架也有一个中间件但是它的规律
只要返回数据了就必须经过所有中间件里面的类似于process_reponse方法
"""
6 process_view 路由匹配成功和视图函数执行之前执行(callback就是视图函数),顺序是按照配置文件中注册的中间件从上往下的顺序依次执行
def process_view(self, request, callback, callback_args, callback_kwargs):
# print(callback)
# print(callback_args)
# print(callback_kwargs)
#
res=callback(request) # 提前调用视图函数
#
print("中间件1的process_view")
return res
7 process_exception 视图函数出错,会执行它(全局异常捕获)(记录日志,哪个ip地址,访问哪个路径,出的错)
# 全局异常捕获,返回4开头的
def process_exception(self, request, exception):
print(exception)
return render(request,'error.html')
8 process_template_response
返回的HttpResponse对象有render属性的时候才会触发
顺序是按照配置文件中注册了的中间件从下往上依次经过
4 CSRF_TOKEN跨站请求伪造
1 跨站请求伪造
2 代码演示
3 django解决了csrf攻击,中间件:django.middleware.csrf.CsrfViewMiddleware
4 后期中间件不能注释,每次发送post请求,都需要携带csrf_token随机字符串
-form表单提交
-在form表单中 {% csrf_token %}
-ajax提交(如何携带)
方式一:放到data中
$.ajax({
url: '/csrf_test/',
method: 'post',
data: {'name': $('[name="name"]').val(),
'password': $('[name="password"]').val(),
'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()
},
success: function (data) {
console.log('成功了')
console.log(data)
},
error: function (data) {
console.log('xxxxx')
console.log(data)
}
})
方式二:放到data中
'csrfmiddlewaretoken':'{{ csrf_token }}'
方式三:放到头中
headers:{'X-CSRFToken':'{{csrf_token}}'},
# jquery.cookie.js
-在浏览器中对cookie进行增,删,查,改
-前后端分离(js操作cookie)
// 第三种 通用方式直接拷贝js代码并应用到自己的html页面上即可
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
# 全局使用,局部禁csrf
-在视图函数上加装饰器
from django.views.decorators.csrf import csrf_exempt,csrf_protect
# 全局启用,局部禁用(中间件不能注释,这个视图函数,已经没有csrf校验了)
@csrf_exempt
def csrf_test(request):
if request.method=='GET':
return render(request,'csrf_test.html')
else:
name=request.POST.get('name')
password=request.POST.get('password')
print(name)
print(password)
return HttpResponse('登录成功')
# 全局禁用,局部使用csrf
@csrf_protect
def csrf_test(request):
if request.method=='GET':
return render(request,'csrf_test.html')
else:
name=request.POST.get('name')
password=request.POST.get('password')
print(name)
print(password)
return HttpResponse('登录成功')
# 古怪的使用方式,在urls.py中
path('csrf_test/', csrf_exempt(views.csrf_test))
from django.views import View
# @method_decorator(csrf_protect,name='post') # 针对csrf_protect 第二种方式可以
# @method_decorator(csrf_exempt,name='post') # 针对csrf_exempt 第二种方式不可以
@method_decorator(csrf_exempt,name='dispatch')
class MyCsrfToken(View):
# @method_decorator(csrf_protect) # 针对csrf_protect 第三种方式可以
# @method_decorator(csrf_exempt) # 针对csrf_exempt 第三种方式可以
def dispatch(self, request, *args, **kwargs):
return super(MyCsrfToken, self).dispatch(request,*args,**kwargs)
def get(self,request):
return HttpResponse('get')
# @method_decorator(csrf_protect) # 针对csrf_protect 第一种方式可以
# @method_decorator(csrf_exempt) # 针对csrf_exempt 第一种方式不可以
def post(self,request):
return HttpResponse('post')
1、importlib模块
# 模块:importlib
import importlib
res = 'myfile.b'
ret = importlib.import_module(res) # from myfile import b
# 该方法最小只能到py文件名
print(ret)
2、基于此模块的使用方法
import settings # 自定义的配置文件,里面配置的是字符串
import importlib
def send_all(content):
for path_str in settings.NOTIFY_LIST: #'notify.email.Email'
module_path,class_name = path_str.rsplit('.',maxsplit=1)
# module_path = 'notify.email' class_name = 'Email'
# 1 利用字符串导入模块
module = importlib.import_module(module_path) # from notify import email
# 2 利用反射获取类名
cls = getattr(module,class_name) # Email、QQ、Wechat
# 3 生成类的对象
obj = cls()
# 4 利用鸭子类型直接调用send方法
obj.send(content)
1、auth组件介绍
1 我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码等功能,内置了强大的用户认证系统--auth,是一个app
2、内置属性方法
数据迁移以后使用
authenticate用户认证
from django.contrib import auth
def login(request):
if request.method=='GET':
return render(request,'login.html')
else:
name=request.POST.get('name')
password=request.POST.get('password') # 明文
## 方案行不通,密码是密文的,永远匹配不成功
# user=User.objects.filter(username=name,password=password)
## 使用此方案
## 第一个参数必须是request对象
##username和password
user=auth.authenticate(request,username=name,password=password)
if user:
return HttpResponse('登录成功')
else:
return HttpResponse('用户名或密码错误')
login
# 表示用户登录了
# 1 存了session
# 2 以后所有的视图函数,都可以使用request.user,它就是当前登录用户
auth.login(request,user)
logout
def logout(request):
# 后续再访问视图函数,就没有当前登录用户了request.user(匿名用户AnonymousUser)
auth.logout(request)
return redirect('/index/')
is_authenticated
# is_authenticated 返回True或者False,判断用户是否登录
# 用在视图中
if request.user.is_authenticated:
print('用户登录了')
else:
print('用户没有登录,匿名用户')
# 用在模板中
{% if request.user.is_authenticated %}
{{ request.user.username }} 登录了
{% else %}
<a href="/login/">滚去登录</a>
{% endif %}
login_requierd
1 装饰器,装饰再视图函数上,只要没有登录,就进不来
# 必须登录后才能访问
@login_required(login_url='/login/')
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
else:
name = request.POST.get('name')
password = request.POST.get('password') # 明文
# 先获取到用户对象
user = User.objects.filter(username=name).first()
# 判断密码是否正确
flag = user.check_password(password)
if flag:
auth.login(request, user)
url = request.GET.get('next')
if url:
return redirect(url)
else:
return redirect('/index/')
else:
return HttpResponse('用户名或密码错误')
# 必须登录后才能访问
@login_required(login_url='/login/')
def order(request):
print(request.user) # 如何实现的?
print(request.user.username) # AnonymousUser
if request.user.is_authenticated:
print('用户登录了')
else:
print('用户没有登录,匿名用户')
return render(request, 'order.html')
# 5.校验用户是否登陆装饰器
from django.contrib.auth.decorators import login_required
# 局部配置
@login_required(login_url='/login/')
# 全局配置
LOGIN_URL = '/login/'
1.如果局部和全局都有 该听谁的?
局部 > 全局
2.局部和全局哪个好呢?
全局的好处在于无需重复写代码 但是跳转的页面却很单一
局部的好处在于不同的视图函数在用户没有登陆的情况下可以跳转到不同的页面
create_user
# 使用内置的create_user或者create_superuser方法
user=User.objects.create_user(username=name,password=password)
# 创建超级用户(了解):使用代码创建超级用户 邮箱是必填的 而用命令创建则可以不填
user=User.objects.create_superuser(username=name,password=password, email=email)
check_password
## 有了用户,校验密码是否正确
# 先获取到用户对象
user = User.objects.filter(username=name).first()
# 判断密码是否正确
flag=user.check_password(password)
set_password
def change_password(request):
if request.method == 'GET':
return render(request, 'change_pwd.html')
else:
old_pwd = request.POST.get('old_pwd')
new_pwd = request.POST.get('new_pwd')
re_new_pwd = request.POST.get('re_new_pwd')
if request.user.check_password(old_pwd):
# 密码正确再修改
request.user.set_password(new_pwd)
# 记住保存(****)
request.user.save()
return redirect('/login/')
else:
return HttpResponse('原来密码错误')
3 User对象的属性
is_staff : 用户是否拥有网站的管理权限,是否可以登录到后台管理
is_superuser:是否是超级管理员(如果is_staff=1,可以任意增删查改任何表数据)
is_active : 是否允许用户登录, 设置为 False,可以在不删除用户的前提下禁止用户登录(三次密码输入错误禁用用户)
4 扩展默认的auth_user表
1 内置的auth_user表,要加字段,加不了,扩展该表
-方式一:一对一
-方式二,通过继承
# 方式二:通过继承,一定要记住再setting中配置
## 重点:使用这种方式,一开始就要用
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
# id=models.AutoField(primary_key=True)
# username = models.CharField(max_length=128)
phone = models.CharField(max_length=32)
addr = models.CharField(max_length=32)
## setting.py中
AUTH_USER_MODEL = "app01.User"
如果项目一开始没有扩展auth_user表,后期想扩展的操作步骤
1 备份--删库---》重新创建出数据库
2 所有app的数据迁移记录删除migrations下除了__init__.py都删除
3 (重要)去源码中删除auth和admin 这俩app的migrations下除了__init__.py都删除
4 数据迁移,同步到数据库
5 备份的数据,恢复回去
5 自定义中间表(中介模型)
1 多对多关系中,第三张表的建立
-默认使用ManyToMany,自动创建
-使用中介模型
-即手动创建第三张表,又要使用好用的查询
-完全自己写第三张表
# 使用中介模型
class Author(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
age = models.IntegerField()
author_detail = models.OneToOneField(to='AuthorDatail', to_field='nid', unique=True, on_delete=models.CASCADE)
class AuthorDatail(models.Model):
nid = models.AutoField(primary_key=True)
telephone = models.BigIntegerField()
birthday = models.DateField()
addr = models.CharField(max_length=64)
class Book(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5, decimal_places=2)
publish_date = models.DateField()
publish = models.ForeignKey(to='Publish', to_field='nid', on_delete=models.CASCADE)
# 当前在哪个表中,元组中的第一个参数就是 表明_id
authors=models.ManyToManyField(to='Author',through='AuthorToBook',through_fields=('book_id','author_id'))
def __str__(self):
return self.name
class Publish(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
city = models.CharField(max_length=32)
email = models.EmailField()
class AuthorToBook(models.Model):
nid = models.AutoField(primary_key=True)
book_id = models.ForeignKey(to=Book, to_field='nid', on_delete=models.CASCADE)
author_id = models.ForeignKey(to=Author, to_field='nid', on_delete=models.CASCADE)
date=models.DecimalField()
# s1.py
import os
if __name__ == '__main__':
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day76.settings")
import django
django.setup()
from app01 import models
# 这本书是lqz和egon写的
# book=models.Book.objects.get(pk=1)
# # book.authors.add(1,2) # 用不了了
# # 只能手动写
# models.AuthorToBook.objects.create(book_id_id=1,author_id_id=1)
# models.AuthorToBook.objects.create(book_id_id=1,author_id_id=2)
# 金瓶梅这本书所有的作者
# book = models.Book.objects.get(pk=1)
# res=models.AuthorToBook.objects.filter(book_id=book)
# print(res)
# book = models.Book.objects.get(pk=1)
# print(book.authors.all())
# add ,remove, clear,set
# 但是连表操作,book.authors这些都能用
book = models.Book.objects.get(pk=1)
book.authors.add(1,2) # 不能用了