Файл Django FileField з upload_to визначається під час виконання


130

Я намагаюся налаштувати свої завантаження так, що якщо користувач Джо завантажує файл, він переходить до 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, але ця функція не має уявлення про те, ким буде користувач, тому я трохи загубився.

Дякую за допомогу!

Відповіді:


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 для дзвінка, якщо вам це подобається.


Так, це, мабуть, належить в документах - це розумно FAQ на IRC
SmileyChris

2
Чи працює це з ModelForm? Я бачу, що цей екземпляр має всі атрибути моделі класу, але немає значень (лише рядок назви поля). У шаблоні користувач прихований. Можливо, мені доведеться поставити запитання, я це гуляю годинами.
mgag

3
Як не дивно, але це не вдається в основному в цій же установці. instance.user не має атрибутів на ньому.
Боб Спрін

11
Ви можете використовувати os.path.joinзамість того, '/'.joinщоб переконатися, що він працює і в системах, що не є Unix. Вони можуть бути рідкісними, але це добра практика;)
Xudonax

2
Привіт, я спробував той самий код, поставив їх у models.py, але отримати помилку Об'єкт вмісту не має атрибута "user".
Гаррі

12

Це справді допомогло. Для трохи короткості, я вирішив використовувати лямбда в моєму випадку:

file = models.FileField(
    upload_to=lambda instance, filename: '/'.join(['mymodel', str(instance.pk), filename]),
)

4
Це не спрацювало для мене в Django 1.7 з використанням міграцій. Закінчилося створення функції замість цього, і міграція відбулася.
aboutaaron

Навіть якщо ви не можете змусити лямбда працювати, використовуючи str (instance.pk), це гарна ідея, якщо у вас є проблеми з перезаписуванням файлів, коли ви цього не хочете.
Джозеф Даттіло

2
примірник не має pkперед збереженням. Він працює лише для оновлень, а не для творінь (вставок).
Мохаммед Джафар Машхаді

лямбда не працює в migrationsопераціях, тому що її не можна серіалізувати згідно з документами
Ебрагім Карімі

4

Примітка про використання pk значення об'єкта 'instance'. Відповідно до документації:

У більшості випадків цей об’єкт ще не був збережений у базі даних, тому якщо він використовує AutoField за замовчуванням, він може ще не мати значення для свого поля первинного ключа.

Тому термін використання pk залежить від того, як визначена ваша конкретна модель.


Я отримую None як значення. Я не можу зрозуміти, як це виправити. Ви можете пояснити трохи детально.
Аман Глибокий

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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.