I am building an app in django (Django==3.1.14).
the app should allow the user to upload zip files (in particular, they are shapefiles).
I want django to upload and manage these files by storing them inside a model, having a FileField t host the file.
I want each file to be uploaded at a specific path, like this:
MEDIA_ROOT/shp/<timestamp>/<file_name>.zip
NOTE: timestamp has seconds precision.
my model has a field shp_file_folder_path
that must have value equal to the path of the folder in which <file_name>.zip
is stored, so
MEDIA_ROOT/shp/<timestamp>
Now, I have written the following model
# Create your models here.
class Shp(models.Model):
name = models.CharField(max_length=50)
description = models.CharField(max_length=1000, blank=True)
shp_file = models.FileField(upload_to="shp/%Y%m%d_%H%M%S")
# this is a file, but in postgres is represented as path
uploaded_date = models.DateField(default=datetime.date.today, blank=True)
shp_file_folder_path = models.CharField(default='undefined', max_length=1000, blank=True)
# this must be nor visible nor editable by admins.
# this default value is not required to be correct because it will be overwritten by the save method
def __str__(self):
return self.name
def clean(self):
super().clean()
if not self.shp_file.name.endswith('.zip'):
raise ValidationError('The file must have .zip extension.')
def save(self, *args, **kwargs):
# update value of shp_file_folder_path
print("Name:", self.shp_file.name)
print("Path:", self.shp_file.path)
print("URL:", self.shp_file.url)
print("Size:", self.shp_file.size)
print("File:", self.shp_file.file)
self.shp_file_folder_path = os.path.dirname( self.shp_file.path )
super().save(*args, **kwargs)
and in my settings I have
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
in my admin.py I have
class ShpAdmin(admin.ModelAdmin):
fields = ('name', 'description', 'shp_file', 'uploaded_date')
readonly_fields = ('shp_file_folder_path',)
admin.site.register(Shp, ShpAdmin)
This code allows me to upload files in directories like the ones I want,
but by inspecting the table for model shp
(shp_shp
) via psql
, I see that the value stored inside field shp_file_folder_path
is .../myapp/media
.
This happens because self.shp_file.path
is .../myapp/media/myfile.zip
; actually the block of prints shows
Name: C_Jamoat.zip
Path: .../myapp/media/C_Jamoat.zip
URL: /media/C_Jamoat.zip
Size: 4048647
File: C_Jamoat.zip
I expected it to be .../myapp/media/shp/<timestamp>/myfile.zip
, where the file was actually saved (I have checked on my OS).
Why is not it so?
Surprisigly, by inspecting again shp_shp via sql, the field shp_file
seems to have somehow the information on the correct path.
select shp_file from shp_shp where id=89;
shp_file
---------------------------------
shp/20240628195348/C_Jamoat.zip
but then, how it is possible that no attributes of instance.shp_file
have information on the right path ?
the value of self.shp_file.path
and of the other attributes of self.shp_file
were not correctly evalued because these are evalued with consistency only when the instance has been saved.
So the solution here is to save the object twice:
1 - the first time to allow django to update automatically the attributes of self.shp_file
2 - the second time to update the value of self.shp_file_folder_path
according to the correctly updated value of self.shp_file.path
solved by changing
def save(self, *args, **kwargs):
# update value of shp_file_folder_path
print("Name:", self.shp_file.name)
print("Path:", self.shp_file.path)
print("URL:", self.shp_file.url)
print("Size:", self.shp_file.size)
print("File:", self.shp_file.file)
self.shp_file_folder_path = os.path.dirname( self.shp_file.path )
super().save(*args, **kwargs)
to
def save(self, *args, **kwargs):
self.shp_file_folder_path = os.path.dirname( self.shp_file.path )
# call the save method, in order to allow django to update the attributes values of self.shp_file
# update value of shp_file_folder_path
print("Name:", self.shp_file.name)
print("Path:", self.shp_file.path)
print("URL:", self.shp_file.url)
print("Size:", self.shp_file.size)
print("File:", self.shp_file.file)
self.shp_file_folder_path = os.path.dirname( self.shp_file.path )
super().save(*args, **kwargs)