有了产品信息之后,下面来写库存信息部分
每一个库存的入库和出库都需要对应一个产品,所以产品信息是它的外键
库存信息
创建App
代码语言:javascript复制python manage.py startapp warehouse
将warehouse
文件夹移动到backend/apps/warehouse
注册App
在backend/LightSeeking/settings.py
的INSTALLED_APPS
中添加
INSTALLED_APPS = [
...
'users.apps.UsersConfig',
'product.apps.ProductConfig',
'warehouse.apps.WarehouseConfig'
]
表结构设计
库存信息包含了
- 产品信息
- 单价
- 生产日期
- 保质期天数
- 数量
- 库存状态
- 订单号
- 供应商
- 备注
产品信息使用外键的方式,并且两个键之间不互相影响
代码语言:javascript复制product = models.ForeignKey(Product, verbose_name="产品信息", on_delete=models.DO_NOTHING, help_text="产品信息")
其他都是普通的字段
代码语言:javascript复制from django.db import models
from product.models import Product
from utils.models import BaseModel
class Warehouse(BaseModel):
STOCKSTATUS = (
(, "入库"),
(, "出库"),
(, "报废"),
)
product = models.ForeignKey(Product, verbose_name="产品信息", on_delete=models.DO_NOTHING, help_text="产品信息")
price = models.DecimalField('成本单价', max_digits=, decimal_places=, default=, help_text='成本单价')
date_of_manufacture = models.DateTimeField("生产日期", help_text="生产日期")
quality_guarantee = models.IntegerField("保质期天数", help_text="保质期天数")
num = models.IntegerField("数量", default=, help_text="数量")
stock_status = models.CharField("库存状态", max_length=, choices=STOCKSTATUS, default=,
help_text='库存状态:1-入库,2-出库,3-报废')
order_id = models.CharField("订单号", max_length=, null=True, blank=True, default='', help_text='订单号')
supplier = models.CharField("供应商", max_length=, null=True, blank=True, default='', help_text='供应商')
desc = models.CharField('备注', max_length=, null=True, blank=True, default='', help_text='备注')
class Meta:
db_table = 'tb_warehouse'
verbose_name = '库存信息'
verbose_name_plural = verbose_name
def __str__(self):
return self.id
数据迁移
代码语言:javascript复制python manage.py makemigrations
python manage.py migrate
序列化器
在库存序列化器中需要指定产品来进行新增,查看的时候需要返回全部的产品信息
所以先新建一个包含全部产品的序列化器
backend/apps/product/serializers.py
class ProductAllModelSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
添加产品的时候使用的是产品的id,这时候就需要判断一下这个产品是否存在了
编写whether_existed_product_id
方法来进行判断
def whether_existed_product_id(value):
"""
检查产品id是否存在
:param value:
:return:
"""
if value != :
if not isinstance(value, int):
raise serializers.ValidationError('所选产品信息有误!')
elif not Product.objects.filter(is_delete=False, id=value).exists():
raise serializers.ValidationError('所选产品信息不存在!')
所以库存的序列化器基础版本为
代码语言:javascript复制class WarehouseModelSerializer(serializers.ModelSerializer):
product_id = serializers.IntegerField(
write_only=True,
help_text='产品信息ID',
validators=[validates.whether_existed_product_id]
)
product = ProductAllModelSerializer(read_only=True, many=False)
class Meta:
model = Warehouse
exclude = ('is_delete',)
extra_kwargs = {
'c_time': {
'read_only': True
},
}
重点
下面是序列化器中的一个神奇的操作,也就是它可以逐行处理返回的数据,根据逻辑给每行添加需要的数据
在这个序列化器中需要顺便计算一下产品的保质期相关数据
新加两个字段剩余天数
、保质期日期
剩余天数 = 保质期天数 - (当前日期 - 生产日期)
保质期日期 = 生产日期 剩余天数
代码语言:javascript复制remaining = serializers.SerializerMethodField(label="剩余天数", read_only=True, help_text="剩余天数")
remaining_day = serializers.SerializerMethodField(label="保质期日期", read_only=True, help_text="保质期日期")
使用serializers.SerializerMethodField
可以在WarehouseModelSerializer
类中编写get_xxx
方法来计算这个值
def get_remaining(self, warehouse):
"""
剩余天数 = 保质期天数 - (当前日期 - 生产日期)
:param warehouse:
:return:
"""
return warehouse.quality_guarantee - (
warehouse.date_of_manufacture - datetime.datetime.now(tz=pytz.UTC)
).days
def get_remaining_day(self, warehouse):
"""
保质期日期 = 生产日期 剩余天数
"""
return warehouse.date_of_manufacture datetime.timedelta(days=warehouse.quality_guarantee)
同理再根据需求添加几个字段
- 总库存
- 出库
- 入库
- 已经出库时间
- 已经入库时间
total = serializers.SerializerMethodField(label="总库存", read_only=True, help_text="一个产品的总库存")
warehouse1 = serializers.SerializerMethodField(label="出库", read_only=True, help_text="一个产品的总出库")
warehouse2 = serializers.SerializerMethodField(label="入库", read_only=True, help_text="一个产品的总入库")
warehouse1_time = serializers.SerializerMethodField(label="已经出库时间", read_only=True, help_text="出库距离当前的时间")
warehouse2_time = serializers.SerializerMethodField(label="已经入库时间", read_only=True, help_text="入库距离当前的时间")
实现字段的计算
代码语言:javascript复制def get_total(self, warehouse):
total = self.get_warehouse1(warehouse) - self.get_warehouse2(warehouse)
return total
def get_warehouse1(self, warehouse):
product_id = warehouse.product_id
warehouse_info = Warehouse.objects.filter(stock_status=).values("product").annotate(total=Sum("num")).all()
total = warehouse_info.filter(product_id=product_id) and
warehouse_info.filter(product_id=product_id).get()["total"] or
return total
def get_warehouse2(self, warehouse):
product_id = warehouse.product_id
warehouse_info = Warehouse.objects.filter(stock_status=).values("product").annotate(total=Sum("num")).all()
total = warehouse_info.filter(product_id=product_id) and
warehouse_info.filter(product_id=product_id).get()["total"] or
return total
def get_warehouse1_time(self, warehouse):
warehouse2_time = warehouse.c_time - datetime.datetime.now(datetime.timezone(datetime.timedelta(hours= )))
return warehouse2_time.days
def get_warehouse2_time(self, warehouse):
warehouse2_time = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours= ))) - warehouse.c_time
return warehouse2_time.days
视图
代码语言:javascript复制from rest_framework.viewsets import ModelViewSet
from utils.pagination import TenItemPerPagePagination
from warehouse.models import Warehouse
from warehouse.serializers import WarehouseModelSerializer
class WarehouseViewSet(ModelViewSet):
queryset = Warehouse.objects.filter(is_delete=False).order_by("-c_time")
serializer_class = WarehouseModelSerializer
pagination_class = TenItemPerPagePagination
ordering_fields = ['c_time']
def perform_destroy(self, instance):
instance.is_delete = True
instance.save() # 逻辑删除
路由
backend/apps/warehouse/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register("warehouse", views.WarehouseViewSet)
urlpatterns = [
path('', include(router.urls))
]
backend/LightSeeking/urls.py
urlpatterns = [
...
path('', include('warehouse.urls')),
]
代码
本次代码修改见:
https://gitee.com/zx660644/light-seeking/commit/2a5f9e4a26ff56e7cef5e6d4bdaf0adc63bb37b0
测试
先添加一个产品信息
访问http://127.0.0.1:8000/product/
填入产品信息后点击POST
再访问http://127.0.0.1:8000/warehouse/
添加一个库存信息
查看结果
代码语言:javascript复制{
"id": ,
"product": {
"id": ,
"c_time": "2022-09-02T12:14:32.356282 08:00",
"u_time": "2022-09-02T12:14:32.356417 08:00",
"is_delete": false,
"product_id": "0001",
"category": "饮料",
"brand": "可口可乐",
"name": "可乐",
"price": "3.00",
"sample_png": "",
"desc": "无"
},
"remaining": ,
"remaining_day": "2023-09-01T12:15:00 08:00",
"total": ,
"warehouse1": ,
"warehouse2": ,
"warehouse1_time": -1,
"warehouse2_time": ,
"c_time": "2022-09-02T12:15:46.366239 08:00",
"u_time": "2022-09-02T12:15:46.366352 08:00",
"price": "2.50",
"date_of_manufacture": "2022-09-01T12:15:00 08:00",
"quality_guarantee": ,
"num": ,
"stock_status": ,
"order_id": "",
"supplier": "测试供应商",
"desc": ""
}
其中product
字段包含了我们入库的产品信息
另外之前序列化器中添加的几个字段也出现了
bug修复
不过从这里看出来,之前写了个bug,之前把日期计算写反了
代码语言:javascript复制def get_remaining(self, warehouse):
"""
剩余天数 = 保质期天数 - (当前日期 - 生产日期)
:param warehouse:
:return:
"""
return warehouse.quality_guarantee - (
datetime.datetime.now(tz=pytz.UTC) - warehouse.date_of_manufacture
).days
再次刷新后查看 remaining
变为了364
{
"data": {
"page": ,
"pageSize": ,
"rows": [
{
"id": ,
"product": {
"id": ,
"c_time": "2022-09-02T12:14:32.356282 08:00",
"u_time": "2022-09-02T12:14:32.356417 08:00",
"is_delete": false,
"product_id": "0001",
"category": "饮料",
"brand": "可口可乐",
"name": "可乐",
"price": "3.00",
"sample_png": "",
"desc": "无"
},
"remaining": ,
"remaining_day": "2023-09-01T04:15:00Z",
"total": ,
"warehouse1": ,
"warehouse2": ,
"warehouse1_time": -1,
"warehouse2_time": ,
"c_time": "2022-09-02T12:15:46.366239 08:00",
"u_time": "2022-09-02T12:15:46.366352 08:00",
"price": "2.50",
"date_of_manufacture": "2022-09-01T12:15:00 08:00",
"quality_guarantee": ,
"num": ,
"stock_status": ,
"order_id": "",
"supplier": "测试供应商",
"desc": ""
}
],
"total":
},
"message": "",
"code": ,
"next": null,
"previous": null
}