验证Django rest中的序列号与掩码。

huangapple go评论66阅读模式
英文:

Validation of the serial number against the mask in Django rest

问题

以下是翻译好的部分:

models.py

from django.db import models
from django.core.validators import MinLengthValidator
from django.core.validators import RegexValidator

class Type_Of_Equipment(models.Model):
    name = models.CharField(verbose_name='设备类型', max_length=64, blank=True, default='无标题')
    sn_mask = models.CharField(verbose_name='序列号掩码', blank=False, max_length=10,
    validators=[MinLengthValidator(10), RegexValidator(regex=r'^[NAaXZ]+$', message='使用了不允许的字符')])

class Equipment(models.Model):
    code = models.CharField(verbose_name='设备类型代码', max_length=64, blank=True)
    sn_number = models.CharField(verbose_name='序列号', max_length=10, blank=False, unique=True, validators=[MinLengthValidator(10), RegexValidator(regex=r'^[A-Za-z0-9-_@]+$', message='使用了不允许的字符')]) 
    type_of_equipment = models.ForeignKey(Type_Of_Equipment, on_delete=models.DO_NOTHING)   
    is_deleted = models.BooleanField(default=False)

    # 用于软删除的方法
    def soft_delete(self):
        self.is_deleted = True
        self.save()

    def restore(self):
        self.is_deleted = False
        self.save()

views.py

from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .serializers import EquipmentSerializer, TypeOfEquipmentSerializer
from .models import Equipment, Type_Of_Equipment
from django.http import Http404

import re

class GetOrCreateEquip(APIView):
    # 获取所有设备
    def get(self, request):
        # 通过查询参数获取设备列表
        equip = Equipment.objects.filter(is_deleted=False)
        code_query = request.query_params.get('code')
        sn_number_query = request.query_params.get('sn_mask')

        if code_query:
            equip = equip.filter(code=code_query)

        elif sn_number_query:
            equip = equip.filter(sn_number=sn_number_query)            

        serializer = EquipmentSerializer(equip, many=True)
        return Response(serializer.data)

    # 创建新设备
    def post(self, request):
        # 只有当序列号与掩码匹配时才创建对象
        # 是否将数据验证逻辑移到模型的clean函数中?
        sn_shifer = {'N': r'^[0-9]+$',
            'A': r'^[A-Z]+$',
            'a': r'^[a-z]+$',
            'X': r'^[A-Z0-9]+$',
            'Z': r"^[-|_|@]+$"
        }

        success = 0

        for equip in request.data:
        # 查找与序列号匹配的设备类型的逻辑
            types_of_equipment = Type_Of_Equipment.objects.all()
            equip_sn = equip.get('sn_number')
            for type_of_equipment in types_of_equipment:
                for i in range(10):

                    if re.match(sn_shifer[type_of_equipment.sn_mask[i]], equip_sn[i]):

                        success += 1
                        if success == 10:
                            request.data["type_of_equipment"] = type_of_equipment
                            serializer = EquipmentSerializer(data=request.data, many=True)
                            if serializer.is_valid():
                                serializer.save()
                                return Response(serializer.data, status=201)

                            return Response(serializer.errors, status=400)
                    else:
                        success = 0
                        break
英文:

There are two tables - equipment type and equipment, the type has a line with a serial number mask, the equipment has a line with a serial number. When creating new equipment, validation must go through, that is, the string with the serial number is compared with each mask of the equipment type, and if a match is found, the equipment is tied to the type, the question is, how is it better to implement this on the django rest framework?

The check is carried out according to the principle:

N is a number from 0 to 9;
A is a capital letter of the Latin alphabet;
a is a lowercase letter of the Latin alphabet;
X is a capital letter of the Latin alphabet or a number from 0 to 9;
Z is a character from the list: “-“, “_”, “@”

That is, if the equipment has a serial number of 0QWER9@123, it corresponds to a mask with characters like NAAAAXZXXX

models.py


from django.db import models
from django.core.validators import MinLengthValidator
from django.core.validators import RegexValidator
class Type_Of_Equipment(models.Model):
    name = models.CharField(verbose_name='Тип оборудования', max_length=64, blank=True, default='Без названия')
    sn_mask = models.CharField(verbose_name='Маска серийного номера', blank=False, max_length=10,
    validators=[MinLengthValidator(10), RegexValidator(regex=r'^[NAaXZ]+$', message='Использованы недопустимые символы')])    

class Equipment(models.Model):
    code = models.CharField(verbose_name='Код типа оборудования', max_length=64, blank=True)
    sn_number = models.CharField(verbose_name='Серийный номер', max_length=10, blank=False, unique=True, validators=[MinLengthValidator(10), RegexValidator(regex=r'^[A-Za-z0-9-_@]+$', message='Использованы недопустимые символы')]) 
    type_of_equipment = models.ForeignKey(Type_Of_Equipment, on_delete=models.DO_NOTHING)   
    is_deleted = models.BooleanField(default=False)

    # Заготовки для мягкого удаления.
    def soft_delete(self):
        self.is_deleted = True
        self.save()
    def restore(self):
        self.is_deleted = False
        self.save()

views.py


from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .serializers import EquipmentSerializer, TypeOfEquipmentSerializer
from .models import Equipment, Type_Of_Equipment
from django.http import Http404

import re

