Používej na miniatury django-imagekit
místo vlastních funkcí volaných v save() * migrace seminar i galerie * miniatury (a "střední velikosti") se nyní vytvářejí podle potřeby v media/CACHE
This commit is contained in:
		
							parent
							
								
									577ac83d04
								
							
						
					
					
						commit
						b3229fafe1
					
				
					 6 changed files with 85 additions and 134 deletions
				
			
		
							
								
								
									
										22
									
								
								galerie/migrations/0006_django_imagekit.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								galerie/migrations/0006_django_imagekit.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | from __future__ import unicode_literals | ||||||
|  | 
 | ||||||
|  | from django.db import models, migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('galerie', '0005_obrazek_ordering_datum'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='obrazek', | ||||||
|  |             name='obrazek_maly', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='obrazek', | ||||||
|  |             name='obrazek_stredni', | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -5,6 +5,8 @@ import seminar.models | ||||||
| from django.db.models import Q | from django.db.models import Q | ||||||
| from django.utils import timezone | from django.utils import timezone | ||||||
| from django.utils.encoding import force_unicode | from django.utils.encoding import force_unicode | ||||||
|  | from imagekit.models import ImageSpecField | ||||||
|  | from imagekit.processors import ResizeToFit, Transpose | ||||||
| 
 | 
 | ||||||
| from PIL import Image | from PIL import Image | ||||||
| from PIL.ExifTags import TAGS | from PIL.ExifTags import TAGS | ||||||
|  | @ -32,47 +34,32 @@ def get_exif(fn): | ||||||
|         ret[decoded] = value |         ret[decoded] = value | ||||||
|     return ret |     return ret | ||||||
| 
 | 
 | ||||||
| def flip_horizontal(im): return im.transpose(Image.FLIP_LEFT_RIGHT) | # tyhle funkce jsou tady jen kvůli starým migracím, které se na ně odkazují | ||||||
| def flip_vertical(im): return im.transpose(Image.FLIP_TOP_BOTTOM) | # až se ty migrace někdy squashnou, tak by mělo být možné funkce smazat | ||||||
| def rotate_180(im): return im.transpose(Image.ROTATE_180) | def obrazek_filename_maly(): | ||||||
| def rotate_90(im): return im.transpose(Image.ROTATE_90) |     pass | ||||||
| def rotate_270(im): return im.transpose(Image.ROTATE_270) | def obrazek_filename_stredni(): | ||||||
| def transpose(im): return rotate_90(flip_horizontal(im)) |     pass | ||||||
| def transverse(im): return rotate_90(flip_vertical(im)) | def obrazek_filename_velky(): | ||||||
| orientation_funcs = [None, |     pass | ||||||
|                  lambda x: x, |  | ||||||
|                  flip_horizontal, |  | ||||||
|                  rotate_180, |  | ||||||
|                  flip_vertical, |  | ||||||
|                  transpose, |  | ||||||
|                  rotate_270, |  | ||||||
|                  transverse, |  | ||||||
|                  rotate_90 |  | ||||||
|                 ] |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| def obrazek_filename(self, filename): | def obrazek_filename(self, filename): | ||||||
|     return obrazek_filename_obecny(self, filename, "velky") |  | ||||||
| 
 |  | ||||||
| def obrazek_filename_stredni(self, filename): |  | ||||||
|     return obrazek_filename_obecny(self, filename, "stredni") |  | ||||||
| 
 |  | ||||||
| def obrazek_filename_maly(self, filename): |  | ||||||
|     return obrazek_filename_obecny(self, filename, "maly") |  | ||||||
| 
 |  | ||||||
| def obrazek_filename_obecny(self, filename, typ): |  | ||||||
|     gal = self.galerie |     gal = self.galerie | ||||||
|     cislo_gal = force_unicode(gal.pk) |     cislo_gal = force_unicode(gal.pk) | ||||||
|     cesta = "" |     cesta = "" | ||||||
|     while(not gal.soustredeni): |     while(not gal.soustredeni): | ||||||
|         gal = gal.galerie_up |         gal = gal.galerie_up | ||||||
|     return os.path.join('Galerie', "soustredeni_" + force_unicode(gal.soustredeni.pk), "galerie_" + cislo_gal, typ, force_unicode(self.nazev)) |     return os.path.join('Galerie', "soustredeni_" + force_unicode(gal.soustredeni.pk), "galerie_" + cislo_gal, "velky", force_unicode(self.nazev)) | ||||||
| 
 | 
 | ||||||
| class Obrazek(models.Model): | class Obrazek(models.Model): | ||||||
|   obrazek_velky = models.ImageField(upload_to=obrazek_filename, |   obrazek_velky = models.ImageField(upload_to=obrazek_filename, | ||||||
|     help_text = "Lze vložit libovolně velký obrázek. Ideální je, aby alespoň jeden rozměr měl alespoň 500px.") |     help_text = "Lze vložit libovolně velký obrázek. Ideální je, aby alespoň jeden rozměr měl alespoň 500px.") | ||||||
|   obrazek_stredni = models.ImageField(upload_to=obrazek_filename_stredni, null = True, editable = False) |   obrazek_stredni = ImageSpecField(source='obrazek_velky', | ||||||
|   obrazek_maly = models.ImageField(upload_to=obrazek_filename_maly, null = True, editable = False) |                                 processors=[Transpose(Transpose.AUTO), ResizeToFit(900, 675, upscale=False)], | ||||||
|  |                                 options={'quality': 95}) | ||||||
|  |   obrazek_maly = ImageSpecField(source='obrazek_velky', | ||||||
|  |                                 processors=[Transpose(Transpose.AUTO), ResizeToFit(167, 167, upscale=False)], | ||||||
|  |                                 options={'quality': 95}) | ||||||
|   nazev = models.CharField('Název', max_length=50, blank = True, null = True) |   nazev = models.CharField('Název', max_length=50, blank = True, null = True) | ||||||
|   popis = models.TextField('Popis', blank = True, null = True) |   popis = models.TextField('Popis', blank = True, null = True) | ||||||
|   datum_vlozeni = models.DateTimeField('Datum vložení', auto_now_add = True) |   datum_vlozeni = models.DateTimeField('Datum vložení', auto_now_add = True) | ||||||
|  | @ -89,55 +76,10 @@ class Obrazek(models.Model): | ||||||
|     original = Image.open(self.obrazek_velky) |     original = Image.open(self.obrazek_velky) | ||||||
|     # vycteni EXIFu |     # vycteni EXIFu | ||||||
|     exif = get_exif(original) |     exif = get_exif(original) | ||||||
|     # otoceni podle EXIFu |  | ||||||
|     if exif['Orientation']: |  | ||||||
|         f = orientation_funcs[exif['Orientation']] |  | ||||||
|         original_otoceny = f(original) |  | ||||||
|         original_otoceny.format = original.format |  | ||||||
|         original = original_otoceny |  | ||||||
|     # datum podle EXIfu |  | ||||||
|     if exif['DateTimeOriginal']: |     if exif['DateTimeOriginal']: | ||||||
|         datum_string = ":".join(exif['DateTimeOriginal'].split(' ')).split(":") |         datum_ints = map(int, ":".join(exif['DateTimeOriginal'].split(' ')).split(":")) | ||||||
|         datum_int = [] |         self.datum = datetime(*datum_ints) | ||||||
|         for retezec in datum_string: |  | ||||||
|             datum_int.append(int(retezec)) |  | ||||||
|         self.datum = datetime(datum_int[0], datum_int[1], datum_int[2], |  | ||||||
|                               datum_int[3], datum_int[4], datum_int[5]) |  | ||||||
|     jmeno = os.path.basename(self.obrazek_velky.file.name) |  | ||||||
|     if not self.obrazek_stredni: |  | ||||||
|       Obrazek._vyrobMiniaturu(original, jmeno, 1024, self.obrazek_stredni) |  | ||||||
|     if not self.obrazek_maly: |  | ||||||
|       Obrazek._vyrobMiniaturu(original, jmeno, 200, self.obrazek_maly) |  | ||||||
|     super(Obrazek, self).save() |     super(Obrazek, self).save() | ||||||
| 
 |  | ||||||
|   @staticmethod |  | ||||||
|   def _vyrobMiniaturu(original, jmeno, maximum, field): |  | ||||||
|     zmensenina = Obrazek._zmensiObrazek(original, maximum) |  | ||||||
|     f = StringIO() |  | ||||||
|     try: |  | ||||||
|       zmensenina.save(f, format=original.format) |  | ||||||
|       data = ContentFile(f.getvalue()) |  | ||||||
|     finally: |  | ||||||
|       f.close() |  | ||||||
|     field.save(jmeno, data, save = False) |  | ||||||
|    |  | ||||||
|   @staticmethod |  | ||||||
|   def _zmensiObrazek(original, maximum): |  | ||||||
|     """Preskaluje obrazek tak, aby byl zachovan pomer stran a zadny rozmer |  | ||||||
|     nepresahoval maxRozmer. Pokud zadny rozmer nepresahuje maxRozmer, tak |  | ||||||
|     vrati puvodni obrazek (tj. nedojde ke zvetseni obrazku).""" |  | ||||||
|     novaVelikost = Obrazek._zmensiVelikost(original.size, maximum) |  | ||||||
|     return original.resize(novaVelikost, Image.ANTIALIAS) |  | ||||||
| 
 |  | ||||||
|   @staticmethod |  | ||||||
|   def _zmensiVelikost(velikost, maximum): |  | ||||||
|     maximum = float(maximum) |  | ||||||
|     w, h = velikost |  | ||||||
|     soucasneMaximum = max(w, h) |  | ||||||
|     if soucasneMaximum <= maximum: |  | ||||||
|       return velikost |  | ||||||
|     pomer = maximum/soucasneMaximum |  | ||||||
|     return (int(w * pomer), int(h * pomer)) |  | ||||||
|    |    | ||||||
| 
 | 
 | ||||||
| class Galerie(models.Model): | class Galerie(models.Model): | ||||||
|  |  | ||||||
|  | @ -45,8 +45,8 @@ Galerie {{galerie.nazev}} | ||||||
|             {% if galerie.titulni_obrazek %} |             {% if galerie.titulni_obrazek %} | ||||||
|             {% with galerie.titulni_obrazek.obrazek_maly as obrazek %} |             {% with galerie.titulni_obrazek.obrazek_maly as obrazek %} | ||||||
|             <img src="{{ obrazek.url }}" |             <img src="{{ obrazek.url }}" | ||||||
|                 width={% widthratio obrazek.width 200 167 %} |                 width="{{ obrazek.width }}" | ||||||
|                 height={% widthratio obrazek.height 200 167 %} /> |                 height="{{ obrazek.height }}" /> | ||||||
|             {% endwith %} |             {% endwith %} | ||||||
|             {% endif %} |             {% endif %} | ||||||
|             <div> |             <div> | ||||||
|  | @ -76,8 +76,8 @@ Galerie {{galerie.nazev}} | ||||||
|     {% for obrazek in obrazky %} |     {% for obrazek in obrazky %} | ||||||
|         <a title="Zobrazit tuto fotografii" href="./{{obrazek.pk}}#nahoru" class="galerie_nahled"><span class="vystredeno"></span><img |         <a title="Zobrazit tuto fotografii" href="./{{obrazek.pk}}#nahoru" class="galerie_nahled"><span class="vystredeno"></span><img | ||||||
|             src="{{obrazek.obrazek_maly.url}}" |             src="{{obrazek.obrazek_maly.url}}" | ||||||
|             width={% widthratio obrazek.obrazek_maly.width 200 167 %} |             width="{{ obrazek.obrazek_maly.width }}" | ||||||
|             height={% widthratio obrazek.obrazek_maly.height 200 167 %} /> |             height="{{ obrazek.obrazek_maly.height }}" /> | ||||||
|         </a> |         </a> | ||||||
|     {% endfor %} |     {% endfor %} | ||||||
|     <br> |     <br> | ||||||
|  |  | ||||||
|  | @ -23,6 +23,7 @@ django-flat-theme==0.9.3 | ||||||
| django-taggit==0.17 | django-taggit==0.17 | ||||||
| django-autocomplete-light==2.2.6 | django-autocomplete-light==2.2.6 | ||||||
| django-crispy-forms==1.4.0 | django-crispy-forms==1.4.0 | ||||||
|  | django-imagekit==3.2.7 | ||||||
| 
 | 
 | ||||||
| # Comments | # Comments | ||||||
| akismet==0.2.0 | akismet==0.2.0 | ||||||
|  |  | ||||||
							
								
								
									
										25
									
								
								seminar/migrations/0035_django_imagekit.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								seminar/migrations/0035_django_imagekit.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | from __future__ import unicode_literals | ||||||
|  | 
 | ||||||
|  | from django.db import models, migrations | ||||||
|  | import imagekit.models.fields | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('seminar', '0034_reseni_forma_default_email'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='organizator', | ||||||
|  |             name='foto_male', | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='organizator', | ||||||
|  |             name='foto', | ||||||
|  |             field=imagekit.models.fields.ProcessedImageField(help_text=b'Vlo\xc5\xbe fotografii organiz\xc3\xa1tora o libovoln\xc3\xa9 velikosti', upload_to=b'image_organizatori/velke/%Y/', null=True, verbose_name=b'Fotografie organiz\xc3\xa1tora', blank=True), | ||||||
|  |             preserve_default=True, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -11,6 +11,8 @@ from django.utils.encoding import force_unicode | ||||||
| from django.utils.text import slugify | from django.utils.text import slugify | ||||||
| from django.core.urlresolvers import reverse | from django.core.urlresolvers import reverse | ||||||
| from django.core.cache import cache | from django.core.cache import cache | ||||||
|  | from imagekit.models import ImageSpecField, ProcessedImageField | ||||||
|  | from imagekit.processors import ResizeToFit, Transpose | ||||||
| 
 | 
 | ||||||
| from PIL import Image | from PIL import Image | ||||||
| import os | import os | ||||||
|  | @ -785,11 +787,19 @@ class Organizator(models.Model): | ||||||
|             "'Přednáší na MFF'") |             "'Přednáší na MFF'") | ||||||
|     strucny_popis_organizatora = models.TextField('Stručný popis organizátora', |     strucny_popis_organizatora = models.TextField('Stručný popis organizátora', | ||||||
|             null = True, blank = True) |             null = True, blank = True) | ||||||
|     foto = models.ImageField('Fotografie organizátora', |     foto = ProcessedImageField(verbose_name='Fotografie organizátora', | ||||||
|             upload_to='image_organizatori/velke/%Y/', null = True, blank = True, |             upload_to='image_organizatori/velke/%Y/', null = True, blank = True, | ||||||
|             help_text = 'Vlož fotografii organizátora o libovolné velikosti') |             help_text = 'Vlož fotografii organizátora o libovolné velikosti', | ||||||
|     foto_male = models.ImageField(upload_to='image_organizatori/male/%Y/', |             processors=[ | ||||||
|             null = True, blank = True, editable = False) |                 Transpose(Transpose.AUTO), | ||||||
|  |                 ResizeToFit(500, 500, upscale=False) | ||||||
|  |             ], | ||||||
|  |             options={'quality': 95}) | ||||||
|  |     foto_male = ImageSpecField(source='foto', | ||||||
|  |             processors=[ | ||||||
|  |                 ResizeToFit(200, 200, upscale=False) | ||||||
|  |             ], | ||||||
|  |             options={'quality': 95}) | ||||||
| 
 | 
 | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return str(self.user) |         return str(self.user) | ||||||
