def add_breadcrumbs(self, lane, annotator, include_lane=False): """Add list of ancestor links in a breadcrumbs element.""" # Ensure that lane isn't top-level before proceeding if annotator.lane_url(lane) != annotator.default_lane_url(): breadcrumbs = AtomFeed.makeelement("{%s}breadcrumbs" % AtomFeed.SIMPLIFIED_NS) # Add root link root_url = annotator.default_lane_url() breadcrumbs.append( AtomFeed.link(title=annotator.top_level_title(), href=root_url) ) # Add links for all visible ancestors that aren't root for ancestor in reversed(lane.visible_ancestors()): lane_url = annotator.lane_url(ancestor) if lane_url != root_url: breadcrumbs.append( AtomFeed.link(title=ancestor.display_name, href=lane_url) ) # Include link to lane # For search, breadcrumbs include the searched lane if include_lane: breadcrumbs.append( AtomFeed.link(title=lane.display_name, href=annotator.lane_url(lane)) ) self.feed.append(breadcrumbs)
def detailed_author(cls, contributor): """Turn a Contributor into a detailed <author> tag.""" children = [] children.append(AtomFeed.name(contributor.display_name or "")) sort_name = AtomFeed.makeelement("{%s}sort_name" % AtomFeed.SIMPLIFIED_NS) sort_name.text = contributor.sort_name children.append(sort_name) if contributor.family_name: family_name = AtomFeed.makeelement(AtomFeed.schema_("family_name")) family_name.text = contributor.family_name children.append(family_name) if contributor.wikipedia_name: wikipedia_name = AtomFeed.makeelement( "{%s}wikipedia_name" % AtomFeed.SIMPLIFIED_NS) wikipedia_name.text = contributor.wikipedia_name children.append(wikipedia_name) if contributor.viaf: viaf_tag = AtomFeed.makeelement(AtomFeed.schema_("sameas")) viaf_tag.text = "http://viaf.org/viaf/%s" % contributor.viaf children.append(viaf_tag) if contributor.lc: lc_tag = AtomFeed.makeelement(AtomFeed.schema_("sameas")) lc_tag.text = "http://id.loc.gov/authorities/names/%s" % contributor.lc children.append(lc_tag) return AtomFeed.author(*children)
def test_add_link_to_entry(self): kwargs = dict(title=1, href="url", extra="extra info") entry = AtomFeed.E.entry() link_child = AtomFeed.E.link_child() AtomFeed.add_link_to_entry(entry, [link_child], **kwargs) assert '<link extra="extra info" href="url" title="1"><link_child/></link>' in etree.tostring( entry)
def rating_tag(cls, type_uri, value): """Generate a schema:Rating tag for the given type and value.""" rating_tag = AtomFeed.makeelement(AtomFeed.schema_("Rating")) value_key = AtomFeed.schema_('ratingValue') rating_tag.set(value_key, "%.4f" % value) if type_uri: type_key = AtomFeed.schema_('additionalType') rating_tag.set(type_key, type_uri) return rating_tag
def series(cls, series_name, series_position): """Generate a schema:Series tag for the given name and position.""" if not series_name: return None series_details = dict() series_details['name'] = series_name if series_position: series_details[AtomFeed.schema_('position')] = unicode(series_position) series_tag = AtomFeed.makeelement(AtomFeed.schema_("Series"), **series_details) return series_tag
def categories(cls, work): """Send out _all_ categories for the work. (So long as the category type has a URI associated with it in Subject.uri_lookup.) """ _db = Session.object_session(work) by_scheme_and_term = dict() identifier_ids = work.all_identifier_ids() classifications = Identifier.classifications_for_identifier_ids( _db, identifier_ids) for c in classifications: subject = c.subject if subject.type in Subject.uri_lookup: scheme = Subject.uri_lookup[subject.type] term = subject.identifier weight_field = AtomFeed.schema_("ratingValue") key = (scheme, term) if not key in by_scheme_and_term: value = dict(term=subject.identifier) if subject.name: value['label'] = subject.name value[weight_field] = 0 by_scheme_and_term[key] = value by_scheme_and_term[key][weight_field] += c.weight # Collapse by_scheme_and_term to by_scheme by_scheme = defaultdict(list) for (scheme, term), value in by_scheme_and_term.items(): by_scheme[scheme].append(value) by_scheme.update(super(VerboseAnnotator, cls).categories(work)) return by_scheme
def indirect_acquisition(cls, indirect_types): top_level_parent = None parent = None for t in indirect_types: indirect_link = AtomFeed.makeelement( "{%s}indirectAcquisition" % AtomFeed.OPDS_NS, type=t) if parent is not None: parent.extend([indirect_link]) parent = indirect_link if top_level_parent is None: top_level_parent = indirect_link return top_level_parent
def minimal_opds_entry(cls, identifier, cover, description, quality): elements = [] representations = [] most_recent_update = None if cover: cover_representation = cover.representation representations.append(cover.representation) cover_link = AtomFeed.makeelement( "link", href=cover_representation.mirror_url, type=cover_representation.media_type, rel=Hyperlink.IMAGE) elements.append(cover_link) if cover_representation.thumbnails: thumbnail = cover_representation.thumbnails[0] representations.append(thumbnail) thumbnail_link = AtomFeed.makeelement( "link", href=thumbnail.mirror_url, type=thumbnail.media_type, rel=Hyperlink.THUMBNAIL_IMAGE ) elements.append(thumbnail_link) if description: content = description.representation.content if isinstance(content, str): content = content.decode("utf8") description_e = AtomFeed.summary(content, type='html') elements.append(description_e) representations.append(description.representation) if quality: elements.append( Annotator.rating_tag(Measurement.QUALITY, quality)) # The update date is the most recent date any of these # resources were mirrored/fetched. potential_update_dates = [ r.mirrored_at or r.fetched_at for r in representations if r.mirrored_at or r.fetched_at ] if potential_update_dates: update_date = max(potential_update_dates) elements.append(AtomFeed.updated(AtomFeed._strftime(update_date))) entry = AtomFeed.entry( AtomFeed.id(identifier.urn), AtomFeed.title(OPDSFeed.NO_TITLE), *elements ) return entry
try: for result in instance._run_self_tests(_db): results.append(result) except Exception, e: # This should only happen when there's a bug in the # self-test method itself. failure = instance.test_failure( "Uncaught exception in the self-test method itself.", e) results.append(failure) end = datetime.datetime.utcnow() # Format the results in a useful way. value = dict(start=AtomFeed._strftime(start), end=AtomFeed._strftime(end), duration=(end - start).total_seconds(), results=[x.to_dict for x in results]) # Store the formatted results in the database, if we can find # a place to store them. if instance and isinstance(instance, ExternalSearchIndex): integration = instance.search_integration(_db) for idx, result in enumerate(value.get("results")): if isinstance(results[idx].result, (list, )): result["result"] = results[idx].result elif instance: integration = instance.external_integration(_db)
def link(cls, rel, href, type): return AtomFeed.makeelement("link", type=type, rel=rel, href=href)
try: for result in instance._run_self_tests(_db): results.append(result) except Exception, e: # This should only happen when there's a bug in the # self-test method itself. failure = instance.test_failure( "Uncaught exception in the self-test method itself.", e ) results.append(failure) integration = instance.external_integration(_db) end = datetime.datetime.utcnow() # Format the results in a useful way. value = dict( start=AtomFeed._strftime(start), end=AtomFeed._strftime(end), duration = (end-start).total_seconds(), results = [x.to_dict for x in results] ) # Store the formatted results in the database, if we can find # a place to store them. if integration: integration.setting( cls.SELF_TEST_RESULTS_SETTING ).value = json.dumps(value) return value, results @classmethod def prior_test_results(cls, _db, constructor_method=None, *args, **kwargs):
except Exception, e: # This should only happen when there's a bug in the # self-test method itself. failure = instance.test_failure( "Uncaught exception in the self-test method itself.", e ) results.append(failure) end = datetime.datetime.utcnow() # Format the results in a useful way. value = dict( start=AtomFeed._strftime(start), end=AtomFeed._strftime(end), duration = (end-start).total_seconds(), results = [x.to_dict for x in results] ) # Store the formatted results in the database, if we can find # a place to store them. if instance and isinstance(instance, ExternalSearchIndex): integration = instance.search_integration(_db) for idx, result in enumerate(value.get("results")): if isinstance(results[idx].result, (list,)): result["result"] = results[idx].result elif instance: integration = instance.external_integration(_db)
def _make_entry_xml(self, work, license_pool, edition, identifier, lane_link): # Find the .epub link epub_href = None p = None links = [] cover_quality = 0 qualities = [] if work: qualities.append(("Work quality", work.quality)) full_url = None thumbnail_urls, full_urls = self.annotator.cover_links(work) for rel, urls in ( (Hyperlink.IMAGE, full_urls), (Hyperlink.THUMBNAIL_IMAGE, thumbnail_urls)): for url in urls: image_type = "image/png" if url.endswith(".jpeg") or url.endswith(".jpg"): image_type = "image/jpeg" elif url.endswith(".gif"): image_type = "image/gif" links.append(AtomFeed.link(rel=rel, href=url, type=image_type)) permalink = self.annotator.permalink_for(work, license_pool, identifier) content = self.annotator.content(work) if isinstance(content, str): content = content.decode("utf8") content_type = 'html' kw = {} if edition.medium: additional_type = Edition.medium_to_additional_type.get( edition.medium) if not additional_type: logging.warn("No additionalType for medium %s", edition.medium) additional_type_field = AtomFeed.schema_("additionalType") kw[additional_type_field] = additional_type entry = AtomFeed.entry( AtomFeed.id(permalink), AtomFeed.title(edition.title or OPDSFeed.NO_TITLE), **kw ) if edition.subtitle: subtitle_tag = AtomFeed.makeelement(AtomFeed.schema_("alternativeHeadline")) subtitle_tag.text = edition.subtitle entry.append(subtitle_tag) if license_pool: provider_name_attr = "{%s}ProviderName" % AtomFeed.BIBFRAME_NS kwargs = {provider_name_attr : license_pool.data_source.name} data_source_tag = AtomFeed.makeelement( "{%s}distribution" % AtomFeed.BIBFRAME_NS, **kwargs ) entry.extend([data_source_tag]) author_tags = self.annotator.authors(work, license_pool, edition, identifier) entry.extend(author_tags) if edition.series: entry.extend([self.annotator.series(edition.series, edition.series_position)]) if content: entry.extend([AtomFeed.summary(content, type=content_type)]) entry.extend([ AtomFeed.updated(AtomFeed._strftime(datetime.datetime.utcnow())), ]) permanent_work_id_tag = AtomFeed.makeelement("{%s}pwid" % AtomFeed.SIMPLIFIED_NS) permanent_work_id_tag.text = edition.permanent_work_id entry.append(permanent_work_id_tag) entry.extend(links) categories_by_scheme = self.annotator.categories(work) category_tags = [] for scheme, categories in categories_by_scheme.items(): for category in categories: if isinstance(category, basestring): category = dict(term=category) category = dict(map(unicode, (k, v)) for k, v in category.items()) category_tag = AtomFeed.category(scheme=scheme, **category) category_tags.append(category_tag) entry.extend(category_tags) # print " ID %s TITLE %s AUTHORS %s" % (tag, work.title, work.authors) language = edition.language_code if language: language_tag = AtomFeed.makeelement("{%s}language" % AtomFeed.DCTERMS_NS) language_tag.text = language entry.append(language_tag) if edition.publisher: publisher_tag = AtomFeed.makeelement("{%s}publisher" % AtomFeed.DCTERMS_NS) publisher_tag.text = edition.publisher entry.extend([publisher_tag]) # We use Atom 'published' for the date the book first became # available to people using this application. now = datetime.datetime.utcnow() today = datetime.date.today() if license_pool and license_pool.availability_time: avail = license_pool.availability_time if isinstance(avail, datetime.datetime): avail = avail.date() if avail <= today: availability_tag = AtomFeed.makeelement("published") # TODO: convert to local timezone. availability_tag.text = AtomFeed._strftime(license_pool.availability_time) entry.extend([availability_tag]) # Entry.issued is the date the ebook came out, as distinct # from Entry.published (which may refer to the print edition # or some original edition way back when). # # For Dublin Core 'created' we use Entry.issued if we have it # and Entry.published if not. In general this means we use # issued date for Gutenberg and published date for other # sources. # # We use dc:created instead of dc:issued because dc:issued is # commonly conflated with atom:published. # # For the date the book was added to our collection we use # atom:published. issued = edition.issued or edition.published if (isinstance(issued, datetime.datetime) or isinstance(issued, datetime.date)): issued_already = False if isinstance(issued, datetime.datetime): issued_already = (issued <= now) elif isinstance(issued, datetime.date): issued_already = (issued <= today) if issued_already: issued_tag = AtomFeed.makeelement("{%s}created" % AtomFeed.DCTERMS_NS) # TODO: convert to local timezone, not that it matters much. issued_tag.text = issued.strftime("%Y-%m-%d") entry.extend([issued_tag]) return entry
def test_contributor(self): kwargs = {'{%s}role' % AtomFeed.OPF_NS: 'ctb'} tag = etree.tostring(AtomFeed.author(**kwargs)) assert tag.startswith('<author') assert 'xmlns:opf="http://www.idpf.org/2007/opf"' in tag assert tag.endswith('opf:role="ctb"/>')
def authors(cls, work, license_pool, edition, identifier): """Create one or more <author> tags for the given work.""" return [AtomFeed.author(AtomFeed.name(edition.author or ""))]
def categories(cls, work): """Return all relevant classifications of this work. :return: A dictionary mapping 'scheme' URLs to dictionaries of attribute-value pairs. Notable attributes: 'term', 'label', 'http://schema.org/ratingValue' """ if not work: return {} categories = {} fiction_term = None if work.fiction == True: fiction_term = 'Fiction' elif work.fiction == False: fiction_term = 'Nonfiction' if fiction_term: fiction_scheme = Subject.SIMPLIFIED_FICTION_STATUS categories[fiction_scheme] = [ dict(term=fiction_scheme + fiction_term, label=fiction_term) ] simplified_genres = [] for wg in work.work_genres: simplified_genres.append(wg.genre.name) if simplified_genres: categories[Subject.SIMPLIFIED_GENRE] = [ dict(term=Subject.SIMPLIFIED_GENRE + urllib.quote(x), label=x) for x in simplified_genres ] # Add the appeals as a category of schema # http://librarysimplified.org/terms/appeal schema_url = AtomFeed.SIMPLIFIED_NS + "appeals/" appeals = [] categories[schema_url] = appeals for name, value in ( (Work.CHARACTER_APPEAL, work.appeal_character), (Work.LANGUAGE_APPEAL, work.appeal_language), (Work.SETTING_APPEAL, work.appeal_setting), (Work.STORY_APPEAL, work.appeal_story), ): if value: appeal = dict(term=schema_url + name, label=name) weight_field = AtomFeed.schema_("ratingValue") appeal[weight_field] = value appeals.append(appeal) # Add the audience as a category of schema # http://schema.org/audience if work.audience: audience_uri = AtomFeed.SCHEMA_NS + "audience" categories[audience_uri] = [ dict(term=work.audience, label=work.audience) ] if work.target_age: uri = Subject.uri_lookup[Subject.AGE_RANGE] target_age = work.target_age_string if target_age: categories[uri] = [dict(term=target_age, label=target_age)] return categories
def license_tags(cls, license_pool, loan, hold): # Generate a list of licensing tags. These should be inserted # into a <link> tag. tags = [] availability_tag_name = None suppress_since = False status = None since = None until = None if not license_pool: return if license_pool.open_access: default_loan_period = default_reservation_period = None else: ds = license_pool.data_source default_loan_period = ds.default_loan_period default_reservation_period = ds.default_reservation_period if loan: status = 'available' since = loan.start until = loan.until(default_loan_period) elif hold: until = hold.until(default_loan_period, default_reservation_period) if hold.position == 0: status = 'ready' since = None else: status = 'reserved' since = hold.start elif (license_pool.open_access or ( license_pool.licenses_available > 0 and license_pool.licenses_owned > 0) ): status = 'available' else: status='unavailable' kw = dict(status=status) if since: kw['since'] = AtomFeed._strftime(since) if until: kw['until'] = AtomFeed._strftime(until) tag_name = "{%s}availability" % AtomFeed.OPDS_NS availability_tag = AtomFeed.makeelement(tag_name, **kw) tags.append(availability_tag) # Open-access pools do not need to display <opds:holds> or <opds:copies>. if license_pool.open_access: return tags holds_kw = dict(total=str(license_pool.patrons_in_hold_queue or 0)) if hold and hold.position: holds_kw['position'] = str(hold.position) holds = AtomFeed.makeelement("{%s}holds" % AtomFeed.OPDS_NS, **holds_kw) tags.append(holds) copies_kw = dict( total=str(license_pool.licenses_owned or 0), available=str(license_pool.licenses_available or 0), ) copies = AtomFeed.makeelement("{%s}copies" % AtomFeed.OPDS_NS, **copies_kw) tags.append(copies) return tags