feat(material): 列表序列化器与筛选扩展

- MaterialListSerializer 补全 A 组字段(规格/应用/优势/成本/星级/施工)
- 新增供应商扩展字段 factory_cooperation_mode/display/province/city
- MaterialSerializer 同步供应商扩展字段供详情使用
- 视图 queryset 增加 select_related('factory','brand')
- 新增筛选: stage / importance_level / factory / cooperation_mode /
  landing_project / cost_compare 区间 / score_level>=N / contact_person / handler
- material_category 改为 icontains 模糊匹配
- choices 接口追加 cooperation_mode

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-04-24 14:11:35 +08:00
parent 2b76bbc62a
commit 99e6df0a57
2 changed files with 83 additions and 7 deletions

View File

@ -20,6 +20,10 @@ class MaterialSerializer(serializers.ModelSerializer):
""" """
factory_name = serializers.CharField(source='factory.factory_name', read_only=True) factory_name = serializers.CharField(source='factory.factory_name', read_only=True)
factory_short_name = serializers.CharField(source='factory.short_name', read_only=True) factory_short_name = serializers.CharField(source='factory.short_name', read_only=True)
factory_cooperation_mode = serializers.CharField(source='factory.cooperation_mode', read_only=True, default=None)
factory_cooperation_mode_display = serializers.SerializerMethodField()
factory_province = serializers.CharField(source='factory.province', read_only=True, default=None)
factory_city = serializers.CharField(source='factory.city', read_only=True, default=None)
brand = serializers.PrimaryKeyRelatedField( brand = serializers.PrimaryKeyRelatedField(
queryset=Brand.objects.all(), allow_null=True, required=False queryset=Brand.objects.all(), allow_null=True, required=False
) )
@ -57,10 +61,15 @@ class MaterialSerializer(serializers.ModelSerializer):
'brochure_url', 'quality_level', 'durability_level', 'eco_level', 'brochure_url', 'quality_level', 'durability_level', 'eco_level',
'carbon_level', 'score_level', 'connection_method', 'construction_method', 'carbon_level', 'score_level', 'connection_method', 'construction_method',
'limit_condition', 'factory', 'factory_name', 'factory_short_name', 'limit_condition', 'factory', 'factory_name', 'factory_short_name',
'factory_cooperation_mode', 'factory_cooperation_mode_display',
'factory_province', 'factory_city',
'brand', 'brand_name', 'brand', 'brand_name',
'status', 'created_at', 'updated_at'] 'status', 'created_at', 'updated_at']
read_only_fields = ['id', 'created_at', 'updated_at'] read_only_fields = ['id', 'created_at', 'updated_at']
def get_factory_cooperation_mode_display(self, obj):
return obj.factory.get_cooperation_mode_display() if obj.factory and obj.factory.cooperation_mode else None
def get_brochure_url(self, obj): def get_brochure_url(self, obj):
if obj.brochure: if obj.brochure:
request = self.context.get('request') request = self.context.get('request')
@ -85,25 +94,44 @@ class MaterialSerializer(serializers.ModelSerializer):
class MaterialListSerializer(serializers.ModelSerializer): class MaterialListSerializer(serializers.ModelSerializer):
""" """
材料列表序列化器简化版 材料列表序列化器完整字段版供列表页按需展示列
""" """
factory_name = serializers.CharField(source='factory.factory_name', read_only=True) factory_name = serializers.CharField(source='factory.factory_name', read_only=True)
factory_short_name = serializers.CharField(source='factory.short_name', read_only=True) factory_short_name = serializers.CharField(source='factory.short_name', read_only=True)
factory_cooperation_mode = serializers.CharField(source='factory.cooperation_mode', read_only=True, default=None)
factory_cooperation_mode_display = serializers.SerializerMethodField()
factory_province = serializers.CharField(source='factory.province', read_only=True, default=None)
factory_city = serializers.CharField(source='factory.city', read_only=True, default=None)
brand_name = serializers.CharField(source='brand.name', read_only=True) brand_name = serializers.CharField(source='brand.name', read_only=True)
major_category_display = serializers.CharField(source='get_major_category_display', read_only=True) major_category_display = serializers.CharField(source='get_major_category_display', read_only=True)
status_display = serializers.CharField(source='get_status_display', read_only=True) status_display = serializers.CharField(source='get_status_display', read_only=True)
stage_display = serializers.CharField(source='get_stage_display', read_only=True) stage_display = serializers.CharField(source='get_stage_display', read_only=True)
importance_level_display = serializers.CharField(source='get_importance_level_display', read_only=True) importance_level_display = serializers.CharField(source='get_importance_level_display', read_only=True)
replace_type_display = serializers.CharField(source='get_replace_type_display', read_only=True)
class Meta: class Meta:
model = Material model = Material
fields = ['id', 'name', 'major_category', 'major_category_display', fields = ['id', 'name', 'major_category', 'major_category_display',
'material_category', 'material_subcategory', 'stage', 'stage_display', 'material_category', 'material_subcategory', 'stage', 'stage_display',
'importance_level', 'importance_level_display', 'landing_project', 'importance_level', 'importance_level_display', 'landing_project',
'contact_person', 'contact_phone', 'handler', 'remark', 'factory', 'contact_person', 'contact_phone', 'handler', 'remark',
'factory_name', 'factory_short_name', 'brand', 'brand_name', 'spec', 'standard',
'application_scene', 'application_desc',
'replace_type', 'replace_type_display',
'advantage', 'advantage_desc',
'connection_method', 'construction_method', 'limit_condition',
'cost_compare', 'cost_desc', 'cases',
'quality_level', 'durability_level', 'eco_level',
'carbon_level', 'score_level',
'factory', 'factory_name', 'factory_short_name',
'factory_cooperation_mode', 'factory_cooperation_mode_display',
'factory_province', 'factory_city',
'brand', 'brand_name',
'status', 'status_display'] 'status', 'status_display']
def get_factory_cooperation_mode_display(self, obj):
return obj.factory.get_cooperation_mode_display() if obj.factory and obj.factory.cooperation_mode else None
class MaterialCategorySerializer(serializers.ModelSerializer): class MaterialCategorySerializer(serializers.ModelSerializer):
""" """

