Add thumbnail generation

This commit is contained in:
Skia 2016-11-20 11:56:33 +01:00
parent 71d22e367b
commit 869634d6e1
12 changed files with 104 additions and 12 deletions

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('club', '0004_auto_20160915_1057'),
]
operations = [
migrations.AlterField(
model_name='club',
name='home',
field=models.OneToOneField(related_name='home_of_club', blank=True, on_delete=django.db.models.deletion.SET_NULL, verbose_name='home', null=True, to='core.SithFile'),
),
]

View File

@ -36,7 +36,8 @@ class Club(models.Model):
default=settings.SITH_GROUPS['root']['id']) default=settings.SITH_GROUPS['root']['id'])
edit_groups = models.ManyToManyField(Group, related_name="editable_club", blank=True) edit_groups = models.ManyToManyField(Group, related_name="editable_club", blank=True)
view_groups = models.ManyToManyField(Group, related_name="viewable_club", blank=True) view_groups = models.ManyToManyField(Group, related_name="viewable_club", blank=True)
home = models.OneToOneField(SithFile, related_name='home_of_club', verbose_name=_("home"), null=True, blank=True) home = models.OneToOneField(SithFile, related_name='home_of_club', verbose_name=_("home"), null=True, blank=True,
on_delete=models.SET_NULL)
def check_loop(self): def check_loop(self):
"""Raise a validation error when a loop is found within the parent list""" """Raise a validation error when a loop is found within the parent list"""

View File

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import core.models
class Migration(migrations.Migration):
dependencies = [
('core', '0008_sithfile_asked_for_removal'),
]
operations = [
migrations.AddField(
model_name='sithfile',
name='compressed',
field=models.FileField(upload_to=core.models.get_compressed_directory, null=True, verbose_name='compressed file', blank=True),
),
migrations.AddField(
model_name='sithfile',
name='thumbnail',
field=models.FileField(upload_to=core.models.get_thumbnail_directory, null=True, verbose_name='thumbnail', blank=True),
),
migrations.AlterField(
model_name='user',
name='home',
field=models.OneToOneField(verbose_name='home', related_name='home_of', on_delete=django.db.models.deletion.SET_NULL, null=True, to='core.SithFile', blank=True),
),
]

View File

