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 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 _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 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)