I have a model which looks like this.
import uuid
from django.db import models
class TemplateId(models.Model):
id = models.SmallAutoField(primary_key=True, serialize=False, verbose_name='ID')
template_name = models.CharField(max_length=255, default="")
template_id = models.UUIDField(max_length=255, default=uuid.UUID, unique=True)
def __str__(self):
return str(self.template_name)
class Meta:
ordering = ('-id',)
I have another function/code where I'm calling an API to fetch the template_name and template_id and store it in dB. But every time when I get a response from the API, I want to override the the whole table (everytime deleting old record and then adding the new records)
currently I'm doing this:
def fetch_template_id():
api_response = # calling the API
for template_id in api_response:
obj = TemplateId(template_id=template_id["id"], template_name=template_id["name"])
obj.save()
In order to override, I tried overriding the save method in my TemplateId model as below but it didn't work
def save(self, *args, **kwargs):
super(TemplateId, self).save(*args, **kwargs)
Since the data gets saved in the model fields by getting the API response, next time when same data is received from the API response, it throws duplicate data error in the dB.
How do I override all the dB fields with each API call?
If the objects exists, update it. If it doesn't, create it.
def fetch_template_id():
api_response = # calling the API
for template_id in api_response:
try:
obj = TemplateId.objects.get(template_id=template_id["id"])
obj.template_name=template_id["name"]
except:
obj = TemplateId(template_id=template_id["id"], template_name=template_id["name"])
obj.save()
If by override you mean update the template_name of a particular template id, you need to change:
obj = TemplateId(template_id=template_id["id"], template_name=template_id["name"])
obj.save()
to
obj = TemplateId.objects.get(id=template_id["id"])
obj.template_name = template_id["name"]
obj.save()
Because in your case you are searching for a particular entry with the 2 conditions, but do not change anything when saving it. save method can stay as is, not need to override it
I'm using boto3 to upload files to S3 and save their path in the FileField.
class SomeFile(models.Model):
file = models.FileField(upload_to='some_folder', max_length=400, blank=True, null=True)
For the above model the following code works to create a record.
ff = SomeFile(file='file path in S3')
ff.full_clean()
ff.save()
Now, when I use ModelSerializer to do the same.
class SomeFileSerializer(serializers.ModelSerializer):
class Meta:
model = SomeFile
fields = ('file')
I get this error after running the code below
rest_framework.exceptions.ValidationError: {'file': [ErrorDetail(string='The submitted data was not a file. Check the encoding type on the form.', code='invalid')]}
serializer = SomeFileSerializer(data={'file': 'file path to S3'})
serializer.is_valid(raise_exception=True)
I need help in setting up the serializer to accept file path without actually having the file.
I was really in the same situation, and it was hard to find the solution on the web.
We have two options to solve this problem.
1. Passing data directly to save method
Read action: use serializer's read only ImageField
Write action: pass kwargs to save method
serializers.py
from rest_framework import serializers
class SomeFileSerializer(serializers.ModelSerializer):
file = serializers.ImageField(read_only=True)
class Meta:
model = SomeFile
fields = ('file')
views.py
serializer = SomeFileSerializer(data={'file': 'file path to S3'})
serializer.is_valid(raise_exception=True)
# for put method
serializer.save(file=request.data.get('file'))
# for patch method (if partial=True in serializer)
if request.data.get('file'):
serializer.save(file=request.data.get('file'))
else:
serializer.save()
2. Using CharField instead of ImageField
Read action: override to_representation function to response absolute url
Write action: use CharField to avoid ImageField's validation and action
serializers.py
from rest_framework import serializers
class SomeFileSerializer(serializers.ModelSerializer):
file = serializers.CharField(max_length=400)
class Meta:
model = SomeFile
fields = ('file')
def to_representation(self, instance):
representation = super().to_representation(instance)
if instance.file:
# update filename to response absolute url
representation['file'] = instance.file_absolute_url
return representation
models.py
class SomeFile(models.Model):
file = models.FileField(upload_to='some_folder', max_length=400, blank=True, null=True)
#property
def file_absolute_url(self):
return self.file.url if self.file else None
Although I chose the 2nd solution because of drf_spectacular for documentation, the 1st solution would be easy to implement.
I have model form JobPosting with a custom field location_query which I use to generate data for a model field location. In doing this I need to create an Address object for the location field and save it to the database. I believe the correct time to save this object is within an overloaded save method of JobPosting.
While the new Address object is created and saved, it does not get saved as the value of the JobPosting's location field, and I'm not sure why.
Below is a simplified example:
class Address(Model):
pass
class JobPosting(Model):
location = ForeignKey(Address, blank=True, null=True)
class JobPostingForm(forms.ModelForm):
location_query = forms.CharField(max_length=256)
class Meta:
model = JobPosting
fields = (
'location_query',
'location', # hidden field
}
def clean_location(self):
data = self.data.get('location_query')
addr = Address()
# do some stuff here to dump data into addr
return addr
def save(self, commit=True, *args, **kwargs):
if self.instance.location and not self.instance.location.uuid :
self.instance.location.save()
instance = super(JobPostingForm, self).save(commit=commit, *args, **kwargs)
return instance
The obnoxious thing is that the code above results in the JobPosting being saved with location as None, but if I save the address in the clean_location function it works correctly. Obviously I don't want to save a database object in a clean function, but I'm pulling my hair out trying to figure out why this is.
The solution is to set the location_id manually. I had been assuming since I'd seen it happen in all other cases, that the <field>_id member was automatically filled in on save, that it would happen here. I'm not sure why it isn't but adding instance.location_id = instance.location.uuid after saving the location did the trick.
I also moved the instance.location setting code to after the super save (as suggested by #SÅ‚awek) as self.instance will not always exist if it hasn't been passed in at form creation time.
The new code looks something like this:
def save(self, commit=True, *args, **kwargs):
instance = super(JobPostingForm, self).save(commit=commit, *args, **kwargs)
if instance.location and not instance.location.uuid :
instance.location.save()
instance.location_id = instance.location.uuid
instance.save()
return instance
So, I have a Django generic view:
class Foobaz(models.Model):
name = models.CharField(max_length=140)
organisation = models.ForeignKey(Organisation)
class FoobazForm(forms.ModelForm):
class Meta:
model = Foobaz
fields = ('name')
class FoobazCreate(CreateView):
form_class = FoobazForm
#login_required
def dispatch(self, *args, **kwargs):
return super(FoobazCreate, self).dispatch(*args, **kwargs)
What I'm trying to do is to take the organisation id from the URL:
/organisation/1/foobaz/create/
And add it back to the created object. I realise I can do this in CreateView.form_valid(), but from what I understand this is then completely unvalidated.
I've tried adding it to get_form_kwargs() but this does not expect the organisation kwarg as it is not in the included fields.
Ideally what I'd like to do is to add it to the instance of the form to validate it with the rest - ensuring it is a valid organisation, and that the user in question has the correct permissions to add a new foobaz to it.
I'm happy to just roll my own view if that is the best way of doing this, but I may just be simply missing a trick.
Thanks!
I thnik it would be better to include the organisation field and define it as hidden and readonly, this way django will validate it for you.
You can then override get_queryset method like this:
def get_queryset(self):
return Foobaz.objects.filter(
organisation__id=self.kwargs['organisation_id'])
where organisation_id is a keyword in url pattern.
You can do it overriding the View's get_kwargs() method and the Form's save() method. In the get_kwargs() I "inject" the organization_id into the initial data of the form, and in save() I retrieve the missing info with the supplied initial data:
In urls.py:
urlpatterns('',
#... Capture the organization_id
url(r'^/organisation/(?P<organization_id>\d+)/foobaz/create/',
FoobazCreate.as_view()),
#...
)
In views.py:
class FoobazCreate(CreateView):
# Override get_kwargs() so you can pass
# extra info to the form (via 'initial')
# ...(all your other code remains the same)
def get_form_kwargs(self):
# get CreateView kwargs
kw = super(CreateComment, self).get_form_kwargs()
# Add any kwargs you need:
kw['initial']['organiztion_id'] = self.kwargs['organization_id']
# Or, altenatively, pass any View kwarg to the Form:
# kw['initial'].update(self.kwargs)
return kw
In forms.py:
class FoobazForm(forms.ModelForm):
# Override save() so that you can add any
# missing field in the form to the model
# ...(Idem)
def save(self, commit=True):
org_id = self.initial['organization_id']
self.instance.organization = Organization.objects.get(pk=org_id)
return super(FoobazForm, self).save(commit=commit)
I am using 1.2.5 with a standard ImageField and using the built-in storage backend. Files upload fine but when I remove an entry from admin the actual file on the server does not delete.
You can receive the pre_delete or post_delete signal (see #toto_tico's comment below) and call the delete() method on the FileField object, thus (in models.py):
class MyModel(models.Model):
file = models.FileField()
...
# Receive the pre_delete signal and delete the file associated with the model instance.
from django.db.models.signals import pre_delete
from django.dispatch.dispatcher import receiver
#receiver(pre_delete, sender=MyModel)
def mymodel_delete(sender, instance, **kwargs):
# Pass false so FileField doesn't save the model.
instance.file.delete(False)
Try django-cleanup
pip install django-cleanup
settings.py
INSTALLED_APPS = (
...
'django_cleanup.apps.CleanupConfig',
)
Django 1.5 solution: I use post_delete for various reasons that are internal to my app.
from django.db.models.signals import post_delete
from django.dispatch import receiver
#receiver(post_delete, sender=Photo)
def photo_post_delete_handler(sender, **kwargs):
photo = kwargs['instance']
storage, path = photo.original_image.storage, photo.original_image.path
storage.delete(path)
I stuck this at the bottom of the models.py file.
the original_image field is the ImageField in my Photo model.
This code runs well on Django 1.4 also with the Admin panel.
class ImageModel(models.Model):
image = ImageField(...)
def delete(self, *args, **kwargs):
# You have to prepare what you need before delete the model
storage, path = self.image.storage, self.image.path
# Delete the model before the file
super(ImageModel, self).delete(*args, **kwargs)
# Delete the file after the model
storage.delete(path)
It's important to get the storage and the path before delete the model or the latter will persist void also if deleted.
You need to remove the actual file on both delete and update.
from django.db import models
class MyImageModel(models.Model):
image = models.ImageField(upload_to='images')
def remove_on_image_update(self):
try:
# is the object in the database yet?
obj = MyImageModel.objects.get(id=self.id)
except MyImageModel.DoesNotExist:
# object is not in db, nothing to worry about
return
# is the save due to an update of the actual image file?
if obj.image and self.image and obj.image != self.image:
# delete the old image file from the storage in favor of the new file
obj.image.delete()
def delete(self, *args, **kwargs):
# object is being removed from db, remove the file from storage first
self.image.delete()
return super(MyImageModel, self).delete(*args, **kwargs)
def save(self, *args, **kwargs):
# object is possibly being updated, if so, clean up.
self.remove_on_image_update()
return super(MyImageModel, self).save(*args, **kwargs)
You may consider using a pre_delete or post_delete signal:
https://docs.djangoproject.com/en/dev/topics/signals/
Of course, the same reasons that FileField automatic deletion was removed also apply here. If you delete a file that is referenced somewhere else you will have problems.
In my case this seemed appropriate because I had a dedicated File model to manage all of my files.
Note: For some reason post_delete doesn't seem to work right. The file got deleted, but the database record stayed, which is completely the opposite of what I would expect, even under error conditions. pre_delete works fine though.
Maybe it's a little late. But the easiest way for me is to use a post_save signal. Just to remember that signals are excecuted even during a QuerySet delete process, but the [model].delete() method is not excecuted during the QuerySet delete process, so it's not the best option to override it.
core/models.py:
from django.db import models
from django.db.models.signals import post_delete
from core.signals import delete_image_slide
SLIDE1_IMGS = 'slide1_imgs/'
class Slide1(models.Model):
title = models.CharField(max_length = 200)
description = models.CharField(max_length = 200)
image = models.ImageField(upload_to = SLIDE1_IMGS, null = True, blank = True)
video_embed = models.TextField(null = True, blank = True)
enabled = models.BooleanField(default = True)
"""---------------------------- SLIDE 1 -------------------------------------"""
post_delete.connect(delete_image_slide, Slide1)
"""--------------------------------------------------------------------------"""
core/signals.py
import os
def delete_image_slide(sender, **kwargs):
slide = kwargs.get('instance')
try:
os.remove(slide.image.path)
except:
pass
This functionality will be removed in Django 1.3 so I wouldn't rely on it.
You could override the delete method of the model in question to delete the file before removing the entry from the database completely.
Edit:
Here is a quick example.
class MyModel(models.Model):
self.somefile = models.FileField(...)
def delete(self, *args, **kwargs):
somefile.delete()
super(MyModel, self).delete(*args, **kwargs)
Using the post_delete is for sure the right way to go. Sometimes though things can go wrong, and files don't get deleted. There is of course the case that you have a bunch of old files that weren't deleted before post_delete was used. I created a function that deletes files for objects based on if the file the object references does not exist then delete object, if the file does not have an object, then also delete, also it can delete based on an "active" flag for an object.. Something I added to most of my models. You have to pass it the objects you want to check, the path to the objects files, the file field and a flag to delete inactive objects:
def cleanup_model_objects(m_objects, model_path, file_field='image', clear_inactive=False):
# PART 1 ------------------------- INVALID OBJECTS
#Creates photo_file list based on photo path, takes all files there
model_path_list = os.listdir(model_path)
#Gets photo image path for each photo object
model_files = list()
invalid_files = list()
valid_files = list()
for obj in m_objects:
exec("f = ntpath.basename(obj." + file_field + ".path)") # select the appropriate file/image field
model_files.append(f) # Checks for valid and invalid objects (using file path)
if f not in model_path_list:
invalid_files.append(f)
obj.delete()
else:
valid_files.append(f)
print "Total objects", len(model_files)
print "Valid objects:", len(valid_files)
print "Objects without file deleted:", len(invalid_files)
# PART 2 ------------------------- INVALID FILES
print "Files in model file path:", len(model_path_list)
#Checks for valid and invalid files
invalid_files = list()
valid_files = list()
for f in model_path_list:
if f not in model_files:
invalid_files.append(f)
else:
valid_files.append(f)
print "Valid files:", len(valid_files)
print "Files without model object to delete:", len(invalid_files)
for f in invalid_files:
os.unlink(os.path.join(model_path, f))
# PART 3 ------------------------- INACTIVE PHOTOS
if clear_inactive:
#inactive_photos = Photo.objects.filter(active=False)
inactive_objects = m_objects.filter(active=False)
print "Inactive Objects to Delete:", inactive_objects.count()
for obj in inactive_objects:
obj.delete()
print "Done cleaning model."
This is how you can use this:
photos = Photo.objects.all()
photos_path, tail = ntpath.split(photos[0].image.path) # Gets dir of photos path, this may be different for you
print "Photos -------------->"
cleanup_model_objects(photos, photos_path, file_field='image', clear_inactive=False) # image file is default
make sure you write "self" before the file. so example above should be
def delete(self, *args, **kwargs):
self.somefile.delete()
super(MyModel, self).delete(*args, **kwargs)
I've forgotten the "self" before my file and that didn't work as it was looking in the global namespace.
If you already have number of unused files in your project and want to delete them, you can use django utility django-unused-media
Django 2.x Solution:
There's no need to install any packages! It's very easy to handle in Django 2. I've tried following solution using Django 2 and SFTP Storage (however I think it would work with any storages)
First write a Custom Manager. So if you want to be able to delete files of a model by using objects methods, you must write and use a [Custom Manager][3] (for overriding delete() method of objects):
class CustomManager(models.Manager):
def delete(self):
for obj in self.get_queryset():
obj.delete()
Now you must delete image before deleting deleting the model itself and for assigning the CustomManager to the model, you must initial objects inside your model:
class MyModel(models.Model):
image = models.ImageField(upload_to='/pictures/', blank=True)
objects = CustomManager() # add CustomManager to model
def delete(self, using=None, keep_parents=False):
objects = CustomManager() # just add this line of code inside of your model
def delete(self, using=None, keep_parents=False):
self.image.storage.delete(self.song.name)
super().delete()
I may have a special case since I am using the upload_to option on my file field with dynamic directory names but the solution I found was to use os.rmdir.
In models:
import os
...
class Some_Model(models.Model):
save_path = models.CharField(max_length=50)
...
def delete(self, *args,**kwargs):
os.rmdir(os.path.join(settings.MEDIA_ROOT, self.save_path)
super(Some_Model,self).delete(*args, **kwargs)