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