Tested for Django 1.6-2.2
[django-sponsors.git] / sponsors / models.py
1 # -*- coding: utf-8 -*-
2 # This file is part of django-sponsors, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
4 #
5 from __future__ import unicode_literals
6
7 import time
8 try:
9     from io import BytesIO
10 except ImportError:
11     # Python 2
12     from StringIO import StringIO as BytesIO
13
14 from django.conf import settings
15 from django.core.files.base import ContentFile
16 from django.db import models
17 from django.utils.translation import ugettext_lazy as _
18 from django.template.loader import render_to_string
19 from PIL import Image
20 from jsonfield import JSONField
21
22
23 THUMB_WIDTH = getattr(settings, 'SPONSORS_THUMB_WIDTH', 120)
24 THUMB_HEIGHT = getattr(settings, 'SPONSORS_THUMB_HEIGHT', 120)
25
26
27 class Sponsor(models.Model):
28     name = models.CharField(_('name'), max_length=120)
29     _description = models.CharField(_('description'), blank=True, max_length=255)
30     logo = models.ImageField(_('logo'), upload_to='sponsorzy/sponsor/logo')
31     url = models.URLField(_('url'), blank=True)
32
33     def __unicode__(self):
34         return self.name
35
36     def description(self):
37         if len(self._description):
38             return self._description
39         else:
40             return self.name
41
42     def size(self):
43         width, height = THUMB_WIDTH, THUMB_HEIGHT
44         if width is None:
45             simg = Image.open(self.logo.path)
46             width = int(float(simg.size[0]) / simg.size[1] * height)
47         elif height is None:
48             simg = Image.open(self.logo.path)
49             height = int(float(simg.size[1]) / simg.size[0] * width)
50         return width, height
51
52
53 class SponsorPage(models.Model):
54     name = models.CharField(_('name'), max_length=120)
55     sponsors = JSONField(_('sponsors'), default={})
56     _html = models.TextField(blank=True, editable=False)
57     sprite = models.ImageField(upload_to='sponsorzy/sprite', blank=True)
58
59     def populated_sponsors(self):
60         result = []
61         offset = 0
62         for column in self.sponsors:
63             result_group = {'name': column['name'], 'sponsors': []}
64             sponsor_objects = Sponsor.objects.in_bulk(column['sponsors'])
65             for sponsor_pk in column['sponsors']:
66                 try:
67                     sponsor = sponsor_objects[sponsor_pk]
68                 except KeyError:
69                     pass
70                 else:
71                     result_group['sponsors'].append((offset, sponsor))
72                     offset -= sponsor.size()[1]
73             result.append(result_group)
74         return result
75
76     def render_sprite(self):
77         sponsor_ids = []
78         for column in self.sponsors:
79             sponsor_ids.extend(column['sponsors'])
80         sponsors = Sponsor.objects.in_bulk(sponsor_ids)
81         total_width = 0
82         total_height = 0
83         for sponsor in sponsors.values():
84             w, h = sponsor.size()
85             total_width = max(total_width, w)
86             total_height += h
87
88         if not total_height:
89             return
90
91         sprite = Image.new('RGBA', (total_width, total_height))
92         offset = 0
93         for i, sponsor_id in enumerate(sponsor_ids):
94             sponsor = sponsors[sponsor_id]
95             simg = Image.open(sponsor.logo.path)
96             thumb_size = sponsor.size()
97             # FIXME: This is too complicated now.
98             if simg.size[0] > thumb_size[0] or simg.size[1] > thumb_size[1]:
99                 size = (
100                     min(thumb_size[0], 
101                         simg.size[0] * thumb_size[1] / simg.size[1]),
102                     min(thumb_size[1],
103                         simg.size[1] * thumb_size[0] / simg.size[0])
104                 )
105                 simg = simg.resize(size, Image.ANTIALIAS)
106             sprite.paste(simg, (
107                     int((thumb_size[0] - simg.size[0]) / 2),
108                     int(offset + (thumb_size[1] - simg.size[1]) / 2),
109                     ))
110             offset += thumb_size[1]
111         imgstr = BytesIO()
112         sprite.save(imgstr, 'png')
113
114         if self.sprite:
115             self.sprite.delete(save=False)
116         self.sprite.save('sponsorzy/sprite/%s-%d.png' % (self.name, time.time()), ContentFile(imgstr.getvalue()), save=False)
117
118     def html(self):
119         return self._html
120     html = property(fget=html)
121
122     def save(self, *args, **kwargs):
123         self.render_sprite()
124         self._html = render_to_string('sponsors/page.html', {
125             'sponsors': self.populated_sponsors(),
126             'page': self,
127         })
128         return super(SponsorPage, self).save(*args, **kwargs)
129
130     def __unicode__(self):
131         return self.name
132