Django FileField com upload_to determinado em tempo de execução

130

Estou tentando configurar meus envios para que, se o usuário joe carregue um arquivo, ele vá para MEDIA_ROOT / joe, em vez de fazer com que todos os arquivos sejam acessados ​​em MEDIA_ROOT. O problema é que não sei como definir isso no modelo. Aqui está como está atualmente:

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

Então, o que eu quero é em vez de '.' como upload_to, seja o nome do usuário.

Eu entendo que a partir do Django 1.0 você pode definir sua própria função para lidar com o upload_to, mas essa função não tem idéia de quem será o usuário, então estou um pouco perdido.

Obrigado pela ajuda!

Teebes
fonte

Respostas:

256

Você provavelmente já leu a documentação , então aqui está um exemplo fácil de fazer 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 você pode ver, você nem precisa usar o nome de arquivo fornecido - você pode substituir isso em seu upload_para chamar também, se quiser.

SmileyChris
fonte
Sim, ele provavelmente pertence em docs - é um razoavelmente FAQ no IRC
SmileyChris
2
Isso funciona com o ModelForm? Eu posso ver que a instância tem todos os atributos do modelo de classe, mas não há valores (apenas um str do nome do campo). No modelo, o usuário está oculto. Talvez eu precise enviar uma pergunta, estou pesquisando isso há horas.
mgag
3
Curiosamente, isso está falhando comigo basicamente nessa mesma configuração. instance.user não possui atributos.
perfil completo de Bob Spryn
11
Você pode usar em os.path.joinvez de '/'.joingarantir que ele também funcione em sistemas não-Unix. Eles podem ser raros, mas é uma boa prática;)
Xudonax
2
Oi, Eu tentei o mesmo código, coloque-os em models.py, mas obtenha o erro Objeto de conteúdo não tem atributo 'usuário'.
Harry
12

Isso realmente ajudou. Por um pouco mais de brevidade, decidi usar lambda no meu caso:

file = models.FileField(
    upload_to=lambda instance, filename: '/'.join(['mymodel', str(instance.pk), filename]),
)
gdakram
fonte
4
Isso não funcionou para mim no Django 1.7 usando migrações. Acabou criando uma função e a migração levou.
aboutaaron
Mesmo que você não consiga que o lambda funcione usando o str (instance.pk), é uma boa idéia se você tiver problemas com a substituição de arquivos quando não desejar.
Joseph Dattilo
2
A instância não possui um pkantes de salvar. Funciona apenas para atualizações, não para criações (inserções).
Mohammad Jafar Mashhadi
O lambda não funcionar em migrationsoperações porque ele não pode ser serializado de acordo com os docs
Ebrahim Karimi
4

Uma observação sobre o uso do valor pk do objeto 'instance'. De acordo com a documentação:

Na maioria dos casos, esse objeto ainda não foi salvo no banco de dados; portanto, se ele usar o AutoField padrão, talvez ainda não tenha um valor para o seu campo de chave primária.

Portanto, a validade do uso de pk depende de como seu modelo específico é definido.

Max Dudzinski
fonte
Estou recebendo Nenhum como o valor. Não consigo descobrir como consertar isso. você pode explicar um pouco mais detalhadamente?
Aman profunda
1

Se você tiver problemas com migrações, provavelmente deve usar o @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
fonte