Django FileField avec upload_to déterminé au moment de l'exécution

130

J'essaie de configurer mes téléchargements de sorte que si l'utilisateur joe télécharge un fichier, il aille dans MEDIA_ROOT / joe au lieu d'avoir les fichiers de tout le monde dans MEDIA_ROOT. Le problème est que je ne sais pas comment définir cela dans le modèle. Voici à quoi il ressemble actuellement:

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

Donc, ce que je veux, c'est au lieu de "." comme upload_to, que ce soit le nom de l'utilisateur.

Je comprends qu'à partir de Django 1.0, vous pouvez définir votre propre fonction pour gérer le upload_to mais cette fonction n'a aucune idée de qui sera l'utilisateur non plus, donc je suis un peu perdu.

Merci pour l'aide!

Teebes
la source

Réponses:

256

Vous avez probablement lu la documentation , voici donc un exemple simple pour comprendre:

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)

Comme vous pouvez le voir, vous n'avez même pas besoin d'utiliser le nom de fichier donné - vous pouvez également le remplacer dans votre appelable upload_to si vous le souhaitez.

SmileyChris
la source
Ouais, cela appartient probablement à la documentation - c'est une FAQ raisonnable sur IRC
SmileyChris
2
Cela fonctionne-t-il avec ModelForm? Je peux voir que cette instance a tous les attributs du modèle de classe, mais il n'y a pas de valeurs (juste une chaîne du nom du champ). Dans le modèle, l'utilisateur est masqué. Il se peut que je doive poser une question, je la recherche sur Google depuis des heures.
mgag
3
Curieusement, cela échoue sur moi dans cette même configuration. instance.user ne contient aucun attribut.
Bob Spryn
11
Vous voudrez peut-être utiliser os.path.joinau lieu de '/'.joinpour vous assurer qu'il fonctionne également sur les systèmes non-Unix. Ils peuvent être rares, mais c'est une bonne pratique;)
Xudonax
2
Salut, j'ai essayé le même code, les mettre dans models.py, mais obtenir une erreur L'objet de contenu n'a pas d'attribut «utilisateur».
Harry
12

Cela a vraiment aidé. Pour un peu plus de brièveté, j'ai décidé d'utiliser lambda dans mon cas:

file = models.FileField(
    upload_to=lambda instance, filename: '/'.join(['mymodel', str(instance.pk), filename]),
)
gdakram
la source
4
Cela n'a pas fonctionné pour moi dans Django 1.7 en utilisant des migrations. J'ai fini par créer une fonction à la place et la migration a pris.
aboutaaron
Même si vous ne pouvez pas faire fonctionner lambda à l'aide de str (instance.pk), c'est une bonne idée si vous rencontrez des problèmes d'écrasement de fichiers lorsque vous ne le souhaitez pas.
Joseph Dattilo
2
l'instance n'a pas de pksauvegarde avant. Cela ne fonctionne que pour les mises à jour et non pour les créations (inserts).
Mohammad Jafar Mashhadi
lambda ne fonctionne pas dans les migrationsopérations car il ne peut pas être sérialisé selon la documentation
Ebrahim Karimi
4

Une note sur l'utilisation de la valeur pk de l'objet 'instance'. Selon la documentation:

Dans la plupart des cas, cet objet n'aura pas encore été enregistré dans la base de données. Par conséquent, s'il utilise l'AutoField par défaut, il se peut qu'il n'ait pas encore de valeur pour son champ de clé primaire.

Par conséquent, la validité de l'utilisation de pk dépend de la manière dont votre modèle particulier est défini.

Max Dudzinski
la source
Je n'ai aucun comme valeur. Je ne sais pas comment y remédier. pouvez-vous expliquer un peu en détail.
Aman Deep
1

Si vous rencontrez des problèmes de migration, vous devriez probablement utiliser @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))

Usage:

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