@ -115,7 +115,8 @@ class User(AbstractBaseUser):
), ),
) )
groups = models.ManyToManyField(RealGroup, related_name='users', blank=True) groups = models.ManyToManyField(RealGroup, related_name='users', blank=True)
home = models.OneToOneField('SithFile', related_name='home_of', verbose_name=_("home"), null=True, blank=True) home = models.OneToOneField('SithFile', related_name='home_of', verbose_name=_("home"), null=True, blank=True,
on_delete=models.SET_NULL)
profile_pict = models.OneToOneField('SithFile', related_name='profile_of', verbose_name=_("profile"), null=True, profile_pict = models.OneToOneField('SithFile', related_name='profile_of', verbose_name=_("profile"), null=True,
blank=True, on_delete=models.SET_NULL) blank=True, on_delete=models.SET_NULL)
avatar_pict = models.OneToOneField('SithFile', related_name='avatar_of', verbose_name=_("avatar"), null=True, avatar_pict = models.OneToOneField('SithFile', related_name='avatar_of', verbose_name=_("avatar"), null=True,
@ -491,10 +492,18 @@ class Preferences(models.Model):
def get_directory(instance, filename): def get_directory(instance, filename):
return '.{0}/{1}'.format(instance.get_parent_path(), filename) return '.{0}/{1}'.format(instance.get_parent_path(), filename)
def get_compressed_directory(instance, filename):
return '.{0}/compressed/{1}'.format(instance.get_parent_path(), filename)
def get_thumbnail_directory(instance, filename):
return '.{0}/thumbnail/{1}'.format(instance.get_parent_path(), filename)
class SithFile(models.Model): class SithFile(models.Model):
name = models.CharField(_('file name'), max_length=256, blank=False) name = models.CharField(_('file name'), max_length=256, blank=False)
parent = models.ForeignKey('self', related_name="children", verbose_name=_("parent"), null=True, blank=True) parent = models.ForeignKey('self', related_name="children", verbose_name=_("parent"), null=True, blank=True)
file = models.FileField(upload_to=get_directory, verbose_name=_("file"), null=True, blank=True) file = models.FileField(upload_to=get_directory, verbose_name=_("file"), null=True, blank=True)
compressed = models.FileField(upload_to=get_compressed_directory, verbose_name=_("compressed file"), null=True, blank=True)
thumbnail = models.FileField(upload_to=get_thumbnail_directory, verbose_name=_("thumbnail"), null=True, blank=True)
owner = models.ForeignKey(User, related_name="owned_files", verbose_name=_("owner")) owner = models.ForeignKey(User, related_name="owned_files", verbose_name=_("owner"))
edit_groups = models.ManyToManyField(Group, related_name="editable_files", verbose_name=_("edit group"), blank=True) edit_groups = models.ManyToManyField(Group, related_name="editable_files", verbose_name=_("edit group"), blank=True)
view_groups = models.ManyToManyField(Group, related_name="viewable_files", verbose_name=_("view group"), blank=True) view_groups = models.ManyToManyField(Group, related_name="viewable_files", verbose_name=_("view group"), blank=True)
@ -528,6 +537,10 @@ class SithFile(models.Model):
for c in self.children.all(): for c in self.children.all():
c.delete() c.delete()
self.file.delete() self.file.delete()
if self.compressed:
self.compressed.delete()
if self.thumbnail:
self.thumbnail.delete()
return super(SithFile, self).delete() return super(SithFile, self).delete()
def clean(self): def clean(self):
@ -605,7 +618,7 @@ class SithFile(models.Model):
return l return l
def get_parent_path(self): def get_parent_path(self):
return '/' + '/'.join([p.name for p in self.get_parent_list()]) return '/' + '/'.join([p.name for p in self.get_parent_list()[::-1]])
def get_display_name(self): def get_display_name(self):
return self.name return self.name

View File

@ -13,7 +13,7 @@ def scale_dimension(width, height, long_edge):
ratio = long_edge * 1. / height ratio = long_edge * 1. / height
return int(width * ratio), int(height * ratio) return int(width * ratio), int(height * ratio)
def resize_image(im, edge, format): # TODO move that into a utils file def resize_image(im, edge, format):
(w, h) = im.size (w, h) = im.size
(width, height) = scale_dimension(w, h, long_edge=edge) (width, height) = scale_dimension(w, h, long_edge=edge)
content = BytesIO() content = BytesIO()

View File

@ -19,7 +19,7 @@ import os
from core.models import SithFile from core.models import SithFile
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, can_view, not_found from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, can_view, not_found
def send_file(request, file_id, file_class=SithFile): def send_file(request, file_id, file_class=SithFile, file_attr="file"):
""" """
Send a file through Django without loading the whole file into Send a file through Django without loading the whole file into
memory at once. The FileWrapper will turn the file object into an memory at once. The FileWrapper will turn the file object into an
@ -35,7 +35,7 @@ def send_file(request, file_id, file_class=SithFile):
Counter.objects.filter(token=request.session['counter_token']).exists()) Counter.objects.filter(token=request.session['counter_token']).exists())
): ):
raise PermissionDenied raise PermissionDenied
name = f.file.name name = f.__getattribute__(file_attr).name
with open(settings.MEDIA_ROOT + name, 'rb') as filename: with open(settings.MEDIA_ROOT + name, 'rb') as filename:
wrapper = FileWrapper(filename) wrapper = FileWrapper(filename)
response = HttpResponse(wrapper, content_type=f.mime_type) response = HttpResponse(wrapper, content_type=f.mime_type)

View File

@ -24,6 +24,12 @@ class Picture(SithFile):
def get_download_url(self): def get_download_url(self):
return reverse('sas:download', kwargs={'picture_id': self.id}) return reverse('sas:download', kwargs={'picture_id': self.id})
def get_download_compressed_url(self):
return reverse('sas:download_compressed', kwargs={'picture_id': self.id})
def get_download_thumb_url(self):
return reverse('sas:download_thumb', kwargs={'picture_id': self.id})
def get_next(self): def get_next(self):
return self.parent.children.exclude(is_moderated=False, asked_for_removal=True).filter(id__gt=self.id).order_by('id').first() return self.parent.children.exclude(is_moderated=False, asked_for_removal=True).filter(id__gt=self.id).order_by('id').first()

