I need to save an uploaded file before super() method is called. It should be saved, because i use some external utils for converting a file to a needed internal format. The code below produce an error while uploading file '123':
OSError: [Errno 36] File name too long: '/var/www/prj/venv/converted/usermedia/-1/uploads/123_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_...'
It seems, that it tries to save it in super().save() twice with the same name in an infinite loop. Also, it creates all these files.
def save(self, **kwargs):
uid = kwargs.pop('uid', -1)
for field in self._meta.fields:
if hasattr(field, 'upload_to'):
field.upload_to = '%s/uploads' % uid
if self.translation_file:
self.translation_file.save(self.translation_file.name, self.translation_file)
#self.mimetype = self.guess_mimetype()
#self.handle_file(self.translation_file.path)
super(Resource, self).save(**kwargs)
EDIT:
Here is inelegant way i wanted to get around (it will double call save() method):
def save(self, *args, **kwargs):
uid = kwargs.pop('uid', -1)
for field in self._meta.fields:
if hasattr(field, 'upload_to'):
field.upload_to = '%s/uploads' % uid
super(Resource, self).save(*args, **kwargs)
if self.__orig_translation_file != self.translation_file:
self.update_mimetype()
super(Resource, self).save(*args, **kwargs)
You got an infinite loop in your first example, thats right.
Calling self.translation_file.save(self.translation_file.name, self.translation_file) will save the uploaded file to disk and call the Resources class save method again because the methods save paramter defaults to true (have a look here https://docs.djangoproject.com/en/dev/ref/files/file/#additional-methods-on-files-attached-to-objects) as well as your custom FileField does anyway.
Calling it like this (just add save=False) is more likely to work:
self.translation_file.save(self.translation_file.name, self.translation_file, save = False)
I hope this points into the right direction.
Related
I’m trying to track which user last updated an object:
Class MyModel(models.Models):
updater_id = models.ForeignKey(Users)
…other fields…
However, I can’t figure out how to require that updater_id be included each time my object is saved.
First I tried overriding save:
def save(self, updater_id, *args, **kwargs):
self.updater_id = updater_id
super(MyModel, self).save(*args, **kwargs)
I realized however that this will break anything native to Django (i.e. MyModel.objects.create())
Next I tried grabbing it from kwargs:
def save(self, *args, **kwargs):
try:
self.updater_id = kwargs[“update_fields”][“updater_id”]
except:
raise Error
super(MyModel, self).save(*args, **kwargs)
updated_fields, however is only rarely used (i.e. default behavior for object.save() is to not use updated_fields)
Then I tried adding a flag:
Class MyModel(models.Models):
updater_id = models.ForeignKey(Users)
updater_updated_flag = models.BooleanField(default=True, editable=False) # default=True so first create/save works
def update_updater_id(self, user_id):
self.updater_id = user_id
self.updater_updated_flag = True
def save(self, *args, **kwargs):
if self.updater_updated_flag:
self.updater_updated_flag = False
else:
raise Exception("Must call update_updater_id() before calling save on this object")
super(MyModel, self).save(*args, **kwargs)
I figured I would ensure the updater_id is updated before save can be called. I realized, however, that this will break create_or_update(), unless I want to wrap it in a try catch and, if the object exists, get the object and call update_updater_id, which already defeats the whole purpose.
Finally I tried django-model-util’s fieldtracker:
def save(self, *args, **kwargs):
if not self.tracker.has_changed(“updater_id”):
raise Error
super(MyModel, self).save(*args, **kwargs)
Sometimes, however the value will not change (i.e. same user edited it again).
And now I’m out of ideas.
I ended up using django-currentuser. Not a universal solution, but it got the job done for me.
I have Book model. It has some fields like title, year publish and etc. Also, i have overrided save() method. When new books is adding, it checks if book exsists, it creating new directory with name self.title in MEDIA_ROOT path.
def save(self, *args, **kwargs):
book_dir = os.path.join(MEDIA_ROOT, self.title)
# check that at least one file is loading
if all([self.pdf, self.fb2, self.epub]):
raise ValidationError("At least 1 file should be uploaded!")
# create book's directory if it not exists
if os.path.exists(book_dir):
raise ValidationError("This book is already exists!")
else:
os.mkdir(book_dir)
# rename and edit storage location of books to book_dir
for field in [self.image, self.pdf, self.fb2, self.epub]:
field.storage.location = book_dir
super().save(*args, **kwargs) # Call the "real" save() method.
Also i have overrided delete() method, that just remove directory of deleted book.
def delete(self, *args, **kwargs):
book_dir = os.path.join(MEDIA_ROOT, self.title)
rmtree(book_dir)
super().delete(*args, **kwargs) # Call the "real" delete() method.
delete() method works well if i delete only 1 book.
But, if i want to delete multiple files (all actions take place in the admin panel), only DB records got deleted.
So, i want to just catch this moment to remove directory of deleted book.
Looks like pre_delete signal could be useful here: https://docs.djangoproject.com/en/2.2/ref/signals/#pre-delete
I want to perform some action (sending email) after updating an (already existing) object.
In order to do it I need to compare values of the object before and after saving and only if something specific has changed - do that action. From reading other related question I understood that I can only do it in pre-save signal, since I can't get the old version inside 'post-save', but - what if there will be some issue with saving and the item will not be saved? I don't want to perform the action in that case. So I thought about implementing it somehow by overriding the view save, but I'm not sure that's the correct way to do it. What do you think?
This is implementing in pre-save:
#staticmethod
#receiver(pre_save, sender=Item)
# check if there is change that requires sending email notification.
def send_email_notification_if_needed(sender, instance, raw, *args, **kwargs):
try:
# if item just created - don't do anything
pre_save_item_obj = sender.objects.get(pk=instance.pk)
except sender.DoesNotExist:
pass # Object is new, so field hasn't technically changed
else:
# check if state changed to Void
if pre_save_item_obj.state_id != VOID and instance.state_id == VOID:
content = {"item_name": instance.title, "item_description": instance.description}
EmailNotificationService().send_email("item_update"
["myemail#gmail.com"], str(instance.container.id) +
str(instance.id) + " changed to Void",
content)
There is nothing wrong with overriding the model's save method. After all, this is where you have all the information that you need:
class X(models.Model):
def save(self, *args, **kwargs):
pre_obj = X.objects.filter(pk=self.pk).first()
super(X, self).save(*args, **kwargs)
# no exception from save
if pre_obj and pre_obj.state_id != VOID and self.state_id == VOID:
# send mail
I have the following code, models.py:
class Location(models.Model):
image_file = models.ImageField(upload_to=upload_to_location, null=True, blank=True)
name = models.CharField(max_length=200)
which calls the upload_to function where the image is being renamed. Then my save method
def save(self, *args, **kwargs):
try:
this = Location.objects.get(id=self.id)
if this.image_file:
os.remove(this.image_file.path)
except ObjectDoesNotExist:
pass
super(Location, self).save(*args, **kwargs)
try:
this = Location.objects.get(id=self.id)
if this.image_file:
resize(this.image_file.path)
except ObjectDoesNotExist:
pass
def upload_to_location(instance, filename):
blocks = filename.split('.')
ext = blocks[-1]
filename = "%s.%s" % (instance.name.replace(" ", "-"), ext)
return filename
checks if a previously uploaded file exists and if so, it deletes it. And then after is being saved, it checks again if the file exists and then calls the resize function. That works, if I upload a file and click save. However, if I only change the name of the Location, the os.remove function is called, deletes my file and then the resize function trhows an error.
Therefore I only want to call the os.remove function when the user uploads a upload_to will be called.
So I tried a couple of things but couldn't get it done. I also tried pre_save and post_save signales. Hereby, I had a problem of getting the path of the image:
#receiver(pre_save, sender=Company)
def my_function2(sender, instance, **kwargs):
print instance.path #didn't work
print os.path.realpath(instance) #didn't work
Does anyone know how I could solve this either with pre_save or something else?
If you just want to call os.remove only when upload_to is invoked, why not move that code inside upload_to_location function?
def upload_to_location(self, filename):
try:
this = Location.objects.get(id=self.id)
if this.image_file:
os.remove(this.image_file.path)
except ObjectDoesNotExist:
pass
# do something else...
This way, when upload_to is called, os.remove will be called.
Extras
Instead of calling os.remove to delete associated file, you can delete the file by calling that file field's delete() method.
if this.image_file:
this.image_file.delete(save=True)
save=True saves the this instance after deleting the file. If you don't want that, pass save=False. Default is True.
And in the function my_function2 where you're listening to signals, it should be:
instance.image_file.path
I'm trying to get some information from a file before the Model is saved to the database.
So basically what i'm doing it is overwriting method save like follow:
class Media(models.Model):
file = models.FileField(upload_to='audio/')
def save(self, *args, **kwargs):
if not self.id:
print self.file.path
super(Media, self).save(*args, **kwargs)
But when I print attribute self.file.path is does not include "audio/" subdirectory.
Instead of this '/Users/me/Dropbox/Public/music/audio/myfile.ext'
I'm getting
'/Users/me/Dropbox/Public/music/myfile.ext'
The file is located where it suppose to be. In
'/Users/me/Dropbox/Public/music/audio/myfile.ext'
My
MEDIA_ROOT = '/Users/me/Dropbox/Public/music'
What I've missed?
UPDATE:
Looks like it add 'audio/' to the path after it save the model.