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
示例#2
0
    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
示例#3
0
    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
示例#6
0
 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)
示例#7
0
    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)
示例#8
0
 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)