Django FileField mit upload_to zur Laufzeit ermittelt

130

Ich versuche, meine Uploads so einzurichten, dass wenn Benutzer Joe eine Datei hochlädt, diese an MEDIA_ROOT / joe gesendet wird, anstatt dass alle Dateien an MEDIA_ROOT gesendet werden. Das Problem ist, dass ich nicht weiß, wie ich das im Modell definieren soll. So sieht es derzeit aus:

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

Also, was ich will, ist anstelle von '.' Geben Sie als upload_to den Namen des Benutzers an.

Ich verstehe, dass Sie ab Django 1.0 Ihre eigene Funktion definieren können, um upload_to zu verarbeiten, aber diese Funktion hat keine Ahnung, wer der Benutzer sein wird, also bin ich ein bisschen verloren.

Danke für die Hilfe!

Teebes
quelle

Antworten:

256

Sie haben wahrscheinlich die Dokumentation gelesen. Hier ist ein einfaches Beispiel, um es sinnvoll zu machen:

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)

Wie Sie sehen, müssen Sie nicht einmal den angegebenen Dateinamen verwenden - Sie können diesen auch in Ihrem upload_to-Aufruf überschreiben, wenn Sie möchten.

SmileyChris
quelle
Ja, es gehört wahrscheinlich in die Dokumentation - es ist eine vernünftige FAQ im IRC
SmileyChris
2
Funktioniert das mit ModelForm? Ich kann sehen, dass diese Instanz alle Attribute des Klassenmodells hat, aber es gibt keine Werte (nur einen Str des Feldnamens). In der Vorlage ist der Benutzer ausgeblendet. Ich muss möglicherweise eine Frage stellen, ich habe dies stundenlang gegoogelt.
mgag
3
Seltsamerweise scheitert dies bei mir im Grunde an demselben Setup. instance.user hat keine Attribute.
Bob Spryn
11
Möglicherweise möchten Sie os.path.joinstattdessen verwenden '/'.join, um sicherzustellen, dass es auch auf Nicht-Unix-Systemen funktioniert. Sie mögen selten sein, aber es ist eine gute Praxis;)
Xudonax
2
Hallo, ich habe den gleichen Code ausprobiert, sie in models.py eingefügt, aber Fehler erhalten. Das Inhaltsobjekt hat kein Attribut 'Benutzer'.
Harry
12

Das hat wirklich geholfen. Aus Gründen der Kürze habe ich mich für Lambda entschieden:

file = models.FileField(
    upload_to=lambda instance, filename: '/'.join(['mymodel', str(instance.pk), filename]),
)
gdakram
quelle
4
Dies hat bei mir in Django 1.7 mit Migrationen nicht funktioniert. Am Ende wurde stattdessen eine Funktion erstellt und die Migration dauerte.
ungefähr Aaron
Auch wenn Sie Lambda nicht mit str (instance.pk) zum Laufen bringen können, ist dies eine gute Idee, wenn Sie Probleme beim Überschreiben von Dateien haben, wenn Sie dies nicht möchten.
Joseph Dattilo
2
Instanz hat keine pkvor dem Speichern. Es funktioniert nur für Updates, nicht für Kreationen (Einfügungen).
Mohammad Jafar Mashhadi
Lambda funktioniert nicht im migrationsBetrieb, da es gemäß den Dokumenten
Ebrahim Karimi,
4

Ein Hinweis zur Verwendung des pk-Werts des 'Instanz'-Objekts. Laut Dokumentation:

In den meisten Fällen wurde dieses Objekt noch nicht in der Datenbank gespeichert. Wenn also das Standard-AutoField verwendet wird, hat es möglicherweise noch keinen Wert für das Primärschlüsselfeld.

Daher hängt die Gültigkeit der Verwendung von pk davon ab, wie Ihr bestimmtes Modell definiert ist.

Max Dudzinski
quelle
Ich habe None als Wert. Ich kann nicht herausfinden, wie ich das beheben kann. Kannst du das etwas genauer erklären?
Aman Deep
1

Wenn Sie Probleme mit Migrationen haben, sollten Sie wahrscheinlich @deconstructibleDecorator verwenden.

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

Verwendung:

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