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 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
54     if not (height or width):
55         width, height = PREVIEW_SIZE
56
57     cover_class = request.GET.get('cover_class', 'default')
58
59     cover = make_cover(info, cover_class=cover_class, width=width, height=height)
60     response = HttpResponse(content_type=cover.mime_type())
61     img = cover.final_image()
62     img.save(response, cover.format)
63
64     if 'download' in request.GET:
65         response['Content-Disposition'] = 'attachment; filename=%s.jpg' % chunk.book.slug
66
67     return response
68
69
70 @csrf_exempt
71 @require_POST
72 def preview_from_xml(request):
73     from hashlib import sha1
74     from PIL import Image
75     from os import makedirs
76     from lxml import etree
77     from librarian.cover import make_cover
78     from librarian.dcparser import BookInfo
79
80     xml = request.POST['xml']
81     try:
82         info = BookInfo.from_bytes(xml.encode('utf-8'))
83     except:
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, 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.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