class GetOrCreateEquip(APIView):
    # Получение всего оборудования
    def get(self, request):
        # Получать список с query-параметрами
        equip = Equipment.objects.filter(is_deleted=False)
        code_query = request.query_params.get('code')
        sn_number_query = request.query_params.get('sn_mask')

        if code_query:
            equip = equip.filter(code=code_query)

        elif sn_number_query:
            equip = equip.filter(sn_number=sn_number_query)            

        serializer = EquipmentSerializer(equip, many=True)
        return Response(serializer.data)

    # Создание нового оборудования
    def post(self, request):
        # Создавать объект, только если серийный номер совпадает с маской 
        # Перенести логику валидации данных в функцию clean в моделях?
        sn_shifer = {'N': r'^[0-9]+$',
            'A': r'^[A-Z]+$',
            'a': r'^[a-z]+$',
            'X': r'^[A-Z0-9]+$',
            'Z': r"^[-|_|@]+$"
        }

        success = 0

        for equip in request.data:
        # Логика поиска нужного типа оборудования под серийный номер
            types_of_equipment = Type_Of_Equipment.objects.all()
            equip_sn = equip.get('sn_number')
            for type_of_equipment in types_of_equipment:
                for i in range(10):

                    if re.match(sn_shifer[type_of_equipment.sn_mask[i]], equip_sn[i]):

                        success += 1
                        if success == 10:
                            request.data["type_of_equipment"] = type_of_equipment
                            serializer = EquipmentSerializer(data=request.data, many=True)
                            if serializer.is_valid():
                                serializer.save()
                                return Response(serializer.data, status=201)

                            return Response(serializer.errors, status=400)
                    else:
                        success = 0
                        break

I tried to implement this in models through the clean function, but someone recommended me to do it through views, I apologize in advance for the terrible code, I try to be better.

答案1

得分: 1

首先,你的Mask存在一个冲突,意味着对于相同的type可能会有多个版本,因为:

A是拉丁字母表的大写字母;
...
X是拉丁字母表的大写字母或数字0到9之间的数字;

更确切地说,0QWER9@123对应于NAAAAXZXXX,但也对应于NAAAANZNNN。除非你施加一些条件,我将在我的示例中这样做以获得所需的结果(只有第一个数字元素获得'N',其他元素将获得'X')。

现在,回答你的问题。我认为实现这一点的一种优雅方法是编写一个自定义验证器,你可以将其放在一个单独的文件中:

validators.py

def validate_sn_mask(value):
    mask = ""
    special_list = ["-", "_", "@"]
    first_number = True

    for character in value:
        if character.isnumeric():
            if first_number:
                mask += "N"
                first_number = False
            else:
                mask += "X"
        elif character.isalpha():
            if character.isupper():
                mask += "A"
            else:
                mask += "a"
        elif character in special_list:
            mask += "Z"
        else:
            # 尽管你的模型中已经有一个正则表达式
            raise ValidationError({"msg": "Invalid Mask"})

    qs = Type_Of_Equipment.objects.filter(sn_mask=mask)

    if not qs:
        raise ValidationError({"msg": "Mask has no correspondent type"})

    return qs[0]

serializers.py

class EquipmentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Equipment
        fields = ['code', 'sn_number','type_of_equipment']
        extra_kwargs = {
            'type_of_equipment': {'required': False}
        }
    
    def validate(self, attrs):
        equipment_type_instance = validate_sn_mask(attrs['sn_number'])
        attrs['type_of_equipment'] = equipment_type_instance
        return super().validate(attrs)

views.py

class GetOrCreateEquip(APIView):
    def get(self, request):
        ...

    def post(self, request):
        serializer = EquipmentSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)
英文:

First of all, there is one clash in your Mask, meaning there will be more than one version for the same type since:

A is a capital letter of the Latin alphabet;
...
X is a capital letter of the Latin alphabet or a number from 0 to 9;

More precisely 0QWER9@123 corresponds to NAAAAXZXXX but also to NAAAANZNNN. Unless you impose some condition, which I will do in my example in order to obtain the desired result (Only the first numeric element gets 'N' the others will get 'X').

Now, to answer your question. I believe an elegant way to implement this would be to write a custom validator that you can place in a separate file:

validators.py

def validate_sn_mask(value):
    mask = ""
    special_list = ["-", "_", "@"]
    first_number = True

    for character in value:
        if character.isnumeric():
            if first_number:
                mask += "N"
                first_number = False
            else:
                mask += "X"
        elif character.isalpha():
            if character.isupper():
                mask += "A"
            else:
                mask += "a"
        elif character in special_list:
            mask += "Z"
        else:
            # Although you already have a regex in your Model
            raise ValidationError({"msg": "Invalid Mask"})

    qs = Type_Of_Equipment.objects.filter(sn_mask=mask)

    if not qs:
        raise ValidationError({"msg": "Mask has no correspondent type"})

    return qs[0]

serializers.py

class EquipmentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Equipment
        fields = ['code', 'sn_number','type_of_equipment']
        extra_kwargs = {
            'type_of_equipment': {'required': False}
        }
    
    def validate(self, attrs):
        equipment_type_instance = validate_sn_mask(attrs['sn_number'])
        attrs['type_of_equipment'] = equipment_type_instance
        return super().validate(attrs)

views.py

class GetOrCreateEquip(APIView):
    def get(self, request):
        ...

    def post(self, request):
        serializer = EquipmentSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)

huangapple
  • 本文由 发表于 2023年5月24日 19:00:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/76322811.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定