Django FileField con upload_to determinato in fase di esecuzione


130

Sto cercando di impostare i miei caricamenti in modo che se l'utente joe carica un file vada su MEDIA_ROOT / joe anziché avere i file di tutti su MEDIA_ROOT. Il problema è che non so come definirlo nel modello. Ecco come appare attualmente:

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

Quindi quello che voglio è invece di '.' come upload_to, deve essere il nome dell'utente.

Comprendo che a partire da Django 1.0 è possibile definire la propria funzione per gestire upload_to ma quella funzione non ha idea di chi sarà l'utente, quindi mi sono perso un po '.

Grazie per l'aiuto!

Risposte:


256

Probabilmente hai letto la documentazione , quindi ecco un semplice esempio per dare un senso:

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)

Come puoi vedere, non è nemmeno necessario utilizzare il nome file indicato, se lo desideri, puoi sovrascriverlo anche nel tuo upload_ per richiamarlo.


Sì, probabilmente appartiene ai documenti - è una domanda frequente su IRC
SmileyChris,

2
Funziona con ModelForm? Vedo che quell'istanza ha tutti gli attributi del modello di classe, ma non ci sono valori (solo una str del nome del campo). Nel modello, l'utente è nascosto. Potrei dover presentare una domanda, ho cercato su Google per ore.
mgag

3
Abbastanza stranamente, questo non mi riesce sostanzialmente in questa stessa configurazione. instance.user non ha attributi su di esso.
Bob Spryn,

11
Si consiglia di utilizzare os.path.joinanziché '/'.joinassicurarsi che funzioni anche su sistemi non Unix. Possono essere rari, ma è una buona pratica;)
Xudonax

2
Ciao, ho provato lo stesso codice, li ho messi in models.py, ma ho ricevuto l'errore L'oggetto contenuto non ha attributo 'utente'.
Harry,

12

Questo ha davvero aiutato. Per un po 'più di brevità, ho deciso di usare lambda nel mio caso:

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

4
Questo non ha funzionato per me in Django 1.7 usando le migrazioni. Finì invece per creare una funzione e la migrazione prese.
aboutaaron,

Anche se non riesci a far funzionare lambda usando str (instance.pk) è una buona idea se hai problemi con la sovrascrittura dei file quando non li vuoi.
Joseph Dattilo,

2
istanza non ha un pkprima di salvare. Funziona solo per gli aggiornamenti, non per le creazioni (inserti).
Mohammad Jafar Mashhadi,

lambda non funziona nelle migrationsoperazioni perché non può essere serializzato secondo i documenti
Ebrahim Karimi

4

Una nota sull'uso del valore pk dell'oggetto 'istanza'. Secondo la documentazione:

Nella maggior parte dei casi, questo oggetto non sarà ancora stato salvato nel database, quindi se utilizza l'AutoField predefinito, potrebbe non avere ancora un valore per il suo campo chiave principale.

Pertanto la validità dell'uso di pk dipende da come viene definito il tuo modello particolare.


Non ricevo nessuno come valore. Non riesco a capire come risolverlo. puoi spiegarci un po 'in dettaglio.
Aman Deep

1

Se hai problemi con le migrazioni, probabilmente dovresti usare @deconstructibleDecorator.

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

Uso:

class MyModel(models.Model):
    file = models.FileField(upload_to=UploadToPath('files/%Y/%m/%d'), max_length=255)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.