Django: доступ к первичному ключу в расположении models.filefield(upload_to)

Я хочу сохранить свои файлы, используя первичный ключ записи.

Вот мой код:

def get_nzb_filename(instance, filename):
    if not instance.pk:
        instance.save() # Does not work.
    name_slug = re.sub('[^a-zA-Z0-9]', '-', instance.name).strip('-').lower()
    name_slug = re.sub('[-]+', '-', name_slug)
    return u'files/%s_%s.nzb' % (instance.pk, name_slug)

class File(models.Model):
    nzb = models.FileField(upload_to=get_nzb_filename)
    name = models.CharField(max_length=256)

Я знаю, что при первом сохранении объекта первичный ключ недоступен, поэтому я готов принять дополнительные меры, чтобы сохранить объект только для того, чтобы получить первичный ключ, а затем продолжить.

Приведенный выше код не работает. Выдает следующую ошибку:

maximum recursion depth exceeded while calling a Python object

Я предполагаю, что это бесконечный цикл. Вызов метода save приведет к вызову метода get_nzb_filename, который снова вызовет метод save и так далее.

Я использую последнюю версию транка Django.

Как я могу получить первичный ключ, чтобы использовать его для сохранения загруженных файлов?


Обновление @muhuk:

Мне нравится ваше решение. Можете ли вы помочь мне реализовать его? Я обновил свой код до следующего, и ошибка 'File' object has no attribute 'create'. Может быть, я использую то, что вы написали, вне контекста?

def create_with_pk(self):
    instance = self.create()
    instance.save()
    return instance

def get_nzb_filename(instance, filename):
    if not instance.pk:
        create_with_pk(instance)
    name_slug = re.sub('[^a-zA-Z0-9]', '-', instance.name).strip('-').lower()
    name_slug = re.sub('[-]+', '-', name_slug)
    return u'files/%s_%s.nzb' % (instance.pk, name_slug)

class File(models.Model):
    nzb = models.FileField(upload_to=get_nzb_filename, blank=True, null=True)
    name = models.CharField(max_length=256)

Вместо того, чтобы применять обязательное поле в моей модели, я сделаю это в своем классе Form. Без проблем.


person Ty.    schedule 16.03.2009    source источник


Ответы (4)


Кажется, вам нужно сначала предварительно сгенерировать свои модели File с пустыми полями файла. Затем возьмите один и сохраните его с данным файловым объектом.

У вас может быть собственный метод менеджера, подобный этому;

def create_with_pk(self):
    instance = self.create()
    instance.save()     # probably this line is unneeded
    return instance

Но это будет проблематично, если требуется какое-либо из ваших полей. Поскольку вы изначально создаете нулевой объект, вы не можете принудительно применять обязательные поля на уровне модели.

РЕДАКТИРОВАТЬ

create_with_pk должен быть пользовательским методом менеджера, в вашем коде это обычный метод. Следовательно, self бессмысленно. Все это должным образом задокументировано с примерами.

person muhuk    schedule 16.03.2009
comment
Не нужно instance.save() после instance = self.create(). - person claymation; 01.05.2013
comment
Мне не удалось экстраполировать дополнительные шаги, связанные с этим решением. Я создал собственный менеджер, определил упомянутый выше метод, но в итоге получил двойное сохранение, когда при первом сохранении был создан пустой объект, а при втором не удалось изменить правильный объект. Я думаю, что любой, кто попытается использовать этот метод, также должен будет изменить свою функцию сохранения. В конечном итоге переключился на решение @Giles ниже. - person seanmus; 28.05.2016

Вы можете сделать это, установив upload_to во временное местоположение и создав собственный метод сохранения.

Метод сохранения должен сначала вызвать super, чтобы сгенерировать первичный ключ (это сохранит файл во временном расположении). Затем вы можете переименовать файл, используя первичный ключ, и переместить его в нужное место. Позвоните super еще раз, чтобы сохранить изменения, и все готово! Это хорошо сработало для меня, когда я столкнулся с этой точной проблемой.

Например:

class File( models.Model ):
    nzb = models.FileField( upload_to='temp' )

    def save( self, *args, **kwargs ):
        # Call save first, to create a primary key
        super( File, self ).save( *args, **kwargs )

        nzb = self.nzb
        if nzb:
            # Create new filename, using primary key and file extension
            oldfile = self.nzb.name
            dot = oldfile.rfind( '.' )
            newfile = str( self.pk ) + oldfile[dot:]

            # Create new file and remove old one
            if newfile != oldfile:
                self.nzb.storage.delete( newfile )
                self.nzb.storage.save( newfile, nzb )
                self.nzb.name = newfile 
                self.nzb.close()
                self.nzb.storage.delete( oldfile )

        # Save again to keep changes
        super( File, self ).save( *args, **kwargs )
person Giles    schedule 15.05.2013
comment
Я смог изменить это, чтобы иметь первичный ключ как часть пути к обслуживаемым файлам. Спасибо. - person seanmus; 27.05.2016
comment
@seanmus Получилось хорошо? Я тоже хочу использовать pk для генерации путей, но тут нужен серьезный як шейпинг (относительные конкатенации путей после редактирования, проблемы с обменом файлами с похожими именами, удалением и т.п.). У вас есть ссылка на элегантный и надежный пример? - person armin; 29.08.2018

Контекст

Была такая же проблема. Решил это, приписав идентификатор текущему объекту, сначала сохранив объект.

Метод

  1. создать пользовательскую функцию upload_to
  2. определить, есть ли у объекта pk
  3. если нет, сначала сохраните экземпляр, извлеките pk и назначьте его объекту
  4. сгенерируйте свой путь с этим

Пример рабочего кода:

class Image(models.Model):
    def upload_path(self, filename):
        if not self.pk:
            i = Image.objects.create()
            self.id = self.pk = i.id
        return "my/path/%s" % str(self.id)
    file = models.ImageField(upload_to=upload_path)
person vinyll    schedule 28.12.2011
comment
Не нужно i.save() после i = Image.objects.create(). - person claymation; 01.05.2013
comment
Это не работает, так как новый созданный объект остается пустым, а сохраняемый объект сохраняется как другой объект. - person Giles; 15.05.2013

Тай, есть ли причина, по которой ты накатил свой собственный фильтр slugify?

Django поставляется со встроенным фильтром slugify, вы можете использовать его так:

from django.template.defaultfilters import slugify

slug = slugify(some_string)

Не уверен, знали ли вы, что он доступен для использования...

person Dana Woodman    schedule 23.03.2011