2011-02-27 48 views
34

Estoy intentando generar rutas dinámicas de archivos en django. Quiero hacer un sistema de archivos como esto:Ruta de archivos dinámicos en Django

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

me encontré con una pregunta relacionada: Django Custom image upload field with dynamic path

Aquí, dicen que podemos cambiar la ruta upload_to y conduce a https://docs.djangoproject.com/en/stable/topics/files/ doc. En la documentación, hay un ejemplo:

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) 

Pero, aún así esto no es dinámica, quiero dar Identificación de coches para el nombre de la imagen, y no puedo asignar la identificación antes de completarse la definición de coches. Entonces, ¿cómo puedo crear una ruta con ID de automóvil?

+0

Pensé que no puedo obtener el atributo 'instance' antes de que se guarde. Estaba equivocado, esos atributos del Carro han sido acotados a 'instancia', sin embargo no han sido guardados en la base de datos – laike9m

Respuesta

54

Puede utilizar un exigible en el argumento upload_to en lugar de utilizar el almacenamiento personalizado. Consulte docs y observe la advertencia de que la clave principal puede no configurarse aún cuando se llama a la función (porque la carga puede realizarse antes de que el objeto se guarde en la base de datos), por lo que no es posible usar ID. Es posible que desee considerar el uso de otro campo en el modelo, como slug. Por ejemplo:

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

a continuación:

photo = models.ImageField(upload_to=get_upload_path) 
+0

Soy nuevo en Python y podría estar perdiendo algo. Pero, no vi que pasara instancia, nombre de archivo en la siguiente declaración - photo = models.ImageField (upload_to = get_upload_path). Entonces, mi pregunta es ¿cómo sabe el método cuáles son los valores? –

+2

Ver el enlace "documentos" en la respuesta. Te vende - "Los dos argumentos que se aprobarán son ..." – DrMeers

+1

Ya no funciona en Django 1.5. –

5

https://docs.djangoproject.com/en/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) 

Hay una advertencia en la documentación, pero no se debe afectar ya que estamos después de la ID User y no el ID Car.

In most cases, this object will not have been saved to the database yet, so if it uses the default AutoField, it might not yet have a value for its primary key field.

+2

Una cosa que puede no ser del todo obvia a menos que lea todos los documentos en' FileField' es que 'upload_to' puede llamarse en realidad devuelve un nombre de archivo '/ ruta/a/base.ext' que se adjunta a la ubicación de su almacenamiento. Debido a que el almacenamiento abstrae la ruta absoluta y las operaciones de archivos, el beneficio menos obvio es que puede mover sus archivos a un almacenamiento/ubicación completamente diferente y sus modelos seguirán funcionando como se esperaba. –

+0

Hola, lo intenté pero obtuve el error: AttributeError: el objeto 'Car' no tiene ningún atributo 'usuario'. ¿Cómo puedo pasar request.user.id (id del usuario conectado) a instance.user.id? Gracias. – learnJQueryUI

0

This guy tiene una manera de hacer ruta dinámica. La idea es configurar su almacenamiento favorito y personalizar el parámetro "upload_to()" con una función.

Espero que esto ayude.

0

Encontré una solución diferente, que está sucia, pero funciona. Debería crear un nuevo modelo ficticio, que se auto sincroniza con el original. No estoy orgulloso de esto, pero no encontré otra solución. En mi caso, quiero subir archivos y almacenarlos en un directorio que tenga el nombre del ID del modelo (porque generaré allí más archivos).

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

que soy muy nuevo en Python y en Django, pero parece que está bien para mí.

otra solución:

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

bien, así que obtienes una identificación aleatoria, ¿cómo vinculas el archivo al objeto guardado? – Mikhail

+1

eh, fue hace mucho tiempo, y dejé de trabajar con django, así que no recuerdo. Busqué los códigos y, por supuesto, estoy usando otra cosa, aquí está (editada mi respuesta): – balazs

+0

Ese es un enfoque interesante, esperando que la clave primaria esté en sincronización con el recuento total de elementos. Supongo que es cierto siempre que no elimine nada. – Mikhail

4

Puede utilizar la función lambda como abajo, tome en cuenta que si la instancia es nuevo, entonces no tendrá el identificador de instancia, por lo que usar otra cosa:

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

bien muy tarde a la fiesta, pero éste funciona para mí.

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) 

modelo así sólo

class Album(models.Model): 
    albumname = models.CharField(max_length=100) 
    audiofile = models.FileField(upload_to=content_file_name) 
+0

Esto funcionó para mí, pero tengo un problema , Necesito generar dos subárboles para una ID de modelo, lo hago de esta manera: upload_dir = os.path.join ('equipos', instancia.equipo_id, 'anexos', 'img') y upload_dir = os.path.join ('equipos', instancia.equipo_id, 'anexos', 'archivos') en diferentes funciones a las que llamo en diferentes modelos, ¿hay alguna manera de hacerlo pero sin romper el paradigma DRY? –

0

Como la clave principal (id) puede no estar disponible si la instancia de modelo no se guarda en la base de datos todavía, escribí mis subclases FileField que mueven el archivo en guardar modelo y una subclase de almacenamiento que elimina los archivos antiguos.

almacenamiento:

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.__name__ = '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 

y mis callables upload_to tener este aspecto:

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

Mi solución no es elegante, pero funciona:

En t que el modelo, utilice un estándar de la función que tendrá el id/pk

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

en views.py guardar el formulario como el siguiente:

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

Se trabajó para mí. Nota: Mi campo de archivos en modelos y permitido para valores nulos. Nulo = verdadero

Cuestiones relacionadas