2009-03-08 8 views
14

Comment redimensionner facilement une image après l'avoir téléchargée dans Django? J'utilise Django 1.0.2 et j'ai installé PIL.redimensionner l'image lors de la sauvegarde

Je pensais à redéfinir la méthode save() du modèle pour le redimensionner, mais je ne sais pas vraiment comment le démarrer et le surcharger.

Quelqu'un peut-il me diriger dans la bonne direction? Merci :-)

@ Guðmundur H: Cela ne fonctionnera pas parce que le package django-stdimage ne fonctionne pas sous Windows :-(

Répondre

12

Vous devez utiliser une méthode pour gérer le fichier téléchargé, comme le montre le Django documentation.

dans cette méthode, vous pourriez concaténer les morceaux dans une variable (plutôt que de les écrire sur le disque directement), créer une image PIL de cette variable, redimensionner l'image et l'enregistrer sur le disque.

En PIL, vous devriez regarder Image.fromstring et Image.resize .

22

Je recommande d'utiliser StdImageField de django-stdimage, il devrait gérer tout le sale boulot pour vous. Il est facile à utiliser, il vous suffit de spécifier les dimensions de l'image redimensionnée dans la définition du champ:

class MyModel(models.Model): 
    image = StdImageField(upload_to='path/to/img', size=(640, 480)) 

Vérifiez les docs - il peut faire aussi des vignettes.

+0

Cela ne semble pas fonctionner sur une machine Windows :-(Il s'agit d'un bogue connu dans le paquetage django-stdimage.Il y a une sorte d'erreur de récursion –

+11

Windows ne peut pas se reproduire.C'est une fonction linux seulement :) – Codygman

+0

fonctionne sur win32 pour moi. –

6

Je recommande fortement l'application sorl-thumbnail pour gérer le redimensionnement d'image facilement et de manière transparente. Il va dans chaque projet Django que je démarre.

+0

Le projet [sorl-thumbnail] (https://github.com/sorl/sorl-thumbnail) a été déplacé vers github btw. – Raj

+0

Sorl a un nouveau mantainer et se prépare avec une nouvelle version bientôt, jetez-y un oeil http://github.com/mariocesar/sorl-thumbnail –

11

J'utilise ce code pour gérer les images téléchargées, les redimensionner en mémoire (sans les enregistrer de façon permanente sur le disque), puis enregistrer le pouce sur un Django ImageField. L'espoir peut aider.

def handle_uploaded_image(i): 
     import StringIO 
     from PIL import Image, ImageOps 
     import os 
     from django.core.files import File 
     # read image from InMemoryUploadedFile 
     image_str = “” 
     for c in i.chunks(): 
      image_str += c 

     # create PIL Image instance 
     imagefile = StringIO.StringIO(image_str) 
     image = Image.open(imagefile) 

     # if not RGB, convert 
     if image.mode not in (“L”, “RGB”): 
      image = image.convert(“RGB”) 

     #define file output dimensions (ex 60x60) 
     x = 130 
     y = 130 

     #get orginal image ratio 
     img_ratio = float(image.size[0])/image.size[1] 

     # resize but constrain proportions? 
     if x==0.0: 
      x = y * img_ratio 
     elif y==0.0: 
      y = x/img_ratio 

     # output file ratio 
     resize_ratio = float(x)/y 
     x = int(x); y = int(y) 

     # get output with and height to do the first crop 
     if(img_ratio > resize_ratio): 
      output_width = x * image.size[1]/y 
      output_height = image.size[1] 
      originX = image.size[0]/2 - output_width/2 
      originY = 0 
     else: 
      output_width = image.size[0] 
      output_height = y * image.size[0]/x 
      originX = 0 
      originY = image.size[1]/2 - output_height/2 

     #crop 
     cropBox = (originX, originY, originX + output_width, originY + output_height) 
     image = image.crop(cropBox) 

     # resize (doing a thumb) 
     image.thumbnail([x, y], Image.ANTIALIAS) 

     # re-initialize imageFile and set a hash (unique filename) 
     imagefile = StringIO.StringIO() 
     filename = hashlib.md5(imagefile.getvalue()).hexdigest()+’.jpg’ 

     #save to disk 
     imagefile = open(os.path.join(‘/tmp’,filename), ‘w’) 
     image.save(imagefile,’JPEG’, quality=90) 
     imagefile = open(os.path.join(‘/tmp’,filename), ‘r’) 
     content = File(imagefile) 

     return (filename, content) 

#views.py 

    form = YourModelForm(request.POST, request.FILES, instance=profile) 
     if form.is_valid(): 
      ob = form.save(commit=False) 
      try: 
       t = handle_uploaded_image(request.FILES[‘icon’]) 
       ob.image.save(t[0],t[1]) 
      except KeyError: 
       ob.save() 
+0

MISE À JOUR: Ceci accepte les images utf8 filename –

+2

Votre méthode fonctionne correctement. Merci pour le code! Je suggérerais, cependant, que vous changiez le nom de variable ci-dessus de str en quelque chose d'autre, parce qu'il ombrage la fonction de BIF de python str(). Si quelqu'un change un peu le code que vous avez écrit et utilise le BIF ci-dessous la déclaration de la variable, cela provoquera une erreur (python dirait que str n'est pas callable) – rvnovaes

+0

Mis à jour, merci! –

2

je sais que c'est vieux, mais pour tout le monde trébuchant sur elle, il y a un paquet, django-thumbs à Django-thumbs - Easy powerful thumbnails for Django integrated with StorageBackend, qui génère automatiquement des vignettes dans les tailles que vous spécifiez, ou rien si vous ne le faites pas. Vous appelez ensuite la vignette que vous voulez avec les dimensions que vous voulez. Par exemple, si vous voulez qu'une image ait des vignettes de 64x64 et 128x128, vous devez simplement importer thumbs.models.ImageWithThumbsField et l'utiliser à la place de ImageField. Ajout d'un paramètre sizes=((64,64),(128,128)) à la définition du champ, puis à partir de votre modèle, vous pouvez appeler:

{{ ClassName.field_name.url_64x64 }} 

et

{{ ClassName.field_name.url_128x128 }} 

pour afficher les vignettes. Voila! Tout le travail est fait dans ce paquet pour vous.

+0

Si vous utilisez du Sud pour la maintenance de base de données, vous aurez également besoin d'ajouter une introspection, comme tel: – Furbeenator

+0

'de add_introspection_rules d'importation south.modelsinspector add_introspection_rules ([ ( [models.ImageField], # Classe (s) elles s'appliquent à [], # arguments positionnels (non utilisés) {# argument de mots-clés "tailles": [ "tailles", { "par défaut": none}], }, ), ], [ "^ django_thumbs \. db \ .models \ .ImageWithThumbsField "])' – Furbeenator

2

Voici une solution complète pour vous en utilisant un formulaire. Je vues admin pour cela:

class MyInventoryItemForm(forms.ModelForm): 

    class Meta: 
     model = InventoryItem 
     exclude = ['thumbnail', 'price', 'active'] 

    def clean_photo(self): 
     import StringIO 
     image_field = self.cleaned_data['photo'] 
     photo_new = StringIO.StringIO(image_field.read()) 

     try: 
      from PIL import Image, ImageOps 

     except ImportError: 
      import Image 
      import ImageOps 

     image = Image.open(photo_new) 

     # ImageOps compatible mode 
     if image.mode not in ("L", "RGB"): 
      image = image.convert("RGB") 

     image.thumbnail((200, 200), Image.ANTIALIAS) 

     image_file = StringIO.StringIO() 
     image.save(image_file, 'png') 

     image_field.file = image_file 

     return image_field 

Mon modèle d'inventaire ressemble à ceci:

class InventoryItem(models.Model): 

    class Meta: 
     ordering = ['name'] 
     verbose_name_plural = "Items" 

    def get_absolute_url(self): 
     return "/products/{0}/".format(self.slug) 

    def get_file_path(instance, filename): 

     if InventoryItem.objects.filter(pk=instance.pk): 
      cur_inventory = InventoryItem.objects.get(pk=instance.pk) 
      if cur_inventory.photo: 
       old_filename = str(cur_inventory.photo) 
       os.remove(os.path.join(MEDIA_ROOT, old_filename)) 

     ext = filename.split('.')[-1] 
     filename = "{0}.{1}".format(uuid.uuid4(), ext) 
     return os.path.join('inventory', filename) 
     #return os.path.join(filename) 

    def admin_image(self): 
     return '<img height="50px" src="{0}/{1}"/>'.format(MEDIA_URL, self.photo) 
    admin_image.allow_tags = True 

    photo = models.ImageField(_('Image'), upload_to=get_file_path, storage=fs, blank=False, null=False) 
    thumbnail = models.ImageField(_('Thumbnail'), upload_to="thumbnails/", storage=fs,  blank=True, null=True) 

....

j'ai fini le lieu d'écraser la fonction d'économie du modèle pour enregistrer la photo et un pouce au lieu de redimensionner juste la photo:

def save(self): 

    # Save this photo instance first 
    super(InventoryItem, self).save() 

    from PIL import Image 
    from cStringIO import StringIO 
    from django.core.files.uploadedfile import SimpleUploadedFile 

    # Set our max thumbnail size in a tuple (max width, max height) 
    THUMBNAIL_SIZE = (200, 200) 

    # Open original photo which we want to thumbnail using PIL's Image object 
    image = Image.open(os.path.join(MEDIA_ROOT, self.photo.name)) 

    if image.mode not in ('L', 'RGB'): 
     image = image.convert('RGB') 

    image.thumbnail(THUMBNAIL_SIZE, Image.ANTIALIAS) 

    # Save the thumbnail 
    temp_handle = StringIO() 
    image.save(temp_handle, 'png') # image stored to stringIO 

    temp_handle.seek(0) # sets position of file to 0 

    # Save to the thumbnail field 
    suf = SimpleUploadedFile(os.path.split(self.photo.name)[-1], 
     temp_handle.read(), content_type='image/png') # reads in the file to save it 

    self.thumbnail.save(suf.name+'.png', suf, save=False) 

    #Save this photo instance again to save the thumbnail 
    super(InventoryItem, self).save() 

Les deux fonctionnent très bien en fonction de ce que vous voulez faire :)