Почему путь self.filefield_field.path экземпляра модели django не совпадает с путем, по которому был загружен файл

Я создаю приложение на django (Django==3.1.14).

Приложение должно позволять пользователю загружать zip-файлы (в частности, это shapefiles).

Я хочу, чтобы django загружал и управлял этими файлами, храня их в модели, имеющей поле FileField для размещения файла.

Я хочу, чтобы каждый файл загружался по определенному пути, например, так:

MEDIA_ROOT/shp/<timestamp>/<file_name>.zip

ПРИМЕЧАНИЕ: временная метка имеет точность в несколько секунд.

в моей модели есть поле shp_file_folder_path, значение которого должно быть равно пути к папке, в которой хранится <file_name>.zip, поэтому

MEDIA_ROOT/shp/<timestamp>

Теперь я написал следующую модель

def generate_current_timestamp():
    return datetime.datetime.now().strftime("%Y%m%d%H%M%S")

def generate_uploaded_shp_file_relpath():

    UPLOADED_SHP_FILES_DIR = 'shp'
    uploaded_shp_files_relpath_from_media_root = UPLOADED_SHP_FILES_DIR

    timestamp = generate_current_timestamp()

    current_file_subfolder = timestamp

    # relative path of the folder which will contain the uploaded file
    uploaded_shp_file_relpath = os.path.join(
        uploaded_shp_files_relpath_from_media_root,  
        current_file_subfolder
        )

    return uploaded_shp_file_relpath


# 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=generate_uploaded_shp_file_relpath()) 
    # 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)

и в настройках у меня есть

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

в моем файле admin.py у меня есть

class ShpAdmin(admin.ModelAdmin):
    fields = ('name', 'description', 'shp_file', 'uploaded_date') 
    readonly_fields = ('shp_file_folder_path',)

admin.site.register(Shp, ShpAdmin)

Этот код позволяет мне загружать файлы в директории, как я хочу,
но, просматривая таблицу для модели shp (shp_shp) через psql, я вижу, что значение, хранящееся в поле shp_file_folder_path, равно .../myapp/media.

Это происходит потому, что self.shp_file.path является .../myapp/media/myfile.zip; на самом деле блок отпечатков показывает

Name: C_Jamoat.zip
Path: .../myapp/media/C_Jamoat.zip
URL: /media/C_Jamoat.zip
Size: 4048647
File: C_Jamoat.zip

Я ожидал, что это будет .../myapp/media/shp/<timestamp>/myfile.zip, где файл действительно был сохранен (я проверил на своей ОС).

Почему это не так?

Удивительно, но при повторном просмотре shp_shp через sql, в поле shp_file, похоже, каким-то образом появилась информация о правильном пути.

select shp_file from shp_shp where id=89;

            shp_file             
---------------------------------
 shp/20240628195348/C_Jamoat.zip

Но тогда как возможно, что ни один атрибут instance.shp_file не имеет информации о правильном пути?

Значение self.shp_file.path и других атрибутов self.shp_file было оценено неверно, поскольку они оцениваются последовательно только после сохранения экземпляра.

Поэтому решение состоит в том, чтобы сохранить объект дважды:

1 - первый раз позволил django автоматически обновлять атрибуты self.shp_file

2 - второй раз обновить значение self.shp_file_folder_path в соответствии с правильно обновленным значением self.shp_file.path

решается изменением

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)

до

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)
Вернуться на верх