由于验证器,Django 1.8 makemigrations 每次都会生成重复的迁移
Django 1.8 makemigrations generates a duplicated migration every time due to validators
我有一个模型,其中有一个带有以下验证器的字段:
validators=[
FileValidator(
allowed_extensions=ALLOWED_PHOTO_EXT,
allowed_mimetypes=ALLOWED_PHOTO_MIME_TYPES,
max_size=ALLOWED_PHOTO_MAX_SIZE,
)
],
这是验证器本身
@deconstructible
class FileValidator(object):
"""
Validator for files, checking the size, extension and mimetype.
Initialization parameters:
allowed_extensions: iterable with allowed file extensions
ie. ('txt', 'doc')
allowd_mimetypes: iterable with allowed mimetypes
ie. ('image/png', )
min_size: minimum number of bytes allowed
ie. 100
max_size: maximum number of bytes allowed
ie. 24*1024*1024 for 24 MB
Usage example::
MyModel(models.Model):
myfile = FileField(validators=FileValidator(max_size=24*1024*1024), ...)
See https://gist.github.com/jrosebr1/2140738 (improved)
"""
extension_message = _("Extension '%(extension)s' not allowed. Allowed extensions are: '%(allowed_extensions)s.'")
mime_message = _("MIME type '%(mimetype)s' is not valid. Allowed types are: %(allowed_mimetypes)s.")
min_size_message = _('The current file %(size)s, which is too small. The minumum file size is %(allowed_size)s.')
max_size_message = _('The current file %(size)s, which is too large. The maximum file size is %(allowed_size)s.')
def __init__(self, *args, **kwargs):
self.allowed_extensions = kwargs.pop('allowed_extensions', None)
self.allowed_mimetypes = kwargs.pop('allowed_mimetypes', None)
self.min_size = kwargs.pop('min_size', 0)
self.max_size = kwargs.pop('max_size', DEFAULT_FILE_MAX_SIZE)
def __call__(self, value):
"""
Check the extension, content type and file size.
"""
# Check the extension
ext = splitext(value.name)[1][1:].lower()
if self.allowed_extensions and not ext in self.allowed_extensions:
message = self.extension_message % {
'extension': ext,
'allowed_extensions': ', '.join(self.allowed_extensions)
}
raise ValidationError(message)
# Check the content type
# mimetype = mimetypes.guess_type(value.name)[0] # XXX Alternative guessing way, unsure
mimetype = magic.from_buffer(value.read(1024), mime=True)
if self.allowed_mimetypes and not mimetype in self.allowed_mimetypes:
message = self.mime_message % {
'mimetype': mimetype,
'allowed_mimetypes': ', '.join(self.allowed_mimetypes)
}
raise ValidationError(message)
# Check the file size
filesize = len(value)
if self.max_size and filesize > self.max_size:
message = self.max_size_message % {
'size': filesizeformat(filesize),
'allowed_size': filesizeformat(self.max_size)
}
raise ValidationError(message)
elif filesize < self.min_size:
message = self.min_size_message % {
'size': filesizeformat(filesize),
'allowed_size': filesizeformat(self.min_size)
}
raise ValidationError(message)
每次我运行./manage.py makemigrations app1
0003_auto_20180521_0325.py:
- Alter field xxx on yyy
它每次都会生成一个新的迁移,它总是做同样的事情(改变字段)
如果我注释掉我的验证器,此行为将停止并且 makemigrations 显示 "no change detected" 消息。
怎么了?我怎样才能避免这种行为?
编辑:根据@benjamin 的回答,这里是完整的代码,包括修复。 (__eq__
函数)
# -*- coding: utf-8 -*-
import magic
from os.path import splitext
from django.core.exceptions import ValidationError
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _
from django.template.defaultfilters import filesizeformat
DEFAULT_FILE_MAX_SIZE = 10 * 1024 * 1024 # 10Mo
# Photo, like profile picture
# Only allow web-friendly extensions/mime types, since they'll be displayed on web pages
# TODO Size is huge, should be optimised
ALLOWED_PHOTO_EXT = [
'jpg',
'jpeg',
'png',
]
ALLOWED_PHOTO_MIME_TYPES = [
'image/jpeg',
'image/pjpeg',
'image/png',
'image/x-png',
]
ALLOWED_PHOTO_MAX_SIZE = 10 * 1024 * 1024 # 10Mo
# Any document
# Allow a wide range of extensions and mime types
# TODO Size is huge, should be optimised
ALLOWED_DOCUMENT_EXT = [
'jpg',
'jpeg',
'png',
'tif',
'bmp',
'pdf',
'doc',
'dot',
'docx',
'dotx',
'xls',
'xlt',
'xla',
'xlsx',
'xltx',
'pptx',
'potx',
'ppsx',
]
ALLOWED_DOCUMENT_MIME_TYPES = [
'image/jpeg',
'image/pjpeg',
'image/png',
'image/x-png',
'image/tiff',
'image/bmp',
'application/pdf',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/zip', # XXX PPTX can be detected as ZIP for some reasons
]
ALLOWED_DOCUMENT_MAX_SIZE = 10 * 1024 * 1024 # 10Mo
# Any document sent to SMoney, which have their own rules and limitations
# Allow a wide range of extensions and mime types
# TODO Size is huge, should be optimised
ALLOWED_DOCUMENT_SMONEY_EXT = [
'jpg',
'jpeg',
'png',
'tif',
'bmp',
'pdf',
]
ALLOWED_DOCUMENT_SMONEY_MIME_TYPES = [
'image/jpeg',
'image/pjpeg',
'image/png',
'image/x-png',
'image/tiff',
'image/bmp',
'application/pdf',
]
ALLOWED_DOCUMENT_SMONEY_MAX_SIZE = 3 * 1024 * 1024 # 3Mo
@deconstructible
class FileValidator(object):
"""
Validator for files, checking the size, extension and mimetype.
Initialization parameters:
allowed_extensions: iterable with allowed file extensions
ie. ('txt', 'doc')
allowd_mimetypes: iterable with allowed mimetypes
ie. ('image/png', )
min_size: minimum number of bytes allowed
ie. 100
max_size: maximum number of bytes allowed
ie. 24*1024*1024 for 24 MB
Usage example::
MyModel(models.Model):
myfile = FileField(validators=FileValidator(max_size=24*1024*1024), ...)
See https://gist.github.com/jrosebr1/2140738 (improved)
"""
extension_message = _("Extension '%(extension)s' not allowed. Allowed extensions are: '%(allowed_extensions)s.'")
mime_message = _("MIME type '%(mimetype)s' is not valid. Allowed types are: %(allowed_mimetypes)s.")
min_size_message = _('The current file %(size)s, which is too small. The minumum file size is %(allowed_size)s.')
max_size_message = _('The current file %(size)s, which is too large. The maximum file size is %(allowed_size)s.')
def __init__(self, *args, **kwargs):
self.allowed_extensions = kwargs.pop('allowed_extensions', None)
self.allowed_mimetypes = kwargs.pop('allowed_mimetypes', None)
self.min_size = kwargs.pop('min_size', 0)
self.max_size = kwargs.pop('max_size', DEFAULT_FILE_MAX_SIZE)
def __call__(self, value):
"""
Check the extension, content type and file size.
"""
# Check the extension
ext = splitext(value.name)[1][1:].lower()
if self.allowed_extensions and not ext in self.allowed_extensions:
message = self.extension_message % {
'extension': ext,
'allowed_extensions': ', '.join(self.allowed_extensions)
}
raise ValidationError(message)
# Check the content type
# mimetype = mimetypes.guess_type(value.name)[0] # XXX Alternative guessing way, unsure
mimetype = magic.from_buffer(value.read(1024), mime=True)
if self.allowed_mimetypes and not mimetype in self.allowed_mimetypes:
message = self.mime_message % {
'mimetype': mimetype,
'allowed_mimetypes': ', '.join(self.allowed_mimetypes)
}
raise ValidationError(message)
# Check the file size
filesize = len(value)
if self.max_size and filesize > self.max_size:
message = self.max_size_message % {
'size': filesizeformat(filesize),
'allowed_size': filesizeformat(self.max_size)
}
raise ValidationError(message)
elif filesize < self.min_size:
message = self.min_size_message % {
'size': filesizeformat(filesize),
'allowed_size': filesizeformat(self.min_size)
}
raise ValidationError(message)
def __eq__(self, other):
return (
isinstance(other, self.__class__) and
self.allowed_extensions == other.allowed_extensions and
self.allowed_mimetypes == other.allowed_mimetypes and
self.min_size == self.min_size and
self.max_size == self.max_size
)
/app_name/migrations
删除除__init__.py
之外的所有.py文件
python manage.py makemigrations
python manage.py migrate
如果这不起作用,那么您可以尝试我已经做过的事情
在视图中,我像这样检查图像是否有效
def check_image(request):
if len(request.FILES) != 0:
image = request.FILES['image'] # <input type="file" name="image" ....>
ext = os.path.splitext(image.name)[1]
if ext in ['.jpg', '.jpeg', '.png', '.gif']:
# file is image
x=0
else:
# file is not image
y=0
return HttpResponseRedirect('/')
我愿意猜测这是因为验证器 class 中缺少 __eq__
方法。默认比较中的某些内容正在寻找 'not equal' 基于其 __dict__
以相同方式初始化的两个实例。尝试添加一个明确的:
def __eq__(self, other):
return (isinstance(other, self.__class__) and
self.allowed_extensions == other.allowed_extensions and
self.allowed_mimetypes == other.allowed_mimetypes and
self.min_size == self.min_size and
self.max_size == self.max_size
)
我有一个模型,其中有一个带有以下验证器的字段:
validators=[
FileValidator(
allowed_extensions=ALLOWED_PHOTO_EXT,
allowed_mimetypes=ALLOWED_PHOTO_MIME_TYPES,
max_size=ALLOWED_PHOTO_MAX_SIZE,
)
],
这是验证器本身
@deconstructible
class FileValidator(object):
"""
Validator for files, checking the size, extension and mimetype.
Initialization parameters:
allowed_extensions: iterable with allowed file extensions
ie. ('txt', 'doc')
allowd_mimetypes: iterable with allowed mimetypes
ie. ('image/png', )
min_size: minimum number of bytes allowed
ie. 100
max_size: maximum number of bytes allowed
ie. 24*1024*1024 for 24 MB
Usage example::
MyModel(models.Model):
myfile = FileField(validators=FileValidator(max_size=24*1024*1024), ...)
See https://gist.github.com/jrosebr1/2140738 (improved)
"""
extension_message = _("Extension '%(extension)s' not allowed. Allowed extensions are: '%(allowed_extensions)s.'")
mime_message = _("MIME type '%(mimetype)s' is not valid. Allowed types are: %(allowed_mimetypes)s.")
min_size_message = _('The current file %(size)s, which is too small. The minumum file size is %(allowed_size)s.')
max_size_message = _('The current file %(size)s, which is too large. The maximum file size is %(allowed_size)s.')
def __init__(self, *args, **kwargs):
self.allowed_extensions = kwargs.pop('allowed_extensions', None)
self.allowed_mimetypes = kwargs.pop('allowed_mimetypes', None)
self.min_size = kwargs.pop('min_size', 0)
self.max_size = kwargs.pop('max_size', DEFAULT_FILE_MAX_SIZE)
def __call__(self, value):
"""
Check the extension, content type and file size.
"""
# Check the extension
ext = splitext(value.name)[1][1:].lower()
if self.allowed_extensions and not ext in self.allowed_extensions:
message = self.extension_message % {
'extension': ext,
'allowed_extensions': ', '.join(self.allowed_extensions)
}
raise ValidationError(message)
# Check the content type
# mimetype = mimetypes.guess_type(value.name)[0] # XXX Alternative guessing way, unsure
mimetype = magic.from_buffer(value.read(1024), mime=True)
if self.allowed_mimetypes and not mimetype in self.allowed_mimetypes:
message = self.mime_message % {
'mimetype': mimetype,
'allowed_mimetypes': ', '.join(self.allowed_mimetypes)
}
raise ValidationError(message)
# Check the file size
filesize = len(value)
if self.max_size and filesize > self.max_size:
message = self.max_size_message % {
'size': filesizeformat(filesize),
'allowed_size': filesizeformat(self.max_size)
}
raise ValidationError(message)
elif filesize < self.min_size:
message = self.min_size_message % {
'size': filesizeformat(filesize),
'allowed_size': filesizeformat(self.min_size)
}
raise ValidationError(message)
每次我运行./manage.py makemigrations app1
0003_auto_20180521_0325.py:
- Alter field xxx on yyy
它每次都会生成一个新的迁移,它总是做同样的事情(改变字段)
如果我注释掉我的验证器,此行为将停止并且 makemigrations 显示 "no change detected" 消息。
怎么了?我怎样才能避免这种行为?
编辑:根据@benjamin 的回答,这里是完整的代码,包括修复。 (__eq__
函数)
# -*- coding: utf-8 -*-
import magic
from os.path import splitext
from django.core.exceptions import ValidationError
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _
from django.template.defaultfilters import filesizeformat
DEFAULT_FILE_MAX_SIZE = 10 * 1024 * 1024 # 10Mo
# Photo, like profile picture
# Only allow web-friendly extensions/mime types, since they'll be displayed on web pages
# TODO Size is huge, should be optimised
ALLOWED_PHOTO_EXT = [
'jpg',
'jpeg',
'png',
]
ALLOWED_PHOTO_MIME_TYPES = [
'image/jpeg',
'image/pjpeg',
'image/png',
'image/x-png',
]
ALLOWED_PHOTO_MAX_SIZE = 10 * 1024 * 1024 # 10Mo
# Any document
# Allow a wide range of extensions and mime types
# TODO Size is huge, should be optimised
ALLOWED_DOCUMENT_EXT = [
'jpg',
'jpeg',
'png',
'tif',
'bmp',
'pdf',
'doc',
'dot',
'docx',
'dotx',
'xls',
'xlt',
'xla',
'xlsx',
'xltx',
'pptx',
'potx',
'ppsx',
]
ALLOWED_DOCUMENT_MIME_TYPES = [
'image/jpeg',
'image/pjpeg',
'image/png',
'image/x-png',
'image/tiff',
'image/bmp',
'application/pdf',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/zip', # XXX PPTX can be detected as ZIP for some reasons
]
ALLOWED_DOCUMENT_MAX_SIZE = 10 * 1024 * 1024 # 10Mo
# Any document sent to SMoney, which have their own rules and limitations
# Allow a wide range of extensions and mime types
# TODO Size is huge, should be optimised
ALLOWED_DOCUMENT_SMONEY_EXT = [
'jpg',
'jpeg',
'png',
'tif',
'bmp',
'pdf',
]
ALLOWED_DOCUMENT_SMONEY_MIME_TYPES = [
'image/jpeg',
'image/pjpeg',
'image/png',
'image/x-png',
'image/tiff',
'image/bmp',
'application/pdf',
]
ALLOWED_DOCUMENT_SMONEY_MAX_SIZE = 3 * 1024 * 1024 # 3Mo
@deconstructible
class FileValidator(object):
"""
Validator for files, checking the size, extension and mimetype.
Initialization parameters:
allowed_extensions: iterable with allowed file extensions
ie. ('txt', 'doc')
allowd_mimetypes: iterable with allowed mimetypes
ie. ('image/png', )
min_size: minimum number of bytes allowed
ie. 100
max_size: maximum number of bytes allowed
ie. 24*1024*1024 for 24 MB
Usage example::
MyModel(models.Model):
myfile = FileField(validators=FileValidator(max_size=24*1024*1024), ...)
See https://gist.github.com/jrosebr1/2140738 (improved)
"""
extension_message = _("Extension '%(extension)s' not allowed. Allowed extensions are: '%(allowed_extensions)s.'")
mime_message = _("MIME type '%(mimetype)s' is not valid. Allowed types are: %(allowed_mimetypes)s.")
min_size_message = _('The current file %(size)s, which is too small. The minumum file size is %(allowed_size)s.')
max_size_message = _('The current file %(size)s, which is too large. The maximum file size is %(allowed_size)s.')
def __init__(self, *args, **kwargs):
self.allowed_extensions = kwargs.pop('allowed_extensions', None)
self.allowed_mimetypes = kwargs.pop('allowed_mimetypes', None)
self.min_size = kwargs.pop('min_size', 0)
self.max_size = kwargs.pop('max_size', DEFAULT_FILE_MAX_SIZE)
def __call__(self, value):
"""
Check the extension, content type and file size.
"""
# Check the extension
ext = splitext(value.name)[1][1:].lower()
if self.allowed_extensions and not ext in self.allowed_extensions:
message = self.extension_message % {
'extension': ext,
'allowed_extensions': ', '.join(self.allowed_extensions)
}
raise ValidationError(message)
# Check the content type
# mimetype = mimetypes.guess_type(value.name)[0] # XXX Alternative guessing way, unsure
mimetype = magic.from_buffer(value.read(1024), mime=True)
if self.allowed_mimetypes and not mimetype in self.allowed_mimetypes:
message = self.mime_message % {
'mimetype': mimetype,
'allowed_mimetypes': ', '.join(self.allowed_mimetypes)
}
raise ValidationError(message)
# Check the file size
filesize = len(value)
if self.max_size and filesize > self.max_size:
message = self.max_size_message % {
'size': filesizeformat(filesize),
'allowed_size': filesizeformat(self.max_size)
}
raise ValidationError(message)
elif filesize < self.min_size:
message = self.min_size_message % {
'size': filesizeformat(filesize),
'allowed_size': filesizeformat(self.min_size)
}
raise ValidationError(message)
def __eq__(self, other):
return (
isinstance(other, self.__class__) and
self.allowed_extensions == other.allowed_extensions and
self.allowed_mimetypes == other.allowed_mimetypes and
self.min_size == self.min_size and
self.max_size == self.max_size
)
/app_name/migrations
删除除__init__.py
之外的所有.py文件python manage.py makemigrations
python manage.py migrate
如果这不起作用,那么您可以尝试我已经做过的事情
在视图中,我像这样检查图像是否有效
def check_image(request):
if len(request.FILES) != 0:
image = request.FILES['image'] # <input type="file" name="image" ....>
ext = os.path.splitext(image.name)[1]
if ext in ['.jpg', '.jpeg', '.png', '.gif']:
# file is image
x=0
else:
# file is not image
y=0
return HttpResponseRedirect('/')
我愿意猜测这是因为验证器 class 中缺少 __eq__
方法。默认比较中的某些内容正在寻找 'not equal' 基于其 __dict__
以相同方式初始化的两个实例。尝试添加一个明确的:
def __eq__(self, other):
return (isinstance(other, self.__class__) and
self.allowed_extensions == other.allowed_extensions and
self.allowed_mimetypes == other.allowed_mimetypes and
self.min_size == self.min_size and
self.max_size == self.max_size
)