Django FileField dengan upload_to ditentukan saat runtime

130

Saya mencoba menyiapkan unggahan saya sehingga jika pengguna joe mengunggah file, ia pergi ke MEDIA_ROOT / joe sebagai lawan meminta semua orang membuka file MEDIA_ROOT. Masalahnya adalah saya tidak tahu bagaimana mendefinisikan ini dalam model. Beginilah tampilannya saat ini:

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

Jadi yang saya inginkan bukan '.' sebagai upload_to, jadikan itu nama pengguna.

Saya mengerti bahwa mulai Django 1.0 Anda dapat mendefinisikan fungsi Anda sendiri untuk menangani upload_to tetapi fungsi itu tidak tahu siapa pengguna itu, jadi saya agak bingung.

Terima kasih untuk bantuannya!

Teebes
sumber

Jawaban:

256

Anda mungkin telah membaca dokumentasinya , jadi inilah contoh mudah untuk membuatnya masuk akal:

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)

Seperti yang Anda lihat, Anda bahkan tidak perlu menggunakan nama file yang diberikan - Anda bisa menimpanya di upload_to Anda juga bisa dipanggil jika Anda suka.

SmileyChris
sumber
Ya, mungkin memang milik di docs - itu adalah cukup FAQ di IRC
SmileyChris
2
Apakah ini berfungsi dengan ModelForm? Saya dapat melihat bahwa instance memiliki semua atribut model kelas, tetapi tidak ada nilai (hanya str dari nama field). Dalam templat, pengguna disembunyikan. Saya mungkin harus mengajukan pertanyaan, saya sudah googling ini selama berjam-jam.
mgag
3
Anehnya ini gagal pada saya pada dasarnya pengaturan yang sama ini. instance.user tidak memiliki atribut di dalamnya.
Bob Spryn
11
Anda mungkin ingin menggunakan os.path.joinalih-alih '/'.joinmemastikan juga berfungsi pada sistem yang bukan Unix. Mereka mungkin jarang, tapi ini praktik yang baik;)
Xudonax
2
Hai, saya mencoba kode yang sama, memasukkannya ke dalam models.py, tetapi mendapatkan konten error objek tidak memiliki atribut 'pengguna'.
Harry
12

Ini sangat membantu. Demi sedikit lebih singkat, memutuskan untuk menggunakan lambda dalam kasus saya:

file = models.FileField(
    upload_to=lambda instance, filename: '/'.join(['mymodel', str(instance.pk), filename]),
)
gdakram
sumber
4
Ini tidak berhasil untuk saya di Django 1.7 menggunakan migrasi. Akhirnya membuat fungsi sebagai gantinya dan migrasi mengambil.
aboutaaron
Bahkan jika Anda tidak dapat membuat lambda bekerja menggunakan str (instance.pk) adalah ide yang bagus jika Anda memiliki masalah dengan file yang ditimpa ketika Anda tidak menginginkannya.
Joseph Dattilo
2
Misalnya tidak memiliki pksebelum menabung. Ini hanya berfungsi untuk pembaruan, bukan kreasi (sisipan).
Mohammad Jafar Mashhadi
lambda tidak bekerja dalam migrationsoperasi karena tidak dapat diserialisasi berdasarkan dokumen
Ebrahim Karimi
4

Catatan tentang menggunakan nilai pk objek 'instance'. Menurut dokumentasi:

Dalam kebanyakan kasus, objek ini belum disimpan ke database, jadi jika ia menggunakan AutoField default, ia mungkin belum memiliki nilai untuk bidang kunci utama.

Oleh karena itu validitas menggunakan pk tergantung pada bagaimana model khusus Anda didefinisikan.

Max Dudzinski
sumber
Saya sudah mendapatkan None sebagai nilainya. Saya tidak tahu cara memperbaikinya. dapat Anda jelaskan sedikit detail.
Aman Deep
1

Jika Anda memiliki masalah dengan migrasi, Anda mungkin harus menggunakan @deconstructibledekorator.

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

Pemakaian:

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