Exemplo n.º 1
0
class PictureArea(models.Model):
    picture = models.ForeignKey('picture.Picture', related_name='areas')
    area = jsonfield.JSONField(_('area'), default={}, editable=False)
    kind = models.CharField(
        _('kind'), max_length=10, blank=False, null=False, db_index=True,
        choices=(('thing', _('thing')), ('theme', _('theme'))))

    objects = models.Manager()
    tagged = managers.ModelTaggedItemManager(catalogue.models.Tag)
    tags = managers.TagDescriptor(catalogue.models.Tag)
    tag_relations = GenericRelation(catalogue.models.Tag.intermediary_table_model)

    short_html_url_name = 'picture_area_short'

    @classmethod
    def rectangle(cls, picture, kind, coords):
        pa = PictureArea()
        pa.picture = picture
        pa.kind = kind
        pa.area = coords
        return pa

    def flush_includes(self, languages=True):
        if not languages:
            return
        if languages is True:
            languages = [lc for (lc, _ln) in settings.LANGUAGES]
        flush_ssi_includes([
            template % (self.pk, lang)
            for template in [
                '/katalog/pa/%d/short.%s.html',
                ]
            for lang in languages
            ])
Exemplo n.º 2
0
class Fragment(models.Model):
    """Represents a themed fragment of a book."""
    text = models.TextField()
    short_text = models.TextField(editable=False)
    anchor = models.CharField(max_length=120)
    book = models.ForeignKey('Book', related_name='fragments')

    objects = models.Manager()
    tagged = managers.ModelTaggedItemManager(Tag)
    tags = managers.TagDescriptor(Tag)

    class Meta:
        ordering = (
            'book',
            'anchor',
        )
        verbose_name = _('fragment')
        verbose_name_plural = _('fragments')
        app_label = 'catalogue'

    def get_absolute_url(self):
        return '%s#m%s' % (reverse('book_text', args=[self.book.slug
                                                      ]), self.anchor)

    def reset_short_html(self):
        if self.id is None:
            return

        cache_key = "Fragment.short_html/%d/%s"
        for lang, langname in settings.LANGUAGES:
            permanent_cache.delete(cache_key % (self.id, lang))

    def get_short_text(self):
        """Returns short version of the fragment."""
        return self.short_text if self.short_text else self.text

    def short_html(self):
        if self.id:
            cache_key = "Fragment.short_html/%d/%s" % (self.id, get_language())
            short_html = permanent_cache.get(cache_key)
        else:
            short_html = None

        if short_html is not None:
            return mark_safe(short_html)
        else:
            short_html = unicode(
                render_to_string('catalogue/fragment_short.html',
                                 {'fragment': self}))
            if self.id:
                permanent_cache.set(cache_key, short_html)
            return mark_safe(short_html)
Exemplo n.º 3
0
class Fragment(models.Model):
    """Represents a themed fragment of a book."""
    text = models.TextField()
    short_text = models.TextField(editable=False)
    anchor = models.CharField(max_length=120)
    book = models.ForeignKey('Book', related_name='fragments')

    objects = models.Manager()
    tagged = managers.ModelTaggedItemManager(Tag)
    tags = managers.TagDescriptor(Tag)
    tag_relations = GenericRelation(Tag.intermediary_table_model)

    short_html_url_name = 'catalogue_fragment_short'

    class Meta:
        ordering = (
            'book',
            'anchor',
        )
        verbose_name = _('fragment')
        verbose_name_plural = _('fragments')
        app_label = 'catalogue'

    def get_absolute_url(self):
        return '%s#m%s' % (reverse('book_text', args=[self.book.slug
                                                      ]), self.anchor)

    def get_short_text(self):
        """Returns short version of the fragment."""
        return self.short_text if self.short_text else self.text

    def flush_includes(self, languages=True):
        if not languages:
            return
        if languages is True:
            languages = [lc for (lc, _ln) in settings.LANGUAGES]
        flush_ssi_includes([
            template % (self.pk, lang) for template in [
                '/katalog/f/%d/short.%s.html',
                '/api/include/fragment/%d.%s.json',
                '/api/include/fragment/%d.%s.xml',
            ] for lang in languages
        ])
