Python Django:如何决定基于类和基于函数的自定义验证器?

Python Django:如何决定基于类和基于函数的自定义验证器?,python,django,Python,Django,这是一个初学者的问题。我正在开发一个网站,允许用户将视频上传到项目模型(通过ModelForm),我想正确验证这个文件。我最初是这样声明字段的: 来自django.db导入模型的 从django.core.validators导入FileExtensionValidator def用户目录路径(实例,文件名): """ 返回路径的代码 """ 类项目(models.Model): """ """ #…一些模型字段。。。 #现在我只使用.mp4文件进行验证和测试。 视频文件=models.File

这是一个初学者的问题。我正在开发一个网站,允许用户将视频上传到项目模型(通过ModelForm),我想正确验证这个文件。我最初是这样声明字段的:

来自django.db导入模型的

从django.core.validators导入FileExtensionValidator
def用户目录路径(实例,文件名):
"""
返回路径的代码
"""
类项目(models.Model):
"""
"""
#…一些模型字段。。。
#现在我只使用.mp4文件进行验证和测试。
视频文件=models.FileField(
上传到=用户目录路径,
验证器=[FileExtensionValidator(允许的扩展=['mp4'])]
)
但我在几个地方读到,最好使用
libmagic
来检查文件的幻数,并确保其内容与扩展名和MIME类型匹配。我对这个很陌生,所以我可能会弄错一些事情

我按照下面的步骤编写了一个使用
magic
的自定义验证器。该文档还讨论了“具有
\uuuu cal\uuuu()
方法的类”,而最受欢迎的答案是使用基于类的验证器。文档中说“对于更复杂或可配置的验证器”可以这样做,但我还不知道具体的例子是什么,以及我的基于函数的验证器是否足以满足我的要求。我想是的,但我没有经验可以肯定

这就是我所拥有的

models.py

来自django.db导入模型的

从.validators导入验证\媒体\文件
def用户目录路径(实例,文件名):
"""
返回路径的代码
"""
类项目(models.Model):
"""
"""
#…一些模型字段。。。
#现在我只使用.mp4文件进行验证和测试。
视频文件=models.FileField(
上传到=用户目录路径,
验证程序=[验证\u媒体\u文件]
)
py(基本上取自中的示例)

导入操作系统
进口魔术
从django.core.exceptions导入ValidationError
从django.utils.translation导入gettext\u lazy作为_
def验证_媒体_文件(值):
"""
"""
#大写,然后查看它是否有魔力
文件扩展名=os.path.splitext(value.name)[1].upper()[1:]
#列表,因为稍后我将验证其他格式
如果文件扩展名不在['MP4']中:
引发验证错误(
_(“文件%(值)s不包含有效扩展名”),
params={'value':value},
)
elif文件扩展名不在魔术中。从缓冲区(value.read()):
引发验证错误(
_(),
params={'value':value},
)
迁移就是这样进行的。我还用一个扩展名为.mp4的纯文本文件测试了它,然后用另一个文件(和扩展名)测试了它,它可以正常工作。然而,我想知道,如果使用这个而不是基于类的验证器,我是否遗漏了一些东西,而且,正如标题所说,我应该在什么时候使用验证器,因为我可能会遇到另一种情况,我需要知道它

我知道我没有包括MIME类型;我可以稍后再做

另外一个问题是,当
magic.from_buffer()
的输出与扩展名和/或MIME类型不匹配时,适当的错误消息是什么?我想说“文件已损坏”,但我不确定。实际上,这是直接基于幻数的输出吗?

何时使用基于类的验证器? 在您的示例中,基于函数的验证器就足够了。如果您需要OOP、类和对象的优点,那么您应该切换到基于类的验证器。 想象一下以下非常虚构的源代码:

class StartsWithValidator():
    def __init__(self, starts_with):
        self.starts_with = starts_with

    def __call__(self, value):
        if not str(value).startswith(self.starts_with):
            raise ValidationError(
                'Your string does not start with: {}!'.format(self.starts_with),
                params={'value': value}
            )

my_validator = StartsWithValidator('123')
test_string = '123OneTwoThree'
my_validator(test_string) # Will it pass the validator?
你可以在这里看到不同的品质:

  • 使用基于类的验证器,您可以使用对象。对象使用不同的内部状态共享相同的功能。现在可以设置一个验证器,它检查字符串是否以“abc”、“123”开头,而无需编写新代码
  • 您可以使用继承。假设您想要重用带有验证的start和其他特性,您只需从“StartsWithValidator”类继承
  • 如果验证器做了很多复杂的事情,那么一个简单的函数可能会导致可读性差的代码。如果使用类,则可以封装功能并将其分组

  • 如果您需要类的优点——继承等等,那么可以使用基于类的验证器。如果您知道现在不需要它,那么基于函数的验证器就足够了。基本上,具有神奇方法“\uuuu call\uuuu()”的类提供了一个函数通常会提供的接口。您可以像调用函数一样调用此类的对象。如果您稍后注意到,您需要在其他地方的不同修改中使用此功能,那么将其转换为类并没有什么大不了的。在我的例子中,我实际上可以使用一个基于类的验证器,并用每种文件格式实例化它,比如
    MediaFormatValidator('MP4')
    MediaFormatValidator('AVI')
    ,等等,并在
    \u call\uuuu()
    方法中运行相同的代码,前提是
    magic.from\u buffer()
    在其输出字符串中包含此格式,就像MP4格式一样,对吗?尽管如此,即使这是正确的,使用一个列表来查看扩展是否在其中还是足够的。是的,你做对了。我还建议只使用一个简单的函数,直到达到这种实现的限制。您会注意到,当您的代码包含太多if语句时,代码会自动重复(注意Ctrl+C/Ctrl+V)或变得太大。还有很多其他的代码气味,但你会找到自己的方法:-)很好。是的,我看这件事太过分了。
    starts_with_abc = StartsWithValidator('abc')
    starts_with_123 = StartsWithValidator('123')
    starts_with_whatever = StartsWithValidator('whatever')
    
    class StartsWithABCValidator(StartsWithValidator):
        def __init__(self):
            super().__init__('ABC')
    
        def __call__(self, value):
            super().__call__(value)