[英]Django admin file upload with current model id
I'm trying to create a simple photo gallery with the default Django admin.我正在尝试使用默认的 Django 管理员创建一个简单的照片库。 I'd like to save a sample photo for each gallery, but I don't want to keep the filname.
我想为每个画廊保存一张样本照片,但我不想保留电影名。 Instead of the filename, I'd like to save the id of the model (
N.jpg
).我不想保存文件名,而是保存 model (
N.jpg
) 的 ID。 But the first time I want to save the object the id does not exist.但是我第一次想保存的object这个id不存在。 How could I know the next auto increment in the model, or somehow save the model data before the upload with
super.save
and after upload the file when self.id
is exists?我怎么知道 model 中的下一个自动增量,或者在使用
super.save
上传之前以及在存在self.id
的情况下上传文件之后以某种方式保存 model 数据? Is there a cool solution?有很酷的解决方案吗?
Something like this:像这样:
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)
And maybe store the filename in a different field.并且可能将文件名存储在不同的字段中。
I ran into the same problem.我遇到了同样的问题。 Okm's answer sent me on the right path but it seems to me it is possible to get the same functionality by just overriding the
save()
method of your Model. 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)
This definitely saves the information correctly.这肯定会正确保存信息。
The image file gets saved before Gallery instance.图像文件在 Gallery 实例之前保存。 So you have to split the saving to two phases by using signals w/ Gallery instance itself carrying the state:
因此,您必须使用带有状态的带有 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)
The upload_path_handler looks like 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)
I suggest using ImageField instead of FileField for type-checking if the field is for image uploading only.如果该字段仅用于图像上传,我建议使用 ImageField 而不是 FileField 进行类型检查。 Also, you may want to normalize filename extension (which is unnecessary because of the mimetype) like
此外,您可能希望标准化文件扩展名(由于 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
For Django 2.2, follow the below code.对于 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)
Add the above code snippet to your 'class Gallery'.将上面的代码片段添加到您的“类库”中。
PS: This will work for DRF as well when you save via views.py.
PS:当您通过 views.py 保存时,这也适用于 DRF。 Note that second if (condition) is required for DRF.
请注意,DRF 需要第二个 if (condition)。
Using Louis's answer , here is recipe to process all FileField
in the model:使用Louis 的回答,这里是处理模型中所有
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)
In django 1.7, the proposed solutions didn't seem to work for me, so I wrote my FileField subclasses along with a storage subclass which removes the old files.在 django 1.7 中,建议的解决方案似乎对我不起作用,因此我编写了 FileField 子类以及删除旧文件的存储子类。
Storage:贮存:
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
and my upload_to callables look like this:我的 upload_to 可调用文件如下所示:
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))
you can create a model instance by passing cleaned data from your form as **kwargs to django model i did it that way & its much easier than anything else您可以创建一个 model 实例,方法是将表单中的清理数据作为 **kwargs 传递给 django model 我就是这样做的,它比其他任何方法都容易得多
in your views post method add this (this code is from my project not adapted to this question)在你的观点后方法中添加这个(这段代码来自我的项目,不适应这个问题)
pk = request.session['_auth_user_id']
user_obj = User.objects.get(pk=pk)
lab_form_instance = lab_form(request.POST,request.FILES)
lab_form_instance.save(commit=False)
# here you can put the form.is_valid() statement
lab_form_instance.cleaned_data['owner'] =user_obj # here iam adding additional needed data for the model
obj = lab(**lab_form_instance.cleaned_data)
obj.save()
*django==4.1 *django==4.1
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.