Exemplo n.º 4
0
class Book(models.Model):
    """Represents a book imported from WL-XML."""
    title = models.CharField(_('title'), max_length=32767)
    sort_key = models.CharField(_('sort key'), max_length=120, db_index=True, editable=False)
    sort_key_author = models.CharField(
        _('sort key by author'), max_length=120, db_index=True, editable=False, default=u'')
    slug = models.SlugField(_('slug'), max_length=120, db_index=True, unique=True)
    common_slug = models.SlugField(_('slug'), max_length=120, db_index=True)
    language = models.CharField(_('language code'), max_length=3, db_index=True, default=app_settings.DEFAULT_LANGUAGE)
    description = models.TextField(_('description'), blank=True)
    created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
    changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
    parent_number = models.IntegerField(_('parent number'), default=0)
    extra_info = jsonfield.JSONField(_('extra information'), default={})
    gazeta_link = models.CharField(blank=True, max_length=240)
    wiki_link = models.CharField(blank=True, max_length=240)

    # files generated during publication
    cover = EbookField(
        'cover', _('cover'),
        null=True, blank=True,
        upload_to=_cover_upload_to,
        storage=bofh_storage, max_length=255)
    # Cleaner version of cover for thumbs
    cover_thumb = EbookField(
        'cover_thumb', _('cover thumbnail'),
        null=True, blank=True,
        upload_to=_cover_thumb_upload_to,
        max_length=255)
    ebook_formats = constants.EBOOK_FORMATS
    formats = ebook_formats + ['html', 'xml']

    parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
    ancestor = models.ManyToManyField('self', blank=True, editable=False, related_name='descendant', symmetrical=False)

    objects = models.Manager()
    tagged = managers.ModelTaggedItemManager(Tag)
    tags = managers.TagDescriptor(Tag)
    tag_relations = GenericRelation(Tag.intermediary_table_model)

    html_built = django.dispatch.Signal()
    published = django.dispatch.Signal()

    short_html_url_name = 'catalogue_book_short'

    class AlreadyExists(Exception):
        pass

    class Meta:
        ordering = ('sort_key_author', 'sort_key')
        verbose_name = _('book')
        verbose_name_plural = _('books')
        app_label = 'catalogue'

    def __unicode__(self):
        return self.title

    def get_initial(self):
        try:
            return re.search(r'\w', self.title, re.U).group(0)
        except AttributeError:
            return ''

    def authors(self):
        return self.tags.filter(category='author')

    def tag_unicode(self, category):
        relations = prefetched_relations(self, category)
        if relations:
            return ', '.join(rel.tag.name for rel in relations)
        else:
            return ', '.join(self.tags.filter(category=category).values_list('name', flat=True))

    def author_unicode(self):
        return self.tag_unicode('author')

    def save(self, force_insert=False, force_update=False, **kwargs):
        from sortify import sortify

        self.sort_key = sortify(self.title)[:120]
        self.title = unicode(self.title)  # ???

        try:
            author = self.authors().first().sort_key
        except AttributeError:
            author = u''
        self.sort_key_author = author

        ret = super(Book, self).save(force_insert, force_update, **kwargs)

        return ret

    @permalink
    def get_absolute_url(self):
        return 'catalogue.views.book_detail', [self.slug]

    @staticmethod
    @permalink
    def create_url(slug):
        return 'catalogue.views.book_detail', [slug]

    def gallery_path(self):
        return gallery_path(self.slug)

    def gallery_url(self):
        return gallery_url(self.slug)

    @property
    def name(self):
        return self.title

    def language_code(self):
        return constants.LANGUAGES_3TO2.get(self.language, self.language)

    def language_name(self):
        return dict(settings.LANGUAGES).get(self.language_code(), "")

    def is_foreign(self):
        return self.language_code() != settings.LANGUAGE_CODE

    def has_media(self, type_):
        if type_ in Book.formats:
            return bool(getattr(self, "%s_file" % type_))
        else:
            return self.media.filter(type=type_).exists()

    def get_media(self, type_):
        if self.has_media(type_):
            if type_ in Book.formats:
                return getattr(self, "%s_file" % type_)
            else:
                return self.media.filter(type=type_)
        else:
            return None

    def get_mp3(self):
        return self.get_media("mp3")

    def get_odt(self):
        return self.get_media("odt")

    def get_ogg(self):
        return self.get_media("ogg")

    def get_daisy(self):
        return self.get_media("daisy")

    def has_description(self):
        return len(self.description) > 0
    has_description.short_description = _('description')
    has_description.boolean = True

    # ugly ugly ugly
    def has_mp3_file(self):
        return bool(self.has_media("mp3"))
    has_mp3_file.short_description = 'MP3'
    has_mp3_file.boolean = True

    def has_ogg_file(self):
        return bool(self.has_media("ogg"))
    has_ogg_file.short_description = 'OGG'
    has_ogg_file.boolean = True

    def has_daisy_file(self):
        return bool(self.has_media("daisy"))
    has_daisy_file.short_description = 'DAISY'
    has_daisy_file.boolean = True

    def wldocument(self, parse_dublincore=True, inherit=True):
        from catalogue.import_utils import ORMDocProvider
        from librarian.parser import WLDocument

        if inherit and self.parent:
            meta_fallbacks = self.parent.cover_info()
        else:
            meta_fallbacks = None

        return WLDocument.from_file(
            self.xml_file.path,
            provider=ORMDocProvider(self),
            parse_dublincore=parse_dublincore,
            meta_fallbacks=meta_fallbacks)

    @staticmethod
    def zip_format(format_):
        def pretty_file_name(book):
            return "%s/%s.%s" % (
                book.extra_info['author'],
                book.slug,
                format_)

        field_name = "%s_file" % format_
        books = Book.objects.filter(parent=None).exclude(**{field_name: ""})
        paths = [(pretty_file_name(b), getattr(b, field_name).path) for b in books.iterator()]
        return create_zip(paths, app_settings.FORMAT_ZIPS[format_])

    def zip_audiobooks(self, format_):
        bm = BookMedia.objects.filter(book=self, type=format_)
        paths = map(lambda bm: (None, bm.file.path), bm)
        return create_zip(paths, "%s_%s" % (self.slug, format_))

    def search_index(self, book_info=None, index=None, index_tags=True, commit=True):
        if index is None:
            from search.index import Index
            index = Index()
        try:
            index.index_book(self, book_info)
            if index_tags:
                index.index_tags()
            if commit:
                index.index.commit()
        except Exception, e:
            index.index.rollback()
            raise e
