Ruta de archivo dinámica en Django

Intento generar rutas de archivos dinámicas en django. Quiero hacer un sistema de archivos como este:

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

Encontré una pregunta relacionada: Django Custom image upload field with dynamic path

Aquí, dicen que podemos cambiar la ruta de upload_to y lleva 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, aun así, esto no es dynamic, quiero dar Identificación del coche al nombre de la imagen, y no puedo asignar la identificación antes de que se complete la definición del automóvil. Entonces, ¿cómo puedo crear una ruta con ID de automóvil?

Puede usar un invocable en el argumento upload_to en lugar de usar almacenamiento personalizado. Consulte los documentos y observe la advertencia de que la clave principal aún no se puede configurar cuando se llama a la función (porque la carga se puede manejar 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. P.ej:

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

entonces:

 photo = models.ImageField(upload_to=get_upload_path) 

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 los documentos, pero no debería afectarlo ya que buscamos la ID de User y no la ID del Car .

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

Puede usar la función lambda como se indica a continuación, tenga en cuenta que si la instancia es nueva, entonces no tendrá el ID de instancia, por lo tanto, use algo más:

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

Bueno, muy tarde para la fiesta, pero este 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í solo

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

Mi solución no es elegante, pero funciona:

En el modelo, use a la función estándar que necesitará la id / pk

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

en views.py guarde el formulario de esta manera:

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

Funcionó para mí Nota: Mi campo de archivos en modelos y permitido para valores nulos. Null = True

Hay dos soluciones en DjangoSnippets

  1. Ahorro en dos etapas: https://djangosnippets.org/snippets/1129/
  2. Recupere el ID (solo PostgreSQL): https://djangosnippets.org/snippets/2731/

Este chico tiene una forma de hacer un camino dynamic. La idea es configurar su almacenamiento favorito y personalizar el parámetro “upload_to ()” con una función.

Espero que esto ayude.

Descubrí 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).

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

Soy muy nuevo en python y en django, pero parece estar 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) 

Como la clave principal (id) puede no estar disponible si la instancia del modelo aún no se guardó en la base de datos, escribí mis subclases FileField, que mueven el archivo en el modelo guardado, 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 upload_to callables se ven así:

 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)) 
 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_/ 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_/shop_/ 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_/shop_/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)