Django FileField с upload_to определяется во время выполнения

130

Я пытаюсь настроить свои загрузки так, чтобы, если пользователь joe загружает файл, он отправляется в MEDIA_ROOT / joe, а не все файлы идут в MEDIA_ROOT. Проблема в том, что я не знаю, как это определить в модели. Вот как это сейчас выглядит:

class Content(models.Model):
    name = models.CharField(max_length=200)
    user = models.ForeignKey(User)
    file = models.FileField(upload_to='.')

Так что я хочу вместо "." в качестве upload_to укажите имя пользователя.

Я понимаю, что начиная с Django 1.0 вы можете определить свою собственную функцию для обработки upload_to, но эта функция также не знает, кем будет пользователь, поэтому я немного потерялся.

Спасибо за помощь!

Teebes
источник

Ответы:

256

Вы, наверное, читали документацию , поэтому вот простой пример, чтобы все было понятно:

def content_file_name(instance, filename):
    return '/'.join(['content', instance.user.username, filename])

class Content(models.Model):
    name = models.CharField(max_length=200)
    user = models.ForeignKey(User)
    file = models.FileField(upload_to=content_file_name)

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

SmileyChris
источник
Да, это, вероятно, относится к документации - это довольно часто задаваемые вопросы по IRC
SmileyChris
2
Это работает с ModelForm? Я вижу, что у этого экземпляра есть все атрибуты модели класса, но нет значений (только строка имени поля). В шаблоне пользователь скрыт. Возможно, мне придется задать вопрос, я много часов гуглил.
mgag 07
3
Как ни странно, у меня в основном это не получается. instance.user не имеет атрибутов.
Боб Сприн,
11
Возможно, вы захотите использовать os.path.joinвместо, '/'.joinчтобы убедиться, что он также работает в системах, отличных от Unix. Они могут быть редкими, но это хорошая практика;)
Xudonax
2
Привет, я попробовал тот же код, поместил их в models.py, но получил ошибку. Объект содержимого не имеет атрибута «пользователь».
Гарри
12

Это действительно помогло. Для краткости я решил использовать лямбду в моем случае:

file = models.FileField(
    upload_to=lambda instance, filename: '/'.join(['mymodel', str(instance.pk), filename]),
)
gdakram
источник
4
У меня это не сработало в Django 1.7 с использованием миграции. Вместо этого закончилось создание функции, и миграция потребовалась.
aboutaaron
Даже если вы не можете заставить лямбда работать с использованием str (instance.pk), это хорошая идея, если у вас есть проблемы с перезаписью файлов, когда вы этого не хотите.
Джозеф Даттило,
2
у экземпляра нет pkперед сохранением. Он работает только для обновлений, а не для создания (вставки).
Мохаммад Джафар Машхади
лямбда не работает в migrationsоперациях, потому что она не может быть сериализована в соответствии с документами
Эбрагим Карими,
4

Примечание об использовании значения pk объекта 'instance'. Согласно документации:

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

Следовательно, обоснованность использования pk зависит от того, как определена ваша конкретная модель.

Макс Дудзински
источник
У меня есть значение None. Я не могу понять, как это исправить. не могли бы вы объяснить немного подробнее.
Aman Deep
1

Если у вас проблемы с миграцией, вам, вероятно, следует использовать @deconstructibleдекоратор.

import datetime
import os
import unicodedata

from django.core.files.storage import default_storage
from django.utils.deconstruct import deconstructible
from django.utils.encoding import force_text, force_str


@deconstructible
class UploadToPath(object):
    def __init__(self, upload_to):
        self.upload_to = upload_to

    def __call__(self, instance, filename):
        return self.generate_filename(filename)

    def get_directory_name(self):
        return os.path.normpath(force_text(datetime.datetime.now().strftime(force_str(self.upload_to))))

    def get_filename(self, filename):
        filename = default_storage.get_valid_name(os.path.basename(filename))
        filename = force_text(filename)
        filename = unicodedata.normalize('NFKD', filename).encode('ascii', 'ignore').decode('ascii')
        return os.path.normpath(filename)

    def generate_filename(self, filename):
        return os.path.join(self.get_directory_name(), self.get_filename(filename))

Использование:

class MyModel(models.Model):
    file = models.FileField(upload_to=UploadToPath('files/%Y/%m/%d'), max_length=255)
Михал-Michalak
источник