Exemplo n.º 5
0
class Book(models.Model):
    """Represents a book imported from WL-XML."""
    title         = models.CharField(_('title'), max_length=120)
    sort_key = models.CharField(_('sort key'), max_length=120, db_index=True, editable=False)
    slug = models.SlugField(_('slug'), max_length=120, db_index=True,
            unique=True)
    common_slug = models.SlugField(_('slug'), max_length=120, db_index=True)
    language = models.CharField(_('language code'), max_length=3, db_index=True,
                    default=app_settings.DEFAULT_LANGUAGE)
    description   = models.TextField(_('description'), blank=True)
    created_at    = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
    changed_at    = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
    parent_number = models.IntegerField(_('parent number'), default=0)
    extra_info    = jsonfield.JSONField(_('extra information'), default='{}')
    gazeta_link   = models.CharField(blank=True, max_length=240)
    wiki_link     = models.CharField(blank=True, max_length=240)
    # files generated during publication

    cover = EbookField('cover', _('cover'),
                upload_to=book_upload_path('jpg'), null=True, blank=True)
    ebook_formats = constants.EBOOK_FORMATS
    formats = ebook_formats + ['html', 'xml']

    parent = models.ForeignKey('self', blank=True, null=True,
        related_name='children')

    _related_info = jsonfield.JSONField(blank=True, null=True, editable=False)

    objects  = models.Manager()
    tagged   = managers.ModelTaggedItemManager(Tag)
    tags     = managers.TagDescriptor(Tag)

    html_built = django.dispatch.Signal()
    published = django.dispatch.Signal()

    class AlreadyExists(Exception):
        pass

    class Meta:
        ordering = ('sort_key',)
        verbose_name = _('book')
        verbose_name_plural = _('books')
        app_label = 'catalogue'

    def __unicode__(self):
        return self.title

    def save(self, force_insert=False, force_update=False, reset_short_html=True, **kwargs):
        from sortify import sortify

        self.sort_key = sortify(self.title)

        ret = super(Book, self).save(force_insert, force_update)

        if reset_short_html:
            self.reset_short_html()

        return ret

    @permalink
    def get_absolute_url(self):
        return ('catalogue.views.book_detail', [self.slug])

    @staticmethod
    @permalink
    def create_url(slug):
        return ('catalogue.views.book_detail', [slug])

    @property
    def name(self):
        return self.title

    def book_tag_slug(self):
        return ('l-' + self.slug)[:120]

    def book_tag(self):
        slug = self.book_tag_slug()
        book_tag, created = Tag.objects.get_or_create(slug=slug, category='book')
        if created:
            book_tag.name = self.title[:50]
            book_tag.sort_key = self.title.lower()
            book_tag.save()
        return book_tag

    def has_media(self, type_):
        if type_ in Book.formats:
            return bool(getattr(self, "%s_file" % type_))
        else:
            return self.media.filter(type=type_).exists()

    def get_media(self, type_):
        if self.has_media(type_):
            if type_ in Book.formats:
                return getattr(self, "%s_file" % type_)
            else:                                             
                return self.media.filter(type=type_)
        else:
            return None

    def get_mp3(self):
        return self.get_media("mp3")
    def get_odt(self):
        return self.get_media("odt")
    def get_ogg(self):
        return self.get_media("ogg")
    def get_daisy(self):
        return self.get_media("daisy")                       

    def reset_short_html(self):
        if self.id is None:
            return

        type(self).objects.filter(pk=self.pk).update(_related_info=None)
        # Fragment.short_html relies on book's tags, so reset it here too
        for fragm in self.fragments.all().iterator():
            fragm.reset_short_html()

    def has_description(self):
        return len(self.description) > 0
    has_description.short_description = _('description')
    has_description.boolean = True

    # ugly ugly ugly
    def has_mp3_file(self):
        return bool(self.has_media("mp3"))
    has_mp3_file.short_description = 'MP3'
    has_mp3_file.boolean = True

    def has_ogg_file(self):
        return bool(self.has_media("ogg"))
    has_ogg_file.short_description = 'OGG'
    has_ogg_file.boolean = True

    def has_daisy_file(self):
        return bool(self.has_media("daisy"))
    has_daisy_file.short_description = 'DAISY'
    has_daisy_file.boolean = True

    def wldocument(self, parse_dublincore=True, inherit=True):
        from catalogue.import_utils import ORMDocProvider
        from librarian.parser import WLDocument

        if inherit and self.parent:
            meta_fallbacks = self.parent.cover_info()
        else:
            meta_fallbacks = None

        return WLDocument.from_file(self.xml_file.path,
                provider=ORMDocProvider(self),
                parse_dublincore=parse_dublincore,
                meta_fallbacks=meta_fallbacks)

    @staticmethod
    def zip_format(format_):
        def pretty_file_name(book):
            return "%s/%s.%s" % (
                book.extra_info['author'],
                book.slug,
                format_)

        field_name = "%s_file" % format_
        books = Book.objects.filter(parent=None).exclude(**{field_name: ""})
        paths = [(pretty_file_name(b), getattr(b, field_name).path)
                    for b in books.iterator()]
        return create_zip(paths, app_settings.FORMAT_ZIPS[format_])

    def zip_audiobooks(self, format_):
        bm = BookMedia.objects.filter(book=self, type=format_)
        paths = map(lambda bm: (None, bm.file.path), bm)
        return create_zip(paths, "%s_%s" % (self.slug, format_))

    def search_index(self, book_info=None, index=None, index_tags=True, commit=True):
        import search
        if index is None:
            index = search.Index()
        try:
            index.index_book(self, book_info)
            if index_tags:
                index.index_tags()
            if commit:
                index.index.commit()
        except Exception, e:
            index.index.rollback()
            raise e