View File

@ -17,6 +17,7 @@ from rest_framework.parsers import MultiPartParser
from .models import Material, MaterialCategory, MaterialSubcategory from .models import Material, MaterialCategory, MaterialSubcategory
from .serializers import MaterialSerializer, MaterialListSerializer, MaterialCategorySerializer, MaterialSubcategorySerializer from .serializers import MaterialSerializer, MaterialListSerializer, MaterialCategorySerializer, MaterialSubcategorySerializer
from .importers import import_materials_plan_excel from .importers import import_materials_plan_excel
from apps.factory.models import COOPERATION_MODE_CHOICES
def _join_choice_values(values, choices): def _join_choice_values(values, choices):
@ -149,7 +150,7 @@ class MaterialViewSet(ModelViewSet):
""" """
根据用户角色过滤材料 根据用户角色过滤材料
""" """
queryset = Material.objects.all().order_by('-created_at', '-id') queryset = Material.objects.select_related('factory', 'brand').order_by('-created_at', '-id')
# 普通用户只能看到自己工厂的材料 # 普通用户只能看到自己工厂的材料
if self.request.user.role != 'admin': if self.request.user.role != 'admin':
@ -180,10 +181,10 @@ class MaterialViewSet(ModelViewSet):
if major_category: if major_category:
queryset = queryset.filter(major_category=major_category) queryset = queryset.filter(major_category=major_category)
# 支持按材料分类过滤 # 支持按材料分类模糊过滤
material_category = self.request.query_params.get('material_category') material_category = self.request.query_params.get('material_category')
if material_category: if material_category:
queryset = queryset.filter(material_category=material_category) queryset = queryset.filter(material_category__icontains=material_category)
# 支持按材料子类过滤 # 支持按材料子类过滤
material_subcategory = self.request.query_params.get('material_subcategory') material_subcategory = self.request.query_params.get('material_subcategory')
@ -200,6 +201,52 @@ class MaterialViewSet(ModelViewSet):
if advantage: if advantage:
queryset = queryset.filter(advantage__contains=[advantage]) queryset = queryset.filter(advantage__contains=[advantage])
# 阶段
stage = self.request.query_params.get('stage')
if stage:
queryset = queryset.filter(stage=stage)
# 重要等级
importance_level = self.request.query_params.get('importance_level')
if importance_level:
queryset = queryset.filter(importance_level=importance_level)
# 供应商(显式 id 过滤)
factory = self.request.query_params.get('factory')
if factory:
queryset = queryset.filter(factory_id=factory)
# 合作模式
cooperation_mode = self.request.query_params.get('factory__cooperation_mode')
if cooperation_mode:
queryset = queryset.filter(factory__cooperation_mode=cooperation_mode)
# 落地项目(模糊)
landing_project = self.request.query_params.get('landing_project')
if landing_project:
queryset = queryset.filter(landing_project__icontains=landing_project)
# 成本比较区间
cost_gte = self.request.query_params.get('cost_compare__gte')
if cost_gte not in (None, ''):
queryset = queryset.filter(cost_compare__gte=cost_gte)
cost_lte = self.request.query_params.get('cost_compare__lte')
if cost_lte not in (None, ''):
queryset = queryset.filter(cost_compare__lte=cost_lte)
# 综合评分下限
score_gte = self.request.query_params.get('score_level__gte')
if score_gte not in (None, ''):
queryset = queryset.filter(score_level__gte=score_gte)
# 对接人 / 经办人
contact_person = self.request.query_params.get('contact_person')
if contact_person:
queryset = queryset.filter(contact_person__icontains=contact_person)
handler = self.request.query_params.get('handler')
if handler:
queryset = queryset.filter(handler__icontains=handler)
return queryset return queryset
def get_serializer_class(self): def get_serializer_class(self):
@ -333,6 +380,7 @@ class MaterialViewSet(ModelViewSet):
'application_scene': Material.APPLICATION_SCENE_CHOICES, 'application_scene': Material.APPLICATION_SCENE_CHOICES,
'star_level': Material.STAR_LEVEL_CHOICES, 'star_level': Material.STAR_LEVEL_CHOICES,
'status': Material.STATUS_CHOICES, 'status': Material.STATUS_CHOICES,
'cooperation_mode': COOPERATION_MODE_CHOICES,
}) })
@action(detail=False, methods=['get'], url_path='template') @action(detail=False, methods=['get'], url_path='template')