diff --git a/.gitignore b/.gitignore index 5e73cfd20a..087f3ab5a4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,11 @@ src* build/* dist/* tmp/* + *.zip +!doajtest/unit/resources/*.zip +!doajtest/preservation_upload_test_package/*.zip + scratch.py .coverage htmlcov/* diff --git a/cms/assets/img/sponsors/Degruyter.svg b/cms/assets/img/sponsors/Degruyter.svg new file mode 100644 index 0000000000..4fcff995f5 --- /dev/null +++ b/cms/assets/img/sponsors/Degruyter.svg @@ -0,0 +1,3 @@ + + + diff --git a/cms/data/nav.yml b/cms/data/nav.yml index ddd6b7cf7e..e8ec84fc22 100644 --- a/cms/data/nav.yml +++ b/cms/data/nav.yml @@ -60,7 +60,7 @@ entries: route: doaj.support # ~~->Support:WebRoute~~ - label: Publishers route: doaj.publisher_supporters # ~~->PublisherSupporters:WebRoute~~ - - label: Supporters + - label: Institutional and library supporters route: doaj.supporters # ~~->Supporters:WebRoute~~ - id: apply label: Apply diff --git a/cms/data/notifications.yml b/cms/data/notifications.yml index 1e417e9b71..d4b9906822 100644 --- a/cms/data/notifications.yml +++ b/cms/data/notifications.yml @@ -1,7 +1,7 @@ # ~~Notifications:Data~~ application:assed:assigned:notify: long: | - An application, or update request for the journal **{journal_title}** has been assigned to you by the Editor of your group **{group_name}**. Please start work on this within 10 days. + An application for the journal **{journal_title}** has been assigned to you by the Editor of your group **{group_name}**. Please start work on this within 10 days. short: New application ({issns}) assigned to you @@ -146,4 +146,4 @@ journal:assed:discontinuing_soon:notify: long: | Journal "{title}" (id: {id}) will discontinue in {days} days. short: - Journal discontinuing \ No newline at end of file + Journal discontinuing diff --git a/cms/data/sponsors.yml b/cms/data/sponsors.yml index 379c51cf69..62f4700a39 100644 --- a/cms/data/sponsors.yml +++ b/cms/data/sponsors.yml @@ -1,17 +1,14 @@ # List of sponsors separated by tiers (premier, sustaining, basic) # ~~Sponsors:Data~~ -gold: - name: Royal Society of Chemistry url: https://www.rsc.org/ logo: rsc.png - + - name: Georg Thieme Verlag KG url: https://www.thieme.com/ logo: thieme.svg - -silver: - name: AOSIS url: https://aosis.co.za/ logo: aosis.png @@ -19,7 +16,7 @@ silver: - name: Cappelen Damm Akademisk url: https://www.cappelendammundervisning.no/ logo: cda.jpg - + - name: Copernicus Publications url: https://publications.copernicus.org logo: copernicus.svg @@ -31,7 +28,7 @@ silver: - name: Frontiers url: https://www.frontiersin.org/ logo: frontiers.svg - + - name: Knowledge E url: https://knowledgee.com/ logo: knowledgee.png @@ -43,7 +40,7 @@ silver: - name: OA.Works url: https://oa.works/ logo: oaworks.png - + - name: SAGE Publishing url: https://www.sagepublications.com/ logo: sage.svg @@ -51,21 +48,19 @@ silver: - name: Taylor & Francis Group url: https://www.taylorandfrancisgroup.com/ logo: tf.svg - + - name: John Wiley and Sons LTD url: https://www.wiley.com/en-us logo: Wiley_Wordmark_black.png - -bronze: - name: American Chemical Society url: https://pubs.acs.org/ logo: acs.jpg - + - name: American Psychological Association url: https://www.apa.org/pubs logo: apa.png - + - name: Cambridge University Press url: https://www.cambridge.org/ logo: cambridge.svg @@ -74,6 +69,10 @@ bronze: url: https://www.digital-science.com/ logo: ds.svg +- name: De Gruyter + url: https://www.degruyter.com/ + logo: Degruyter.svg + - name: eLife Sciences Publications url: https://elifesciences.org/ logo: elife.svg @@ -81,7 +80,7 @@ bronze: - name: Elsevier url: https://www.elsevier.com/ logo: elsevier.svg - + - name: Emerald Publishing url: https://emeraldpublishing.com/ logo: emerald.svg @@ -89,15 +88,15 @@ bronze: - name: IEEE url: https://www.ieee.org/ logo: ieee.png - + - name: Institute of Physics url: https://www.iop.org/ logo: iop.jpg - + - name: International Union for Conservation of Nature url: https://iucn.org/ logo: IUCN.svg - + - name: JMIR Publications url: https://jmirpublications.com/ logo: jmir.svg @@ -129,12 +128,11 @@ bronze: - name: SciFree url: https://scifree.se/ logo: scifree.svg - + - name: The IET url: https://www.theiet.org/ logo: iet.svg - name: Ubiquity Press url: https://www.ubiquitypress.com/ - logo: ubiquity.svg - + logo: ubiquity.svg diff --git a/cms/pages/about/at-20.md b/cms/pages/about/at-20.md index 1678adfe2a..918d1214b1 100644 --- a/cms/pages/about/at-20.md +++ b/cms/pages/about/at-20.md @@ -31,15 +31,16 @@ There is also an opportunity for you to [support DOAJ during its 20th year](/at- - Event Time: 13:00 UTC - Duration: 90 mins - {% include "includes/svg/at-20/theme_global.svg" %} - - **[Registration is open](https://us02web.zoom.us/webinar/register/WN_fu42oi59S7GZ366rjyAUGg#/registration)** + - **[Recording is available](https://www.youtube.com/watch?v=TRjtc-7tg8w)** - Name: _DOAJ at 20: Global_ - Date: _28th September 2023_ - - Event Time: 13:00 UTC ([Check the event time](https://www.timeanddate.com/worldclock/fixedtime.html?iso=20230928T13&ah=1&am=30) where you are.) + - Event Time: 13:00 UTC - Duration: 2 hours - {% include "includes/svg/at-20/theme_trusted.svg" %} + - **[Registration is open](https://us02web.zoom.us/webinar/register/WN_sePTjc09QbWHUDhgmZ28-A#/registration)** - Name: _DOAJ at 20: Trusted_ - Date: _7th December 2023_ - - Event Time: to be confirmed + - Event Time: 14:00 UTC ([Check the event time](https://www.timeanddate.com/worldclock/fixedtime.html?msg=DOAJ+at+20%3A+Trusted&iso=20231207T14&p1=1440&ah=1&am=30) where you are.) - Duration: 90 mins ## Open @@ -48,11 +49,11 @@ Our first of three events marking our 20th anniversary took place on the 15th Ju ## Global -Our second event will be around the theme Global, where we will have eight lighting talks from speakers from around the world. Our moderator and DOAJ Ambassador, Ivonne Lujano, will introduce speakers and manage two Q&As, where the audience can ask our speakers questions. More information about the event and all the speakers can be found on the [registration page](https://us02web.zoom.us/webinar/register/WN_fu42oi59S7GZ366rjyAUGg#/registration). +Our second event was around the theme Global and took place on the 28th September 2023. The event consisted of eight lighting talks from speakers from around the world. Our moderator and DOAJ Ambassador, Ivonne Lujano, introduced speakers and managed two Q&As, where the audience could ask our speakers questions. A recording of the event is [available on YouTube](https://www.youtube.com/watch?v=TRjtc-7tg8w). ## Trusted -Our third and last DOAJ at 20 event will be around the theme Trusted. More information about this event will be available later in the year. +Our third and last DOAJ at 20 event will be around the theme Trusted, where our four speakers, Judith Barnsby, Ivan Oransky, Ixchel Faniel and Dr Haseeb Irfanullah will discuss what trust and mistrust means in scholarly communications. The event will be moderated by Joy Owango, Founding Director of TCC Africa, and there will be a Q&A where the audience can ask questions. [Registration is open](https://us02web.zoom.us/webinar/register/WN_sePTjc09QbWHUDhgmZ28-A#/registration) for this event, and the event is free to attend. ## Timeline diff --git a/cms/pages/apply/guide.md b/cms/pages/apply/guide.md index 14bc161758..6f037f0ad0 100644 --- a/cms/pages/apply/guide.md +++ b/cms/pages/apply/guide.md @@ -7,7 +7,7 @@ sticky_sidenav: true featuremap: ~~GuideToApplying:Fragment~~ --- -Before you start the application process, you will be asked to log in or register. You will be able to save your progress and review all your answers before you submit them. A [PDF version of the application form](/static/doaj/docs/2023-07-04-DOAJQuestions.pdf) is available for reference only. +Before you start the application process, you will be asked to log in or register. You can save your progress and review all your answers before you submit them. A [PDF version of the application form](/static/doaj/docs/2023-07-04-DOAJQuestions.pdf) is available for reference only. ## Basic criteria for inclusion @@ -17,11 +17,11 @@ Open access journals published in any language may apply. Journals should adhere - The journal must be actively publishing scholarly research - Any research subject area - - Publish at least 5 research articles per year + - Publish at least five research articles per year - Primary target audience of researchers or practitioners - Newly launched journals - - Before applying to DOAJ, a new or flipped journal must demonstrate a publishing history of more than one year, or have published at least 10 open access research articles. + - Before applying to DOAJ, a new or flipped journal must demonstrate a publishing history of more than one year or have published at least ten open access research articles. --- @@ -44,10 +44,10 @@ Open access journals published in any language may apply. Journals should adhere - one unique URL per article - HTML or PDF, as a minimum - Journals that include intrusive advertising will not be accepted. See best practice [recommendations](https://www.doaj.org/apply/transparency) for advertising. -- DOAJ does not approve of the use of Impact Factors or ranking metrics. However, journals are allowed to display the Journal Impact Factor calculated by Clarivate and metrics from Scopus. Journals must not display any reference (images, links, logos) to Impact Factors or ranking metrics from any other service. -- The website does not need to be in English. If the site is available in multiple languages the information provided must be the same in all languages. +- DOAJ does not approve of using Impact Factors or ranking metrics. However, journals may display the Journal Impact Factor calculated by Clarivate and metrics from Scopus. Journals must not display any reference (images, links, logos) to Impact Factors or ranking metrics from any other service. +- The website does not need to be in English. If the site is available in multiple languages, the information provided must be the same in all languages. -The following information must be available online, and easily accessible from the journal homepage: +The following information must be available online and easily accessible from the journal homepage: - Open access policy - Aims and scope @@ -57,26 +57,26 @@ The following information must be available online, and easily accessible from t - Licensing terms - Copyright terms - Author charges - - If a journal doesn’t have any charges then this must be stated + - If a journal doesn’t have any charges, then this must be stated - Must include all fees that may be charged to the author, from submission to publication, including: - submission fees - editorial processing charges - article processing charges (APCs) - page charges - colour charges - - Any fee waiver must be clearly specified with the conditions of waiver e.g. amount, time period. + - Any fee waiver must be clearly specified with the conditions of waiver, e.g. amount, time period. - If there are charges for withdrawing the article after submission, they cannot exceed the author charges. - Contact details - Include the name of a contact person and the journal's dedicated email address. - - The country in the application and on the journal website must be the country where the publisher is registered and carries out its business activities. + - The country in the application and on the journal website must be where the publisher is registered and carries out its business activities. --- ### ISSN -- A journal must have at least one ISSN (International Standard Serial Number) which is registered and confirmed at [issn.org](https://portal.issn.org/). +- A journal must have at least one ISSN (International Standard Serial Number) that is registered and confirmed at [issn.org](https://portal.issn.org/). - The ISSN(s) must be displayed on the website. -- The name of the journal in the application and on the website must match what is shown at [issn.org](https://portal.issn.org/). +- The journal's name in the application and on the website must match what is shown at [issn.org](https://portal.issn.org/). --- @@ -88,28 +88,42 @@ The following information must be available online, and easily accessible from t - Journals that display a list of reviewers must include their names and affiliations. - The editorial board for the journal should consist of at least five editors with appropriate qualifications and expertise. It is recommended that board members should not all come from the same institution. - All articles must pass through a quality control system (peer review) before publication. - - The type and details of the peer review process must be stated clearly on the website. + - The type and details of the peer review process must be stated clearly on the website, including the process for special issues, if applicable. - At least two independent reviewers should review each article. -- Use of a plagiarism checking service is highly recommended, but not required for inclusion in DOAJ. +- Use of a plagiarism checking service is highly recommended but not required for inclusion in DOAJ. - Endogeny should be minimised. - - The proportion of published research papers where at least one of the authors is an editor, editorial board member or reviewer must not exceed 25% based on either of the latest two issues. + - The proportion of published research papers where at least one of the authors is an editor, editorial board member, or reviewer must not exceed 25% in either of the latest two issues. + +--- + +### Special issues + +Journals that publish special issues or other content curated by guest editors must adhere to these additional criteria: + +- The Editor-in-Chief must be responsible for the content of the entire journal, including all special issues, which must fall within the scope of the journal +- Special issue articles must have the same editorial oversight as regular papers, including external peer review, and be clearly labelled +- Journals must ensure that guest editors’ credentials are checked and approved +- The Editor-in-Chief or dedicated board members must oversee the guest editors +- Papers submitted to a special issue by the guest editor(s) must be handled under an independent review process and make up no more than 25% of the issue's total + +DOAJ will not accept a journal if all content in the last year/volume is published as special issues. --- ### Licensing - The licensing terms for use and re-use of the published content must be clearly stated on the website. -- DOAJ recommends the use of [Creative Commons](https://creativecommons.org/) licenses for this purpose. +- DOAJ recommends using [Creative Commons](https://creativecommons.org/) licenses for this purpose. - If Creative Commons licensing is not used, similar terms and conditions should be applied. - Extra care must be taken to state these terms clearly. -- It is recommended that licensing information is displayed or embedded in full text articles, but this is not required for inclusion in DOAJ. +- It is recommended that licensing information is displayed or embedded in full-text articles, but this is not required for inclusion in DOAJ. - [More about licensing](/apply/copyright-and-licensing/) --- ### Copyright -- The copyright terms applied to the published content must be clearly stated and separate from the copyright terms applied to the website. +- The copyright terms applied to the published content must be clearly stated and separate from those applied to the website. - Copyright terms must not contradict the licensing terms or the terms of the open access policy. - “All rights reserved” is never appropriate for open access content. - [More about copyright](/apply/copyright-and-licensing/) @@ -122,15 +136,15 @@ The following information must be available online, and easily accessible from t - ### Arts and humanities journals - For these disciplines ([pages 10-16 only](http://uis.unesco.org/sites/default/files/documents/international-standard-classification-of-education-fields-of-education-and-training-2013-detailed-field-descriptions-2015-en.pdf)), DOAJ can accept journals that undertake editorial review, rather than peer review. Editorial review must be handled with a minimum of two editors. - ### Clinical case reports journals - - DOAJ only considers a case report as research if it includes the retrospective analysis of three or more clinical cases or a literature review. The journal must publish at least five articles per year that meet this definition. + - DOAJ only considers a case report research if it includes the retrospective analysis of three or more clinical cases or a literature review. The journal must publish at least five articles per year that meet this definition. - ### Conference proceedings journals - - For DOAJ to include journals that are dedicated to publishing conference proceedings, the journal must have an ISSN and a permanent editorial or advisory board providing editorial oversight. Papers from each published conference must be peer-reviewed according to DOAJ criteria. Full text of all conference papers must be available. Individual conference proceedings will not be indexed. + - For DOAJ to include journals dedicated to publishing conference proceedings, the journal must have an ISSN and a permanent editorial or advisory board providing editorial oversight. Papers from each published conference must be peer-reviewed according to DOAJ criteria. Full text of all conference papers must be available. Individual conference proceedings will not be indexed. - ### Data journals - DOAJ will accept journals publishing research articles about data or datasets, but not journals that simply link to datasets or announce their availability. - ### Overlay journals - - DOAJ will accept journals that select and peer-review articles that are hosted on a preprint server or other site. + - DOAJ will accept journals that select and peer-review articles hosted on a preprint server or other site. - ### Student-run journals - - If a journal is run by a student body, there must be an advisory board for the journal where at least two members have a PhD or equivalent qualification. + - If a student body runs a journal, there must be an advisory board for the journal where at least two members have a PhD or equivalent qualification. - ### Flipped journals - Where a journal was previously published as a subscription or hybrid journal and has now flipped to a fully open access model, this information must be clearly displayed: - The date of the change to fully open access @@ -149,14 +163,14 @@ The following information must be available online, and easily accessible from t 1. Your application is successfully submitted when you see a _Thank You_ screen and receive a confirmation email. - If you do not receive that email, contact us. -2. Your application will be reviewed by an editor. You may receive an email alerting you when this process has started. -3. The editor may need to contact you as part of the review process. Please answer any questions they send you. This helps us process your application correctly. Be aware that we will reject an application if, after one month, we haven't received answers to our questions. +2. An editor will review your application. You may receive an email alerting you when this process has started. +3. The editor may need to contact you during the review process. Please answer any questions they send you. This helps us process your application correctly. Be aware that we will reject an application if, after one month, we haven't received answers to our questions. 4. The editor reviewing your application may be a volunteer and will not have a doaj.org email address. Check your spam folder frequently just in case the emails have ended up there. 5. If your application is accepted, you will receive an email to confirm this. ### Time from submission to decision -The time from submission to decision varies greatly and is dependent on the responsiveness of the journal contact and/or applicant. Generally a decision is reached within three months. +The time from submission to decision varies greatly and depends on the responsiveness of the journal contact and/or applicant. Generally, a decision is reached within three months. We cannot provide a status update for applications which are less than three months old. @@ -164,11 +178,11 @@ We cannot provide a status update for applications which are less than three mon ## If your application is rejected -You will receive an email giving the reasons for rejection of the application. Unless otherwise advised by DOAJ staff, you may not submit another application for the same journal until 6 months after the date of the notification of rejection. +You will receive an email giving the reasons for rejection of the application. Unless otherwise advised by DOAJ staff, you may not submit another application for the same journal until six months after the date of the notification of rejection. -If your application was rejected due to an unconfirmed ISSN, we may be able to reopen the application if the ISSN is confirmed within 3 months of the DOAJ rejection. Send an email to the [DOAJ Helpdesk](mailto:helpdesk@doaj.org). +If your application was rejected due to an unconfirmed ISSN, we may be able to reopen the application if the ISSN is confirmed within three months of the DOAJ rejection. Email the [DOAJ Helpdesk](mailto:helpdesk@doaj.org). -You are responsible for providing accurate information when submitting an application. Applications that contain information that is inaccurate or wrong, or that have answers missing, are automatically rejected. +You are responsible for providing accurate information when you submit an application. Applications that contain information that is inaccurate or wrong or that have answers missing are automatically rejected. DOAJ only accepts journals that follow good publishing practice. If our review finds a publisher is not adhering to best practice, has provided information that is untrue or misleading, or has questionable publishing practices, DOAJ will not allow further applications from that publisher for a period of up to three years. @@ -176,13 +190,13 @@ Journals or publishers that have had multiple exclusions totalling six years or Journals already accepted into DOAJ may be removed if they are found to be no longer adhering to DOAJ criteria or publishing best practice. -Please ensure that, if you do apply again after the exclusion period, the journal meets the DOAJ criteria for inclusion and any necessary changes have been made to the journal website or policies. +Please ensure that, if you do apply again after the exclusion period, the journal meets the DOAJ criteria for inclusion and that any necessary changes have been made to the journal website or policies. --- ## Appeals -You may submit an appeal if your journal has been removed from DOAJ, or your application has been rejected and no new application is allowed for at least a year. Send an email to the [DOAJ Appeals Committee](mailto:appeals@doaj.org). +You may submit an appeal if your journal has been removed from DOAJ or your application has been rejected and no new application is allowed for at least a year. Email the [DOAJ Appeals Committee](mailto:appeals@doaj.org). The appeal should contain the following information: @@ -192,7 +206,7 @@ The appeal should contain the following information: - Evidence for the DOAJ Appeals Committee to consider - The original notification of rejection/removal -Appeals are considered by the DOAJ Appeals Committee, consisting of the Editor-in-Chief and members of the editorial team. Committee meetings are held monthly. After your case is discussed a member of the Committee will contact you with the result of your appeal. This will not be the same Managing Editor who handled your case originally. +Appeals are considered by the DOAJ Appeals Committee, consisting of the Editor-in-Chief and members of the editorial team. Committee meetings are held monthly. After your case is discussed, a member of the Committee will contact you with the result of your appeal. This will not be the same Managing Editor who handled your case originally. No further communication will be entered into after the appeal is heard and the Committee has made their decision. @@ -200,15 +214,15 @@ No further communication will be entered into after the appeal is heard and the ## Updating your journal record -For journals that have been accepted into DOAJ, it is important that the information we hold about them is up to date. +For journals accepted into DOAJ, it is important that the information we hold about them is up to date. -You can update the journal information via our API or by logging into your account and going to your Publisher dashboard. Under 'My journals' is a list of all the journals connected to your account. Use the black 'Update' button, next to each journal record, to submit an update request to us. +You can update the journal information via our API or by logging into your account and going to your Publisher dashboard. Under 'My journals' is a list of all the journals connected to your account. Use the black 'Update' button next to each journal record to submit an update request to us. A journal update request form will open with the current answers pre-filled. Change any answers that need to be updated. If you haven't updated the journal since before November 2020, when we revised the form, we suggest that you review every answer. Note that some questions may not have an answer, as new questions were added in November 2020. Once you have amended and reviewed the answers, submit the update request to us. -Update requests are reviewed by the DOAJ editorial team for accuracy and we aim to process them within six weeks of submission. +The DOAJ editorial team reviews update requests for accuracy and we aim to process them within six weeks of submission. --- @@ -234,11 +248,12 @@ Our criteria are in: --- -## Change log +## Change log -This is Version 1.3 of the Guide to applying. +This is Version 2.0 of the Guide to applying. -*Version: 1.3 (April 2023 - further clarification to endogeny, displaying journal rankings, information about author charges and reviewers, flipped journals and journals/publishers with multiple exclusions )*
+*Version 2.0 (November 2023 - addition of new criteria around special issues, smaller changes for clarity and plain English.)* +Version: 1.3 (April 2023 - further clarification to endogeny, displaying journal rankings, information about author charges and reviewers, flipped journals and journals/publishers with multiple exclusions )
Version: 1.2 (February 2022 - extra copyright and licensing information were moved to a separate page)
Version: 1.1 (December 2021 - a clarification to endogeny criterion)
Version 1.0 (November 1.0) diff --git a/cms/pages/apply/seal.md b/cms/pages/apply/seal.md index b9c4968cca..7fc7f4d414 100644 --- a/cms/pages/apply/seal.md +++ b/cms/pages/apply/seal.md @@ -12,7 +12,7 @@ The DOAJ Seal is awarded to journals that demonstrate best practice in open acce **Journals do not need to meet the Seal criteria to be accepted into DOAJ.** -There are seven criteria which a journal must meet to be eligible for the DOAJ Seal. These relate to best practice in long term preservation, use of persistent identifiers, discoverability, reuse policies and authors' rights. +There are seven criteria which a journal must meet to be eligible for the DOAJ Seal. These relate to best practices in long-term preservation, use of persistent identifiers, discoverability, reuse policies and authors' rights. --- @@ -21,29 +21,29 @@ There are seven criteria which a journal must meet to be eligible for the DOAJ S All seven criteria must be met for a journal to be awarded the Seal. Failure to maintain the best practice and standards described in these criteria may lead to removal of the Seal. {:.tabular-list .tabular-list--ordered} -1. Digital preservation +1. Digital preservation (Archiving policy) - The journal content must be continuously deposited in one of these archives: - any archiving agency included in [Keepers Registry](https://keepers.issn.org/keepers) - Internet Archive - PubMed Central -2. Persistent article identifiers +2. Self-archiving (Repository policy) + - Authors must be permitted to deposit all versions of their paper in an institutional or subject repository. + - Preprint + - Author's Accepted Manuscript + - Published article (Version of Record) + - An embargo may not be applied. +3. Persistent article identifiers (Unique identifiers) - Articles must use persistent article identifiers. DOI, ARK or Handle are the most commonly used. - All persistent links must resolve correctly. -3. Metadata supply to DOAJ +4. Metadata supply to DOAJ - Article metadata must be uploaded to DOAJ regularly. -4. License type +5. License type - The journal must permit the use of a Creative Commons license that allows the creation of derivative products. - CC BY - CC BY-SA - CC BY-NC - CC BY-NC-SA -5. License information in articles +6. License information in articles - Creative Commons licensing information must be displayed in all full-text article formats. -6. Copyright and publishing rights +7. Copyright and publishing rights - Authors must retain unrestricted copyright and all publishing rights when publishing under any license permitted by the journal. -7. Self-archiving policy - - Authors must be permitted to deposit all versions of their paper in an institutional or subject repository. - - Preprint - - Author's Accepted Manuscript - - Published article (Version of Record) - - An embargo may not be applied. diff --git a/cms/pages/apply/transparency.md b/cms/pages/apply/transparency.md index 0bda2aae3d..913d061882 100644 --- a/cms/pages/apply/transparency.md +++ b/cms/pages/apply/transparency.md @@ -23,13 +23,14 @@ These principles also acknowledge that publishers and editors are responsible fo ### JOURNAL CONTENT -#### 1. Name of journal +**1. Name of journal** + The journal's name should: - Be unique and not be one that is easily confused with another journal. - Not mislead potential authors and readers about the journal's origin, scope, or association with other journals and organisations. -#### 2. Website +**2. Website** - Websites should be properly supported and maintained, with particular attention given to security aspects that help protect users from viruses and malware. - As a minimum, websites should use https and not http, and all traffic should be redirected through https. Those responsible for the website should apply web standards and best ethical practices to the website's content, presentation, and application. @@ -45,20 +46,22 @@ In addition to the requirements outlined above, the following items should be cl - Authorship criteria. - ISSNs (separate for print and electronic versions). -#### 3. Publishing schedule +**3. Publishing schedule** + A journal's publishing frequency should be clearly described, and the journal must keep to its publishing schedule unless there are exceptional circumstances. -#### 4. Archiving +**4. Archiving** + A journal's plan for electronic backup and long term digital preservation of the journal content, in the event that the journal and/or publisher stops operating, should be clearly indicated. Examples include PMC and those listed in [the Keepers Registry](https://keepers.issn.org/). -#### 5. Copyright +**5. Copyright** - The copyright terms for published content should be clearly stated on the website and in the content. - The copyright terms should be separate and distinct from the copyright of the website. - The copyright holder should be named on the full text of all published articles (HTML and PDF). - If the copyright terms are described in a separate form, this should be easy to find on the website and available to all. -#### 6. Licencing +**6. Licencing** - Licencing information should be clearly described on the website. - Licencing terms should be indicated on the full text of all published articles (HTML and PDF). @@ -69,7 +72,8 @@ If Creative Commons licences are used, then the terms of that licence should als ### JOURNAL PRACTICES -#### 7. Publication ethics and related editorial policies +**7. Publication ethics and related editorial policies** + A journal should have policies on publication ethics (for example, [COPE's Core Practice guidance](https://publicationethics.org/core-practices)). These should be visible on its website, and should refer to: - Journal's policies on [authorship and contributorship](https://publicationethics.org/authorship). @@ -84,7 +88,8 @@ A journal should have policies on publication ethics (for example, [COPE's Core Editors and publishers are responsible for ensuring the integrity of the scholarly literature in their journals and should ensure they outline their policies and procedures for handling such issues when they arise. These issues include plagiarism, citation manipulation, and data falsification/fabrication, among others. Neither the journal’s policies nor the statements of its editors should encourage such misconduct, or knowingly allow such misconduct to take place. In the event that a journal's editors or publisher are made aware of any allegation of research misconduct relating to a submitted or published article in their journal, the editor or publisher should follow [COPE's guidance](https://publicationethics.org/guidance) (or equivalent) in dealing with allegations. -#### 8. Peer review +**8. Peer review** + Peer review is defined as obtaining advice on manuscripts from reviewers/experts in the manuscript’s subject area. Those individuals should not be part of the journal's editorial team. However, the specific elements of peer review may differ by journal and discipline, so the following should be clearly stated on the website: - Whether or not the content is peer reviewed. @@ -105,30 +110,31 @@ Journals should not guarantee acceptance of initial manuscript submissions. Stat The date of publication should be published with all published research. Dates of submission and acceptance are preferred as well. -#### 9. Access +**9. Access** + If any of the online content is not freely accessible to everyone, the method of gaining access (for example, registration, subscription, or pay-per-view fees) should be clearly described. If offline versions (for example, print) are available, this should be clearly described along with any associated charges. ### ORGANISATION -#### 10. Ownership and management +**10. Ownership and management** - Information about the ownership and management of a journal should be clearly indicated on the journal's website. - Organisational names should not be used in a way that could mislead potential authors and editors about the nature of the journal's owner. - If a journal is affiliated with a society, institution, or sponsor, links to their website(s) should be provided where available. -#### 11. Advisory body +**11. Advisory body** Journals should have editorial boards or other advisory bodies whose members are recognised experts in the subject areas stated in the journal's aims and scope. - The full names and affiliations of the members should be provided on the journal's website. - The list should be up to date, and members must agree to serve. - To avoid being associated with predatory or deceptive journals, journals should periodically review their board to ensure it is still relevant and appropriate. -#### 12. Editorial team/contact information +**12. Editorial team/contact information** Journals should provide the full names and affiliations of their editors as well as contact information for the editorial office, including a full mailing address, on the journal’s website. ### BUSINESS PRACTICES -#### 13. Author fees +**13. Author fees** - If author fees are charged (such as article processing charges, page charges, editorial processing charges, language editing fees, colour charges, submission fees, membership fees, or other supplementary charges), then the fees should be clearly stated on the website. - If there are no such fees, this should be clearly stated. @@ -141,14 +147,16 @@ Journals should provide the full names and affiliations of their editors as well - When and how to apply for a waiver. - Author fees or waiver status should not influence editorial decision making, and this should be clearly stated. -#### 14. Other revenue +**14. Other revenue** + Business models or revenue sources should be clearly stated on the journal's website. Examples include author fees (see section 13), subscriptions, sponsorships and subsidies, advertising (see section 15), reprints, supplements, or special issues. Business models or revenue sources (for example, reprint income, supplements, special issues, sponsorships) should not influence editorial decision making. -#### 15. Advertising +**15. Advertising** + Journals should state whether they accept advertising. If they do, they should state their advertising policy, including: - Which types of advertisements will be considered. @@ -157,7 +165,8 @@ Journals should state whether they accept advertising. If they do, they should s Advertisements should not be related in any way to editorial decision making and should be kept separate from the published content. -#### 16. Direct marketing +**16. Direct marketing** + Any direct marketing activities, including solicitation of manuscripts, that are conducted on behalf of the journal should be appropriate, well targeted, and unobtrusive. Information provided about the publisher or journal should be truthful and not misleading for readers or authors. ## Version history diff --git a/cms/pages/legal/contact.md b/cms/pages/legal/contact.md index 0219545d6f..39d0c914dc 100644 --- a/cms/pages/legal/contact.md +++ b/cms/pages/legal/contact.md @@ -7,11 +7,14 @@ featuremap: ~~ContactUs:Fragment~~ --- -We welcome your feedback, comments and questions about the DOAJ web site and our services, or if you have feedback on a journal in the index. +Use this page to: -All communication with us is treated as confidential. +- send feedback and questions about our website or services +- send feedback about a journal in our index +- submit a complaint about our website or services -Read our [FAQs](/docs/faq/) before you contact us. If you cannot find the answer to your question, send an email to [the DOAJ helpdesk](mailto:helpdesk@doaj.org). If your email is about a journal, please include the title and ISSN(s). - - - +Both complaints and feedback sent to us about specific journals are treated as confidential. + +If you're a publisher and need help, read our [publisher help page](/publisher/help/). + +Send an email to [the DOAJ helpdesk](mailto:helpdesk@doaj.org). If you're emailing about a journal or application, please include the title and ISSN(s). diff --git a/cms/pages/preservation/index.md b/cms/pages/preservation/index.md index 9601600341..b9c4adf0d7 100644 --- a/cms/pages/preservation/index.md +++ b/cms/pages/preservation/index.md @@ -11,11 +11,11 @@ featuremap: ~~Preservation:Fragment~~ [Esta página está disponible en Español](https://docs.google.com/document/d/1dCxZYO0HDmFWMyazbkayZJtpCYkO9AIf0xqw-z55rU0/edit?usp=sharing). - Project JASPER (JournAlS are Preserved forevER) is an initiative to preserve open access journals. It was launched on [World Preservation Day 2020](https://www.dpconline.org/events/world-digital-preservation-day) and is in response to research* that shows that online journals—both open and closed access journals—can just disappear from the internet. This happens because of a lack of awareness amongst smaller publishers around the need for long-term digital preservation and/or the resources to enroll a journal in a long-term digital preservation scheme. + Project JASPER (JournAlS are Preserved forevER) is an initiative to preserve open access journals. It was launched on [World Preservation Day 2020](https://www.dpconline.org/events/world-digital-preservation-day) and is in response to research* that shows that online journals—both open and closed access journals—can disappear from the internet. -Long-term archiving of research resources is of paramount importance for scholarship. Authors want to ensure their contributions to the scholarly record will be permanent. Scholars must be able to access all of the published research in their fields, both now and long into the future. +Long-term archiving of scholarship is of paramount importance, but too often, publishers don't realise this or don't have the resources to do anything about it. Authors want to ensure their contributions to the scholarly record will be permanent. Scholars must be able to access all of the published research in their fields, both now and long into the future. -As a scholarly community, we are pledged to eliminate the possibility that high-value resources can disappear*. Project JASPER aims to close the gap in preservation coverage that currently exists among open access journals. +As a scholarly community, we are pledged to eliminate the possibility that high-value resources can disappear*. Project JASPER aims to close the gap in preservation coverage among open access journals. Questions? [Email us](mailto:preservation@doaj.org). @@ -24,59 +24,54 @@ _*References_ 1. M. Laakso, M. Matthias, N. Jahn. _Open is not forever: A study of vanished open access journals_. Journal of the Association for Information Science and Technology, Feb. 2021. [https://doi.org/10.1002/asi.24460](https://doi.org/10.1002/asi.24460) 2. J. Bosman et al. _OA Diamond Journals Study_. [https://zenodo.org/record/4558704](https://zenodo.org/record/4558704) -## Phase One -Phases One of Project JASPER was a pilot project between CLOCKSS, DOAJ, Internet Archive, Keepers Registry and PKP to scope out a solution that will reduce the number of unarchived open access journals. +## The project -## Phase Two -Phase One was considered a success. Phase Two will onboard more journals and the project partners will seek funding for a more sustainable solution, covering a wider journal base. +The premise of the JASPER process is that all journals are different, with different needs, different priorities and different resources. The workflow offers viable alternatives to journals so that they engage with one or more archiving services that meet their requirements and capacities. The JASPER process is simple: -JASPER's aim is to get more journals archived by establishing a simple process: +1 work out which archiving option might be the best fit for the publisher +2 establish the level where the amount of effort is manageable by the publisher, based on: + a platform + b ability to send article metadata to DOAJ + c the ability to export and zip full text, images etc +3 direct the journal to the most appropriate archiving option -- work out which archiving option might be the best fit for the publisher, -- establish the level where the amount of effort is manageable by the publisher. This is based on the following factors: - - platform - - ability to send article metadata to DOAJ - - ability to export and zip full text, images etc easily -- provide the journal's representatives with the correct information to make archiving happen. +## For publishers -The premise of the JASPER process is that all journals are different, with different needs, different priorities and different resources. The workflow needs to offer viable alternatives to journals so that they engage with one or more archiving services that meet their requirements and capacities. - -### For publishers -We hope that many open access publishers, whose journals are not currently archived, will want to take part. The current criteria for eligibility are +We hope that many DOAJ-indexed publishers will want to take part but to ensure our resources are focussed, we have eligibility criteria: - Your journal must be indexed in DOAJ -- The abstracts of your articles must be indexed in DOAJ -- Your journal may not charge any fees of any kind, or be subsidised in any way. -- Your journal is not already archived in a preservation service (although it may be archived in PKP PN). -- Your content is licensed with a CC BY licence (although sometimes, we will accept less open licenses). +- The journal's article abstracts must be indexed in DOAJ +- Your journal may not charge any fees or be subsidised in any way. +- A commercial contract with an archiving solution does not already cover your journal. +- Your content is licensed with a CC BY licence (sometimes, we will accept less open licenses). - The copyright of published content remains with the author. -Please note that the project partners are self-funding the JASPER project, which means that we will analyse the financial status of journals or publishers quite closely. We reserve the right to not include a journal or a publisher in JASPER if we think that it is or could be supported or funded by an organisation capable of covering the costs of long-term preservation. +We will analyse the financial status of journals or publishers quite closely. We reserve the right not to include a journal or a publisher in JASPER if we think it is or could be supported or funded by an organisation capable of covering the costs of long-term preservation. This is a necessary step while JASPER remains unfunded. -If you have any questions about these criteria, don't hesitate to [get in touch](mailto:preservation@doaj.org). +## To apply -#### Archiving options -1. *The OJS route*: OJS journals can use [the PKP PN preservation](https://docs.pkp.sfu.ca/pkp-pn/en/) service which is built on LOCKSS software. You need to enable a plugin in the journal's dashboard and agree to some simple terms. However, your journal must be on the right version of the OJS software to take part. JASPER will make sure that the journals are pointed to the guidance they need to upgrade and become compliant. Journals on OJS may also choose to have their content preserved via the upload route below. +If your journal meets the criteria above, [you may apply here](https://www.surveymonkey.com/r/JASPERPRESERVATION). If you have any questions about these criteria, don't hesitate to [get in touch](mailto:preservation@doaj.org). -2. *The upload route*: journals indicate to DOAJ that they wish to preserve their content with CLOCKSS. DOAJ matches the full text sent to us with article metadata that we hold. We send everything to a remote folder. From this single location, archiving agencies, such as CLOCKSS and Internet Archive (and, later, other “Keepers”) collect the content. +## Different archiving options +1. *The OJS route*: OJS journals can use [the PKP PN preservation](https://docs.pkp.sfu.ca/pkp-pn/en/) service which is built on LOCKSS software. You must enable a plugin in the journal's dashboard and agree to some simple terms. However, your journal must be on the right version of the OJS software to take part. JASPER will ensure that the journals are pointed to the guidance they need to upgrade and become compliant. Journals on OJS may also choose to have their content preserved via the upload route below. -3. *The web-crawing route*: if you are not able to or do not want to export article metadata and full text, your journal website details will be provided to the Internet Archive for inclusion in a best-effort, automated web harvesting. +2. *The upload route*: journals indicate to DOAJ that they wish to preserve their content with CLOCKSS. DOAJ matches the full text sent to us with the article metadata that we hold. We send everything to a remote folder. From this single location, archiving agencies, such as CLOCKSS and Internet Archive (and, later, other “Keepers”) collect the content. -#### More copies, better preservation -It is always safer for a journal’s content to be archived in more than one place and, ideally, in at least three. To make this easy for smaller publishers, journals opting for Route 2 will automatically have their content preserved via Route 3. Future phases of the project will add the possibility for Route 1 journals to take part in other routes too. +3. *The web-crawling route*: if you cannot or do not want to export article metadata and full text, your journal website details will be provided to the Internet Archive for inclusion in a best-effort, automated web harvesting. -### For the Keepers -There are often costs associated with adding new journals to a preservation service. JASPER aims to significantly reduce these costs by using DOAJ as a common interface to the thousands of journals indexed in it. By delivering content to an FTP server hosted by Internet Archive, content deliveries are unified into a single format and available from a single location. +### More copies, better preservation +It is always safer for a journal’s content to be archived in more than one place, ideally, in at least three. To simplify this for smaller publishers, journals opting for Route 2 will automatically have their content preserved via Route 3. Future phases of the project will allow Route 1 journals to participate in other routes, too. -Phase One designed, implemented and tested this process, supported by money or services donated by the five project partners. Phase Two will onboard more publishers and seek funding. +## For the Keepers +There are often costs associated with adding new journals to a preservation service. JASPER aims to significantly reduce these costs by using DOAJ as a common interface to the thousands of journals indexed in it. By delivering content to an FTP server hosted by Internet Archive, content deliveries are unified into a single format and available from a single location. If you want to join JASPER, [get in touch](mailto:preservation@doaj.org). -### For libraries and universities -Long-term preservation is a profoundly important mission for libraries, universities, and other memory organisations. By supporting DOAJ, the archiving services participating in this project, and in Keepers, you are helping us preserve valuable scholarly content for the long-term and in a very cost-effective way. Thank you for your continuing support! +## For libraries and universities +Long-term digital preservation is a profoundly important mission for libraries, universities, and other memory organisations. By [supporting JASPER](https://doaj.org/support/), you are helping us preserve valuable scholarly content for the long term and in a very cost-effective way. Thank you for your continuing support! ## About the project partners ### CLOCKSS -[CLOCKSS](https://clockss.org/) is a not-for-profit collaboration of the world’s leading academic publishers and research libraries. Our mission is to ensure the long-term survival of digital scholarly content and to instill confidence in authors, scholars, policy makers, libraries and publishers around the world that their content will be safely and securely preserved for future generations. We are entrusted with the long-term preservation of more than 51 million journal articles and 400,000 scholarly books. CLOCKSS operates [12 archive nodes](https://clockss.org/archive-nodes/) at leading academic institutions worldwide. This secure, robust, and decentralized infrastructure can withstand threats from technological, economic, environmental, and political failures. A destructive event in one location doesn’t jeopardize the survival of preserved digital content because the 11 other locations serve as mirror sites to backup and repair the disrupted location’s archive. Content is triggered from our dark archive if it disappears from the web, and is made available to everyone under an Open Access license. +[CLOCKSS](https://clockss.org/) is a not-for-profit collaboration of leading academic publishers and research libraries. Our mission is to ensure the long-term survival of digital scholarly content and instil confidence in authors, scholars, policymakers, libraries and publishers worldwide that their content will be safely and securely preserved for future generations. We are entrusted with the long-term preservation of more than 51 million journal articles and 400,000 scholarly books. CLOCKSS operates [12 archive nodes](https://clockss.org/archive-nodes/) at leading academic institutions worldwide. This secure, robust, and decentralized infrastructure can withstand threats from technological, economic, environmental, and political failures. A destructive event in one location doesn’t jeopardize the survival of preserved digital content because the 11 other locations serve as mirror sites to back up and repair the disrupted location’s archive. Content is triggered from our dark archive if it disappears from the web and is made available to everyone under an Open Access license. ### DOAJ DOAJ is the largest, most diverse, free directory of open access journals in the world. DOAJ's mission is to increase the visibility, accessibility, reputation, usage and impact of quality, peer-reviewed, open access scholarly research journals globally, regardless of discipline, geography or language. By assisting journals to become archived in long-term digital preservation schemes, DOAJ fulfils its mission by ensuring accessibility, visibility and usage of these crucial scholarly records. @@ -88,4 +83,4 @@ DOAJ is the largest, most diverse, free directory of open access journals in the [Keepers Registry](https://keepers.issn.org) is the global monitor on the archiving arrangements for continuing resources managed by the ISSN International Centre and fuelled by reports from contributing archiving agencies. ### PKP -A research and development initiative within Simon Fraser University’s Core Facilities Program, the [Public Knowledge Project](https://pkp.sfu.ca/) (PKP) develops (free) open-source software. It conducts research to improve the quality and reach of scholarly publishing. As part of its mandate to make knowledge public, PKP understands that preserving and maintaining long-term access to published content is of utmost importance. To support this, PKP has launched a [Preservation Network](https://pkp.sfu.ca/pkp-pn/), a freely available private LOCKSS network that can be used by any OJS journal running an up-to-date version of OJS. PKP welcomes the opportunity to participate in Project JASPER with our colleagues at DOAJ, Keepers Registry, Internet Archive and CLOCKSS. +A research and development initiative within Simon Fraser University’s Core Facilities Program, the [Public Knowledge Project](https://pkp.sfu.ca/) (PKP) develops (free) open-source software. It carries out research to improve the quality and reach of scholarly publishing. As part of its mandate to make knowledge public, PKP understands that preserving and maintaining long-term access to published content is paramount. To support this, PKP has launched a [Preservation Network](https://pkp.sfu.ca/pkp-pn/), a freely available private LOCKSS network that can be used by any OJS journal running an up-to-date version of OJS. PKP welcomes the opportunity to participate in Project JASPER with our colleagues at DOAJ, Keepers Registry, Internet Archive and CLOCKSS. diff --git a/cms/pages/support/publisher-supporters.md b/cms/pages/support/publisher-supporters.md index 1d82e26b2b..61c3b24eef 100644 --- a/cms/pages/support/publisher-supporters.md +++ b/cms/pages/support/publisher-supporters.md @@ -1,7 +1,7 @@ --- layout: sidenav include: /data/publisher-supporters.html -title: Publisher supporters +title: Publisher supporter model section: Support sticky_sidenav: true toc: true @@ -10,27 +10,63 @@ featuremap: - ~~->PublisherSupportersData:Template~~ --- -The publishers on this page have chosen to show their commitment to quality, peer-reviewed open access by supporting DOAJ. We thank them! Without them, our work would not be possible. +DOAJ relies on the support of publishers and [libraries](/support/) to ensure that its metadata and services remain free for all. The publishers on this page have chosen to show their commitment to quality, peer-reviewed open access by supporting DOAJ. We thank them as without them, our work would not be possible. -**To become a publisher supporter**, send an email to [our Help desk](mailto:helpdesk@doaj.org) and we will provide with details on how to support us. Your organisation will be listed on this page. +## Pricing -'Premier' and 'Sustaining' publishers have committed to supporting DOAJ for a three-year period. 'Basic' publishers support us for one year. +We are introducing a revised and simplified model for publishers to support DOAJ for 2024 and publishing this openly in line with [our commitment to the Principles of Open Scholarly Infrastructure](https://blog.doaj.org/2022/10/06/doaj-commits-to-the-principles-of-open-scholarly-infrastructure-posi/). We are also relaunching the set of benefits for publishers choosing to support us. -
{% include '/data/sponsors.html' %}
+We only accept support through our publisher supporter model from publishers with journals already indexed in DOAJ. Non-commercial/institutional rates are only available to community-led, smaller publishers with limited funding. Please contact [supporters@doaj.org](mailto:supporters@doaj.org) if you are unsure which category applies. + +Please contact [supporters@doaj.org](mailto:supporters@doaj.org) if you want to contribute to DOAJ’s operating costs as a publisher supporter. + +### Commercial publishers + +| Band | Number of journals in DOAJ | GBPs (£)* | +|------|----------------------------|-----------| +| A | 600+ | 25,000 | +| B | 400-599 | 20,000 | +| C | 150-399 | 17,000 | +| D | 100-149 | 14,000 | +| E | 50-99 | 8000 | +| F | 30-49 | 6000 | +| G | 10-29 | 5000 | +| H | 1-9 | 3500 | + +### Non-commercial / institutional publishers + +| Band | Number of journals in DOAJ | GBPs (£)* | +|------|----------------------------|-----------| +| C | 150-399 | 3500 | +| D | 100-149 | 3000 | +| E | 50-99 | 2500 | +| F | 30-49 | 2000 | +| G | 10-29 | 1500 | +| H | 1-9 | 1000 | + +*A 50% discount is available for supporters in Low- and Middle-Income Countries according to the World Bank classification. -## Benefits for contributing publishers and aggregators +## Benefits -([A downloadable version](https://docs.google.com/document/d/1xTVxUvqLkh2-r53cYlWdSIHsPGSnhcE7gi7bRFCaJik/edit?usp=sharing) of these benefits is available.) +1. Your logo on the DOAJ website +2. A post from all our social media platforms (Twitter, Facebook, LinkedIn, Mastodon, Instagram) acknowledging your organisation as a Supporter +3. A blog post at the start of the year introducing our new supporters +4. Our DOAJ Supporter logo which you can use for your website +5. Access to our Public Data Dump +6. For supporters from Bands A-E, or those contributing over the suggested amounts, a personal DOAJ contact to whom all enquiries regarding your applications and updates can be directed -| Basic contribution | Sustaining contribution (25% higher than Basic) | Premier contribution (50% higher than Basic) | -|--------------------------|-------------------------------------------------|----------------------------------------------| -| Your logo on our website | Your logo on our website | Your logo on our website | -| A Tweet from the DOAJ account (21,900 followers) acknowledging your organisation as a Supporter | An interview-style blog post on [blog.doaj.org](https://blog.doaj.org/) (12,000 views per month) acknowledging your organisation as a Contributor, with questions about the state of open access and other current developments in open access scholarly publishing | An interview-style blog post on [blog.doaj.org](https://blog.doaj.org/) (12,000 views per month) acknowledging your organisation as a Contributor with questions about the state of open access and other current developments in open access scholarly publishing| -| | A one-pager for journal editors explaining, in general terms, how article metadata is ingested, and then pulled downstream by discovery services. | A one-pager for journal editors explaining, in general terms, how article metadata is ingested, and then pulled downstream by discovery services.| -| | General presentation or documentation, for raising awareness among your stakeholders, about the benefits of DOAJ, the work we do and how DOAJ benefits individual journal titles. | Dedicated presentation or documentation, tailored to your organisation, about the benefits of DOAJ, the work we do and how DOAJ benefits individual journal titles. Written as a joint document with the publisher. | -| | A report, (a Google Sheet), collecting metadata errors, as reported to us by discovery services. | A report, (a Google Sheet), collecting metadata errors, as reported to us by discovery services. | -| | | A yearly account review between the customer and DOAJ. The agenda can be determined by the publisher contact and a DOAJ representative. | -| | | A CSV file, generated annually, for recording changes in and which DOAJ updates your live records with. | -| | | Exposure across all our social media channels: Twitter, Instagram, LinkedIn, Facebook, WeChat. (Stats available.) | +## Sponsorship opportunities + +We are particularly grateful to those publishers who can contribute over and above these amounts. In these cases, we can offer sponsorship opportunities that enhance our services and support open access globally, for example: + +- Specific technical developments +- Ambassador programme +- Webinar programmes and events + +Please get in touch to discuss. + +## Publisher supporters + +
{% include '/data/sponsors.html' %}
## Other publisher supporters diff --git a/doajtest/mocks/preservation.py b/doajtest/mocks/preservation.py index 3c6d610d5b..7ced1a4b85 100644 --- a/doajtest/mocks/preservation.py +++ b/doajtest/mocks/preservation.py @@ -226,3 +226,227 @@ class PreservationMock: "es_type": "article" } + ARTICLE_DATA_JOURNAL2 = { + "index": { + "issn": [ + "2673-611X" + ], + "date": "2016-10-01T00:00:00Z", + "date_toc_fv_month": "2016-10-01T00:00:00Z", + "subject": [ + "Science: Natural history (General): General. Including nature conservation, geographical distribution" + ], + "schema_subject": [ + "LCC:Neurology. Diseases of the nervous system" + ], + "classification": [ + "Neurology. Diseases of the nervous system" + ], + "publisher": [ + "Frontiers Media S.A." + ], + "license": [ + "CC BY" + ], + "language": [ + "English" + ], + "country": "United Kingdom", + "schema_code": [ + "LCC:RC346-429" + ], + "classification_paths": [ + "Medicine: Internal medicine: Neurosciences. Biological psychiatry. Neuropsychiatry: Neurology. Diseases of the nervous system" + ], + "unpunctitle": "Variation in TMEM106B in chronic traumatic encephalopathy", + "asciiunpunctitle": "Variation in TMEM106B in chronic traumatic encephalopathy", + "has_seal": "Yes", + "doi": "10.3389/fcosc.2022.1028295", + "fulltext": "https://frontiersin.org/articles/10.3389/fcosc.2022.1028295", + "schema_codes_tree": [ + "LCC:RC31-1245", + "LCC:RC346-429", + "LCC:RC321-571", + "LCC:R" + ] + }, + "last_updated": "2020-11-24T21:55:20Z", + "admin": { + "in_doaj": "true", + "seal": "true" + }, + "created_date": "2018-11-04T12:37:46Z", + "id": "00005741594643f4996e2666a01e0310", + "bibjson": { + "title": "Variation in TMEM106B in chronic traumatic encephalopathy", + "year": "2018", + "month": "11", + "start_page": "1", + "end_page": "9", + "abstract": "Abstract The genetic basis of chronic traumatic encephalopathy (CTE) is poorly understood. Variation in transmembrane protein 106B (TMEM106B) has been associated with enhanced neuroinflammation during aging and with TDP-43-related neurodegenerative disease, and rs3173615, a missense coding SNP in TMEM106B, has been implicated as a functional variant in these processes. Neuroinflammation and TDP-43 pathology are prominent features in CTE. The purpose of this study was to determine whether genetic variation in TMEM106B is associated with CTE risk, pathological features, and ante-mortem dementia. Eighty-six deceased male athletes with a history of participation in American football, informant-reported Caucasian, and a positive postmortem diagnosis of CTE without comorbid neurodegenerative disease were genotyped for rs3173615. The minor allele frequency (MAF = 0.42) in participants with CTE did not differ from previously reported neurologically normal controls (MAF = 0.43). However, in a case-only analysis among CTE cases, the minor allele was associated with reduced phosphorylated tau (ptau) pathology in the dorsolateral frontal cortex (DLFC) (AT8 density, odds ratio [OR] of increasing one quartile = 0.42, 95% confidence interval [CI] 0.22–0.79, p = 0.008), reduced neuroinflammation in the DLFC (CD68 density, OR of increasing one quartile = 0.53, 95% CI 0.29–0.98, p = 0.043), and increased synaptic protein density (β = 0.306, 95% CI 0.065–0.546, p = 0.014). Among CTE cases, TMEM106B minor allele was also associated with reduced ante-mortem dementia (OR = 0.40, 95% CI 0.16–0.99, p = 0.048), but was not associated with TDP-43 pathology. All case-only models were adjusted for age at death and duration of football play. Taken together, variation in TMEM106B may have a protective effect on CTE-related outcomes.", + "journal": { + "volume": "6", + "number": "1", + "publisher": "BMC", + "title": "Acta Neuropathologica Communications", + "country": "GB", + "license": [ + { + "title": "CC BY", + "type": "CC BY", + "url": "https://actaneurocomms.biomedcentral.com/submission-guidelines/copyright", + "open_access": "true" + } + ], + "language": [ + "EN" + ], + "issns": [ + "2673-611X" + ] + }, + "identifier": [ + { + "type": "doi", + "id": "10.1186/s40478-018-0619-9" + }, + { + "type": "eissn", + "id": "2051-5960" + } + ], + "keywords": [ + "Chronic traumatic encephalopathy", + "TMEM106B", + "Neuroinflammation", + "Football", + "Traumatic brain injury", + "Tau" + ], + "link": [ + { + "type": "fulltext", + "url": "http://link.springer.com/article/10.1186/s40478-018-0619-9", + "content_type": "HTML" + } + ], + "subject": [ + { + "scheme": "LCC", + "term": "Neurology. Diseases of the nervous system", + "code": "RC346-429" + } + ], + "author": [ + { + "name": "Jonathan D. Cherry", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Jesse Mez", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "John F. Crary", + "affiliation": "Department of Pathology, Fishberg Department of Neuroscience, Friedman Brain Institute, Ronald M. Loeb Center for Alzheimer’s Disease, Icahn School of Medicine at Mount Sinai School" + }, + { + "name": "Yorghos Tripodis", + "affiliation": "Department of Biostatistics, Boston University School of Public Health" + }, + { + "name": "Victor E. Alvarez", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Ian Mahar", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Bertrand R. Huber", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Michael L. Alosco", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Raymond Nicks", + "affiliation": "Department of Veterans Affairs Medical Center" + }, + { + "name": "Bobak Abdolmohammadi", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Patrick T. Kiernan", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Laney Evers", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Sarah Svirsky", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Katharine Babcock", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Hannah M. Gardner", + "affiliation": "VA Boston Healthcare System" + }, + { + "name": "Gaoyuan Meng", + "affiliation": "VA Boston Healthcare System" + }, + { + "name": "Christopher J. Nowinski", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Brett M. Martin", + "affiliation": "Department of Biostatistics, Boston University School of Public Health" + }, + { + "name": "Brigid Dwyer", + "affiliation": "Department of Neurology, Boston University School of Medicine" + }, + { + "name": "Neil W. Kowall", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Robert C. Cantu", + "affiliation": "Department of Anatomy and Neurobiology, Boston University School of Medicine" + }, + { + "name": "Lee E. Goldstein", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Douglas I. Katz", + "affiliation": "Department of Neurology, Boston University School of Medicine" + }, + { + "name": "Robert A. Stern", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Lindsay A. Farrer", + "affiliation": "Department of Neurology, Boston University School of Medicine" + }, + { + "name": "Ann C. McKee", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Thor D. Stein", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + } + ] + }, + "es_type": "article" + } diff --git a/doajtest/preservation_upload_test_package/invalid_article.zip b/doajtest/preservation_upload_test_package/invalid_article.zip new file mode 100644 index 0000000000..dbbcf59bf1 Binary files /dev/null and b/doajtest/preservation_upload_test_package/invalid_article.zip differ diff --git a/doajtest/preservation_upload_test_package/multi_journals.zip b/doajtest/preservation_upload_test_package/multi_journals.zip new file mode 100644 index 0000000000..cd160b2c46 Binary files /dev/null and b/doajtest/preservation_upload_test_package/multi_journals.zip differ diff --git a/doajtest/preservation_upload_test_package/valid_article.zip b/doajtest/preservation_upload_test_package/valid_article.zip new file mode 100644 index 0000000000..883394c578 Binary files /dev/null and b/doajtest/preservation_upload_test_package/valid_article.zip differ diff --git a/doajtest/testbook/articles_preservation/user_test_script.yml b/doajtest/testbook/articles_preservation/upload_preservation_files.yml similarity index 53% rename from doajtest/testbook/articles_preservation/user_test_script.yml rename to doajtest/testbook/articles_preservation/upload_preservation_files.yml index 051673c68d..effbe29bc5 100644 --- a/doajtest/testbook/articles_preservation/user_test_script.yml +++ b/doajtest/testbook/articles_preservation/upload_preservation_files.yml @@ -1,6 +1,6 @@ # ~~ ArticlesPreservation:FunctionalTest -> Preservation:Feature ~~ suite: Articles Preservation -testset: User Test Script +testset: Upload Preservation Files tests: - title: Publisher without preservation role context: @@ -22,7 +22,8 @@ tests: steps: - step: Ensure that the archive package has been created with correct structure - step: Go to preservation area /publisher/preservation - - step: Select 'Browse' and select the paaackage zip file + - step: Select 'Browse' and select the package zip file + resource: /preservation_upload_test_package/valid_article.zip - step: click 'Upload' results: - 'A flash message appears at the top of the screen: File uploaded and waiting @@ -40,7 +41,8 @@ tests: steps: - step: Create a incorrect or junk file that does not have articles - step: Go to preservation area /publisher/preservation - - step: Select 'Browse' and select the paaackage zip file + - step: Select 'Browse' and select the package zip file + resource: /preservation_upload_test_package/invalid_article.zip - step: click 'Upload' results: - 'A flash message appears at the top of the screen: File uploaded and waiting @@ -56,9 +58,11 @@ tests: context: role: publisher steps: - - step: Create a incorrect or junk file that does not have articles + - step: Make sure not to login as admin user as admin does not have restrictions. Login as publisher. + - step: Ensure that the archive package has been created with correct structure but the article does not own by the logged in user. - step: Go to preservation area /publisher/preservation - - step: Select 'Browse' and select the paaackage zip file + - step: Select 'Browse' and select the package zip file + resource: /preservation_upload_test_package/valid_article.zip - step: click 'Upload' results: - 'A flash message appears at the top of the screen: File uploaded and waiting @@ -75,6 +79,42 @@ tests: - if the status is 'partially success', show details link will be displayed. When clicked on the link details of how many articles successful and how many not successful will be displayed +- title: Upload correctly structured file with multiple journals and it is processed correctly + context: + role: publisher + setup: + - Have a publisher account which is the owner of 3 journals that are in DOAJ, and which have articles + uploaded for them + - Select 1 article from each journal and note its DOI + - Download the multi_journals.zip test package (see link below in second step) + - Unzip the multi_journals.zip test package + - In the identifiers.csv file in the root of the test package, put each of the DOIs from your articles + next to one of the article directory names listed (removing the existing test DOIs that are present in that + file already). For example you will have a line which reads "article1,10.1224/mydoi/3" where the DOI + is the one from your article. + - Re-zip the multi_journals.zip package. This wil be the file you will upload for testing in the below script + steps: + - step: Ensure that the archive package has been created with correct structure + - step: Ensure that the archive package contains more than one article with different journals. + A sample zip file is available for testing purpose. If there are no articles with the DOIs specified in the indentifiers.csv + file in your DSoace instance, + the results may not be as expected. In that case unzip the file and update the identifiers.csv file with appropriate identifiers for the articles. + resource: /preservation_upload_test_package/multi_journals.zip + - step: Go to preservation area /publisher/preservation + - step: Select 'Browse' and select the package zip file + - step: click 'Upload' + results: + - 'A flash message appears at the top of the screen: File uploaded and waiting + to be processed.' + - Upload status is shown in 'History of uploads' with status processing + - step: wait a short amount of time for the job to process, then reload the page + (do not re-submit the form data). If the job remains in "pending", reload the + page until the status changes. + results: + - Upload status in 'History of uploads' changes to 'successfully processed' and + the 'Notes' shows as 'uploaded to archive server' + - The 'Notes' has the label 'UPLOADED PACKAGES' + - Under the label 'UPLOADED PACKAGES', details of the packages that were uploaded are displayed in the format _.tar.gz - title: Test maintenance mode for 'Upload preservation file' tab context: role: publisher @@ -82,4 +122,4 @@ tests: - step: Set the value of 'PRESERVATION_PAGE_UNDER_MAINTENANCE' to True in the configuration file (app.cfg/dev.cfg/test.cfg) - step: Go to preservation area /publisher/preservation results: - - Maintenance page should be displayed with the content as 'This page is currently offline for maintenance' \ No newline at end of file + - Maintenance page should be displayed with the content as 'This page is currently offline for maintenance' diff --git a/doajtest/unit/resources/invalid_article.zip b/doajtest/unit/resources/invalid_article.zip new file mode 120000 index 0000000000..e955a12f14 --- /dev/null +++ b/doajtest/unit/resources/invalid_article.zip @@ -0,0 +1 @@ +../../preservation_upload_test_package/invalid_article.zip \ No newline at end of file diff --git a/doajtest/unit/resources/multi_journals.zip b/doajtest/unit/resources/multi_journals.zip new file mode 120000 index 0000000000..ac64455e26 --- /dev/null +++ b/doajtest/unit/resources/multi_journals.zip @@ -0,0 +1 @@ +../../preservation_upload_test_package/multi_journals.zip \ No newline at end of file diff --git a/doajtest/unit/resources/preservation_multiple_journals.zip b/doajtest/unit/resources/preservation_multiple_journals.zip new file mode 100644 index 0000000000..a55b9decd2 Binary files /dev/null and b/doajtest/unit/resources/preservation_multiple_journals.zip differ diff --git a/doajtest/unit/resources/valid_article.zip b/doajtest/unit/resources/valid_article.zip new file mode 120000 index 0000000000..ebf17a3099 --- /dev/null +++ b/doajtest/unit/resources/valid_article.zip @@ -0,0 +1 @@ +../../preservation_upload_test_package/valid_article.zip \ No newline at end of file diff --git a/doajtest/unit/test_query.py b/doajtest/unit/test_query.py index 6ab470754d..d1717341c7 100644 --- a/doajtest/unit/test_query.py +++ b/doajtest/unit/test_query.py @@ -86,14 +86,42 @@ } } -JOURNAL_QUERY_ROUTE = { +SEARCH_ALL_QUERY_ROUTE = { "query" : { "journal" : { "auth" : False, "role" : None, - "query_filters" : ["only_in_doaj", "search_all_meta"], + "query_filters" : ["search_all_meta"], "dao" : "portality.models.Journal" } + }, + "editor_query" : { + "journal" : { + "auth" : True, + "role" : "editor", + "query_filters" : ["search_all_meta"], + "dao" : "portality.models.Journal" + }, + "suggestion" : { + "auth" : False, + "role" : "editor", + "query_filters" : ["search_all_meta"], + "dao" : "portality.models.Application" + } + }, + "associate_query": { + "journal": { + "auth": False, + "role": "associate_editor", + "query_filters" : ["search_all_meta"], + "dao": "portality.models.Journal" + }, + "suggestion" : { + "auth" : False, + "role" : "associate_editor", + "query_filters" : ["search_all_meta"], + "dao" : "portality.models.Application" + } } } @@ -412,7 +440,7 @@ def test_10_scroll(self): def test_public_query_notes(self): - self.app_test.config['QUERY_ROUTE'] = JOURNAL_QUERY_ROUTE + self.app_test.config['QUERY_ROUTE'] = SEARCH_ALL_QUERY_ROUTE self.get_journal_with_notes() @@ -440,6 +468,8 @@ def test_admin_query_notes(self): def test_editor_query_notes(self): + self.app_test.config['QUERY_ROUTE'] = SEARCH_ALL_QUERY_ROUTE + self.get_journal_with_notes() editor = models.Account(**AccountFixtureFactory.make_editor_source()) @@ -455,10 +485,12 @@ def test_editor_query_notes(self): 'default_operator': 'AND'}}, 'size': 0, 'aggs': {'country_publisher': {'terms': {'field': 'index.country.exact', 'size': 100, 'order': {'_count': 'desc'}}}}, 'track_total_hits': True}, account=editor, additional_parameters={}) - assert res['hits']['total']["value"] == 1, res['hits']['total']["value"] + assert res['hits']['total']["value"] == 0, res['hits']['total']["value"] def test_associate_editor_query_notes(self): + self.app_test.config['QUERY_ROUTE'] = SEARCH_ALL_QUERY_ROUTE + self.get_journal_with_notes() associate = models.Account(**AccountFixtureFactory.make_assed1_source()) @@ -474,10 +506,12 @@ def test_associate_editor_query_notes(self): 'default_operator': 'AND'}}, 'size': 0, 'aggs': {'country_publisher': {'terms': {'field': 'index.country.exact', 'size': 100, 'order': {'_count': 'desc'}}}}, 'track_total_hits': True}, account=associate, additional_parameters={}) - assert res['hits']['total']["value"] == 1, res['hits']['total']["value"] + assert res['hits']['total']["value"] == 0, res['hits']['total']["value"] def test_associate_editor_application_query_notes(self): + self.app_test.config['QUERY_ROUTE'] = SEARCH_ALL_QUERY_ROUTE + app = self.get_application_with_notes() associate = models.Account(**AccountFixtureFactory.make_assed1_source()) @@ -494,10 +528,12 @@ def test_associate_editor_application_query_notes(self): 'default_operator': 'AND'}}, 'size': 0, 'aggs': {'country_publisher': {'terms': {'field': 'index.country.exact', 'size': 100, 'order': {'_count': 'desc'}}}}, 'track_total_hits': True}, account=associate, additional_parameters={}) - assert res['hits']['total']["value"] == 1, res['hits']['total']["value"] + assert res['hits']['total']["value"] == 0, res['hits']['total']["value"] def test_editor_application_query_notes(self): + self.app_test.config['QUERY_ROUTE'] = SEARCH_ALL_QUERY_ROUTE + app = self.get_application_with_notes() editor = models.Account(**AccountFixtureFactory.make_editor_source()) @@ -514,9 +550,9 @@ def test_editor_application_query_notes(self): 'default_operator': 'AND'}}, 'size': 0, 'aggs': {'country_publisher': {'terms': {'field': 'index.country.exact', 'size': 100, 'order': {'_count': 'desc'}}}}, 'track_total_hits': True}, account=editor, additional_parameters={}) - assert res['hits']['total']["value"] == 1, res['hits']['total']["value"] + assert res['hits']['total']["value"] == 0, res['hits']['total']["value"] - def test_editor_application_query_notes(self): + def test_admin_application_query_notes(self): app = self.get_application_with_notes() diff --git a/doajtest/unit/test_task_preservation.py b/doajtest/unit/test_task_preservation.py index 29c170f742..d8d62d87e8 100644 --- a/doajtest/unit/test_task_preservation.py +++ b/doajtest/unit/test_task_preservation.py @@ -12,34 +12,80 @@ from portality.models.article import Article -class TestPreservation(DoajTestCase): +def mock_pull_by_key(key, value): + if value == "http://link.springer.com/article/10.1186/s40478-018-0619-9": + article = Article() + article.data = PreservationMock.ARTICLE_DATA + return article + elif value == "https://www.frontiersin.org/articles/10.3389/fcosc.2022.1028295": + article = Article() + article.data = PreservationMock.ARTICLE_DATA_JOURNAL2 + return article - def setUp(self): - super(TestPreservation, self).setUp() - articles_zip_path = test_constants.PATH_RESOURCES / "articles.zip" + +def mock_requests_post(*args, **kwargs): + class MockResponse: + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code + + def json(self): + return self.json_data + + if not args[0] == None and kwargs["data"]["org"] == "DOAJ": + return MockResponse({ + "files": [ + { + "name": "name_of_tarball.tar.gz", + "sha256": "decafbad" + } + ] + }, 200) + + return MockResponse(None, 404) + + +def mock_owner_of_article(*args, **kwargs): + return True + + +class TestPreservationSetup(DoajTestCase): + + def initial_setup(self, package_name): + super(TestPreservationSetup, self).setUp() + articles_zip_path = test_constants.PATH_RESOURCES / package_name with open(articles_zip_path, 'rb') as zf: - self.zip_file = FileStorage(BytesIO(zf.read()), filename="articles.zip") + self.zip_file = FileStorage(BytesIO(zf.read()), filename=package_name) self.upload_dir = app.config.get("UPLOAD_DIR", ".") created_time = dates.now_str("%Y-%m-%d-%H-%M-%S") - owner = "rama" - dir_name = owner + "-" + created_time + self.owner = "rama" + self.journal_dir = "2051-5960" + dir_name = self.owner + "-" + created_time self.local_dir = os.path.join(preservation.Preservation.UPLOAD_DIR, dir_name) - self.preserve = preservation.Preservation(self.local_dir, owner) - self.package = preservation.PreservationPackage(self.preserve.preservation_dir, owner) - self.local_dir = os.path.join(self.local_dir,"tmp") + self.preserve = preservation.Preservation(self.local_dir, self.owner) + self.tmp_dir = os.path.join(self.local_dir, "tmp") self.preservation_collection = app.config.get("PRESERVATION_COLLECTION") - app.config["PRESERVATION_COLLECTION"] = {"rama":["test","2"]} + app.config["PRESERVATION_COLLECTION"] = {"rama": ["test", "2"]} def tearDown(self): - super(TestPreservation, self).tearDown() + super(TestPreservationSetup, self).tearDown() preservation.Preservation.delete_local_directory(self.local_dir) app.config["PRESERVATION_COLLECTION"] = self.preservation_collection + +class TestPreservation(TestPreservationSetup): + + def setUp(self): + super(TestPreservation, self).initial_setup("articles.zip") + + def tearDown(self): + super(TestPreservation, self).tearDown() + def test_local_directory(self): - #Test creation of local directory - #TestPreservation.preserve.create_local_directories() + # Test creation of local directory + # TestPreservation.preserve.create_local_directories() job = preservation.PreservationBackgroundTask.prepare("rama", upload_file=self.zip_file) params = job.params local_dir = params["preserve__local_dir"] @@ -48,81 +94,137 @@ def test_local_directory(self): assert os.path.isdir(os.path.join(self.upload_dir, dir_name)) assert os.path.isdir(os.path.join(self.upload_dir, dir_name,dir_name)) - #Test deletion of local directory + # Test deletion of local directory preservation.Preservation.delete_local_directory(local_dir) assert not os.path.exists(os.path.join(self.upload_dir, dir_name)) - def mock_pull_by_key(key, value): - article = Article() - article.data = PreservationMock.ARTICLE_DATA - return article + @patch.object(Article, 'pull_by_key', mock_pull_by_key) + @patch.object(requests, "post", mock_requests_post) + @patch.object(preservation.Preservation, 'owner_of_article', mock_owner_of_article) + def test_preservation(self): + self.preserve.save_file(self.zip_file) + + assert os.path.exists(os.path.join(self.tmp_dir, self.zip_file.filename)) + + # Test extraction of zip file + self.preserve.extract_zip_file() + + assert os.path.exists(os.path.join(self.tmp_dir, "articles")) + assert os.path.isdir(os.path.join(self.tmp_dir, "articles")) + assert os.path.isdir(os.path.join(self.tmp_dir, "articles", "article_1")) + assert os.path.exists(os.path.join(self.tmp_dir, "articles", + "article_1", "identifier.txt")) + + reader = preservation.CSVReader(os.path.join(self.tmp_dir, + "articles", "identifiers.csv")) + data = reader.articles_info() + + assert "article_1" in data + assert "article/10.1186/s40478-018-0619-9" in data["article_1"][0] + + # Test package structure + self.preserve.create_package_structure() + package_dir = os.path.join(self.upload_dir, + self.preserve.dir_name, self.preserve.dir_name, self.journal_dir) + tag_manifest_file = os.path.join(package_dir, "00003741594643f4996e2555a01e03c7", "tagmanifest-sha256.txt") + manifest_file = os.path.join(package_dir, "00003741594643f4996e2555a01e03c7", "manifest-sha256.txt") + assert os.path.exists(package_dir) + assert os.path.exists(tag_manifest_file) + assert os.path.exists(manifest_file) + + package = preservation.PreservationPackage(self.preserve.preservation_dir, self.journal_dir, self.owner) + + # Test creation of tar file + package.create_package() + tar_file = package_dir + "_" + package.created_time + ".tar.gz" + assert os.path.exists(tar_file) + + sha256 = package.sha256(tar_file) + response = package.upload_package(sha256, tar_file) + assert response.status_code == 200 + + def test_get_article_info(self): + issn, article_id, metadata_json = self.preserve.get_article_info(PreservationMock.ARTICLE_DATA) - def mock_requests_post(*args, **kwargs): - class MockResponse: - def __init__(self, json_data, status_code): - self.json_data = json_data - self.status_code = status_code + assert issn == "2051-5960" + assert article_id == "00003741594643f4996e2555a01e03c7" + assert metadata_json["bibjson"]["identifier"][0]["id"] == "10.1186/s40478-018-0619-9" - def json(self): - return self.json_data - if not args[0] == None and kwargs["data"]["org"] == "DOAJ": - return MockResponse({ - "files": [ - { - "name": "name_of_tarball.tar.gz", - "sha256": "decafbad" - } - ] - }, 200) +class TestPreservationMultipleJournals(TestPreservationSetup): - return MockResponse(None, 404) + def setUp(self): + super(TestPreservationMultipleJournals, self).initial_setup("preservation_multiple_journals.zip") + self.another_journal_dir = "2673-611X" - def mock_owner_of_article(*args, **kwargs): - return True + def tearDown(self): + super(TestPreservationMultipleJournals, self).tearDown() @patch.object(Article, 'pull_by_key', mock_pull_by_key) - @patch.object(requests,"post", mock_requests_post) + @patch.object(requests, "post", mock_requests_post) @patch.object(preservation.Preservation, 'owner_of_article', mock_owner_of_article) - def test_preservation(self): + def test_preservation_multiple_journals(self): self.preserve.save_file(self.zip_file) - assert os.path.exists(os.path.join(self.local_dir, self.zip_file.filename)) - # Test extraction of zip file self.preserve.extract_zip_file() - assert os.path.exists(os.path.join(self.local_dir, "articles")) - assert os.path.isdir(os.path.join(self.local_dir, "articles")) - assert os.path.isdir(os.path.join(self.local_dir, "articles", "article_1")) - assert os.path.exists(os.path.join(self.local_dir, "articles", - "article_1", "identifier.txt")) + assert os.path.exists(os.path.join(self.tmp_dir, "articles")) + assert os.path.isdir(os.path.join(self.tmp_dir, "articles")) + assert os.path.isdir(os.path.join(self.tmp_dir, "articles", "article_1")) + assert os.path.exists(os.path.join(self.tmp_dir, "articles", + "article_1", "Identifier.txt")) - reader = preservation.CSVReader(os.path.join(self.local_dir, - "articles", "identifiers.csv")) + reader = preservation.CSVReader(os.path.join(self.tmp_dir, + "articles", "Identifiers.csv")) data = reader.articles_info() assert "article_1" in data assert "article/10.1186/s40478-018-0619-9" in data["article_1"][0] + assert "article_2" in data + assert "10.3389/fcosc.2022.1028295" in data["article_2"][0] + # Test package structure self.preserve.create_package_structure() package_dir = os.path.join(self.upload_dir, - self.preserve.dir_name, self.preserve.dir_name) - tag_manifest_file = os.path.join(package_dir, "2051-5960", "00003741594643f4996e2555a01e03c7", "tagmanifest-sha256.txt") - manifest_file = os.path.join(package_dir,"2051-5960", "00003741594643f4996e2555a01e03c7", "manifest-sha256.txt") + self.preserve.dir_name, self.preserve.dir_name, self.journal_dir) + tag_manifest_file = os.path.join(package_dir, "00003741594643f4996e2555a01e03c7", "tagmanifest-sha256.txt") + manifest_file = os.path.join(package_dir, "00003741594643f4996e2555a01e03c7", "manifest-sha256.txt") assert os.path.exists(package_dir) assert os.path.exists(tag_manifest_file) assert os.path.exists(manifest_file) + package = preservation.PreservationPackage(self.preserve.preservation_dir, self.journal_dir, self.owner) + # Test creation of tar file - self.package.create_package() - assert os.path.exists(package_dir + ".tar.gz") + package.create_package() + tar_file = package_dir + "_" + package.created_time + ".tar.gz" + assert os.path.exists(tar_file) - sha256 = self.package.sha256() - response = self.package.upload_package(sha256) + sha256 = package.sha256(tar_file) + response = package.upload_package(sha256, tar_file) assert response.status_code == 200 + # Test another journal package + package_dir = os.path.join(self.upload_dir, + self.preserve.dir_name, self.preserve.dir_name, self.another_journal_dir) + tag_manifest_file = os.path.join(package_dir, "00005741594643f4996e2666a01e0310", "tagmanifest-sha256.txt") + manifest_file = os.path.join(package_dir, "00005741594643f4996e2666a01e0310", "manifest-sha256.txt") + assert os.path.exists(package_dir) + assert os.path.exists(tag_manifest_file) + assert os.path.exists(manifest_file) + + package = preservation.PreservationPackage(self.preserve.preservation_dir, self.another_journal_dir, self.owner) + + # Test creation of tar file for another journal + package.create_package() + tar_file = package_dir + "_" + package.created_time + ".tar.gz" + assert os.path.exists(tar_file) + + sha256 = package.sha256(tar_file) + response = package.upload_package(sha256, tar_file) + assert response.status_code == 200 def test_get_article_info(self): issn, article_id, metadata_json = self.preserve.get_article_info(PreservationMock.ARTICLE_DATA) diff --git a/doajtest/unit/test_toc.py b/doajtest/unit/test_toc.py index bfe6564d8c..dafe64ac31 100644 --- a/doajtest/unit/test_toc.py +++ b/doajtest/unit/test_toc.py @@ -1,16 +1,57 @@ -from doajtest.helpers import DoajTestCase from doajtest.fixtures import ArticleFixtureFactory, JournalFixtureFactory +from doajtest.helpers import DoajTestCase +from portality import app as _app # noqa, make sure route is registered from portality import models +from portality.util import url_for + + +def _test_toc_uses_both_issns_when_available(app_test, url_name): + j = models.Journal(**JournalFixtureFactory.make_journal_source(in_doaj=True)) + pissn = j.bibjson().first_pissn + eissn = j.bibjson().first_eissn + j.set_last_manual_update() + j.save(blocking=True) + a = models.Article(**ArticleFixtureFactory.make_article_source(pissn=pissn, eissn=eissn, in_doaj=True)) + a.save(blocking=True) + with app_test.test_client() as t_client: + response = t_client.get(url_for(url_name, identifier=j.bibjson().get_preferred_issn())) + assert response.status_code == 200 + assert pissn in response.data.decode() + assert eissn in response.data.decode() + + +def _test_toc_correctly_uses_pissn(app_test, url_name): + j = models.Journal(**JournalFixtureFactory.make_journal_source(in_doaj=True)) + pissn = j.bibjson().first_pissn + # remove eissn + del j.bibjson().eissn + j.set_last_manual_update() + j.save(blocking=True) + a = models.Article(**ArticleFixtureFactory.make_article_source(pissn=pissn, in_doaj=True)) + a.save(blocking=True) + with app_test.test_client() as t_client: + response = t_client.get(url_for(url_name, identifier=j.bibjson().get_preferred_issn())) + assert response.status_code == 200 + assert pissn in response.data.decode() + + +def _test_toc_correctly_uses_eissn(app_test, url_name): + j = models.Journal(**JournalFixtureFactory.make_journal_source(in_doaj=True)) + eissn = j.bibjson().first_eissn + # remove pissn + del j.bibjson().pissn + j.set_last_manual_update() + j.save(blocking=True) + a = models.Article(**ArticleFixtureFactory.make_article_source(pissn=eissn, in_doaj=True)) + a.save(blocking=True) + with app_test.test_client() as t_client: + response = t_client.get(url_for(url_name, identifier=j.bibjson().get_preferred_issn())) + assert response.status_code == 200 + assert eissn in response.data.decode() class TestTOC(DoajTestCase): - def setUp(self): - super(TestTOC, self).setUp() - - def tearDown(self): - super(TestTOC, self).tearDown() - def test_01_article_index_date_parsing(self): """ The ToC date histogram needs an accurate datestamp in the article's index """ a = models.Article(**ArticleFixtureFactory.make_article_source()) @@ -43,9 +84,9 @@ def test_01_article_index_date_parsing(self): d = a.bibjson().get_publication_date() assert d == '2012-03-01T00:00:00Z' - a.bibjson().year = '86' # beware: this test will give a false negative 70 years from - a.bibjson().month = '11' # the time of writing; this gives adequate warning (24 years) - d = a.bibjson().get_publication_date() # to fix hard-coding of centuries in get_publication_date(). + a.bibjson().year = '86' # beware: this test will give a false negative 70 years from + a.bibjson().month = '11' # the time of writing; this gives adequate warning (24 years) + d = a.bibjson().get_publication_date() # to fix hard-coding of centuries in get_publication_date(). assert d == '1986-11-01T00:00:00Z' # Check we can handle numeric months @@ -90,45 +131,21 @@ def test_02_toc_requirements(self): assert a.data['index']['date_toc_fv_month'] == a.data['index']['date'] == "1991-01-01T00:00:00Z" def test_03_toc_uses_both_issns_when_available(self): - j = models.Journal(**JournalFixtureFactory.make_journal_source(in_doaj=True)) - pissn = j.bibjson().first_pissn - eissn = j.bibjson().first_eissn - j.set_last_manual_update() - j.save(blocking=True) - a = models.Article(**ArticleFixtureFactory.make_article_source(pissn=pissn, eissn=eissn, in_doaj=True)) - a.save(blocking=True) - with self.app_test.test_client() as t_client: - response = t_client.get('/toc/{}'.format(j.bibjson().get_preferred_issn())) - assert response.status_code == 200 - assert pissn in response.data.decode() - assert eissn in response.data.decode() + _test_toc_uses_both_issns_when_available(self.app_test, 'doaj.toc') + + def test_04_toc_correctly_uses_pissn(self): + _test_toc_correctly_uses_pissn(self.app_test, 'doaj.toc') + + def test_05_toc_correctly_uses_eissn(self): + _test_toc_correctly_uses_eissn(self.app_test, 'doaj.toc') + + +class TestTOCArticles(DoajTestCase): + def test_03_toc_uses_both_issns_when_available(self): + _test_toc_uses_both_issns_when_available(self.app_test, 'doaj.toc_articles') def test_04_toc_correctly_uses_pissn(self): - j = models.Journal(**JournalFixtureFactory.make_journal_source(in_doaj=True)) - pissn = j.bibjson().first_pissn - # remove eissn - del j.bibjson().eissn - - j.set_last_manual_update() - j.save(blocking=True) - a = models.Article(**ArticleFixtureFactory.make_article_source(pissn=pissn, in_doaj=True)) - a.save(blocking=True) - with self.app_test.test_client() as t_client: - response = t_client.get('/toc/{}'.format(j.bibjson().get_preferred_issn())) - assert response.status_code == 200 - assert pissn in response.data.decode() + _test_toc_correctly_uses_pissn(self.app_test, 'doaj.toc_articles') def test_05_toc_correctly_uses_eissn(self): - j = models.Journal(**JournalFixtureFactory.make_journal_source(in_doaj=True)) - eissn = j.bibjson().first_eissn - # remove pissn - del j.bibjson().pissn - - j.set_last_manual_update() - j.save(blocking=True) - a = models.Article(**ArticleFixtureFactory.make_article_source(pissn=eissn, in_doaj=True)) - a.save(blocking=True) - with self.app_test.test_client() as t_client: - response = t_client.get('/toc/{}'.format(j.bibjson().get_preferred_issn())) - assert response.status_code == 200 - assert eissn in response.data.decode() + _test_toc_correctly_uses_eissn(self.app_test, 'doaj.toc_articles') diff --git a/portality/bll/services/journal.py b/portality/bll/services/journal.py index c1d005300d..d27956d700 100644 --- a/portality/bll/services/journal.py +++ b/portality/bll/services/journal.py @@ -10,8 +10,9 @@ from portality.lib.dates import FMT_DATETIME_SHORT from portality.store import StoreFactory, prune_container, StoreException from portality.crosswalks.journal_questions import Journal2QuestionXwalk +from portality.util import no_op -from datetime import datetime +from datetime import datetime, timedelta import re, csv, random, string @@ -131,6 +132,10 @@ def csv(self, prune=True, logger=None): {"arg": logger, "allow_none": True, "arg_name": "logger"} ], exceptions.ArgumentException) + # None isn't executable, so convert logger to NO-OP + if logger is None: + logger = no_op + # ~~->FileStoreTemp:Feature~~ filename = 'journalcsv__doaj_' + dates.now_str(FMT_DATETIME_SHORT) + '_utf8.csv' container_id = app.config.get("STORE_CACHE_CONTAINER") @@ -254,42 +259,64 @@ def _get_article_kvs(journal): return kvs # ~~!JournalCSV:Feature->Journal:Model~~ - cols = {} - for j in models.Journal.all_in_doaj(page_size=1000): #Fixme: limited by ES, this may not be sufficient + csvwriter = csv.writer(file_object) + first = True + for j in models.Journal.all_in_doaj(page_size=100): + export_start = datetime.utcnow() logger("Exporting journal {x}".format(x=j.id)) + time_log = [] bj = j.bibjson() issn = bj.get_one_identifier(idtype=bj.P_ISSN) if issn is None: issn = bj.get_one_identifier(idtype=bj.E_ISSN) + time_log.append("{x} - got issn".format(x=datetime.utcnow())) + if issn is None: continue # ~~!JournalCSV:Feature->JournalQuestions:Crosswalk~~ kvs = Journal2QuestionXwalk.journal2question(j) + time_log.append("{x} - crosswalked questions".format(x=datetime.utcnow())) meta_kvs = _get_doaj_meta_kvs(j) + time_log.append("{x} - got meta kvs".format(x=datetime.utcnow())) article_kvs = _get_article_kvs(j) + time_log.append("{x} - got article kvs".format(x=datetime.utcnow())) additionals = [] if additional_columns is not None: for col in additional_columns: additionals += col(j) - cols[issn] = kvs + meta_kvs + article_kvs + additionals + time_log.append("{x} - got additionals".format(x=datetime.utcnow())) + row = kvs + meta_kvs + article_kvs + additionals # Get the toc URL separately from the meta kvs because it needs to be inserted earlier in the CSV # ~~-> ToC:WebRoute~~ toc_kv = _get_doaj_toc_kv(j) - cols[issn].insert(2, toc_kv) + row.insert(2, toc_kv) + time_log.append("{x} - got toc kvs".format(x=datetime.utcnow())) - logger("All journals exported") - issns = cols.keys() - - csvwriter = csv.writer(file_object) - qs = None - for i in sorted(issns): - if qs is None: - qs = [q for q, _ in cols[i]] + if first is True: + qs = [q for q, _ in row] csvwriter.writerow(qs) - vs = [v for _, v in cols[i]] + first = False + + vs = [v for _, v in row] csvwriter.writerow(vs) - logger("CSV Written") + time_log.append("{x} - written row to csv".format(x=datetime.utcnow())) + + export_end = datetime.utcnow() + if export_end - export_start > timedelta(seconds=10): + for l in time_log: + logger(l) + + logger("All journals exported and CSV written") + # issns = cols.keys() + # qs = None + # for i in sorted(issns): + # if qs is None: + # qs = [q for q, _ in cols[i]] + # csvwriter.writerow(qs) + # vs = [v for _, v in cols[i]] + # csvwriter.writerow(vs) + # logger("CSV Written") diff --git a/portality/forms/application_forms.py b/portality/forms/application_forms.py index d1f8a44d20..e66a8e2ef2 100644 --- a/portality/forms/application_forms.py +++ b/portality/forms/application_forms.py @@ -169,18 +169,21 @@ class FieldDefinitions: "contexts": { "admin": { "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] }, "editor": { "disabled": True, "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] }, "associate_editor": { "disabled": True, "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] }, @@ -212,16 +215,19 @@ class FieldDefinitions: }, "admin": { "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] }, "associate_editor": { "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] }, "editor": { "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -474,7 +480,7 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - {"autocomplete": {"type" : "journal", "field": "bibjson.publisher.name.exact"}}, + {"autocomplete": {"type" : "journal", "field": "bibjson.publisher.name.exact"}}, # ~~^-> Autocomplete:FormWidget~~ "full_contents" # ~~^->FullContents:FormWidget~~ ], "help": { @@ -486,16 +492,22 @@ class FieldDefinitions: }, "admin": { "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ + {"autocomplete": {"type": "journal", "field": "bibjson.publisher.name.exact"}}, # ~~^-> Autocomplete:FormWidget~~ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] }, "associate_editor": { "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ + {"autocomplete": {"type": "journal", "field": "bibjson.publisher.name.exact"}}, # ~~^-> Autocomplete:FormWidget~~ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] }, "editor": { "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ + {"autocomplete": {"type": "journal", "field": "bibjson.publisher.name.exact"}}, # ~~^-> Autocomplete:FormWidget~~ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -546,28 +558,37 @@ class FieldDefinitions: "a society or other type of institution, enter that here."], "placeholder": "Type or select the society or institution’s name" }, - "contexts" : { + "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ + {"autocomplete": {"type" : "journal", "field": "bibjson.institution.name.exact"}}, # ~~^-> Autocomplete:FormWidget~~ + "full_contents" # ~~^->FullContents:FormWidget~~ + ], + "contexts": { "admin": { "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ + {"autocomplete": {"type": "journal", "field": "bibjson.institution.name.exact"}}, + # ~~^-> Autocomplete:FormWidget~~ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] }, "associate_editor": { "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ + {"autocomplete": {"type": "journal", "field": "bibjson.institution.name.exact"}}, + # ~~^-> Autocomplete:FormWidget~~ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] }, "editor": { "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ + {"autocomplete": {"type": "journal", "field": "bibjson.institution.name.exact"}}, + # ~~^-> Autocomplete:FormWidget~~ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] } - }, - "widgets": [ - "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - {"autocomplete": {"type" : "journal", "field": "bibjson.institution.name.exact"}}, - "full_contents" # ~~^->FullContents:FormWidget~~ - ] + } } # ~~->$ InstitutionCountry:FormField~~ @@ -1639,7 +1660,7 @@ class FieldDefinitions: "owner_exists" ], "widgets": [ - {"autocomplete": {"type" : "account", "field": "id", "include" : False}}, + {"autocomplete": {"type" : "account", "field": "id", "include" : False}}, # ~~^-> Autocomplete:FormWidget~~ "clickable_owner" ], "contexts" : { @@ -1697,7 +1718,7 @@ class FieldDefinitions: "label": "Group", "input": "text", "widgets": [ - {"autocomplete": {"type" : "editor_group", "field": "name", "include" : False}} + {"autocomplete": {"type" : "editor_group", "field": "name", "include" : False}} # ~~^-> Autocomplete:FormWidget~~ ], "contexts" : { "editor" : { @@ -1705,7 +1726,7 @@ class FieldDefinitions: }, "admin" : { "widgets" : [ - {"autocomplete": {"type": "editor_group", "field": "name", "include" : False}}, + {"autocomplete": {"type": "editor_group", "field": "name", "include" : False}}, # ~~^-> Autocomplete:FormWidget~~ {"load_editors" : {"field" : "editor"}} ] } diff --git a/portality/models/preservation.py b/portality/models/preservation.py index c89c2f3f6f..cdaffb2708 100644 --- a/portality/models/preservation.py +++ b/portality/models/preservation.py @@ -99,6 +99,10 @@ def no_files_articles(self, articles_list): if articles_list is not None and len(articles_list) > 0: self.data["articles_info"]["no_files_articles"] = ", ".join(articles_list) + def uploaded_journals(self, uploaded_journals): + if uploaded_journals is not None and len(uploaded_journals) > 0: + self.data["articles_info"]["uploaded_journals"] = ", ".join(uploaded_journals) + @classmethod def by_owner(cls, owner, size=10): q = OwnerFileQuery(owner) diff --git a/portality/scripts/230609_find_articles_with_invalid_issns.py b/portality/scripts/230609_find_articles_with_invalid_issns.py index 8b857faa01..dd861ea04d 100644 --- a/portality/scripts/230609_find_articles_with_invalid_issns.py +++ b/portality/scripts/230609_find_articles_with_invalid_issns.py @@ -22,15 +22,26 @@ parser.add_argument("-o", "--out", help="output file path", required=True) args = parser.parse_args() - with open(args.out, "w", encoding="utf-8") as f: - writer = csv.writer(f) - writer.writerow(["ID", "PISSN", "EISSN", "Journals found with article's PISSN", "In doaj?", "Journals found with article's EISSN", "In doaj?", "Error"]) + + with open(args.out+"notfound.csv", "w", encoding="utf-8") as f_notfound, open(args.out+"-identical.csv", "w", encoding="utf-8") as f_identical, open(args.out+"-others.csv", "w", encoding="utf-8") as f_others: + writer_notfound = csv.writer(f_notfound) + writer_notfound.writerow(["ID", "PISSN", "EISSN", "Journals found with article's PISSN", "In doaj?", + "Journals found with article's EISSN", "In doaj?", "Error"]) + + writer_identical = csv.writer(f_identical) + writer_identical.writerow(["ID", "PISSN", "EISSN", "Journals found with article's PISSN", "In doaj?", + "Journals found with article's EISSN", "In doaj?", "Error"]) + + writer_others = csv.writer(f_others) + writer_others.writerow(["ID", "PISSN", "EISSN", "Journals found with article's PISSN", "In doaj?", + "Journals found with article's EISSN", "In doaj?", "Error"]) for a in models.Article.iterate(q=IN_DOAJ, page_size=100, keepalive='5m'): article = models.Article(_source=a) bibjson = article.bibjson() try: articlesvc.ArticleService._validate_issns(bibjson) + articlesvc.ArticleService.match_journal_with_validation(bibjson) except exceptions.ArticleNotAcceptable as e: id = article.id pissn = bibjson.get_identifiers("pissn") @@ -53,4 +64,9 @@ j_e_in_doaj.append(jobj.is_in_doaj()) else: j_e_in_doaj.append("n/a") - writer.writerow([id, pissn, eissn, j_p, j_p_in_doaj, j_e, j_e_in_doaj, str(e)]) + if (str(e) == "The Print and Online ISSNs supplied are identical. If you supply 2 ISSNs they must be different."): + writer_identical.writerow([id, pissn, eissn, j_p, j_p_in_doaj, j_e, j_e_in_doaj, "Identical ISSNs"]) + elif (str(e) == "ISSNs provided don't match any journal."): + writer_notfound.writerow([id, pissn, eissn, j_p, j_p_in_doaj, j_e, j_e_in_doaj, "No matching journal found."]) + else: + writer_others.writerow([id, pissn, eissn, j_p, j_p_in_doaj, j_e, j_e_in_doaj, str(e)]) \ No newline at end of file diff --git a/portality/settings.py b/portality/settings.py index 551cec44cb..c3a817da76 100644 --- a/portality/settings.py +++ b/portality/settings.py @@ -9,7 +9,7 @@ # Application Version information # ~~->API:Feature~~ -DOAJ_VERSION = "6.4.3" +DOAJ_VERSION = "6.4.6" API_VERSION = "3.0.1" ###################################### @@ -423,11 +423,14 @@ # Crontab for never running a job - February 31st (use to disable tasks) CRON_NEVER = {"month": "2", "day": "31", "day_of_week": "*", "hour": "*", "minute": "*"} +# Additional Logging for scheduled JournalCSV +EXTRA_JOURNALCSV_LOGGING = False + # Crontab schedules must be for unique times to avoid delays due to perceived race conditions HUEY_SCHEDULE = { "sitemap": {"month": "*", "day": "*", "day_of_week": "*", "hour": "8", "minute": "0"}, "reporting": {"month": "*", "day": "1", "day_of_week": "*", "hour": "0", "minute": "0"}, - "journal_csv": CRON_NEVER, # {"month": "*", "day": "*", "day_of_week": "*", "hour": "*", "minute": "20"}, + "journal_csv": {"month": "*", "day": "*", "day_of_week": "*", "hour": "*", "minute": "20"}, "read_news": {"month": "*", "day": "*", "day_of_week": "*", "hour": "*", "minute": "30"}, "article_cleanup_sync": {"month": "*", "day": "2", "day_of_week": "*", "hour": "0", "minute": "0"}, "async_workflow_notifications": {"month": "*", "day": "*", "day_of_week": "1", "hour": "5", "minute": "0"}, diff --git a/portality/static/js/edges/public.journal.edge.js b/portality/static/js/edges/public.journal.edge.js index 47d366d55d..f4494d8c4d 100644 --- a/portality/static/js/edges/public.journal.edge.js +++ b/portality/static/js/edges/public.journal.edge.js @@ -274,7 +274,7 @@ $.extend(true, doaj, { value: false }) ], - display : "Without article processing charges (APCs)" + display : "Without any fees" } ], fieldDisplays : { diff --git a/portality/tasks/journal_csv.py b/portality/tasks/journal_csv.py index 9b5b74269d..153a13735e 100644 --- a/portality/tasks/journal_csv.py +++ b/portality/tasks/journal_csv.py @@ -19,12 +19,18 @@ def run(self): def logger(msg): self.background_job.add_audit_message(msg) + _l = logger if app.config.get('EXTRA_JOURNALCSV_LOGGING', False) else None + job = self.background_job journalService = DOAJ.journalService() - url, action_register = journalService.csv(logger=logger) - # for ar in action_register: - # job.add_audit_message(ar) + url, action_register = journalService.csv(logger=_l) + + # Log directly to the task if we don't have extra logging configured + if _l is None: + for ar in action_register: + job.add_audit_message(ar) + job.add_audit_message("CSV generated; will be served from {y}".format(y=url)) def cleanup(self): diff --git a/portality/tasks/preservation.py b/portality/tasks/preservation.py index 037ba4c1dd..4fc1cf09b9 100644 --- a/portality/tasks/preservation.py +++ b/portality/tasks/preservation.py @@ -5,7 +5,6 @@ import shutil import tarfile from copy import deepcopy -from datetime import datetime from zipfile import ZipFile import requests @@ -125,11 +124,15 @@ def __init__(self): self.__unbagged_articles = [] self.__not_found_articles = [] self.__no_files_articles = [] + self.__uploaded_journals = [] self.has_errors = False def add_successful_article(self, article: ArticlePackage): self.__successful_articles.append(os.path.basename(article.article_dir)) + def add_uploaded_journal(self, journal_package): + self.__uploaded_journals.append(journal_package) + def add_unowned_articles(self, article: ArticlePackage): self.has_errors = True self.__unowned_articles.append(os.path.basename(article.article_dir)) @@ -167,6 +170,9 @@ def not_found_articles(self): def no_files_articles(self): return self.__no_files_articles + def uploaded_journals(self): + return self.__uploaded_journals + def get_count(self): return len(self.__successful_articles) + \ len(self.__unowned_articles) + \ @@ -242,24 +248,50 @@ def run(self): job.add_audit_message("Create Package structure") articles_list = preserv.create_package_structure() - self.save_articles_list(articles_list, preserve_model) + app.logger.debug("Created package structure") if len(articles_list.successful_articles()) > 0: - package = PreservationPackage(preserv.preservation_dir, job.user) - job.add_audit_message("Create preservation package") - tar_file = package.create_package() - app.logger.debug(f"Created tar file {tar_file}") + # Each subdirectory is a jornal and the directory name is ISSN of the journal + # iterate through the directories and upload each journal as an individual package + dirs = [f.name for f in os.scandir(preserv.preservation_dir) if f.is_dir()] + upload_failed = False + for sub_dir in dirs: + + package = PreservationPackage(preserv.preservation_dir, sub_dir, job.user) + job.add_audit_message("Create preservation package for " + sub_dir) + tar_file = package.create_package() + + app.logger.debug(f"Created tar file {tar_file}") + + job.add_audit_message("Create shasum for " + sub_dir) + sha256 = package.sha256(package.tar_file) + + job.add_audit_message("Upload package " + sub_dir) + response = package.upload_package(sha256, package.tar_file) + app.logger.debug(f"Uploaded. Response{response.text}") + + job.add_audit_message("Validate response") + self.validate_response(response, tar_file, sha256, preserve_model) + + if preserve_model.status == 'failed': + upload_failed = True + break + else: + articles_list.add_uploaded_journal(package.tar_file_name) - job.add_audit_message("Create shasum") - sha256 = package.sha256() + # Upload the identifier file + job.add_audit_message("Create shasum for identifier") + sha256 = package.sha256(package.identifier_file) - job.add_audit_message("Upload package") - response = package.upload_package(sha256) - app.logger.debug(f"Uploaded. Response{response.text}") + identifier_file_name = os.path.basename(package.identifier_file) + job.add_audit_message("Upload identifier file " + identifier_file_name) + package.upload_package(sha256, package.identifier_file) + articles_list.add_uploaded_journal(identifier_file_name) + app.logger.debug(f"Uploaded identifier file " + identifier_file_name) - job.add_audit_message("Validate response") - self.validate_response(response, tar_file, sha256, preserve_model) + if not upload_failed: + preserve_model.uploaded_to_ia() # Check if the only few articles are successful if articles_list.is_partial_success(): @@ -277,6 +309,8 @@ def run(self): preserve_model.failed(FailedReasons.no_valid_article_available) preserve_model.save() + self.save_articles_list(articles_list, preserve_model) + except (PreservationException, Exception) as exp: # ~~-> PreservationException:Exception~~ preserve_model.failed(str(exp)) @@ -304,6 +338,8 @@ def save_articles_list(self, articles_list: ArticlesList, model: PreservationSta model.unbagged_articles(articles_list.unbagged_articles()) if len(articles_list.no_files_articles()) > 0: model.no_files_articles(articles_list.no_files_articles()) + if len(articles_list.uploaded_journals()) > 0: + model.uploaded_journals(articles_list.uploaded_journals()) model.save() def cleanup(self): @@ -344,8 +380,7 @@ def validate_response(self, response, tar_file, sha256, model): if res_filename and res_filename == tar_file: if res_shasum and res_shasum == sha256: - app.logger.info("successfully uploaded") - model.uploaded_to_ia() + app.logger.info("successfully uploaded " + tar_file) else: model.failed(FailedReasons.checksum_doesnot_match) else: @@ -378,7 +413,7 @@ def validate_response(self, response, tar_file, sha256, model): model.save() else: - app.logger.error(f"Upload failed {response.text}") + app.logger.error(f"Upload failed for {tar_file}. Reason - {response.text}") model.failed(response.text) model.save() @@ -534,11 +569,13 @@ def create_package_structure(self) -> ArticlesList: # Fetch identifiers at the root directory if os.path.dirname(dir) == self.__local_dir: - if Preservation.IDENTIFIERS_CSV in files: - # Get articles info from csv file - # ~~-> CSVReader:Feature~~ - csv_reader = CSVReader(os.path.join(dir, Preservation.IDENTIFIERS_CSV)) - self.__csv_articles_dict = csv_reader.articles_info() + for file in files: + if Preservation.IDENTIFIERS_CSV.lower() == file.lower(): + # Get articles info from csv file + # ~~-> CSVReader:Feature~~ + csv_reader = CSVReader(os.path.join(dir, file)) + self.__csv_articles_dict = csv_reader.articles_info() + break # process only the directories that has articles else: self.__process_article(dir, files, articles_list) @@ -557,10 +594,12 @@ def __process_article(self, dir_path, files, articles_list): return # check if identifier file exist - if Preservation.IDENTIFIER_FILE in files: - with open(os.path.join(dir_path, Preservation.IDENTIFIER_FILE)) as file: - identifiers = file.read().splitlines() - elif self.__csv_articles_dict: + for file in files: + if Preservation.IDENTIFIER_FILE.lower() == file.lower(): + with open(os.path.join(dir_path, file)) as identifier_file: + identifiers = identifier_file.read().splitlines() + + if not identifiers and self.__csv_articles_dict: if dir_name in self.__csv_articles_dict: identifiers = self.__csv_articles_dict[dir_name] @@ -570,10 +609,9 @@ def __process_article(self, dir_path, files, articles_list): if article: article_data = article.data - if not self.owner_of_article(article): - articles_list.add_unowned_articles(package) + is_owner = self.owner_of_article(article) - else: + if isinstance(is_owner, bool) and is_owner == True: issn, article_id, metadata_json = self.get_article_info(article_data) try: package = ArticlePackage(dir_path, files) @@ -584,10 +622,17 @@ def __process_article(self, dir_path, files, articles_list): package.create_article_bagit_structure() + # Create and update the identifier file for all articles in the journal + with open(os.path.join(self.__preservation_dir, issn + ".txt"), 'a') as identifier_file: + identifier_file.write(os.path.basename(dir_path) + "," + article_id + "," + + ','.join(identifiers) + "\n") + articles_list.add_successful_article(package) except Exception: articles_list.add_unbagged_articles(package) app.logger.exception(f"Error while create article ( {article_id} ) package") + else: + articles_list.add_unowned_articles(package) else: # skip the article if not found @@ -677,11 +722,20 @@ class PreservationPackage: Creates preservation package and upload to Internet Server """ - def __init__(self, directory, owner): - self.package_dir = directory - self.tar_file = self.package_dir + ".tar.gz" + def __init__(self, preservation_dir, journal_dir, owner): + self.preservation_dir = preservation_dir + self.journal_dir = journal_dir + self.package_dir = os.path.join(self.preservation_dir, journal_dir) + self.created_time = dates.now_str("%Y-%m-%d-%H-%M-%S") + self.tar_file = self.package_dir + "_" + self.created_time + ".tar.gz" self.tar_file_name = os.path.basename(self.tar_file) self.__owner = owner + self.identifier_file = self.package_dir + "_" + self.created_time + ".txt" + try: + # Rename the identifier file to match the tar file + shutil.move(self.package_dir + ".txt", self.identifier_file) + except Exception as e: + app.logger.exception(e) def create_package(self): """ @@ -697,7 +751,7 @@ def create_package(self): return self.tar_file_name - def upload_package(self, sha256sum): + def upload_package(self, sha256sum, file): url = app.config.get("PRESERVATION_URL") username = app.config.get("PRESERVATION_USERNAME") @@ -707,7 +761,7 @@ def upload_package(self, sha256sum): collection = params[0] collection_id = params[1] - file_name = os.path.basename(self.tar_file) + file_name = os.path.basename(file) # payload for upload request payload = { @@ -727,7 +781,7 @@ def upload_package(self, sha256sum): headers = {} # get the file to upload try: - with open(self.tar_file, "rb") as f: + with open(file, "rb") as f: files = {'file_field': (file_name, f)} response = requests.post(url, headers=headers, auth=(username, password), files=files, data=payload) except (IOError, Exception) as exp: @@ -736,13 +790,13 @@ def upload_package(self, sha256sum): return response - def sha256(self): + def sha256(self, file): """ Creates sha256 hash for the tar file """ sha256_hash = hashlib.sha256() - with open(self.tar_file, "rb") as f: + with open(file, "rb") as f: # Read and update hash string value in blocks of 64K for byte_block in iter(lambda: f.read(65536), b""): sha256_hash.update(byte_block) diff --git a/portality/templates/data/sponsors.html b/portality/templates/data/sponsors.html index 5dfee23989..4cffdbf57b 100644 --- a/portality/templates/data/sponsors.html +++ b/portality/templates/data/sponsors.html @@ -1,35 +1,7 @@ -{% if data.sponsors.gold %} -

Premier contributors

-
- {% for i in data.sponsors.gold %} - - {% endfor %} -
-{% endif %} -
- -

Sustaining contributors

-
- {% for i in data.sponsors.silver %} - - {% endfor %} -
- -
- -

Basic contributors

- {% for i in data.sponsors.bronze %} - + {% endif %} -

- - -
- {% include "includes/_hotjar.html" %} - -{% endblock %} -{% block extra_js_bottom %} + {% if bibjson.pid_scheme %} +
+ +
+

Permanent article identifier:

+
    + {% for identifier in bibjson.pid_scheme %} +
  • {{ identifier }}
  • + {% endfor %} +
+
+
+ {% endif %} + + +
+

Journal metadata

+ +
+ +
+ {% if bibjson.publisher_name %} +
Publisher
+
+ {% set source = search_query_source(terms=[{"bibjson.publisher.name.exact": [bibjson.publisher_name]}]) %} + {{ bibjson.publisher_name }} + {% if bibjson.publisher_country %}, {{ bibjson.publisher_country_name() }}{% endif %} +
+ {% endif %} - + {% if bibjson.language %} +
Manuscripts accepted in
+
{{ bibjson.language_name()|join(", ") }}
+ {% endif %} +
+
+ +
+ +
+ {% for path, code in bibjson.lcc_paths_and_codes() %} + {% if loop.index0 == 0 %} +
LCC subjects + + Look up the Library of Congress Classification Outline + +
{% endif %} +
+ {% set source = search_query_source(terms=[{"index.schema_codes_tree.exact": [code]}]) %} + + {{ path }} + +
+ {% endfor %} + + {% if bibjson.keywords %} +
Keywords
+
+ {% for keyword in bibjson.keywords %} + {% set source = search_query_source(query_string=keyword) %} + {{ keyword }} + {% endfor %} +
+ {% endif %} +
+
+
+ + - {% include "_edges_common_js.html" %} - +{% endblock %} -{% endblock extra_js_bottom %} diff --git a/portality/templates/doaj/toc_articles.html b/portality/templates/doaj/toc_articles.html new file mode 100644 index 0000000000..2446a355df --- /dev/null +++ b/portality/templates/doaj/toc_articles.html @@ -0,0 +1,44 @@ +{% extends "layouts/toc_base.html" %} + +{% block toc_content %} + +
+
+
+
+
+ +{% endblock %} + +{% block extra_js_bottom %} + + + + {% include "_edges_common_js.html" %} + + +{% endblock extra_js_bottom %} diff --git a/portality/templates/includes/contribution_rates.html b/portality/templates/includes/contribution_rates.html index 2449e24e3c..9c2e3da277 100644 --- a/portality/templates/includes/contribution_rates.html +++ b/portality/templates/includes/contribution_rates.html @@ -1,303 +1,3 @@ -
    - -
  • -

    2023 pricing for academic institutions, including:

    -
      -
    • Individual institutions
    • -
    • Academic libraries
    • -
    • A university or universities
    • -
    • Colleges
    • -
    -

    - -

    - -
  • - - -
  • -

    2023 pricing for institutions that are members of:

    -
      -
    • A billing consortium
    • -
    -

    - -

    - -
  • - -
diff --git a/portality/templates/layouts/toc_base.html b/portality/templates/layouts/toc_base.html new file mode 100644 index 0000000000..dff69e334f --- /dev/null +++ b/portality/templates/layouts/toc_base.html @@ -0,0 +1,147 @@ +{% extends "layouts/public_base.html" %} + +{% block body_class %} + journal-details +{% endblock %} + +{% block page_title %}{% include "doaj/includes/_journal_meta_title.html" %}{% endblock %} +{% block meta_og_title %}{% include "doaj/includes/_journal_meta_title.html" %}{% endblock %} +{% block meta_twitter_title %}{% include "doaj/includes/_journal_meta_title.html" %}{% endblock %} +{%- block meta_description -%}{% include "doaj/includes/_journal_meta_description.html" %}{%- endblock -%} +{%- block meta_og_description -%}{% include "doaj/includes/_journal_meta_description.html" %}{%- endblock -%} +{%- block meta_twitter_description -%}{% include "doaj/includes/_journal_meta_description.html" %}{%- endblock -%} + +{% block content %} + + +
+
+ {% if journal.last_manually_updated_since(days=30) %} + + + Updated recently + + {% endif %} +

+ + {{ bibjson.title }} + {% if bibjson.alternative_title %} + + {% endif %} + + {%- set seal = journal.has_seal() -%} + {%- if seal -%} + + {%- endif %} +

+

+ + + {# this next bit has to be all on one line so that the spacing is correct #} + {% if bibjson.pissn %}{{bibjson.pissn}} (Print){% endif %}{% if bibjson.eissn %}{% if bibjson.pissn %} / {% endif %}{{bibjson.eissn}} (Online){% endif %} + +

+ + {% if bibjson.discontinued_date is not none and bibjson.discontinued_date | is_in_the_past %} +

Ceased publication on {{ bibjson.discontinued_datestamp.strftime("%d %B %Y") }}

+ {% endif %} + + {% set past = journal.get_past_continuations() %} + {% if past %} +

Continues + {% for p in past %} + {% set bibjson = p.bibjson() %} + {% if bibjson.issns()|length > 0 %} + {% if p.is_in_doaj() %} + {{ bibjson.title }} + {% else %} + {{ bibjson.title }}, ISSN: {{ bibjson.get_preferred_issn() }} (not available in DOAJ) + {% endif %} + {% endif %} + {% if not loop.last %}; {% endif %} + {% endfor %} +

+ {% endif %} + + {% set future = journal.get_future_continuations() %} + {% if future %} +

Continued by + {% for f in future %} + {% set bibjson = f.bibjson() %} + {% if bibjson.issns()|length > 0 %} + {% if f.is_in_doaj() %} + {{ bibjson.title }} + {% else %} + {{ bibjson.title }}, ISSN: {{ bibjson.get_preferred_issn() }} (not available in DOAJ) + {% endif %} + {% endif %} + {% if not loop.last %}; {% endif %} + {% endfor %} +

+ {% endif %} + + +
+ +
+ + +
+ {% block toc_content %} {% endblock %} +
+
+ +
+

+ Added {{journal.created_timestamp.strftime("%e %B %Y")}} + {% if journal.last_manual_update_timestamp and + dates.format(journal.last_manual_update_timestamp) != dates.DEFAULT_TIMESTAMP_VAL + %} + • Updated {{journal.last_manual_update_timestamp.strftime("%e %B %Y")}} + {% endif %} +

+
+ +
+ {% include "includes/_hotjar.html" %} + +{% endblock %} diff --git a/portality/templates/publisher/help.html b/portality/templates/publisher/help.html index 9f4daddeb4..abf09a4cff 100644 --- a/portality/templates/publisher/help.html +++ b/portality/templates/publisher/help.html @@ -6,7 +6,7 @@

Help for publishers

-

Uploading metadata/article content a file

+

Uploading metadata/article content

There are three ways to upload article metadata to DOAJ:

@@ -20,6 +20,8 @@

Uploading metadata/article content a file

There are instructions on how to prepare and upload your XML file on our XML documentation page.

+

Are you receiving an error about one of your ISSNs that you haven't seen before? We recently changed the rules for uploading article metadata. We now require that a Print ISSN is in an issn tag and the Online ISSN is in an eissn tag. [See our sample XML file](https://doaj.org/docs/xml/#example-doaj-xml-file) for more information.

+

Failed XML uploads explained

This section explains the error messages that you may see when you upload article XML. Use the message in the 'Notes' column of your History of uploads table to correct your XML.

diff --git a/portality/templates/publisher/preservation.html b/portality/templates/publisher/preservation.html index 611eb380af..d4bcb03722 100644 --- a/portality/templates/publisher/preservation.html +++ b/portality/templates/publisher/preservation.html @@ -144,6 +144,10 @@

History of uploads (showing last {{previous|length}})

{% endif %} {% if file.status == "uploaded" or file.status == "partial" and file.articles_info %}