Exemplo n.º 6
0
class Picture(models.Model):
    """
    Picture resource.

    """
    title = models.CharField(_('title'), max_length=120)
    slug = models.SlugField(_('slug'),
                            max_length=120,
                            db_index=True,
                            unique=True)
    sort_key = models.CharField(_('sort key'),
                                max_length=120,
                                db_index=True,
                                editable=False)
    created_at = models.DateTimeField(_('creation date'),
                                      auto_now_add=True,
                                      db_index=True)
    changed_at = models.DateTimeField(_('creation date'),
                                      auto_now=True,
                                      db_index=True)
    xml_file = models.FileField('xml_file',
                                upload_to="xml",
                                storage=picture_storage)
    image_file = ImageField(_('image_file'),
                            upload_to="images",
                            storage=picture_storage)
    objects = models.Manager()
    tagged = managers.ModelTaggedItemManager(catalogue.models.Tag)
    tags = managers.TagDescriptor(catalogue.models.Tag)

    class AlreadyExists(Exception):
        pass

    class Meta:
        ordering = ('sort_key', )

        verbose_name = _('picture')
        verbose_name_plural = _('pictures')

    def save(self,
             force_insert=False,
             force_update=False,
             reset_short_html=True,
             **kwargs):
        from sortify import sortify

        self.sort_key = sortify(self.title)

        ret = super(Picture, self).save(force_insert, force_update)

        if reset_short_html:
            self.reset_short_html()

        return ret

    def __unicode__(self):
        return self.title

    @permalink
    def get_absolute_url(self):
        return ('picture.views.picture_detail', [self.slug])

    @classmethod
    def from_xml_file(cls, xml_file, image_file=None, overwrite=False):
        """
        Import xml and it's accompanying image file.
        If image file is missing, it will be fetched by librarian.picture.ImageStore
        which looks for an image file in the same directory the xml is, with extension matching
        its mime type.
        """
        from sortify import sortify
        from django.core.files import File
        from librarian.picture import WLPicture, ImageStore
        close_xml_file = False
        close_image_file = False
        # class SimpleImageStore(object):
        #     def path(self_, slug, mime_type):
        #         """Returns the image file. Ignores slug ad mime_type."""
        #         return image_file

        if image_file is not None and not isinstance(image_file, File):
            image_file = File(open(image_file))
            close_image_file = True

        if not isinstance(xml_file, File):
            xml_file = File(open(xml_file))
            close_xml_file = True

        try:
            # use librarian to parse meta-data
            picture_xml = WLPicture.from_file(
                xml_file,
                image_store=ImageStore(picture_storage.path('images')))
            # image_store=SimpleImageStore

            picture, created = Picture.objects.get_or_create(
                slug=picture_xml.slug)
            if not created and not overwrite:
                raise Picture.AlreadyExists('Picture %s already exists' %
                                            picture_xml.slug)

            picture.title = picture_xml.picture_info.title

            motif_tags = set()
            for part in picture_xml.partiter():
                for motif in part['themes']:
                    tag, created = catalogue.models.Tag.objects.get_or_create(
                        slug=slughifi(motif), category='theme')
                    if created:
                        tag.name = motif
                        tag.sort_key = sortify(tag.name)
                        tag.save()
                    motif_tags.add(tag)

            picture.tags = catalogue.models.Tag.tags_from_info(picture_xml.picture_info) + \
                list(motif_tags)

            if image_file is not None:
                img = image_file
            else:
                img = picture_xml.image_file()

            # FIXME: hardcoded extension
            picture.image_file.save(path.basename(picture_xml.image_path),
                                    File(img))

            picture.xml_file.save("%s.xml" % picture.slug, File(xml_file))
            picture.save()
        finally:
            if close_xml_file:
                xml_file.close()
            if close_image_file:
                image_file.close()
        return picture

    @classmethod
    def picture_list(cls, filter=None):
        """Generates a hierarchical listing of all pictures
        Pictures are optionally filtered with a test function.
        """

        pics = cls.objects.all().order_by('sort_key')\
            .only('title', 'slug', 'image_file')

        if filter:
            pics = pics.filter(filter).distinct()

        pics_by_author = SortedDict()
        orphans = []
        for tag in catalogue.models.Tag.objects.filter(category='author'):
            pics_by_author[tag] = []

        for pic in pics.iterator():
            authors = list(pic.tags.filter(category='author'))
            if authors:
                for author in authors:
                    pics_by_author[author].append(pic)
            else:
                orphans.append(pic)

        return pics_by_author, orphans

    @property
    def info(self):
        if not hasattr(self, '_info'):
            from librarian import dcparser
            from librarian import picture
            info = dcparser.parse(self.xml_file.path, picture.PictureInfo)
            self._info = info
        return self._info

    def reset_short_html(self):
        if self.id is None:
            return

        cache_key = "Picture.short_html/%d" % (self.id)
        get_cache('permanent').delete(cache_key)

    def short_html(self):
        if self.id:
            cache_key = "Picture.short_html/%d" % (self.id)
            short_html = get_cache('permanent').get(cache_key)
        else:
            short_html = None

        if short_html is not None:
            return mark_safe(short_html)
        else:
            tags = self.tags.filter(category__in=('author', 'kind', 'epoch'))
            tags = split_tags(tags)

            short_html = unicode(
                render_to_string('picture/picture_short.html', {
                    'picture': self,
                    'tags': tags
                }))

            if self.id:
                get_cache('permanent').set(cache_key, short_html)
            return mark_safe(short_html)
