# P07 在视图集中构建数据表

在视图集中构建数据表时，我们可以使用外键（ForeignKey）来建立表之间的关联关系。外键是一种约束，用于确保一个模型中的一个或多个字段的值必须符合另一个模型中对应字段的值。

## **外键的概念**
外键是一种用于建立表之间关联关系的约束，通常指的是一个模型中的一个或多个字段的值必须符合另一个模型中对应字段的值。
在 Django 中，定义外键字段非常简单。只需在模型中的字段中使用 ForeignKey 类，并指定关联的模型作为参数。例如：

```python
class Personnel(models.Model):
    id = models.AutoField(verbose_name="工号", primary_key=True, db_index=True)
    name = models.CharField(verbose_name="姓名", max_length=16, null=False)
    age = models.PositiveIntegerField(verbose_name="年龄", default=1)

class Contact(models.Model):
    con_id = models.AutoField(verbose_name="联系表序号", primary_key=True)
    phone = models.CharField(verbose_name="手机号码", max_length=16, null=False)
    address = models.TextField(verbose_name="住址", default='宿舍')
    work_id = models.ForeignKey(to="Personnel", on_delete= models.SET_NULL, null=True)
```
那么在以上代码中有两个模型，分别是 Personnel 模型和 Contact 模型，其中 Personnel 模型中设定了 id 这个字段为主键，Contact 模型设定了 con_id 这个字段为主键，work_id 为外键，让我们细看 work_id 这个字段的定义：

- to：需要传递被外键连接的主表模型作为值，如果连接时主表模型还没定义，建议使用主表模型类名加上""，这样就不会报错。
- on_delete: 需要传递当主表中的一条数据删除时，从表中与这条数据相关联的数据要执行怎样的动作。
- to_field：允许你指定要引用的目标模型中的特定字段作为外键
- to 和 on_delete 这两个参数不可避免，但 to_field 参数不指定，一般会默认为连接的主表的主键。

**增加数据** 
* 苹果 - 水果
* 桃子 - 水果
* 猴子 - 动物
* 大象 - 动物
  
构建根据分类获取某个分类下的产品 API

1.首先，在 models.py 文件中定义模型时，我们可以使用外键来建立关联关系。例如，假设我们有一个  GoodsCategory 模型，我们可以在另一个模型中使用外键来引用 GoodsCategory 模型。
  
  **models.py**
```python
from django.db import models

class Goods(models.Model):
    name = models.CharField(max_length=100)
    category = models.ForeignKey(GoodsCategory, on_delete=models.SET_NULL, related_name='goods_set', null=True, verbose_name='产品分类')
    # 其他字段...

```
在上述代码中，我们在 Goods 模型中定义了一个 category 字段，它是一个外键，引用了GoodsCategory 模型。on_delete 参数用于设置当外键对应的数据被删除时的反应，这里我们设置为 models.SET_NULL，表示当 GoodsCategory 对象被删除时，将该外键的值设为 NULL。null=True 表示该外键可以为空。

2.接下来，我们可以在视图中编写代码来构建根据分类获取某个分类下的产品的 API。
  
  **views.py**
```python
from django.shortcuts import render
from rest_framework.response import Response
from .models import *
from rest_framework.decorators import api_view
from django.shortcuts import get_object_or_404
# Create your views here.
# GET
# POST

# 函数式编程
@api_view(['POST', 'GET'])
def InsertGoodsCategory(request):
    category_name = request.data.get('分类名字')
    
    # 获取分类对象或创建新的分类对象
    category, created = GoodsCategory.objects.get_or_create(name=category_name)
    
    # 判断是否已存在分类
    if not created:
        return Response({"status": "已存在", "goods_category": category_name}, status=200)
    else:
        return Response({"message": f"Successfully inserted category '{category_name}'."})

@api_view(['POST','GET'])
def FilterGoodsCategory(request):
    data = request.data.get('分类名字')
    goods = GoodsCategory.objects.filter(name=data)
    if goods.exists():
        return Response({"status": "已存在", "goods_category": data}, status=200)
    else:
        return Response({"status": "不存在" ,"goods_category": data}, status=404)
```
在上述代码中，我们使用 api_view 装饰器来定义一个接受 GET 和 POST 请求的视图函数。通过传入 category_id 参数，我们可以根据分类的 ID 获取该分类下的产品。首先，使用 get_object_or_404 函数来获取指定ID的分类对象，然后使用filter函数来获取该分类下的产品。最后可以对获取到的产品进行处理，并返回相应的响应。

3.将上述视图函数添加到 urls.py 文件中的路由配置中，以便能够访问该 API。

**urls.py**

```python
# 放入路由
from django.contrib import admin
from django.urls import path
from apps.erp_test.views import *

urlpatterns = [
    path('admin/', admin.site.urls),
    path('filtergoodscategory/', FilterGoodsCategory),
    path('insertgoodscategory/', InsertGoodsCategory),
  ]

```

4.使用 postman 针对 **insertGoodsCategory/** 和 **filtergoodscategory/** API 接口进行测试，测试结果如下：  

![image](assets/postman_test01.png)

![image](assets/postman_test02.png)

## 针对这两个 API 接口的测试步骤：
1. 打开 Postman 应用程序，并确保已经创建了一个新的请求。
2. 对于 insertGoodsCategory/ 接口，选择 HTTP 方法为 POST，并将请求 URL 设置为对应的 API 端点 URL（http://127.0.0.1:8000/insertgoodscategory/）
3. 在请求体中，设置要发送的数据。可以选择 "raw" 选项，并选择 JSON 格式，然后输入要发送的数据。
4. 点击 "Send" 按钮发送请求，并等待响应。Postman 将显示请求的状态码和响应内容。
5. 对于 filtergoodscategory/ 接口，选择 HTTP 方法为 POST，并将请求 URL 设置为对应的 API 端点 URL（http://127.0.0.1:8000/filtergoodscategory/）。
6. 在请求体中，设置要发送的数据。可以选择 "raw" 选项，并选择 JSON 格式，然后输入要发送的数据。
7. 点击 "Send" 按钮发送请求，并等待响应。Postman 将显示请求的状态码和响应内容。
8. 通过以上步骤，可以使用 Postman 针对 insertGoodsCategory/ 和 filtergoodscategory/ API 接口进行测试，并查看请求和响应的结果。

### 课后习题：
[课后练习题](/contents/P13.md#5-创建班级和学生表)