View File

@ -28,7 +28,7 @@
{% if p.as_picture.can_be_viewed_by(user) %} {% if p.as_picture.can_be_viewed_by(user) %}
<div style="display: inline-block; border: solid 1px black; width: 9%; margin: 0.1%"> <div style="display: inline-block; border: solid 1px black; width: 9%; margin: 0.1%">
<a href="{{ url("sas:picture", picture_id=p.id) }}#pict"> <a href="{{ url("sas:picture", picture_id=p.id) }}#pict">
<img src="{{ p.as_picture.get_download_url() }}" alt="{{ p.get_display_name() }}" style="max-width: 100%"/> <img src="{{ p.as_picture.get_download_thumb_url() }}" alt="{{ p.get_display_name() }}" style="max-width: 100%"/>
</a> </a>
</div> </div>
{% endif %} {% endif %}

View File

@ -17,7 +17,7 @@
{% endif %} {% endif %}
<p> <p>
<a href="{{ url("sas:picture", picture_id=p.id) }}"> <a href="{{ url("sas:picture", picture_id=p.id) }}">
<img src="{{ url('sas:download', picture_id=p.id) }}" alt="{{ p.name }}" style="width: 100px"> <img src="{{ p.get_download_thumb_url() }}" alt="{{ p.name }}" style="width: 100px">
</a><br/> </a><br/>
{% trans %}Full name: {% endtrans %}{{ p.get_parent_path()+'/'+p.name }}<br/> {% trans %}Full name: {% endtrans %}{{ p.get_parent_path()+'/'+p.name }}<br/>
{% trans %}Owner: {% endtrans %}{{ p.owner.get_display_name() }}<br/> {% trans %}Owner: {% endtrans %}{{ p.owner.get_display_name() }}<br/>

View File

@ -35,21 +35,21 @@
{{ print_path(picture.parent) }} {{ picture.get_display_name() }} {{ print_path(picture.parent) }} {{ picture.get_display_name() }}
<h3>{{ picture.get_display_name() }}</h3> <h3>{{ picture.get_display_name() }}</h3>
<div style="display: inline-block; width: 89%; background: #333;" id="pict"> <div style="display: inline-block; width: 89%; background: #333;" id="pict">
<img src="{{ picture.get_download_url() }}" alt="{{ picture.get_display_name() }}" style="width: 90%; display: block; margin: auto"/> <img src="{{ picture.get_download_compressed_url() }}" alt="{{ picture.get_display_name() }}" style="width: 90%; display: block; margin: auto"/>
</div> </div>
<div style="display: inline-block; width: 10%; vertical-align: top;"> <div style="display: inline-block; width: 10%; vertical-align: top;">
<div> <div>
<div id="prev"> <div id="prev">
{% if picture.get_previous() %} {% if picture.get_previous() %}
<a href="{{ url("sas:picture", picture_id=picture.get_previous().id) }}#pict"> <a href="{{ url("sas:picture", picture_id=picture.get_previous().id) }}#pict">
<img src="{{ picture.get_previous().as_picture.get_download_url() }}" alt="{{ picture.get_previous().get_display_name() }}" /> <img src="{{ picture.get_previous().as_picture.get_download_thumb_url() }}" alt="{{ picture.get_previous().get_display_name() }}" />
</a> </a>
{% endif %} {% endif %}
</div> </div>
<div id="next"> <div id="next">
{% if picture.get_next() %} {% if picture.get_next() %}
<a href="{{ url("sas:picture", picture_id=picture.get_next().id) }}#pict"> <a href="{{ url("sas:picture", picture_id=picture.get_next().id) }}#pict">
<img src="{{ picture.get_next().as_picture.get_download_url() }}" alt="{{ picture.get_next().get_display_name() }}" /> <img src="{{ picture.get_next().as_picture.get_download_thumb_url() }}" alt="{{ picture.get_next().get_display_name() }}" />
</a> </a>
{% endif %} {% endif %}
</div> </div>
@ -73,6 +73,9 @@
<p><input type="submit" value="{% trans %}Go{% endtrans %}" /></p> <p><input type="submit" value="{% trans %}Go{% endtrans %}" /></p>
</form> </form>
</div> </div>
<p>
<a href="{{ picture.get_download_url() }}">{% trans %}HD version{% endtrans %}</a>
</p>
<p style="font-size: smaller;"> <p style="font-size: smaller;">
<a href="?ask_removal">{% trans %}Ask for removal{% endtrans %}</a> <a href="?ask_removal">{% trans %}Ask for removal{% endtrans %}</a>
</p> </p>

