跳到主要内容
Jason Lee
The owner of this blog site

I'm a programmer who loves to learn new technologies and build cool stuff. I'm currently working as a software engineer at a small company in Shanghai. Hope to find some connections with you!

View All Authors

django get和post获取请求参数或路由变量

· 阅读需 2 分钟
Jason Lee
The owner of this blog site

前言

前后端分离的情况下,数据通过 axios 传输到后端,如何取出携带的数据?

get 请求

get 请求里面,可能携带的参数位置都在 url 上,分别是路径上的参数和查询参数:

类视图中的获取方式如下:

def get(self, request, *args, **kwargs):
user_id = kwargs.get('id') # 路径参数
filter_type = request.GET.get('query') # 查询参数

而在 urls.py 中,pk 要作为参数的名字被设置:

path('friend/request/<uuid:pk>/', api.FriendshipRequestView.as_view(), name='friendshiprequest'),

post 请求

两种可能的参数,一种是通过表单传来的数据,另一种是通过 json 格式传来的数据:

axios.post('/api/login/', this.form) # 通过表单
axios.post('api/post/', {'body': this.body}) # 通过json

他们的获取方式分别是:

def post(self, request,  *args, **kwargs):
form = request.POST # 表单
body = request.data.get('body') # json

request.data 也可以获得表单数据,所以建议还是都用 request.data

django默认和自定义的用户身份验证

· 阅读需 3 分钟
Jason Lee
The owner of this blog site

前言

在做项目时,身份验证是目前疑虑最多的地方:为什么这里要新建一个 User 模型?为什么要重写管理器?这篇文章通过查阅文档,尝试从最佳实践的角度给出答案。

默认验证方式

模型 视图函数 表单

注册 登录 登出

默认 User 模型

默认 User 模型主要包含以下字段:username、password、email、first_name、last_name。也就是说,如果有别的字段的需求(比如用户头像),就要自定义用户模型。目前暂不涉及权限,等到后续项目中接触到了,再添加。

视图函数

注册

没有内置的注册视图,有内置的注册表单 UserCreationForm。UserCreationForm 的要求字段为用户名、邮箱和两次密码输入。

# views.py
class SignUpView(FormView):
template_name = 'core/signup.html'
form_class = forms.SignUpForm
success_url = '/login/'

def form_valid(self, form):
form.save()
return super().form_valid(form)

# forms.py
class SignUpForm(UserCreationForm):
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']

username = forms.CharField(widget=forms.TextInput(attrs={
'placeholder': 'Your username',
'class': 'w-full py-4 px-6 rounded-xl'
}))
email = forms.CharField(widget=forms.EmailInput(attrs={
'placeholder': 'Your email address',
'class': 'w-full py-4 px-6 rounded-xl'
}))
password1 = forms.CharField(widget=forms.PasswordInput(attrs={
'placeholder': 'Your password',
'class': 'w-full py-4 px-6 rounded-xl'
}))
password2 = forms.CharField(widget=forms.PasswordInput(attrs={
'placeholder': 'Repeat password',
'class': 'w-full py-4 px-6 rounded-xl'
}))

登录

有内置视图 LoginView,有内置的表单 AuthenticationForm

# forms.py
class LogInForm(AuthenticationForm):
username = forms.CharField(widget=forms.TextInput(attrs={
'placeholder': 'Your username',
'class': 'w-full py-4 px-6 rounded-xl'
}))
password = forms.CharField(widget=forms.PasswordInput(attrs={
'placeholder': 'Your password',
'class': 'w-full py-4 px-6 rounded-xl'
}))

# urls.py
path('login/', auth_views.LoginView.as_view(template_name='core/login.html', authentication_form=LogInForm), name='login'),

# settings.py
LOGIN_REDIRECT_URL = '/'

登出

有内置视图 LogoutView,无需视图

# urls.py
path('logout/', auth_views.LogoutView.as_view(), name='logout')

# settings.py
LOGOUT_REDIRECT_URL = '/'

自定义验证方式

有两个可以自定义的地方:认证后端和 User 模型

认证后端

前面提到的 authenticate 和 login 方法都是 django 默认认证后端提供的。

