web-dev-qa-db-fra.com

Chemin de fichier dynamique dans Django

J'essaie de générer des chemins de fichiers dynamiques dans Django. Je veux faire un système de fichiers comme ceci:

 -- user_12
     --- photo_1
     --- photo_2
 --- user_ 13
     ---- photo_1

J'ai trouvé une question connexe: Django Champ de téléchargement d'image personnalisé avec chemin dynamique

Ici, ils disent que nous pouvons changer le chemin upload_to et mène à https://docs.djangoproject.com/en/stable/topics/files/ doc. Dans la documentation, il y a un exemple:

from Django.db import models
from Django.core.files.storage import FileSystemStorage

fs = FileSystemStorage(location='/media/photos')

class Car(models.Model):
    ...
    photo = models.ImageField(storage=fs)

Mais, cela n’est toujours pas dynamique, je veux attribuer l’identifiant voiture au nom de l’image et je ne peux pas attribuer l’identifiant avant que la définition de la voiture ne soit terminée. Alors, comment puis-je créer un chemin avec l'identification de la voiture? 

37
iva123

Vous pouvez utiliser un appelable dans l'argument upload_to plutôt que d'utiliser un stockage personnalisé. Voir docs et noter l’avertissement que la clé primaire peut ne pas encore être définie lors de l’appel de la fonction (car le téléchargement peut être traité avant que l’objet ne soit enregistré dans la base de données), de sorte que ID pourrait ne pas être possible. . Vous voudrez peut-être envisager d'utiliser un autre champ sur le modèle, tel que slug. Par exemple:

import os
def get_upload_path(instance, filename):
    return os.path.join(
      "user_%d" % instance.owner.id, "car_%s" % instance.slug, filename)

puis:

photo = models.ImageField(upload_to=get_upload_path)
62
DrMeers

https://docs.djangoproject.com/fr/stable/ref/models/fields/#Django.db.models.FileField.upload_to

def upload_path_handler(instance, filename):
    return "user_{id}/{file}".format(id=instance.user.id, file=filename)

class Car(models.Model):
    ...
    photo = models.ImageField(upload_to=upload_path_handler, storage=fs)

Il y a un avertissement dans la documentation, mais cela ne devrait pas vous affecter car nous recherchons l'ID User et non l'ID Car.

Dans la plupart des cas, cet objet ne sera pas ont déjà été enregistrés dans la base de données, Donc, s'il utilise le champ automatique par défaut, il pourrait ne pas avoir encore de valeur pour sa champ clé primaire.

5
Yuji 'Tomita' Tomita

Vous pouvez utiliser la fonction lambda comme ci-dessous, notez que si l'instance est nouvelle, elle n'aura pas l'id d'instance, utilisez donc autre chose

logo = models.ImageField(upload_to=lambda instance, filename: 'directory/images/{0}/{1}'.format(instance.owner.id, filename))
4
James Lin

Bien très tard pour la fête mais celle-ci fonctionne pour moi.

def content_file_name(instance, filename):
    upload_dir = os.path.join('uploads',instance.albumname)
    if not os.path.exists(upload_dir):
        os.makedirs(upload_dir)
    return os.path.join(upload_dir, filename)

Modèle comme ça seulement

class Album(models.Model):
    albumname = models.CharField(max_length=100)
    audiofile = models.FileField(upload_to=content_file_name)
2
Raja Simon

Ma solution n'est pas élégante, mais ça marche: 

Dans le modèle, utilisez la fonction standard qui nécessitera l'id/pk

def directory_path(instance, filename):
    return 'files/instance_id_{0}/{1}'.format(instance.pk, filename)

dans views.py, enregistrez le formulaire comme suit:

f=form.save(commit=False)
ftemp1=f.filefield
f.filefield=None
f.save()
#And now that we have crated the record we can add it
f.filefield=ftemp1
f.save()

Cela a fonctionné pour moi. Remarque: Mon champ de fichier dans les modèles et permettait des valeurs Null. Null = True

2
George Battaglia

Il y a deux solutions sur DjangoSnippets

  1. Sauvegarde en deux étapes: https://djangosnippets.org/snippets/1129/
  2. Récupérez l'ID (PostgreSQL uniquement): https://djangosnippets.org/snippets/2731/
1
blueyed

Ce gars a un moyen de faire le chemin dynamique. L'idée est de définir votre stockage préféré et de personnaliser le paramètre "upload_to ()" avec une fonction. 

J'espère que cela t'aides.

0
Laur Ivan

J'ai découvert une solution différente, qui est sale, mais ça marche. Vous devez créer un nouveau modèle factice auto-synchronisé avec le modèle d'origine. Je ne suis pas fier de cela, mais je n'ai pas trouvé d'autre solution. Dans mon cas, je veux télécharger des fichiers et les stocker dans un répertoire nommé d'après l'ID du modèle (car je générerai plus de fichiers). 

le model.py

class dummyexperiment(models.Model):
  def __unicode__(self):
    return str(self.id)

class experiment(models.Model):
  def get_exfile_path(instance, filename):
    if instance.id == None:
      iid = instance.dummye.id
    else:
      iid = instance.id
    return os.path.join('experiments', str(iid), filename)

  exfile = models.FileField(upload_to=get_exfile_path)

  def save(self, *args, **kwargs):
    if self.id == None:
      self.dummye = dummyexperiment()
      self.dummye.save()
    super(experiment, self).save(*args, **kwargs)

Je suis très nouveau en python et en Django, mais ça me semble correct.

une autre solution:

