Django FileField con upload_to determinado en tiempo de ejecución

130

Estoy tratando de configurar mis cargas para que, si el usuario Joe sube un archivo, vaya a MEDIA_ROOT / joe en lugar de que todos los archivos vayan a MEDIA_ROOT. El problema es que no sé cómo definir esto en el modelo. Así es como se ve actualmente:

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

Entonces, lo que quiero es en lugar de '.' como upload_to, que sea el nombre del usuario.

Entiendo que a partir de Django 1.0 puede definir su propia función para manejar upload_to, pero esa función tampoco tiene idea de quién será el usuario, así que estoy un poco perdido.

¡Gracias por la ayuda!

Teebes
fuente

Respuestas:

256

Probablemente haya leído la documentación , así que aquí hay un ejemplo sencillo para que tenga sentido:

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)

Como puede ver, ni siquiera necesita usar el nombre de archivo dado; también puede anularlo en upload_to invocable si lo desea.

SmileyChris
fuente
Sí, probablemente pertenece a los documentos: es una pregunta frecuente razonablemente sobre IRC
SmileyChris
2
¿Funciona esto con ModelForm? Puedo ver que la instancia tiene todos los atributos del modelo de clase, pero no hay valores (solo una cadena del nombre del campo). En la plantilla, el usuario está oculto. Puede que tenga que enviar una pregunta, he estado buscando en Google durante horas.
Miércoles
3
Curiosamente, esto me está fallando básicamente en esta misma configuración. instance.user no tiene atributos en él.
Bob Spryn
11
Es posible que desee usar en os.path.joinlugar de '/'.joinasegurarse de que también funcione en sistemas que no sean Unix. Pueden ser raros, pero es una buena práctica;)
Xudonax
2
Hola, probé el mismo código, los puse en models.py, pero recibí un error. El objeto Content no tiene el atributo 'user'.
Harry
12

Esto realmente ayudó. Por un poco más de brevedad, decidí usar lambda en mi caso:

file = models.FileField(
    upload_to=lambda instance, filename: '/'.join(['mymodel', str(instance.pk), filename]),
)
gdakram
fuente
44
Esto no funcionó para mí en Django 1.7 usando migraciones. Terminé creando una función en su lugar y la migración tomó.
aboutaaron
Incluso si no puede hacer que lambda funcione con str (instance.pk), es una buena idea si tiene problemas con la sobrescritura de archivos cuando no lo desea.
Joseph Dattilo
2
instancia no tiene un pkantes de guardar. Solo funciona para actualizaciones, no creaciones (inserciones).
Mohammad Jafar Mashhadi
lambda no funciona en migrationsoperaciones porque no se puede serializar según los documentos
Ebrahim Karimi
4

Una nota sobre el uso del valor pk del objeto 'instancia'. De acuerdo con la documentación:

En la mayoría de los casos, este objeto aún no se habrá guardado en la base de datos, por lo que si utiliza el Autocampo predeterminado, es posible que aún no tenga un valor para su campo de clave principal.

Por lo tanto, la validez de usar pk depende de cómo se defina su modelo particular.

Max Dudzinski
fuente
No tengo ninguno como valor. No puedo entender cómo solucionarlo. ¿Puedes explicarlo con un poco de detalle?
Aman Deep
1

Si tiene problemas con las migraciones, probablemente debería estar usando @deconstructibledecorador.

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)
michal-michalak
fuente