Add missing constraint.
[wolnelektury.git] / src / catalogue / tests / test_book_import.py
1 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
3 #
4 from django.core.files.base import ContentFile
5 from catalogue.test_utils import *
6 from catalogue import models
7 from librarian import WLURI
8
9 from os import path, makedirs
10
11
12 class BookImportLogicTests(WLTestCase):
13
14     def setUp(self):
15         WLTestCase.setUp(self)
16         self.book_info = BookInfoStub(
17             url=WLURI.from_slug("default-book"),
18             about="http://wolnelektury.pl/example/URI/default_book",
19             title="Default Book",
20             author=PersonStub(("Jim",), "Lazy"),
21             kind="X-Kind",
22             genre="X-Genre",
23             epoch="X-Epoch",
24             language="pol",
25         )
26
27         self.expected_tags = [
28             ('author', 'jim-lazy'),
29             ('genre', 'x-genre'),
30             ('epoch', 'x-epoch'),
31             ('kind', 'x-kind'),
32         ]
33         self.expected_tags.sort()
34
35     def test_empty_book(self):
36         book_text = "<utwor />"
37         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
38         book.refresh_from_db()
39
40         self.assertEqual(book.title, "Default Book")
41         self.assertEqual(book.slug, "default-book")
42         self.assertTrue(book.parent is None)
43         self.assertFalse(book.has_html_file())
44
45         # no fragments generated
46         self.assertEqual(book.fragments.count(), 0)
47
48         # TODO: this should be filled out probably...
49         self.assertEqual(book.wiki_link, '')
50         self.assertEqual(book.gazeta_link, '')
51         self.assertEqual(book.description, '')
52
53         tags = [(tag.category, tag.slug) for tag in book.tags]
54         tags.sort()
55
56         self.assertEqual(tags, self.expected_tags)
57
58     def test_not_quite_empty_book(self):
59         """ Not empty, but without any real text.
60
61         Should work like any other non-empty book.
62         """
63
64         book_text = """<utwor>
65         <liryka_l>
66             <nazwa_utworu>Nic</nazwa_utworu>
67         </liryka_l></utwor>
68         """
69
70         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
71         book.refresh_from_db()
72         self.assertTrue(book.has_html_file())
73
74     def test_book_with_fragment(self):
75         book_text = """<utwor>
76         <opowiadanie>
77             <akap><begin id="m01" /><motyw id="m01">Love</motyw>Ala ma kota<end id="m01" /></akap>
78         </opowiadanie></utwor>
79         """
80
81         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
82         book.refresh_from_db()
83         self.assertTrue(book.has_html_file())
84
85         self.assertEqual(book.fragments.count(), 1)
86         self.assertEqual(book.fragments.all()[0].text, '<p class="paragraph">Ala ma kota</p>\n')
87
88         self.assertTrue(('theme', 'love') in [(tag.category, tag.slug) for tag in book.fragments.all()[0].tags])
89
90     def test_book_with_empty_theme(self):
91         """ empty themes should be ignored """
92
93         book_text = """<utwor>
94         <opowiadanie>
95             <akap><begin id="m01" /><motyw id="m01"> , Love , , </motyw>Ala ma kota<end id="m01" /></akap>
96         </opowiadanie></utwor>
97         """
98
99         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
100         self.assertEqual(
101             [('theme', 'love')],
102             list(
103                 book.fragments.all()[0].tags.filter(
104                     category='theme'
105                 ).values_list('category', 'slug')
106             )
107         )
108
109     def test_book_with_no_theme(self):
110         """ fragments with no themes shouldn't be created at all """
111
112         book_text = """<utwor>
113         <opowiadanie>
114             <akap><begin id="m01" /><motyw id="m01"></motyw>Ala ma kota<end id="m01" /></akap>
115         </opowiadanie></utwor>
116         """
117
118         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
119         self.assertEqual(book.fragments.count(), 0)
120         self.assertEqual(book.tags.filter(category='theme').count(), 0)
121
122     def test_book_with_invalid_slug(self):
123         """ Book with invalid characters in slug shouldn't be imported """
124         self.book_info.url = WLURI.from_slug("default_book")
125         book_text = "<utwor />"
126         with self.assertRaises(ValueError):
127             models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
128
129     def test_book_replace_title(self):
130         book_text = """<utwor />"""
131         models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
132         self.book_info.title = "Extraordinary"
133         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info, overwrite=True)
134
135         tags = [(tag.category, tag.slug) for tag in book.tags]
136         tags.sort()
137
138         self.assertEqual(tags, self.expected_tags)
139
140     def test_book_replace_author(self):
141         book_text = """<utwor />"""
142         models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
143         self.book_info.author = PersonStub(("Hans", "Christian"), "Andersen")
144         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info, overwrite=True)
145
146         tags = [(tag.category, tag.slug) for tag in book.tags]
147         tags.sort()
148
149         self.expected_tags.remove(('author', 'jim-lazy'))
150         self.expected_tags.append(('author', 'hans-christian-andersen'))
151         self.expected_tags.sort()
152
153         self.assertEqual(tags, self.expected_tags)
154
155         # the old tag shouldn't disappear
156         models.Tag.objects.get(slug="jim-lazy", category="author")
157
158     def test_book_remove_fragment(self):
159         book_text = """<utwor>
160         <opowiadanie>
161             <akap>
162                 <begin id="m01" /><motyw id="m01">Love</motyw>Ala ma kota<end id="m01" />
163                 <begin id="m02" /><motyw id="m02">Hatred</motyw>To kot Ali<end id="m02" />
164             </akap>
165         </opowiadanie></utwor>
166         """
167         book_text_after = """<utwor>
168         <opowiadanie>
169             <akap>
170                 <begin id="m01" /><motyw id="m01">Love</motyw>Ala ma kota<end id="m01" />
171                 To kot Ali
172             </akap>
173         </opowiadanie></utwor>
174         """
175
176         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
177         self.assertEqual(book.fragments.count(), 2)
178         book = models.Book.from_text_and_meta(ContentFile(book_text_after), self.book_info, overwrite=True)
179         self.assertEqual(book.fragments.count(), 1)
180
181     def test_multiple_tags(self):
182         book_text = """<utwor />"""
183         self.book_info.authors = self.book_info.author, PersonStub(("Joe",), "Dilligent"),
184         self.book_info.kinds = self.book_info.kind, 'Y-Kind',
185         self.book_info.genres = self.book_info.genre, 'Y-Genre',
186         self.book_info.epochs = self.book_info.epoch, 'Y-Epoch',
187
188         self.expected_tags.extend([
189             ('author', 'joe-dilligent'),
190             ('genre', 'y-genre'),
191             ('epoch', 'y-epoch'),
192             ('kind', 'y-kind'),
193         ])
194         self.expected_tags.sort()
195
196         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
197         tags = [(tag.category, tag.slug) for tag in book.tags]
198         tags.sort()
199
200         self.assertEqual(tags, self.expected_tags)
201
202
203 class ChildImportTests(WLTestCase):
204
205     def setUp(self):
206         WLTestCase.setUp(self)
207         self.child_info = BookInfoStub(
208             genre='X-Genre',
209             epoch='X-Epoch',
210             kind='X-Kind',
211             author=PersonStub(("Joe",), "Doe"),
212             **info_args("Child")
213         )
214
215         self.parent_info = BookInfoStub(
216             genre='X-Genre',
217             epoch='X-Epoch',
218             kind='X-Kind',
219             author=PersonStub(("Jim",), "Lazy"),
220             parts=[self.child_info.url],
221             **info_args("Parent")
222         )
223
224     def test_child(self):
225         text = """<utwor />"""
226         child = models.Book.from_text_and_meta(ContentFile(text), self.child_info)
227         parent = models.Book.from_text_and_meta(ContentFile(text), self.parent_info)
228         author = parent.tags.get(category='author')
229         books = self.client.get(author.get_absolute_url()).context['object_list']
230         self.assertEqual(len(books), 1, "Only parent book should be visible on author's page")
231
232     def test_child_replace(self):
233         parent_text = """<utwor />"""
234         child_text = """<utwor>
235         <opowiadanie>
236             <akap><begin id="m01" /><motyw id="m01">Pies</motyw>Ala ma kota<end id="m01" /></akap>
237         </opowiadanie></utwor>
238         """
239         child = models.Book.from_text_and_meta(ContentFile(child_text), self.child_info)
240         parent = models.Book.from_text_and_meta(ContentFile(parent_text), self.parent_info)
241         child_text = """<utwor>
242         <opowiadanie>
243             <akap><begin id="m01" /><motyw id="m01">Kot</motyw>Ala ma kota<end id="m01" /></akap>
244         </opowiadanie></utwor>
245         """
246         child = models.Book.from_text_and_meta(ContentFile(child_text), self.child_info, overwrite=True)
247         themes = parent.related_themes()
248         self.assertEqual(['Kot'], [tag.name for tag in themes], 'wrong related theme list')
249
250
251 class TreeImportTest(WLTestCase):
252     def setUp(self):
253         WLTestCase.setUp(self)
254         self.child_info = BookInfoStub(
255             genre='X-Genre',
256             epoch='X-Epoch',
257             kind='X-Kind',
258             author=PersonStub(("Joe",), "Doe"),
259             **info_args("Child")
260         )
261         self.CHILD_TEXT = """<utwor>
262         <opowiadanie>
263             <akap><begin id="m01" /><motyw id="m01">Pies</motyw>
264                 Ala ma kota<end id="m01" /></akap>
265         </opowiadanie></utwor>
266         """
267         self.child = models.Book.from_text_and_meta(
268             ContentFile(self.CHILD_TEXT), self.child_info)
269
270         self.book_info = BookInfoStub(
271             genre='X-Genre',
272             epoch='X-Epoch',
273             kind='X-Kind',
274             author=PersonStub(("Joe",), "Doe"),
275             parts=[self.child_info.url],
276             **info_args("Book")
277         )
278         self.BOOK_TEXT = """<utwor />"""
279         self.book = models.Book.from_text_and_meta(
280             ContentFile(self.BOOK_TEXT), self.book_info)
281
282         self.parent_info = BookInfoStub(
283             genre='X-Genre',
284             epoch='X-Epoch',
285             kind='X-Kind',
286             author=PersonStub(("Jim",), "Lazy"),
287             parts=[self.book_info.url],
288             **info_args("Parent")
289         )
290         self.PARENT_TEXT = """<utwor />"""
291         self.parent = models.Book.from_text_and_meta(
292             ContentFile(self.PARENT_TEXT), self.parent_info)
293
294     def test_ok(self):
295         self.assertEqual(
296             list(self.client.get('/katalog/gatunek/x-genre/').context['object_list']),
297             [self.parent],
298             "There should be only parent on common tag page."
299         )
300         # pies = models.Tag.objects.get(slug='pies')
301         themes = self.parent.related_themes()
302         self.assertEqual(len(themes), 1, "There should be child theme in parent theme counter.")
303         # TODO: book_count is deprecated, update here.
304         # epoch = models.Tag.objects.get(slug='x-epoch')
305         # self.assertEqual(epoch.book_count, 1, "There should be only parent in common tag's counter.")
306
307     def test_child_republish(self):
308         child_text = """<utwor>
309         <opowiadanie>
310             <akap><begin id="m01" /><motyw id="m01">Pies, Kot</motyw>
311                 Ala ma kota<end id="m01" /></akap>
312         </opowiadanie></utwor>
313         """
314         models.Book.from_text_and_meta(
315             ContentFile(child_text), self.child_info, overwrite=True)
316         self.assertEqual(
317             list(self.client.get('/katalog/gatunek/x-genre/').context['object_list']),
318             [self.parent],
319             "There should only be parent on common tag page."
320         )
321         # pies = models.Tag.objects.get(slug='pies')
322         # kot = models.Tag.objects.get(slug='kot')
323         self.assertEqual(len(self.parent.related_themes()), 2,
324                          "There should be child themes in parent theme counter.")
325         # TODO: book_count is deprecated, update here.
326         # epoch = models.Tag.objects.get(slug='x-epoch')
327         # self.assertEqual(epoch.book_count, 1, "There should only be parent in common tag's counter.")
328
329     def test_book_change_child(self):
330         second_child_info = BookInfoStub(
331             genre='X-Genre',
332             epoch='X-Epoch',
333             kind='Other-Kind',
334             author=PersonStub(("Joe",), "Doe"),
335             **info_args("Second Child")
336         )
337         second_child_text = """<utwor>
338         <opowiadanie>
339             <akap><begin id="m01" /><motyw id="m01">Kot</motyw>
340                 Ala ma kota<end id="m01" /></akap>
341         </opowiadanie></utwor>
342         """
343         # Import a second child.
344         second_child = models.Book.from_text_and_meta(
345             ContentFile(second_child_text), second_child_info)
346         # The book has only this new child now.
347         self.book_info.parts = [second_child_info.url]
348         self.book = models.Book.from_text_and_meta(
349             ContentFile(self.BOOK_TEXT), self.book_info, overwrite=True)
350
351         self.assertEqual(
352             set(self.client.get('/katalog/gatunek/x-genre/').context['object_list']),
353             {self.parent, self.child},
354             "There should be parent and old child on common tag page."
355         )
356         # kot = models.Tag.objects.get(slug='kot')
357         self.assertEqual(len(self.parent.related_themes()), 1,
358                          "There should only be new child themes in parent theme counter.")
359         # # book_count deprecated, update test.
360         # epoch = models.Tag.objects.get(slug='x-epoch')
361         # self.assertEqual(epoch.book_count, 2,
362         #                  "There should be parent and old child in common tag's counter.")
363         self.assertEqual(
364             list(self.client.get('/katalog/lektura/parent/motyw/kot/').context['fragments']),
365             [second_child.fragments.all()[0]],
366             "There should be new child's fragments on parent's theme page."
367         )
368         self.assertEqual(
369             list(self.client.get('/katalog/lektura/parent/motyw/pies/').context['fragments']),
370             [],
371             "There should be no old child's fragments on parent's theme page."
372         )
373
374
375 class MultilingualBookImportTest(WLTestCase):
376     def setUp(self):
377         WLTestCase.setUp(self)
378         common_uri = WLURI.from_slug('common-slug')
379
380         self.pol_info = BookInfoStub(
381             genre='X-Genre',
382             epoch='X-Epoch',
383             kind='X-Kind',
384             author=PersonStub(("Joe",), "Doe"),
385             variant_of=common_uri,
386             **info_args("Książka")
387         )
388
389         self.eng_info = BookInfoStub(
390             genre='X-Genre',
391             epoch='X-Epoch',
392             kind='X-Kind',
393             author=PersonStub(("Joe",), "Doe"),
394             variant_of=common_uri,
395             **info_args("A book", "eng")
396         )
397
398     def test_multilingual_import(self):
399         book_text = """<utwor><opowiadanie><akap>A</akap></opowiadanie></utwor>"""
400
401         models.Book.from_text_and_meta(ContentFile(book_text), self.pol_info)
402         models.Book.from_text_and_meta(ContentFile(book_text), self.eng_info)
403
404         self.assertEqual(
405             {b.language for b in models.Book.objects.all()},
406             {'pol', 'eng'},
407             'Books imported in wrong languages.'
408         )
409
410
411 class BookImportGenerateTest(WLTestCase):
412     def setUp(self):
413         WLTestCase.setUp(self)
414         xml = path.join(path.dirname(__file__), 'files/fraszka-do-anusie.xml')
415         self.book = models.Book.from_xml_file(xml)
416
417     def test_gen_pdf(self):
418         self.book.pdf_file.build()
419         book = models.Book.objects.get(pk=self.book.pk)
420         self.assertTrue(path.exists(book.pdf_file.path))
421
422     def test_gen_pdf_parent(self):
423         """This book contains a child."""
424         xml = path.join(path.dirname(__file__), "files/fraszki.xml")
425         parent = models.Book.from_xml_file(xml)
426         parent.pdf_file.build()
427         parent = models.Book.objects.get(pk=parent.pk)
428         self.assertTrue(path.exists(parent.pdf_file.path))
429
430     def test_custom_pdf(self):
431         from catalogue.tasks import build_custom_pdf
432         out = 'test-custom.pdf'
433         absoulute_path = path.join(settings.MEDIA_ROOT, out)
434
435         if not path.exists(path.dirname(absoulute_path)):
436             makedirs(path.dirname(absoulute_path))
437
438         build_custom_pdf(self.book.id, customizations=['nofootnotes', '13pt', 'a4paper'], file_name=out)
439         self.assertTrue(path.exists(absoulute_path))