def create_lane_for_tiny_collection(_db, library, parent, languages, priority=0): """Create a single lane for a tiny collection based on language. :param parent: The parent of the new lane. """ if not languages: return None if isinstance(languages, basestring): languages = [languages] name = LanguageCodes.name_for_languageset(languages) language_lane, ignore = create( _db, Lane, library=library, display_name=name, parent=parent, genres=[], media=[Edition.BOOK_MEDIUM], fiction=None, priority=priority, languages=languages, ) return priority + 1
def lane_for_other_languages(_db, library, exclude_languages): """Make a lane for all books not in one of the given languages.""" language_lanes = [] other_languages = Configuration.tiny_collection_languages(library) if not other_languages: return None for language_set in other_languages: name = LanguageCodes.name_for_languageset(language_set) language_lane = Lane( _db, library, full_name=name, genres=None, fiction=Lane.BOTH_FICTION_AND_NONFICTION, searchable=True, languages=language_set, ) language_lanes.append(language_lane) lane = Lane( _db, library, full_name="Other Languages", sublanes=language_lanes, exclude_languages=exclude_languages, searchable=True, genres=None, ) lane.default_for_language = True return lane
def library_configuration_settings(self, library, validator): for setting in Configuration.LIBRARY_SETTINGS: if setting.get("format") == "geographic": locations = validator.validate_geographic_areas( self.list_setting(setting), self._db) if isinstance(locations, ProblemDetail): return locations value = locations or self.current_value(setting, library) elif setting.get("type") == "list": value = self.list_setting(setting) or self.current_value( setting, library) if setting.get("format") == "language-code": value = json.dumps([ LanguageCodes.string_to_alpha_3(language) for language in json.loads(value) ]) elif setting.get("type") == "image": value = self.image_setting(setting) or self.current_value( setting, library) else: default = setting.get('default') value = flask.request.form.get(setting['key'], default) ConfigurationSetting.for_library(setting['key'], library).value = value
def languages(self, library): ":yield: A list of output lines, one per language." for abbreviation, count in library.estimated_holdings_by_language( include_open_access=False ).most_common(): display_name = LanguageCodes.name_for_languageset(abbreviation) yield "%s %i (%s)" % (abbreviation, count, display_name)
def create_lane_for_tiny_collection(_db, library, parent, languages, priority=0): """Create a single lane for a tiny collection based on language, if the language exists in the lookup table. :param parent: The parent of the new lane. """ if not languages: return None if isinstance(languages, basestring): languages = [languages] try: name = LanguageCodes.name_for_languageset(languages) except ValueError as e: logging.getLogger().warn( "Could not create a lane for tiny collection with languages %s", languages ) return 0 language_lane, ignore = create( _db, Lane, library=library, display_name=name, parent=parent, genres=[], media=[Edition.BOOK_MEDIUM], fiction=None, priority=priority, languages=languages, ) return priority + 1
def create_lane_for_small_collection(_db, library, parent, languages, priority=0): """Create a lane (with sublanes) for a small collection based on language. :param parent: The parent of the new lane. """ if isinstance(languages, basestring): languages = [languages] ADULT = Classifier.AUDIENCES_ADULT YA_CHILDREN = [Classifier.AUDIENCE_YOUNG_ADULT, Classifier.AUDIENCE_CHILDREN] common_args = dict( languages=languages, media=[Edition.BOOK_MEDIUM], genres=[], ) language_identifier = LanguageCodes.name_for_languageset(languages) sublane_priority = 0 adult_fiction, ignore = create( _db, Lane, library=library, display_name="Fiction", fiction=True, audiences=ADULT, priority=sublane_priority, **common_args ) sublane_priority += 1 adult_nonfiction, ignore = create( _db, Lane, library=library, display_name="Nonfiction", fiction=False, audiences=ADULT, priority=sublane_priority, **common_args ) sublane_priority += 1 ya_children, ignore = create( _db, Lane, library=library, display_name="Children & Young Adult", fiction=None, audiences=YA_CHILDREN, priority=sublane_priority, **common_args ) sublane_priority += 1 lane, ignore = create( _db, Lane, library=library, display_name=language_identifier, parent=parent, sublanes=[adult_fiction, adult_nonfiction, ya_children], priority=priority, **common_args ) priority += 1 return priority
def parse(cls, file, data_source_name): metadata_records = [] reader = csv.DictReader(file) for row in reader: publisher = unicode(row.get('Sello Editorial'), 'utf-8') title = unicode(row.get('Title'), 'utf-8') # The spreadsheet's identifier column is labeled ISBN, but # contains custom eLiburutegia IDs, like "ELIB201600288". identifier = row.get('ISBN') primary_identifier = IdentifierData( Identifier.ELIB_ID, identifier) issued_date = datetime.datetime.strptime(row.get('Publication Date'), "%m/%d/%Y") author = unicode(row.get('Author'), 'utf-8') contributors = [ContributorData( sort_name=author, roles=[Contributor.AUTHOR_ROLE] )] subjects = [] bisac = row.get('BISAC') if bisac: subjects.append(SubjectData(Classifier.BISAC, bisac)) ibic = row.get('IBIC') if ibic: # I haven't found any documentation on IBIC, so I am # treating it as BIC for now. It's possible that some # of the codes won't be valid BIC codes, but they'll # just be ignored. subjects.append(SubjectData(Classifier.BIC, ibic)) age = row.get('Age') if age: age_re = re.compile(".*\(([\d-]+)\)") match = age_re.match(age) if match: subjects.append(SubjectData(Classifier.AGE_RANGE, match.groups()[0])) language = row.get('Language') if language: language = LanguageCodes.string_to_alpha_3(language) metadata_records.append(Metadata( data_source=data_source_name, title=title, language=language, medium=Edition.BOOK_MEDIUM, publisher=publisher, issued=issued_date, primary_identifier=primary_identifier, contributors=contributors, subjects=subjects, )) return metadata_records
def parse_args(self, cmd_args=None): parser = self.arg_parser(self._db) parsed = parser.parse_args(cmd_args) self.languages = [] if parsed.language: for language in parsed.language: alpha = LanguageCodes.string_to_alpha_3(language) if alpha: self.languages.append(alpha) else: self.log.warn("Ignored unrecognized language code %s", alpha) self.max_depth = parsed.max_depth self.min_depth = parsed.min_depth # Return the parsed arguments in case a subclass needs to # process more args. return parsed
def _validate_setting(self, library, setting, validator=None): """Validate the incoming value for a single library setting. :param library: A Library :param setting: Configuration data for one of the library's settings. :param validator: A validation object for data of this type. """ # TODO: there are some opportunities for improvement here: # * There's no standard interface for validators. # * We can handle settings that are lists of certain types (language codes, # geographic areas), but not settings that are a single value of that type # (_one_ language code or geographic area). Sometimes there's even an implication # that a certain data type ('geographic') _must_ mean a list. # * A list value is returned as a JSON-encoded string. It # would be better to keep that as a list for longer in case # controller code needs to look at it. format = setting.get("format") type = setting.get("type") # In some cases, if there is no incoming value we can use a # default value or the current value. # # When the configuration item is a list, we can't do this # because an empty list may be a valid value. current_value = self.current_value(setting, library) default_value = setting.get("default") or current_value if format == "geographic": value = self.list_setting(setting) value = validator.validate_geographic_areas(value, self._db) elif type == "announcements": value = self.list_setting(setting, json_objects=True) value = validator.validate_announcements(value) elif type == "list": value = self.list_setting(setting) if format == "language-code": value = json.dumps([ LanguageCodes.string_to_alpha_3(language) for language in json.loads(value) ]) else: if type == "image": value = self.image_setting(setting) or default_value else: value = self.scalar_setting(setting) or default_value return value
def library_configuration_settings(self, library, validator): for setting in Configuration.LIBRARY_SETTINGS: if setting.get("format") == "geographic": locations = validator.validate_geographic_areas(self.list_setting(setting), self._db) if isinstance(locations, ProblemDetail): return locations value = locations or self.current_value(setting, library) elif setting.get("type") == "list": value = self.list_setting(setting) or self.current_value(setting, library) if setting.get("format") == "language-code": value = json.dumps([LanguageCodes.string_to_alpha_3(language) for language in json.loads(value)]) elif setting.get("type") == "image": value = self.image_setting(setting) or self.current_value(setting, library) else: default = setting.get('default') value = flask.request.form.get(setting['key'], default) ConfigurationSetting.for_library(setting['key'], library).value = value
def lane_for_small_collection(_db, library, languages): YA = Classifier.AUDIENCE_YOUNG_ADULT CHILDREN = Classifier.AUDIENCE_CHILDREN common_args = dict( include_best_sellers=False, include_staff_picks=False, languages=languages, genres=None, ) adult_fiction = Lane(_db, library, full_name="Adult Fiction", display_name="Fiction", fiction=True, audiences=Classifier.AUDIENCES_ADULT, **common_args) adult_nonfiction = Lane(_db, library, full_name="Adult Nonfiction", display_name="Nonfiction", fiction=False, audiences=Classifier.AUDIENCES_ADULT, **common_args) ya_children = Lane(_db, library, full_name="Children & Young Adult", fiction=Lane.BOTH_FICTION_AND_NONFICTION, audiences=[YA, CHILDREN], **common_args) name = LanguageCodes.name_for_languageset(languages) lane = Lane(_db, library, full_name=name, languages=languages, sublanes=[adult_fiction, adult_nonfiction, ya_children], searchable=True) lane.default_for_language = True return lane
def lane_for_small_collection(_db, languages): YA = Classifier.AUDIENCE_YOUNG_ADULT CHILDREN = Classifier.AUDIENCE_CHILDREN common_args = dict( include_best_sellers=False, include_staff_picks=False, languages=languages, genres=None, ) adult_fiction = Lane( _db, full_name="Adult Fiction", display_name="Fiction", fiction=True, audiences=Classifier.AUDIENCES_ADULT, **common_args ) adult_nonfiction = Lane( _db, full_name="Adult Nonfiction", display_name="Nonfiction", fiction=False, audiences=Classifier.AUDIENCES_ADULT, **common_args ) ya_children = Lane( _db, full_name="Children & Young Adult", fiction=Lane.BOTH_FICTION_AND_NONFICTION, audiences=[YA, CHILDREN], **common_args ) name = LanguageCodes.name_for_languageset(languages) lane = Lane( _db, full_name=name, languages=languages, sublanes=[adult_fiction, adult_nonfiction, ya_children], searchable=True ) lane.default_for_language = True return lane
def lanes_for_large_collection(_db, library, languages): YA = Classifier.AUDIENCE_YOUNG_ADULT CHILDREN = Classifier.AUDIENCE_CHILDREN common_args = dict( languages=languages, include_best_sellers=True, include_staff_picks=True, ) adult_fiction = Lane(_db, library, full_name="Adult Fiction", display_name="Fiction", genres=None, sublanes=lanes_from_genres( _db, library, fiction_genres, languages=languages, audiences=Classifier.AUDIENCES_ADULT, ), fiction=True, audiences=Classifier.AUDIENCES_ADULT, **common_args) adult_nonfiction = Lane(_db, library, full_name="Adult Nonfiction", display_name="Nonfiction", genres=None, sublanes=lanes_from_genres( _db, library, nonfiction_genres, languages=languages, audiences=Classifier.AUDIENCES_ADULT, ), fiction=False, audiences=Classifier.AUDIENCES_ADULT, **common_args) ya_common_args = dict( audiences=YA, languages=languages, ) ya_fiction = Lane( _db, library, full_name="Young Adult Fiction", genres=None, fiction=True, include_best_sellers=True, include_staff_picks=True, sublanes=[ Lane(_db, library, full_name="YA Dystopian", display_name="Dystopian", genres=[genres.Dystopian_SF], **ya_common_args), Lane(_db, library, full_name="YA Fantasy", display_name="Fantasy", genres=[genres.Fantasy], subgenre_behavior=Lane.IN_SAME_LANE, **ya_common_args), Lane(_db, library, full_name="YA Graphic Novels", display_name="Comics & Graphic Novels", genres=[genres.Comics_Graphic_Novels], **ya_common_args), Lane(_db, library, full_name="YA Literary Fiction", display_name="Contemporary Fiction", genres=[genres.Literary_Fiction], **ya_common_args), Lane(_db, library, full_name="YA LGBTQ Fiction", display_name="LGBTQ Fiction", genres=[genres.LGBTQ_Fiction], **ya_common_args), Lane(_db, library, full_name="Mystery & Thriller", genres=[genres.Suspense_Thriller, genres.Mystery], subgenre_behavior=Lane.IN_SAME_LANE, **ya_common_args), Lane(_db, library, full_name="YA Romance", display_name="Romance", genres=[genres.Romance], subgenre_behavior=Lane.IN_SAME_LANE, **ya_common_args), Lane(_db, library, full_name="YA Science Fiction", display_name="Science Fiction", genres=[genres.Science_Fiction], subgenre_behavior=Lane.IN_SAME_LANE, exclude_genres=[genres.Dystopian_SF, genres.Steampunk], **ya_common_args), Lane(_db, library, full_name="YA Steampunk", genres=[genres.Steampunk], subgenre_behavior=Lane.IN_SAME_LANE, display_name="Steampunk", **ya_common_args), # TODO: # Paranormal -- what is it exactly? ], **ya_common_args) ya_nonfiction = Lane( _db, library, full_name="Young Adult Nonfiction", genres=None, fiction=False, include_best_sellers=True, include_staff_picks=True, sublanes=[ Lane(_db, library, full_name="YA Biography", genres=genres.Biography_Memoir, display_name="Biography", **ya_common_args), Lane(_db, library, full_name="YA History", genres=[genres.History, genres.Social_Sciences], display_name="History & Sociology", subgenre_behavior=Lane.IN_SAME_LANE, **ya_common_args), Lane(_db, library, full_name="YA Life Strategies", display_name="Life Strategies", genres=[genres.Life_Strategies], **ya_common_args), Lane(_db, library, full_name="YA Religion & Spirituality", display_name="Religion & Spirituality", genres=genres.Religion_Spirituality, subgenre_behavior=Lane.IN_SAME_LANE, **ya_common_args) ], **ya_common_args) children_common_args = dict( audiences=genres.Classifier.AUDIENCE_CHILDREN, languages=languages, ) children = Lane(_db, library, full_name="Children and Middle Grade", genres=None, fiction=Lane.BOTH_FICTION_AND_NONFICTION, include_best_sellers=True, include_staff_picks=True, searchable=True, sublanes=[ Lane(_db, library, full_name="Picture Books", age_range=[0, 1, 2, 3, 4], genres=None, fiction=Lane.BOTH_FICTION_AND_NONFICTION, **children_common_args), Lane(_db, library, full_name="Easy readers", age_range=[5, 6, 7, 8], genres=None, fiction=Lane.BOTH_FICTION_AND_NONFICTION, **children_common_args), Lane(_db, library, full_name="Chapter books", age_range=[9, 10, 11, 12], genres=None, fiction=Lane.BOTH_FICTION_AND_NONFICTION, **children_common_args), Lane(_db, library, full_name="Children's Poetry", display_name="Poetry books", genres=[genres.Poetry], **children_common_args), Lane(_db, library, full_name="Children's Folklore", display_name="Folklore", genres=[genres.Folklore], subgenre_behavior=Lane.IN_SAME_LANE, **children_common_args), Lane(_db, library, full_name="Children's Fantasy", display_name="Fantasy", fiction=True, genres=[genres.Fantasy], subgenre_behavior=Lane.IN_SAME_LANE, **children_common_args), Lane(_db, library, full_name="Children's SF", display_name="Science Fiction", fiction=True, genres=[genres.Science_Fiction], subgenre_behavior=Lane.IN_SAME_LANE, **children_common_args), Lane(_db, library, full_name="Realistic fiction", fiction=True, genres=[genres.Literary_Fiction], subgenre_behavior=Lane.IN_SAME_LANE, **children_common_args), Lane(_db, library, full_name="Children's Graphic Novels", display_name="Comics & Graphic Novels", genres=[genres.Comics_Graphic_Novels], **children_common_args), Lane(_db, library, full_name="Biography", genres=[genres.Biography_Memoir], subgenre_behavior=Lane.IN_SAME_LANE, **children_common_args), Lane(_db, library, full_name="Historical fiction", genres=[genres.Historical_Fiction], subgenre_behavior=Lane.IN_SAME_LANE, **children_common_args), Lane(_db, library, full_name="Informational books", genres=None, fiction=False, exclude_genres=[genres.Biography_Memoir], subgenre_behavior=Lane.IN_SAME_LANE, **children_common_args) ], **children_common_args) name = LanguageCodes.name_for_languageset(languages) lane = Lane(_db, library, full_name=name, genres=None, sublanes=[ adult_fiction, adult_nonfiction, ya_fiction, ya_nonfiction, children ], fiction=Lane.BOTH_FICTION_AND_NONFICTION, searchable=True, invisible=True, **common_args) return [lane]
def _is_language(self, language): # Check that the input string is in the list of recognized language codes. return LanguageCodes.string_to_alpha_3(language)
def lanes_for_large_collection(_db, languages): YA = Classifier.AUDIENCE_YOUNG_ADULT CHILDREN = Classifier.AUDIENCE_CHILDREN common_args = dict( languages=languages, include_best_sellers=True, include_staff_picks=True, ) adult_fiction = Lane( _db, full_name="Adult Fiction", display_name="Fiction", genres=None, sublanes=lanes_from_genres( _db, fiction_genres, languages=languages, audiences=Classifier.AUDIENCES_ADULT, ), fiction=True, audiences=Classifier.AUDIENCES_ADULT, **common_args ) adult_nonfiction = Lane( _db, full_name="Adult Nonfiction", display_name="Nonfiction", genres=None, sublanes=lanes_from_genres( _db, nonfiction_genres, languages=languages, audiences=Classifier.AUDIENCES_ADULT, ), fiction=False, audiences=Classifier.AUDIENCES_ADULT, **common_args ) ya_common_args = dict( audiences=YA, languages=languages, ) ya_fiction = Lane( _db, full_name="Young Adult Fiction", genres=None, fiction=True, include_best_sellers=True, include_staff_picks=True, sublanes=[ Lane(_db, full_name="YA Dystopian", display_name="Dystopian", genres=[genres.Dystopian_SF], **ya_common_args), Lane(_db, full_name="YA Fantasy", display_name="Fantasy", genres=[genres.Fantasy], subgenre_behavior=Lane.IN_SAME_LANE, **ya_common_args), Lane(_db, full_name="YA Graphic Novels", display_name="Comics & Graphic Novels", genres=[genres.Comics_Graphic_Novels], **ya_common_args), Lane(_db, full_name="YA Literary Fiction", display_name="Contemporary Fiction", genres=[genres.Literary_Fiction], **ya_common_args), Lane(_db, full_name="YA LGBTQ Fiction", display_name="LGBTQ Fiction", genres=[genres.LGBTQ_Fiction], **ya_common_args), Lane(_db, full_name="Mystery & Thriller", genres=[genres.Suspense_Thriller, genres.Mystery], subgenre_behavior=Lane.IN_SAME_LANE, **ya_common_args), Lane(_db, full_name="YA Romance", display_name="Romance", genres=[genres.Romance], subgenre_behavior=Lane.IN_SAME_LANE, **ya_common_args), Lane(_db, full_name="YA Science Fiction", display_name="Science Fiction", genres=[genres.Science_Fiction], subgenre_behavior=Lane.IN_SAME_LANE, exclude_genres=[genres.Dystopian_SF, genres.Steampunk], **ya_common_args), Lane(_db, full_name="YA Steampunk", genres=[genres.Steampunk], subgenre_behavior=Lane.IN_SAME_LANE, display_name="Steampunk", **ya_common_args), # TODO: # Paranormal -- what is it exactly? ], **ya_common_args ) ya_nonfiction = Lane( _db, full_name="Young Adult Nonfiction", genres=None, fiction=False, include_best_sellers=True, include_staff_picks=True, sublanes=[ Lane(_db, full_name="YA Biography", genres=genres.Biography_Memoir, display_name="Biography", **ya_common_args ), Lane(_db, full_name="YA History", genres=[genres.History, genres.Social_Sciences], display_name="History & Sociology", subgenre_behavior=Lane.IN_SAME_LANE, **ya_common_args ), Lane(_db, full_name="YA Life Strategies", display_name="Life Strategies", genres=[genres.Life_Strategies], **ya_common_args ), Lane(_db, full_name="YA Religion & Spirituality", display_name="Religion & Spirituality", genres=genres.Religion_Spirituality, subgenre_behavior=Lane.IN_SAME_LANE, **ya_common_args ) ], **ya_common_args ) children_common_args = dict( audiences=genres.Classifier.AUDIENCE_CHILDREN, languages=languages, ) children = Lane( _db, full_name="Children and Middle Grade", genres=None, fiction=Lane.BOTH_FICTION_AND_NONFICTION, include_best_sellers=True, include_staff_picks=True, sublanes=[ Lane(_db, full_name="Picture Books", age_range=[0,1,2,3,4], genres=None, fiction=Lane.BOTH_FICTION_AND_NONFICTION, **children_common_args ), Lane(_db, full_name="Easy readers", age_range=[5,6,7,8], genres=None, fiction=Lane.BOTH_FICTION_AND_NONFICTION, **children_common_args ), Lane(_db, full_name="Chapter books", age_range=[9,10,11,12], genres=None, fiction=Lane.BOTH_FICTION_AND_NONFICTION, **children_common_args ), Lane(_db, full_name="Children's Poetry", display_name="Poetry books", genres=[genres.Poetry], **children_common_args ), Lane(_db, full_name="Children's Folklore", display_name="Folklore", genres=[genres.Folklore], subgenre_behavior=Lane.IN_SAME_LANE, **children_common_args ), Lane(_db, full_name="Children's Fantasy", display_name="Fantasy", fiction=True, genres=[genres.Fantasy], subgenre_behavior=Lane.IN_SAME_LANE, **children_common_args ), Lane(_db, full_name="Children's SF", display_name="Science Fiction", fiction=True, genres=[genres.Science_Fiction], subgenre_behavior=Lane.IN_SAME_LANE, **children_common_args ), Lane(_db, full_name="Realistic fiction", fiction=True, genres=[genres.Literary_Fiction], subgenre_behavior=Lane.IN_SAME_LANE, **children_common_args ), Lane(_db, full_name="Children's Graphic Novels", display_name="Comics & Graphic Novels", genres=[genres.Comics_Graphic_Novels], **children_common_args ), Lane(_db, full_name="Biography", genres=[genres.Biography_Memoir], subgenre_behavior=Lane.IN_SAME_LANE, **children_common_args ), Lane(_db, full_name="Historical fiction", genres=[genres.Historical_Fiction], subgenre_behavior=Lane.IN_SAME_LANE, **children_common_args ), Lane(_db, full_name="Informational books", genres=None, fiction=False, exclude_genres=[genres.Biography_Memoir], **children_common_args ) ], **children_common_args ) name = LanguageCodes.name_for_languageset(languages) lane = Lane( _db, full_name=name, genres=None, sublanes=[adult_fiction, adult_nonfiction, ya_fiction, ya_nonfiction, children], fiction=Lane.BOTH_FICTION_AND_NONFICTION, searchable=True, invisible=True, **common_args ) return [lane]
def create_lanes_for_large_collection(_db, library, languages, priority=0): """Ensure that the lanes appropriate to a large collection are all present. This means: * A "%(language)s Adult Fiction" lane containing sublanes for each fiction genre. * A "%(language)s Adult Nonfiction" lane containing sublanes for each nonfiction genre. * A "%(language)s YA Fiction" lane containing sublanes for the most popular YA fiction genres. * A "%(language)s YA Nonfiction" lane containing sublanes for the most popular YA fiction genres. * A "%(language)s Children and Middle Grade" lane containing sublanes for childrens' books at different age levels. :param library: Newly created lanes will be associated with this library. :param languages: Newly created lanes will contain only books in these languages. :return: A list of top-level Lane objects. TODO: If there are multiple large collections, their top-level lanes do not have distinct display names. """ if isinstance(languages, basestring): languages = [languages] ADULT = Classifier.AUDIENCES_ADULT YA = [Classifier.AUDIENCE_YOUNG_ADULT] CHILDREN = [Classifier.AUDIENCE_CHILDREN] common_args = dict( languages=languages, media=None ) adult_common_args = dict(common_args) adult_common_args['audiences'] = ADULT include_best_sellers = False nyt_data_source = DataSource.lookup(_db, DataSource.NYT) nyt_integration = get_one( _db, ExternalIntegration, goal=ExternalIntegration.METADATA_GOAL, protocol=ExternalIntegration.NYT, ) if nyt_integration: include_best_sellers = True language_identifier = LanguageCodes.name_for_languageset(languages) sublanes = [] if include_best_sellers: best_sellers, ignore = create( _db, Lane, library=library, display_name="Best Sellers", priority=priority, **common_args ) priority += 1 best_sellers.list_datasource = nyt_data_source sublanes.append(best_sellers) adult_fiction_sublanes = [] adult_fiction_priority = 0 if include_best_sellers: adult_fiction_best_sellers, ignore = create( _db, Lane, library=library, display_name="Best Sellers", fiction=True, priority=adult_fiction_priority, **adult_common_args ) adult_fiction_priority += 1 adult_fiction_best_sellers.list_datasource = nyt_data_source adult_fiction_sublanes.append(adult_fiction_best_sellers) for genre in fiction_genres: if isinstance(genre, basestring): genre_name = genre else: genre_name = genre.get("name") genre_lane = lane_from_genres( _db, library, [genre], priority=adult_fiction_priority, **adult_common_args) adult_fiction_priority += 1 adult_fiction_sublanes.append(genre_lane) adult_fiction, ignore = create( _db, Lane, library=library, display_name="Fiction", genres=[], sublanes=adult_fiction_sublanes, fiction=True, priority=priority, **adult_common_args ) priority += 1 sublanes.append(adult_fiction) adult_nonfiction_sublanes = [] adult_nonfiction_priority = 0 if include_best_sellers: adult_nonfiction_best_sellers, ignore = create( _db, Lane, library=library, display_name="Best Sellers", fiction=False, priority=adult_nonfiction_priority, **adult_common_args ) adult_nonfiction_priority += 1 adult_nonfiction_best_sellers.list_datasource = nyt_data_source adult_nonfiction_sublanes.append(adult_nonfiction_best_sellers) for genre in nonfiction_genres: # "Life Strategies" is a YA-specific genre that should not be # included in the Adult Nonfiction lane. if genre != genres.Life_Strategies: if isinstance(genre, basestring): genre_name = genre else: genre_name = genre.get("name") genre_lane = lane_from_genres( _db, library, [genre], priority=adult_nonfiction_priority, **adult_common_args) adult_nonfiction_priority += 1 adult_nonfiction_sublanes.append(genre_lane) adult_nonfiction, ignore = create( _db, Lane, library=library, display_name="Nonfiction", genres=[], sublanes=adult_nonfiction_sublanes, fiction=False, priority=priority, **adult_common_args ) priority += 1 sublanes.append(adult_nonfiction) ya_common_args = dict(common_args) ya_common_args['audiences'] = YA ya_fiction, ignore = create( _db, Lane, library=library, display_name="Young Adult Fiction", genres=[], fiction=True, sublanes=[], priority=priority, **ya_common_args ) priority += 1 sublanes.append(ya_fiction) ya_fiction_priority = 0 if include_best_sellers: ya_fiction_best_sellers, ignore = create( _db, Lane, library=library, display_name="Best Sellers", fiction=True, priority=ya_fiction_priority, **ya_common_args ) ya_fiction_priority += 1 ya_fiction_best_sellers.list_datasource = nyt_data_source ya_fiction.sublanes.append(ya_fiction_best_sellers) ya_fiction.sublanes.append( lane_from_genres(_db, library, [genres.Dystopian_SF], priority=ya_fiction_priority, **ya_common_args)) ya_fiction_priority += 1 ya_fiction.sublanes.append( lane_from_genres(_db, library, [genres.Fantasy], priority=ya_fiction_priority, **ya_common_args)) ya_fiction_priority += 1 ya_fiction.sublanes.append( lane_from_genres(_db, library, [genres.Comics_Graphic_Novels], priority=ya_fiction_priority, **ya_common_args)) ya_fiction_priority += 1 ya_fiction.sublanes.append( lane_from_genres(_db, library, [genres.Literary_Fiction], display_name="Contemporary Fiction", priority=ya_fiction_priority, **ya_common_args)) ya_fiction_priority += 1 ya_fiction.sublanes.append( lane_from_genres(_db, library, [genres.LGBTQ_Fiction], priority=ya_fiction_priority, **ya_common_args)) ya_fiction_priority += 1 ya_fiction.sublanes.append( lane_from_genres(_db, library, [genres.Suspense_Thriller, genres.Mystery], display_name="Mystery & Thriller", priority=ya_fiction_priority, **ya_common_args)) ya_fiction_priority += 1 ya_fiction.sublanes.append( lane_from_genres(_db, library, [genres.Romance], priority=ya_fiction_priority, **ya_common_args)) ya_fiction_priority += 1 ya_fiction.sublanes.append( lane_from_genres(_db, library, [genres.Science_Fiction], exclude_genres=[genres.Dystopian_SF, genres.Steampunk], priority=ya_fiction_priority, **ya_common_args)) ya_fiction_priority += 1 ya_fiction.sublanes.append( lane_from_genres(_db, library, [genres.Steampunk], priority=ya_fiction_priority, **ya_common_args)) ya_fiction_priority += 1 ya_nonfiction, ignore = create( _db, Lane, library=library, display_name="Young Adult Nonfiction", genres=[], fiction=False, sublanes=[], priority=priority, **ya_common_args ) priority += 1 sublanes.append(ya_nonfiction) ya_nonfiction_priority = 0 if include_best_sellers: ya_nonfiction_best_sellers, ignore = create( _db, Lane, library=library, display_name="Best Sellers", fiction=False, priority=ya_nonfiction_priority, **ya_common_args ) ya_nonfiction_priority += 1 ya_nonfiction_best_sellers.list_datasource = nyt_data_source ya_nonfiction.sublanes.append(ya_nonfiction_best_sellers) ya_nonfiction.sublanes.append( lane_from_genres(_db, library, [genres.Biography_Memoir], display_name="Biography", priority=ya_nonfiction_priority, **ya_common_args)) ya_nonfiction_priority += 1 ya_nonfiction.sublanes.append( lane_from_genres(_db, library, [genres.History, genres.Social_Sciences], display_name="History & Sociology", priority=ya_nonfiction_priority, **ya_common_args)) ya_nonfiction_priority += 1 ya_nonfiction.sublanes.append( lane_from_genres(_db, library, [genres.Life_Strategies], priority=ya_nonfiction_priority, **ya_common_args)) ya_nonfiction_priority += 1 ya_nonfiction.sublanes.append( lane_from_genres(_db, library, [genres.Religion_Spirituality], priority=ya_nonfiction_priority, **ya_common_args)) ya_nonfiction_priority += 1 children_common_args = dict(common_args) children_common_args['audiences'] = CHILDREN children, ignore = create( _db, Lane, library=library, display_name="Children and Middle Grade", genres=[], fiction=None, sublanes=[], priority=priority, **children_common_args ) priority += 1 sublanes.append(children) children_priority = 0 if include_best_sellers: children_best_sellers, ignore = create( _db, Lane, library=library, display_name="Best Sellers", priority=children_priority, **children_common_args ) children_priority += 1 children_best_sellers.list_datasource = nyt_data_source children.sublanes.append(children_best_sellers) picture_books, ignore = create( _db, Lane, library=library, display_name="Picture Books", target_age=(0,4), genres=[], fiction=None, priority=children_priority, languages=languages, ) children_priority += 1 children.sublanes.append(picture_books) easy_readers, ignore = create( _db, Lane, library=library, display_name="Easy Readers", target_age=(5,8), genres=[], fiction=None, priority=children_priority, languages=languages, ) children_priority += 1 children.sublanes.append(easy_readers) chapter_books, ignore = create( _db, Lane, library=library, display_name="Chapter Books", target_age=(9,12), genres=[], fiction=None, priority=children_priority, languages=languages, ) children_priority += 1 children.sublanes.append(chapter_books) children_poetry, ignore = create( _db, Lane, library=library, display_name="Poetry Books", priority=children_priority, **children_common_args ) children_priority += 1 children_poetry.add_genre(genres.Poetry.name) children.sublanes.append(children_poetry) children_folklore, ignore = create( _db, Lane, library=library, display_name="Folklore", priority=children_priority, **children_common_args ) children_priority += 1 children_folklore.add_genre(genres.Folklore.name) children.sublanes.append(children_folklore) children_fantasy, ignore = create( _db, Lane, library=library, display_name="Fantasy", fiction=True, priority=children_priority, **children_common_args ) children_priority += 1 children_fantasy.add_genre(genres.Fantasy.name) children.sublanes.append(children_fantasy) children_sf, ignore = create( _db, Lane, library=library, display_name="Science Fiction", fiction=True, priority=children_priority, **children_common_args ) children_priority += 1 children_sf.add_genre(genres.Science_Fiction.name) children.sublanes.append(children_sf) realistic_fiction, ignore = create( _db, Lane, library=library, display_name="Realistic Fiction", fiction=True, priority=children_priority, **children_common_args ) children_priority += 1 realistic_fiction.add_genre(genres.Literary_Fiction.name) children.sublanes.append(realistic_fiction) children_graphic_novels, ignore = create( _db, Lane, library=library, display_name="Comics & Graphic Novels", priority=children_priority, **children_common_args ) children_priority += 1 children_graphic_novels.add_genre(genres.Comics_Graphic_Novels.name) children.sublanes.append(children_graphic_novels) children_biography, ignore = create( _db, Lane, library=library, display_name="Biography", priority=children_priority, **children_common_args ) children_priority += 1 children_biography.add_genre(genres.Biography_Memoir.name) children.sublanes.append(children_biography) children_historical_fiction, ignore = create( _db, Lane, library=library, display_name="Historical Fiction", priority=children_priority, **children_common_args ) children_priority += 1 children_historical_fiction.add_genre(genres.Historical_Fiction.name) children.sublanes.append(children_historical_fiction) informational, ignore = create( _db, Lane, library=library, display_name="Informational Books", fiction=False, genres=[], priority=children_priority, **children_common_args ) children_priority += 1 informational.add_genre(genres.Biography_Memoir.name, inclusive=False) children.sublanes.append(informational) return priority
def edit(self, identifier_type, identifier): """Edit a work's metadata.""" self.require_librarian(flask.request.library) # TODO: It would be nice to use the metadata layer for this, but # this code handles empty values differently than other metadata # sources. When a staff member deletes a value, that indicates # they think it should be empty. This needs to be indicated in the # db so that it can overrule other data sources that set a value, # unlike other sources which set empty fields to None. work = self.load_work(flask.request.library, identifier_type, identifier) if isinstance(work, ProblemDetail): return work changed = False staff_data_source = DataSource.lookup(self._db, DataSource.LIBRARY_STAFF) primary_identifier = work.presentation_edition.primary_identifier staff_edition, is_new = get_one_or_create( self._db, Edition, primary_identifier_id=primary_identifier.id, data_source_id=staff_data_source.id) self._db.expire(primary_identifier) new_title = flask.request.form.get("title") if new_title and work.title != new_title: staff_edition.title = unicode(new_title) changed = True new_subtitle = flask.request.form.get("subtitle") if work.subtitle != new_subtitle: if work.subtitle and not new_subtitle: new_subtitle = NO_VALUE staff_edition.subtitle = unicode(new_subtitle) changed = True # The form data includes roles and names for contributors in the same order. new_contributor_roles = flask.request.form.getlist("contributor-role") new_contributor_names = [ unicode(n) for n in flask.request.form.getlist("contributor-name") ] # The first author in the form is considered the primary author, even # though there's no separate MARC code for that. for i, role in enumerate(new_contributor_roles): if role == Contributor.AUTHOR_ROLE: new_contributor_roles[i] = Contributor.PRIMARY_AUTHOR_ROLE break roles_and_names = zip(new_contributor_roles, new_contributor_names) # Remove any contributions that weren't in the form, and remove contributions # that already exist from the list so they won't be added again. deleted_contributions = False for contribution in staff_edition.contributions: if (contribution.role, contribution.contributor.display_name ) not in roles_and_names: self._db.delete(contribution) deleted_contributions = True changed = True else: roles_and_names.remove( (contribution.role, contribution.contributor.display_name)) if deleted_contributions: # Ensure the staff edition's contributions are up-to-date when # calculating the presentation edition later. self._db.refresh(staff_edition) # Any remaining roles and names are new contributions. for role, name in roles_and_names: # There may be one extra role at the end from the input for # adding a contributor, in which case it will have no # corresponding name and can be ignored. if name: if role not in Contributor.MARC_ROLE_CODES.keys(): self._db.rollback() return UNKNOWN_ROLE.detailed( _("Role %(role)s is not one of the known contributor roles.", role=role)) contributor = staff_edition.add_contributor(name=name, roles=[role]) contributor.display_name = name changed = True new_series = flask.request.form.get("series") if work.series != new_series: if work.series and not new_series: new_series = NO_VALUE staff_edition.series = unicode(new_series) changed = True new_series_position = flask.request.form.get("series_position") if new_series_position != None and new_series_position != '': try: new_series_position = int(new_series_position) except ValueError: self._db.rollback() return INVALID_SERIES_POSITION else: new_series_position = None if work.series_position != new_series_position: if work.series_position and new_series_position == None: new_series_position = NO_NUMBER staff_edition.series_position = new_series_position changed = True new_medium = flask.request.form.get("medium") if new_medium: if new_medium not in Edition.medium_to_additional_type.keys(): self._db.rollback() return UNKNOWN_MEDIUM.detailed( _("Medium %(medium)s is not one of the known media.", medium=new_medium)) staff_edition.medium = new_medium changed = True new_language = flask.request.form.get("language") if new_language != None and new_language != '': new_language = LanguageCodes.string_to_alpha_3(new_language) if not new_language: self._db.rollback() return UNKNOWN_LANGUAGE else: new_language = None if new_language != staff_edition.language: staff_edition.language = new_language changed = True new_publisher = flask.request.form.get("publisher") if new_publisher != staff_edition.publisher: if staff_edition.publisher and not new_publisher: new_publisher = NO_VALUE staff_edition.publisher = unicode(new_publisher) changed = True new_imprint = flask.request.form.get("imprint") if new_imprint != staff_edition.imprint: if staff_edition.imprint and not new_imprint: new_imprint = NO_VALUE staff_edition.imprint = unicode(new_imprint) changed = True new_issued = flask.request.form.get("issued") if new_issued != None and new_issued != '': try: new_issued = datetime.strptime(new_issued, '%Y-%m-%d') except ValueError: self._db.rollback() return INVALID_DATE_FORMAT else: new_issued = None if new_issued != staff_edition.issued: staff_edition.issued = new_issued changed = True # TODO: This lets library staff add a 1-5 rating, which is used in the # quality calculation. However, this doesn't work well if there are any # other measurements that contribute to the quality. The form will show # the calculated quality rather than the staff rating, which will be # confusing. It might also be useful to make it more clear how this # relates to the quality threshold in the library settings. changed_rating = False new_rating = flask.request.form.get("rating") if new_rating != None and new_rating != '': try: new_rating = float(new_rating) except ValueError: self._db.rollback() return INVALID_RATING scale = Measurement.RATING_SCALES[DataSource.LIBRARY_STAFF] if new_rating < scale[0] or new_rating > scale[1]: self._db.rollback() return INVALID_RATING.detailed( _("The rating must be a number between %(low)s and %(high)s.", low=scale[0], high=scale[1])) if (new_rating - scale[0]) / (scale[1] - scale[0]) != work.quality: primary_identifier.add_measurement( staff_data_source, Measurement.RATING, new_rating, weight=WorkController.STAFF_WEIGHT) changed = True changed_rating = True changed_summary = False new_summary = flask.request.form.get("summary") or "" if new_summary != work.summary_text: old_summary = None if work.summary and work.summary.data_source == staff_data_source: old_summary = work.summary work.presentation_edition.primary_identifier.add_link( Hyperlink.DESCRIPTION, None, staff_data_source, content=new_summary) # Delete previous staff summary if old_summary: for link in old_summary.links: self._db.delete(link) self._db.delete(old_summary) changed = True changed_summary = True if changed: # Even if the presentation doesn't visibly change, we want # to regenerate the OPDS entries and update the search # index for the work, because that might be the 'real' # problem the user is trying to fix. policy = PresentationCalculationPolicy( classify=True, regenerate_opds_entries=True, regenerate_marc_record=True, update_search_index=True, calculate_quality=changed_rating, choose_summary=changed_summary, ) work.calculate_presentation(policy=policy) return Response("", 200)