From 32798d19a684b857973691754df04fac33b46cb6 Mon Sep 17 00:00:00 2001 From: Stefan Beck Date: Mon, 7 Oct 2019 14:06:49 +0200 Subject: [PATCH 01/11] Make protocol files + xfail tests that currently fail --- deposit/darkarchive/__init__.py | 0 deposit/darkarchive/conftest.py | 28 ++++++++++++++++++++++++++++ deposit/darkarchive/protocol.py | 8 ++++++++ deposit/darkarchive/tests.py | 18 ++++++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 deposit/darkarchive/__init__.py create mode 100644 deposit/darkarchive/conftest.py create mode 100644 deposit/darkarchive/protocol.py create mode 100644 deposit/darkarchive/tests.py diff --git a/deposit/darkarchive/__init__.py b/deposit/darkarchive/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deposit/darkarchive/conftest.py b/deposit/darkarchive/conftest.py new file mode 100644 index 000000000..89ff61bb2 --- /dev/null +++ b/deposit/darkarchive/conftest.py @@ -0,0 +1,28 @@ +import pytest + +from deposit.darkarchive.protocol import DarkArchiveProtocol +from deposit.models import Repository + + +@pytest.fixture +def dark_archive_repository(simple_logo, oaisource): + """ + Returns a repository for dark archive tests + """ + repository = Repository.objects.create( + name='Dark Archivce Test Repository', + description='Dark archive test repository', + logo=simple_logo, + protocol='DarkArchive', + oaisource=oaisource.dummy_oaisource(), + ) + + return repository + + +@pytest.fixture +def dark_archive_protocol(request, dark_archive_repository): + """ + Sets the dark archive protocol as protocol to be used + """ + request.cls.protocol = DarkArchiveProtocol(dark_archive_repository) diff --git a/deposit/darkarchive/protocol.py b/deposit/darkarchive/protocol.py new file mode 100644 index 000000000..4153e2ee7 --- /dev/null +++ b/deposit/darkarchive/protocol.py @@ -0,0 +1,8 @@ +from deposit.protocol import RepositoryProtocol + + +class DarkArchiveProtocol(RepositoryProtocol): + """ + A protocol that does does directly sends data to a repository, but to an intermediata database + """ + pass diff --git a/deposit/darkarchive/tests.py b/deposit/darkarchive/tests.py new file mode 100644 index 000000000..b5c9cc7a1 --- /dev/null +++ b/deposit/darkarchive/tests.py @@ -0,0 +1,18 @@ +import pytest + +from deposit.tests.test_protocol import MetaTestProtocol + + +@pytest.mark.usefixtures('dark_archive_protocol') +class TestDarkArchiveProtocol(MetaTestProtocol): + """ + A test class for named protocol + """ + + @pytest.mark.xfail + def test_get_bound_form(self): + pass + + @pytest.mark.xfail + def test_protocol_registered(self): + pass From 427b52a1f0f6fdb0f7a099373d579a7e4be56c68 Mon Sep 17 00:00:00 2001 From: Stefan Beck Date: Mon, 7 Oct 2019 14:14:59 +0200 Subject: [PATCH 02/11] Give protocol a name --- deposit/darkarchive/protocol.py | 7 ++++++- deposit/darkarchive/tests.py | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/deposit/darkarchive/protocol.py b/deposit/darkarchive/protocol.py index 4153e2ee7..e3706628d 100644 --- a/deposit/darkarchive/protocol.py +++ b/deposit/darkarchive/protocol.py @@ -5,4 +5,9 @@ class DarkArchiveProtocol(RepositoryProtocol): """ A protocol that does does directly sends data to a repository, but to an intermediata database """ - pass + + def __str__(self): + """ + Return human readable class name + """ + return "Dark Archive Protocol" diff --git a/deposit/darkarchive/tests.py b/deposit/darkarchive/tests.py index b5c9cc7a1..84a115cdb 100644 --- a/deposit/darkarchive/tests.py +++ b/deposit/darkarchive/tests.py @@ -16,3 +16,11 @@ def test_get_bound_form(self): @pytest.mark.xfail def test_protocol_registered(self): pass + + + def test_str(self): + """ + Tests the string output + """ + s = self.protocol.__str__() + assert isinstance(s, str) == True From f016c43415d91169f695d3a8ac65f2be67eae3a3 Mon Sep 17 00:00:00 2001 From: Stefan Beck Date: Wed, 9 Oct 2019 08:58:21 +0200 Subject: [PATCH 03/11] Create metadata for deposit --- deposit/conftest.py | 52 ++++++++- deposit/darkarchive/protocol.py | 94 +++++++++++++++++ deposit/darkarchive/tests.py | 180 ++++++++++++++++++++++++++++++++ deposit/protocol.py | 29 +++++ deposit/sword/protocol.py | 16 ++- deposit/tests/test_protocol.py | 56 ++++++++++ pytest.ini | 2 +- 7 files changed, 418 insertions(+), 11 deletions(-) diff --git a/deposit/conftest.py b/deposit/conftest.py index b0d3d5440..27c830e51 100644 --- a/deposit/conftest.py +++ b/deposit/conftest.py @@ -37,6 +37,8 @@ def depositing_user(db, request, user_leibniz): orcid=request.param, ) + user_leibniz.orcid = request.param + return user_leibniz @@ -133,6 +135,54 @@ def dummy_publisher(): return p +@pytest.fixture +def dummy_journal(dummy_publisher): + """ + Empty Journal with FK to Publisher + """ + j = Journal.objects.create( + publisher=dummy_publisher, + ) + + return j + + +@pytest.fixture +def dummy_oairecord(dummy_paper, dummy_oaisource): + """ + Empty OaiRecord with FK to empty_paper and empty OaiSource + """ + o = OaiRecord.objects.create( + source=dummy_oaisource, + about=dummy_paper, + identifier='dummy', + ) + + return o + + +@pytest.fixture +def dummy_paper(): + """ + Just an empty paper + """ + p = Paper.objects.create( + pubdate='2019-10-08', + ) + + return p + + +@pytest.fixture +def dummy_publisher(): + """ + Empty Publisher + """ + p = Publisher.objects.create() + + return p + + @pytest.fixture def empty_user_preferences(db, user_isaac_newton): """ @@ -163,7 +213,7 @@ def license_standard(db): Returns a standard test license """ - license = License.objects.get_or_create( + license = License.objects.create( name="Standard License", uri="https://dissem.in/deposit/license/standard" ) diff --git a/deposit/darkarchive/protocol.py b/deposit/darkarchive/protocol.py index e3706628d..462ef29c6 100644 --- a/deposit/darkarchive/protocol.py +++ b/deposit/darkarchive/protocol.py @@ -11,3 +11,97 @@ def __str__(self): Return human readable class name """ return "Dark Archive Protocol" + + + def _get_authors(self): + """ + Returns a list of authors, containing a dict with: + first + last + orcid + """ + authors = [ + { + 'first' : author['name']['first'], + 'last' : author['name']['last'], + 'orcid' : author['orcid'], + } + for author in self.paper.authors_list + ] + + return authors + + + def _get_depositor(self): + """ + Returns a dictionary with first and last name + """ + d = { + 'first' : self.user.first_name, + 'last' : self.user.last_name, + } + + return d + + + def _get_eissn(self): + """ + Returns the eissn / essn if available or `None` + """ + if self.publication.journal is not None: + return self.publication.journal.essn + + + def _get_issn(self): + """ + Returns the issn if available or `None` + """ + if self.publication.journal is not None: + return self.publication.journal.issn + + + def _get_license(self, license_chooser): + """ + Returns a dictionary with fields about name, URI and transmit id + """ + if license_chooser: + d = { + 'name' : license_chooser.license.name, + 'uri' : license_chooser.license.uri, + 'transmit_id' : license_chooser.transmit_id + } + return d + + + def _get_metadata(self, form): + """ + Creates metadata ready to be converted into JSON. + Mainly we create a dictionary with some types as content that serialize well into JSON + """ + + md = { + 'abstract' : form.cleaned_data.get('abstract', None), + 'authors' : self._get_authors(), + 'date' : self.paper.pubdate, + 'depositor' : { + 'first' : self.user.first_name, + 'last' : self.user.last_name, + 'orcid' : self._get_depositor_orcid(), + 'is_contributor' : self.paper.is_owned_by(self.user, flexible=True), + }, + 'dissemin_id' : self.paper.pk, + 'doctype' : self.paper.doctype, + 'doi' : self.publication.doi, + 'eissn' : self._get_eissn(), + 'issn' : self._get_issn(), + 'issue' : self.publication.issue, + 'journal' : self.publication.full_journal_title(), + 'license' : self._get_license(form.cleaned_data.get('license', None)), + 'page' : self.publication.pages, + 'publisher' : self._get_publisher_name(), + 'sherpa_romeo_id' : self._get_sherpa_romeo_id(), + 'title' : self.paper.title, + 'volume' : self.publication.volume, + } + + return md diff --git a/deposit/darkarchive/tests.py b/deposit/darkarchive/tests.py index 84a115cdb..52bd13571 100644 --- a/deposit/darkarchive/tests.py +++ b/deposit/darkarchive/tests.py @@ -1,6 +1,33 @@ +import json +import os import pytest + +from deposit.models import License +from deposit.models import LicenseChooser from deposit.tests.test_protocol import MetaTestProtocol +from dissemin.settings import BASE_DIR +from papers.models import Paper +from papers.models import Researcher + + +author_one = [{ + 'name': { + 'full': 'herbert quain', + 'first': 'Herbert', + 'last': 'Quain', + }, + 'orcid' : None +}] + +author_two = [{ + 'name': { + 'full': 'aristoteles', + 'first': 'Aristoteles', + 'last': 'Stageira' + }, + 'orcid': None, +}] @pytest.mark.usefixtures('dark_archive_protocol') @@ -9,6 +36,50 @@ class TestDarkArchiveProtocol(MetaTestProtocol): A test class for named protocol """ + @pytest.mark.write_darkarchive_examples + def test_write_dark_archive_examples(self, db, upload_data, user_leibniz): + """ + This is not really a test. It just outputs metadata examples that the protocol generates. + Ususally this test is omitted, you can run it explicetely with "-m write_darkarchive_examples". + In case of changes of the protocol or repository, you should run this function, but make sure it's up to date + """ + self.protocol.paper = upload_data['paper'] + self.protocol.user = user_leibniz + + Researcher.create_by_name( + user=user_leibniz, + first=user_leibniz.first_name, + last=user_leibniz.last_name, + orcid="2543-2454-2345-234X", + ) + + # Set form data + data = dict() + if upload_data['oairecord'].description is not None: + data['abstract'] = upload_data['oairecord'].description + else: + data['abstract'] = upload_data['abstract'] + + l = License.objects.get(uri="https://creativecommons.org/licenses/by/4.0/") + lc = LicenseChooser.objects.create( + license=l, + repository=self.protocol.repository, + transmit_id='cc_by-40' + ) + licenses = LicenseChooser.objects.by_repository(repository=self.protocol.repository) + data['license'] = lc.pk + + form = self.protocol.form_class(licenses=licenses, data=data) + form.is_valid() + + md = self.protocol._get_metadata(form) + + f_path = os.path.join(BASE_DIR, 'doc', 'sphinx', 'examples', 'darkarchive') + f_name = os.path.join(f_path, upload_data['load_name'] + '.json') + os.makedirs(f_path, exist_ok=True) + with open(f_name, 'w') as fout: + json.dump(md, fout, indent=4) + @pytest.mark.xfail def test_get_bound_form(self): pass @@ -18,6 +89,115 @@ def test_protocol_registered(self): pass + @pytest.mark.parametrize('authors_list', [author_one, author_one + author_two]) + def test_get_authors_single_author(self, authors_list): + """ + Tests if authors are generated accordingly + """ + self.protocol.paper = Paper.objects.create(authors_list=authors_list, pubdate='2014-01-01') + + authors = self.protocol._get_authors() + + assert isinstance(authors, list) + assert len(authors) == len(authors_list) + for idx, author in enumerate(authors): + assert author['first'] == authors_list[idx]['name']['first'] + assert author['last'] == authors_list[idx]['name']['last'] + assert author['orcid'] == authors_list[idx]['orcid'] + + + @pytest.mark.parametrize('eissn', [None, '2343-2345']) + def test_get_eissn(self, dummy_oairecord, dummy_journal, eissn): + """ + Tests if the correct eissn / essn is returned + """ + dummy_journal.essn = eissn + dummy_journal.save() + dummy_oairecord.journal = dummy_journal + dummy_oairecord.save() + self.protocol.publication = dummy_oairecord + + assert self.protocol._get_eissn() == eissn + + + def test_get_eissn_no_journal(self, dummy_oairecord): + """ + If no journal, expect None + """ + self.protocol.publication = dummy_oairecord + + assert self.protocol._get_eissn() == None + + + @pytest.mark.parametrize('issn', [None, '2343-2345']) + def test_get_issn(self, dummy_oairecord, dummy_journal, issn): + """ + Tests if the correct eissn / essn is returned + """ + dummy_journal.issn = issn + dummy_journal.save() + dummy_oairecord.journal = dummy_journal + dummy_oairecord.save() + self.protocol.publication = dummy_oairecord + + assert self.protocol._get_issn() == issn + + + def test_get_issn_no_journal(self, dummy_oairecord): + """ + If no journal, expect None + """ + self.protocol.publication = dummy_oairecord + + assert self.protocol._get_issn() == None + + + def test_get_license(self, license_standard): + """ + Function should return a dict with license name, URI and transmit_id for given LicenseChooser object + """ + transmit_id = 'standard' + lc = LicenseChooser.objects.create( + license=license_standard, + repository=self.protocol.repository, + transmit_id=transmit_id, + ) + + l = self.protocol._get_license(lc) + + assert isinstance(l, dict) == True + assert l.get('name') == license_standard.name + assert l.get('uri') == license_standard.uri + assert l.get('transmit_id') == transmit_id + + + def test_get_metadata(self, upload_data, license_chooser, depositing_user): + """ + Test if the metadata is correctly generated + """ + self.protocol.paper = upload_data['paper'] + self.protocol.user = depositing_user + + # Set form data + data = dict() + if upload_data['oairecord'].description is not None: + data['abstract'] = upload_data['oairecord'].description + else: + data['abstract'] = upload_data['abstract'] + + if license_chooser: + data['license'] = license_chooser.pk + + form = self.protocol.form_class(data=data) + form.is_valid() + + md = self.protocol._get_metadata(form) + + md_fields = ['abstract', 'authors', 'date', 'depositor', 'doctype', 'doi', 'eissn', 'issn', 'issue', 'journal', 'page', 'publisher', 'title', 'volume', ] + + assert all(k in md for k in md_fields) == True + + def test_str(self): """ Tests the string output diff --git a/deposit/protocol.py b/deposit/protocol.py index 44293f7c0..7b258bc55 100644 --- a/deposit/protocol.py +++ b/deposit/protocol.py @@ -32,6 +32,7 @@ from deposit.forms import BaseMetadataForm from deposit.models import DEPOSIT_STATUS_CHOICES from deposit.models import LicenseChooser +from papers.models import Researcher logger = logging.getLogger('dissemin.' + __name__) @@ -428,3 +429,31 @@ def get_preferences_form(self, user, *args, **kwargs): kwargs['instance'] = prefs return self.preferences_form_class(*args, **kwargs) + ### Metadata helpers + # This are functions that help to retreive some metadata + + def _get_depositor_orcid(self): + """ + Get's the ORCID of the depositor if available + """ + r = Researcher.objects.get(user=self.user) + + return r.orcid + + + def _get_publisher_name(self): + """ + Returns the name of the publisher or ``None`` + """ + if self.publication.publisher: + return self.publication.publisher.name + else: + return self.publication.publisher_name + + + def _get_sherpa_romeo_id(self): + """ + Returns the SHERPA/RoMEO id if found + """ + if self.publication.publisher is not None: + return self.publication.publisher.romeo_id diff --git a/deposit/sword/protocol.py b/deposit/sword/protocol.py index 4dad5b6e4..7dff5c2fe 100644 --- a/deposit/sword/protocol.py +++ b/deposit/sword/protocol.py @@ -183,11 +183,11 @@ def _get_xml_dissemin_metadata(self, form): ds_email = etree.SubElement(ds_depositor, DS + 'email') ds_email.text = form.cleaned_data['email'] - r = Researcher.objects.get(user=self.user) + orcid = self._get_depositor_orcid() - if r.orcid: + if orcid is not None: ds_orcid = etree.SubElement(ds_depositor, DS + 'orcid') - ds_orcid.text = r.orcid + ds_orcid.text = orcid ds_is_contributor = etree.SubElement(ds_depositor, DS + 'isContributor') if self.paper.is_owned_by(self.user, flexible=True): @@ -217,7 +217,9 @@ def _get_xml_dissemin_metadata(self, form): ds_embargo = etree.SubElement(ds_publication, DS + 'embargoDate') ds_embargo.text = embargo.isoformat() - if self.publication.publisher is not None: + romeo_id = self._get_sherpa_romeo_id() + + if romeo_id is not None: ds_romeo = etree.SubElement(ds_publication, DS + 'romeoId') ds_romeo.text = str(self.publication.publisher.romeo_id) @@ -358,11 +360,7 @@ def _get_xml_metadata(self, form): mods_doi.text = self.publication.doi # Publisher - publisher = self.publication.publisher - if self.publication.publisher is not None: - publisher = self.publication.publisher.name - else: - publisher = self.publication.publisher_name + publisher = self._get_publisher_name() if publisher is not None: mods_publisher = etree.SubElement(mods_origin_info, MODS + 'publisher') diff --git a/deposit/tests/test_protocol.py b/deposit/tests/test_protocol.py index 4f21ce891..eec12c292 100644 --- a/deposit/tests/test_protocol.py +++ b/deposit/tests/test_protocol.py @@ -90,6 +90,15 @@ def test_deposit_page_status(self, authenticated_client, rendering_get_page, boo assert r.status_code == 200 + def test_get_depositor_orcid(self, depositing_user): + """ + Tested function returns the ORCID of the depositing user if available + """ + self.protocol.user = depositing_user + + assert self.protocol._get_depositor_orcid() == depositing_user.orcid + + def test_get_form(self, book_god_of_the_labyrinth, abstract_required, ddc, embargo, license_chooser): self.protocol.paper = book_god_of_the_labyrinth form = self.protocol.get_form() @@ -140,6 +149,53 @@ def test_get_form_return_type(self, book_god_of_the_labyrinth, user_isaac_newton assert isinstance(form, Form) + @pytest.mark.parametrize('pub_name', [None, 'BMC']) + def test_get_publisher_name_from_oairecord(self, dummy_oairecord, pub_name): + """ + Tests if the publisher name is returned or if not available: None + """ + dummy_oairecord.publisher_name = pub_name + self.protocol.publication = dummy_oairecord + + assert self.protocol._get_publisher_name() == pub_name + + + def test_get_publisher_name_from_publisher(self, dummy_oairecord, dummy_publisher): + """ + Tests if the publisher name is returned from the publisher object + """ + dummy_publisher.name = 'BMC' + dummy_publisher.save() + dummy_oairecord.publisher = dummy_publisher + dummy_oairecord.save() + self.protocol.publication = dummy_oairecord + + assert self.protocol._get_publisher_name() == dummy_publisher.name + + + def test_get_sherpa_romeo_id(self, dummy_oairecord, dummy_publisher): + """ + Tests if the SHERPA/RoMEO id is returned from the publisher object + """ + romeo_id = 1 + dummy_publisher.romeo_id = romeo_id + dummy_publisher.save() + dummy_oairecord.publisher = dummy_publisher + dummy_oairecord.save() + self.protocol.publication = dummy_oairecord + + assert self.protocol._get_sherpa_romeo_id() == romeo_id + + + def test_get_sherpa_romeo_id_no_publisher(self, dummy_oairecord): + """ + If no publisher for OaiRecord if found, expect ``None``. + """ + self.protocol.publication = dummy_oairecord + + assert self.protocol._get_sherpa_romeo_id() == None + + def test_init_deposit(self, user_isaac_newton, book_god_of_the_labyrinth): """ init_deposit shall return a bool diff --git a/pytest.ini b/pytest.ini index 70ff3df7c..e90505e9d 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,5 @@ [pytest] DJANGO_SETTINGS_MODULE = dissemin.testsettings # We generate examples with pytest, because we can use virtual database and use pytest fixtures. By default we do not generate the examples. Rund them manually with "-m write_mets_examples" -addopts = -m "not write_mets_examples" +addopts = -m "not write_mets_examples and not write_darkarchive_examples" python_files = tests.py test*.py *_tests.py From 4cb73f025c196ab9fa55428d34062def3c9861ec Mon Sep 17 00:00:00 2001 From: Stefan Beck Date: Wed, 9 Oct 2019 08:58:53 +0200 Subject: [PATCH 04/11] Generate dark archive json examples --- ...-chapter_acute_interstitial_nephritis.json | 39 ++++++ ...for_multisensor_maritime_surveillance.json | 44 +++++++ .../book_god_of_the_labyrinth.json | 34 +++++ ...ent_in_older_adults_unique_situations.json | 39 ++++++ ...ects_mhc_genotype_in_a_social_primate.json | 59 +++++++++ ...s_zum_strafrechtlichen_vorsatzbegriff.json | 34 +++++ ...-article_confrontation_with_heidegger.json | 39 ++++++ ...e_constructing_matrix_geometric_means.json | 34 +++++ ...ournal-article_lobjet_de_la_dpression.json | 34 +++++ .../journal-issue_mode_und_gender.json | 119 ++++++++++++++++++ ...ation_based_feature_model_composition.json | 49 ++++++++ ...ication_in_neurodegenerative_diseases.json | 34 +++++ .../preprint_nikomachische_ethik.json | 34 +++++ ...r_weaklystructured_business_processes.json | 44 +++++++ ..._method_to_capture_complex_sea_states.json | 44 +++++++ .../proceedings-article_cursos_efa.json | 34 +++++ .../proceedings_sein_und_nichtsein.json | 34 +++++ ...alysis_using_pairedend_tag_sequencing.json | 54 ++++++++ ..._targeted_experiments_to_inform_bison.json | 34 +++++ ...baus_von_manganknollen_in_der_tiefsee.json | 34 +++++ 20 files changed, 870 insertions(+) create mode 100644 doc/sphinx/examples/darkarchive/book-chapter_acute_interstitial_nephritis.json create mode 100644 doc/sphinx/examples/darkarchive/book-chapter_adaptive_multiagent_system_for_multisensor_maritime_surveillance.json create mode 100644 doc/sphinx/examples/darkarchive/book_god_of_the_labyrinth.json create mode 100644 doc/sphinx/examples/darkarchive/dataset_sexuality_assessment_in_older_adults_unique_situations.json create mode 100644 doc/sphinx/examples/darkarchive/journal-article_a_female_signal_reflects_mhc_genotype_in_a_social_primate.json create mode 100644 doc/sphinx/examples/darkarchive/journal-article_altes_und_neues_zum_strafrechtlichen_vorsatzbegriff.json create mode 100644 doc/sphinx/examples/darkarchive/journal-article_confrontation_with_heidegger.json create mode 100644 doc/sphinx/examples/darkarchive/journal-article_constructing_matrix_geometric_means.json create mode 100644 doc/sphinx/examples/darkarchive/journal-article_lobjet_de_la_dpression.json create mode 100644 doc/sphinx/examples/darkarchive/journal-issue_mode_und_gender.json create mode 100644 doc/sphinx/examples/darkarchive/other_assisting_configuration_based_feature_model_composition.json create mode 100644 doc/sphinx/examples/darkarchive/poster_development_of_new_gpe_conjugates_with_application_in_neurodegenerative_diseases.json create mode 100644 doc/sphinx/examples/darkarchive/preprint_nikomachische_ethik.json create mode 100644 doc/sphinx/examples/darkarchive/proceedings-article_activitycentric_support_for_weaklystructured_business_processes.json create mode 100644 doc/sphinx/examples/darkarchive/proceedings-article_an_efficient_vofbased_rans_method_to_capture_complex_sea_states.json create mode 100644 doc/sphinx/examples/darkarchive/proceedings-article_cursos_efa.json create mode 100644 doc/sphinx/examples/darkarchive/proceedings_sein_und_nichtsein.json create mode 100644 doc/sphinx/examples/darkarchive/reference-entry_chromatin_interaction_analysis_using_pairedend_tag_sequencing.json create mode 100644 doc/sphinx/examples/darkarchive/report_execution_of_targeted_experiments_to_inform_bison.json create mode 100644 doc/sphinx/examples/darkarchive/thesis_blue_mining_planung_des_abbaus_von_manganknollen_in_der_tiefsee.json diff --git a/doc/sphinx/examples/darkarchive/book-chapter_acute_interstitial_nephritis.json b/doc/sphinx/examples/darkarchive/book-chapter_acute_interstitial_nephritis.json new file mode 100644 index 000000000..2438f481f --- /dev/null +++ b/doc/sphinx/examples/darkarchive/book-chapter_acute_interstitial_nephritis.json @@ -0,0 +1,39 @@ +{ + "abstract": "This abstract is pure user input", + "authors": [ + { + "first": "Jerome A.", + "last": "Rossert", + "orcid": null + }, + { + "first": "Evelyne A.", + "last": "Fischer", + "orcid": null + } + ], + "date": "2010-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 1, + "doctype": "book-chapter", + "doi": "10.1016/b978-0-323-05876-6.00060-5", + "eissn": null, + "issn": null, + "issue": null, + "journal": "Comprehensive Clinical Nephrology", + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": "729-737", + "publisher": "Elsevier BV", + "sherpa_romeo_id": null, + "title": "Acute Interstitial Nephritis", + "volume": null +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/book-chapter_adaptive_multiagent_system_for_multisensor_maritime_surveillance.json b/doc/sphinx/examples/darkarchive/book-chapter_adaptive_multiagent_system_for_multisensor_maritime_surveillance.json new file mode 100644 index 000000000..b710e8e35 --- /dev/null +++ b/doc/sphinx/examples/darkarchive/book-chapter_adaptive_multiagent_system_for_multisensor_maritime_surveillance.json @@ -0,0 +1,44 @@ +{ + "abstract": "Maritime surveillance is a difficult task as its aim is to detect any threatening event in a dynamic, complex and hugely distributed system. As there are many different types of vessels, behaviours, situations, and because the system is constantly evolving, classical automated surveillance approaches are unrealistic. We propose an adaptive multi-agent system in which each agent is responsible for a vessel. This agent perceives local anomalies and combines them to maintain a criticality value, used to decide when an alert is appropriate. The importance of the anomalies and how they are combined emerges from a cooperative selfadjusting process taking user feedback into account. This software is currently under development in ScanMaris, a project supported by the French National Research Agency.", + "authors": [ + { + "first": "Jean-Pierre", + "last": "Mano", + "orcid": null + }, + { + "first": "Jean-Pierre", + "last": "Georg\u00e9", + "orcid": null + }, + { + "first": "Marie-Pierre", + "last": "Gleizes", + "orcid": null + } + ], + "date": "2010-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 2, + "doctype": "book-chapter", + "doi": "10.1007/978-3-642-12384-9_34", + "eissn": null, + "issn": null, + "issue": null, + "journal": "Advances in Intelligent and Soft Computing", + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": "285-290", + "publisher": "Springer Science + Business Media", + "sherpa_romeo_id": null, + "title": "Adaptive Multi-agent System for Multi-sensor Maritime Surveillance", + "volume": null +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/book_god_of_the_labyrinth.json b/doc/sphinx/examples/darkarchive/book_god_of_the_labyrinth.json new file mode 100644 index 000000000..1b96408c2 --- /dev/null +++ b/doc/sphinx/examples/darkarchive/book_god_of_the_labyrinth.json @@ -0,0 +1,34 @@ +{ + "abstract": "A detective story in which the solution given is wrong, although this fact is not immediately obvious.", + "authors": [ + { + "first": "Herbert", + "last": "Quain", + "orcid": null + } + ], + "date": "1933-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 3, + "doctype": "book", + "doi": "10.0123/quain-1933", + "eissn": null, + "issn": null, + "issue": null, + "journal": null, + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": null, + "publisher": null, + "sherpa_romeo_id": null, + "title": "The God of the Labyrinth", + "volume": null +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/dataset_sexuality_assessment_in_older_adults_unique_situations.json b/doc/sphinx/examples/darkarchive/dataset_sexuality_assessment_in_older_adults_unique_situations.json new file mode 100644 index 000000000..aa97ece58 --- /dev/null +++ b/doc/sphinx/examples/darkarchive/dataset_sexuality_assessment_in_older_adults_unique_situations.json @@ -0,0 +1,39 @@ +{ + "abstract": "The user entered this text.", + "authors": [ + { + "first": "Maggie L.", + "last": "Syme", + "orcid": null + }, + { + "first": "Michelle S.", + "last": "Ballan", + "orcid": null + } + ], + "date": "2010-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 4, + "doctype": "dataset", + "doi": "10.1037/e606472010-001", + "eissn": null, + "issn": null, + "issue": null, + "journal": "PsycEXTRA Dataset", + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": null, + "publisher": "American Psychological Association (APA)", + "sherpa_romeo_id": null, + "title": "Sexuality Assessment in Older Adults: Unique Situations", + "volume": null +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/journal-article_a_female_signal_reflects_mhc_genotype_in_a_social_primate.json b/doc/sphinx/examples/darkarchive/journal-article_a_female_signal_reflects_mhc_genotype_in_a_social_primate.json new file mode 100644 index 000000000..d8aa026d3 --- /dev/null +++ b/doc/sphinx/examples/darkarchive/journal-article_a_female_signal_reflects_mhc_genotype_in_a_social_primate.json @@ -0,0 +1,59 @@ +{ + "abstract": "Males from many species are believed to advertise their genetic quality through striking ornaments that attract mates. Yet the connections between signal expression, body condition and the genes associated with individual quality are rarely elucidated. This is particularly problematic for the signals of females in species with conventional sex roles, whose evolutionary significance has received little attention and is poorly understood. Here we explore these questions in the sexual swellings of female primates, which are among the most conspicuous of mammalian sexual signals and highly variable in size, shape and colour. We investigated the relationships between two components of sexual swellings (size and shape), body condition, and genes of the Major Histocompatibility Complex (MHC) in a wild baboon population (Papio ursinus) where males prefer large swellings.", + "authors": [ + { + "first": "Elise", + "last": "Huchard", + "orcid": null + }, + { + "first": "Michel", + "last": "Raymond", + "orcid": null + }, + { + "first": "Julio", + "last": "Benavides", + "orcid": null + }, + { + "first": "Harry", + "last": "Marshall", + "orcid": null + }, + { + "first": "Leslie A.", + "last": "Knapp", + "orcid": null + }, + { + "first": "Guy", + "last": "Cowlishaw", + "orcid": null + } + ], + "date": "2010-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 5, + "doctype": "journal-article", + "doi": "10.1186/1471-2148-10-96", + "eissn": "1471-2148", + "issn": null, + "issue": "1", + "journal": "BMC Evolutionary Biology", + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": "96", + "publisher": "BMC", + "sherpa_romeo_id": "22", + "title": "A female signal reflects MHC genotype in a social primate", + "volume": "10" +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/journal-article_altes_und_neues_zum_strafrechtlichen_vorsatzbegriff.json b/doc/sphinx/examples/darkarchive/journal-article_altes_und_neues_zum_strafrechtlichen_vorsatzbegriff.json new file mode 100644 index 000000000..6bff1a75e --- /dev/null +++ b/doc/sphinx/examples/darkarchive/journal-article_altes_und_neues_zum_strafrechtlichen_vorsatzbegriff.json @@ -0,0 +1,34 @@ +{ + "abstract": "This abstract was filled in by a user", + "authors": [ + { + "first": "G\u00fcnther", + "last": "Jakobs", + "orcid": null + } + ], + "date": "2010-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 6, + "doctype": "journal-article", + "doi": "10.5771/1868-8098-2010-3-283", + "eissn": null, + "issn": "1868-8098", + "issue": null, + "journal": "Rechtswissenschaft", + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": "283-315", + "publisher": "Nomos Verlagsgesellschaft", + "sherpa_romeo_id": "1423", + "title": "Altes und Neues zum strafrechtlichen Vorsatzbegriff", + "volume": null +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/journal-article_confrontation_with_heidegger.json b/doc/sphinx/examples/darkarchive/journal-article_confrontation_with_heidegger.json new file mode 100644 index 000000000..297f342e6 --- /dev/null +++ b/doc/sphinx/examples/darkarchive/journal-article_confrontation_with_heidegger.json @@ -0,0 +1,39 @@ +{ + "abstract": "This abstract was filled in by the user", + "authors": [ + { + "first": "M.", + "last": "Cacciari", + "orcid": null + }, + { + "first": "T. S.", + "last": "Murphy", + "orcid": null + } + ], + "date": "2010-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 7, + "doctype": "journal-article", + "doi": "10.1215/00166928-43-3-4-353", + "eissn": "2160-0228", + "issn": "0016-6928", + "issue": "3-4", + "journal": "Genre", + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": "353-368", + "publisher": "Duke University Press", + "sherpa_romeo_id": null, + "title": "Confrontation with Heidegger", + "volume": "43" +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/journal-article_constructing_matrix_geometric_means.json b/doc/sphinx/examples/darkarchive/journal-article_constructing_matrix_geometric_means.json new file mode 100644 index 000000000..2cee9170c --- /dev/null +++ b/doc/sphinx/examples/darkarchive/journal-article_constructing_matrix_geometric_means.json @@ -0,0 +1,34 @@ +{ + "abstract": "In this paper, we analyze the process of assembling new matrix geometric means from existing ones, through function composition or limit processes. We show that for n a new matrix mean exists which is simpler to compute than the existing ones. Moreover, we show that for n the existing proving strategies cannot provide a mean computationally simpler than the existing ones.", + "authors": [ + { + "first": "Federico G.", + "last": "Poloni", + "orcid": null + } + ], + "date": "2010-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 8, + "doctype": "journal-article", + "doi": "10.13001/1081-3810.1385", + "eissn": null, + "issn": null, + "issue": "1", + "journal": "Electronic Journal of Linear Algebra", + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": null, + "publisher": "University of Wyoming Libraries", + "sherpa_romeo_id": null, + "title": "Constructing matrix geometric means", + "volume": "20" +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/journal-article_lobjet_de_la_dpression.json b/doc/sphinx/examples/darkarchive/journal-article_lobjet_de_la_dpression.json new file mode 100644 index 000000000..9c43a9715 --- /dev/null +++ b/doc/sphinx/examples/darkarchive/journal-article_lobjet_de_la_dpression.json @@ -0,0 +1,34 @@ +{ + "abstract": "This abstract is filled in from the user", + "authors": [ + { + "first": "Alain", + "last": "Vanier", + "orcid": null + } + ], + "date": "2010-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 9, + "doctype": "journal-article", + "doi": "10.3917/cla.017.0023", + "eissn": null, + "issn": null, + "issue": "1", + "journal": "La clinique lacanienne", + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": "23", + "publisher": "Armand Colin", + "sherpa_romeo_id": "982", + "title": "L'objet de la d\u00e9pression", + "volume": "17" +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/journal-issue_mode_und_gender.json b/doc/sphinx/examples/darkarchive/journal-issue_mode_und_gender.json new file mode 100644 index 000000000..9b65f38ee --- /dev/null +++ b/doc/sphinx/examples/darkarchive/journal-issue_mode_und_gender.json @@ -0,0 +1,119 @@ +{ + "abstract": "User filled in abstract", + "authors": [ + { + "first": "Gertrud", + "last": "Lehnert", + "orcid": null + }, + { + "first": "Jenny", + "last": "B\u00fcnnig", + "orcid": null + }, + { + "first": "Miriam", + "last": "von Maydell", + "orcid": null + }, + { + "first": "Maria", + "last": "Weilandt", + "orcid": null + }, + { + "first": "Julia", + "last": "Hahmann", + "orcid": null + }, + { + "first": "Claudia", + "last": "Amsler", + "orcid": null + }, + { + "first": "Antonella", + "last": "Giannone", + "orcid": null + }, + { + "first": "Petra", + "last": "Leutner", + "orcid": null + }, + { + "first": "Folke", + "last": "Brodersen", + "orcid": null + }, + { + "first": "Mona", + "last": "Motakef", + "orcid": null + }, + { + "first": "Julia", + "last": "Bringmann", + "orcid": null + }, + { + "first": "Christine", + "last": "Wimbauer", + "orcid": null + }, + { + "first": "Doroth\u00e9e", + "last": "de N\u00e8ve", + "orcid": null + }, + { + "first": "Niklas", + "last": "Ferch", + "orcid": null + }, + { + "first": "Carolin", + "last": "Braunm\u00fchl", + "orcid": null + }, + { + "first": "Susanne", + "last": "Richter", + "orcid": null + }, + { + "first": "Janina", + "last": "Scholz", + "orcid": null + }, + { + "first": "Charlotte", + "last": "Binder", + "orcid": null + } + ], + "date": "2018-09-17", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 10, + "doctype": "journal-issue", + "doi": null, + "eissn": null, + "issn": null, + "issue": "3-2018", + "journal": "GENDER \u2013 Zeitschrift f\u00fcr Geschlecht, Kultur und Gesellschaft", + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": null, + "publisher": "Barbara Budrich Publishers", + "sherpa_romeo_id": null, + "title": "Mode und Gender", + "volume": "10" +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/other_assisting_configuration_based_feature_model_composition.json b/doc/sphinx/examples/darkarchive/other_assisting_configuration_based_feature_model_composition.json new file mode 100644 index 000000000..c548614e2 --- /dev/null +++ b/doc/sphinx/examples/darkarchive/other_assisting_configuration_based_feature_model_composition.json @@ -0,0 +1,49 @@ +{ + "abstract": "Feature Models (FMs) have been introduced in the domain of Software Product Lines (SPL) to model and represent product variability. They have become a de facto standard, based on a logical tree structure accompanied by textual cross-tree constraints. Other representations are: (product) configuration sets from concrete software product lines, logical representations, constraint programming, or conceptual structures, coming from the Formal Concept Analysis (FCA) framework. Modeling variability through FMs may consist in extracting them from configuration sets (namely, doing FM synthesis), or designing them in several steps potentially involving several teams with different concerns. FM composition is useful in this design activity as it may assist FM iterative building. In this paper, we describe an approach, based on a configuration set and focusing on two main composition semantics (union, intersection), to assist designers in FM composition. We also introduce an approximate intersection notion. FCA is used to represent, for a product family, all the FMs that have the same configuration set through a canonical form. The approach is able to take into account cross-tree constraints and FMs with different feature sets and tree structure, thus it lets the expert free of choosing a different ontological interpretation. We describe the implementation of our approach and we present a set of concrete examples.", + "authors": [ + { + "first": "Jessie", + "last": "Carbonnel", + "orcid": null + }, + { + "first": "Marianne", + "last": "Huchard", + "orcid": null + }, + { + "first": "Andr\u00e9", + "last": "Miralles", + "orcid": null + }, + { + "first": "Cl\u00e9mentine", + "last": "Nebut", + "orcid": null + } + ], + "date": "2019-06-13", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 11, + "doctype": "other", + "doi": "10.1007/978-3-319-94135-6_6", + "eissn": null, + "issn": null, + "issue": null, + "journal": "Communications in Computer and Information Science", + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": null, + "publisher": "Springer International Publishing", + "sherpa_romeo_id": null, + "title": "Assisting Configurations-Based Feature Model Composition", + "volume": null +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/poster_development_of_new_gpe_conjugates_with_application_in_neurodegenerative_diseases.json b/doc/sphinx/examples/darkarchive/poster_development_of_new_gpe_conjugates_with_application_in_neurodegenerative_diseases.json new file mode 100644 index 000000000..f26138154 --- /dev/null +++ b/doc/sphinx/examples/darkarchive/poster_development_of_new_gpe_conjugates_with_application_in_neurodegenerative_diseases.json @@ -0,0 +1,34 @@ +{ + "abstract": "User filled in abstract", + "authors": [ + { + "first": "Sara C.", + "last": "Silva Reis", + "orcid": "0000-0003-2024-0696" + } + ], + "date": "2018-05-18", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 12, + "doctype": "poster", + "doi": null, + "eissn": null, + "issn": null, + "issue": null, + "journal": null, + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": null, + "publisher": null, + "sherpa_romeo_id": null, + "title": "Development of New GPE Conjugates with Application in Neurodegenerative Diseases", + "volume": null +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/preprint_nikomachische_ethik.json b/doc/sphinx/examples/darkarchive/preprint_nikomachische_ethik.json new file mode 100644 index 000000000..8c5cc1028 --- /dev/null +++ b/doc/sphinx/examples/darkarchive/preprint_nikomachische_ethik.json @@ -0,0 +1,34 @@ +{ + "abstract": "Beschreibt die Art und Weise zu leben", + "authors": [ + { + "first": "Aristoteles", + "last": "Stageira", + "orcid": null + } + ], + "date": "0344-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 13, + "doctype": "preprint", + "doi": null, + "eissn": null, + "issn": null, + "issue": null, + "journal": null, + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": null, + "publisher": null, + "sherpa_romeo_id": null, + "title": "Nikomachische Ethik", + "volume": null +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/proceedings-article_activitycentric_support_for_weaklystructured_business_processes.json b/doc/sphinx/examples/darkarchive/proceedings-article_activitycentric_support_for_weaklystructured_business_processes.json new file mode 100644 index 000000000..4e2767245 --- /dev/null +++ b/doc/sphinx/examples/darkarchive/proceedings-article_activitycentric_support_for_weaklystructured_business_processes.json @@ -0,0 +1,44 @@ +{ + "abstract": "Knowledge-intensive tasks are a blind spot for business process management systems, as these tasks are executed in an unsupervised, highly individual manner. Hence, individual experience is not disseminated and task execution largely depends on implicit knowledge. In this paper we present a framework, realizing situation-specific and personalized task execution support for knowledge-intensive tasks in business processes. As a core concept we suggest activity scheme: a structure capturing a probabilistic task execution model. Activity schemes seamlessly integrate the organizational business process with the individual task execution process based on personalization and generalization of user interactions in the working applications.", + "authors": [ + { + "first": "Benedikt", + "last": "Schmidt", + "orcid": null + }, + { + "first": "Todor", + "last": "Stoitsev", + "orcid": null + }, + { + "first": "Max", + "last": "M\u00fchlh\u00e4user", + "orcid": null + } + ], + "date": "2010-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 15, + "doctype": "proceedings-article", + "doi": "10.1145/1822018.1822057", + "eissn": null, + "issn": null, + "issue": null, + "journal": "Proceedings of the 2nd ACM SIGCHI symposium on Engineering interactive computing systems - EICS '10", + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": null, + "publisher": "Association for Computing Machinery (ACM)", + "sherpa_romeo_id": null, + "title": "Activity-centric support for weakly-structured business processes", + "volume": null +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/proceedings-article_an_efficient_vofbased_rans_method_to_capture_complex_sea_states.json b/doc/sphinx/examples/darkarchive/proceedings-article_an_efficient_vofbased_rans_method_to_capture_complex_sea_states.json new file mode 100644 index 000000000..d73baec49 --- /dev/null +++ b/doc/sphinx/examples/darkarchive/proceedings-article_an_efficient_vofbased_rans_method_to_capture_complex_sea_states.json @@ -0,0 +1,44 @@ +{ + "abstract": "Viscous seakeeping and manoeuvring simulations are becoming more important for an investigation of intact and damage conditions of ships exposed to extreme sea-states. The computational effort for RANS-based viscous seaway simulations is still prohibitive and reduces the possible fields of industrial applications. The limitations are associated to cumbersome grid requirements which restrain the flexibility and enhance the computational costs. The requirements follow from the aim to accurately predict the wave propagation towards the floating object and concurrently suppress wave reflections at the outlet boundary. To overcome these difficulties, a simple viscous/inviscid-coupling approach is suggested. Therein, the viscous RANS method is implicitly forced to comply with a prescribed invis-cid solution towards the far-field boundaries. Employing consistent boundary conditions at all far-field boundaries, the method facilitates the use of small domains and allows to investigate the behaviour of floating objects exposed to time-variable wave-field directions. Selected 2D and 3D examples are presented to demonstrate the capabilities of the approach.", + "authors": [ + { + "first": "Katja", + "last": "Woeckner", + "orcid": null + }, + { + "first": "Withold", + "last": "Drazyk", + "orcid": null + }, + { + "first": "Thomas", + "last": "Rung", + "orcid": null + } + ], + "date": "2010-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 16, + "doctype": "proceedings-article", + "doi": "10.1115/omae2010-20674", + "eissn": null, + "issn": null, + "issue": null, + "journal": "29th International Conference on Ocean, Offshore and Arctic Engineering: Volume 6", + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": null, + "publisher": "ASME International", + "sherpa_romeo_id": null, + "title": "An Efficient VOF-Based RANS Method to Capture Complex Sea States", + "volume": null +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/proceedings-article_cursos_efa.json b/doc/sphinx/examples/darkarchive/proceedings-article_cursos_efa.json new file mode 100644 index 000000000..57c2afa81 --- /dev/null +++ b/doc/sphinx/examples/darkarchive/proceedings-article_cursos_efa.json @@ -0,0 +1,34 @@ +{ + "abstract": "User filled in abstract", + "authors": [ + { + "first": "Filipa", + "last": "Seabra", + "orcid": "0000-0003-1690-9502" + } + ], + "date": "2017-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 17, + "doctype": "proceedings-article", + "doi": null, + "eissn": null, + "issn": null, + "issue": null, + "journal": null, + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": null, + "publisher": null, + "sherpa_romeo_id": null, + "title": "Cursos EFA: Alternativas curriculares no distrito de Lisboa. Percursos de Investiga\u00e7\u00e3o.", + "volume": null +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/proceedings_sein_und_nichtsein.json b/doc/sphinx/examples/darkarchive/proceedings_sein_und_nichtsein.json new file mode 100644 index 000000000..22a530702 --- /dev/null +++ b/doc/sphinx/examples/darkarchive/proceedings_sein_und_nichtsein.json @@ -0,0 +1,34 @@ +{ + "abstract": "Hamlets Weisheiten", + "authors": [ + { + "first": "Ghost", + "last": "Hamlet", + "orcid": null + } + ], + "date": "1610-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 14, + "doctype": "proceedings", + "doi": null, + "eissn": null, + "issn": null, + "issue": null, + "journal": null, + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": null, + "publisher": null, + "sherpa_romeo_id": null, + "title": "Sein und Nichtsein", + "volume": null +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/reference-entry_chromatin_interaction_analysis_using_pairedend_tag_sequencing.json b/doc/sphinx/examples/darkarchive/reference-entry_chromatin_interaction_analysis_using_pairedend_tag_sequencing.json new file mode 100644 index 000000000..cac50067f --- /dev/null +++ b/doc/sphinx/examples/darkarchive/reference-entry_chromatin_interaction_analysis_using_pairedend_tag_sequencing.json @@ -0,0 +1,54 @@ +{ + "abstract": "Chromatin Interaction Analysis using Paired-End Tag sequencing (ChIA-PET) is a technique developed for large-scale, de novo analysis of higher-order chromatin structures. Cells are treated with formaldehyde to cross-link chromatin interactions, DNA segments bound by protein factors are enriched by chromatin immunoprecipitation, and interacting DNA fragments are then captured by proximity ligation. The Paired-End Tag (PET) strategy is applied to the construction of ChIA-PET libraries, which are sequenced by high-throughput next-generation sequencing technologies. Finally, raw PET sequences are subjected to bioinformatics analysis, resulting in a genome-wide map of binding sites and chromatin interactions mediated by the protein factor under study. This unit describes ChIA-PET for genome-wide analysis of chromatin interactions in mammalian cells, with the application of Roche/454 and Illumina sequencing technologies. Curr. Protoc. Mol. Biol. 89:21.15.1-21.15.25. \u00a9 2010 by John Wiley & Sons, Inc.", + "authors": [ + { + "first": "Melissa J.", + "last": "Fullwood", + "orcid": null + }, + { + "first": "Yuyuan", + "last": "Han", + "orcid": null + }, + { + "first": "Chia-Lin", + "last": "Wei", + "orcid": null + }, + { + "first": "Xiaoan", + "last": "Ruan", + "orcid": null + }, + { + "first": "Yijun", + "last": "Ruan", + "orcid": null + } + ], + "date": "2010-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 18, + "doctype": "reference-entry", + "doi": "10.1002/0471142727.mb2115s89", + "eissn": null, + "issn": null, + "issue": null, + "journal": "Current Protocols in Molecular Biology", + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": null, + "publisher": "Wiley-Blackwell", + "sherpa_romeo_id": null, + "title": "Chromatin Interaction Analysis Using Paired-End Tag Sequencing", + "volume": null +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/report_execution_of_targeted_experiments_to_inform_bison.json b/doc/sphinx/examples/darkarchive/report_execution_of_targeted_experiments_to_inform_bison.json new file mode 100644 index 000000000..9d82b104a --- /dev/null +++ b/doc/sphinx/examples/darkarchive/report_execution_of_targeted_experiments_to_inform_bison.json @@ -0,0 +1,34 @@ +{ + "abstract": "User filled in abstract", + "authors": [ + { + "first": "Maxim", + "last": "Gussev", + "orcid": null + } + ], + "date": "2019-02-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 19, + "doctype": "report", + "doi": "10.2172/1513377", + "eissn": null, + "issn": null, + "issue": null, + "journal": null, + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": null, + "publisher": null, + "sherpa_romeo_id": null, + "title": "Execution of Targeted Experiments to Inform BISON for ATF Materials: An Advanced Approach to Tube Burst Testing", + "volume": null +} \ No newline at end of file diff --git a/doc/sphinx/examples/darkarchive/thesis_blue_mining_planung_des_abbaus_von_manganknollen_in_der_tiefsee.json b/doc/sphinx/examples/darkarchive/thesis_blue_mining_planung_des_abbaus_von_manganknollen_in_der_tiefsee.json new file mode 100644 index 000000000..eb72a0c5f --- /dev/null +++ b/doc/sphinx/examples/darkarchive/thesis_blue_mining_planung_des_abbaus_von_manganknollen_in_der_tiefsee.json @@ -0,0 +1,34 @@ +{ + "abstract": "Please my theses about blue mining", + "authors": [ + { + "first": "Sebastian Ernst", + "last": "Volkmann", + "orcid": null + } + ], + "date": "2018-01-01", + "depositor": { + "first": "Gottfried", + "last": "Leibniz", + "orcid": "2543-2454-2345-234X", + "is_contributor": false + }, + "dissemin_id": 20, + "doctype": "thesis", + "doi": "10.18154/rwth-2018-230772", + "eissn": null, + "issn": null, + "issue": null, + "journal": "Dissertation", + "license": { + "name": "Creative Commons Attribution 4.0 International (CC BY 4.0)", + "uri": "https://creativecommons.org/licenses/by/4.0/", + "transmit_id": "cc_by-40" + }, + "page": "2018-", + "publisher": "RWTH Aachen University", + "sherpa_romeo_id": null, + "title": "Blue Mining\u2014Planung des Abbaus von Manganknollen in der Tiefsee", + "volume": "RWTH Aachen University" +} \ No newline at end of file From e59eb0de1bcefd16138a67162251adbb9691d787 Mon Sep 17 00:00:00 2001 From: Stefan Beck Date: Wed, 9 Oct 2019 10:07:53 +0200 Subject: [PATCH 05/11] Argument missing in function --- deposit/darkarchive/tests.py | 3 --- deposit/tests/test_protocol.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/deposit/darkarchive/tests.py b/deposit/darkarchive/tests.py index 52bd13571..b77b6d227 100644 --- a/deposit/darkarchive/tests.py +++ b/deposit/darkarchive/tests.py @@ -80,9 +80,6 @@ def test_write_dark_archive_examples(self, db, upload_data, user_leibniz): with open(f_name, 'w') as fout: json.dump(md, fout, indent=4) - @pytest.mark.xfail - def test_get_bound_form(self): - pass @pytest.mark.xfail def test_protocol_registered(self): diff --git a/deposit/tests/test_protocol.py b/deposit/tests/test_protocol.py index eec12c292..394f007c3 100644 --- a/deposit/tests/test_protocol.py +++ b/deposit/tests/test_protocol.py @@ -134,7 +134,7 @@ def test_get_bound_form(self, book_god_of_the_labyrinth, abstract_required, ddc, if embargo == 'required': data['embargo'] = '2019-10-10' - form = self.protocol.get_bound_form() + form = self.protocol.get_bound_form(data=data) if not form.is_valid(): print(form.errors) raise AssertionError("Form not valid") From d927ca853d9b4522a3a608d4fb2fae3300bcfe63 Mon Sep 17 00:00:00 2001 From: Stefan Beck Date: Wed, 20 Nov 2019 09:40:15 +0100 Subject: [PATCH 06/11] Double fixtures --- deposit/conftest.py | 48 ------------------------------------ deposit/darkarchive/tests.py | 2 +- 2 files changed, 1 insertion(+), 49 deletions(-) diff --git a/deposit/conftest.py b/deposit/conftest.py index 27c830e51..4533abb3d 100644 --- a/deposit/conftest.py +++ b/deposit/conftest.py @@ -87,18 +87,6 @@ def embargo(request): return request.param -@pytest.fixture -def dummy_journal(dummy_publisher): - """ - Empty Journal with FK to Publisher - """ - j = Journal.objects.create( - publisher=dummy_publisher, - ) - - return j - - @pytest.fixture def dummy_oairecord(dummy_paper, dummy_oaisource): """ @@ -147,42 +135,6 @@ def dummy_journal(dummy_publisher): return j -@pytest.fixture -def dummy_oairecord(dummy_paper, dummy_oaisource): - """ - Empty OaiRecord with FK to empty_paper and empty OaiSource - """ - o = OaiRecord.objects.create( - source=dummy_oaisource, - about=dummy_paper, - identifier='dummy', - ) - - return o - - -@pytest.fixture -def dummy_paper(): - """ - Just an empty paper - """ - p = Paper.objects.create( - pubdate='2019-10-08', - ) - - return p - - -@pytest.fixture -def dummy_publisher(): - """ - Empty Publisher - """ - p = Publisher.objects.create() - - return p - - @pytest.fixture def empty_user_preferences(db, user_isaac_newton): """ diff --git a/deposit/darkarchive/tests.py b/deposit/darkarchive/tests.py index b77b6d227..f8e6b08fa 100644 --- a/deposit/darkarchive/tests.py +++ b/deposit/darkarchive/tests.py @@ -87,7 +87,7 @@ def test_protocol_registered(self): @pytest.mark.parametrize('authors_list', [author_one, author_one + author_two]) - def test_get_authors_single_author(self, authors_list): + def test_get_authors(self, authors_list): """ Tests if authors are generated accordingly """ From 4437494f2289f1d97f6159ddfd54fd24ec7e2390 Mon Sep 17 00:00:00 2001 From: Stefan Beck Date: Wed, 20 Nov 2019 10:18:59 +0100 Subject: [PATCH 07/11] Add support for embargo --- deposit/darkarchive/protocol.py | 1 + deposit/darkarchive/tests.py | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/deposit/darkarchive/protocol.py b/deposit/darkarchive/protocol.py index 462ef29c6..0c45dfe21 100644 --- a/deposit/darkarchive/protocol.py +++ b/deposit/darkarchive/protocol.py @@ -93,6 +93,7 @@ def _get_metadata(self, form): 'doctype' : self.paper.doctype, 'doi' : self.publication.doi, 'eissn' : self._get_eissn(), + 'embargo' : form.cleaned_data.get('embargo', None), 'issn' : self._get_issn(), 'issue' : self.publication.issue, 'journal' : self.publication.full_journal_title(), diff --git a/deposit/darkarchive/tests.py b/deposit/darkarchive/tests.py index f8e6b08fa..771453138 100644 --- a/deposit/darkarchive/tests.py +++ b/deposit/darkarchive/tests.py @@ -60,6 +60,8 @@ def test_write_dark_archive_examples(self, db, upload_data, user_leibniz): else: data['abstract'] = upload_data['abstract'] + data['embargo'] = upload_data.get('embargo', None) + l = License.objects.get(uri="https://creativecommons.org/licenses/by/4.0/") lc = LicenseChooser.objects.create( license=l, @@ -168,7 +170,7 @@ def test_get_license(self, license_standard): assert l.get('transmit_id') == transmit_id - def test_get_metadata(self, upload_data, license_chooser, depositing_user): + def test_get_metadata(self, upload_data, license_chooser, embargo, depositing_user): """ Test if the metadata is correctly generated """ @@ -185,12 +187,15 @@ def test_get_metadata(self, upload_data, license_chooser, depositing_user): if license_chooser: data['license'] = license_chooser.pk + if embargo is not 'none': + data['embargo'] = upload_data.get('embargo', None) + form = self.protocol.form_class(data=data) form.is_valid() md = self.protocol._get_metadata(form) - md_fields = ['abstract', 'authors', 'date', 'depositor', 'doctype', 'doi', 'eissn', 'issn', 'issue', 'journal', 'page', 'publisher', 'title', 'volume', ] + md_fields = ['abstract', 'authors', 'date', 'depositor', 'doctype', 'doi', 'eissn', 'embargo', 'issn', 'issue', 'journal', 'page', 'publisher', 'title', 'volume', ] assert all(k in md for k in md_fields) == True From ca4b2051dffe9debd7b23f9319f4466ebabf3ed0 Mon Sep 17 00:00:00 2001 From: Stefan Beck Date: Wed, 20 Nov 2019 11:27:36 +0100 Subject: [PATCH 08/11] Add E-Mail --- deposit/darkarchive/forms.py | 18 ++++++++++++++++++ deposit/darkarchive/protocol.py | 5 +++++ deposit/darkarchive/tests.py | 22 ++++++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 deposit/darkarchive/forms.py diff --git a/deposit/darkarchive/forms.py b/deposit/darkarchive/forms.py new file mode 100644 index 000000000..3bd445a10 --- /dev/null +++ b/deposit/darkarchive/forms.py @@ -0,0 +1,18 @@ +from django import forms + +from django.utils.translation import ugettext_lazy as _ + +from deposit.forms import BaseMetadataForm + +class DarkArchiveForm(BaseMetadataForm): + """ + Form that extends BaseMetadataForm for extra fields needed by DarkArchive protocol + """ + + field_order = ['email', 'abstract', 'license'] + + email = forms.EmailField( + label=_('E-mail'), + required=True, + max_length=255, + ) diff --git a/deposit/darkarchive/protocol.py b/deposit/darkarchive/protocol.py index 0c45dfe21..6babee926 100644 --- a/deposit/darkarchive/protocol.py +++ b/deposit/darkarchive/protocol.py @@ -1,3 +1,4 @@ +from deposit.darkarchive.forms import DarkArchiveForm from deposit.protocol import RepositoryProtocol @@ -6,6 +7,9 @@ class DarkArchiveProtocol(RepositoryProtocol): A protocol that does does directly sends data to a repository, but to an intermediata database """ + # The class of the form the protocol uses + form_class = DarkArchiveForm + def __str__(self): """ Return human readable class name @@ -86,6 +90,7 @@ def _get_metadata(self, form): 'depositor' : { 'first' : self.user.first_name, 'last' : self.user.last_name, + 'email' : form.cleaned_data.get('email', None), 'orcid' : self._get_depositor_orcid(), 'is_contributor' : self.paper.is_owned_by(self.user, flexible=True), }, diff --git a/deposit/darkarchive/tests.py b/deposit/darkarchive/tests.py index 771453138..997e2f0ed 100644 --- a/deposit/darkarchive/tests.py +++ b/deposit/darkarchive/tests.py @@ -55,6 +55,8 @@ def test_write_dark_archive_examples(self, db, upload_data, user_leibniz): # Set form data data = dict() + data['email'] = user_leibniz.email + if upload_data['oairecord'].description is not None: data['abstract'] = upload_data['oairecord'].description else: @@ -105,6 +107,24 @@ def test_get_authors(self, authors_list): assert author['orcid'] == authors_list[idx]['orcid'] + def test_get_bound_form(self, book_god_of_the_labyrinth, empty_user_preferences, abstract_required, license_chooser): + self.protocol.paper = book_god_of_the_labyrinth + self.protocol.user = empty_user_preferences.user + data = { + 'paper_pk' : book_god_of_the_labyrinth.pk, + 'email' : 'spam@ham.co.uk', + } + if abstract_required: + data['abstract'] = 'Simple abstract' + if license_chooser: + data['license'] = license_chooser.pk + + form = self.protocol.get_bound_form(data=data) + if not form.is_valid(): + print(form.errors) + raise AssertionError("Form not valid") + + @pytest.mark.parametrize('eissn', [None, '2343-2345']) def test_get_eissn(self, dummy_oairecord, dummy_journal, eissn): """ @@ -190,6 +210,8 @@ def test_get_metadata(self, upload_data, license_chooser, embargo, depositing_us if embargo is not 'none': data['embargo'] = upload_data.get('embargo', None) + data['email'] = depositing_user.email + form = self.protocol.form_class(data=data) form.is_valid() From 00ae3f22c38a33d6c052875a972aa39e5398bc80 Mon Sep 17 00:00:00 2001 From: Stefan Beck Date: Wed, 20 Nov 2019 15:20:44 +0100 Subject: [PATCH 09/11] Retrieval per token --- conftest.py | 12 +++++++++ deposit/tests/conftest.py | 12 +-------- dissemin/urls.py | 3 +++ requirements.txt | 1 + upload/admin.py | 2 +- upload/migrations/0004_file_token.py | 19 +++++++++++++ upload/models.py | 18 ++++++++++++- upload/tests/test_views.py | 40 ++++++++++++++++++++++++++-- upload/tests/tests.py | 4 +++ upload/views.py | 29 ++++++++++++++++++++ 10 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 upload/migrations/0004_file_token.py create mode 100644 upload/tests/tests.py diff --git a/conftest.py b/conftest.py index b68a40685..1a78b1acf 100644 --- a/conftest.py +++ b/conftest.py @@ -13,6 +13,7 @@ from django.conf import settings from django.contrib.auth.models import User +from django.core.files.base import ContentFile from django.core.files.uploadedfile import InMemoryUploadedFile from django.core.management import call_command from django.urls import reverse @@ -30,6 +31,7 @@ from papers.models import Researcher from publishers.models import Journal from publishers.models import Publisher +from upload.models import UploadedPDF @pytest.fixture @@ -213,6 +215,16 @@ def blank_pdf(blank_pdf_path): pdf = f.read() return pdf +@pytest.fixture +def uploaded_pdf(user_leibniz, blank_pdf): + """ + A simple uploaded pdf of user leibniz. + """ + pdf = UploadedPDF.objects.create( + user=user_leibniz, + ) + pdf.file.save('spam.pdf', ContentFile(blank_pdf)) + return pdf """ Depending on the environment variable DISSEMIN_TEST_ALL_LANGAUAGES sets the languages to be tested. If not set, use english, otherwise all languages from settings.POSSIBLE_LANGUAGE_CODES diff --git a/deposit/tests/conftest.py b/deposit/tests/conftest.py index ebf713025..8d4173931 100644 --- a/deposit/tests/conftest.py +++ b/deposit/tests/conftest.py @@ -1,19 +1,9 @@ import pytest from deposit.models import DepositRecord -from upload.models import UploadedPDF -@pytest.fixture -def uploaded_pdf(user_leibniz): - """ - A simple uploaded pdf of user leibniz. The file does not exist. - """ - pdf = UploadedPDF.objects.create( - user=user_leibniz, - file='uploaded_pdf.pdf', - ) - return pdf + @pytest.fixture def deposit_record(request, db, book_god_of_the_labyrinth, authenticated_client, dummy_repository, uploaded_pdf): diff --git a/dissemin/urls.py b/dissemin/urls.py index 06ffcc5b2..3c66ddc2c 100644 --- a/dissemin/urls.py +++ b/dissemin/urls.py @@ -33,6 +33,8 @@ from django.views.i18n import JavaScriptCatalog import django_js_reverse.views +from upload.views import FileDownloadView + admin.autodiscover() try: @@ -81,6 +83,7 @@ def handler(request, *args, **kwargs): } urlpatterns = [ + path('file///', FileDownloadView.as_view(), name='file-download'), # Errors path('404-error', temp('404.html')), path('500-error', temp('500.html')), diff --git a/requirements.txt b/requirements.txt index 604293ca3..966c4ea9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -42,6 +42,7 @@ pillow psycopg2-binary pyasn1 pyopenssl>=0.14 +python2-secrets ; python_version < '3.6' reportlab requests[security] requests_mock diff --git a/upload/admin.py b/upload/admin.py index 14eb37379..183707f6e 100644 --- a/upload/admin.py +++ b/upload/admin.py @@ -25,6 +25,6 @@ class UploadedPDFAdmin(admin.ModelAdmin): - list_display = ('file', 'user', 'timestamp') + list_display = ('file', 'user', 'timestamp', 'token') admin.site.register(UploadedPDF, UploadedPDFAdmin) diff --git a/upload/migrations/0004_file_token.py b/upload/migrations/0004_file_token.py new file mode 100644 index 000000000..0d40ec1fd --- /dev/null +++ b/upload/migrations/0004_file_token.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.6 on 2019-11-20 12:11 + +from django.db import migrations, models +import upload.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('upload', '0003_verbose_name'), + ] + + operations = [ + migrations.AddField( + model_name='uploadedpdf', + name='token', + field=models.CharField(default=upload.models.new_urlsafe_token, max_length=64), + ), + ] diff --git a/upload/models.py b/upload/models.py index 2701782ef..169370476 100644 --- a/upload/models.py +++ b/upload/models.py @@ -18,16 +18,24 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # - +from secrets import token_urlsafe from django.contrib.auth.models import User from django.db import models +from django.urls import reverse MAX_ORIG_NAME_LENGTH = 1024 THUMBNAIL_MAX_HEIGHT = 297/2 THUMBNAIL_MAX_WIDTH = 210/2 +def new_urlsafe_token(): + """ + :returns: Token of length 64 + """ + return token_urlsafe(64)[:64] + + class UploadedPDF(models.Model): """ A PDF file, plus some useful info about who/when/how it was uploaded. @@ -41,6 +49,8 @@ class UploadedPDF(models.Model): orig_name = models.CharField(max_length=MAX_ORIG_NAME_LENGTH) #: Number of pages num_pages = models.IntegerField(default=0) + #: secret token to expose file by credential + token = models.CharField(max_length=64, default=new_urlsafe_token) #: The file itself file = models.FileField(upload_to='uploads/%Y/%m/%d') @@ -49,3 +59,9 @@ class UploadedPDF(models.Model): class Meta: verbose_name = 'Uploaded PDF' + + def get_object_url(self): + """ + Returns the url to object + """ + return reverse('file-download', args=[self.pk, self.token]) diff --git a/upload/tests/test_views.py b/upload/tests/test_views.py index a787a93c5..78bc870f0 100644 --- a/upload/tests/test_views.py +++ b/upload/tests/test_views.py @@ -19,17 +19,53 @@ # import os -import unittest import requests import requests_mock +import unittest + +import wand.image as image from django.conf import settings from django.contrib.auth.models import User from django.urls import reverse + from papers.tests.test_ajax import JsonRenderingTest from upload.models import THUMBNAIL_MAX_WIDTH from upload.views import make_thumbnail -import wand.image as image + + +class TestFileDownload(): + """ + Tests FileDownloadView + """ + + def test_object_not_found(self, uploaded_pdf, check_status): + """ + If no object is found, expect 404, independent of token + """ + pk = uploaded_pdf.pk + token = 'spam' + uploaded_pdf.delete() + check_status(404, 'file-download', args=[pk, token]) + + def test_success(self, uploaded_pdf, dissemin_base_client): + """ + If everything is fine, we expect 200 and a pdf + """ + response = dissemin_base_client.get(uploaded_pdf.get_object_url()) + assert response.status_code == 200 + assert response.as_attachment == True + assert response._headers['content-type'][0] == "Content-Type" + + + def test_wrong_token(self, uploaded_pdf, check_status): + """ + If token does not match, except 403 + """ + pk = uploaded_pdf.pk + token = 'spam' + check_status(403, 'file-download', args=[pk, token]) + class ThumbnailTest(unittest.TestCase): diff --git a/upload/tests/tests.py b/upload/tests/tests.py new file mode 100644 index 000000000..36d8024b2 --- /dev/null +++ b/upload/tests/tests.py @@ -0,0 +1,4 @@ +class TestUploadPDF(): + + def test_init(self, uploaded_pdf): + pass diff --git a/upload/views.py b/upload/views.py index ee4769826..a43343a44 100644 --- a/upload/views.py +++ b/upload/views.py @@ -22,6 +22,7 @@ from io import BytesIO +import os import requests from requests.packages.urllib3.exceptions import HTTPError from requests.packages.urllib3.exceptions import ReadTimeoutError @@ -29,8 +30,12 @@ from django.conf import settings from django.contrib.auth.decorators import user_passes_test from django.core.files.base import ContentFile +from django.http import FileResponse +from django.http import HttpResponseForbidden +from django.shortcuts import get_object_or_404 from django.utils.translation import ugettext as _ from django.views.decorators.http import require_POST +from django.views.generic import View from jsonview.decorators import json_view from papers.user import is_authenticated import PyPDF2 @@ -203,3 +208,27 @@ def handleUrlDownload(request): return response, 403 return response + +class FileDownloadView(View): + """ + View to get an uploaded file + """ + + def get(self, request, pk, token): + """ + :param pk: Primary key of object + :param token: Token that must coincident with objects token + :returns: HttpResponse + """ + pdf = get_object_or_404(UploadedPDF.objects, pk=pk) + + if pdf.token != token: + return HttpResponseForbidden(_("Access to this resource not permitted")) + + path = os.path.join(settings.MEDIA_ROOT, pdf.file.name) + + return FileResponse(open(path, 'rb'), as_attachment=True) + + + + From 6b4cafd58fbd070c743951d453a5452ad784cc71 Mon Sep 17 00:00:00 2001 From: Stefan Beck Date: Thu, 21 Nov 2019 09:14:21 +0100 Subject: [PATCH 10/11] Pass UploadedPDF object instead of path, to have access to named object --- conftest.py | 2 +- deposit/hal/protocol.py | 2 +- deposit/sword/protocol.py | 4 ++-- deposit/sword/tests.py | 12 ++++++------ deposit/tests/test_protocol.py | 18 +++++++++++++----- deposit/views.py | 4 +--- deposit/zenodo/protocol.py | 2 +- upload/models.py | 14 +++++++++++++- upload/tests/test_views.py | 2 +- upload/views.py | 4 ---- 10 files changed, 39 insertions(+), 25 deletions(-) diff --git a/conftest.py b/conftest.py index 1a78b1acf..fc6ffe594 100644 --- a/conftest.py +++ b/conftest.py @@ -223,7 +223,7 @@ def uploaded_pdf(user_leibniz, blank_pdf): pdf = UploadedPDF.objects.create( user=user_leibniz, ) - pdf.file.save('spam.pdf', ContentFile(blank_pdf)) + pdf.file.save('blank.pdf', ContentFile(blank_pdf)) return pdf """ diff --git a/deposit/hal/protocol.py b/deposit/hal/protocol.py index 1aeaa4549..3922bdf54 100644 --- a/deposit/hal/protocol.py +++ b/deposit/hal/protocol.py @@ -156,7 +156,7 @@ def get_bound_form(self, data): def create_zip(self, pdf, metadata): s = BytesIO() with ZipFile(s, 'w') as zipFile: - zipFile.write(pdf, "article.pdf") + zipFile.write(pdf.absolute_path, "article.pdf") zipFile.writestr("meta.xml", metadata) return s diff --git a/deposit/sword/protocol.py b/deposit/sword/protocol.py index 7dff5c2fe..2a57cb652 100644 --- a/deposit/sword/protocol.py +++ b/deposit/sword/protocol.py @@ -151,7 +151,7 @@ def _get_mets_container(pdf, mets): """ s = BytesIO() with ZipFile(s, 'w') as zip_file: - zip_file.write(pdf, 'document.pdf') + zip_file.write(pdf.absolute_path, 'document.pdf') zip_file.writestr('mets.xml', mets) return s @@ -262,7 +262,7 @@ def submit_deposit(self, pdf, form): """ Submit paper to the repository. This is a wrapper for the subclasses and calls some protocol specific functions. It creates the METS container and deposits. - :param pdf: Filename to dhe PDF file to submit + :param pdf: UploadedPDF object :param form: The form returned by get_form and completed by the user :returns: DepositResult object diff --git a/deposit/sword/tests.py b/deposit/sword/tests.py index 96825a0ce..e2da5a4c5 100644 --- a/deposit/sword/tests.py +++ b/deposit/sword/tests.py @@ -249,11 +249,11 @@ def test_get_mets_integration(self, mets_xsd, depositing_user, upload_data, ddc, mets_xsd.assertValid(etree.fromstring(bytes(mets_xml, encoding='utf-8'))) - def test_get_mets_container(self, blank_pdf_path, metadata_xml_mets): + def test_get_mets_container(self, uploaded_pdf, metadata_xml_mets): """ A test for creating a mets container """ - s = SWORDMETSProtocol._get_mets_container(blank_pdf_path, metadata_xml_mets) + s = SWORDMETSProtocol._get_mets_container(uploaded_pdf, metadata_xml_mets) with ZipFile(s, 'r') as zip_file: files = zip_file.namelist() for filename in ['mets.xml', 'document.pdf']: @@ -296,7 +296,7 @@ def test_get_xml_dissemin_metadata(self, db, monkeypatch_paper_is_owned, dissemi @responses.activate - def test_submit_deposit(self, blank_pdf_path, monkeypatch, monkeypatch_metadata_creation, monkeypatch_get_deposit_result): + def test_submit_deposit(self, uploaded_pdf, monkeypatch, monkeypatch_metadata_creation, monkeypatch_get_deposit_result): """ A test for submit deposit. """ @@ -308,7 +308,7 @@ def test_submit_deposit(self, blank_pdf_path, monkeypatch, monkeypatch_metadata_ # Monkeypatch _add_embargo_date_to_deposit monkeypatch.setattr(self.protocol, '_add_embargo_date_to_deposit_result', lambda x, y: x) - assert isinstance(self.protocol.submit_deposit(blank_pdf_path, None), DepositResult) + assert isinstance(self.protocol.submit_deposit(uploaded_pdf, None), DepositResult) headers = responses.calls[0].request.headers expected_headers = { 'Content-Type': 'application/zip', @@ -320,14 +320,14 @@ def test_submit_deposit(self, blank_pdf_path, monkeypatch, monkeypatch_metadata_ @responses.activate - def test_submit_deposit_server_error(self, blank_pdf_path, monkeypatch_metadata_creation): + def test_submit_deposit_server_error(self, uploaded_pdf, monkeypatch_metadata_creation): """ A test where the repository is not available. Should raise ``DepositError`` """ responses.add(responses.POST, self.protocol.repository.endpoint, status=401) with pytest.raises(DepositError): - self.protocol.submit_deposit(blank_pdf_path, None) + self.protocol.submit_deposit(uploaded_pdf, None) @pytest.mark.parametrize('username,password', userdata) diff --git a/deposit/tests/test_protocol.py b/deposit/tests/test_protocol.py index 394f007c3..4395de2f6 100644 --- a/deposit/tests/test_protocol.py +++ b/deposit/tests/test_protocol.py @@ -33,15 +33,17 @@ from deposit.protocol import DepositResult from deposit.protocol import RepositoryProtocol from deposit.registry import protocol_registry +from django.conf import settings from django.contrib.auth.models import User from django.core.files.uploadedfile import InMemoryUploadedFile +from django.core.files.base import ContentFile from django.forms import Form -from django.test.utils import override_settings from django.urls import reverse from papers.models import OaiSource from papers.models import OaiRecord from papers.models import Paper from deposit.tasks import refresh_deposit_statuses +from upload.models import UploadedPDF class MetaTestProtocol(): @@ -442,7 +444,6 @@ class ProtocolTest(django.test.TestCase): def __init__(self, *args, **kwargs): super(ProtocolTest, self).__init__(*args, **kwargs) - @override_settings(MEDIA_ROOT='mediatest/') def setUp(self): if type(self) is ProtocolTest: raise unittest.SkipTest("Base test") @@ -454,8 +455,15 @@ def setUp(self): self.username = 'mydepositinguser' self.password = 'supersecret' self.user = User.objects.create_user(username=self.username, email="my@email.com", password=self.password) - self.testdir = os.path.dirname(os.path.abspath(__file__)) - self.pdfpath = os.path.join(self.testdir, 'data/blank.pdf') + path = os.path.join(settings.BASE_DIR, 'upload', 'tests', 'data', 'blank.pdf') + with open(path, 'rb') as f: + blank_pdf = f.read() + + self.pdf = UploadedPDF.objects.create( + user=self.user, + ) + self.pdf.file.save('blank.pdf', ContentFile(blank_pdf)) + def setUpForProtocol(self, protocol_class, repository): @@ -522,7 +530,7 @@ def deposit(self, paper, dry_run=False, **form_fields): if not form.is_valid(): print(form.errors) self.assertTrue(form.is_valid()) - pdf = self.pdfpath + pdf = self.pdf deposit_result = self.proto.submit_deposit_wrapper(pdf, form, dry_run=dry_run) self.assertIsInstance(deposit_result, DepositResult) diff --git a/deposit/views.py b/deposit/views.py index 41bde84dd..636ff13a0 100644 --- a/deposit/views.py +++ b/deposit/views.py @@ -19,7 +19,6 @@ -import os import logging from datetime import date @@ -232,7 +231,6 @@ def submitDeposit(request, pk): return context, 400 # Submit the paper to the repository - path = os.path.join(settings.MEDIA_ROOT, pdf.file.name) # Create initial record d = DepositRecord( @@ -243,7 +241,7 @@ def submitDeposit(request, pk): file=pdf) d.save() - submitResult = protocol.submit_deposit_wrapper(path, repositoryForm) + submitResult = protocol.submit_deposit_wrapper(pdf, repositoryForm) d.request = submitResult.logs if submitResult.status == 'failed': diff --git a/deposit/zenodo/protocol.py b/deposit/zenodo/protocol.py index 8354f129f..3a14059b2 100644 --- a/deposit/zenodo/protocol.py +++ b/deposit/zenodo/protocol.py @@ -151,7 +151,7 @@ def submit_deposit(self, pdf, form, dry_run=False): # Uploading the PDF self.log("### Uploading the PDF") data = {'name': 'article.pdf'} - files = {'file': open(pdf, 'rb')} + files = {'file': open(pdf.absolute_path, 'rb')} r = requests.post( ( self.api_url + "/%s/files?access_token=%s" % diff --git a/upload/models.py b/upload/models.py index 169370476..7bf55319b 100644 --- a/upload/models.py +++ b/upload/models.py @@ -18,8 +18,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # +import os + from secrets import token_urlsafe +from django.conf import settings from django.contrib.auth.models import User from django.db import models from django.urls import reverse @@ -60,8 +63,17 @@ class UploadedPDF(models.Model): class Meta: verbose_name = 'Uploaded PDF' - def get_object_url(self): + def get_absolute_url(self): """ Returns the url to object + :returns: object url """ return reverse('file-download', args=[self.pk, self.token]) + + @property + def absolute_path(self): + """ + Returns the full path to the file as property + :returns: full path to file + """ + return os.path.join(settings.MEDIA_ROOT, self.file.name) diff --git a/upload/tests/test_views.py b/upload/tests/test_views.py index 78bc870f0..bebedd439 100644 --- a/upload/tests/test_views.py +++ b/upload/tests/test_views.py @@ -52,7 +52,7 @@ def test_success(self, uploaded_pdf, dissemin_base_client): """ If everything is fine, we expect 200 and a pdf """ - response = dissemin_base_client.get(uploaded_pdf.get_object_url()) + response = dissemin_base_client.get(uploaded_pdf.get_absolute_url()) assert response.status_code == 200 assert response.as_attachment == True assert response._headers['content-type'][0] == "Content-Type" diff --git a/upload/views.py b/upload/views.py index a43343a44..1720312c5 100644 --- a/upload/views.py +++ b/upload/views.py @@ -228,7 +228,3 @@ def get(self, request, pk, token): path = os.path.join(settings.MEDIA_ROOT, pdf.file.name) return FileResponse(open(path, 'rb'), as_attachment=True) - - - - From d49265bbefaac178780ba530fd611f90ae1b862a Mon Sep 17 00:00:00 2001 From: Stefan Beck Date: Thu, 21 Nov 2019 10:19:32 +0100 Subject: [PATCH 11/11] Add file url to metadata + better tests --- deposit/darkarchive/protocol.py | 19 +++++++++++-- deposit/darkarchive/tests.py | 49 +++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/deposit/darkarchive/protocol.py b/deposit/darkarchive/protocol.py index 6babee926..85273065a 100644 --- a/deposit/darkarchive/protocol.py +++ b/deposit/darkarchive/protocol.py @@ -1,3 +1,5 @@ +from django.contrib.sites.models import Site + from deposit.darkarchive.forms import DarkArchiveForm from deposit.protocol import RepositoryProtocol @@ -56,6 +58,18 @@ def _get_eissn(self): return self.publication.journal.essn + def _get_embargo(self, form): + """ + Takes the embargo date from the form and returns it as isoformatted string or None + :param form: django form (BaseMetadataForm) + :returns: string + """ + embargo = form.cleaned_data.get('embargo', None) + if embargo is not None: + embargo = embargo.isoformat() + return embargo + + def _get_issn(self): """ Returns the issn if available or `None` @@ -77,7 +91,7 @@ def _get_license(self, license_chooser): return d - def _get_metadata(self, form): + def _get_metadata(self, form, pdf): """ Creates metadata ready to be converted into JSON. Mainly we create a dictionary with some types as content that serialize well into JSON @@ -98,7 +112,8 @@ def _get_metadata(self, form): 'doctype' : self.paper.doctype, 'doi' : self.publication.doi, 'eissn' : self._get_eissn(), - 'embargo' : form.cleaned_data.get('embargo', None), + 'embargo' : self._get_embargo(form), + 'file_url' : 'https://{}{}'.format(Site.objects.get_current(), pdf.get_absolute_url()), 'issn' : self._get_issn(), 'issue' : self.publication.issue, 'journal' : self.publication.full_journal_title(), diff --git a/deposit/darkarchive/tests.py b/deposit/darkarchive/tests.py index 997e2f0ed..633f7b430 100644 --- a/deposit/darkarchive/tests.py +++ b/deposit/darkarchive/tests.py @@ -2,6 +2,7 @@ import os import pytest +from django import forms from deposit.models import License from deposit.models import LicenseChooser @@ -37,7 +38,7 @@ class TestDarkArchiveProtocol(MetaTestProtocol): """ @pytest.mark.write_darkarchive_examples - def test_write_dark_archive_examples(self, db, upload_data, user_leibniz): + def test_write_dark_archive_examples(self, db, upload_data, user_leibniz, uploaded_pdf): """ This is not really a test. It just outputs metadata examples that the protocol generates. Ususally this test is omitted, you can run it explicetely with "-m write_darkarchive_examples". @@ -73,10 +74,10 @@ def test_write_dark_archive_examples(self, db, upload_data, user_leibniz): licenses = LicenseChooser.objects.by_repository(repository=self.protocol.repository) data['license'] = lc.pk - form = self.protocol.form_class(licenses=licenses, data=data) + form = self.protocol.form_class(licenses=licenses, embargo='optional', data=data) form.is_valid() - md = self.protocol._get_metadata(form) + md = self.protocol._get_metadata(form, uploaded_pdf) f_path = os.path.join(BASE_DIR, 'doc', 'sphinx', 'examples', 'darkarchive') f_name = os.path.join(f_path, upload_data['load_name'] + '.json') @@ -148,6 +149,24 @@ def test_get_eissn_no_journal(self, dummy_oairecord): assert self.protocol._get_eissn() == None + @pytest.mark.parametrize('value', [None, '2019-01-01']) + def test_get_embargo(self, value): + class test_form(forms.Form): + embargo = forms.DateField( + required=False + ) + + data = { + 'embargo' : value + } + form = test_form(data=data) + valid = form.is_valid() + if not valid: + raise AssertionError("Form not valid") + assert self.protocol._get_embargo(form) == value + + + @pytest.mark.parametrize('issn', [None, '2343-2345']) def test_get_issn(self, dummy_oairecord, dummy_journal, issn): """ @@ -190,7 +209,7 @@ def test_get_license(self, license_standard): assert l.get('transmit_id') == transmit_id - def test_get_metadata(self, upload_data, license_chooser, embargo, depositing_user): + def test_get_metadata(self, upload_data, uploaded_pdf, license_chooser, embargo, depositing_user): """ Test if the metadata is correctly generated """ @@ -204,23 +223,37 @@ def test_get_metadata(self, upload_data, license_chooser, embargo, depositing_us else: data['abstract'] = upload_data['abstract'] + licenses = None if license_chooser: data['license'] = license_chooser.pk + licenses = LicenseChooser.objects.by_repository(repository=self.protocol.repository) if embargo is not 'none': data['embargo'] = upload_data.get('embargo', None) + if embargo == 'required': + data['embargo'] = '2019-10-10' data['email'] = depositing_user.email - form = self.protocol.form_class(data=data) - form.is_valid() + form = self.protocol.form_class(licenses=licenses, embargo=embargo, data=data) + valid = form.is_valid() + if not valid: + raise AssertionError("Form not valid") - md = self.protocol._get_metadata(form) + md = self.protocol._get_metadata(form, uploaded_pdf) - md_fields = ['abstract', 'authors', 'date', 'depositor', 'doctype', 'doi', 'eissn', 'embargo', 'issn', 'issue', 'journal', 'page', 'publisher', 'title', 'volume', ] + md_fields = ['abstract', 'authors', 'date', 'depositor', 'doctype', 'doi', 'eissn', 'embargo', 'file_url', 'issn', 'issue', 'journal', 'page', 'publisher', 'title', 'volume', ] assert all(k in md for k in md_fields) == True + # Assertion for some fields + if license_chooser: + assert md.get('license') != None + if embargo is 'optional': + assert md.get('embargo') == upload_data.get('embargo') + elif embargo is 'required': + assert md.get('embargo') == '2019-10-10' + def test_str(self): """