View File

@ -8,6 +8,8 @@ urlpatterns = [
url(r'^album/(?P<album_id>[0-9]+)$', AlbumView.as_view(), name='album'), url(r'^album/(?P<album_id>[0-9]+)$', AlbumView.as_view(), name='album'),
url(r'^picture/(?P<picture_id>[0-9]+)$', PictureView.as_view(), name='picture'), url(r'^picture/(?P<picture_id>[0-9]+)$', PictureView.as_view(), name='picture'),
url(r'^picture/(?P<picture_id>[0-9]+)/download$', send_pict, name='download'), url(r'^picture/(?P<picture_id>[0-9]+)/download$', send_pict, name='download'),
url(r'^picture/(?P<picture_id>[0-9]+)/download/compressed$', send_compressed, name='download_compressed'),
url(r'^picture/(?P<picture_id>[0-9]+)/download/thumb$', send_thumb, name='download_thumb'),
# url(r'^album/new$', AlbumCreateView.as_view(), name='album_new'), # url(r'^album/new$', AlbumCreateView.as_view(), name='album_new'),
# url(r'^(?P<club_id>[0-9]+)/$', ClubView.as_view(), name='club_view'), # url(r'^(?P<club_id>[0-9]+)/$', ClubView.as_view(), name='club_view'),
] ]

View File

@ -23,6 +23,9 @@ class SASForm(forms.Form):
required=False) required=False)
def process(self, parent, owner, files, automodere=False): def process(self, parent, owner, files, automodere=False):
from core.utils import resize_image
from io import BytesIO
from PIL import Image
try: try:
if self.cleaned_data['album_name'] != "": if self.cleaned_data['album_name'] != "":
album = Album(parent=parent, name=self.cleaned_data['album_name'], owner=owner, is_moderated=automodere) album = Album(parent=parent, name=self.cleaned_data['album_name'], owner=owner, is_moderated=automodere)
@ -36,8 +39,15 @@ class SASForm(forms.Form):
is_folder=False, is_moderated=automodere) is_folder=False, is_moderated=automodere)
try: try:
new_file.clean() new_file.clean()
# TODO: generate thumbnail im = Image.open(BytesIO(f.read()))
thumb = resize_image(im, 200, f.content_type.split('/')[-1])
compressed = resize_image(im, 600, f.content_type.split('/')[-1])
new_file.thumbnail = thumb
new_file.thumbnail.name = new_file.name
new_file.compressed = compressed
new_file.compressed.name = new_file.name
new_file.save() new_file.save()
print(new_file.compressed)
except Exception as e: except Exception as e:
self.add_error(None, _("Error uploading file %(file_name)s: %(msg)s") % {'file_name': f, 'msg': repr(e)}) self.add_error(None, _("Error uploading file %(file_name)s: %(msg)s") % {'file_name': f, 'msg': repr(e)})
@ -120,6 +130,12 @@ class PictureView(CanViewMixin, DetailView, FormMixin):
def send_pict(request, picture_id): def send_pict(request, picture_id):
return send_file(request, picture_id, Picture) return send_file(request, picture_id, Picture)
def send_compressed(request, picture_id):
return send_file(request, picture_id, Picture, "compressed")
def send_thumb(request, picture_id):
return send_file(request, picture_id, Picture, "thumbnail")
class AlbumView(CanViewMixin, DetailView, FormMixin): class AlbumView(CanViewMixin, DetailView, FormMixin):
model = Album model = Album
form_class = SASForm form_class = SASForm