Covers from MPW, Rawpixel.
[redakcja.git] / src / cover / views.py
1 # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
3 #
4 import os.path
5 from django.conf import settings
6 from django.contrib.auth.decorators import permission_required
7 from django.http import HttpResponse, HttpResponseRedirect, Http404
8 from django.shortcuts import get_object_or_404, render
9 from django.views.decorators.csrf import csrf_exempt
10 from django.views.decorators.http import require_POST
11 from lxml import etree
12 from librarian import RDFNS, DCNS
13 from documents.helpers import active_tab
14 from documents.models import Book, Chunk
15 from cover.models import Image
16 from cover import forms
17 from cover.utils import get_import_data
18
19
20 PREVIEW_SIZE = (216, 300)
21
22
23 def preview(request, book, chunk=None, rev=None):
24     """Creates a cover image.
25
26     If chunk and rev number are given, use version from given revision.
27     If rev is not given, use publishable version.
28     """
29     from PIL import Image
30     from librarian.cover import make_cover
31     from librarian.dcparser import BookInfo
32
33     chunk = Chunk.get(book, chunk)
34     if rev is not None:
35         try:
36             revision = chunk.at_revision(rev)
37         except Chunk.change_model.DoesNotExist:
38             raise Http404
39     else:
40         revision = chunk.publishable()
41         if revision is None:
42             revision = chunk.head
43     xml = revision.materialize().encode('utf-8')
44
45     try:
46         info = BookInfo.from_bytes(xml)
47     except:
48         return HttpResponseRedirect(os.path.join(settings.STATIC_URL, "img/sample_cover.png"))
49     width = request.GET.get('width')
50     width = int(width) if width else None
51     height=request.GET.get('height')
52     height = int(height) if height else None
53     cover = make_cover(info, width=width, height=height)
54     #cover = make_cover(info)
55     response = HttpResponse(content_type=cover.mime_type())
56     if height or width:
57         size = (width, height)
58     else:
59         size = PREVIEW_SIZE
60     img = cover.image().resize(size, Image.ANTIALIAS)
61     img.save(response, cover.format)
62
63     if 'download' in request.GET:
64         response['Content-Disposition'] = 'attachment; filename=%s.jpg' % chunk.book.slug
65
66     return response
67
68
69 @csrf_exempt
70 @require_POST
71 def preview_from_xml(request):
72     from hashlib import sha1
73     from PIL import Image
74     from os import makedirs
75     from lxml import etree
76     from librarian.cover import make_cover
77     from librarian.dcparser import BookInfo
78
79     xml = request.POST['xml']
80     try:
81         info = BookInfo.from_bytes(xml.encode('utf-8'))
82     except:
83         return HttpResponse(os.path.join(settings.STATIC_URL, "img/sample_cover.png"))
84     coverid = sha1(etree.tostring(info.to_etree())).hexdigest()
85     cover = make_cover(info)
86
87     cover_dir = 'cover/preview'
88     try:
89         makedirs(os.path.join(settings.MEDIA_ROOT, cover_dir))
90     except OSError:
91         pass
92     fname = os.path.join(cover_dir, "%s.%s" % (coverid, cover.ext()))
93     img = cover.image().resize(PREVIEW_SIZE, Image.ANTIALIAS)
94     img.save(os.path.join(settings.MEDIA_ROOT, fname))
95     return HttpResponse(os.path.join(settings.MEDIA_URL, fname))
96
97
98 @active_tab('cover')
99 def image(request, pk):
100     img = get_object_or_404(Image, pk=pk)
101
102     if request.user.has_perm('cover.change_image'):
103         if request.method == "POST":
104             form = forms.ImageEditForm(request.POST, request.FILES, instance=img)
105             if form.is_valid():
106                 form.save()
107                 return HttpResponseRedirect(img.get_absolute_url())
108         else:
109             form = forms.ImageEditForm(instance=img)
110         editable = True
111     else:
112         form = forms.ReadonlyImageEditForm(instance=img)
113         editable = False
114
115     return render(request, "cover/image_detail.html", {
116         "object": Image.objects.get(id=img.id),
117         "form": form,
118         "editable": editable,
119     })
120
121
122 def image_file(request, pk):
123     img = get_object_or_404(Image, pk=pk)
124     return HttpResponseRedirect(img.file.url)
125
126
127 @active_tab('cover')
128 def image_list(request):
129     return render(request, "cover/image_list.html", {
130         'object_list': Image.objects.all().order_by('-id'),
131         'can_add': request.user.has_perm('cover.add_image'),
132     })
133
134
135 @permission_required('cover.add_image')
136 @active_tab('cover')
137 def add_image(request):
138     form = ff = None
139     if request.method == 'POST':
140         if request.POST.get('form_id') == 'import':
141             ff = forms.ImportForm(request.POST)
142             if ff.is_valid():
143                 form = forms.ImageAddForm(ff.cleaned_data)
144         else:
145             form = forms.ImageAddForm(request.POST, request.FILES)
146             if form.is_valid():
147                 obj = form.save()
148                 return HttpResponseRedirect(obj.get_absolute_url())
149     if form is None:
150         form = forms.ImageAddForm()
151     if ff is None:
152         ff = forms.ImportForm()
153     return render(request, 'cover/add_image.html', {
154             'form': form,
155             'ff': ff,
156         })
157
158 @permission_required('cover.add_image')
159 def quick_import(request, pk):
160     url = request.POST.get('url')
161     if url.startswith('%s://%s/' % (
162             request.scheme,
163             request.get_host())):
164         cover_id = url.rsplit('/', 1)[-1]
165         cover = Image.objects.get(pk=cover_id)
166     else:
167         data = get_import_data(url)
168         same = Image.objects.filter(source_url=data['source_url'])
169         if not same.exists():
170             same = Image.objects.filter(download_url=data['download_url'])
171         if same.exists():
172             cover = same.first()
173         else:
174             form = forms.ImageAddForm(data)
175             if form.is_valid():
176                 cover = form.save()
177
178     # We have a cover. Now let's commit.
179     book = Book.objects.get(pk=pk)
180     chunk = book[0]
181     text = chunk.head.materialize()
182
183     root = etree.fromstring(text)
184     rdf = root.find('.//' + RDFNS('Description'))
185     for tag in 'url', 'attribution', 'source':
186         for elem in rdf.findall('.//' + DCNS('relation.coverImage.%s' % tag)):
187             rdf.remove(elem)
188     e = etree.Element(DCNS('relation.coverImage.url'))
189     e.text = request.build_absolute_uri(cover.use_file.url)
190     rdf.append(e)
191     e.tail = '\n    '
192     e = etree.Element(DCNS('relation.coverImage.attribution'))
193     e.text = ''
194     if cover.title:
195         e.text += cover.title + ', '
196     if cover.author:
197         e.text += cover.author + ', '
198     e.text += cover.license_name
199     e.tail = '\n    '
200     rdf.append(e)
201     e = etree.Element(DCNS('relation.coverImage.source'))
202     e.text = cover.get_full_url()
203     e.tail = '\n    '
204     rdf.append(e)
205
206     xml = etree.tostring(root, encoding='unicode')
207     chunk.commit(
208         xml,
209         author=request.user,
210         comment='Cover',
211         publishable=chunk.head.publishable,
212     )
213     return HttpResponseRedirect(book.get_absolute_url())
214