|  | @ -797,52 +807,3 @@ class Organizator(models.Model): | ||||||
|     class Meta: |     class Meta: | ||||||
|         verbose_name = 'Organizátor' |         verbose_name = 'Organizátor' | ||||||
|         verbose_name_plural = 'Organizátoři' |         verbose_name_plural = 'Organizátoři' | ||||||
| 
 |  | ||||||
|     def save(self): |  | ||||||
|         # v databázi uložený záznam o organizátorovi |  | ||||||
|         puvodni = None |  | ||||||
| 
 |  | ||||||
|         # pokud už organizátor v databázi existuje, nastav puvodni |  | ||||||
|         if self.id is not None: |  | ||||||
|             puvodni = Organizator.objects.get(id=self.id) |  | ||||||
|         # pokud nahráváme fotku |  | ||||||
|         if self.foto: |  | ||||||
|             # a je jiná než ta stará |  | ||||||
|             if not puvodni or puvodni.foto != self.foto: |  | ||||||
|                 # uložíme ji |  | ||||||
|                 original = Image.open(self.foto) |  | ||||||
|                 jmeno = os.path.basename(self.foto.file.name) |  | ||||||
|                 Organizator._vyrobMiniaturu(original, jmeno, 500, self.foto) |  | ||||||
|                 Organizator._vyrobMiniaturu(original, jmeno, 200, self.foto_male) |  | ||||||
|         super(Organizator, self).save() |  | ||||||
| 
 |  | ||||||