Exemplo n.º 7
0
class Picture(models.Model):
    """
    Picture resource.

    """
    title = models.CharField(_('title'), max_length=32767)
    slug = models.SlugField(_('slug'), max_length=120, db_index=True, unique=True)
    sort_key = models.CharField(_('sort key'), max_length=120, db_index=True, editable=False)
    sort_key_author = models.CharField(
        _('sort key by author'), max_length=120, db_index=True, editable=False, default=u'')
    created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
    changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
    xml_file = models.FileField(_('xml file'), upload_to="xml", storage=picture_storage)
    image_file = ImageField(_('image file'), upload_to="images", storage=picture_storage)
    html_file = models.FileField(_('html file'), upload_to="html", storage=picture_storage)
    areas_json = jsonfield.JSONField(_('picture areas JSON'), default={}, editable=False)
    extra_info = jsonfield.JSONField(_('extra information'), default={})
    culturepl_link = models.CharField(blank=True, max_length=240)
    wiki_link = models.CharField(blank=True, max_length=240)

    width = models.IntegerField(null=True)
    height = models.IntegerField(null=True)

    objects = models.Manager()
    tagged = managers.ModelTaggedItemManager(catalogue.models.Tag)
    tags = managers.TagDescriptor(catalogue.models.Tag)
    tag_relations = GenericRelation(catalogue.models.Tag.intermediary_table_model)

    short_html_url_name = 'picture_short'

    class AlreadyExists(Exception):
        pass

    class Meta:
        ordering = ('sort_key_author', 'sort_key')

        verbose_name = _('picture')
        verbose_name_plural = _('pictures')

    def save(self, force_insert=False, force_update=False, **kwargs):
        from sortify import sortify

        self.sort_key = sortify(self.title)[:120]

        try:
            author = self.authors().first().sort_key
        except AttributeError:
            author = u''
        self.sort_key_author = author

        ret = super(Picture, self).save(force_insert, force_update)

        return ret

    def __unicode__(self):
        return self.title

    def authors(self):
        return self.tags.filter(category='author')

    def tag_unicode(self, category):
        relations = prefetched_relations(self, category)
        if relations:
            return ', '.join(rel.tag.name for rel in relations)
        else:
            return ', '.join(self.tags.filter(category=category).values_list('name', flat=True))

    def author_unicode(self):
        return self.tag_unicode('author')

    @permalink
    def get_absolute_url(self):
        return 'picture.views.picture_detail', [self.slug]

    def get_initial(self):
        try:
            return re.search(r'\w', self.title, re.U).group(0)
        except AttributeError:
            return ''

    def get_next(self):
        try:
            return type(self).objects.filter(sort_key__gt=self.sort_key)[0]
        except IndexError:
            return None

    def get_previous(self):
        try:
            return type(self).objects.filter(sort_key__lt=self.sort_key).order_by('-sort_key')[0]
        except IndexError:
            return None

    @classmethod
    def from_xml_file(cls, xml_file, image_file=None, image_store=None, overwrite=False):
        """
        Import xml and it's accompanying image file.
        If image file is missing, it will be fetched by librarian.picture.ImageStore
        which looks for an image file in the same directory the xml is, with extension matching
        its mime type.
        """
        from sortify import sortify
        from django.core.files import File
        from librarian.picture import WLPicture, ImageStore
        close_xml_file = False
        close_image_file = False

        if image_file is not None and not isinstance(image_file, File):
            image_file = File(open(image_file))
            close_image_file = True

        if not isinstance(xml_file, File):
            xml_file = File(open(xml_file))
            close_xml_file = True

        with transaction.atomic():
            # use librarian to parse meta-data
            if image_store is None:
                image_store = ImageStore(picture_storage.path('images'))
            picture_xml = WLPicture.from_file(xml_file, image_store=image_store)

            picture, created = Picture.objects.get_or_create(slug=picture_xml.slug[:120])
            if not created and not overwrite:
                raise Picture.AlreadyExists('Picture %s already exists' % picture_xml.slug)

            picture.areas.all().delete()
            picture.title = unicode(picture_xml.picture_info.title)
            picture.extra_info = picture_xml.picture_info.to_dict()

            picture_tags = set(catalogue.models.Tag.tags_from_info(picture_xml.picture_info))
            motif_tags = set()
            thing_tags = set()

            area_data = {'themes': {}, 'things': {}}

            # Treat all names in picture XML as in default language.
            lang = settings.LANGUAGE_CODE

            for part in picture_xml.partiter():
                if picture_xml.frame:
                    c = picture_xml.frame[0]
                    part['coords'] = [[p[0] - c[0], p[1] - c[1]] for p in part['coords']]
                if part.get('object', None) is not None:
                    _tags = set()
                    for objname in part['object'].split(','):
                        objname = objname.strip().capitalize()
                        tag, created = catalogue.models.Tag.objects.get_or_create(
                            slug=slughifi(objname), category='thing')
                        if created:
                            tag.name = objname
                            setattr(tag, 'name_%s' % lang, tag.name)
                            tag.sort_key = sortify(tag.name)
                            tag.save()
                        # thing_tags.add(tag)
                        area_data['things'][tag.slug] = {
                            'object': objname,
                            'coords': part['coords'],
                            }

                        _tags.add(tag)
                    area = PictureArea.rectangle(picture, 'thing', part['coords'])
                    area.save()
                    area.tags = _tags
                else:
                    _tags = set()
                    for motifs in part['themes']:
                        for motif in motifs.split(','):
                            tag, created = catalogue.models.Tag.objects.get_or_create(
                                slug=slughifi(motif), category='theme')
                            if created:
                                tag.name = motif
                                tag.sort_key = sortify(tag.name)
                                tag.save()
                            # motif_tags.add(tag)
                            _tags.add(tag)
                            area_data['themes'][tag.slug] = {
                                'theme': motif,
                                'coords': part['coords']
                                }

                    logging.debug("coords for theme: %s" % part['coords'])
                    area = PictureArea.rectangle(picture, 'theme', part['coords'])
                    area.save()
                    area.tags = _tags.union(picture_tags)

            picture.tags = picture_tags.union(motif_tags).union(thing_tags)
            picture.areas_json = area_data

            if image_file is not None:
                img = image_file
            else:
                img = picture_xml.image_file()

            modified = cls.crop_to_frame(picture_xml, img)
            modified = cls.add_source_note(picture_xml, modified)

            picture.width, picture.height = modified.size

            modified_file = StringIO()
            modified.save(modified_file, format='JPEG', quality=95)
            # FIXME: hardcoded extension - detect from DC format or orginal filename
            picture.image_file.save(path.basename(picture_xml.image_path), File(modified_file))

            picture.xml_file.save("%s.xml" % picture.slug, File(xml_file))
            picture.save()
            tasks.generate_picture_html(picture.id)

        if close_xml_file:
            xml_file.close()
        if close_image_file:
            image_file.close()

        return picture

    @classmethod
    def crop_to_frame(cls, wlpic, image_file):
        img = Image.open(image_file)
        if wlpic.frame is None or wlpic.frame == [[0, 0], [-1, -1]]:
            return img
        img = img.crop(itertools.chain(*wlpic.frame))
        return img

    @staticmethod
    def add_source_note(wlpic, img):
        from PIL import ImageDraw, ImageFont
        from librarian import get_resource

        annotated = Image.new(img.mode, (img.size[0], img.size[1] + 40), (255, 255, 255))
        annotated.paste(img, (0, 0))
        annotation = Image.new('RGB', (img.size[0] * 3, 120), (255, 255, 255))
        ImageDraw.Draw(annotation).text(
            (30, 15),
            wlpic.picture_info.source_name,
            (0, 0, 0),
            font=ImageFont.truetype(get_resource("fonts/DejaVuSerif.ttf"), 75)
        )
        annotated.paste(annotation.resize((img.size[0], 40), Image.ANTIALIAS), (0, img.size[1]))
        return annotated

    # WTF/unused
    @classmethod
    def picture_list(cls, filter=None):
        """Generates a hierarchical listing of all pictures
        Pictures are optionally filtered with a test function.
        """

        pics = cls.objects.all().order_by('sort_key').only('title', 'slug', 'image_file')

        if filter:
            pics = pics.filter(filter).distinct()

        pics_by_author = SortedDict()
        orphans = []
        for tag in catalogue.models.Tag.objects.filter(category='author'):
            pics_by_author[tag] = []

        for pic in pics.iterator():
            authors = list(pic.authors().only('pk'))
            if authors:
                for author in authors:
                    pics_by_author[author].append(pic)
            else:
                orphans.append(pic)

        return pics_by_author, orphans

    @property
    def info(self):
        if not hasattr(self, '_info'):
            from librarian import dcparser
            from librarian import picture
            info = dcparser.parse(self.xml_file.path, picture.PictureInfo)
            self._info = info
        return self._info

    def pretty_title(self, html_links=False):
        names = [(tag.name, tag.get_absolute_url()) for tag in self.authors().only('name', 'category', 'slug')]
        names.append((self.title, self.get_absolute_url()))

        if html_links:
            names = ['<a href="%s">%s</a>' % (tag[1], tag[0]) for tag in names]
        else:
            names = [tag[0] for tag in names]
        return ', '.join(names)

    def related_themes(self):
        return catalogue.models.Tag.objects.usage_for_queryset(
            self.areas.all(), counts=True).filter(category__in=('theme', 'thing'))

    def flush_includes(self, languages=True):
        if not languages:
            return
        if languages is True:
            languages = [lc for (lc, _ln) in settings.LANGUAGES]
        flush_ssi_includes([
            template % (self.pk, lang)
            for template in [
                '/katalog/p/%d/short.%s.html',
                '/katalog/p/%d/mini.%s.html',
                ]
            for lang in languages
            ])