我有一个项目,我需要在其中创建一个 Django 命令来一次上传多个图像。我有一个定义文件的基类 Image
,以及将单词和描述与图标相关联的子类 Icon
。
我的问题是在调用 Icon.objects.create()
时出现错误。它适用于单个上传上下文,但不适用于批量上传器。我该怎么办?
models.py
class Image(TimestampedModel):
"""
Abstract model containing an image, a hash of that image, and operation methods.
"""
class Meta:
"""
The metaclass defining its parent as abstract.
"""
abstract = True
# Static variables
RELATIVE_PATH = 'img'
BLOCK_SIZE = 2**16
# Attributes
image = models.ImageField(
blank=True, null=True, default=None, upload_to=RELATIVE_PATH)
_hash = models.BinaryField(
_('MD5 hash'), editable=False, null=True, default=None, max_length=16)
class Icon(Image):
"""
Image file associated with a word, a descriptor, and (for verbs) tense.
"""
# Static variables
TENSE_CHOICES = [
(None, 0),
('1', 1), # present
('c', 2), # present participle
('p', 3), # past
('pp', 4), # past participle
]
BLOCK_SIZE = 2**12
# Attributes
word = models.CharField(max_length=32)
descriptor = models.CharField(blank=True, null=True, max_length=32)
tense = models.PositiveSmallIntegerField(
blank=True, null=True, default=None, choices=TENSE_CHOICES)
is_approved = models.BooleanField(default=False)
bulk_uploader.py
class BulkUploader:
"""
Class defining a utility method for uploading multiple icons.
"""
@classmethod
def save(cls, filepath, word, descriptor, tense=None):
filename = filepath.split('/')[-1]
with open(filepath, 'rb') as f:
bytes_str = b''
for buffer in iter(partial(f.read, Icon.BLOCK_SIZE), b''):
bytes_str += buffer
breakpoint()
icon = Icon.objects.create(
word=word,
descriptor=descriptor,
tense=tense)
icon.image.save(filename, ContentFile(bytes_str), save=False)
icon.save()
调试输出
> /home/matt/Repositories/my_project/utils/bulk_uploader.py(28)save()
-> icon = Icon.objects.create(
(Pdb) n
> /home/matt/Repositories/my_project/utils/bulk_uploader.py(29)save()
-> word=word,
(Pdb)
> /home/matt/Repositories/my_project/utils/bulk_uploader.py(30)save()
-> descriptor=descriptor,
(Pdb)
> /home/matt/Repositories/my_project/utils/bulk_uploader.py(31)save()
-> tense=tense)
(Pdb)
> /home/matt/Repositories/my_project/utils/bulk_uploader.py(28)save()
-> icon = Icon.objects.create(
(Pdb)
TypeError: join() argument must be str, bytes, or os.PathLike object, not 'NoneType'
> /home/matt/Repositories/my_project/utils/bulk_uploader.py(28)save()
-> icon = Icon.objects.create(
(Pdb)
编辑
upload.py
from django.core.management.base import BaseCommand, CommandError
from api.dictionary.utils.bulk_uploader import BulkUploader
class Command(BaseCommand):
help = 'Uploads icons in a given directory based on its part of speech format.'
def add_arguments(self, parser):
parser.add_argument('part_speech', type=str)
parser.add_argument('directory', type=str)
def handle(self, *args, **options):
try:
BulkUploader.upload(options['part_speech'], options['directory'])
except Exception as exc:
raise CommandError(exc)
self.stdout.write(self.style.SUCCESS('Upload successful.'))
答案 0 :(得分:0)
你在使用管理命令吗?你连接到数据库了吗?。如果你在 Django 之外运行它,请使用 django.setup()。尝试将脚本更改为管理命令并使用 python manage.py。
答案 1 :(得分:0)
在您的模型上,您有以下字段:
_hash = models.BinaryField(
_('MD5 hash'), editable=False, null=True, default=None, max_length=16
)
这里需要注意的一点是 BinaryField
是一个 基于字符串的 字段,documentation 明确建议不要在基于字符串的字段上使用 null=True
.此外,您设置的 default=None
确实是多余的(如果您不指定它,Django 会将 None
作为默认值传递)并且实际上是破坏您的代码的原因。可能在内部调用了一些代码,该代码试图在此字段值上调用 join
,因此给您一个错误。通常,该字段的默认值是一个空字节 b''
,但您已将其设置为 None
。事实上,您还在其他字段上设置了 default=None
,我建议您将其删除,因为它没有用。
此外,您已经设置了 editable=False
(这也是 BinaryField
这个 kwarg 的默认值,所以这是多余的)这一事实让我感到困惑,因为您没有为此字段设置值在创建实例时,这意味着您以后将无法设置它的值,否则会使该字段变得无用。
考虑到所有这些,您应该将字段定义更改为:
_hash = models.BinaryField(_('MD5 hash'), editable=True, max_length=16)
答案 2 :(得分:0)
我发现了这个问题。我有一个 post_save
信号接收器在这条线上被触发:
icon = Icon.objects.create(word=word, descriptor=descriptor, tense=tense)
所以我的解决方案是断开连接然后重新连接到信号。
post_save.disconnect(Image.post_save, sender=Icon, dispatch_uid='0')
icon = Icon.objects.create(word=word, descriptor=descriptor, tense=tense)
icon.image.save(filename, ContentFile(bytes_str), save=False)
post_save.connect(Image.post_save, sender=Icon, dispatch_uid='0')
icon.save()