641 lines
26 KiB
Python
641 lines
26 KiB
Python
from io import BytesIO
|
||
from pathlib import Path
|
||
|
||
from django.http import FileResponse, Http404
|
||
from django.utils import timezone
|
||
from openpyxl import Workbook
|
||
from openpyxl.worksheet.datavalidation import DataValidation
|
||
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
|
||
from openpyxl.utils import get_column_letter
|
||
from django.db.models import Count
|
||
from rest_framework import generics, status
|
||
from rest_framework.decorators import api_view, action
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework.response import Response
|
||
from rest_framework.viewsets import ModelViewSet
|
||
from rest_framework.exceptions import PermissionDenied
|
||
from rest_framework.parsers import MultiPartParser
|
||
from django.core.cache import cache
|
||
from .models import Material, MaterialCategory, MaterialSubcategory
|
||
from .serializers import MaterialSerializer, MaterialListSerializer, MaterialCategorySerializer, MaterialSubcategorySerializer
|
||
from .importers import import_materials_plan_excel
|
||
from apps.factory.models import COOPERATION_MODE_CHOICES
|
||
|
||
|
||
CATEGORY_TREE_CACHE_KEY = 'material:category_tree:approved'
|
||
|
||
|
||
def invalidate_category_tree_cache():
|
||
cache.delete(CATEGORY_TREE_CACHE_KEY)
|
||
|
||
|
||
def _join_choice_values(values, choices):
|
||
if not values:
|
||
return ""
|
||
mapping = dict(choices)
|
||
if isinstance(values, str):
|
||
values = [values]
|
||
return "、".join(mapping.get(value, value) for value in values)
|
||
|
||
|
||
def _apply_template_header_style(worksheet, row_index, total_columns):
|
||
header_font = Font(name='SimSun', size=18, bold=True, color='FFFFFFFF')
|
||
header_fill = PatternFill(fill_type='solid', fgColor='FFC39E61')
|
||
header_alignment = Alignment(horizontal='center', vertical='center')
|
||
header_side = Side(style='thin', color='FF000000')
|
||
header_border = Border(left=header_side, right=header_side, top=header_side)
|
||
|
||
worksheet.row_dimensions[row_index].height = 23.25
|
||
for column_index in range(1, total_columns + 1):
|
||
cell = worksheet.cell(row=row_index, column=column_index)
|
||
cell.font = header_font
|
||
cell.fill = header_fill
|
||
cell.alignment = header_alignment
|
||
cell.border = header_border
|
||
|
||
|
||
def _apply_template_title_style(worksheet, row_index, total_columns, title):
|
||
title_font = Font(name='SimSun', size=18, bold=True, color='FFFFFFFF')
|
||
title_fill = PatternFill(fill_type='solid', fgColor='FF039843')
|
||
title_alignment = Alignment(horizontal='center', vertical='center')
|
||
|
||
worksheet.merge_cells(start_row=row_index, start_column=1, end_row=row_index, end_column=total_columns)
|
||
cell = worksheet.cell(row=row_index, column=1)
|
||
cell.value = title
|
||
cell.font = title_font
|
||
cell.fill = title_fill
|
||
cell.alignment = title_alignment
|
||
worksheet.row_dimensions[row_index].height = 23.25
|
||
|
||
|
||
def _text_display_width(text):
|
||
width = 0
|
||
for char in str(text or ""):
|
||
width += 2 if ord(char) > 127 else 1
|
||
return width
|
||
|
||
|
||
def _apply_header_column_widths(worksheet, headers):
|
||
for column_index, header in enumerate(headers, start=1):
|
||
display_width = _text_display_width(header)
|
||
worksheet.column_dimensions[get_column_letter(column_index)].width = max(16, display_width + 4)
|
||
|
||
|
||
def _apply_export_dropdowns(worksheet, headers, data_row_count):
|
||
dropdown_configs = [
|
||
{
|
||
"headers": {"质量等级", "耐久等级", "环保等级", "低碳等级", "总评分"},
|
||
"options": ["1星", "2星", "3星"],
|
||
"prompt": "请选择星级",
|
||
"error": "仅支持 1星、2星、3星",
|
||
},
|
||
{
|
||
"headers": {"材料子类"},
|
||
"options": [
|
||
"节能材料",
|
||
"涂料",
|
||
"界面剂",
|
||
"地坪",
|
||
"水磨石",
|
||
"S垫疏水垫",
|
||
"商用地垫",
|
||
"方块毯",
|
||
"保温板(无机材料)",
|
||
"预制墙板(无机材料)",
|
||
],
|
||
"prompt": "请选择材料子类",
|
||
"error": "仅支持下拉列表中的材料子类",
|
||
},
|
||
{
|
||
"headers": {"替代材料类型"},
|
||
"options": ["平替", "新研发"],
|
||
"prompt": "请选择替代材料类型",
|
||
"error": "仅支持 平替、新研发",
|
||
},
|
||
{
|
||
"headers": {"竞争优势"},
|
||
"options": ["品质", "成本"],
|
||
"prompt": "可从下拉选择;如需多填,请手动输入并用、分隔",
|
||
"error": "仅支持 品质、成本;多值请用、分隔",
|
||
},
|
||
{
|
||
"headers": {"应用场景"},
|
||
"options": ["府系", "境系", "城系", "住系", "保障房"],
|
||
"prompt": "Excel 单元格下拉不支持原生多选;如需多填,请手动输入并用、分隔",
|
||
"error": "仅支持 府系、境系、城系、住系、保障房;多值请用、分隔",
|
||
},
|
||
]
|
||
|
||
start_row = 3
|
||
end_row = max(start_row, data_row_count + 202)
|
||
source_column_index = len(headers) + 2
|
||
|
||
for config in dropdown_configs:
|
||
source_column_letter = get_column_letter(source_column_index)
|
||
for row_index, option in enumerate(config["options"], start=1):
|
||
worksheet.cell(row=row_index, column=source_column_index, value=option)
|
||
worksheet.column_dimensions[source_column_letter].hidden = True
|
||
option_formula = f"=${source_column_letter}$1:${source_column_letter}${len(config['options'])}"
|
||
|
||
for column_index, header in enumerate(headers, start=1):
|
||
if header not in config["headers"]:
|
||
continue
|
||
validation = DataValidation(type="list", formula1=option_formula, allow_blank=True)
|
||
validation.prompt = config["prompt"]
|
||
validation.error = config["error"]
|
||
worksheet.add_data_validation(validation)
|
||
validation.add(f"{get_column_letter(column_index)}{start_row}:{get_column_letter(column_index)}{end_row}")
|
||
|
||
source_column_index += 1
|
||
|
||
|
||
class MaterialViewSet(ModelViewSet):
|
||
"""
|
||
材料视图集
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def get_queryset(self):
|
||
"""
|
||
根据用户角色过滤材料
|
||
"""
|
||
queryset = Material.objects.select_related('factory', 'brand').order_by('-created_at', '-id')
|
||
|
||
# 普通用户只能看到自己工厂的材料
|
||
if self.request.user.role != 'admin':
|
||
queryset = queryset.filter(factory=self.request.user.factory)
|
||
|
||
# 支持按状态过滤
|
||
status_filter = self.request.query_params.get('status')
|
||
if status_filter:
|
||
queryset = queryset.filter(status=status_filter)
|
||
|
||
# 支持按名称搜索
|
||
name = self.request.query_params.get('name')
|
||
if name:
|
||
queryset = queryset.filter(name__icontains=name)
|
||
|
||
# 支持按工厂过滤
|
||
factory_id = self.request.query_params.get('factory_id')
|
||
if factory_id:
|
||
queryset = queryset.filter(factory_id=factory_id)
|
||
|
||
# 支持按品牌过滤
|
||
brand_id = self.request.query_params.get('brand')
|
||
if brand_id:
|
||
queryset = queryset.filter(brand_id=brand_id)
|
||
|
||
# 支持按专业类别过滤
|
||
major_category = self.request.query_params.get('major_category')
|
||
if major_category:
|
||
queryset = queryset.filter(major_category=major_category)
|
||
|
||
# 支持按材料分类过滤
|
||
material_category = self.request.query_params.get('material_category')
|
||
if material_category:
|
||
queryset = queryset.filter(material_category=material_category)
|
||
|
||
# 支持按材料子类过滤
|
||
material_subcategory = self.request.query_params.get('material_subcategory')
|
||
if material_subcategory:
|
||
queryset = queryset.filter(material_subcategory=material_subcategory)
|
||
|
||
# 支持按应用场景过滤 (JSONField contains)
|
||
application_scene = self.request.query_params.get('application_scene')
|
||
if application_scene:
|
||
queryset = queryset.filter(application_scene__contains=[application_scene])
|
||
|
||
# 支持按竞争优势过滤 (JSONField contains)
|
||
advantage = self.request.query_params.get('advantage')
|
||
if 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
|
||
|
||
def get_serializer_class(self):
|
||
"""
|
||
根据操作类型选择序列化器
|
||
"""
|
||
if self.action == 'list':
|
||
return MaterialListSerializer
|
||
return MaterialSerializer
|
||
|
||
def perform_create(self, serializer):
|
||
"""
|
||
创建材料时自动设置工厂
|
||
"""
|
||
# 普通用户只能为自己工厂创建材料
|
||
if self.request.user.role != 'admin':
|
||
serializer.save(factory=self.request.user.factory)
|
||
else:
|
||
serializer.save()
|
||
|
||
def perform_update(self, serializer):
|
||
"""
|
||
更新材料时的权限控制
|
||
"""
|
||
# 普通用户只能更新自己工厂的材料
|
||
if (self.request.user.role != 'admin' and
|
||
self.request.user.factory_id != self.get_object().factory_id):
|
||
raise PermissionDenied("无权修改其他工厂的材料")
|
||
|
||
# 普通用户只能编辑创建中的材料
|
||
if self.request.user.role != 'admin' and self.get_object().status != 'draft':
|
||
raise PermissionDenied("只有创建中的材料可以编辑")
|
||
serializer.save()
|
||
|
||
def perform_destroy(self, instance):
|
||
"""
|
||
删除材料时的权限控制
|
||
"""
|
||
# 普通用户只能删除自己工厂的材料
|
||
if (self.request.user.role != 'admin' and
|
||
self.request.user.factory_id != instance.factory_id):
|
||
raise PermissionDenied("无权删除其他工厂的材料")
|
||
|
||
# 普通用户只能删除创建中的材料
|
||
if self.request.user.role != 'admin' and instance.status != 'draft':
|
||
raise PermissionDenied("只有创建中的材料可以删除")
|
||
was_approved = instance.status == 'approved'
|
||
instance.delete()
|
||
if was_approved:
|
||
invalidate_category_tree_cache()
|
||
|
||
@action(detail=True, methods=['post'])
|
||
def submit(self, request, pk=None):
|
||
"""
|
||
提交审核
|
||
"""
|
||
material = self.get_object()
|
||
|
||
# 普通用户只能提交自己工厂的材料
|
||
if (request.user.role != 'admin' and
|
||
request.user.factory_id != material.factory_id):
|
||
return Response(
|
||
{"detail": "无权提交其他工厂的材料"},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
if material.status != 'draft':
|
||
return Response(
|
||
{"detail": "只有创建中的材料才能提交审核"},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
material.status = 'pending'
|
||
material.save()
|
||
return Response({"status": "已提交审核"})
|
||
|
||
@action(detail=True, methods=['post'])
|
||
def approve(self, request, pk=None):
|
||
"""
|
||
审核通过
|
||
"""
|
||
# 只有管理员可以审核
|
||
if request.user.role != 'admin':
|
||
return Response(
|
||
{"detail": "只有管理员可以审核材料"},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
material = self.get_object()
|
||
if material.status != 'pending':
|
||
return Response(
|
||
{"detail": "只有待审核的材料才能审核"},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
material.status = 'approved'
|
||
material.save()
|
||
invalidate_category_tree_cache()
|
||
return Response({"status": "审核通过"})
|
||
|
||
@action(detail=True, methods=['post'])
|
||
def reject(self, request, pk=None):
|
||
"""
|
||
审核拒绝
|
||
"""
|
||
# 只有管理员可以审核
|
||
if request.user.role != 'admin':
|
||
return Response(
|
||
{"detail": "只有管理员可以审核材料"},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
material = self.get_object()
|
||
if material.status != 'pending':
|
||
return Response(
|
||
{"detail": "只有待审核的材料才能审核"},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
was_approved = material.status == 'approved'
|
||
material.status = 'draft'
|
||
material.save()
|
||
if was_approved:
|
||
invalidate_category_tree_cache()
|
||
return Response({"status": "审核拒绝"})
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def choices(self, request):
|
||
"""
|
||
材料字段枚举
|
||
"""
|
||
return Response({
|
||
'major_category': Material.MAJOR_CATEGORY_CHOICES,
|
||
'stage': Material.STAGE_CHOICES,
|
||
'importance_level': Material.IMPORTANCE_LEVEL_CHOICES,
|
||
'replace_type': Material.REPLACE_TYPE_CHOICES,
|
||
'advantage': Material.ADVANTAGE_CHOICES,
|
||
'application_scene': Material.APPLICATION_SCENE_CHOICES,
|
||
'star_level': Material.STAR_LEVEL_CHOICES,
|
||
'status': Material.STATUS_CHOICES,
|
||
'cooperation_mode': COOPERATION_MODE_CHOICES,
|
||
})
|
||
|
||
@action(detail=False, methods=['get'], url_path='template')
|
||
def template(self, request):
|
||
template_path = Path(__file__).resolve().parents[3] / 'frontend' / 'public' / 'material_import_template.xlsx'
|
||
if not template_path.exists():
|
||
raise Http404("模板文件不存在")
|
||
|
||
return FileResponse(
|
||
template_path.open('rb'),
|
||
as_attachment=True,
|
||
filename='material_import_template.xlsx',
|
||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
)
|
||
|
||
@action(detail=False, methods=['get'], url_path='export-excel')
|
||
def export_excel(self, request):
|
||
workbook = Workbook()
|
||
worksheet = workbook.active
|
||
worksheet.title = "材料"
|
||
headers = [
|
||
"材料名称","材料大类", "细分种类", "材料子类", "阶段", "重要等级",
|
||
"落地项目", "对接人", "对接人联系方式", "经办人", "供应商", "工厂全称", "规格型号",
|
||
"符合标准", "应用场景", "应用说明", "替代材料类型", "竞争优势", "优势说明",
|
||
"成本对比(%)", "成本说明", "案例", "质量等级", "耐久等级", "环保等级",
|
||
"低碳等级", "总评分", "连接方式", "施工工艺", "限制条件", "备注",
|
||
]
|
||
_apply_template_title_style(worksheet, 1, len(headers), "新材料工作进展目录")
|
||
worksheet.append(headers)
|
||
_apply_template_header_style(worksheet, 2, len(headers))
|
||
_apply_header_column_widths(worksheet, headers)
|
||
|
||
queryset = self.get_queryset().select_related('factory')
|
||
for material in queryset:
|
||
worksheet.append([
|
||
material.name or "",
|
||
material.get_major_category_display() or "",
|
||
material.material_category or "",
|
||
material.material_subcategory or "",
|
||
material.get_stage_display() or "",
|
||
material.get_importance_level_display() or "",
|
||
material.landing_project or "",
|
||
material.contact_person or "",
|
||
material.contact_phone or "",
|
||
material.handler or "",
|
||
material.factory.short_name if material.factory else "",
|
||
material.factory.factory_name if material.factory else "",
|
||
material.spec or "",
|
||
material.standard or "",
|
||
_join_choice_values(material.application_scene, Material.APPLICATION_SCENE_CHOICES),
|
||
material.application_desc or "",
|
||
material.get_replace_type_display() or "",
|
||
_join_choice_values(material.advantage, Material.ADVANTAGE_CHOICES),
|
||
material.advantage_desc or "",
|
||
"" if material.cost_compare is None else str(material.cost_compare),
|
||
material.cost_desc or "",
|
||
material.cases or "",
|
||
material.get_quality_level_display() or "",
|
||
material.get_durability_level_display() or "",
|
||
material.get_eco_level_display() or "",
|
||
material.get_carbon_level_display() or "",
|
||
material.get_score_level_display() or "",
|
||
material.connection_method or "",
|
||
material.construction_method or "",
|
||
material.limit_condition or "",
|
||
material.remark or "",
|
||
])
|
||
|
||
_apply_export_dropdowns(worksheet, headers, queryset.count())
|
||
|
||
output = BytesIO()
|
||
workbook.save(output)
|
||
output.seek(0)
|
||
filename = f"materials_export_{timezone.localtime().strftime('%Y%m%d_%H%M%S')}.xlsx"
|
||
return FileResponse(
|
||
output,
|
||
as_attachment=True,
|
||
filename=filename,
|
||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
)
|
||
|
||
@action(detail=False, methods=['post'], parser_classes=[MultiPartParser], url_path='import-excel')
|
||
def import_excel(self, request):
|
||
if request.user.role != 'admin':
|
||
raise PermissionDenied("只有管理员可以导入材料")
|
||
|
||
excel_file = request.FILES.get('file')
|
||
if not excel_file:
|
||
return Response({"detail": "未提供 Excel 文件"}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if not excel_file.name.lower().endswith('.xlsx'):
|
||
return Response({"detail": "仅支持 .xlsx 格式的 Excel 文件"}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
result = import_materials_plan_excel(excel_file)
|
||
except ValueError as exc:
|
||
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||
except Exception as exc:
|
||
return Response({"detail": f"导入失败: {exc}"}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
invalidate_category_tree_cache()
|
||
return Response(result)
|
||
|
||
@action(detail=False, methods=['get'], url_path='category-tree', permission_classes=[IsAuthenticated])
|
||
def category_tree(self, request):
|
||
"""
|
||
H5 浏览端的全量种类树:一次返回所有大类 → 材料种类 → 子类的聚合结果。
|
||
与用户无关(仅依赖 status='approved'),故走全局缓存;Material 变更时由 signals 失效。
|
||
"""
|
||
cached = cache.get(CATEGORY_TREE_CACHE_KEY)
|
||
if cached is not None:
|
||
return Response(cached)
|
||
|
||
rows = (Material.objects
|
||
.filter(status='approved')
|
||
.exclude(major_category__isnull=True).exclude(major_category__exact='')
|
||
.exclude(material_category__isnull=True).exclude(material_category__exact='')
|
||
.values('major_category', 'material_category', 'material_subcategory')
|
||
.annotate(count=Count('id'))
|
||
.order_by('major_category', 'material_category', 'material_subcategory'))
|
||
|
||
majors = {}
|
||
for row in rows:
|
||
mj = row['major_category']
|
||
cat = row['material_category']
|
||
sub = row['material_subcategory'] or ''
|
||
cnt = row['count']
|
||
|
||
major_node = majors.setdefault(mj, {})
|
||
cat_node = major_node.setdefault(cat, {'count': 0, 'subs': {}})
|
||
cat_node['count'] += cnt
|
||
if sub:
|
||
cat_node['subs'][sub] = cat_node['subs'].get(sub, 0) + cnt
|
||
|
||
major_display = dict(Material.MAJOR_CATEGORY_CHOICES)
|
||
data = []
|
||
for mj_value, cats in majors.items():
|
||
data.append({
|
||
'value': mj_value,
|
||
'label': major_display.get(mj_value, mj_value),
|
||
'categories': [
|
||
{
|
||
'value': cat,
|
||
'count': node['count'],
|
||
'subcategories': [
|
||
{'value': s, 'count': c}
|
||
for s, c in sorted(node['subs'].items())
|
||
],
|
||
}
|
||
for cat, node in sorted(cats.items())
|
||
],
|
||
})
|
||
|
||
cache.set(CATEGORY_TREE_CACHE_KEY, data, timeout=60 * 5)
|
||
return Response(data)
|
||
|
||
@action(detail=False, methods=['get'], url_path='categories-by-major')
|
||
def categories_by_major(self, request):
|
||
major = request.query_params.get('major_category')
|
||
if not major:
|
||
return Response({"detail": "major_category 参数必填"}, status=status.HTTP_400_BAD_REQUEST)
|
||
qs = (Material.objects
|
||
.filter(major_category=major, status='approved')
|
||
.exclude(material_category__isnull=True)
|
||
.exclude(material_category__exact='')
|
||
.values('material_category')
|
||
.annotate(count=Count('id'))
|
||
.order_by('material_category'))
|
||
data = [{"value": row['material_category'], "count": row['count']} for row in qs]
|
||
return Response(data)
|
||
|
||
@action(detail=False, methods=['get'], url_path='subcategories-by-category')
|
||
def subcategories_by_category(self, request):
|
||
major = request.query_params.get('major_category')
|
||
category = request.query_params.get('material_category')
|
||
if not major or not category:
|
||
return Response({"detail": "major_category 和 material_category 均必填"}, status=status.HTTP_400_BAD_REQUEST)
|
||
qs = (Material.objects
|
||
.filter(major_category=major, material_category=category, status='approved')
|
||
.exclude(material_subcategory__isnull=True)
|
||
.exclude(material_subcategory__exact='')
|
||
.values('material_subcategory')
|
||
.annotate(count=Count('id'))
|
||
.order_by('material_subcategory'))
|
||
data = [{"value": row['material_subcategory'], "count": row['count']} for row in qs]
|
||
return Response(data)
|
||
|
||
|
||
class MaterialCategoryViewSet(ModelViewSet):
|
||
"""
|
||
材料分类视图集
|
||
"""
|
||
queryset = MaterialCategory.objects.all()
|
||
serializer_class = MaterialCategorySerializer
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def perform_create(self, serializer):
|
||
if self.request.user.role != 'admin':
|
||
raise PermissionDenied("只有管理员可以创建材料分类")
|
||
serializer.save()
|
||
|
||
def perform_update(self, serializer):
|
||
if self.request.user.role != 'admin':
|
||
raise PermissionDenied("只有管理员可以更新材料分类")
|
||
serializer.save()
|
||
|
||
def perform_destroy(self, instance):
|
||
if self.request.user.role != 'admin':
|
||
raise PermissionDenied("只有管理员可以删除材料分类")
|
||
instance.delete()
|
||
|
||
|
||
class MaterialSubcategoryViewSet(ModelViewSet):
|
||
"""
|
||
材料子分类视图集
|
||
"""
|
||
queryset = MaterialSubcategory.objects.select_related('category').all()
|
||
serializer_class = MaterialSubcategorySerializer
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def get_queryset(self):
|
||
queryset = super().get_queryset()
|
||
category_id = self.request.query_params.get('category_id')
|
||
if category_id:
|
||
queryset = queryset.filter(category_id=category_id)
|
||
return queryset
|
||
|
||
def perform_create(self, serializer):
|
||
if self.request.user.role != 'admin':
|
||
raise PermissionDenied("只有管理员可以创建材料子分类")
|
||
serializer.save()
|
||
|
||
def perform_update(self, serializer):
|
||
if self.request.user.role != 'admin':
|
||
raise PermissionDenied("只有管理员可以更新材料子分类")
|
||
serializer.save()
|
||
|
||
def perform_destroy(self, instance):
|
||
if self.request.user.role != 'admin':
|
||
raise PermissionDenied("只有管理员可以删除材料子分类")
|
||
instance.delete()
|