diff --git a/docker-compose-dev-local-db-all-containers.yml b/docker-compose-dev-local-db-all-containers.yml index 2ade34bfa..0a4cdbf6c 100644 --- a/docker-compose-dev-local-db-all-containers.yml +++ b/docker-compose-dev-local-db-all-containers.yml @@ -179,8 +179,6 @@ services: celery_exporter: image: danihodovic/celery-exporter:0.10.8 - networks: - - prod-net secrets: - redis_pass - redis_host diff --git a/webclient/core/repository_connector.py b/webclient/core/repository_connector.py index 11ed491c6..c9f8dedca 100644 --- a/webclient/core/repository_connector.py +++ b/webclient/core/repository_connector.py @@ -18,6 +18,8 @@ from xml_generator.generator import DocumentGenerator from xml_generator.models import ModelWithMetadata +from redis import ResponseError + logger = logging.getLogger(__name__) @@ -1336,7 +1338,13 @@ def _send_transaction_request(self, operation=FedoraTransactionOperation.COMMIT) auth = HTTPBasicAuth(settings.FEDORA_ADMIN_USER, settings.FEDORA_ADMIN_USER_PASSWORD) if operation == FedoraTransactionOperation.COMMIT: response = requests.put(url, auth=auth, verify=False) - self._save_transaction_result_to_redis(FedoraTransactionResult.COMMITED) + try: + self._save_transaction_result_to_redis(FedoraTransactionResult.COMMITED) + except ResponseError as err: + logger.error( + "core_repository_connector.FedoraTransaction._save_transaction_result_to_redis.failed", + extra={"transaction": self.uid, "err": err}, + ) elif operation == FedoraTransactionOperation.ROLLBACK: response = requests.delete(url, auth=auth, verify=False) self._save_transaction_result_to_redis(FedoraTransactionResult.ABORTED) diff --git a/webclient/core/validators.py b/webclient/core/validators.py index 99dbc76a5..4ab1f9eaa 100644 --- a/webclient/core/validators.py +++ b/webclient/core/validators.py @@ -10,6 +10,9 @@ logger = logging.getLogger(__name__) +orcid_pattern = re.compile(r"\d{4}-\d{4}-\d{4}-\d{4}") + + def validate_phone_number(number): """ Validátor pro ověření telefonního čísla na správny formát. @@ -37,3 +40,9 @@ def validate_date_min_1600(value): raise ValidationError(_("core.validators.validate_date_min_1600.not_valid_upper")) elif value and value <= min_date: raise ValidationError(_("core.validators.validate_date_min_1600.not_valid")) + + +def validate_orcid(orcid): + match = orcid_pattern.search(orcid) + if not match: + raise ValidationError(_("core.validators.validate_orcid.not_valid")) diff --git a/webclient/doi/admin.py b/webclient/doi/admin.py index 6c36f6859..bcb618eac 100644 --- a/webclient/doi/admin.py +++ b/webclient/doi/admin.py @@ -1,8 +1,8 @@ from django.contrib import admin from django.template.response import TemplateResponse from django.urls import path -from doi.doi_serializers import DokumentSerializer from doi.forms import UpdateDocumentObjectIdentifierFileForm +from doi.model_serializers import DokumentSerializer from dokument.models import Dokument diff --git a/webclient/doi/doi_serializers.py b/webclient/doi/doi_serializers.py deleted file mode 100644 index cdd477889..000000000 --- a/webclient/doi/doi_serializers.py +++ /dev/null @@ -1,348 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Dict, List - -from arch_z.models import AkceVedouci -from core.constants import AZ_STAV_ARCHIVOVANY -from django.core.exceptions import ObjectDoesNotExist -from dokument.models import Dokument, DokumentCast -from heslar.hesla_dynamicka import PRISTUPNOST_ANONYM_ID -from heslar.models import RuianKatastr -from komponenta.models import Komponenta -from uzivatel.models import Organizace, Osoba - - -class ModelSerializer(ABC): - def __init__(self, object): - self.object = object - - @abstractmethod - def serialize_publish(self): - pass - - -class PartialSerializer(ABC): - def __init__(self, object): - self.object = object - - def serialize_publish(self): - pass - - -def format_katastr_place(katastr: RuianKatastr): - return f"{katastr.nazev}, {katastr.okres.nazev}, {katastr.okres.kraj.nazev}, Czech Republic" - - -def serialize_geom(geom=None, place: str | None = None) -> frozenset: - serialized_geom = {} - if geom: - serialized_geom.update({"pointLatitude": geom.centroid.y, "pointLongitude": geom.centroid.x}) - if place: - if isinstance(place, str): - serialized_geom["geoLocationPlace"] = place - if isinstance(place, RuianKatastr): - serialized_geom["geoLocationPlace"] = format_katastr_place(place) - return frozenset(serialized_geom.items()) - - -def serialize_organizace(organizace: Organizace): - serialized_organizace = {"name": organizace.nazev} - if organizace.ror: - serialized_organizace["affiliationIdentifier"] = f"https://ror.org/{organizace.ro}" - serialized_organizace["affiliationIdentifierScheme"] = "ROR" - serialized_organizace["schemeUri"] = "https://ror.org/" - return serialized_organizace - - -def serialize_osoba(osoba: Osoba, organizace: Organizace | None = None) -> Dict: - serialized_object = { - "name": osoba.vypis_cely if not osoba.anonym else ":unkn", - "nameType": "Personal", - "givenName": osoba.jmeno if not osoba.anonym else " ", - "familyName": osoba.prijmeni if not osoba.anonym else " ", - "affiliation": [serialize_organizace(organizace)] if organizace and not organizace.general else [], - "nameIdentifiers": [ - { - "schemeUri": "", - "nameIdentifier": "", - "nameIdentifierScheme": "AMCR Vocabulary", - }, - *( - [ - { - "schemeUri": "https://orcid.org", - "nameIdentifier": f"https://orcid.org/{osoba.orcid}", - "nameIdentifierScheme": "ORCID", - } - ] - if osoba.orcid - else [] - ), - *( - [ - { - "schemeUri": "https://www.wikidata.org/wiki", - "nameIdentifier": f"https://www.wikidata.org/wiki/{osoba.wikidata}", - "nameIdentifierScheme": "ORCID", - } - ] - if osoba.wikidata - else [] - ), - ], - } - return serialized_object - - -def serialize_subject(serialized_object, subject_attr="heslo_en"): - if serialized_object is None: - return frozenset() - output = { - "subject": getattr(serialized_object, subject_attr), - "valueUri": f"{serialized_object.ident_cely}", - "schemeUri": "", - "subjectScheme": "AMCR Vocabulary", - } - return frozenset(output.items()) - - -class DokumentSerializer(ModelSerializer): - def __init__(self, object: Dokument): - super().__init__(object) - self.object: Dokument - - def _serialize_contributors(self): - contributors = [] - if self.object.let and self.object.let.pozorovatel: - contributors.append(serialize_osoba(self.object.let.pozorovatel, self.object.let.organizace)) - for cast in self.object.casti.all(): - cast: DokumentCast - try: - if cast.neident_akce and cast.neident_akce.vedouci: - for vedouci in cast.neident_akce.vedouci.all(): - contributors.append(serialize_osoba(vedouci)) - except ObjectDoesNotExist: - pass - if cast.archeologicky_zaznam: - try: - if cast.archeologicky_zaznam.akce.hlavni_vedouci: - contributors.append( - serialize_osoba( - cast.archeologicky_zaznam.akce.hlavni_vedouci, cast.archeologicky_zaznam.akce.organizace - ) - ) - for akce_vedouci in cast.archeologicky_zaznam.akce.akcevedouci_set.all(): - akce_vedouci: AkceVedouci - contributors.append(serialize_osoba(akce_vedouci.vedouci, akce_vedouci.organizace)) - except ObjectDoesNotExist: - pass - if cast.projekt and cast.projekt.vedouci_projektu: - contributors.append(serialize_osoba(cast.projekt.vedouci_projektu, cast.projekt.organizace)) - return contributors - - def _serialize_dates(self): - dates = [] - try: - if self.object.extra_data and self.object.extra_data.datum_vzniku: - dates.append({"date": self.object.extra_data.datum_vzniku, "dateType": "Created"}) - except ObjectDoesNotExist: - pass - if self.object.historie: - updated_dates = [ - {"date": date.datum_zmeny, "dateType": "Updated"} for date in self.object.historie.historie_set.all() - ] - dates.extend(updated_dates) - return dates - - def _serialize_descriptions(self): - descriptions = [] - if self.object.popis: - descriptions.append({"lang": "cs", "description": self.object.popis, "descriptionType": "Abstract"}) - if self.object.poznamka: - descriptions.append({"lang": "cs", "description": self.object.poznamka, "descriptionType": "TechnicalInfo"}) - return descriptions - - def _serialize_geo_locations(self): - geo_locations: List[frozenset] = [] - try: - if self.object.extra_data.geom: - geo_locations.append(serialize_geom(self.object.extra_data.geom)) - except ObjectDoesNotExist: - pass - for cast in self.object.casti.all(): - try: - if cast.neident_akce and cast.neident_akce.katastr: - geo_locations.append( - serialize_geom(cast.neident_akce.katastr.definicni_bod, cast.neident_akce.katastr) - ) - except ObjectDoesNotExist: - pass - if cast.projekt and cast.projekt.hlavni_katastr and cast.projekt.pristupnost.pk == PRISTUPNOST_ANONYM_ID: - geo_locations.append( - serialize_geom(cast.projekt.hlavni_katastr.definicni_bod, cast.projekt.hlavni_katastr) - ) - geo_locations.append(serialize_geom(place=cast.projekt.hlavni_katastr)) - for katastr in cast.projekt.katastry.all(): - if katastr.pk != cast.projekt.hlavni_katastr: - geo_locations.append(serialize_geom(katastr.definicni_bod, katastr)) - geo_locations.append(serialize_geom(place=katastr)) - if ( - cast.archeologicky_zaznam - and cast.archeologicky_zaznam.hlavni_katastr - and cast.archeologicky_zaznam.pristupnost.pk == PRISTUPNOST_ANONYM_ID - and cast.archeologicky_zaznam.stav == AZ_STAV_ARCHIVOVANY - ): - geo_locations.append( - serialize_geom( - cast.archeologicky_zaznam.hlavni_katastr.definicni_bod, cast.archeologicky_zaznam.hlavni_katastr - ) - ) - geo_locations.append(serialize_geom(place=cast.archeologicky_zaznam.hlavni_katastr)) - for katastr in cast.archeologicky_zaznam.katastry.all(): - if katastr.pk != cast.archeologicky_zaznam.hlavni_katastr: - geo_locations.append(serialize_geom(katastr.definicni_bod, katastr)) - geo_locations.append(serialize_geom(place=katastr)) - geo_locations_dict = [dict(item) for item in list(set(geo_locations))] - return geo_locations_dict - - def _serialize_rights_list(self): - if self.object.licence: - serialized_rights = {"rights": self.object.licence.heslo_en} - spdx_query = self.object.licence.heslar_odkaz.filter(zdroj="SPDX") - if spdx_query.exists(): - serialized_rights["rightsUri"] = self.object.licence.heslar_odkaz.filter(zdroj="SPDX").first().uri - serialized_rights["schemeUri"] = self.object.licence.heslar_odkaz.filter(zdroj="SPDX").first().uri - serialized_rights["rightsIdentifier"] = ( - self.object.licence.heslar_odkaz.filter(zdroj="SPDX").first().kod - ) - serialized_rights["rightsIdentifierScheme"] = ( - self.object.licence.heslar_odkaz.filter(zdroj="SPDX").first().zdroj - ) - return [ - serialized_rights, - ] - return [] - - def _serialize_subjects(self): - first_item = frozenset( - { - "subject": "History and Archaeology", - "valueUri": "http://dd.eionet.europa.eu/vocabulary/eurostat/fos07/FOS601", - "schemeUri": "http://dd.eionet.europa.eu/vocabulary/eurostat/fos07/", - "subjectScheme": "Field of science and technology classification (FOS 2007)", - }.items() - ) - serialized_subjects = [ - first_item, - ] - serialized_subjects += [serialize_subject(posudek) for posudek in self.object.posudky.all()] - serialized_subjects += [serialize_subject(osoba, "vypis_cely") for osoba in self.object.osoby.all()] - if self.object.has_extra_data(): - serialized_subjects += [serialize_subject(self.object.extra_data.udalost_typ)] - serialized_subjects += [serialize_subject(komp.obdobi) for komp in self.object.get_komponenty()] - serialized_subjects += [serialize_subject(komp.areal) for komp in self.object.get_komponenty()] - for komp in self.object.get_komponenty(AZ_STAV_ARCHIVOVANY): - komp: Komponenta - serialized_subjects += [serialize_subject(aktivita) for aktivita in komp.aktivity.all()] - serialized_subjects += [serialize_subject(objekt.druh) for objekt in komp.objekty.all()] - serialized_subjects += [serialize_subject(objekt.specifikace) for objekt in komp.objekty.all()] - serialized_subjects += [serialize_subject(predmet.druh) for predmet in komp.predmety.all()] - serialized_subjects += [serialize_subject(predmet.specifikace) for predmet in komp.predmety.all()] - - for cast in self.object.casti.all(): - try: - if cast.archeologicky_zaznam and cast.archeologicky_zaznam.stav == AZ_STAV_ARCHIVOVANY: - serialized_subjects.append(serialize_subject(cast.archeologicky_zaznam.akce.hlavni_typ)) - serialized_subjects.append(serialize_subject(cast.archeologicky_zaznam.akce.vedlejsi_typ)) - except ObjectDoesNotExist: - pass - - try: - if cast.archeologicky_zaznam and cast.archeologicky_zaznam.stav == AZ_STAV_ARCHIVOVANY: - serialized_subjects.append(serialize_subject(cast.archeologicky_zaznam.lokalita.typ_lokality)) - serialized_subjects.append(serialize_subject(cast.archeologicky_zaznam.lokalita.druh)) - except ObjectDoesNotExist: - pass - serialized_subjects = [dict(item) for item in set(serialized_subjects)] - return serialized_subjects - - def _serialize_types(self): - serialized_types = {"resourceType": self.object.typ_dokumentu.heslo_en} - resource_type_query = self.object.typ_dokumentu.heslar_odkaz.filter(zdroj="DataCite").filter( - nazev_kodu="resourceTypeGeneral" - ) - if resource_type_query.exists(): - serialized_types["resourceTypeGeneral"] = resource_type_query.first().kod - return serialized_types - - def serialize_publish(self): - return { - "data": { - "type": "dois", - "attributes": { - "event": "publish", - "alternateIdentifiers": [ - { - "alternateIdentifierType": "Local accession number", - "alternateIdentifier": self.object.ident_cely, - }, - *( - [ - { - "alternateIdentifierType": "Original label", - "alternateIdentifier": self.object.oznaceni_originalu, - } - ] - if self.object.oznaceni_originalu - else [] - ), - ], - }, - "creators": [ - serialize_osoba(author.autor, self.object.organizace) - for author in self.object.dokumentautor_set.all() - ], - "titles": [ - { - "lang": "en", - "title": f"AMCR {self.object.ident_cely} – {self.object.typ_dokumentu.heslo_en}", - }, - { - "lang": "cs", - "title": f"AMCR {self.object.ident_cely} – {self.object.typ_dokumentu.heslo}", - "titleType": "TranslatedTitle", - }, - ], - "publisher": { - "lang": "en", - "name": "Archaeological Information System of the Czech Republic", - "schemeUri": "https://ror.org/", - "publisherIdentifier": "https://ror.org/xxx", # doplnit - "publisherIdentifierScheme": "ROR", - }, - "container": { - "type": "DataRepository", - "title": "Archaeological Map of the Czech Republic", - "identifier": "https://www.re3data.org/repository/r3d100013576", - "identifierType": "re3data", - }, - "publicationYear": self.object.rok_vzniku, - "subjects": self._serialize_subjects(), - "contributors": self._serialize_contributors(), - "dates": self._serialize_dates(), - "language": self.object.jazyky.first(), - "types": self._serialize_types(), - "relatedIdentifiers": [], - "sizes": [ - f"{sum([soubor.size_mb for soubor in self.object.soubory.soubory.all()])} MB", - f"{sum([soubor.rozsah for soubor in self.object.soubory.soubory.all()])} pages", - ] - if self.object.soubory.soubory.exists() - else [], - "formats": list(set([soubor.mimetype for soubor in self.object.soubory.soubory.all()])), - "version": self.object.historie.historie_set.last().datum_zmeny, - "rightsList": self._serialize_rights_list(), - "descriptions": self._serialize_descriptions(), - "geoLocations": self._serialize_geo_locations(), - "url": "", - } - } diff --git a/webclient/doi/model_serializers.py b/webclient/doi/model_serializers.py new file mode 100644 index 000000000..0bc81f796 --- /dev/null +++ b/webclient/doi/model_serializers.py @@ -0,0 +1,729 @@ +from abc import ABC, abstractmethod +from typing import Dict, List + +from arch_z.models import AkceVedouci, ArcheologickyZaznam, ExterniOdkaz +from core.constants import ARCHIVACE_AZ, ARCHIVACE_SN, AZ_STAV_ARCHIVOVANY, D_STAV_ARCHIVOVANY +from django.core.exceptions import ObjectDoesNotExist +from dokument.models import Dokument, DokumentCast +from heslar.hesla_dynamicka import PRISTUPNOST_ANONYM_ID +from heslar.models import RuianKatastr +from komponenta.models import Komponenta +from lokalita.models import Lokalita +from pas.models import SamostatnyNalez +from uzivatel.models import Organizace, Osoba + +from webclient.settings.base import DIGIARCHIV_URL + +AIS_AMCR = Organizace.objects.get(ident_cely="ORG-000091") + + +class ModelSerializer(ABC): + def __init__(self, object): + self.object = object + + @abstractmethod + def _get_creators(self): + pass + + @abstractmethod + def _get_publication_year(self): + pass + + def _get_language(self): + return "cs" + + @abstractmethod + def _get_prefix(self): + pass + + @abstractmethod + def _get_title(self, language: str): + pass + + @abstractmethod + def _serialize_alternate_identifiers(self): + return [ + { + "alternateIdentifierType": "Local accession number", + "alternateIdentifier": self.object.ident_cely, + }, + ] + + @abstractmethod + def _serialize_contributors(self): + pass + + def _serialize_creators(self): + return [serialize_osoba(author.autor, self.object.organizace) for author in self._get_creators()] + + @abstractmethod + def _serialize_dates(self): + dates = [] + if self.object.historie: + updated_dates = [ + {"date": date.datum_zmeny, "dateType": "Updated"} for date in self.object.historie.historie_set.all() + ] + dates.extend(updated_dates) + return dates + + @abstractmethod + def _serialize_descriptions(self): + pass + + @abstractmethod + def _serialize_geo_locations(self): + pass + + def _serialize_related_identifiers(self): + return [ + { + "relationType": "HasMetadata", + "relatedIdentifier": "", + "resourceTypeGeneral": "Dataset", + "relatedIdentifierType": "URL", + } + ] + + @abstractmethod + def _serialize_rights_list(self): + pass + + def _serialize_subjects(self): + return [ + frozenset( + { + "subject": "History and Archaeology", + "valueUri": "http://dd.eionet.europa.eu/vocabulary/eurostat/fos07/FOS601", + "schemeUri": "http://dd.eionet.europa.eu/vocabulary/eurostat/fos07/", + "subjectScheme": "Field of science and technology classification (FOS 2007)", + }.items() + ) + ] + + @abstractmethod + def _serialize_types(self): + pass + + def serialize_publish(self): + return { + "data": { + "type": "dois", + "attributes": { + "event": "publish", + "doi": f"{self._get_prefix()}/{self.object.ident_cely}", + "alternateIdentifiers": self._serialize_alternate_identifiers(), + }, + "creators": self._serialize_creators(), + "titles": [ + { + "lang": "en", + "title": self._get_title("en"), + }, + { + "lang": "cs", + "title": self._get_title("cs"), + "titleType": "TranslatedTitle", + }, + ], + "publisher": { + "lang": "en", + "name": "Archaeological Information System of the Czech Republic", + "schemeUri": "https://ror.org/", + "publisherIdentifier": f"https://ror.org/{AIS_AMCR.ror}", # doplnit + "publisherIdentifierScheme": "ROR", + }, + "container": { + "type": "DataRepository", + "title": "Archaeological Map of the Czech Republic", + "identifier": "https://www.re3data.org/repository/r3d100013576", + "identifierType": "re3data", + }, + "publicationYear": self._get_publication_year(), + "subjects": self._serialize_subjects(), + "contributors": self._serialize_contributors(), + "dates": self._serialize_dates(), + "language": self._get_language(), + "types": self._serialize_types(), + "relatedIdentifiers": self._serialize_related_identifiers(), + "sizes": [ + f"{sum([soubor.size_mb for soubor in self.object.soubory.soubory.all()])} MB", + f"{sum([soubor.rozsah for soubor in self.object.soubory.soubory.all()])} pages", + ] + if self.object.soubory.soubory.exists() + else [], + "formats": set([soubor.mimetype for soubor in self.object.soubory.soubory.all()]) + if self.object.soubory.soubory.exists() + else [], + "version": self.object.historie.historie_set.last().datum_zmeny, + "rightsList": self._serialize_rights_list(), + "descriptions": self._serialize_descriptions(), + "geoLocations": self._serialize_geo_locations(), + "url": "", + } + } + + +class PartialSerializer(ABC): + def __init__(self, object): + self.object = object + + def serialize_publish(self): + pass + + +def format_katastr_place(katastr: RuianKatastr): + return f"{katastr.nazev}, {katastr.okres.nazev}, {katastr.okres.kraj.nazev}, Czech Republic" + + +def serialize_geom(geom=None, place: str | None = None) -> frozenset: + serialized_geom = {} + if geom: + serialized_geom.update({"pointLatitude": geom.centroid.y, "pointLongitude": geom.centroid.x}) + if place: + if isinstance(place, str): + serialized_geom["geoLocationPlace"] = place + if isinstance(place, RuianKatastr): + serialized_geom["geoLocationPlace"] = format_katastr_place(place) + return frozenset(serialized_geom.items()) + + +def _serialize_komponenty_m2n_fields(komponenty): + result = [] + for komp in komponenty: + komp: Komponenta + result += [serialize_subject(aktivita) for aktivita in komp.aktivity.all()] + result += [serialize_subject(objekt.druh) for objekt in komp.objekty.all()] + result += [serialize_subject(objekt.specifikace) for objekt in komp.objekty.all()] + result += [serialize_subject(predmet.druh) for predmet in komp.predmety.all()] + result += [serialize_subject(predmet.specifikace) for predmet in komp.predmety.all()] + return result + + +def serialize_organizace(organizace: Organizace): + serialized_organizace = {"name": organizace.nazev} + if organizace.ror: + serialized_organizace["affiliationIdentifier"] = f"https://ror.org/{organizace.ro}" + serialized_organizace["affiliationIdentifierScheme"] = "ROR" + serialized_organizace["schemeUri"] = "https://ror.org/" + return serialized_organizace + + +def serialize_osoba(osoba: Osoba, organizace: Organizace | None = None) -> Dict: + serialized_object = { + "name": osoba.vypis_cely if not osoba.anonym else ":unkn", + "nameType": "Personal", + "givenName": osoba.jmeno if not osoba.anonym else " ", + "familyName": osoba.prijmeni if not osoba.anonym else " ", + "affiliation": [serialize_organizace(organizace)] if organizace and not organizace.general else [], + "nameIdentifiers": [ + { + "schemeUri": "", + "nameIdentifier": "", + "nameIdentifierScheme": "AMCR Vocabulary", + }, + *( + [ + { + "schemeUri": "https://orcid.org", + "nameIdentifier": f"https://orcid.org/{osoba.orcid}", + "nameIdentifierScheme": "ORCID", + } + ] + if osoba.orcid + else [] + ), + *( + [ + { + "schemeUri": "https://www.wikidata.org/wiki", + "nameIdentifier": f"https://www.wikidata.org/wiki/{osoba.wikidata}", + "nameIdentifierScheme": "ORCID", + } + ] + if osoba.wikidata + else [] + ), + ], + } + return serialized_object + + +def serialize_subject(serialized_object, subject_attr="heslo_en"): + if serialized_object is None: + return frozenset() + output = { + "subject": getattr(serialized_object, subject_attr), + "valueUri": f"{serialized_object.ident_cely}", + "schemeUri": "", + "subjectScheme": "AMCR Vocabulary", + } + return frozenset(output.items()) + + +class DokumentSerializer(ModelSerializer): + def __init__(self, object: Dokument): + super().__init__(object) + self.object: Dokument + + def _get_creators(self): + return self.object.dokumentautor_set.all() + + def _get_language(self): + return self.object.jazyky.first() + + def _get_publication_year(self): + return self.object.rok_vzniku + + def _get_prefix(self): + return "" + + def _get_title(self, language: str): + return { + "en": f"AMCR {self.object.ident_cely} – {self.object.typ_dokumentu.heslo_en}", + "cs": f"AMCR {self.object.ident_cely} – {self.object.typ_dokumentu.heslo}", + }[language] + + def _serialize_alternate_identifiers(self): + alternate_identifiers = super()._serialize_alternate_identifiers() + if self.object.oznaceni_originalu: + alternate_identifiers.append( + { + "alternateIdentifierType": "Original label", + "alternateIdentifier": self.object.oznaceni_originalu, + } + ) + return alternate_identifiers + + def _serialize_contributors(self): + contributors = [] + if self.object.let and self.object.let.pozorovatel: + contributors.append(serialize_osoba(self.object.let.pozorovatel, self.object.let.organizace)) + for cast in self.object.casti.all(): + cast: DokumentCast + try: + if cast.neident_akce and cast.neident_akce.vedouci: + for vedouci in cast.neident_akce.vedouci.all(): + contributors.append(serialize_osoba(vedouci)) + except ObjectDoesNotExist: + pass + if cast.archeologicky_zaznam: + try: + if cast.archeologicky_zaznam.akce.hlavni_vedouci: + contributors.append( + serialize_osoba( + cast.archeologicky_zaznam.akce.hlavni_vedouci, cast.archeologicky_zaznam.akce.organizace + ) + ) + for akce_vedouci in cast.archeologicky_zaznam.akce.akcevedouci_set.all(): + akce_vedouci: AkceVedouci + contributors.append(serialize_osoba(akce_vedouci.vedouci, akce_vedouci.organizace)) + except ObjectDoesNotExist: + pass + if cast.projekt and cast.projekt.vedouci_projektu: + contributors.append(serialize_osoba(cast.projekt.vedouci_projektu, cast.projekt.organizace)) + return contributors + + def _serialize_dates(self): + dates = super()._serialize_dates() + try: + if self.object.extra_data and self.object.extra_data.datum_vzniku: + dates.append({"date": self.object.extra_data.datum_vzniku, "dateType": "Created"}) + except ObjectDoesNotExist: + pass + return dates + + def _serialize_descriptions(self): + descriptions = [] + if self.object.popis: + descriptions.append({"lang": "cs", "description": self.object.popis, "descriptionType": "Abstract"}) + if self.object.poznamka: + descriptions.append({"lang": "cs", "description": self.object.poznamka, "descriptionType": "TechnicalInfo"}) + return descriptions + + def _serialize_geo_locations(self): + geo_locations: List[frozenset] = [] + try: + if self.object.extra_data.geom: + geo_locations.append(serialize_geom(self.object.extra_data.geom)) + except ObjectDoesNotExist: + pass + for cast in self.object.casti.all(): + try: + if cast.neident_akce and cast.neident_akce.katastr: + geo_locations.append( + serialize_geom(cast.neident_akce.katastr.definicni_bod, cast.neident_akce.katastr) + ) + except ObjectDoesNotExist: + pass + if cast.projekt and cast.projekt.hlavni_katastr and cast.projekt.pristupnost.pk == PRISTUPNOST_ANONYM_ID: + geo_locations.append( + serialize_geom(cast.projekt.hlavni_katastr.definicni_bod, cast.projekt.hlavni_katastr) + ) + geo_locations.append(serialize_geom(place=cast.projekt.hlavni_katastr)) + for katastr in cast.projekt.katastry.all(): + if katastr.pk != cast.projekt.hlavni_katastr: + geo_locations.append(serialize_geom(katastr.definicni_bod, katastr)) + geo_locations.append(serialize_geom(place=katastr)) + if ( + cast.archeologicky_zaznam + and cast.archeologicky_zaznam.hlavni_katastr + and cast.archeologicky_zaznam.pristupnost.pk == PRISTUPNOST_ANONYM_ID + and cast.archeologicky_zaznam.stav == AZ_STAV_ARCHIVOVANY + ): + geo_locations.append( + serialize_geom( + cast.archeologicky_zaznam.hlavni_katastr.definicni_bod, cast.archeologicky_zaznam.hlavni_katastr + ) + ) + geo_locations.append(serialize_geom(place=cast.archeologicky_zaznam.hlavni_katastr)) + for katastr in cast.archeologicky_zaznam.katastry.all(): + if katastr.pk != cast.archeologicky_zaznam.hlavni_katastr: + geo_locations.append(serialize_geom(katastr.definicni_bod, katastr)) + geo_locations.append(serialize_geom(place=katastr)) + geo_locations_dict = [dict(item) for item in list(set(geo_locations))] + return geo_locations_dict + + def _serialize_related_identifiers(self): + related_identifiers = super()._serialize_related_identifiers() + if self.object.soubory.soubory.exists(): + related_identifiers.append( + { + "relationType": "HasPart", + "relatedIdentifier": "", + "resourceTypeGeneral": "Dataset", + "relatedIdentifierType": "URL", + } + ) + if self.object.let: + related_identifiers.append( + { + "relationType": "IsDerivedFrom", + "relatedIdentifier": f"{DIGIARCHIV_URL}{self.object.let.ident_cely}", + "resourceTypeGeneral": "Event", + "relatedIdentifierType": "URL", + } + ) + for cast in self.object.casti.all(): + cast: DokumentCast + if cast.archeologicky_zaznam and cast.archeologicky_zaznam.stav.pk == AZ_STAV_ARCHIVOVANY: + if cast.archeologicky_zaznam.typ_zaznamu == ArcheologickyZaznam.TYP_ZAZNAMU_AKCE: + related_identifiers.append( + { + "relationType": "Documents", + "relatedIdentifier": f"{DIGIARCHIV_URL}{cast.archeologicky_zaznam.ident_cely}", + "resourceTypeGeneral": "Event", + "relatedIdentifierType": "URL", + } + ) + elif cast.archeologicky_zaznam.typ_zaznamu == ArcheologickyZaznam.TYP_ZAZNAMU_LOKALITA: + related_identifiers.append( + { + "relationType": "Documents", + "relatedIdentifier": f"{DIGIARCHIV_URL}{cast.archeologicky_zaznam.lokalita.igsn}", + "resourceTypeGeneral": "PhysicalObject", + "relatedIdentifierType": "IGSN", + } + ) + elif cast.projekt: + related_identifiers.append( + { + "relationType": "Documents", + "relatedIdentifier": f"{DIGIARCHIV_URL}{cast.projekt.ident_cely}", + "resourceTypeGeneral": "Event", + "relatedIdentifierType": "URL", + } + ) + return related_identifiers + + def _serialize_rights_list(self): + if self.object.licence: + serialized_rights = {"rights": self.object.licence.heslo_en} + spdx_query = self.object.licence.heslar_odkaz.filter(zdroj="SPDX") + if spdx_query.exists(): + serialized_rights["rightsUri"] = self.object.licence.heslar_odkaz.filter(zdroj="SPDX").first().uri + serialized_rights["schemeUri"] = self.object.licence.heslar_odkaz.filter(zdroj="SPDX").first().uri + serialized_rights["rightsIdentifier"] = ( + self.object.licence.heslar_odkaz.filter(zdroj="SPDX").first().kod + ) + serialized_rights["rightsIdentifierScheme"] = ( + self.object.licence.heslar_odkaz.filter(zdroj="SPDX").first().zdroj + ) + return [ + serialized_rights, + ] + return [] + + def _serialize_subjects(self): + serialized_subjects = super()._serialize_subjects() + serialized_subjects += [serialize_subject(posudek) for posudek in self.object.posudky.all()] + serialized_subjects += [serialize_subject(osoba, "vypis_cely") for osoba in self.object.osoby.all()] + if self.object.has_extra_data(): + serialized_subjects += [serialize_subject(self.object.extra_data.udalost_typ)] + serialized_subjects += [serialize_subject(komp.obdobi) for komp in self.object.get_komponenty()] + serialized_subjects += [serialize_subject(komp.areal) for komp in self.object.get_komponenty()] + serialized_subjects += _serialize_komponenty_m2n_fields(self.object.get_komponenty(AZ_STAV_ARCHIVOVANY)) + + for cast in self.object.casti.all(): + try: + if cast.archeologicky_zaznam and cast.archeologicky_zaznam.stav == AZ_STAV_ARCHIVOVANY: + serialized_subjects.append(serialize_subject(cast.archeologicky_zaznam.akce.hlavni_typ)) + serialized_subjects.append(serialize_subject(cast.archeologicky_zaznam.akce.vedlejsi_typ)) + except ObjectDoesNotExist: + pass + + try: + if cast.archeologicky_zaznam and cast.archeologicky_zaznam.stav == AZ_STAV_ARCHIVOVANY: + serialized_subjects.append(serialize_subject(cast.archeologicky_zaznam.lokalita.typ_lokality)) + serialized_subjects.append(serialize_subject(cast.archeologicky_zaznam.lokalita.druh)) + except ObjectDoesNotExist: + pass + serialized_subjects = [dict(item) for item in set(serialized_subjects)] + return serialized_subjects + + def _serialize_types(self): + serialized_types = {"resourceType": self.object.typ_dokumentu.heslo_en} + resource_type_query = self.object.typ_dokumentu.heslar_odkaz.filter(zdroj="DataCite").filter( + nazev_kodu="resourceTypeGeneral" + ) + if resource_type_query.exists(): + serialized_types["resourceTypeGeneral"] = resource_type_query.first().kod + return serialized_types + + +class SamostatnyNalezSerializer(ModelSerializer): + def __init__(self, object: SamostatnyNalez): + super().__init__(object) + self.object: SamostatnyNalez + + def _get_creators(self): + return [self.object.nalezce] + + def _get_prefix(self): + return "" + + def _get_publication_year(self): + return self.object.historie.historie_set.filter(typ_zmeny=ARCHIVACE_SN).first().datum_zmeny.year + + def _get_title(self, language: str): + return { + "en": f"AMCR {self.object.ident_cely} – {self.object.druh_nalezu.heslo_en} ({self.object.specifikace.heslo_en}), {self.object.obdobi.heslo_en}", + "cs": f"AMCR {self.object.ident_cely} – {self.object.druh_nalezu.heslo} ({self.object.specifikace.heslo}), {self.object.obdobi.heslo}", + }[language] + + def _serialize_alternate_identifiers(self): + alternate_identifiers = super()._serialize_alternate_identifiers() + if self.object.oznaceni_originalu: + alternate_identifiers.append( + { + "alternateIdentifierType": "Find inventory number", + "alternateIdentifier": self.object.evidencni_cislo, + } + ) + return alternate_identifiers + + def _serialize_contributors(self): + contributors = [serialize_osoba(self.object.let.pozorovatel, self.object.projekt.vedouci_projektu)] + if self.object.organizace and not self.object.organizace.general: + contributors.append(serialize_organizace(self.object.organizace)) + return contributors + + def _serialize_dates(self): + dates = super()._serialize_dates() + dates.append({"date": self.object.datum_nalezu, "dateType": "Collected"}) + return dates + + def _serialize_descriptions(self): + descriptions = [{"lang": "en", "description": "Collected by field survey.", "descriptionType": "Methods"}] + if self.object.poznamka: + descriptions.append({"lang": "cs", "description": self.object.poznamka, "descriptionType": "Abstract"}) + if self.object.okolnosti: + descriptions.append( + { + "lang": "cs", + "description": f"Finding context: {self.object.okolnosti.heslo_en}", + "descriptionType": "Abstract", + } + ) + if self.object.okolnosti: + descriptions.append( + {"lang": "cs", "description": f"Finding depth: {self.object.hloubka} cm", "descriptionType": "Abstract"} + ) + if self.object.pocet: + descriptions.append( + {"lang": "cs", "description": f"Number of finds: {self.object.pocet}", "descriptionType": "Abstract"} + ) + return descriptions + + def _serialize_geo_locations(self): + geo_locations: List[frozenset] = [] + if self.object.pristupnost.pk == PRISTUPNOST_ANONYM_ID and self.object.geom: + geo_locations.append(serialize_geom(self.object.geom)) + if self.object.pristupnost.pk == PRISTUPNOST_ANONYM_ID and self.object.katastr: + geo_locations.append(serialize_geom(place=self.object.katastr)) + return geo_locations + + def _serialize_related_identifiers(self): + related_identifiers = super()._serialize_related_identifiers() + if self.object.soubory.soubory.exists(): + related_identifiers.append( + { + "relationType": "IsDocumentedBy", + "relatedIdentifier": "", + "resourceTypeGeneral": "Image", + "relatedIdentifierType": "URL", + } + ) + related_identifiers.append( + { + "relationType": "IsDerivedFrom", + "relatedIdentifier": f"{DIGIARCHIV_URL}{self.object.projekt.ident_cely}", + "resourceTypeGeneral": "Event", + "relatedIdentifierType": "URL", + } + ) + + def _serialize_rights_list(self): + return [ + { + "rights": "Creative Commons Attribution-NonCommercial 4.0 International", + "rightsUri": "https://spdx.org/licenses/CC-BY-NC-4.0.html", + "schemeUri": "https://spdx.org/licenses", + "rightsIdentifier": "CC-BY-NC-4.0", + "rightsIdentifierScheme": "SPDX", + } + ] + + def _serialize_subjects(self): + serialized_subjects = super()._serialize_subjects() + serialized_subjects += [serialize_subject(posudek) for posudek in self.object.obdobi] + serialized_subjects += [serialize_subject(posudek) for posudek in self.object.druh_nalezu] + serialized_subjects += [serialize_subject(posudek) for posudek in self.object.specifikace] + return serialized_subjects + + def _serialize_types(self): + return {"resourceType": "archaeological find", "resourceTypeGeneral": "PhysicalObject"} + + +class LokalitaSerializer(ModelSerializer): + def __init__(self, object: Lokalita): + super().__init__(object) + self.object: Lokalita + + def _get_externi_odkaz_query(self): + return self.object.archeologicky_zaznam.externi_odkazy.filter( + externi_zdroj__typ__heslar_odkaz__zdroj="DataCite" + ).filter(externi_zdroj__typ_heslar_odkaz__nazev_kodu="resourceTypeGeneral") + + def _get_prefix(self): + return "" + + def _get_publication_year(self): + return self.object.historie.historie_set.filter(typ_zmeny=ARCHIVACE_AZ).first().datum_zmeny.year + + def _get_title(self, language: str): + return { + "en": f"AMCR {self.object.ident_cely} – {self.object.druh.heslo_en} {self.object.nazev * self.object.pristupnost.pk == PRISTUPNOST_ANONYM_ID}", + "cs": f"AMCR {self.object.ident_cely} – {self.object.druh.heslo} {self.object.nazev * self.object.pristupnost.pk == PRISTUPNOST_ANONYM_ID}", + }[language] + + def _serialize_alternate_identifiers(self): + alternate_identifiers = super()._serialize_alternate_identifiers() + if self.object.uzivatelske_oznaceni: + alternate_identifiers.append( + { + "alternateIdentifierType": "External identifier", + "alternateIdentifier": self.object.uzivatelske_oznaceni, + } + ) + return alternate_identifiers + + def _serialize_creators(self): + return [ + { + "name": "AIS CR", + "nameType": "Organizational", + "nameIdentifiers": [ + { + "affiliationIdentifier": f"https://ror.org/{AIS_AMCR.ident_cely}", + "affiliationIdentifierScheme": "ROR", + "schemeUri": "https://ror.org/", + } + ], + } + ] + + def _serialize_related_identifiers(self): + related_identifiers = super()._serialize_related_identifiers() + casti_dokumentu_query = ( + self.object.archeologicky_zaznam.casti_dokumentu.filter( + dokument__typ_dokumentu__heslar_odkaz__zdroj="DataCite" + ) + ).filter(dokument__typ_dokumentu__heslar_odkaz__nazev_kodu="resourceTypeGeneral") + for cast in casti_dokumentu_query: + cast: DokumentCast + if cast.dokument.stav == D_STAV_ARCHIVOVANY: + related_identifiers.append( + { + "relationType": "IsDocumentedBy", + "relatedIdentifier": cast.dokument.doi, + "resourceTypeGeneral": cast.dokument.typ_dokumentu.heslar_odkaz.kod, + "relatedIdentifierType": "DOI", + } + ) + for externi_odkaz in self._get_externi_odkaz_query(): + externi_odkaz: ExterniOdkaz + if externi_odkaz.externi_zdroj.doi: + related_identifiers.append( + { + "relationType": "IsPublishedIn", + "relatedIdentifier": externi_odkaz.externi_zdroj.doi, + "resourceTypeGeneral": externi_odkaz.externi_zdroj.typ.heslar_odkaz.kod, + "relatedIdentifierType": "DOI", + } + ) + related_identifiers.append( + { + "relationType": "IsPublishedIn", + "relatedIdentifier": f"{DIGIARCHIV_URL}{externi_odkaz.externi_zdroj.ident_cely}", + "resourceTypeGeneral": externi_odkaz.externi_zdroj.typ.heslar_odkaz.kod, + "relatedIdentifierType": "URL", + } + ) + return related_identifiers + + def _serialize_related_items(self): + related_items = [] + for externi_odkaz in self._get_externi_odkaz_query(): + related_item = {} + externi_odkaz: ExterniOdkaz + related_item.update( + { + "relatedItemType": externi_odkaz.externi_zdroj.typ.heslar_odkaz.kod, + "relationType": "IsPublishedIn", + "relatedItemIdentifier": { + "relatedItemIdentifier": externi_odkaz.externi_zdroj.doi + or externi_odkaz.externi_zdroj.ident_cely, + "relatedItemIdentifierType": "DOI", + }, + } + ) + return related_items + + def _serialize_subjects(self): + serialized_subjects = super()._serialize_subjects() + try: + if self.object.archeologicky_zaznam and self.object.archeologicky_zaznam.stav == AZ_STAV_ARCHIVOVANY: + serialized_subjects.append(serialize_subject(self.object.archeologicky_zaznam.lokalita.druh)) + except ObjectDoesNotExist: + pass + serialized_subjects += [serialize_subject(komp.obdobi) for komp in self.object.get_komponenty()] + serialized_subjects += [serialize_subject(komp.areal) for komp in self.object.get_komponenty()] + serialized_subjects += _serialize_komponenty_m2n_fields(self.object.get_komponenty(AZ_STAV_ARCHIVOVANY)) + return serialized_subjects + + def _serialize_types(self): + return {"resourceType": "archaeological site", "resourceTypeGeneral": "PhysicalObject"} + + def serialize_publish(self): + publish = super().serialize_publish() + publish["data"]["attributes"]["relatedItems"] = self._serialize_related_items() diff --git a/webclient/ez/forms.py b/webclient/ez/forms.py index 35b5b8c66..eb2edca46 100644 --- a/webclient/ez/forms.py +++ b/webclient/ez/forms.py @@ -143,10 +143,11 @@ class Meta: def clean_doi(self): doi = self.cleaned_data["doi"] - match = re.search(r"10\.\d{4,9}\/[-._;()/:a-zA-Z0-9]+", doi) - if not match: - raise ValidationError(_("ez.forms.externiZdrojForm.doi.error")) - doi = match.group() + if doi: + match = re.search(r"10\.\d{4,9}\/[-._;()/:a-zA-Z0-9]+", doi) + if not match: + raise ValidationError(_("ez.forms.externiZdrojForm.doi.error")) + doi = match.group() return doi def __init__(self, *args, required=None, required_next=None, readonly=False, **kwargs): diff --git a/webclient/heslar/admin.py b/webclient/heslar/admin.py index f0d65ed83..f5691cb86 100644 --- a/webclient/heslar/admin.py +++ b/webclient/heslar/admin.py @@ -150,7 +150,7 @@ class HeslarOdkazAdmin(admin.ModelAdmin): """ list_display = ("heslo_ident_cely", "heslo", "zdroj", "nazev_kodu", "kod", "uri", "skos_mapping_relation") - fields = ("heslar_nazev", "heslo", "zdroj", "nazev_kodu", "kod", "uri", "skos_mapping_relation") + fields = ("heslar_nazev", "heslo", "zdroj", "nazev_kodu", "kod", "uri", "skos_mapping_relation", "scheme_uri") search_fields = ("heslo__ident_cely", "heslo__heslo", "zdroj", "nazev_kodu", "kod", "uri") list_filter = ("zdroj", "skos_mapping_relation") form = HeslarOdkazForm diff --git a/webclient/heslar/migrations/0008_heslarodkaz_scheme_uri.py b/webclient/heslar/migrations/0008_heslarodkaz_scheme_uri.py new file mode 100644 index 000000000..cf51c6b06 --- /dev/null +++ b/webclient/heslar/migrations/0008_heslarodkaz_scheme_uri.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.3 on 2024-12-06 01:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("heslar", "0007_alter_heslar_unique_together_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="heslarodkaz", + name="scheme_uri", + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/webclient/heslar/models.py b/webclient/heslar/models.py index c6cfc9843..c97d6bee5 100644 --- a/webclient/heslar/models.py +++ b/webclient/heslar/models.py @@ -250,6 +250,7 @@ class HeslarOdkaz(ExportModelOperationsMixin("heslar_odkaz"), models.Model): verbose_name=_("heslar.models.HeslarOdkaz.skos_mapping_relation"), choices=SKOS_MAPPING_RELATION_CHOICES, ) + scheme_uri = models.CharField(max_length=255, null=True, blank=True) class Meta: db_table = "heslar_odkaz" diff --git a/webclient/lokalita/migrations/0002_lokalita_igsn.py b/webclient/lokalita/migrations/0002_lokalita_igsn.py new file mode 100644 index 000000000..d4f248050 --- /dev/null +++ b/webclient/lokalita/migrations/0002_lokalita_igsn.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.3 on 2024-12-06 00:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("lokalita", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="lokalita", + name="igsn", + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/webclient/lokalita/models.py b/webclient/lokalita/models.py index a0503a746..ab5ef1653 100644 --- a/webclient/lokalita/models.py +++ b/webclient/lokalita/models.py @@ -58,6 +58,7 @@ class Lokalita(ExportModelOperationsMixin("lokalita"), models.Model): primary_key=True, ) dalsi_katastry_snapshot = models.CharField(max_length=5000, null=True, blank=True) + igsn = models.CharField(max_length=255, blank=True, null=True) class Meta: db_table = "lokalita" diff --git a/webclient/pas/migrations/0008_samostatnynalez_igsn_alter_samostatnynalez_projekt.py b/webclient/pas/migrations/0008_samostatnynalez_igsn_alter_samostatnynalez_projekt.py new file mode 100644 index 000000000..d84dd4b86 --- /dev/null +++ b/webclient/pas/migrations/0008_samostatnynalez_igsn_alter_samostatnynalez_projekt.py @@ -0,0 +1,20 @@ +# Generated by Django 5.1.3 on 2024-12-05 22:15 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("pas", "0007_remove_samostatnynalez_samostatny_nalez_geom_check_and_more"), + ("projekt", "0008_alter_projekt_datum_ukonceni_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="samostatnynalez", + name="igsn", + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/webclient/pas/models.py b/webclient/pas/models.py index 50d958f05..85e974bcc 100644 --- a/webclient/pas/models.py +++ b/webclient/pas/models.py @@ -150,6 +150,7 @@ class SamostatnyNalez(ExportModelOperationsMixin("samostatny_nalez"), ModelWithM ) geom_updated_at = models.DateTimeField(null=True, blank=True) geom_sjtsk_updated_at = models.DateTimeField(null=True, blank=True) + igsn = models.CharField(max_length=255, blank=True, null=True) @property def initial_pristupnost(self): diff --git a/webclient/templates/toolbar_dokument.html b/webclient/templates/toolbar_dokument.html index f8e723674..9eba44b51 100644 --- a/webclient/templates/toolbar_dokument.html +++ b/webclient/templates/toolbar_dokument.html @@ -13,7 +13,7 @@ {% if d.ident_cely %} {{ d.ident_cely }} {% if d.doi %} - {{ d.doi }} + ({{ d.doi }}) {% endif %} {% if showback %}{% endif %} {% elif index%} diff --git a/webclient/templates/toolbar_pas.html b/webclient/templates/toolbar_pas.html index 5ea8a1ab1..a71349d6a 100644 --- a/webclient/templates/toolbar_pas.html +++ b/webclient/templates/toolbar_pas.html @@ -7,6 +7,9 @@
{{ sn.ident_cely }}{% if show.backtoprojekt %}{% trans "templates.toolbarPas.backToProjekt.label" %}{% endif %} + {% if sn.igsn %} + ({{ sn.igsn }}) + {% endif %}
{% if showcontrols %} diff --git a/webclient/uzivatel/admin.py b/webclient/uzivatel/admin.py index 3cf0bb912..a89a2b551 100644 --- a/webclient/uzivatel/admin.py +++ b/webclient/uzivatel/admin.py @@ -185,6 +185,7 @@ class CustomUserAdmin(DjangoObjectActions, UserAdmin): "last_name", "telefon", "osoba", + "orcid", "groups", ) }, @@ -206,12 +207,21 @@ class CustomUserAdmin(DjangoObjectActions, UserAdmin): "last_name", "telefon", "osoba", + "orcid", "groups", ), }, ), ) - search_fields = ("email", "organizace__nazev_zkraceny", "ident_cely", "first_name", "last_name", "telefon") + search_fields = ( + "email", + "organizace__nazev_zkraceny", + "ident_cely", + "first_name", + "last_name", + "telefon", + "orcid", + ) ordering = ("email",) change_actions = ("metadata",) diff --git a/webclient/uzivatel/forms.py b/webclient/uzivatel/forms.py index d81e58e10..f56d6d541 100644 --- a/webclient/uzivatel/forms.py +++ b/webclient/uzivatel/forms.py @@ -1,6 +1,6 @@ import logging -from core.validators import validate_phone_number +from core.validators import validate_orcid, validate_phone_number from core.widgets import ForeignKeyReadOnlyTextInput from crispy_forms.bootstrap import AppendedText from crispy_forms.helper import FormHelper @@ -31,6 +31,13 @@ class AuthUserCreationForm(RegistrationForm): Formulář pro vytvoření uživatele. """ + orcid = forms.CharField( + validators=[validate_orcid], + help_text=_("uzivatel.forms.AuthUserCreationForm.orcid.tooltip"), + label=_("uzivatel.forms.AuthUserCreationForm.orcid.label"), + widget=forms.TextInput(), + ) + class Meta(RegistrationForm): model = User fields = ( @@ -39,6 +46,7 @@ class Meta(RegistrationForm): "email", "telefon", "organizace", + "orcid", "password1", "password2", ) @@ -79,6 +87,7 @@ def __init__(self, *args, **kwargs): Field("email"), Field("telefon"), Field("organizace"), + Field("orcid"), AppendedText( "password1", mark_safe(''), @@ -122,9 +131,16 @@ class AuthUserChangeForm(forms.ModelForm): Formulář pro editaci uživatele. """ + orcid = forms.CharField( + validators=[validate_orcid], + help_text=_("uzivatel.forms.AuthUserChangeForm.orcid.tooltip"), + label=_("uzivatel.forms.AuthUserChangeForm.orcid.label"), + widget=forms.TextInput(), + ) + class Meta: model = User - fields = ("telefon",) + fields = ("telefon", "orcid") labels = { "telefon": _("uzivatel.forms.userChange.telefon.label"), } @@ -143,6 +159,7 @@ def __init__(self, *args, **kwargs): self.helper.layout = Layout( Div( Div("telefon", css_class="col-sm-3"), + Div("orcid", css_class="col-sm-3"), css_class="row", ) ) @@ -376,6 +393,14 @@ class OsobaForm(forms.ModelForm): Formulář pro vytvoření osoby. """ + orcid = forms.CharField( + validators=[validate_orcid], + help_text=_("uzivatel.forms.OsobaForm.orcid.tooltip"), + label=_("uzivatel.forms.OsobaForm.orcid.label"), + widget=forms.TextInput(), + required=False, + ) + class Meta: model = Osoba fields = ( diff --git a/webclient/uzivatel/migrations/0017_osoba_orcid_osoba_wikidata.py b/webclient/uzivatel/migrations/0017_osoba_orcid_osoba_wikidata.py index e994645be..c5b91d025 100644 --- a/webclient/uzivatel/migrations/0017_osoba_orcid_osoba_wikidata.py +++ b/webclient/uzivatel/migrations/0017_osoba_orcid_osoba_wikidata.py @@ -20,4 +20,9 @@ class Migration(migrations.Migration): name="wikidata", field=models.CharField(blank=True, max_length=255, null=True), ), + migrations.AddField( + model_name="user", + name="orcid", + field=models.CharField(blank=True, max_length=255, null=True), + ), ] diff --git a/webclient/uzivatel/models.py b/webclient/uzivatel/models.py index 3b07de817..0346cb9da 100644 --- a/webclient/uzivatel/models.py +++ b/webclient/uzivatel/models.py @@ -89,6 +89,7 @@ class User(ExportModelOperationsMixin("user"), AbstractBaseUser, PermissionsMixi limit_choices_to={"ident_cely__icontains": "S-E-"}, default=only_notification_groups, ) + orcid = models.CharField(max_length=255, blank=True, null=True) USERNAME_FIELD = "email" REQUIRED_FIELDS = [] diff --git a/webclient/uzivatel/signals.py b/webclient/uzivatel/signals.py index fcfaec908..bef8b8036 100644 --- a/webclient/uzivatel/signals.py +++ b/webclient/uzivatel/signals.py @@ -1,9 +1,9 @@ import logging -import re from cacheops import invalidate_model from core.ident_cely import get_uzivatel_ident from core.repository_connector import FedoraRepositoryConnector, FedoraTransaction +from core.validators import orcid_pattern from django.contrib.auth import user_logged_in from django.contrib.auth.hashers import check_password from django.core.exceptions import ObjectDoesNotExist, ValidationError @@ -35,7 +35,7 @@ def orgnaizace_save_metadata(sender, instance: Organizace, **kwargs): @receiver(pre_save, sender=Osoba, weak=False) def osoba_pre_save(sender, instance: Osoba, **kwargs): if instance.orcid: - match = re.search(r"\d{4}-\d{4}-\d{4}-\d{4}", instance.orcid) + match = orcid_pattern.search(instance.orcid) instance.orcid = match.group() if match else None if instance.wikidata and "/" in instance.wikidata: instance.wikidata = instance.wikidata.split("/")[-1] @@ -66,6 +66,9 @@ def create_ident_cely(sender, instance: User, **kwargs): instance.old = database_user_query.first() else: instance.old = None + if instance.orcid: + match = orcid_pattern.search(instance.orcid) + instance.orcid = match.group() if match else None if instance.pk is None: instance.model_is_updated = False logger.debug("uzivatel.signals.create_ident_cely.running_create_ident_cely_receiver") diff --git a/webclient/uzivatel/views.py b/webclient/uzivatel/views.py index 26674a7d2..7ec28bf8f 100644 --- a/webclient/uzivatel/views.py +++ b/webclient/uzivatel/views.py @@ -305,7 +305,10 @@ def post(self, request, *args, **kwargs): request_data = dict(request.POST) logger.debug("uzivatel.views.UserAccountUpdateView.post.start", extra={"request_data": request_data}) form = self.form_class(data=request.POST, instance=self.request.user) - has_changed = request.POST.get("telefon") != self.request.user.telefon + has_changed = ( + request.POST.get("telefon") != self.request.user.telefon + or request.POST.get("orcid") != self.request.user.orcid + ) if form.is_valid() and has_changed: obj = form.save(commit=False) obj: User diff --git a/webclient/xml_generator/definitions/amcr.xsd b/webclient/xml_generator/definitions/amcr.xsd index 6270ee2a7..b48ee86f5 100644 --- a/webclient/xml_generator/definitions/amcr.xsd +++ b/webclient/xml_generator/definitions/amcr.xsd @@ -187,6 +187,7 @@ + @@ -220,6 +221,7 @@ + @@ -411,6 +413,7 @@ + @@ -624,6 +627,7 @@ +