also: fixdc command instead of fix_rdf_about, now fixes wluri-s.
renamed_files_other[f] = self.set_prefix(f, 1, True)
# finally, move / rename files.
- from nose.tools import set_trace
- # set_trace()
for frm, to in renamed_files.items():
move(join(self.path(self.dest), frm),
join(self.path(self.dest), to))
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from collections import defaultdict
+from django.db import transaction
+from lxml import etree
+
+
+class XmlUpdater(object):
+ """A base class for massive XML updates.
+
+ In a subclass, override `fix_tree` and/or use `fixes_field` decorator.
+ Attributes:
+ * commit_desc: commits description
+ * retain_publishable: set publishable if head is (default: True)
+ * only_first_chunk: process only first chunks of books (default: False)
+ """
+ commit_desc = "auto-update"
+ retain_publishable = True
+ only_first_chunk = False
+
+ _element_fixers = defaultdict(list)
+
+ def __init__(self):
+ self.counters = defaultdict(lambda: 0)
+
+ @classmethod
+ def fixes_elements(cls, xpath):
+ """Decorator, registering a function as a fixer for given field type.
+
+ Any decorated function will be called like
+ f(element, change=..., verbose=...)
+ providing changeset as context.
+
+ :param xpath: element lookup, e.g. ".//{namespace-uri}tag-name"
+ :returns: True if anything changed
+ """
+ def wrapper(fixer):
+ cls._element_fixers[xpath].append(fixer)
+ return fixer
+ return wrapper
+
+ def fix_tree(self, tree, verbose):
+ """Override to provide general tree-fixing mechanism.
+
+ :param tree: the parsed XML tree
+ :param verbose: verbosity level
+ :returns: True if anythig changed
+ """
+ return False
+
+ def fix_chunk(self, chunk, user, verbose=0, dry_run=False):
+ """Runs the update for a single chunk."""
+ if verbose >= 2:
+ print chunk.get_absolute_url()
+ old_head = chunk.head
+ src = old_head.materialize()
+ try:
+ tree = etree.fromstring(src)
+ except:
+ if verbose:
+ print "%s: invalid XML" % chunk.get_absolute_url()
+ self.counters['Bad XML'] += 1
+ return
+
+ dirty = False
+ # Call the general fixing function.
+ if self.fix_tree(tree, verbose=verbose):
+ dirty = True
+ # Call the registered fixers.
+ for xpath, fixers in self._element_fixers.items():
+ for elem in tree.findall(xpath):
+ for fixer in fixers:
+ if fixer(elem, change=old_head, verbose=verbose):
+ dirty = True
+
+ if not dirty:
+ self.counters['Clean'] += 1
+ return
+
+ if not dry_run:
+ new_head = chunk.commit(
+ etree.tostring(tree, encoding=unicode),
+ author=user,
+ description=self.commit_desc
+ )
+ if self.retain_publishable:
+ if old_head.publishable:
+ new_head.set_publishable(True)
+ if verbose >= 2:
+ print "done"
+ self.counters['Updated chunks'] += 1
+
+ def run(self, user, verbose=0, dry_run=False, books=None):
+ """Runs the actual update."""
+ if books is None:
+ from catalogue.models import Book
+ books = Book.objects.all()
+
+ # Start transaction management.
+ transaction.commit_unless_managed()
+ transaction.enter_transaction_management()
+ transaction.managed(True)
+
+ for book in books:
+ self.counters['All books'] += 1
+ chunks = book.chunk_set.all()
+ if self.only_first_chunk:
+ chunks = chunks[:1]
+ for chunk in chunks:
+ self.counters['All chunks'] += 1
+ self.fix_chunk(chunk, user, verbose, dry_run)
+
+ transaction.commit()
+ transaction.leave_transaction_management()
+
+ def print_results(self):
+ """Prints the counters."""
+ for item in sorted(self.counters.items()):
+ print "%s: %d" % item
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+import sys
+from optparse import make_option
+from django.contrib.auth.models import User
+from django.core.management.base import BaseCommand
+from catalogue.models import Book
+
+
+class XmlUpdaterCommand(BaseCommand):
+ """Base class for creating massive XML-updating commands.
+
+ In a subclass, provide an XmlUpdater class in the `updater' attribute.
+ """
+ option_list = BaseCommand.option_list + (
+ make_option('-q', '--quiet', action='store_false', dest='verbose',
+ default=True, help='Less output'),
+ make_option('-d', '--dry-run', action='store_true', dest='dry_run',
+ default=False, help="Don't actually touch anything"),
+ make_option('-u', '--username', dest='username', metavar='USER',
+ help='Assign commits to this user (required, preferably yourself).'),
+ )
+ args = "[slug]..."
+
+ def handle(self, *args, **options):
+ verbose = options.get('verbose')
+ dry_run = options.get('dry_run')
+ username = options.get('username')
+
+ if username:
+ user = User.objects.get(username=username)
+ else:
+ print 'Please provide a username.'
+ sys.exit(1)
+
+ books = Book.objects.filter(slug__in=args) if args else None
+
+ updater = self.updater()
+ updater.run(user, verbose=verbose, dry_run=dry_run, books=books)
+ updater.print_results()
+++ /dev/null
-# -*- coding: utf-8 -*-
-
-from optparse import make_option
-
-from django.contrib.auth.models import User
-from django.core.management.base import BaseCommand
-from django.db import transaction
-
-from catalogue.models import Book
-
-
-class Command(BaseCommand):
- option_list = BaseCommand.option_list + (
- make_option('-q', '--quiet', action='store_false', dest='verbose',
- default=True, help='Less output'),
- make_option('-d', '--dry-run', action='store_true', dest='dry_run',
- default=False, help="Don't actually touch anything"),
- )
- help = 'Updates the rdf:about metadata field.'
-
- def handle(self, *args, **options):
- from lxml import etree
-
- verbose = options.get('verbose')
- dry_run = options.get('dry_run')
-
- # Start transaction management.
- transaction.commit_unless_managed()
- transaction.enter_transaction_management()
- transaction.managed(True)
-
- all_books = 0
- nonxml = 0
- nordf = 0
- already = 0
- done = 0
-
- for b in Book.objects.all():
- all_books += 1
- if verbose:
- print "%s: " % b.title,
- chunk = b[0]
- old_head = chunk.head
- src = old_head.materialize()
-
- try:
- t = etree.fromstring(src)
- except:
- nonxml += 1
- if verbose:
- print "invalid XML"
- continue
- desc = t.find(".//{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description")
- if desc is None:
- nordf += 1
- if verbose:
- print "no RDF found"
- continue
-
- correct_about = b.correct_about()
- attr_name = "{http://www.w3.org/1999/02/22-rdf-syntax-ns#}about"
- if desc.get(attr_name) == correct_about:
- already += 1
- if verbose:
- print "already correct"
- continue
- desc.set(attr_name, correct_about)
- if not dry_run:
- new_head = chunk.commit(etree.tostring(t, encoding=unicode),
- author_name='platforma redakcyjna',
- description='auto-update rdf:about'
- )
- # retain the publishable status
- if old_head.publishable:
- new_head.set_publishable(True)
- if verbose:
- print "done"
- done += 1
-
- # Print results
- print "All books: ", all_books
- print "Invalid XML: ", nonxml
- print "No RDF found: ", nordf
- print "Already correct: ", already
- print "Books updated: ", done
-
- transaction.commit()
- transaction.leave_transaction_management()
-
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from librarian import RDFNS, WLURI, ValidationError
+from librarian.dcparser import BookInfo
+from catalogue.management import XmlUpdater
+from catalogue.management.commands import XmlUpdaterCommand
+
+
+class FixDC(XmlUpdater):
+ commit_desc = "auto-fixing DC"
+ retain_publishable = True
+ only_first_chunk = True
+
+ def fix_wluri(elem, change, verbose):
+ try:
+ WLURI.strict(elem.text)
+ except ValidationError:
+ correct_field = unicode(WLURI.from_slug(WLURI(elem.text).slug))
+ if verbose:
+ print "Changing %s from %s to %s" % (
+ elem.tag, elem.text, correct_field
+ )
+ elem.text = correct_field
+ return True
+ for field in BookInfo.FIELDS:
+ if field.validator == WLURI:
+ XmlUpdater.fixes_elements('.//' + field.uri)(fix_wluri)
+
+ @XmlUpdater.fixes_elements(".//" + RDFNS("Description"))
+ def fix_rdfabout(elem, change, verbose):
+ correct_about = change.tree.book.correct_about()
+ attr_name = RDFNS("about")
+ current_about = elem.get(attr_name)
+ if current_about != correct_about:
+ if verbose:
+ print "Changing rdf:about from %s to %s" % (
+ current_about, correct_about
+ )
+ elem.set(attr_name, correct_about)
+ return True
+
+
+class Command(XmlUpdaterCommand):
+ updater = FixDC
+ help = 'Fixes obvious errors in DC: rdf:about and WLURI format.'
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+"""Testing utilities."""
+
+from os.path import abspath, dirname, join
+
+
+def get_fixture(path):
+ f_path = join(dirname(abspath(__file__)), 'tests/files', path)
+ with open(f_path) as f:
+ return unicode(f.read(), 'utf-8')
-from os.path import abspath, dirname, join, basename, exists
-from os import makedirs, listdir
-from nose.tools import *
-from mock import patch
-from django.test import TestCase
-from django.contrib.auth.models import User
-from catalogue.models import Book, BookPublishRecord
-from tempfile import mkdtemp
-from django.conf import settings
-
-def get_fixture(path):
- f_path = join(dirname(abspath(__file__)), 'files', path)
- with open(f_path) as f:
- return unicode(f.read(), 'utf-8')
-
-
-class PublishTests(TestCase):
-
- def setUp(self):
- self.user = User.objects.create(username='tester')
- self.text1 = get_fixture('chunk1.xml')
- self.book = Book.create(self.user, self.text1, slug='test-book')
-
- @patch('apiclient.api_call')
- def test_unpublishable(self, api_call):
- with self.assertRaises(AssertionError):
- self.book.publish(self.user)
-
- @patch('apiclient.api_call')
- def test_publish(self, api_call):
- self.book[0].head.set_publishable(True)
- self.book.publish(self.user)
- api_call.assert_called_with(self.user, 'books/', {"book_xml": self.text1})
-
- @patch('apiclient.api_call')
- def test_publish_multiple(self, api_call):
- self.book[0].head.set_publishable(True)
- self.book[0].split(slug='part-2')
- self.book[1].commit(get_fixture('chunk2.xml'))
- self.book[1].head.set_publishable(True)
- self.book.publish(self.user)
- api_call.assert_called_with(self.user, 'books/', {"book_xml": get_fixture('expected.xml')})
-
-
-class ManipulationTests(TestCase):
-
- def setUp(self):
- self.user = User.objects.create(username='tester')
- self.book1 = Book.create(self.user, 'book 1', slug='book1')
- self.book2 = Book.create(self.user, 'book 2', slug='book2')
-
- def test_append(self):
- self.book1.append(self.book2)
- self.assertEqual(Book.objects.all().count(), 1)
- self.assertEqual(len(self.book1), 2)
-
- def test_append_to_self(self):
- with self.assertRaises(AssertionError):
- self.book1.append(Book.objects.get(pk=self.book1.pk))
- self.assertEqual(Book.objects.all().count(), 2)
- self.assertEqual(len(self.book1), 1)
-
- def test_prepend_history(self):
- self.book1.prepend_history(self.book2)
- self.assertEqual(Book.objects.all().count(), 1)
- self.assertEqual(len(self.book1), 1)
- self.assertEqual(self.book1.materialize(), 'book 1')
-
- def test_prepend_history_to_self(self):
- with self.assertRaises(AssertionError):
- self.book1.prepend_history(self.book1)
- self.assertEqual(Book.objects.all().count(), 2)
- self.assertEqual(self.book1.materialize(), 'book 1')
- self.assertEqual(self.book2.materialize(), 'book 2')
-
- def test_split_book(self):
- self.book1.chunk_set.create(number=2, title='Second chunk',
- slug='book3')
- self.book1[1].commit('I survived!')
- self.assertEqual(len(self.book1), 2)
- self.book1.split()
- self.assertEqual(set([b.slug for b in Book.objects.all()]),
- set(['book2', '1', 'book3']))
- self.assertEqual(
- Book.objects.get(slug='book3').materialize(),
- 'I survived!')
-
-
-class GalleryAppendTests(TestCase):
- def setUp(self):
- self.user = User.objects.create(username='tester')
- self.book1 = Book.create(self.user, 'book 1', slug='book1')
- self.book1.chunk_set.create(number=2, title='Second chunk',
- slug='book 1 / 2')
- c=self.book1[0]
- c.gallery_start=1
- c=self.book1[1]
- c.gallery_start=3
-
- self.scandir = join(settings.MEDIA_ROOT, settings.IMAGE_DIR)
- if not exists(self.scandir):
- makedirs(self.scandir)
-
- def make_gallery(self, book, files):
- d = mkdtemp('gallery', dir=self.scandir)
- for named, cont in files.items():
- f = open(join(d, named), 'w')
- f.write(cont)
- f.close()
- book.gallery = basename(d)
-
-
- def test_both_indexed(self):
- self.book2 = Book.create(self.user, 'book 2', slug='book2')
- self.book2.chunk_set.create(number=2, title='Second chunk of second book',
- slug='book 2 / 2')
-
- c = self.book2[0]
- c.gallery_start = 1
- c.save()
- c = self.book2[1]
- c.gallery_start = 3
- c.save()
-
- print "gallery starts:",self.book2[0].gallery_start, self.book2[1].gallery_start
-
- self.make_gallery(self.book1, {
- '1-0001_1l' : 'aa',
- '1-0001_2r' : 'bb',
- '1-0002_1l' : 'cc',
- '1-0002_2r' : 'dd',
- })
-
- self.make_gallery(self.book2, {
- '1-0001_1l' : 'dd', # the same, should not be moved
- '1-0001_2r' : 'ff',
- '2-0002_1l' : 'gg',
- '2-0002_2r' : 'hh',
- })
-
- self.book1.append(self.book2)
-
- files = listdir(join(self.scandir, self.book1.gallery))
- files.sort()
- print files
- self.assertEqual(files, [
- '1-0001_1l',
- '1-0001_2r',
- '1-0002_1l',
- '1-0002_2r',
- # '2-0001_1l',
- '2-0001_2r',
- '3-0002_1l',
- '3-0002_2r',
- ])
-
- self.assertEqual((4, 6), (self.book1[2].gallery_start, self.book1[3].gallery_start))
-
-
- def test_none_indexed(self):
- self.book2 = Book.create(self.user, 'book 2', slug='book2')
- self.make_gallery(self.book1, {
- '0001_1l' : 'aa',
- '0001_2r' : 'bb',
- '0002_1l' : 'cc',
- '0002_2r' : 'dd',
- })
-
- self.make_gallery(self.book2, {
- '0001_1l' : 'ee',
- '0001_2r' : 'ff',
- '0002_1l' : 'gg',
- '0002_2r' : 'hh',
- })
-
- self.book1.append(self.book2)
-
- files = listdir(join(self.scandir, self.book1.gallery))
- files.sort()
- print files
- self.assertEqual(files, [
- '0-0001_1l',
- '0-0001_2r',
- '0-0002_1l',
- '0-0002_2r',
- '1-0001_1l',
- '1-0001_2r',
- '1-0002_1l',
- '1-0002_2r',
- ])
-
-
- def test_none_indexed(self):
- import nose.tools
- self.book2 = Book.create(self.user, 'book 2', slug='book2')
- self.make_gallery(self.book1, {
- '1-0001_1l' : 'aa',
- '1-0001_2r' : 'bb',
- '1002_1l' : 'cc',
- '1002_2r' : 'dd',
- })
-
- self.make_gallery(self.book2, {
- '0001_1l' : 'ee',
- '0001_2r' : 'ff',
- '0002_1l' : 'gg',
- '0002_2r' : 'hh',
- })
-
- self.book1.append(self.book2)
-
- files = listdir(join(self.scandir, self.book1.gallery))
- files.sort()
- print files
- self.assertEqual(files, [
- '0-1-0001_1l',
- '0-1-0001_2r',
- '0-1002_1l',
- '0-1002_2r',
- '1-0001_1l',
- '1-0001_2r',
- '1-0002_1l',
- '1-0002_2r',
- ])
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from catalogue.tests.book import *
+from catalogue.tests.gallery import *
+from catalogue.tests.publish import *
+from catalogue.tests.xml_updater import *
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+"""Tests for manipulating books in the catalogue."""
+
+from django.test import TestCase
+from django.contrib.auth.models import User
+from catalogue.models import Book
+
+
+class ManipulationTests(TestCase):
+
+ def setUp(self):
+ self.user = User.objects.create(username='tester')
+ self.book1 = Book.create(self.user, 'book 1', slug='book1')
+ self.book2 = Book.create(self.user, 'book 2', slug='book2')
+
+ def test_append(self):
+ self.book1.append(self.book2)
+ self.assertEqual(Book.objects.all().count(), 1)
+ self.assertEqual(len(self.book1), 2)
+
+ def test_append_to_self(self):
+ with self.assertRaises(AssertionError):
+ self.book1.append(Book.objects.get(pk=self.book1.pk))
+ self.assertEqual(Book.objects.all().count(), 2)
+ self.assertEqual(len(self.book1), 1)
+
+ def test_prepend_history(self):
+ self.book1.prepend_history(self.book2)
+ self.assertEqual(Book.objects.all().count(), 1)
+ self.assertEqual(len(self.book1), 1)
+ self.assertEqual(self.book1.materialize(), 'book 1')
+
+ def test_prepend_history_to_self(self):
+ with self.assertRaises(AssertionError):
+ self.book1.prepend_history(self.book1)
+ self.assertEqual(Book.objects.all().count(), 2)
+ self.assertEqual(self.book1.materialize(), 'book 1')
+ self.assertEqual(self.book2.materialize(), 'book 2')
+
+ def test_split_book(self):
+ self.book1.chunk_set.create(number=2, title='Second chunk',
+ slug='book3')
+ self.book1[1].commit('I survived!')
+ self.assertEqual(len(self.book1), 2)
+ self.book1.split()
+ self.assertEqual(set([b.slug for b in Book.objects.all()]),
+ set(['book2', '1', 'book3']))
+ self.assertEqual(
+ Book.objects.get(slug='book3').materialize(),
+ 'I survived!')
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+"""Tests for galleries of scans."""
+
+from os.path import join, basename, exists
+from os import makedirs, listdir
+from django.test import TestCase
+from django.contrib.auth.models import User
+from catalogue.models import Book
+from tempfile import mkdtemp
+from django.conf import settings
+
+
+class GalleryAppendTests(TestCase):
+ def setUp(self):
+ self.user = User.objects.create(username='tester')
+ self.book1 = Book.create(self.user, 'book 1', slug='book1')
+ self.book1.chunk_set.create(number=2, title='Second chunk',
+ slug='book 1 / 2')
+ c=self.book1[0]
+ c.gallery_start=1
+ c=self.book1[1]
+ c.gallery_start=3
+
+ self.scandir = join(settings.MEDIA_ROOT, settings.IMAGE_DIR)
+ if not exists(self.scandir):
+ makedirs(self.scandir)
+
+ def make_gallery(self, book, files):
+ d = mkdtemp('gallery', dir=self.scandir)
+ for named, cont in files.items():
+ f = open(join(d, named), 'w')
+ f.write(cont)
+ f.close()
+ book.gallery = basename(d)
+
+
+ def test_both_indexed(self):
+ self.book2 = Book.create(self.user, 'book 2', slug='book2')
+ self.book2.chunk_set.create(number=2, title='Second chunk of second book',
+ slug='book 2 / 2')
+
+ c = self.book2[0]
+ c.gallery_start = 1
+ c.save()
+ c = self.book2[1]
+ c.gallery_start = 3
+ c.save()
+
+ print "gallery starts:",self.book2[0].gallery_start, self.book2[1].gallery_start
+
+ self.make_gallery(self.book1, {
+ '1-0001_1l' : 'aa',
+ '1-0001_2r' : 'bb',
+ '1-0002_1l' : 'cc',
+ '1-0002_2r' : 'dd',
+ })
+
+ self.make_gallery(self.book2, {
+ '1-0001_1l' : 'dd', # the same, should not be moved
+ '1-0001_2r' : 'ff',
+ '2-0002_1l' : 'gg',
+ '2-0002_2r' : 'hh',
+ })
+
+ self.book1.append(self.book2)
+
+ files = listdir(join(self.scandir, self.book1.gallery))
+ files.sort()
+ print files
+ self.assertEqual(files, [
+ '1-0001_1l',
+ '1-0001_2r',
+ '1-0002_1l',
+ '1-0002_2r',
+ # '2-0001_1l',
+ '2-0001_2r',
+ '3-0002_1l',
+ '3-0002_2r',
+ ])
+
+ self.assertEqual((4, 6), (self.book1[2].gallery_start, self.book1[3].gallery_start))
+
+
+ def test_none_indexed(self):
+ self.book2 = Book.create(self.user, 'book 2', slug='book2')
+ self.make_gallery(self.book1, {
+ '0001_1l' : 'aa',
+ '0001_2r' : 'bb',
+ '0002_1l' : 'cc',
+ '0002_2r' : 'dd',
+ })
+
+ self.make_gallery(self.book2, {
+ '0001_1l' : 'ee',
+ '0001_2r' : 'ff',
+ '0002_1l' : 'gg',
+ '0002_2r' : 'hh',
+ })
+
+ self.book1.append(self.book2)
+
+ files = listdir(join(self.scandir, self.book1.gallery))
+ files.sort()
+ print files
+ self.assertEqual(files, [
+ '0-0001_1l',
+ '0-0001_2r',
+ '0-0002_1l',
+ '0-0002_2r',
+ '1-0001_1l',
+ '1-0001_2r',
+ '1-0002_1l',
+ '1-0002_2r',
+ ])
+
+
+ def test_none_indexed(self):
+ import nose.tools
+ self.book2 = Book.create(self.user, 'book 2', slug='book2')
+ self.make_gallery(self.book1, {
+ '1-0001_1l' : 'aa',
+ '1-0001_2r' : 'bb',
+ '1002_1l' : 'cc',
+ '1002_2r' : 'dd',
+ })
+
+ self.make_gallery(self.book2, {
+ '0001_1l' : 'ee',
+ '0001_2r' : 'ff',
+ '0002_1l' : 'gg',
+ '0002_2r' : 'hh',
+ })
+
+ self.book1.append(self.book2)
+
+ files = listdir(join(self.scandir, self.book1.gallery))
+ files.sort()
+ print files
+ self.assertEqual(files, [
+ '0-1-0001_1l',
+ '0-1-0001_2r',
+ '0-1002_1l',
+ '0-1002_2r',
+ '1-0001_1l',
+ '1-0001_2r',
+ '1-0002_1l',
+ '1-0002_2r',
+ ])
+
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+"""Tests for the publishing process."""
+
+from catalogue.test_utils import get_fixture
+
+from mock import patch
+from django.test import TestCase
+from django.contrib.auth.models import User
+from catalogue.models import Book
+
+
+class PublishTests(TestCase):
+ def setUp(self):
+ self.user = User.objects.create(username='tester')
+ self.text1 = get_fixture('chunk1.xml')
+ self.book = Book.create(self.user, self.text1, slug='test-book')
+
+ @patch('apiclient.api_call')
+ def test_unpublishable(self, api_call):
+ with self.assertRaises(AssertionError):
+ self.book.publish(self.user)
+
+ @patch('apiclient.api_call')
+ def test_publish(self, api_call):
+ self.book[0].head.set_publishable(True)
+ self.book.publish(self.user)
+ api_call.assert_called_with(self.user, 'books/', {"book_xml": self.text1})
+
+ @patch('apiclient.api_call')
+ def test_publish_multiple(self, api_call):
+ self.book[0].head.set_publishable(True)
+ self.book[0].split(slug='part-2')
+ self.book[1].commit(get_fixture('chunk2.xml'))
+ self.book[1].head.set_publishable(True)
+ self.book.publish(self.user)
+ api_call.assert_called_with(self.user, 'books/', {"book_xml": get_fixture('expected.xml')})
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+"""XmlUpdater tests."""
+
+from catalogue.test_utils import get_fixture
+from django.test import TestCase
+from django.contrib.auth.models import User
+from catalogue.models import Book
+from catalogue.management import XmlUpdater
+from librarian import DCNS
+
+
+class XmlUpdaterTests(TestCase):
+ class SimpleUpdater(XmlUpdater):
+ @XmlUpdater.fixes_elements('.//' + DCNS('title'))
+ def fix_title(element, **kwargs):
+ element.text = element.text + " fixed"
+ return True
+
+ def setUp(self):
+ self.user = User.objects.create(username='tester')
+ text = get_fixture('chunk1.xml')
+ Book.create(self.user, text, slug='test-book')
+ self.title = "Do M***"
+
+ def test_xml_updater(self):
+ self.SimpleUpdater().run(self.user)
+ self.assertEqual(
+ Book.objects.get(slug='test-book').wldocument(
+ publishable=False).book_info.title,
+ self.title + " fixed"
+ )