我正在尝试使用默认的Django管理员创建一个简单的照片库。我想为每个图库保存一张示例照片,但我不想保留该名称。我想保存模型的id(N.jpg
),而不是文件名。但是我第一次想要保存对象时id不存在。我怎么知道模型中的下一个自动增量,或者在上传super.save
之前以某种方式保存模型数据,并在self.id
存在后上传文件?有一个很酷的解决方案吗?
这样的事情:
def upload_path_handler(instance, filename):
ext = filename extension
return "site_media/images/gallery/{id}.{ext}".format(id=instance.nextincrement, ext=ext)
class Gallery(models.Model):
name = models.TextField()
image = models.FileField(upload_to=upload_path_handler)
也许可以将文件名存储在不同的字段中。
答案 0 :(得分:35)
我遇到了同样的问题。 Okm的回答让我走上了正确的道路,但在我看来,通过覆盖save()
方法可以获得相同的功能。
def save(self, *args, **kwargs):
if self.pk is None:
saved_image = self.image
self.image = None
super(Material, self).save(*args, **kwargs)
self.image = saved_image
super(Material, self).save(*args, **kwargs)
这肯定会正确保存信息。
答案 1 :(得分:10)
图像文件在Gallery实例之前保存。因此,您必须使用带有状态的图库实例本身的信号将保存分为两个阶段:
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
_UNSAVED_FILEFIELD = 'unsaved_filefield'
@receiver(pre_save, sender=Image)
def skip_saving_file(sender, instance, **kwargs):
if not instance.pk and not hasattr(instance, _UNSAVED_FILEFIELD):
setattr(instance, _UNSAVED_FILEFIELD, instance.image)
instance.image = None
@receiver(post_save, sender=Image)
def save_file(sender, instance, created, **kwargs):
if created and hasattr(instance, _UNSAVED_FILEFIELD):
instance.image = getattr(instance, _UNSAVED_FILEFIELD)
instance.save()
# delete it if you feel uncomfortable...
# instance.__dict__.pop(_UNSAVED_FILEFIELD)
upload_path_handler看起来像
def upload_path_handler(instance, filename):
import os.path
fn, ext = os.path.splitext(filename)
return "site_media/images/gallery/{id}{ext}".format(id=instance.pk, ext=ext)
如果该字段仅用于图片上传,我建议使用ImageField而不是FileField进行类型检查。此外,您可能希望规范化文件扩展名(由于mimetype而不需要),如
def normalize_ext(image_field):
try:
from PIL import Image
except ImportError:
import Image
ext = Image.open(image_field).format
if hasattr(image_field, 'seek') and callable(image_field.seek):
image_field.seek(0)
ext = ext.lower()
if ext == 'jpeg':
ext = 'jpg'
return '.' + ext
答案 2 :(得分:0)
在django 1.7中,提出的解决方案似乎对我没用,所以我编写了我的FileField子类以及一个删除旧文件的存储子类。
存储
class OverwriteFileSystemStorage(FileSystemStorage):
def _save(self, name, content):
self.delete(name)
return super()._save(name, content)
def get_available_name(self, name):
return name
def delete(self, name):
super().delete(name)
last_dir = os.path.dirname(self.path(name))
while True:
try:
os.rmdir(last_dir)
except OSError as e:
if e.errno in {errno.ENOTEMPTY, errno.ENOENT}:
break
raise e
last_dir = os.path.dirname(last_dir)
的FileField:
def tweak_field_save(cls, field):
field_defined_in_this_class = field.name in cls.__dict__ and field.name not in cls.__bases__[0].__dict__
if field_defined_in_this_class:
orig_save = cls.save
if orig_save and callable(orig_save):
assert isinstance(field.storage, OverwriteFileSystemStorage), "Using other storage than '{0}' may cause unexpected behavior.".format(OverwriteFileSystemStorage.__name__)
def save(self, *args, **kwargs):
if self.pk is None:
orig_save(self, *args, **kwargs)
field_file = getattr(self, field.name)
if field_file:
old_path = field_file.path
new_filename = field.generate_filename(self, os.path.basename(old_path))
new_path = field.storage.path(new_filename)
os.makedirs(os.path.dirname(new_path), exist_ok=True)
os.rename(old_path, new_path)
setattr(self, field.name, new_filename)
# for next save
if len(args) > 0:
args = tuple(v if k >= 2 else False for k, v in enumerate(args))
kwargs['force_insert'] = False
kwargs['force_update'] = False
orig_save(self, *args, **kwargs)
cls.save = save
def tweak_field_class(orig_cls):
orig_init = orig_cls.__init__
def __init__(self, *args, **kwargs):
if 'storage' not in kwargs:
kwargs['storage'] = OverwriteFileSystemStorage()
if orig_init and callable(orig_init):
orig_init(self, *args, **kwargs)
orig_cls.__init__ = __init__
orig_contribute_to_class = orig_cls.contribute_to_class
def contribute_to_class(self, cls, name):
if orig_contribute_to_class and callable(orig_contribute_to_class):
orig_contribute_to_class(self, cls, name)
tweak_field_save(cls, self)
orig_cls.contribute_to_class = contribute_to_class
return orig_cls
def tweak_file_class(orig_cls):
"""
Overriding FieldFile.save method to remove the old associated file.
I'm doing the same thing in OverwriteFileSystemStorage, but it works just when the names match.
I probably want to preserve both methods if anyone calls Storage.save.
"""
orig_save = orig_cls.save
def new_save(self, name, content, save=True):
self.delete(save=False)
if orig_save and callable(orig_save):
orig_save(self, name, content, save=save)
new_save.__name__ = 'save'
orig_cls.save = new_save
return orig_cls
@tweak_file_class
class OverwriteFieldFile(models.FileField.attr_class):
pass
@tweak_file_class
class OverwriteImageFieldFile(models.ImageField.attr_class):
pass
@tweak_field_class
class RenamedFileField(models.FileField):
attr_class = OverwriteFieldFile
@tweak_field_class
class RenamedImageField(models.ImageField):
attr_class = OverwriteImageFieldFile
我的upload_to callable看起来像这样:
def user_image_path(instance, filename):
name, ext = 'image', os.path.splitext(filename)[1]
if instance.pk is not None:
return os.path.join('users', os.path.join(str(instance.pk), name + ext))
return os.path.join('users', '{0}_{1}{2}'.format(uuid1(), name, ext))
答案 3 :(得分:0)
使用Louis's answer,这是处理模型中所有FileField
的方法:
class MyModel(models.Model):
file_field = models.FileField(upload_to=upload_to, blank=True, null=True)
def save(self, *args, **kwargs):
if self.id is None:
saved = []
for f in self.__class__._meta.get_fields():
if isinstance(f, models.FileField):
saved.append((f.name, getattr(self, f.name)))
setattr(self, f.name, None)
super(self.__class__, self).save(*args, **kwargs)
for name, val in saved:
setattr(self, name, val)
super(self.__class__, self).save(*args, **kwargs)
答案 4 :(得分:0)
对于Django 2.2,请遵循以下代码。
def save(self, *args, **kwargs):
if self.pk is None:
saved_image = self.image
self.image = None
super(Gallery, self).save(*args, **kwargs)
self.image = saved_image
if 'force_insert' in kwargs:
kwargs.pop('force_insert')
super(Gallery, self).save(*args, **kwargs)
将上述代码段添加到“类库”中。
P.S .:当您通过views.py保存时,这也适用于DRF。 请注意,DRF需要第二个if(条件)。