def get_theme_path(instance, filename):
  id = instance.id
  if id == None:
    id = max(map(lambda a:a.id,Theme.objects.all())) + 1
  return os.path.join('experiments', str(id), filename)
0
balazs
MEDIA_ROOT/
   /company_Company1/company.png
                    /shop_Shop1/shop.png
                               /bikes/bike.png


def photo_path_company(instance, filename):
# file will be uploaded to MEDIA_ROOT/company_<name>/
    return 'company_{0}/{1}'.format(instance.name, filename)

class Company(models.Model):
    name = models.CharField()
    photo = models.ImageField(max_length=255, upload_to=photo_path_company)

def photo_path_shop(instance, filename):
# file will be uploaded to MEDIA_ROOT/company_<name>/shop_<name>/
    parent_path = instance.company._meta.get_field('photo').upload_to(instance.company, '')
    return parent_path + 'shop_{0}/{1}'.format(instance.name, filename)

class Shop(models.Model):
    name = models.CharField()
    photo = models.ImageField(max_length=255, upload_to=photo_path_shop)

def photo_path_bike(instance, filename):
    # file will be uploaded to MEDIA_ROOT/company_<name>/shop_<name>/bikes/
    parent_path = instance.shop._meta.get_field('photo').upload_to(instance.shop, '')
    return parent_path + 'bikes/{0}'.format(filename)


class Bike(models.Model):
    name = models.CharField()
    photo = models.ImageField(max_length=255, upload_to=photo_path_bike)
0
Evgeni Shudzel

Comme la clé primaire (id) peut ne pas être disponible si l'instance de modèle n'a pas encore été enregistrée dans la base de données, j'ai écrit mes sous-classes FileField qui déplacent le fichier lors de l'enregistrement du modèle et une sous-classe de stockage qui supprime les anciens fichiers.

Espace de rangement:

class OverwriteFileSystemStorage(FileSystemStorage):
    def _save(self, name, content):
        self.delete(name)
        return super()._save(name, content)

    def get_available_name(self, name):
        return name

    def delete(self, name):
        super().delete(name)

        last_dir = os.path.dirname(self.path(name))

        while True:
            try:
                os.rmdir(last_dir)
            except OSError as e:
                if e.errno in {errno.ENOTEMPTY, errno.ENOENT}:
                    break

                raise e

            last_dir = os.path.dirname(last_dir)

FileField:

def Tweak_field_save(cls, field):
    field_defined_in_this_class = field.name in cls.__dict__ and field.name not in cls.__bases__[0].__dict__

    if field_defined_in_this_class:
        orig_save = cls.save

        if orig_save and callable(orig_save):
            assert isinstance(field.storage, OverwriteFileSystemStorage), "Using other storage than '{0}' may cause unexpected behavior.".format(OverwriteFileSystemStorage.__name__)

            def save(self, *args, **kwargs):
                if self.pk is None:
                    orig_save(self, *args, **kwargs)

                    field_file = getattr(self, field.name)

                    if field_file:
                        old_path = field_file.path
                        new_filename = field.generate_filename(self, os.path.basename(old_path))
                        new_path = field.storage.path(new_filename)
                        os.makedirs(os.path.dirname(new_path), exist_ok=True)
                        os.rename(old_path, new_path)
                        setattr(self, field.name, new_filename)

                    # for next save
                    if len(args) > 0:
                        args = Tuple(v if k >= 2 else False for k, v in enumerate(args))

                    kwargs['force_insert'] = False
                    kwargs['force_update'] = False

                orig_save(self, *args, **kwargs)

            cls.save = save


def Tweak_field_class(orig_cls):
    orig_init = orig_cls.__init__

    def __init__(self, *args, **kwargs):
        if 'storage' not in kwargs:
            kwargs['storage'] = OverwriteFileSystemStorage()

        if orig_init and callable(orig_init):
            orig_init(self, *args, **kwargs)

    orig_cls.__init__ = __init__

    orig_contribute_to_class = orig_cls.contribute_to_class

    def contribute_to_class(self, cls, name):
        if orig_contribute_to_class and callable(orig_contribute_to_class):
            orig_contribute_to_class(self, cls, name)

        Tweak_field_save(cls, self)

    orig_cls.contribute_to_class = contribute_to_class

    return orig_cls


def Tweak_file_class(orig_cls):
    """
    Overriding FieldFile.save method to remove the old associated file.
    I'm doing the same thing in OverwriteFileSystemStorage, but it works just when the names match.
    I probably want to preserve both methods if anyone calls Storage.save.
    """

    orig_save = orig_cls.save

    def new_save(self, name, content, save=True):
        self.delete(save=False)

        if orig_save and callable(orig_save):
            orig_save(self, name, content, save=save)

    new_save.__= 'save'
    orig_cls.save = new_save

    return orig_cls


@Tweak_file_class
class OverwriteFieldFile(models.FileField.attr_class):
    pass


@Tweak_file_class
class OverwriteImageFieldFile(models.ImageField.attr_class):
    pass


@Tweak_field_class
class RenamedFileField(models.FileField):
    attr_class = OverwriteFieldFile


@Tweak_field_class
class RenamedImageField(models.ImageField):
    attr_class = OverwriteImageFieldFile

et mes callables upload_to ressemblent à ceci:

def user_image_path(instance, filename):
    name, ext = 'image', os.path.splitext(filename)[1]

    if instance.pk is not None:
        return os.path.join('users', os.path.join(str(instance.pk), name + ext))

    return os.path.join('users', '{0}_{1}{2}'.format(uuid1(), name, ext))
0
Kukosk