Django FileField z upload_to ustalonym w czasie wykonywania

130

Próbuję ustawić przesyłanie tak, aby jeśli użytkownik joe prześle plik, trafił do MEDIA_ROOT / joe, w przeciwieństwie do wszystkich plików, które mają trafiać do MEDIA_ROOT. Problem w tym, że nie wiem, jak to zdefiniować w modelu. Oto jak to obecnie wygląda:

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

Więc to, czego chcę, to zamiast „”. jako upload_to, niech to będzie nazwa użytkownika.

Rozumiem, że od Django 1.0 możesz zdefiniować własną funkcję do obsługi upload_to, ale ta funkcja nie ma pojęcia, kim będzie użytkownik, więc jestem trochę zagubiony.

Dzięki za pomoc!

Teebes
źródło

Odpowiedzi:

256

Prawdopodobnie przeczytałeś dokumentację , więc oto prosty przykład, który ma sens:

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)

Jak widzisz, nie musisz nawet używać podanej nazwy pliku - możesz ją również zmienić w swoim pliku upload_to callable, jeśli chcesz.

SmileyChris
źródło
Tak, prawdopodobnie należy do dokumentacji - to rozsądne FAQ na IRC
SmileyChris
2
Czy to działa z ModelForm? Widzę, że instancja ma wszystkie atrybuty modelu klasy, ale nie ma żadnych wartości (tylko str z nazwy pola). W szablonie użytkownik jest ukryty. Być może będę musiał zadać pytanie, szukam tego od godzin.
mgag
3
Co dziwne, zawodzi to w zasadzie w tej samej konfiguracji. instance.user nie ma żadnych atrybutów.
Bob Spryn,
11
Możesz użyć os.path.joinzamiast, '/'.joinaby upewnić się, że działa również na systemach innych niż Unix. Mogą być rzadkie, ale to dobra praktyka;)
Xudonax
2
Cześć, wypróbowałem ten sam kod, umieściłem je w models.py, ale otrzymałem błąd Obiekt Content nie ma atrybutu „user”.
Harry,
12

To naprawdę pomogło. Dla większej zwięzłości zdecydowałem się w moim przypadku na lambdę:

file = models.FileField(
    upload_to=lambda instance, filename: '/'.join(['mymodel', str(instance.pk), filename]),
)
gdakram
źródło
4
To nie zadziałało dla mnie w Django 1.7 przy użyciu migracji. Skończyło się na utworzeniu funkcji, a migracja zajęła.
aboutaaron
Nawet jeśli nie możesz uruchomić lambdy przy użyciu str (instance.pk), jest to dobry pomysł, jeśli masz problemy z nadpisywaniem plików, gdy tego nie chcesz.
Joseph Dattilo,
2
wystąpienie nie ma pkprzed zapisaniem. Działa tylko w przypadku aktualizacji, a nie kreacji (wstawek).
Mohammad Jafar Mashhadi
lambda nie działa w migrationsoperacjach, ponieważ nie można jej serializować zgodnie z dokumentacją
Ebrahim Karimi
4

Uwaga dotycząca używania wartości pk obiektu „instancja”. Zgodnie z dokumentacją:

W większości przypadków ten obiekt nie został jeszcze zapisany w bazie danych, więc jeśli używa domyślnego pola automatycznego, może nie mieć jeszcze wartości dla swojego pola klucza podstawowego.

Dlatego ważność użycia pk zależy od tego, jak zdefiniowany jest twój konkretny model.

Max Dudziński
źródło
1
Otrzymuję wartość Brak. Nie wiem, jak to naprawić. czy możesz wyjaśnić trochę szczegółowo.
Aman Deep
2

Jeśli masz problemy z migracjami, prawdopodobnie powinieneś użyć @deconstructibledekoratora.

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

Stosowanie:

class MyModel(models.Model):
    file = models.FileField(upload_to=UploadToPath('files/%Y/%m/%d'), max_length=255)
michal-michalak
źródło