2012-04-01 8 views
13

Ich versuche, eine einfache Fotogalerie mit dem Standard-Django-Admin erstellen. Ich möchte ein Beispielfoto für jede Galerie speichern, aber ich möchte den Dateinamen nicht behalten. Anstelle des Dateinamens möchte ich die ID des Modells speichern (N.jpg). Aber das erste Mal, wenn ich das Objekt speichern möchte, existiert die ID nicht. Wie kann ich das nächste automatische Inkrement im Modell erkennen oder die Modelldaten vor dem Hochladen mit super.save speichern und nach dem Hochladen der Datei, wenn self.id existiert? Gibt es eine coole Lösung?Django Admin-Datei hochladen mit aktuellen Modell-ID

Etwas wie folgt aus:

def upload_path_handler(instance, filename): 
    ext = filename extension 
    return "site_media/images/gallery/{id}.{ext}".format(id=instance.nextincrement, ext=ext) 

class Gallery(models.Model): 
    name = models.TextField() 
    image = models.FileField(upload_to=upload_path_handler) 

und speichern vielleicht den Dateinamen in einem anderen Feld.

+4

Warum würde dies eine downvote verdienen? Es ist sicherlich eine Frage der besseren Qualität als einige. – hop

+0

Es gibt keine zuverlässige Möglichkeit, die ID des nächsten Datensatzes im Voraus zu kennen. Sie können die ID erhalten, nachdem der Datensatz erstellt wurde, aber auch dies unterliegt den Rennbedingungen. Mein Tipp: Wählen Sie etwas anderes als die ID, um Ihre Dateien zu benennen. – Brandon

+0

Zum Beispiel, aktuelle Zeitstempel + Mikrosekunden – ilvar

Antwort

9

Die Bilddatei wird vor der Gallery-Instanz gespeichert. So haben Sie die Einsparung auf zwei Phasen aufzuteilen durch Signale w/Galerie Instanz selbst, den Staat mit:

from django.db.models.signals import post_save, pre_save 
from django.dispatch import receiver 

_UNSAVED_FILEFIELD = 'unsaved_filefield' 

@receiver(pre_save, sender=Image) 
def skip_saving_file(sender, instance, **kwargs): 
    if not instance.pk and not hasattr(instance, _UNSAVED_FILEFIELD): 
     setattr(instance, _UNSAVED_FILEFIELD, instance.image) 
     instance.image = None 

@receiver(post_save, sender=Image) 
def save_file(sender, instance, created, **kwargs): 
    if created and hasattr(instance, _UNSAVED_FILEFIELD): 
     instance.image = getattr(instance, _UNSAVED_FILEFIELD) 
     instance.save()   
     # delete it if you feel uncomfortable... 
     # instance.__dict__.pop(_UNSAVED_FILEFIELD) 

Die upload_path_handler sieht aus wie

def upload_path_handler(instance, filename): 
    import os.path 
    fn, ext = os.path.splitext(filename) 
    return "site_media/images/gallery/{id}{ext}".format(id=instance.pk, ext=ext) 

Ich schlage vor, mit Imagefield statt Filefield für Typ- Überprüfen, ob das Feld nur zum Hochladen von Bildern dient. Außerdem können Sie die Dateinamenerweiterung (die wegen des MIME-Typs nicht erforderlich ist) wie

def normalize_ext(image_field): 
    try: 
     from PIL import Image 
    except ImportError: 
     import Image 
    ext = Image.open(image_field).format 
    if hasattr(image_field, 'seek') and callable(image_field.seek): 
     image_field.seek(0) 
    ext = ext.lower() 
    if ext == 'jpeg': 
     ext = 'jpg' 
    return '.' + ext 
+0

Vielen Dank! :) Mein einziger Kommentar ist: sender = Bild ist das Objekt des Modellobjekts, wenn jemand anderes versucht, diese Lösung zu verwenden. –

+0

@KBalazs froh, dass es hilft, reparieren Sie einfach den Code, überprüfen Sie die Bearbeitung bitte – okm

31

Ich lief in das gleiche Problem normalisieren. Okms Antwort hat mich auf den richtigen Weg geschickt, aber es scheint mir, dass es möglich ist, die gleiche Funktionalität zu erhalten, indem man einfach die save() Methode überschreibt.

def save(self, *args, **kwargs): 
    if self.pk is None: 
     saved_image = self.image 
     self.image = None 
     super(Material, self).save(*args, **kwargs) 
     self.image = saved_image 

    super(Material, self).save(*args, **kwargs) 

Dies speichert definitiv die Informationen korrekt.

+0

ist dies in Django 1.7 gebrochen? – Kukosk

+1

@Kukosk Das funktioniert in Django 1.7! – Ajoy

+3

Schöne Lösung! Nach einer Weile bemerkte ich, dass es bei Komponententests abbricht, da Kwargs 'force_insert = True' enthalten und das zweite Speichern in IntegrityError resultiert: (1062, "Duplicate entry"). Das Hinzufügen von kwargs.pop ('force_insert') am Ende des if-Blocks löst das Problem. – jurer

0

In Django 1.7, die vorgeschlagenen Lösungen schien nicht für mich arbeiten, so schrieb ich meine FileField-Unterklassen zusammen mit einer Speicherunterklasse, die die alten Dateien entfernt.

Lagerung:

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 

und meine upload_to Callables wie folgt aussehen:

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