|     @staticmethod |  | ||||||
|     def _vyrobMiniaturu(original, jmeno, maximum, field): |  | ||||||
|         zmensenina = Organizator._zmensiObrazek(original, maximum) |  | ||||||
|         f = StringIO() |  | ||||||
|         try: |  | ||||||
|             zmensenina.save(f, format=original.format) |  | ||||||
|             data = ContentFile(f.getvalue()) |  | ||||||
|         finally: |  | ||||||
|             f.close() |  | ||||||
|         field.save(jmeno, data, save = False) |  | ||||||
|              |  | ||||||
|     @staticmethod |  | ||||||
|     def _zmensiObrazek(original, maximum): |  | ||||||
|         """Preskaluje obrazek tak, aby byl zachovan pomer stran |  | ||||||
|         a zadny rozmer nepresahoval maxRozmer. Pokud zadny rozmer |  | ||||||
|         nepresahuje maxRozmer, tak vrati puvodni obrazek |  | ||||||
|         (tj. nedojde ke zvetseni obrazku).""" |  | ||||||
|         novaVelikost = Organizator._zmensiVelikost(original.size, maximum) |  | ||||||
|         return original.resize(novaVelikost, Image.ANTIALIAS) |  | ||||||
| 
 |  | ||||||
|     @staticmethod |  | ||||||
|     def _zmensiVelikost(velikost, maximum): |  | ||||||
|         maximum = float(maximum) |  | ||||||
|         w, h = velikost |  | ||||||
|         soucasneMaximum = max(w, h) |  | ||||||
|         if soucasneMaximum <= maximum: |  | ||||||
|             return velikost |  | ||||||
|         pomer = maximum/soucasneMaximum |  | ||||||
|         return (int(w * pomer), int(h * pomer)) |  | ||||||
| 
 |  | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Matěj Kocián
						Matěj Kocián