User 模型

一般有两个需要扩展:模型本身和管理器(manager)

# models.py

class CustomUserManager(UserManager):
def _create_user(self, name, email, password, **extra_fields):
if not email:
raise ValueError("You have not provided a valid e-mail address")

email = self.normalize_email(email)
user = self.model(email=email, name=name, **extra_fields)
user.set_password(password)
user.save(using=self._db)

return user

def create_user(self, name=None, email=None, password=None, **extra_fields):
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
return self._create_user(name, email, password, **extra_fields)

def create_superuser(self, name=None, email=None, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
return self._create_user(name, email, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField(unique=True)
name = models.CharField(max_length=255, blank=True, default='')
avatar = models.ImageField(upload_to='avatars', blank=True, null=True)

is_active = models.BooleanField(default=True)
is_superuser = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)

date_joined = models.DateTimeField(default=timezone.now)
last_login = models.DateTimeField(blank=True, null=True)

objects = CustomUserManager()

USERNAME_FIELD = 'email'
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = []

JWT认证

· 阅读需 5 分钟
Jason Lee
The owner of this blog site

前言

各种认证方式一直是我头痛的点。奇怪的是,几乎所有的 web 开发教程都默认你已经会了,而不会专门教学,只是一笔带过。你只能自己去找博客看。参考视频教程

jwt 归为一种身份验证(authentication)的方式。

令牌(token)

这个词挺神奇的。transformer 结构里有 token 这个概念,这里也有。不过他们的含义应该完全不同。在 web 开发中,token 指的是令牌,或者凭证。

最开始,人们使用 session+cookie 的认证方式。这种情景下,服务器存储用户的相关信息,仅返回给浏览器一个 session-id。浏览器下次请求,将在请求中的 cookie 中携带 session-id,服务器接收到这个 id,并且能查找到相关用户的信息,则认证成功。

但是这种模式越来越不适合当代多服务器的 web 模式。因为这相当于要求每个服务器都要存储一个用户信息表。所以,需要一种新的认证方式。

针对上述问题,一种解决方案是,不在服务器单独建用户信息表,而是将用户信息(token)存起来发给浏览器,浏览器下次来直接带完整的用户信息。然后,因为服务器这时候已经不存储任何用户信息,且所有信息完全来自用户,为了防止用户携带假信息进行欺诈,在发给浏览器的信息中加入签名进行防伪。

至于 JWT 的存储方式,可以是 cookie,也可以是 localstorage 等。

实现方式

上图是 jwt 的结构。三段式,分别是头部、负荷和签名。头部有两个字段:生成签名用到的加密算法和 token 类型。负荷中的 iat 指的是 issue at,签发时间。签名框里的意思是,头部加负荷,再加上密钥,一同经过 sha256 算法,生成了签名。

chatgpt 的解释:

JWT 的验证签名原理如下:

服务端在接收到 JWT 后,会对头部和载荷进行验证,验证包括以下几个步骤:

a. 解析出 JWT 中的头部和载荷,并检查头部中的算法是否为可信的算法。

b. 对头部和载荷使用相同的算法和密钥进行签名,得到签名结果。

c. 将服务端得到的签名结果和 JWT 中附带的签名结果进行比较,如果两个签名结果相同,则认为 JWT 是有效的,否则认为 JWT 是无效的。

通过以上验证过程,JWT 的签名可以保证数据的完整性和真实性,从而确保 JWT 的安全性。值得注意的是,JWT 并不加密,只是通过签名来验证数据的真实性和完整性,因此在使用 JWT 时,应该注意保护 JWT 的传输过程,避免 JWT 被篡改或窃取。

其他

  • 在软件售卖场景中,有把 jwt 作为许可证来使用的。即软件使用过程中不断去读取 jwt token 文件,如果验证出过期,则软件无法使用。与一般的登录场景类似的是,许可证也是由服务端(软件卖方)签发,每次使用都要检查 token。

我在2023年回顾python3基础

· 阅读需 5 分钟
Jason Lee
The owner of this blog site

到底什么是数字、元组、字符串的不可变性?

不可变性,指的是不能改变原有位置上的元素;不包括在空位置上创建元素。所以,元组都可以连接其他同类元素使得自己变得更长,而不能改变原来的元素。

数字、字符串的不可变性:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变 a 的值,相当于新生成了 a。字符串类似。

生成器和迭代器

生成器和迭代器都可以提高性能和内存效率,因为它们可以一次生成一个元素,而不必在内存中存储整个数据集。通过理解生成器和迭代器的工作原理,我们可以更有效地处理大型数据集。

假如有一个场景,我们深度学习训练时读取数据,由于训练图片很多,不可能一次性读进内存,所以采用生成器或迭代器的方式,一次读取几张图像。

生成器和迭代器实现的事情都是类似的,即一次只输出少量的数据,只是他们的创建方式有差异。

迭代器

迭代器是一种对象,它可以用于迭代序列中的元素。迭代器具有一个next()方法,该方法返回序列中的下一个元素,并在没有更多元素时引发 StopIteration 异常。

创建迭代器的方法:一个从现有序列中直接创造:iter(seq),另一种需要创建一个类(由类实例化出对象),类带有__iter____next__方法:

class FibonacciIterator:
def __init__(self):
self.a = 0
self.b = 1

def __iter__(self):
return self

def __next__(self):
result = self.b
self.a, self.b = self.b, self.a + self.b
return result

fib_iter = FibonacciIterator()
for i in range(10):
print(next(fib_iter))
## 另外一种调用方式
for value in fib_iter:
print(value)

生成器

生成器是一种特殊的函数,它使用 yield 关键字来产生一个值,并暂停函数的执行。每次调用生成器函数时,它都会从上一次停止的位置继续执行,直到遇到 yield 语句。生成器是一种简单而强大的工具,可以在处理大型数据集时提高性能和内存效率。

def fibonacci():
a, b = 0, 1
while True:
yield b
a, b = b, a + b

f = fibonacci()
for i in range(10):
print(next(f))
## 也可以:
for value in f:
print(value)

可以看出,生成器在这个例子中会简短一些。同时可以看出,调用的时候,生成器和迭代器的两种方式都一样,分别是固定次数(可能会超出边界而引发 StopIteration 异常,以及 forin 循环。创建时的不同是:生成器是一个函数,而迭代器是一个对象。

不定长参数

不定长参数就是那种在定义函数或者方法时,括号里的参数带一个或者两个星星的。一个星星被放入元组,实参不带参数关键字;两个星星被放入字典,实参带关键字,关键字被当作字典的键名。

def printinfo( arg1, *vartuple, **vardict ):
"打印任何传入的参数"
print ("输出: ")
print (arg1)
print(vartuple)
print(vardict)

printinfo( 70, 6, a=60, b=50)

out:

输出:
70
(6,)
{'a': 60, 'b': 50}

异常处理

raise 抛出异常,程序把异常打印出来(抛给用户),并停止后续代码的运行。如果没有 raise,程序将继续运行。

flex/grid布局在tailwindcss中的使用

· 阅读需 4 分钟
Jason Lee
The owner of this blog site

前言

tailwindcss 使得不用离开 html 界面就可以完成 css coding。常见的样式多写几次自然就记住了,如果不会的,还可以查文档,但是遇到布局相关的 flex 和 grid,需要一定的知识。

flex

菜鸟教程的基础上,做一点总结。

基本组成,flex container 和 flex item。他们可以分别设置属性。

容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做 main start,结束位置叫做 main end;交叉轴的开始位置叫做 cross start,结束位置叫做 cross end。项目默认沿主轴排列。单个项目占据的主轴空间叫做 main size,占据的交叉轴空间叫做 cross size。

所以想到 flex 布局,应该在脑海中想起的画面是:一个容器内有两条轴,一条横轴从左到右,一条竖轴从上到下。项目也是按上述顺序排列。

flex 容器的属性

  • flex-direction:决定主轴是哪一条及其方向
  • flex-wrap:主轴空间不够时是否换行
  • flex-flow:flex-direction 和 flex-flow 一起设置
  • justify-content:主轴的对齐方式
  • align-items:交叉轴的对齐方式
  • align-content:align-content 属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用

这里对 align-content 做出一点说明。所谓的多根轴线,是指在元素较多,形成换行后,每一行元素所在的轴,而不是指主轴和交叉轴:

如图,每一根红线都是一个轴。也就是说,align-content 定义了多行元素的整体对齐方式。作为对比,align-items 的对象是当行元素:

换句话说,多行元素用 align-content,单行元素用 align-items。

flex 项目属性

去除掉不常用的 order,有五个属性:

  • flex-grow:当有剩余空间时,项目放大比例
  • flex-shrink:空间不足时,缩小比例
  • flex-basis:定义了在分配多余空间之前,项目占据的主轴空间(main size)
  • flex:上述三个属性的快捷设置
  • align-self:对单个项目脱离 align-items 的对齐方式

关键是理解剩余空间。首先空间都是针对主轴来说的。

grid

grid 适合设置多行多列的,目前我遇到的情况是,设置好几个列,还有列之间的间隔,然后确定实际的一个元素占几列,就可以完成绝大部分任务了。

flex 和 grid 的 tailwindcss 写法

flex

<div class="flex 主轴对齐 交叉轴对齐 元素间距">
...
</div>

grid

<div class="max-w-7xl mx-auto(居中) grid grid-cols-n gap-n">
<div class="col-span-x">
...
</div>
<div class="col-span-n-x">
...
</div>
</div>

django-rest-framework使用入门

· 阅读需 2 分钟
Jason Lee
The owner of this blog site

浏览器行为

当我用 drf 提供的浏览器可视化界面,发现了一个奇怪的问题。同样是访问http://127.0.0.1:8000/snippets/,如果我之前填了一个表单并提交,那么按刷新按钮,会再提交一次,也就是会在数据库中添加一条和上一次一样的数据。只有在地址栏回车一下,才会真正回到列表中。这说明,如果页面中有表单,刷新按钮可能会执行 post 请求,而浏览器回车才能保证发出的 get 请求。

到底什么是序列化

python 的数据格式(字典、列表等),转化为可以传输的字符串(JSON、HTML 等),就是序列化。序列化类的实例并不是直接生成 json 数据,而是将模型实例转为一种 python 字典,然后由视图函数的JSONRenderer().render()函数转化为 json 字符串。

反过来,如果接受到了 json,按照下列方式:

import io

stream = io.BytesIO(content)
data = JSONParser().parse(stream)

serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>

和表单的处理很相似。

类视图的使用

· 阅读需 4 分钟
Jason Lee
The owner of this blog site

前言

对于类视图和函数视图,在我初学 django 的时,有过这样的迷思:自己定义的类视图似乎和函数视图没有太大的区别,无非就是判断请求方式的时候,CBV 用函数,FBV 用if...else...。而如果上了框架提供的通用类视图,代码风格似乎过于抽象,且不灵活,无法完全替代函数视图。今天,就以刚刚用函数视图完成的项目为例,尝试将所有函数视图转为类视图,看看过程中有什么新的感悟。

通用类视图里的方法

添加额外的上下文:

def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books
context["book_list"] = Book.objects.all()
return context

根据 url 内传来的 query 参数来动态查询

这项工作的关键部分是当基于类的视图被调用的时候,各种常用的东西被存储在 self 上,而且请求 (self.request) 根据 URLconf 抓取位置(self.args) 和基于名称 (self.kwargs) 的参数。

def get_queryset(self):
self.publisher = get_object_or_404(Publisher, name=self.kwargs["publisher"])
return Book.objects.filter(publisher=self.publisher)

执行额外的任务

class AuthorDetailView(DetailView):
queryset = Author.objects.all()

def get_object(self):
obj = super().get_object()
# Record the last accessed date
obj.last_accessed = timezone.now()
obj.save()
return obj

通用类视图到底为开发者默认做了哪些事

django 框架内置的通用类视图之所以能少些很多代码,很明显是它有一些默认行为。那么这里就记录一下各种常见内置视图的默认行为。

FormView

class ContactFormView(FormView):
template_name = "contact.html"
form_class = ContactForm
success_url = "/thanks/"

def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
form.send_email()
return super().form_valid(form)

表单视图的一般行为是判断请求方式,如果是 post,就检验字段有效性,且把错误返回给浏览器,成功则重定向;如果是 get 就返回待填写的表单。

所以,FormView 就是通过暴露一些简单的配置给开发者,比如 template_name,其他行为会自动完成。如果有特殊需求,重写 get 或 post 方法。form_valid 是执行表单验证之后的程序。

form_valid 函数是一定要重写的,这也说明 django 文档里一般都是最小实现形式,不能减少。

总结

  • 类视图完全可以替代函数视图。
  • 优先用内置的通用类视图,首先确认任务类型,如果是展示类,就考虑 ListView、DetailVIew;如果是修改类,就是 CreateView、UpdateView、DeleteView。
  • 修改类的通用视图,注意表单相关项。运用上面提到的三个函数,可以实现较复杂的需求。
  • 如果不方便,也可以不用内置视图,而采用根据请求方式划分方法,比如 get 请求。
  • 类视图在采用了内置类的写法后,代码会变少;但更重要的意义是,增加了代码重用的可能性。比如,另外一个 views.py 可以继承过来。

ModelForm详解

· 阅读需 3 分钟
Jason Lee
The owner of this blog site

前言

web 开发中,我目前遇到最复杂、又是最需要的需求,就是用户验证系统。除了一般的注册登录,还有权限、分组等。而从注册开始,需要理解 django 为什么设计了 modelForm 来处理用户的表单输入。

从 form 讲起

在注册的例子中,文档给出的方法是调用如下函数:

from django.contrib.auth.models import User
User.objects.create_user(username='', email='', password='')

但是正常的网站注册方式都是通过表单,怎么建立表单和 create_user 函数的联系呢?如果搜索文档里的表单,得到的答案是:

from django.http import HttpResponseRedirect
from django.shortcuts import render

from .forms import NameForm


def get_name(request):
# if this is a POST request we need to process the form data
if request.method == "POST":
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect("/thanks/")

# if a GET (or any other method) we'll create a blank form
else:
form = NameForm()

return render(request, "name.html", {"form": form})

我们知道了,对于提交上来的表单,先验证数据是否都有效,然后进一步的处理并重定向。可是什么是进一步的处理?

所以这大概就是文档有时候不能解决实际场景的问题,要结合别人写的代码,再回到文档中理解。

ModelForm

表单,一般都是和模型(也就是表中的字段)绑定的。因此,如果从头由forms.ModelForm继承来的表单类开始写,无疑是一种重复代码。因为与之绑定的模型字段在之前的 models.py 中定义过了。因此,django 创建了 ModelForm,直接在表单创建时和模型绑定,这样,提交上来的表单在验证通过后,保存表单就会同时保存提交上来的数据。

关于用户注册,需要知道的是,django 自带的 User 对象已经可以满足大部分场景的使用了,一般直接将其作为模型使用,而不用在 models.py 中创建。

通过这个例子,我的体会是:django 的写法太灵活了,往往一个问题有多种解决方案。可能最好的办法是摸索出一套自己习惯的写法。

tailwindcss配置

· 阅读需 3 分钟
Jason Lee
The owner of this blog site

前言

一个准备实现的 django 项目中用到了 tailwindcss。年初我曾接触过 tcss,但是由于文档对我来说有点看不懂,配置了很久还是失败了,所以搁置了下来。今天,经过良久的尝试,将成功的经验记录下来。

cdn

cdn 是最快尝试 tcss 的方法。它是通过在线引入 tcss 的 stylesheet 来实现的。

<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">

通过 CDN 引入的显著缺点是,每一次更改样式都要请求网络,如果网络不好或者断网,就会带来不好的开发体验。官网文档还给出了一些缺点:

Tailwind CLI

利用脚手架工具,可以生成一个定制的 css 文件,里面预定义了一大堆样式,比如 mt-5 等,正是我们在 html 文件中用到的 tcss 类名。不仅可以离线开发,而且可以添加自定义样式。操作方式如下:

首先,创建一个/src/tcss.css

@tailwind base;
@tailwind components;
@tailwind utilities;

<!-- your style -->
@layer components {
.btn-blue {
@apply py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75;
}
}

可以看出,tcss 有三个层次,base 层起到清除浏览器的初始默认样式,比如一些 margin 和 padding。components 负责将一些重复使用的样式组提取出来,单独取一个名字,比如上面代码的 btn-blue。最后是 utilities,就是最熟悉的 tcss 自带类,也可以自定义。

创建好上面的源 tcss 文件后,进行解析:

npx tailwindcss-cli build src/tailwind.css -o tailwind.css

上述命令竟然是我从别的教学视频里得到的,官方文档给出的命令只能得到 500 多行的解析文件,只包含了 base 层。不得不说,tcss 文档写的很烂。正常来说,上面命令运行后生成的文件含有 18 多万行代码。

在 index.html 中,header 部分引入生成的tailwind.css:

<link href="/tailwind.css" rel="stylesheet">

可以开始愉快地使用 tcss 了。

django关系字段

· 阅读需 4 分钟
Jason Lee
The owner of this blog site

前言

mysql 断断续续地学,一直对诸如外键的概念不甚清晰,今天做一个备忘。在这篇博文的基础上,加上了一些自己的理解。

建表

#_*_coding:utf-8_*_
from django.db import models

# Create your models here.

class Colors(models.Model):
colors=models.CharField(max_length=10) #蓝色
def __str__(self):
return self.colors

class Ball(models.Model):
color=models.OneToOneField("Colors") #与颜色表为一对一,颜色表为母表
description=models.CharField(max_length=10) #描述
def __str__(self):
return self.description

class Clothes(models.Model):
color=models.ForeignKey("Colors") #与颜色表为外键,颜色表为母表
description=models.CharField(max_length=10) #描述
def __str__(self):
return self.description

class Child(models.Model):
name=models.CharField(max_length=10) #姓名
favor=models.ManyToManyField('Colors') #与颜色表为多对多

一对一(models.OneToOneField)

子表从母表中选出一条数据一一对应,母表中选出来一条就少一条,子表不可以再选择母表中已被选择的那条数据。一般用于某张表的补充,比如用户基本信息是一张表,但并非每一个用户都需要有登录的权限,不需要记录用户名和密码,此时,合理的做法就是新建一张记录登录信息的表,与用户信息进行一对一的关联,可以方便的从子表查询母表信息或反向查询。

## 增加

color_obj=models.Colors.objects.create(colors="黑") #先在母表中创建颜色,并实例化给颜色表对象
models.Ball.objects.create(color=color_obj,description="黑球") #更新Ball表,color字段为颜色表对象,添加description字段

## 删除
models.Ball.objects.get(description="灰球").delete()

## 修改
color_obj=models.Colors.objects.get(colors="黑") #.get()等同于.filter().first()
color_obj.colors="灰"
color_obj.save()
models.Ball.objects.filter(description="黑球").update(color=color_obj,description="灰球")

## 查询
### 子表查询母表
models.Ball.objects.get(description="红球").color.colors

### 母表查询子表
models.Colors.objects.get(colors="红").ball.description

一对多(models.ForeignKey)

子表从母表中选出一条数据一一对应,但母表的这条数据还可以被其他子表数据选择。这里的一和多指的是自身的表的数据在对方表里出现的次数,一次为一,多次为多。比如每个员工归属于一个部门,那么就可以让员工表的部门字段与部门表进行一对多关联,可以查询到一个员工归属于哪个部门,也可反向查出某一部门有哪些员工。

## 查

color_obj=models.Colors.objects.get(colors="红")
color_obj.clothes_set.all()

models.Clothes.objects.filter(color__colors="红")

多对多(models.ManyToManyField)

比如有多个孩子,和多种颜色。每个孩子可以喜欢多种颜色,一种颜色可以被多个孩子喜欢,对于双向均是可以有多个选择。

## 查

#写法1:
child_obj=models.Child.objects.get(name="小明") #写法:子表对象.子表多对多字段.过滤条件(all()/filter())
print(child_obj.favor.all())
#写法2,反向从母表入手:
print(models.Colors.objects.filter(child__name="小明")) #母表对象.filter(子表表名小写__子表字段名="过滤条件")