2012-04-01 8 views
13

Próbuję utworzyć prostą galerię zdjęć z domyślnym administratorem Django. Chciałbym zapisać przykładowe zdjęcie dla każdej galerii, ale nie chcę zachować nazwy pliku. Zamiast nazwy pliku chciałbym zapisać identyfikator modelu (N.jpg). Ale za pierwszym razem, gdy chcę zapisać obiekt, id nie istnieje. Skąd mogłem wiedzieć, jaki następny automatyczny przyrost w modelu, lub jakoś zapisać dane modelu przed przesłaniem z super.save i po przesłaniu pliku, gdy istnieje self.id? Czy istnieje fajne rozwiązanie?Wczytanie pliku admina Django z bieżącym modelem id

coś takiego:

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) 

A może zapisać pliku w innej dziedzinie.

+4

Dlaczego miałoby to zasługują downvote? Jest to z pewnością kwestia lepszej jakości niż niektóre. – hop

+0

Nie ma niezawodnego sposobu na poznanie identyfikatora następnego rekordu z wyprzedzeniem. Możesz uzyskać identyfikator po utworzeniu rekordu, ale to też podlega warunkom wyścigu. Moja rada - wybierz coś innego oprócz id, aby nazwać twoje pliki. – Brandon

+0

Na przykład bieżący datownik + mikrosekundy – ilvar

Odpowiedz

9

Plik obrazu zostanie zapisany przed wystąpieniem galerii. Więc trzeba podzielić na dwie fazy oszczędzania za pomocą sygnałów w/przykład Gallery sama niosąc Stan:

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) 

upload_path_handler wygląda

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) 

Proponuję za pomocą ImageField zamiast FileField dla Typu sprawdzenie, czy pole służy wyłącznie do przesyłania zdjęć. Ponadto, może chcesz normalizować rozszerzenie pliku (co jest konieczne ze względu na MIME) jak

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

Dziękuję bardzo! :) Moim jedynym komentarzem jest: sender = Image to obiekt obiektu modelu, jeśli ktoś inny spróbuje użyć tego rozwiązania. –

+0

@KBalazs cieszę się, że to pomaga, po prostu napraw kod, sprawdź edytuj proszę – okm

31

wpadłem na ten sam problem. Odpowiedź Okma wysłała mnie na właściwą ścieżkę, ale wydaje mi się, że możliwe jest uzyskanie tej samej funkcjonalności przez przesłonięcie metody save().

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) 

To zdecydowanie zapisuje informacje poprawnie.

+0

czy to jest uszkodzone w django 1.7? – Kukosk

+1

@Kukosk To działa w Django 1.7! – Ajoy

+3

Ładne rozwiązanie! Po pewnym czasie zauważyłem, że przerywa on testy jednostkowe, ponieważ kwargs zawiera "force_insert = True", a drugi zapis w IntegrityError: (1062, "Duplicate entry"). Dodanie kwargs.pop ('force_insert') na końcu bloku if rozwiązuje problem. – jurer

0

W django 1.7 proponowane rozwiązania nie sprawdziły się, więc napisałem podklasy FileField wraz z podklasą pamięci, która usuwa stare pliki.

Przechowywanie:

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 

i moi callables upload_to wyglądać następująco:

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)) 
Powiązane problemy