models.py 6.64 KB
Newer Older
1
import hashlib
2
3
import os
import random
Thom Wiggers's avatar
Thom Wiggers committed
4
import logging
5

6
7
8
from django.conf import settings
from django.db import models
from django.urls import reverse
9
from django.utils import timezone
10
from django.utils.functional import cached_property
11
from django.utils.translation import ugettext_lazy as _
12
from PIL import Image
13

14
from members.models import Member
15
from photos.services import photo_determine_rotation
16
from utils.translation import ModelTranslateMeta, MultilingualField
17
from pushnotifications.models import ScheduledMessage, Category
18

19

20
COVER_FILENAME = 'cover.jpg'
21

22

Thom Wiggers's avatar
Thom Wiggers committed
23
24
25
logger = logging.getLogger(__name__)


26
def photo_uploadto(instance, filename):
27
    num = instance.album.photo_set.count()
28
    extension = os.path.splitext(filename)[1]
29
30
    new_filename = str(num).zfill(4) + extension
    return os.path.join(Album.photosdir, instance.album.dirname, new_filename)
31
32
33


class Photo(models.Model):
34

35
36
37
38
39
40
41
42
43
44
45
    album = models.ForeignKey(
        'Album',
        on_delete=models.CASCADE,
        verbose_name=_("album")
    )

    file = models.ImageField(
        _('file'),
        upload_to=photo_uploadto
    )

Joost Rijneveld's avatar
Joost Rijneveld committed
46
    rotation = models.IntegerField(
47
        verbose_name=_('rotation'),
Joost Rijneveld's avatar
Joost Rijneveld committed
48
49
        default=0,
        choices=((x, x) for x in (0, 90, 180, 270)),
50
51
52
53
54
55
        help_text=_('This does not modify the original image file.'),
    )

    hidden = models.BooleanField(
        _('hidden'),
        default=False
Joost Rijneveld's avatar
Joost Rijneveld committed
56
    )
57

58
59
60
61
62
    _digest = models.CharField(
        'digest',
        max_length=40,
    )

63
64
65
66
67
68
69
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.file:
            self._orig_file = self.file.path
        else:
            self._orig_file = ""

70
    def __str__(self):
Tom van Bussel's avatar
Tom van Bussel committed
71
        return os.path.basename(self.file.name)
72

73
    def save(self, *args, **kwargs):
74
        super().save(*args, **kwargs)
75

76
        if self._orig_file != self.file.path:
77
            image_path = self.file.path
78
            image = Image.open(image_path)
Thom Wiggers's avatar
Thom Wiggers committed
79
80
            image_path, _ext = os.path.splitext(image_path)
            image_path = "{}.jpg".format(image_path)
81

82
            self.rotation = photo_determine_rotation(image)
83

84
            # Image.thumbnail does not upscale an image that is smaller
85
            image.thumbnail(settings.PHOTO_UPLOAD_SIZE, Image.ANTIALIAS)
Thom Wiggers's avatar
Thom Wiggers committed
86
87
88
89
90
91

            logger.info("Trying to save to %s", image_path)
            image.convert("RGB").save(image_path, "JPEG")
            self._orig_file = image_path
            image_name, _ext = os.path.splitext(self.file.name)
            self.file.name = "{}.jpg".format(image_name)
92

93
94
95
            hash_sha1 = hashlib.sha1()
            for chunk in iter(lambda: self.file.read(4096), b""):
                hash_sha1.update(chunk)
Tom van Bussel's avatar
Tom van Bussel committed
96
            self.file.close()
97
98
            self._digest = hash_sha1.hexdigest()

99
100
101
            # Save again, to update changes in digest and rotation
            super().save(*args, **kwargs)

102
103
104
    class Meta:
        ordering = ('file', )

105

106
107
108
109
class Album(models.Model, metaclass=ModelTranslateMeta):
    title = MultilingualField(
        models.CharField,
        _("title"),
110
111
112
113
114
115
116
117
118
119
120
121
122
123
        max_length=200,
    )

    dirname = models.CharField(
        verbose_name=_('directory name'),
        max_length=200,
    )

    date = models.DateField(
        verbose_name=_('date'),
    )

    slug = models.SlugField(
        verbose_name=_('slug'),
Tom van Bussel's avatar
Tom van Bussel committed
124
        unique=True,
125
126
127
128
129
130
131
    )

    hidden = models.BooleanField(
        verbose_name=_('hidden'),
        default=False
    )

132
133
134
135
136
    new_album_notification = models.ForeignKey(
        ScheduledMessage, on_delete=models.deletion.SET_NULL,
        blank=True, null=True
    )

137
138
139
140
141
142
143
144
145
146
147
148
149
    _cover = models.OneToOneField(
        Photo,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
        related_name='covered_album',
        verbose_name=_('cover image'),
    )

    shareable = models.BooleanField(
        verbose_name=_('shareable'),
        default=False
    )
150
151
152
153
154
155

    photosdir = 'photos'
    photospath = os.path.join(settings.MEDIA_ROOT, photosdir)

    @cached_property
    def cover(self):
156
        cover = None
157
158
        if self._cover is not None:
            return self._cover
159
        elif self.photo_set.exists():
160
            random.seed(self.dirname)
161
162
            cover = random.choice(self.photo_set.all())
        return cover
163

164
165
166
167
    def __str__(self):
        return '{} {}'.format(self.date.strftime('%Y-%m-%d'), self.title)

    def get_absolute_url(self):
168
        return reverse('photos:album', args=[str(self.slug)])
169
170

    def save(self, *args, **kwargs):
171
172
173
        # dirname is only set for new objects, to avoid ever changing it
        if self.pk is None:
            self.dirname = self.slug
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193

        if (not self.hidden and (self.new_album_notification is None
                                 or not self.new_album_notification.sent)):
            new_album_notification_time = (timezone.now() +
                                           timezone.timedelta(hours=1))
            new_album_notification = ScheduledMessage()

            if (self.new_album_notification is not None
                    and not self.new_album_notification.sent):
                new_album_notification = self.new_album_notification

            new_album_notification.title_en = 'New album uploaded'
            new_album_notification.title_nl = 'Nieuw album geüpload'
            new_album_notification.body_en = ('A new photo album \'{}\' has '
                                              'just been uploaded'
                                              .format(self.title_en))
            new_album_notification.body_nl = ('Een nieuw fotoalbum \'{}\' is '
                                              'zojuist geüpload'
                                              .format(self.title_nl))
            new_album_notification.category = Category.objects.get(key='photo')
194
195
196
            new_album_notification.url = (
                        f'{settings.BASE_URL}'
                        f'{self.get_absolute_url()}')
197
198
199
200
201
202
203
204
205
206
            new_album_notification.time = new_album_notification_time
            new_album_notification.save()
            self.new_album_notification = new_album_notification
            self.new_album_notification.users.set(Member.current_members.all())
        elif (self.hidden and self.new_album_notification is not None
              and not self.new_album_notification.sent):
            existing_notification = self.new_album_notification
            self.new_album_notification = None
            existing_notification.delete()

207
        super().save(*args, **kwargs)
208
209
210
211
212

    @property
    def access_token(self):
        return hashlib.sha256('{}album{}'.format(settings.SECRET_KEY, self.pk)
                              .encode('utf-8')).hexdigest()
213
214

    class Meta:
215
        ordering = ('-date', 'title_en')