djangodjango-modelsdjango-file-uploadfilefield

why does self.<FileField_field>.path of an instance of a django model does not match the path where the file was actually uploaded


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 ?


Solution

  • 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)