From c3c2c55e9fcf15027fb389e70817a8cf4c08e2de Mon Sep 17 00:00:00 2001 From: Zhenghao-Zhao Date: Thu, 28 Mar 2019 11:09:17 +1100 Subject: [PATCH 001/154] Initial commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4063c6 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Gnosis-dev \ No newline at end of file From 90e480e8e3c82044a4c59de7d841811a878ffb56 Mon Sep 17 00:00:00 2001 From: Zhenghao-Zhao Date: Thu, 28 Mar 2019 11:35:09 +1100 Subject: [PATCH 002/154] add files --- stellar-gnosis | 1 + 1 file changed, 1 insertion(+) create mode 160000 stellar-gnosis diff --git a/stellar-gnosis b/stellar-gnosis new file mode 160000 index 0000000..9ac34e9 --- /dev/null +++ b/stellar-gnosis @@ -0,0 +1 @@ +Subproject commit 9ac34e9003bd6088251fdf6d02395a7fc7ad7413 From 6c6b17b74f812941baa128cd3be40a43cf147311 Mon Sep 17 00:00:00 2001 From: Zhenghao-Zhao Date: Thu, 28 Mar 2019 11:40:51 +1100 Subject: [PATCH 003/154] add files --- .gitignore | 4 + LICENSE | 201 +++ gnosis/catalog/__init__.py | 1 + gnosis/catalog/admin.py | 3 + gnosis/catalog/apps.py | 5 + gnosis/catalog/forms.py | 332 ++++ gnosis/catalog/migrations/__init__.py | 0 gnosis/catalog/models.py | 197 +++ .../static/catalog/csiro-data61-logos.png | Bin 0 -> 11940 bytes .../static/catalog/csiro-data61-logos.xcf | Bin 0 -> 23457 bytes .../static/catalog/csiro-logo-black.png | Bin 0 -> 5065 bytes gnosis/catalog/static/catalog/data61-logo.png | Bin 0 -> 3785 bytes gnosis/catalog/templates/build.html | 9 + gnosis/catalog/templates/code_detail.html | 24 + gnosis/catalog/templates/code_find.html | 16 + gnosis/catalog/templates/code_form.html | 17 + gnosis/catalog/templates/code_update.html | 16 + gnosis/catalog/templates/codes.html | 31 + gnosis/catalog/templates/comment_detail.html | 12 + gnosis/catalog/templates/comment_form.html | 11 + gnosis/catalog/templates/comment_update.html | 14 + gnosis/catalog/templates/comments.html | 15 + gnosis/catalog/templates/dataset_detail.html | 33 + gnosis/catalog/templates/dataset_find.html | 16 + gnosis/catalog/templates/dataset_form.html | 17 + gnosis/catalog/templates/dataset_update.html | 16 + gnosis/catalog/templates/datasets.html | 30 + gnosis/catalog/templates/paper_authors.html | 27 + .../templates/paper_connect_author.html | 35 + .../catalog/templates/paper_connect_code.html | 35 + .../templates/paper_connect_dataset.html | 26 + .../templates/paper_connect_paper.html | 27 + .../templates/paper_connect_venue.html | 29 + gnosis/catalog/templates/paper_detail.html | 121 ++ gnosis/catalog/templates/paper_find.html | 17 + gnosis/catalog/templates/paper_form.html | 22 + gnosis/catalog/templates/paper_results.html | 18 + gnosis/catalog/templates/paper_update.html | 16 + gnosis/catalog/templates/papers.html | 29 + gnosis/catalog/templates/people.html | 35 + gnosis/catalog/templates/people_results.html | 25 + gnosis/catalog/templates/person_detail.html | 32 + gnosis/catalog/templates/person_find.html | 16 + gnosis/catalog/templates/person_form.html | 16 + gnosis/catalog/templates/person_update.html | 16 + gnosis/catalog/templates/persons.html | 14 + gnosis/catalog/templates/venue_detail.html | 34 + gnosis/catalog/templates/venue_find.html | 16 + gnosis/catalog/templates/venue_form.html | 15 + gnosis/catalog/templates/venue_update.html | 16 + gnosis/catalog/templates/venues.html | 36 + gnosis/catalog/tests.py | 3 + gnosis/catalog/urls.py | 70 + gnosis/catalog/views/__init__.py | 3 + gnosis/catalog/views/views.py | 1377 +++++++++++++++++ gnosis/catalog/views/views_codes.py | 147 ++ gnosis/catalog/views/views_people.py | 154 ++ gnosis/gnosis/__init__.py | 0 gnosis/gnosis/settings.py | 137 ++ gnosis/gnosis/urls.py | 39 + gnosis/gnosis/wsgi.py | 16 + gnosis/home/__init__.py | 0 gnosis/home/admin.py | 3 + gnosis/home/apps.py | 5 + gnosis/home/migrations/__init__.py | 0 gnosis/home/models.py | 3 + gnosis/home/templates/home.html | 34 + gnosis/home/tests.py | 3 + gnosis/home/urls.py | 6 + gnosis/home/views.py | 31 + gnosis/manage.py | 15 + .../registration_complete.html | 11 + .../registration_form.html | 17 + gnosis/templates/gnosis_theme.html | 190 +++ gnosis/templates/registration/logged_out.html | 9 + gnosis/templates/registration/login.html | 48 + .../registration/password_reset_complete.html | 9 + .../registration/password_reset_confirm.html | 33 + .../registration/password_reset_done.html | 8 + .../registration/password_reset_email.html | 8 + .../registration/password_reset_form.html | 11 + requirements.txt | 29 + setup.py | 26 + stellar-gnosis | 1 - 84 files changed, 4138 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 gnosis/catalog/__init__.py create mode 100644 gnosis/catalog/admin.py create mode 100644 gnosis/catalog/apps.py create mode 100644 gnosis/catalog/forms.py create mode 100644 gnosis/catalog/migrations/__init__.py create mode 100644 gnosis/catalog/models.py create mode 100644 gnosis/catalog/static/catalog/csiro-data61-logos.png create mode 100644 gnosis/catalog/static/catalog/csiro-data61-logos.xcf create mode 100644 gnosis/catalog/static/catalog/csiro-logo-black.png create mode 100644 gnosis/catalog/static/catalog/data61-logo.png create mode 100644 gnosis/catalog/templates/build.html create mode 100644 gnosis/catalog/templates/code_detail.html create mode 100644 gnosis/catalog/templates/code_find.html create mode 100644 gnosis/catalog/templates/code_form.html create mode 100644 gnosis/catalog/templates/code_update.html create mode 100644 gnosis/catalog/templates/codes.html create mode 100644 gnosis/catalog/templates/comment_detail.html create mode 100644 gnosis/catalog/templates/comment_form.html create mode 100644 gnosis/catalog/templates/comment_update.html create mode 100644 gnosis/catalog/templates/comments.html create mode 100644 gnosis/catalog/templates/dataset_detail.html create mode 100644 gnosis/catalog/templates/dataset_find.html create mode 100644 gnosis/catalog/templates/dataset_form.html create mode 100644 gnosis/catalog/templates/dataset_update.html create mode 100644 gnosis/catalog/templates/datasets.html create mode 100644 gnosis/catalog/templates/paper_authors.html create mode 100644 gnosis/catalog/templates/paper_connect_author.html create mode 100644 gnosis/catalog/templates/paper_connect_code.html create mode 100644 gnosis/catalog/templates/paper_connect_dataset.html create mode 100644 gnosis/catalog/templates/paper_connect_paper.html create mode 100644 gnosis/catalog/templates/paper_connect_venue.html create mode 100644 gnosis/catalog/templates/paper_detail.html create mode 100644 gnosis/catalog/templates/paper_find.html create mode 100644 gnosis/catalog/templates/paper_form.html create mode 100644 gnosis/catalog/templates/paper_results.html create mode 100644 gnosis/catalog/templates/paper_update.html create mode 100644 gnosis/catalog/templates/papers.html create mode 100644 gnosis/catalog/templates/people.html create mode 100644 gnosis/catalog/templates/people_results.html create mode 100644 gnosis/catalog/templates/person_detail.html create mode 100644 gnosis/catalog/templates/person_find.html create mode 100644 gnosis/catalog/templates/person_form.html create mode 100644 gnosis/catalog/templates/person_update.html create mode 100644 gnosis/catalog/templates/persons.html create mode 100644 gnosis/catalog/templates/venue_detail.html create mode 100644 gnosis/catalog/templates/venue_find.html create mode 100644 gnosis/catalog/templates/venue_form.html create mode 100644 gnosis/catalog/templates/venue_update.html create mode 100644 gnosis/catalog/templates/venues.html create mode 100644 gnosis/catalog/tests.py create mode 100644 gnosis/catalog/urls.py create mode 100644 gnosis/catalog/views/__init__.py create mode 100644 gnosis/catalog/views/views.py create mode 100644 gnosis/catalog/views/views_codes.py create mode 100644 gnosis/catalog/views/views_people.py create mode 100644 gnosis/gnosis/__init__.py create mode 100644 gnosis/gnosis/settings.py create mode 100644 gnosis/gnosis/urls.py create mode 100644 gnosis/gnosis/wsgi.py create mode 100644 gnosis/home/__init__.py create mode 100644 gnosis/home/admin.py create mode 100644 gnosis/home/apps.py create mode 100644 gnosis/home/migrations/__init__.py create mode 100644 gnosis/home/models.py create mode 100644 gnosis/home/templates/home.html create mode 100644 gnosis/home/tests.py create mode 100644 gnosis/home/urls.py create mode 100644 gnosis/home/views.py create mode 100644 gnosis/manage.py create mode 100644 gnosis/templates/django_registration/registration_complete.html create mode 100644 gnosis/templates/django_registration/registration_form.html create mode 100644 gnosis/templates/gnosis_theme.html create mode 100644 gnosis/templates/registration/logged_out.html create mode 100644 gnosis/templates/registration/login.html create mode 100644 gnosis/templates/registration/password_reset_complete.html create mode 100644 gnosis/templates/registration/password_reset_confirm.html create mode 100644 gnosis/templates/registration/password_reset_done.html create mode 100644 gnosis/templates/registration/password_reset_email.html create mode 100644 gnosis/templates/registration/password_reset_form.html create mode 100644 requirements.txt create mode 100644 setup.py delete mode 160000 stellar-gnosis diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..01294de --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +.DS_Store +*.sqlite3 +__pycache__/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/gnosis/catalog/__init__.py b/gnosis/catalog/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/gnosis/catalog/__init__.py @@ -0,0 +1 @@ + diff --git a/gnosis/catalog/admin.py b/gnosis/catalog/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/gnosis/catalog/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/gnosis/catalog/apps.py b/gnosis/catalog/apps.py new file mode 100644 index 0000000..ac42aa1 --- /dev/null +++ b/gnosis/catalog/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class CatalogConfig(AppConfig): + name = 'catalog' diff --git a/gnosis/catalog/forms.py b/gnosis/catalog/forms.py new file mode 100644 index 0000000..1a402aa --- /dev/null +++ b/gnosis/catalog/forms.py @@ -0,0 +1,332 @@ +from django import forms +from django.forms import ModelForm, Form +from .models import Paper, Person, Dataset, Venue, Comment, Code + + +# +# Search forms +# +class SearchVenuesForm(Form): + + def __init__(self, *args, **kwargs): + super(Form, self).__init__(*args, **kwargs) + for visible in self.visible_fields(): + visible.field.widget.attrs['class'] = 'form-control' + + def clean_venue_name(self): + return self.cleaned_data['venue_name'] + + def clean_venue_publication_year(self): + return self.cleaned_data['venue_publication_year'] + + venue_name = forms.CharField(required=True) + venue_publication_year = forms.CharField(required=True) + + +class SearchDatasetsForm(Form): + + def __init__(self, *args, **kwargs): + super(Form, self).__init__(*args, **kwargs) + + self.fields['name'].label = 'Name' + self.fields['keywords'].label = 'Keyword (single keyword, e.g. network, computer vision)' + + for visible in self.visible_fields(): + visible.field.widget.attrs['class'] = 'form-control' + + def clean_name(self): + return self.cleaned_data['name'] + + def clean_keywords(self): + return self.cleaned_data['keywords'] + + # one of them is required but we are going to enforce this in the + # view code because we don't know at this stage which one of the + # two the user will specify and we want to give her the option to + # search by dataset name or keywords or both. + name = forms.CharField(required=False) + keywords = forms.CharField(required=False) + + +class SearchPapersForm(Form): + + def __init__(self, *args, **kwargs): + super(Form, self).__init__(*args, **kwargs) + for visible in self.visible_fields(): + visible.field.widget.attrs['class'] = 'form-control' + + def clean_paper_title(self): + return self.cleaned_data['paper_title'] + + paper_title = forms.CharField(required=True) + + +class SearchPeopleForm(Form): + + def __init__(self, *args, **kwargs): + super(Form, self).__init__(*args, **kwargs) + for visible in self.visible_fields(): + visible.field.widget.attrs['class'] = 'form-control' + + def clean_person_name(self): + return self.cleaned_data['person_name'] + + person_name = forms.CharField(required=True) + + +class SearchCodesForm(Form): + + def __init__(self, *args, **kwargs): + super(Form, self).__init__(*args, **kwargs) + + self.fields['keywords'].label = 'Keywords (e.g. GCN, network, computer vision)' + + for visible in self.visible_fields(): + visible.field.widget.attrs['class'] = 'form-control' + + def clean_keywords(self): + return self.cleaned_data['keywords'] + + keywords = forms.CharField(required=False) + + +# +# Model forms +# +class PaperForm(ModelForm): + + def __init__(self, *args, **kwargs): + super(ModelForm, self).__init__(*args, **kwargs) + + self.fields['abstract'].widget = forms.Textarea() + self.fields['abstract'].widget.attrs.update({'rows': '8'}) + self.fields['title'].label = 'Title*' + self.fields['abstract'].label = 'Abstract*' + self.fields['keywords'].label = 'Keywords' + self.fields['download_link'].label = 'Download Link*' + + for visible in self.visible_fields(): + visible.field.widget.attrs['class'] = 'form-control' + + def clean_title(self): + return self.cleaned_data['title'] + + def clean_abstract(self): + return self.cleaned_data['abstract'] + + def clean_keywords(self): + return self.cleaned_data['keywords'] + + def clean_download_link(self): + return self.cleaned_data['download_link'] + + class Meta: + model = Paper + fields = ['title', 'abstract', 'keywords', 'download_link'] + + +class PaperImportForm(Form): + """ + A form for importing a paper from a website such as arXiv.org. + The form only present the user with a field to enter a url. + """ + def __init__(self, *args, **kwargs): + super(Form, self).__init__(*args, **kwargs) + + for visible in self.visible_fields(): + visible.field.widget.attrs['class'] = 'form-control' + + def clean_url(self): + return self.cleaned_data['url'] + + url = forms.CharField(label='Source URL, e.g., https://arxiv.org/abs/1607.00653*', max_length=200) + + +class PersonForm(ModelForm): + + def __init__(self, *args, **kwargs): + super(ModelForm, self).__init__(*args, **kwargs) + + self.fields['first_name'].label = 'First Name*' + self.fields['middle_name'].label = 'Middle Name' + self.fields['last_name'].label = 'Last Name*' + self.fields['affiliation'].label = 'Affiliation' + self.fields['website'].label = 'Website' + + for visible in self.visible_fields(): + visible.field.widget.attrs['class'] = 'form-control' + visible.field.widget.attrs.update({'style': 'width:25em'}) + print(visible.field.widget.attrs.items()) + + def clean_first_name(self): + return self.cleaned_data['first_name'] + + def clean_middle_name(self): + return self.cleaned_data['middle_name'] + + def clean_last_name(self): + return self.cleaned_data['last_name'] + + def clean_affiliation(self): + return self.cleaned_data['affiliation'] + + def clean_website(self): + return self.cleaned_data['website'] + + class Meta: + model = Person + fields = ['first_name', 'middle_name', 'last_name', 'affiliation', 'website'] + + +class DatasetForm(ModelForm): + + def __init__(self, *args, **kwargs): + super(ModelForm, self).__init__(*args, **kwargs) + # The default for the description field widget is text input. Buy we want to display + # more than one rows so we replace it with a Textarea widget. + self.fields['description'].widget = forms.Textarea() + self.fields['description'].widget.attrs.update({'rows': '5'}) + + self.fields['name'].label = 'Name*' + self.fields['keywords'].label = 'Keywords*' + self.fields['description'].label = 'Description*' + self.fields['source_type'].label = 'Type*' + self.fields['publication_date'].label = 'Publication Date (yyyy-mm-dd)' + self.fields['website'].label = 'Website' + + for visible in self.visible_fields(): + visible.field.widget.attrs['class'] = 'form-control' + visible.field.widget.attrs.update({'style': 'width:25em'}) + + print(type(self.fields['description'].widget)) + print(self.fields['description'].widget.attrs.items()) + + def clean_name(self): + return self.cleaned_data['name'] + + def clean_keywords(self): + return self.cleaned_data['keywords'] + + def clean_description(self): + return self.cleaned_data['description'] + + def clean_source_type(self): + return self.cleaned_data['source_type'] + + def clean_publication_date(self): + return self.cleaned_data['publication_date'] + + def clean_website(self): + return self.cleaned_data['website'] + + class Meta: + model = Dataset + fields = ['name', 'keywords', 'description', 'source_type', 'publication_date', 'website'] + + +class VenueForm(ModelForm): + + def __init__(self, *args, **kwargs): + super(ModelForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = 'Name*' + self.fields['publisher'].label = 'Publisher*' + # self.fields['publication_date'].help_text = 'YYYY-MM-DD' + self.fields['publication_date'].label = 'Publication Date (yyyy-mm-dd)*' + self.fields['type'].label = 'Type*' + self.fields['peer_reviewed'].label = 'Peer Reviewed*' + self.fields['keywords'].label = 'Keywords*' + self.fields['website'].label = 'Website' + + for visible in self.visible_fields(): + visible.field.widget.attrs['class'] = 'form-control' + visible.field.widget.attrs.update({'style': 'width:25em'}) + print(visible.field.widget.attrs.items()) + + def clean_name(self): + return self.cleaned_data['name'] + + def clean_publisher(self): + return self.cleaned_data['publisher'] + + def clean_publication_date(self): + return self.cleaned_data['publication_date'] + + def clean_type(self): + return self.cleaned_data['type'] + + def clean_peer_reviewed(self): + return self.cleaned_data['peer_reviewed'] + + def clean_keywords(self): + return self.cleaned_data['keywords'] + + def clean_website(self): + return self.cleaned_data['website'] + + class Meta: + model = Venue + fields = ['name', 'publisher', 'publication_date', 'type', 'peer_reviewed', 'keywords', 'website'] + + +class CommentForm(ModelForm): + + def __init__(self, *args, **kwargs): + super(ModelForm, self).__init__(*args, **kwargs) + + self.fields['text'].widget = forms.Textarea() + self.fields['text'].widget.attrs.update({'rows': '5'}) + self.fields['text'].label = '' + + for visible in self.visible_fields(): + visible.field.widget.attrs['class'] = 'form-control' + visible.field.widget.attrs.update({'style': 'width:35em'}) + print(visible.field.widget.attrs.items()) + + def clean_text(self): + return self.cleaned_data['text'] + + def clean_publication_date(self): + return self.cleaned_data['publication_date'] + + # def clean_author(self): + # return self.cleaned_data['author'] + + class Meta: + model = Comment + fields = ['text'] + + +class CodeForm(ModelForm): + + def __init__(self, *args, **kwargs): + super(ModelForm, self).__init__(*args, **kwargs) + # The default for the description field widget is text input. Buy we want to display + # more than one rows so we replace it with a Textarea widget. + self.fields['description'].widget = forms.Textarea() + self.fields['description'].widget.attrs.update({'rows': '5'}) + + self.fields['website'].label = 'Website*' + self.fields['keywords'].label = 'Keywords*' + self.fields['description'].label = 'Description*' + + for visible in self.visible_fields(): + visible.field.widget.attrs['class'] = 'form-control' + visible.field.widget.attrs.update({'style': 'width:25em'}) + + # print(type(self.fields['description'].widget)) + # print(self.fields['description'].widget.attrs.items()) + + def clean_keywords(self): + return self.cleaned_data['keywords'] + + def clean_description(self): + return self.cleaned_data['description'] + + def clean_website(self): + return self.cleaned_data['website'] + + class Meta: + model = Code + fields = ['website', 'keywords', 'description'] + diff --git a/gnosis/catalog/migrations/__init__.py b/gnosis/catalog/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gnosis/catalog/models.py b/gnosis/catalog/models.py new file mode 100644 index 0000000..fafd42b --- /dev/null +++ b/gnosis/catalog/models.py @@ -0,0 +1,197 @@ +from datetime import datetime +from django_neomodel import DjangoNode +from django.urls import reverse +from neomodel import StringProperty, DateTimeProperty, DateProperty, UniqueIdProperty, \ + IntegerProperty, RelationshipTo + + +# Create your models here. +class Paper(DjangoNode): + + uid = UniqueIdProperty() + + created = DateTimeProperty(default=datetime.now()) + created_by = IntegerProperty() # The uid of the user who created this node + + # These are always required + title = StringProperty(required=True) + abstract = StringProperty(required=True) + keywords = StringProperty(required=False) + download_link = StringProperty(required=True) + + # Links + cites = RelationshipTo("Paper", "cites") + uses = RelationshipTo("Paper", "uses") + extends = RelationshipTo("Paper", "extends") + evaluates_on = RelationshipTo("Dataset", "evaluates_on") + was_published_at = RelationshipTo("Venue", "was_published_at") + published = RelationshipTo("Dataset", "published") + + class Meta: + app_label = 'catalog' + ordering = ["title", "-published"] # title is A-Z and published is from newest to oldest + + def __str__(self): + """ + String for representing the Paper object, e.g., in Admin site. + :return: The paper's title + """ + return self.title + + def get_absolute_url(self): + return reverse('paper_detail', args=[self.id]) + + +class Person(DjangoNode): + + uid = UniqueIdProperty() + created = DateTimeProperty(default=datetime.now()) + created_by = IntegerProperty() # The uid of the user who created this node + + # These are always required + first_name = StringProperty(required=True) + last_name = StringProperty(required=True) + middle_name = StringProperty() + affiliation = StringProperty() + website = StringProperty() + + authors = RelationshipTo("Paper", "authors") + co_authors_with = RelationshipTo("Person", "co_authors_with") + advisor_of = RelationshipTo("Person", "advisor_of") + + class Meta: + app_label = 'catalog' + ordering = ['last_name', 'first_name', 'affiliation'] + + def __str__(self): + if self.middle_name is not None or len(self.middle_name) > 0: + return '{} {} {}'.format(self.first_name, self.middle_name, self.last_name) + return '{} {}'.format(self.first_name, self.last_name) + + def get_absolute_url(self): + return reverse('person_detail', args=[self.id]) + + +class Dataset(DjangoNode): + + uid = UniqueIdProperty() + created = DateTimeProperty(default=datetime.now()) + created_by = IntegerProperty() # The uid of the user who created this node + + # These are always required + name = StringProperty(required=True) + # keywords that describe the dataset + keywords = StringProperty(required=True) + # A brief description of the dataset + description = StringProperty(required=True) + # The date of publication. + publication_date = DateProperty(required=False) + + # data_types = {'N': 'Network', 'I': 'Image(s)', 'V': 'Video(s)', 'M': 'Mix'} + data_types = (('N', 'Network'), + ('I', 'Image(s)'), + ('V', 'Video(s)'), + ('M', 'Mix'),) + source_type = StringProperty(choices=data_types) + website = StringProperty() + + # We should be able to link a dataset to a paper if the dataset was + # published as part of the evaluation for a new algorithm. We note + # that the Paper model already includes a link of type 'published' + # so a dataset list or detail view should provide a link to add a + # 'published' edge between a dataset and a paper. + + class Meta: + app_label = 'catalog' + ordering = ['name', 'type'] + + def __str__(self): + return '{}'.format(self.name) + + def get_absolute_url(self): + return reverse('dataset_detail', args=[self.id]) + + +class Venue(DjangoNode): + + venue_types = (('J', 'Journal'), + ('C', 'Conference'), + ('W', 'Workshop'), + ('O', 'Open Source'), + ('R', 'Tech Report'), + ('O', 'Other'),) + + review_types = (('Y', 'Yes'), + ('N', 'No'),) + + uid = UniqueIdProperty() + created = DateTimeProperty(default=datetime.now()) + created_by = IntegerProperty() # The uid of the user who created this node + + # These are always required + name = StringProperty(required=True) + publication_date = DateProperty(required=True) + type = StringProperty(required=True, choices=venue_types) # journal, tech report, open source, conference, workshop + publisher = StringProperty(required=True) + keywords = StringProperty(required=True) + + peer_reviewed = StringProperty(required=True, choices=review_types) # Yes or no + website = StringProperty() + + class Meta: + app_label = 'catalog' + ordering = ['name', 'publisher', 'publication_date', 'type'] + + def __str__(self): + return '{} by {} on {}'.format(self.name, self.publisher, self.publication_date) + + def get_absolute_url(self): + return reverse('venue_detail', args=[self.id]) + + +class Comment(DjangoNode): + + uid = UniqueIdProperty() + created = DateTimeProperty(default=datetime.now()) + created_by = IntegerProperty() # The uid of the user who created this node + + # These are always required + author = StringProperty() # required but should be able to get it from user object + text = StringProperty(required=True) + + publication_date = DateTimeProperty(default_now=True) + + discusses = RelationshipTo("Paper", "discusses") + + class Meta: + app_label = 'catalog' + ordering = ['publication_date'] + + def __str__(self): + return '{%s}'.format(self.author) + + def get_absolute_url(self): + return reverse('comment_detail', args=[self.id]) + + +class Code(DjangoNode): + + uid = UniqueIdProperty() + created = DateTimeProperty(default=datetime.now()) + created_by = IntegerProperty() # The uid of the user who created this node + + description = StringProperty(required=True) + website = StringProperty(required=True) + keywords = StringProperty(required=True) + + implements = RelationshipTo("Paper", "implements") + + class Meta: + app_label = 'catalog' + ordering = ['website', 'description', 'keywords'] + + def __str__(self): + return '{}'.format(self.website) + + def get_absolute_url(self): + return reverse('code_detail', args=[self.id]) diff --git a/gnosis/catalog/static/catalog/csiro-data61-logos.png b/gnosis/catalog/static/catalog/csiro-data61-logos.png new file mode 100644 index 0000000000000000000000000000000000000000..5b8fba20260788d9929f875800a380c3a7a3c9f4 GIT binary patch literal 11940 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 literal 0 HcmV?d00001 diff --git a/gnosis/catalog/static/catalog/csiro-data61-logos.xcf b/gnosis/catalog/static/catalog/csiro-data61-logos.xcf new file mode 100644 index 0000000000000000000000000000000000000000..30136c9060065926606e3d3253c1bc3b55905e21 GIT binary patch literal 23457 zcmeHv33MFAwRZK4c6qnt1xwvMJ(3sM+AQ0#S?nY%FY7xG0%1w=@;e3`2Lc2`fP_SZ z#I{Bw+cJ<4mKO+w5S#~(#Yi)fWm(?H3p_&1PeKwB@Pk0GF}8a4>F(+GebqCPj7`A* z`OkmOe@Us(b6+8m(UQ^=tGSS6!}OzUIoy1b%*n&!sHjXEHvDf?red zDLb>;{&#**uEM7%|CyG560REXHF!>14YG}EzP@txWsR$^T_fqCT@iEFUU$=#m+5P- zzH-f_^D5?EzIxTwS6+RsK36^(uU-9>m3m84i~Ko3p*7z$52#-`0cxRLfByneZ(MuL z%2jKwTCKMh`aD#4i$$`jxwW;q8Pg-$L{>I5 zE6w)8riG$aWLuhB7PjC$+tS+7vQ#vSY#ULVea%)YX|)KYMQQcp4W#jwo!``yN5G?v z2n`Z}Bo;Qew6bPJQxm!b2~;!!$y(5a!T6e$7Dp2CHRl%Ml?Y9N(&{v~h=rhtKII^rn(H8mWCKXH zv^F(0Kt|~c8ab9Gu}s+Hvt4-_h1OuiMMf!K` zU`^jpA8?>zPe*p_jH1TcxuZ{3Y>Py8kw|v??*6_fi4=)$-?d%V?21GqtQm|%cMh^9 zx~o6h12VCm-t9a4B9Z9e;PxoV_V#Vx<53xk^h+vRBYi^y#Od1=jYukPZv<3)5o@Tw zZ#$8C`tw~6?Cy`Wv!iVh7C9IoJ;j zMs`K|A(wvY-qzmjd;6l>#8Q!UBfWj?(7tWL-u9~}sC|*>vje?Qy3Edj-hDkTfHEMw zvzH}b#QsO&?+5`3+TPI_4pTwHo#D-$-JuW_v@^76b11Yi)IkO9+R(W<98yB|Z5>@y z&~@R?+xVUhZwZGsa(eIT2zPH}P3PuL)L=X7AOT;<+PtZAJ(c9v&JchB(be76A(f;( z6z=YlHCsZV^`z1s>fF3ZQt1wNb^uEWCAvT`)VXO>SC~{bgt~6)^r(b7Jt~Es!`+=2 zo$PtD?76!u9FjQU&=!faaZ6{II7+9zu`rfU#}C+ZXhUeU=XJn}4PyMVXK+sS3~$)X zem8FEK)+DW_6=Jw{>}{@Qav|pybaQW4k{fE`jFb$amTIH&W_F>cde&(Zr&KWqeE&Z z=x&xJU&#JP;qM3m+Bu`s*|8YfX;)tH2Ul^O*XKas&L@Ukzv~=56*FPCK9qBdTxYlq zs%cl>^0r&-+V6a;9MftSuYAERah-=~Af2@Rl@$b=a`OSc#WvUR zt?0sM`DXveQev%mj99|@t3|L(H@`;cZzN&*3M5s#hl9h;KP39iV5J#8Moi{x%QEeO8xS0 zNV(P{AK~_=oc(X-`JiCuV4#g3S?pVEFKHVGhsiE(tA_7nPhGrZamixmBjfPmR4jr| zZC&!l(M(!NTN(5D)o|rfSN`pBIAy+Hq#h#i?JKFUAY&X7}B} zy~^I~u0OlhIdH`emJGP=X>b&~_U+rd>#+OR-Fx@$y9It@$}ru|qwayPigCia!F|wI zrj%ui)u-JKU}f8mq})zWj+d8}72oULQe0M6&Ou)7THwxkRS}iKzQO$oVC_|I7>^9z z4!&F_iiLfr`z<`IlAZBi5crim@cjh73}pQxUAEtz^KkBLLf_@yOz5`svpS*wI2QVH z4|=h?Ph9S983X+d4D)@UQyuO%MSc8F)7Tw}gPo#%?$Q&(`$V$hH`yh^x7+>X7=%;p z{gv;F@K5eu_{nSC+s7bmaBqc|eWLM0Y8xRCMOgLe1LMKG9F$*V#y@YJ{ysR0h)?{3 zwOH}{DtEZ&jluc2{hJAX{|Wm6&c|H0uQwX$`EU1UV{pRmp(mr!_rbAlcT+antd#po zEoYb#^9gIhi*6Fye#~?vR$)3s)n8JpPFt=l&z`a3IrqX*l=R2k-=B5H^5tVYx*HNo z-so-;;sSSf5ykaQ_uocQ?EBpB;|&{voOSC)x9_ZTeCH%D_#Fi1yVv~=6=SY-XcnYr z--A~OaoTIv8DnsAH@l^7`sF$?>yW#I;6(%O*FEs>5&R48{>fv&t+SqbI@0UwP4(Q{ zh~vdiySM;9_0)yLIQywj@NwFZF2YV?{jpZuz?PL`S`OXhx@Pw^YuDa%UXd`aUVG#3 zT=&ak+95FaF`U_~C*D(kgX`XhGhViA+wlz#u%Y}t4GCb}BmCp4s*1tY%z`si2Kk++&F^MR%XL$8-=|%1Qx9En=6BO>>a-I<#MpbW5Uq#Z zBniQ8JXGpXov^mthfhD-mAvo9nDE*(wARFqS{Mvm#6S-1sqPbh~%XxJ?1$xGp<_h~? z#3YgOhhy%m7r8%1**V>T-)fm}{bjnCD4et6d`oS0&*$66V|3t6v5Ov$>@O?)PqP`b{|BDa}?(b8`dF&uz^MTUrq!6ibBN(9{ZxfZ!ei zYm0={(A1WXAT~F(@<@#sV?_?M0AHhUx~QQL4gVPn4>#HPXLZ2KD;E zR}WR8Cpxf`s<3T)pGO+8$Np`Yr_2-m+c7?1_XM;d`&1tziimpd*cGA5MQpG~3W!?vJ^+0{ zP5&-gGq5Y#I|zMHdTk7tFo=5gv}1;{gWIBBWHh>ad-Q3l#MYjHos^ck33C{=te+G263_-n{MCKL5;YpV^gQ65glQMeA4Y3IyW&+1&z3?1kg5cuEJIqLTb>7EI<(^Ltt!EcX++rA2x(KWhjtJfw-YJ zO2ocGB-P&8A;;}>c2g@tU0sDf83Ke>AQp-45o+An-N9XAT}PgYuI_M0n7c$*2cwP* z#Uc9EMXd-q;ckQ)vzs@CLsBW)*Ly?Ac0$DKsTA!U;Z2NBTH)I})>A1qY(_T>H-n^GNjATrt&T1Tzuv^Ir0?xI#~q*JvX zT7mQ4Um1Cb;Yc+wK~X+ozUMHK+X2IpyA8%RhEtg{_`m}%xo!rL&e7wDRdLOI7tn%# z)?sYucjMVu5piLnXNsHxDcSz@hY|m>bHD#2k5Q#tUFRaPKscvfe1UR-{rS(Gf+Mok z^riEvp^Un)Yd$}l%IG`yW$UePPJuEy70>f{z3+S|qciTeBxOCZ7^>)$Jk8jYxmkk( z*@1g_cDMid_3?PihtKwWhAn0P`Vw0*57ScFr`#&zTZh-z@~!QGEZbgMg|nu}Re=#( z`Sf0L(*JR)TiKU?3w5){t@$Itta}@%YUkh}3H`9OO~b5x{iey7d^>Ol&2W`)mOT!9 z``It}vFq5A*S>)fSA*>5eoeBEo+p3f8t8upmKW>|_A&7UvoI8KF_3Iu ze^lnptN{XGYv}Fk%V)24N3q!>KoRZf%O}ouA*2PEShRP@Gp^hFdx-N?U)0N+!N~O8 zhZ-x|H{@C9p=h+9Ua>SM%$XtoH9@3gzHA4u!8P+$eLatm>pmpgq8Pm$?Ry#*6P(`m z0^{qDN9hWIaizn$9f=b%9^SAiG=^~|6~OrVFm}Wc8OLVVy)k@8NHX4zd;#}SyBqId zyd&)8W2}u#D1eC{bcVfzOxKnWk_})yydk{BOUPjJUk4baBhf8QNH@1c;DkFnu~>)+w>J@~DuVArl(^D^JEKV5(}=abtBH*gN&7TlX*%O5T+W6LdX^5xwP zJpW+5XMWiX#;~~eoQs~Z4gTdDQ^1mfE%1i{vXp!LG_rJYkz@%M2oUZ=5a0XZ?FoK( z6QX+6+BMf+EiX?re&x!QtF9?GkifSpzC#_UZm@dKrmkO1pF~v}Vej``MO;!&Cm0*qG3+C#f09Jq?I@;kX1X0Gv$OWM-mdcjd2J|KnH0BwT zgjK_uc?Jvz;pQ9I+UrqM4PxVgWcZEoVuoP{>QrN#Vb`I_Z;TVNP=y|l3qe(>k*x!aIKf6tpAsxZsTw6h`HTtyic^df2ZjH0@(m>b(nf%=n+B^;6EGC~J~2$0 z$kiEB$rL&XVCV$1BvFj%7;*?8bJUaNV6y>mN!T>1&_hu6G1_;jsFrXFe&+}m1AL$X zQwi`Mx)5EH5AL`XlA2Vx#P(_EWo!p zfLCBgG{K!PIHyhXX;$6z0QYN~{FF(q!D_An@Gv;2n*`N8546O5b&dE{!)Lu|AMw3F8?2~t8_Fh%SGeWF-NVNzvH6>pgvA5gGPi7wFW)YYgHRl6qdDb%Q%ivQzO zzh>2{s`L_OX{^)KDXOAn(WuqJPsr*LP*t(p!%e8EMS~82YGKs^M6FY&3I*?TR5%D~ ztvXMogTR(~Djfs>%~#Mk*ie|&>jU~Q38~UXTb?Be45wf!h>VaP3(mfd1E-`Wl6BvSdN>Y6G69ExNXc5YO{gknk4(IO zPg!b54O|aJn?x1C#`wW~S(F;x73c}Dq#vfU>hE?{z*4lyp0vG+4}k-oW0$QofdFuZjjI@VQ&n3@sODGHbsD_PXEa{8~sABPR+W;4e zYGJLO2o9S*;JE1+=nf<+k0B-daOwzy{i#-k0&E`U|7Xq=W(Fd^}LwUtCGR^{$osXDbY z(Tg*K<3$zy0O|mz!PbP%ZA2HIAAo-Rsx7cTlo7Os4ABq3P<(0@Um1~p0DA*2oIpxI zIs#G_Lak8W#CjLCAW&N`C{wV6zG?z>q=4=S7}L;$hX0@^DT#UxU(p~fOr#&OG-17F ziCJm@!vvKP)T{=3p=*kk)G@ah2Y$oQ1jSO6OVor0tUBUoYEhv(^jgDSbpWI36#2Ut zHmDY!KrO}$dDQ7>8NoQLp=;Jq|8)5q#IY%o83liCx~B8R4oFWRS4TC~r{d$+py|>JAcU%6i$}C5pas!}NCBn> z)dr{_Qjk-1MW3V5574!F_>Knx1?Xd*Mn3>x^ELVbC` zj%kW!>skPW$2QUaYg5SqcBp&{6ijEiScB`zRzyqCoaJb^qO*8ngvA9A9UzJh$AJ8L zOCF+t-V51Tu~s6;GXM${EC7G~y88Z9ec(H!bh9F^3E_oGeW6eAeK@#by!-x+fr8nh=vT6}wt6bj5q znR^rdp&-fLln*8e+h3zqeJD8n!@*7aU}D$@XF;Qn;g_~b(J^W~IqRM%FqQ9tqx#6D zaSR+*o5b-H5qeGqfHlQ4H*YQ4 zJQ&Y{f`Vk>Sg0%(2QZ=GB4LyAbFd?eT1cfS449kDOnfHe1A*v1uvmoh z0%)zK1putkQWle9la}Dx(0G{EwPjo>1o#%fPX}f4c&di&U7)a(U3hEUN8C2eLa4Dp zJ58h37DL}v?Q{){Edq84AlpMXW)PzF={QBBL5D=kH8>nK04w1n;0O(P{4X9sIqVHHKwvb<-^E%Ql80p) z(J2Q5EFHmNcL80|lPbUi8Vx8KIX-P11W3avRe6-9VKIl&AS5306pd#Q`@tz1!>gnL zqo&&AjWY0LfeQjbmShw8DAuw8jFdB(1Vi`-4IU#rxEJ#LT#Rr;KqO1+PQ7gN~ z>9{!ocxl&-zw!9WAk1m1xnsZMrV}x<+Ds?Xj{AonFM(anEV<_(Qk7GYQjg;tK|!dNmgVR@(Y`aOU;HWF-|( zJy2isop=Hq!wP@)V9uOsrZNZS3*nFE2#|hr7Rq}p0N`r_pFU`tlfX*MG_4ot3_3q? z0cEFOLyl57cR2tP0F~pIo{^%hJ-*K;(wDZu`PhI=ltXMj1 zj{%3tAHT;n$D65y?W9ceI3(+4iS)K3kuqSw%s4nS+t*{V#zBVWllQ-#IsqQ9)Mu9+ z9L3B27T3V>HJdlGz)QqaHy0~szLh$@!($M$cRSg=b?onMNNgOY@{Q>rZ2kOr_N-$N zt-Es&7w{HC8sn(~yfb0lnfrhaJv_g#lzZG^i6|Lo^?T)ce23SQ!KOngD3|R`;Yr|i zK%gg$huvw`ej7S^lKlbiLafS@#>1wj{4@vEJ<0y)5FALK81Dt1_pSTf6Uh^J7bX%X z#LNAG>(+Ygeej$g=CIX_877xnY~jIl=9?wH^ADwu?JX(iSCsF157Lc9I^*4BVz}pL zZ>3`=L^2L!yq+ck_?;g+*?rSQ@jX_|Oju6Rj31A4oe-yHV;;@~;3V&JV%FXpwql>b zX7wI~6h?Nf5SO`F)5D3_{}Q79PifQ4;yR02G6KCczd!+PKUyj_Ti7Tw={LXg<$V^F zIBwm5ezMDsdTT#*w-ZnP{Jh80W|e6rT{la`i#b0h%ys$gB=uv*jM?t<33DD->+PRd z`(`rzS_MFy`?=>iutN>zaNNn{lEbODambkXVixtexY=kr@tCQY>CE6q&-hU?W-c|e zws{o#w#>A$xZ;FoI=k<4pZs@7VF@NkX&Zd~EtY%r z(T%r1vfs&=X8hfOr=B?)iys>5?eq0zBLiogwM(mH;C$|J97si@%Rh zT7nrcznFlD7$+73`?odl%91yMaZ8{YI`K^QCHN8ZZ|m><-Yjv;aN0aq6n}eY`;VHc z`)z56mH`b0CwTAA9=z*cgqZMLHuaV;z-Y0VjyYcyV$ts*lEibHj-*m=H;bABxTkUW zwZU|3EcP-RwXyg=3$Z!pepmP&cZi<-Q=KT^;ev}Ju?$opdFUge^lL92wcQ-*{+pX( zzW2r0CQW?tN_^Y>TZ<4YF)u}C+I^~6v>ZuMvt!wIuzGO9@>}{%%N*PFQr@L9V`mES zGj|AoCqjSOlDPK^ajr?X!b%@pT?VQBclo)$x5i?ga_x=Zyzxw*nEO{ZLP0urxDs=# z5R1`kshJ*5r*~d?8ds%x>RYKX<3;?VHfp=kQZ(6%-%UZ!p0%HwE+)O08-~gxz$|{d zj(d2|Ez^Z~(4ke%^Xi{4(J#cuUUE(A#oE<8v@(gotO%Ve=ERIz_*F=0p$?Z#yjukm zkr+ghns?SpnOzIu@{3G&1#nYk9Ii4{B0Xy03d;oCPMBzv4|N zyF~uR6^kM!CYhEtXd9V38c%38Fs~bsPmCe}OU6v9A)kOSAkL`(^^0kSPZTZ?Of&3u zoP?(Gf&g^RU^cUwxjb2RX0rz2EZ#Uk+7ZucgUR6OOd3t&uXd$|k0qciZ*hz_lE_{n z%SqwzIsvY3WK|HSBW}%RZH3Gga%CtN@N~uOp%OH48HHCJfXcvA1yDIrbutr|#c>T3 zOPGX&limg@K4!gU%A{NgMh}2`FB9gGDVD+j0rptL^c{(#HXz9|S%yR#a->YR!RRk7 zkn|Fjawd78#bm3sm~4hTe82D{hzRE{UK42_UbHy0K6vA-cotd@jc-1!Wf<=pnV+yQbo=#!B zk-gO+C<>WiKA1>^cp2Hvz+$u^2FF;WikU`;*s8@mBhZZCJ&t=SW6lZ=`?*sXE!AaB zDWa>+tXi}%f(a0$3oufi0_6)vU&q*|6u@eMC}ulugW=RIXBS5}O`pTr#X)?EDC5L3 z!l=uM)c`FgR>Ogt6FUyE#Y)sxit&tpl#2-n2jH*XApRw%l?ZZLQ79FYsV|sR982YT zMI{1YGgL$dK#!Bl2nGQpc`RVO7U4HuF`I zXW(g?gISxdIfBs~gdGF|EVc?h5wX#=cI9gfU@*7>#xW4aG#F2zZsQgCTW<>O**NBo z(`-Y=CAo0N;lQkVv#21l!C7R$2GR$0pPm6jBEYkL3M4U;!1pl%(k2?b5CW&tJ9YyX ztqiCHz>XcnNQ%IZtVLYKTd@cL6l>V=Wg{ZWoKT5`I06GIa$uk36+~PZe)Jq4X4P`z z4=>^lZ6@6}*PViC^})2b|MkyUwMC}o#5yP8+H$dpZ#effVC9zJ!0i6+Y;M!0ojdqB z%y5}$4O_9VV)gPIoLr6NTY;)<{36VDB^wWZ91}hcrQFN&xs*-Y9jJny(<8&zo3V?V zN-}pLCf^|kER`V5IC69i*@HP=d?;m}!GjA@SR0+rv>oubpnU2H$$?hW9lEKH|FWcN1`2t@MAunpT-k zV!t`vgn7lUnAtA2@gXaw$u|5>KLI}?Zn<|~ z8o`Wm^ci31{N)OU?zWdwD51!uR2YHRInwztlX8Z$2+UeCqps(apHq zS^bPNOusyqy$OI$!wc|@(g)7LeSj4G^>>m-mI_hK1HBn@|Fc3&d4`@)%G!f#m&U;~ ze8F_mJWGg&7+`Q*60w&ZPO&}_`;rj%(FL*7Z{tfII6T4&kqzewaldSEAI9i}RX&L0 zC|qjmbzPK3a^TudB+o^QUm79UUz#{SZUMbQlO_MvV~Nm^j=jwiY!kC#$$SP>fmX~f zjXbvM0O}lEJmD3@2AEzY_!dY8VNgfV0ncuDYX#UV@WRVL7{j6ISbtcKUVBX-h%KZJ zG!DV8%6mKWcw@$pE&|OuvI6qT3><|35BDkBdmyg>H*z3{3Z3%e7C3EsN za1Wvmz6e~r;9~r?H6XVcWx_${ZE&y_^BQQtSct2l?5G7k7+za&&s4f9jl6=&D?bZ( zDV2r!xTosO4&aHr6mPoDHSh%)LkhCqK<0{r;drJa*+EWu^>rq0KC>=p%;4kz3swW) zX{qyW$5weaD+2+6bF&5tgq$ED;VsS-bs|#}ksL%rj=z*znWZiVSZ+4_0my<4qbX{<&?VrF z7_`)?i&W@>wiv(ZiXi2UG@XuGr~)qhEW&Ldr&d#2aA8Q%T5y|4LN)TDQ2^izc=d?a zndaj*k~6y&Ph9xd8gN$$DLRe|*9Xv%qSdQV0A^c}BBQ%8gKua*MY=#3IsQ%sZb)$q zRpb_JHQw~YIO3o5nMg>^zg0s@m6%~-$hn7KpdH~Fh55r;0`9Owd1oHiDh^_y4Hxavl0OO0w$s<()sWXb2IUd zFgjj!ZH^`tgM+Nc&9}FRAaeg{EY(GQvqK93g$E?mGAu5~a;152|d?B|VXZrG{Wl)rcH`3_?6U0&m zEHts`P}2G{?rkDG63aoi?AE`ffo>(Uck)1!TDBeWk4Kjkq3QL;ojCZvRwfoQL^4Al zC>!H<-p3cIM|=#|7XcV2bHV_DM#vR(DO1Dl_cUyP(kBa3#B$G*L+ z0(zI4vhe)x|Ki1A#&}Y(;r)+?&ug23jR~%e;S1t&HRhV%-H_*IS_*9J0tOvdnGslo zg)4#;<3*qpPwEXi&RDQw^gnPw=Y=0U7}4+?fP5SU&aaWCQRRo{bZimmjqX9Yfhz+H zdBCz3BTm9V2V8D1{=#5MnP6O`9KQhpPhCP(@QDFcAPgfzBKRX)9?E!xY*3@iPz_w7 ziRcsS6eDUrR&H?g<4$UH7;ojq*3=%c8sl~7>JR}=+?#VSxlSZ~RpN@D9+9rw;)X910QBtz(or!s9+VwB*dA6?;PI01}@t#4mC%^6|SxG1v`zK63PDF3q^~EzkdhwQ5+{7?DaPQ-3A#TRdZQR6O#notAf8k0f zf(^i-HJSYi*8^bh#K9|Z8jK;_D#k$qkUVZ&lvIhB3a3h3^4~&T{D|r6j z{;R|wDSuA%)jD6H5hn3KzFZhG!oZg}LVO{F&kv7n`@%fbEym|#_Px#1ZP1_K>z@;j|==^1poj532;bRa{vGr5dZ)e5dq33^FIIp6JAL~K~#8N?Oh3w z9aR)p!NOF;CX)Nn?<2-m*M#&coX=U5@y&Fk#GMFz7cuO3w;06Hg6BI z*7mSWEq;7cS65eOm^a4;zP&gy=8niWYXIEV$k^xk1aAgE3oZpX%>?^d0R0@El2`H@ z^9ahkJ@m|Fu^$}Y?fb!u88cp>RgZm&JL5b+I2L>rfL#r6`U~JVVEe&YfDVNT{^nG< z{iyF@JQv}4LhN~wHfQV+y7HR?W6lP+E1-SbQ@~PVv&=#>nRT$P?Wq5G@B`pWyMkap zG;N}5VVhHs5B5X%y#hk6M^`4e+rg~{wi2`M#Kb%av$eo__Px2>SrfF$?M0F2y%F7% z06!^0IU72X0Pn(Vlk_ZO`|*2z7>4_5^G84VzW*9{3bWAohf?MtQ_#`EXy*V;3KZi} zw0*Yc^-NW9Mkm3wdQarpJJEQWhrzTkZNf5uLP`D>S^FUsYxGb)pMObc!qd=D*kRG) zQDq|PU>Qd+rkrbxd9{i;vIM*4LyNBmz&p^mR+tI)jBVh0+`c65kY9Y{`C`9eyKjtr zzgNW_8R{}-8f@ARLlf72}90r(%#tUsd;*il<;@{+LT_3zV}34R`Sc6P}G z$+u?!Fe*YlU~{mfjO#H59i`$A55-<^1S{4CWHzor-U2UR4P8X7GMDSiO+@%&gjcI` zGrfnde7;k2=KA{jCStP0L7F`zD^E4%3ufL;m=m0+#kQ94RzH9W*Mn*MvKmznQ`6GD zVk-b$zE>RQwOS!`;VapdQ|YpJGxDyU)Z6=V%_ao!FGDy7xvV_3@*wIT2tE5bEw(ij zndmT7mVnH+oYk12FeeHWo@EVk0Mg$CdPR>hR;w05_*FFmy{6P#XcExUPH;cGtYfw4 z<`N9$J^+O_xzWZZtb?bWjlu5VzzK< z1XmMsD7+mnZoXC_1UvebzyZz0pK3sFB5?d$!;*on)j*R8HphxpMdL^##Km(o@_ICq z!ZgjhZ~z5g)oO%-eDGGpJt1+Gf#%Zc0KAepXxq%lGv^?T!V~P_09x3|V3%}vckijy z)KBVGiE!yk8f6Ej{hC%I6nf#lh@ zd0I_WQ2KA)mh38_X`1Ui;{W*d6L=^88)8?!&csny$t5oj_Oqy{2u zkI|}zMm?{6?JMYeIzTcjsFm5B>7~#n)2op127$(4WWTAwLIfIl)*+7ey%wOU`TUz+ zL_;Q#<)F#=HT7aF4B#rOy6P{9^mf>C>n0j$(I%J9G$uUYjh~ zj1bU+fw6aS$DQHvlqr+ox-LLmLz+zmLKz#n^7bI9uogl$yxalN&|9Wme>ez&$y%%s z8Z?grj}Xv1T-haqFgJ_cU|-i-NWv_egEWe)z7HwF8rzHkP^Q8?Jw30`qJ=PMT#^Vp z%7CU)H45|qo}0K#p+%Ra-B9Tj4g?v&GYhPdEBUh{3Uq0`h&vAX8U`A?QOY;=1{7Of ztAoy9ZzO)4_NJtOdU!knEn1CwHiUjyetM3^o{fZ!dKw9jOQ6Zx{<&IwF>(Et!hxH$ z{5fuc25*7+cr?t1Z_+Au5@$AONL~ADWq3RT?fh!bd|9g~5C=h?g=^-H$+!j@Or>3X zRxY>m1XSGFnGeu7L^N|vqfwxj_lBXbQ9>A`=d_JRJBMOD<`H{qHO_1xkd-v^jWz=G z4y1XYRHHG2Tj=t&g+_A%gxRLQvo2KpQwmp-uu-lGbm@Au???L}AHibJ+Jbh?(xuP| zm;`(TVaY_Q9e{Rr+BerqC34EhH-8ujpkvRRrolpi7rYY#$Ww^p42wcIgfd9DFC zYgs0kfV2hBVA|>B-5ni!=KL-{WWx}^6|Ahp^|AmRhT*GJ9w7j7DZ+Jm(%y~u*J;%5 zaEW;O6d;PwEQ5mcZ-Phg8OAQz5YVlff@iQO3)(V`cJDy^(eRpV1`E(}9KTZK5yE&d zM}c;lY3yC0XHUiN>i_~9R&6JzaiC3bvIYyGO-CbND}WaBL9@q^{4;0(ZAoUr2AGZJ zfs;?DJUpk@g|G*~Y43(-C0IYZSv8Q!nLyXL@K5u&4KgoCUnD~K1+L7dD;s%R?EAB+ z(Y*b{O0p6)%A29Bk7DFK1J93(sc5_|2Q+$t;v__k24EWyS8XdR+1Qldc{#n+Ih^YA_U_+kG%BWnMqN zRWU9Q2FCBG8Oh4ia&Bk$t{ii5?18TQ&U0{FY6Z}@AiPyG;A$-H-EwrbcAKzNVk!-1 zx{G0>-_vF7+W_*V_`Birbqz2kEWW)-?~Y_8Xjdkn)L!;huISM=kCcgYQ}7lqDvc<^ zCi4;~m1rbQBd_EArrFD+6lkbI)sv^y+{`v%vgH3T#}7SWK;#Nmy#>pfgO2E2qI4H{ z+$5n=cP)I1Ct-xKXJxg6D|WgKe-s9qmfFbiX6<)0UZ{Yb0Vrzed^cNxg zmk8%cDOo5=rSF%0M9(b2^9GiSG+cKkFa&@bws#=>COqj`r1@qDx&%P41@k498}zxf zx+9SK?L5jLvOp&M<{CPEbGB=!%EI`5#w;{}NOEYkc1y)4NiTViPE6eO8|7c&E4>yeIH z%KL~bd#vr5o}0cqWl1x4Q6^8``(-@F5SZXX)(Yco`F8I}PR%Ejz9p3qXJ}|7FM{fw5qlHw$7r-%uPe<5 zx9oTV4KN9c06nR9l3Y3N4&>}d8t8lDxU=Xgb2$RoUq_y_2g3ZTxDBRBg!*!Q6Uq9g z5MII*C;+GOHJe#t8d;wQ(;e}x-F-jFGZ&)Z-xEl5^KT~? zXYd>51h&*u)08(eB~vc9J8xDYjJ@gBC~q&OmIL@n&>)mkFgb}v%Z>?zl}*?b(1oL8 zu@^_|?kMjX)OR(0LxIeP8AswqG5-E5@(sHDb}j1Um%@aisZ%|~e;4sIJByL#e8lgi z(ab*yd1w-;Xw}Y-^JNU8ysv9^`Oseo@M^KsvW)}*3OD>i&)XN3-!E^kh$)ag4*d2t=^OzU&p#0Uyvs7P7qBS)G9ZiiGw=K3ox-I=t>a#ro zz(bzoe8-s@3}y3sk#~90mI9;hSiU0W2)(UO>#k@*kY<3go6h3iG69j?JLH?n%&5$6 z_h1y|#bo67=iwWBn`?AYI)Wqv44%>;q~XH*;{f_?%uACI81n`84y2P!RXtZ%I#$#S zbm2&uiuB@y9dhS zfLsl$tx&K#DJSz42wHjq3aw~=q*9FhddrG-Yg*_*V^e}vVChO|$JPCDN=_<2DwX*Mj2F)XE>dg z0zjzy7q0B$v}tBM#{|u?liY}JRv}T@S)w#Pb&?&8o~d!Q*mg1nSsz;aIRN)!Ev7(n zJPkG+;M|XNNjqu=y2tC8%EK{GhSN~SEcXlvvK*Z?9p?4O^N50Ub2134$$7Gj!cewN zFh3t-S`yODsRHz7xQgo{Kbo#tiV48|q*4JIcbz8Ju4-%5Xfg*7f=#|X@`EqJj=h~) ztwjpK?>rFYuBB~f+FF6GL{SuZNWT~?6A}JSy0zThDc>^%Xkc@s6a=EORl^Bo@yiXn zxf~_bozL^?+fx;QVWXAcYT%U5SEea?+zPe{(UVV55t|T_fFW(#V${?*xF~ zO3Ec6{A%Q<9V|MhQ3dFUd}(SD3ho*5ybI-VEJ(m%zLf-@@%zg`Pk*VltH_%`I`*uG zP`}i{mxI4=MtJF1qUqt18Z(gct}_w22jK=3 zcND;_67n0R-0h&I5=fc$cG3GtT}42--_~~240LSbw;=36^aLu)oMbHNHs;!Q{EK5B7>K6h$tEAOyoeiV2pQpU@9b}pRD>WAY=HkaFqb8tKt*>mOtj0|Gc_;EL%HVk-h6%!q&)%Y?!lAa z=KwI~G5k(-PyZK4KO~KYDccWWa@P;x@b}FL3@62boUf+Il9vvKpKk9-$|VWdPC1^S zEG!#xd_FaynuxL!?<7kRDc?2>oFCEjxPC682 zjWk?Y<5Zg3!#rHlr2VGW{SJVijC3=}u&YPo?{lEZ|3aBb9tyyE)*gM%#RWGFZV*E+$@vfJPm8-0J>~Xqa5)&L1-KgKvtRov5`FcrqfmXH2&V{RXEMs zC7*`7_1-kQrOd2e++> zyrlnd6;a_E8o^e65QXS>H!>?IWpHTLOxx<9aMH!9efm5o<+n83VFbrYY4iM9u}it8Xc5y`H295FF~=VI5wrF@6N z)EweSjKb+fB(tlm287c)W`xpvYYnBe5BP3V?UXhPnfZsk|eM-9Uf zz*HZTtl|0^dMO1#a44p=tJL)ylYX|y#;bMpetWc8#D$#5m?a8!=7LL`GS^tREU=7w znkw$dQg?U9p1Sx#yIpB*q+OU}4?hTSMbjoD%_J`NL$g<^Ce*qE~*uX}E+rgZ0M@hyRD9Kzd{Hr*lpSaBrtzL-kCqh7g(Fi#0Fijno zITr_BaD+C$?Pvo3DI>o?3t`-MJ28D#AxDzk*=#wsWtYZ5&{uj%zg=l!?9ml8bH!D1 zfb#<-wBwokZ%yW8vpG3vz7BQI=aNEte{)O`_akEyd<9<0tpHsv@+826o4IKqGgmE} z6ON~09`3^U@eLmQ(dLYOq9%c-h1Q-*?Se1PPx#1ZP1_K>z@;j|==^1poj532;bRa{vGr5dZ)e5dq33^FIIp4qiz_K~#8N?Oh3w z990=!PLV?j1UUo)76NW|x_fp2Io%Qn0v;t`KtP}n5HV1KR3W6$!GzuEV|H^b0s=;Y z5wnz-V>8n|y+=Z_fnq5XC=_BKAYcdrIW`hWSjO*r(~tB{dS+*5m&wkizpAg2e*Io| z?>B${?|*;4eqA$~P}?3qR7>fmYMcE^#_G~s>qKu?h>?IY$J$4A%t>m8^>TFSw5;Zs z+2|9~v#M>p5lI`zdDTJ;2a0W;t7eR)(e9RPbjdV|E2wogXO*u^~+~w;fDtZ%NO2f zDzwG=1kE;_XqV@Q9iPA;%QWnR$UTbP^lh(Mh*B}WeactS+1;bq#^(AbggY1@u-1`; zFNSry$yI7bzcIEjzPHyjL@B7X;|IcO??kxvx^NQ^bUT3|JpSLOImWc8-E@rCG(;(A z=#K9W>%ByA%wDvs~yHSyp|zKg_24{71w$MV)l!uA>U2Fs_QwbbIli2+q$|T9^c(-8ln`O*lr$* zc(@TluFVO#eLO6(ZI1E2V(U}YMR$MEYZ{^y9Jk!qFOp7N3o%!5cpMb234vW}GyXqE zaT@EqrXfm2LwEP?iet?BBz*}{2MEk^`e)Z#qGgQpy=EbX3dFkmDCxxW^%U7>#ljE6 zKf^_=g5Z;mNyR_sH3?A;a^V|<&JC4w=}SZtO2%BL*a@T7)xYFb4KW-{Y-`&M{yCv_ zG;gl&;XuUNilOy9rQQ0PS0%)V0Wq^?>l4(p{*aQfF06^iclGv!2oWMgh!7zPfVqtb z$jF4VjQl(aQV>Zb8Q^Zy!ptb9# zMxScELR~OryjN5%?gX>o`(QWN878E9p&K{ATRzAke0WXk8}kbO)dsmM+XamE`t`j8Dw1(%m*jd_>cV{$hs7j+j3vf z1e~9Ntjiz#Bk+6Rh=2n<)iErsBiR@0{Y_SJldHv}J29t%Pe;;5HnyTQtEBYRHQ6k! zrW{D2el0|2Xe}FM$-T80a3@X0q5S4BjnEz)c`s5Zl}bjtCT2KOI{3 zi^)JX=d4VJwuy~3{;V8DSJL{b5fJ+KVk;Qefvgi`r33|i9;BslCh4Q#$KV98814so zT)MAf;N3#s-3uNCt^r>K4*`dbNIG#ggxadD?eqSe+(8Anvnwc z)3*|`{tU5I7^Mn*3&?uXb+v)q{=V26CKM-eCWP+G6S{9S3QYmH|2DBzQ}Y#&xA{N8 zFR-8hN)ejjCx1ZJlXFTa@L<n9P{{`O#i~W!#p<0CgCdhpZ%lz~3ZUtFBOKb(B6rmF!>-b}5c8&``mS4hx;h_V2L5!=`8T!Mfk%S*C?+&5 z_yv&rKk)wbvmcblXWULpFAJkdXgM>zEAD=$^0+3eC97x#@o+~Z%Z^c$HZUayW{G_0N2U$l(7VPX~Lg<$)C<11}*jdug z=1)6&01P??W8>FAwxcB%gS^{1Kwj67Q7NHmO}x;-$Rv?`oq~cgbC7ZX3yOg0Q<-k%LxCZqQbN;;7lBNLGFo7d?kA%MS}&i%NYLUQ z2AN|n1)P4ybL9K!2_VbQ6qqAYK7h9V#U>--rzo&CivfObW>%dk}|}McYxRl+X1o(yK{5JaRc_1H%a?;`kkQuh1H*7

?WT;Z`uKFgEujf`2>p=BBypk=>&i^3DUzXrl~aqW#*&#Ww$ zw1oOhWtIznu7k6G_`(neF64`#+{ZViG|-PRA#}{u_lKsBQ%^2dz*QlXv@xqDw($PF zRa?JV>uQ;;rA^Me7!JMzQeY`oAu@TCKP!}ZY(6=cb;_Q^vZKMZPSkW_lG@cWx6U@U zdl9Kzci{&|flZ+7^zzAfQ8+CtXhJz(tdy@g@W-nDMF=NOEPD=Lso>1|OF`Ztk{5#) zfro>I!0EG0_%oFn^u}z65FtW@2vK=NQu+~U+PYX-+%&9rJH{dq7N1n#VP3!$)x4rI z5RdPwI*AL^jQNt**|Jr2j5TW7yp5}!do^}i)Y?tQC>g62BfdVZGr2`|lS`u+qpWGZ zav<}p{Dg=65CRUoLUXJUd7Zb~z%L+S)c13(!&)tt)W+s2S*eTK?$&)3 zTYpXr%VpxpF>-73yGdBMxj;+p9N)6ylKQaN??Qv8Xl(gww2FlnZDU?}Lbt{DgwTs) zPu>&wF@)Uey4FVglGCE?7GLunlc>+=C&Oynn0O2N0urq{XVJP|b}|lQ_M_EK>j7Bu z2Vyzn?beIV367JjRBZDmuFmT-FuQ=1un zph0=xlyS{YHPuvXO{n1moxwZpzYe8<177RBCclnrRCq~DY=r{z$1>FGuyi&Lm zL?iwSr+e`%V=MgkgNVOty`~iheXHV_?ZO?fa~0V?R&)l`D-hmSr)+oz7x zI?NVa{DwgPO~Fx){cqq8xg*k^80<~f5reY8IvVF}g*({9YYXzjxlf)Luc|5ImfCq$ zkZOp!<|**q^M$aqngP8x+zH(bE!?$wVPTg~Z2R;c`9zi_4#2hCijM3};fLwQ`~r(F zAi*d)k{eZ9zlUQ7uW6M4{#&sV7eVBwweA9+7TM;%|I8&Oy_O?`czjnxMwel9+e?9k zANGH+Qk-A7vyagk&Q}+k`O_b&3&+hj_EX&C?XZ~FggeRa*%mPV*3hnZXF+Sgq4~ zHo7DiPWgoq9aPUO&YD`IxPyzLI*A{OcsS>C2M8?6(Ldmab2QiFG8a1^>J~H}g7`bG zI{KP?|IOPRE@WLPU%H2{ji1naubAuhNqhWw6q&ghxUB1#Eb^-FyofALL=b{K!i1E# z?ID=3!nEGU=W<@Pf{V^6h;%o;@&gAO;j7BO)Xe@w4h2npQkr-92vDCig);5x47 zBQView Catalog.

+ +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/code_detail.html b/gnosis/catalog/templates/code_detail.html new file mode 100644 index 0000000..c7960a6 --- /dev/null +++ b/gnosis/catalog/templates/code_detail.html @@ -0,0 +1,24 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + + +
+

+ {{ code.website }} + {% if user.is_authenticated %} + {% if user.is_superuser or user.id == code.created_by %} + + + + + + {% endif %} + {% endif %} +

+

+

{{ code.description }}

+

Keywords: {{ code.keywords }}

+
+ +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/code_find.html b/gnosis/catalog/templates/code_find.html new file mode 100644 index 0000000..c4b0055 --- /dev/null +++ b/gnosis/catalog/templates/code_find.html @@ -0,0 +1,16 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

Find Code

+ +{% if message %} +

{{ message }}

+{% endif %} + +
+ {% csrf_token %} + {{ form }} +

+ +
+{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/code_form.html b/gnosis/catalog/templates/code_form.html new file mode 100644 index 0000000..f0bd156 --- /dev/null +++ b/gnosis/catalog/templates/code_form.html @@ -0,0 +1,17 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

New Code

+ +
+ {% csrf_token %} + + {{ form.as_table }} +
+ + +
+ +

*required
+
+{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/code_update.html b/gnosis/catalog/templates/code_update.html new file mode 100644 index 0000000..0f01814 --- /dev/null +++ b/gnosis/catalog/templates/code_update.html @@ -0,0 +1,16 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

Update {{ code.name }}

+ + +
+ {% csrf_token %} + + {{ form }} +
+ +
+ +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/codes.html b/gnosis/catalog/templates/codes.html new file mode 100644 index 0000000..054ab48 --- /dev/null +++ b/gnosis/catalog/templates/codes.html @@ -0,0 +1,31 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

+

+ + Search Codes + +

+ +{% load el_pagination_tags %} + +{% paginate codes %} + +{% for code in codes %} +
+

{{ code.website }}

+

{{ code.description|truncatewords:25 }}

+

Keywords: {{ code.keywords }}

+
+{% endfor %} + +{% get_pages %} +Showing entries +{{ pages.current_start_index }}-{{ pages.current_end_index }} of +{{ pages.total_count }}. +{# Just print pages to render the Digg-style pagination. #} +{{ pages.get_rendered }} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/comment_detail.html b/gnosis/catalog/templates/comment_detail.html new file mode 100644 index 0000000..a26f2af --- /dev/null +++ b/gnosis/catalog/templates/comment_detail.html @@ -0,0 +1,12 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +{% if user.is_authenticated %} +

!! You have Permission !!

+{% else %} +

No permission

+{% endif %} +

Dataset Detail

+ +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/comment_form.html b/gnosis/catalog/templates/comment_form.html new file mode 100644 index 0000000..2f83334 --- /dev/null +++ b/gnosis/catalog/templates/comment_form.html @@ -0,0 +1,11 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

Share your thoughts!

+ +
+ {% csrf_token %} + {{ form }} + +
+{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/comment_update.html b/gnosis/catalog/templates/comment_update.html new file mode 100644 index 0000000..cfd6ad8 --- /dev/null +++ b/gnosis/catalog/templates/comment_update.html @@ -0,0 +1,14 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

Revise your thoughts {{ comment.author }}

+ + +
+ {% csrf_token %} + {{ form }} + +
+ +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/comments.html b/gnosis/catalog/templates/comments.html new file mode 100644 index 0000000..80b6578 --- /dev/null +++ b/gnosis/catalog/templates/comments.html @@ -0,0 +1,15 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

User Comments in our DB

+
    +{% for comment in comments %} +
  • {{ comment.author }} + {% if user.is_authenticated %}|| Edit{% endif %}
  • +{% endfor %} +
+

Number of comments {{ num_comments }}

+

Home|Papers

+ +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/dataset_detail.html b/gnosis/catalog/templates/dataset_detail.html new file mode 100644 index 0000000..05bdd3a --- /dev/null +++ b/gnosis/catalog/templates/dataset_detail.html @@ -0,0 +1,33 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + + +
+

+

{{ dataset.name}}

+ {% if dataset.website %} + + + + {% endif %} + {% if user.is_authenticated %} + {% if user.is_superuser or user.id == dataset.created_by %} + + + + + + {% endif %} + {% endif %} +

+
+ +

Keywords: {{ dataset.keywords }}

+

Publication Date: {{ dataset.publication_date }}

+ +

Description: {{ dataset.description }}

+ +

Type: {{dataset.source_type}}

+ +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/dataset_find.html b/gnosis/catalog/templates/dataset_find.html new file mode 100644 index 0000000..8560fb9 --- /dev/null +++ b/gnosis/catalog/templates/dataset_find.html @@ -0,0 +1,16 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

Find a dataset

+ +{% if message %} +

{{ message }}

+{% endif %} + +
+ {% csrf_token %} + {{ form }} +

+ +
+{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/dataset_form.html b/gnosis/catalog/templates/dataset_form.html new file mode 100644 index 0000000..707f2d5 --- /dev/null +++ b/gnosis/catalog/templates/dataset_form.html @@ -0,0 +1,17 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

New Dataset

+ +
+ {% csrf_token %} + + {{ form.as_table }} +
+ + +
+ +

*required
+
+{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/dataset_update.html b/gnosis/catalog/templates/dataset_update.html new file mode 100644 index 0000000..bf4c08d --- /dev/null +++ b/gnosis/catalog/templates/dataset_update.html @@ -0,0 +1,16 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

Update {{ dataset.name }}

+ + +
+ {% csrf_token %} + + {{ form }} +
+ +
+ +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/datasets.html b/gnosis/catalog/templates/datasets.html new file mode 100644 index 0000000..7099503 --- /dev/null +++ b/gnosis/catalog/templates/datasets.html @@ -0,0 +1,30 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

+

+ + Search Datasets + +

+ +{% load el_pagination_tags %} + +{% paginate datasets %} + +{% for dataset in datasets %} +
+

{{ dataset.name }}

+

{{ dataset.description|truncatewords:25 }}

+
+{% endfor %} + +{% get_pages %} +Showing entries +{{ pages.current_start_index }}-{{ pages.current_end_index }} of +{{ pages.total_count }}. +{# Just print pages to render the Digg-style pagination. #} +{{ pages.get_rendered }} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/paper_authors.html b/gnosis/catalog/templates/paper_authors.html new file mode 100644 index 0000000..9cf04d3 --- /dev/null +++ b/gnosis/catalog/templates/paper_authors.html @@ -0,0 +1,27 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

+ +

{{ paper.title }}

+ +{% for author, author_url in authors %} + +{% endfor %} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/paper_connect_author.html b/gnosis/catalog/templates/paper_connect_author.html new file mode 100644 index 0000000..47905a7 --- /dev/null +++ b/gnosis/catalog/templates/paper_connect_author.html @@ -0,0 +1,35 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

Find a person

+ +
+{% if message %} +

{{ message }}

+{% endif %} + +
+ {% csrf_token %} + {{ form }} +

+ +
+
+ +{% if people %} +
+

These are the people that matched your query

+ +
+{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/paper_connect_code.html b/gnosis/catalog/templates/paper_connect_code.html new file mode 100644 index 0000000..adae618 --- /dev/null +++ b/gnosis/catalog/templates/paper_connect_code.html @@ -0,0 +1,35 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

Find a code repo

+ +
+{% if message %} +

{{ message }}

+{% endif %} + +
+ {% csrf_token %} + {{ form }} +

+ +
+
+ +{% if codes %} +
+

These are the code repos that matched your query

+
    + {% for code, code_connect_url in codes %} +
  • + {{ code.website }} + {% if user.is_authenticated and user.is_superuser %} + Connect + {% endif %} +
  • + {% endfor %} +
+
+{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/paper_connect_dataset.html b/gnosis/catalog/templates/paper_connect_dataset.html new file mode 100644 index 0000000..3e4db1d --- /dev/null +++ b/gnosis/catalog/templates/paper_connect_dataset.html @@ -0,0 +1,26 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

Find a dataset

+ +{% if message %} +

{{ message }}

+{% endif %} + +
+ {% csrf_token %} + {{ form }} +

+ +
+ +{% if people %} +

These are the datasets that matched your query

+ +{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/paper_connect_paper.html b/gnosis/catalog/templates/paper_connect_paper.html new file mode 100644 index 0000000..1b3bf03 --- /dev/null +++ b/gnosis/catalog/templates/paper_connect_paper.html @@ -0,0 +1,27 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

Find a Paper

+ +{% if message %} +

{{ message }}

+{% endif %} + +
+ {% csrf_token %} + {{ form }} +

+ +
+ + +{% if papers %} +

These are the papers that matched your query

+ +{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/paper_connect_venue.html b/gnosis/catalog/templates/paper_connect_venue.html new file mode 100644 index 0000000..066f181 --- /dev/null +++ b/gnosis/catalog/templates/paper_connect_venue.html @@ -0,0 +1,29 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

Find a Venue

+ +{% if message %} +

{{ message }}

+{% endif %} + +
+ {% csrf_token %} + {{ form }} +

+ +
+ + +

+ Venue

+ +{% if venues %} +

These are the venues that matched your query

+ +{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/paper_detail.html b/gnosis/catalog/templates/paper_detail.html new file mode 100644 index 0000000..69ff534 --- /dev/null +++ b/gnosis/catalog/templates/paper_detail.html @@ -0,0 +1,121 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

{{ paper.title }}

+ +

+ + {% if user.is_authenticated %} + + {% endif %} +

+ +
+ {{ authors }} {% if user.is_authenticated %}{% endif %} +
+ +
+ keywords: {{ paper.keywords }} +
+ +

Abstract: {{ paper.abstract }}

+{% if venue %} +

+ Venue: {{ venue }} +

+{% endif%} + +{% if messages %} +{% for message in messages %} +

{{ message }}

+{% endfor %} +{% endif %} + +

{% if user.is_authenticated %} Connect with: Paper + || Venue || + Author || + Dataset || + Code Repo{% endif %}

+ +{% if codes %} +

Code

+ +{% endif %} + +
+ +

{% if user.is_authenticated %} + Comment

+{% else %} +

Login to comment

+{% endif %} +{% if num_comments > 0 %} +

{{ num_comments }} Comments

+{% for comment in comments %} +
+

{{comment.author }} says:

+

+ {{ comment.publication_date }} +

+

{{ comment.text }}

+ {% if comment.created_by == user.id %} + Edit + {% endif %} +
+{% endfor %} +{% endif %} + + + +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/paper_find.html b/gnosis/catalog/templates/paper_find.html new file mode 100644 index 0000000..0f3e0a9 --- /dev/null +++ b/gnosis/catalog/templates/paper_find.html @@ -0,0 +1,17 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

Find a paper

+ +{% if message %} +

{{ message }}

+{% endif %} + +
+ {% csrf_token %} + {{ form }} +

+ + +
+{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/paper_form.html b/gnosis/catalog/templates/paper_form.html new file mode 100644 index 0000000..95aceca --- /dev/null +++ b/gnosis/catalog/templates/paper_form.html @@ -0,0 +1,22 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

New Paper

+ +
+ {% csrf_token %} + {{ form }} +

+ +
+ +
+

{{ message }}

+
+ +{% if message %} +

{{ message }}

+{% endif %} +

*required
+
+{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/paper_results.html b/gnosis/catalog/templates/paper_results.html new file mode 100644 index 0000000..a7de9a4 --- /dev/null +++ b/gnosis/catalog/templates/paper_results.html @@ -0,0 +1,18 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

Papers that match your query

+ +{% for paper in papers %} + +{% endfor %} + + +{% if message %} +

{{ message }}

+{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/paper_update.html b/gnosis/catalog/templates/paper_update.html new file mode 100644 index 0000000..4e9fb48 --- /dev/null +++ b/gnosis/catalog/templates/paper_update.html @@ -0,0 +1,16 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

{{ paper.title }}

+ + +
+ {% csrf_token %} + {{ form }} +
+ +
+
+ +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/papers.html b/gnosis/catalog/templates/papers.html new file mode 100644 index 0000000..3a43f76 --- /dev/null +++ b/gnosis/catalog/templates/papers.html @@ -0,0 +1,29 @@ +{% extends "gnosis_theme.html" %} +{% block content %} +

+

+ + Search Papers + +

+ +{% load el_pagination_tags %} + +{% paginate papers %} +{% for paper, authors, venue in papers %} +
+

{{ paper.title }}

+

{{ authors }}

+

{{ venue }}

+ {{ paper.abstract|truncatewords:50 }} +
+{% endfor %} + +{% get_pages %} +Showing entries +{{ pages.current_start_index }}-{{ pages.current_end_index }} of +{{ pages.total_count }}. +{# Just print pages to render the Digg-style pagination. #} +{{ pages.get_rendered }} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/people.html b/gnosis/catalog/templates/people.html new file mode 100644 index 0000000..595660a --- /dev/null +++ b/gnosis/catalog/templates/people.html @@ -0,0 +1,35 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

+

+ + Search People + +

+ +{% load el_pagination_tags %} + +{% paginate people %} + +{% for person in people %} +
+ {% if person.middel_name %} +

{{person.first_name}} {{person.middle_name}} {{ person.last_name }} +

+ {% else %} +

{{person.first_name}} {{ person.last_name }}

+ {% endif %} + {% if person.affiliation %}

Affiliation: {{ person.affiliation }}

{% endif %} +
+{% endfor %} + +{% get_pages %} +Showing entries +{{ pages.current_start_index }}-{{ pages.current_end_index }} of +{{ pages.total_count }}. +{# Just print pages to render the Digg-style pagination. #} +{{ pages.get_rendered }} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/people_results.html b/gnosis/catalog/templates/people_results.html new file mode 100644 index 0000000..14c8d89 --- /dev/null +++ b/gnosis/catalog/templates/people_results.html @@ -0,0 +1,25 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

People that match your query

+ +{% load el_pagination_tags %} + +{% paginate people %} + +{% for person in people %} + +{% endfor %} + + +{% get_pages %} +Showing entries +{{ pages.current_start_index }}-{{ pages.current_end_index }} of +{{ pages.total_count }}. +{# Just print pages to render the Digg-style pagination. #} +{{ pages.get_rendered }} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/person_detail.html b/gnosis/catalog/templates/person_detail.html new file mode 100644 index 0000000..2fed18a --- /dev/null +++ b/gnosis/catalog/templates/person_detail.html @@ -0,0 +1,32 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +
+

+ {% if person.middle_name %} +

{{ person.first_name}} {{person.middle_name}} {{person.last_name}}

+ {% else %} +

{{ person.first_name}} {{person.last_name}}

+ {% endif %} + {% if user.is_authenticated %} + {% if user.is_superuser or user.id == person.created_by %} +

+ +

+ {% endif %} + {% endif %} +

+
+ +{% if person.website %} +

Website:

+{% endif %} + +{% if person.affiliation %} +

Affiliation: {{ person.affiliation }}

+{% else %} +

Affiliation: Unknown

+{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/person_find.html b/gnosis/catalog/templates/person_find.html new file mode 100644 index 0000000..b6e460a --- /dev/null +++ b/gnosis/catalog/templates/person_find.html @@ -0,0 +1,16 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

Find a person

+ +{% if message %} +

{{ message }}

+{% endif %} + +
+ {% csrf_token %} + {{ form }} +

+ +
+{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/person_form.html b/gnosis/catalog/templates/person_form.html new file mode 100644 index 0000000..2a6107b --- /dev/null +++ b/gnosis/catalog/templates/person_form.html @@ -0,0 +1,16 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

New Person/Author

+ +
+ {% csrf_token %} + + {{ form.as_table }} +
+ + +
+

*required
+
+{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/person_update.html b/gnosis/catalog/templates/person_update.html new file mode 100644 index 0000000..0503b73 --- /dev/null +++ b/gnosis/catalog/templates/person_update.html @@ -0,0 +1,16 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

Update {{ person.last_name }}

+ + +
+ {% csrf_token %} + + {{ form }} +
+ +
+ +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/persons.html b/gnosis/catalog/templates/persons.html new file mode 100644 index 0000000..1976938 --- /dev/null +++ b/gnosis/catalog/templates/persons.html @@ -0,0 +1,14 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

People in our DB

+
    +{% for person in persons %} +
  • {{ person.first_name }} {{ person.last_name }}
  • +{% endfor %} +
+

Number of people {{ num_people }}

+

Home|Papers

+ +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/venue_detail.html b/gnosis/catalog/templates/venue_detail.html new file mode 100644 index 0000000..1d26995 --- /dev/null +++ b/gnosis/catalog/templates/venue_detail.html @@ -0,0 +1,34 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + + +
+

+

{{ venue.name}}

+ {% if user.is_authenticated %} + {% if user.is_superuser or user.id == venue.created_by %} +

+ + + +

+ {% endif %} + {% endif %} +

+
+ +{% if venue.website %} +

Website

+{% endif %} + +

Publication Date: {{ venue.publication_date }}

+{% if venue.keywords %} +

Keywords: {{ venue.keywords }}

+{% endif %} + +

Publisher: {{venue.publisher}}

+

Type: {{venue.type}}

+

+ +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/venue_find.html b/gnosis/catalog/templates/venue_find.html new file mode 100644 index 0000000..8664143 --- /dev/null +++ b/gnosis/catalog/templates/venue_find.html @@ -0,0 +1,16 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

Find a venue

+ +{% if message %} +

{{ message }}

+{% endif %} + +
+ {% csrf_token %} + {{ form }} +

+ +
+{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/venue_form.html b/gnosis/catalog/templates/venue_form.html new file mode 100644 index 0000000..c11a02c --- /dev/null +++ b/gnosis/catalog/templates/venue_form.html @@ -0,0 +1,15 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

New Venue

+ +
+ {% csrf_token %} + + {{ form.as_table }} +
+ + +
+
*required
+{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/venue_update.html b/gnosis/catalog/templates/venue_update.html new file mode 100644 index 0000000..76c6c50 --- /dev/null +++ b/gnosis/catalog/templates/venue_update.html @@ -0,0 +1,16 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

Update {{ venue.name }}

+ + +
+ {% csrf_token %} + + {{ form }} +
+ +
+ +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/templates/venues.html b/gnosis/catalog/templates/venues.html new file mode 100644 index 0000000..f694690 --- /dev/null +++ b/gnosis/catalog/templates/venues.html @@ -0,0 +1,36 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

+

+ + Search Venues + +

+ +{% load el_pagination_tags %} + +{% paginate venues %} + +{% for venue in venues %} +
+

{{ venue.name }}

+

Published on {{ venue.publication_date }} by {{ venue.publisher }}

+
+{% endfor %} + +{% get_pages %} +Showing entries +{{ pages.current_start_index }}-{{ pages.current_end_index }} of +{{ pages.total_count }}. +{# Just print pages to render the Digg-style pagination. #} +{{ pages.get_rendered }} + +{% comment %} +{{ pages.previous }} {{ pages.next }} +{% show_pages %} +{% endcomment %} + + +{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/tests.py b/gnosis/catalog/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/gnosis/catalog/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/gnosis/catalog/urls.py b/gnosis/catalog/urls.py new file mode 100644 index 0000000..3daa60f --- /dev/null +++ b/gnosis/catalog/urls.py @@ -0,0 +1,70 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.papers), + path('papers/', views.papers, name='papers_index'), + path('persons/', views.persons, name='persons_index'), + path('paper//', views.paper_detail, name='paper_detail'), + path('build', views.build, name='build_db'), +] + +# for updating/creating a new Paper node +urlpatterns += [ + path('paper//update', views.paper_update, name='paper_update'), + path('paper//connect/venue', views.paper_connect_venue, name='paper_connect_venue'), + path('paper//connect/author', views.paper_connect_author, name='paper_connect_author'), + path('paper//connect/author/', views.paper_connect_author_selected, name='paper_connect_author_selected'), + path('paper//connect/paper', views.paper_connect_paper, name='paper_connect_paper'), + path('paper//connect/dataset', views.paper_connect_dataset, name='paper_connect_dataset'), + path('paper//connect/code', views.paper_connect_code, name='paper_connect_code'), + path('paper//connect/code/', views.paper_connect_code_selected, name='paper_connect_code_selected'), + path('paper//authors', views.paper_authors, name='paper_authors'), + path('paper//remove/author/', views.paper_remove_author, name='paper_remove_author'), + path('paper/create/', views.paper_create, name='paper_create'), + path('paper/import/', views.paper_create_from_arxiv, name='paper_create_from_arxiv'), + path('paper/find/', views.paper_find, name='paper_find'), +] + +# for updating/creating a new Person node +urlpatterns += [ + path('person/create/', views.person_create, name='person_create'), + path('person//', views.person_detail, name='person_detail'), + path('person//update', views.person_update, name='person_update'), + path('person/find/', views.person_find, name='person_find'), +] + +# for updating/creating a new Dataset node +urlpatterns += [ + path('datasets/', views.datasets, name='datasets_index'), + path('dataset/create/', views.dataset_create, name='dataset_create'), + path('dataset/find/', views.dataset_find, name='dataset_find'), + path('dataset//', views.dataset_detail, name='dataset_detail'), + path('dataset//update', views.dataset_update, name='dataset_update'), +] + +# for updating/creating a new Venue node +urlpatterns += [ + path('venues/', views.venues, name='venues_index'), + path('venue/create/', views.venue_create, name='venue_create'), + path('venue/find/', views.venue_find, name='venue_find'), + path('venue//', views.venue_detail, name='venue_detail'), + path('venue//update', views.venue_update, name='venue_update'), +] + +# for updating/creating a new Comment node +urlpatterns += [ + path('comments/', views.comments, name='comments_index'), + path('comment/create/', views.comment_create, name='comment_create'), + path('comment//', views.comment_detail, name='comment_detail'), + path('comment//update', views.comment_update, name='comment_update'), +] + +# for updating/creating a new Code node +urlpatterns += [ + path('codes/', views.codes, name='codes_index'), + path('code/create/', views.code_create, name='code_create'), + path('code/find/', views.code_find, name='code_find'), + path('code//', views.code_detail, name='code_detail'), + path('code//update', views.code_update, name='code_update'), +] \ No newline at end of file diff --git a/gnosis/catalog/views/__init__.py b/gnosis/catalog/views/__init__.py new file mode 100644 index 0000000..cda4e2e --- /dev/null +++ b/gnosis/catalog/views/__init__.py @@ -0,0 +1,3 @@ +from catalog.views.views import * +from catalog.views.views_people import * +from catalog.views.views_codes import * \ No newline at end of file diff --git a/gnosis/catalog/views/views.py b/gnosis/catalog/views/views.py new file mode 100644 index 0000000..9867c04 --- /dev/null +++ b/gnosis/catalog/views/views.py @@ -0,0 +1,1377 @@ +from django.contrib.auth.decorators import login_required +from django.contrib.admin.views.decorators import staff_member_required +from django.shortcuts import render, redirect +from catalog.models import Paper, Person, Dataset, Venue, Comment, Code +from catalog.forms import PaperForm, DatasetForm, VenueForm, CommentForm, PaperImportForm +from catalog.forms import SearchVenuesForm, SearchPapersForm, SearchPeopleForm, SearchDatasetsForm, SearchCodesForm +from django.urls import reverse +from django.http import HttpResponseRedirect +from neomodel import db +from datetime import date +from nltk.corpus import stopwords +from urllib.request import urlopen +from urllib.error import HTTPError, URLError +from bs4 import BeautifulSoup +from django.contrib import messages + +from catalog.views.views_codes import _code_find + + +# +# Paper Views +# +def get_paper_authors(paper): + query = "MATCH (:Paper {title: {paper_title}})<--(a:Person) RETURN a" + results, meta = db.cypher_query(query, dict(paper_title=paper.title)) + if len(results) > 0: + authors = [Person.inflate(row[0]) for row in results] + else: + authors = [] + # pdb.set_trace() + authors = ['{}. {}'.format(author.first_name[0], author.last_name) for author in authors] + + return authors + + +def _get_paper_codes(paper): + query = "MATCH (:Paper {title: {paper_title}})<--(c:Code) RETURN c" + results, meta = db.cypher_query(query, dict(paper_title=paper.title)) + if len(results) > 0: + codes = [Code.inflate(row[0]) for row in results] + else: + codes = [] + # pdb.set_trace() + #authors = ['{}. {}'.format(author.first_name[0], author.last_name) for author in authors] + + return codes + + +def get_paper_venue(paper): + query = "MATCH (:Paper {title: {paper_title}})--(v:Venue) RETURN v" + results, meta = db.cypher_query(query, dict(paper_title=paper.title)) + if len(results) == 1: # there should only be one venue associated with a paper + venue = [Venue.inflate(row[0]) for row in results][0] + else: + venue = None + # pdb.set_trace() + if venue is not None: + return '{}, {}'.format(venue.name, venue.publication_date) + else: + return '' + + +def papers(request): + # Retrieve the papers ordered by newest addition to DB first. + # limit to maximum 50 papers until we get pagination to work. + # However, even with pagination, we are going to want to limit + # the number of papers retrieved for speed, especially when the + # the DB grows large. + all_papers = Paper.nodes.order_by('-created')[:50] + # Retrieve all comments about this paper. + all_authors = [', '.join(get_paper_authors(paper)) for paper in all_papers] + all_venues = [get_paper_venue(paper) for paper in all_papers] + + papers = list(zip(all_papers, all_authors, all_venues)) + + return render(request, + 'papers.html', + {'papers': papers, + 'papers_only': all_papers, + 'num_papers': len(Paper.nodes.all())}) + + +def paper_authors(request, id): + """Displays the list of authors associated with this paper""" + paper = _get_paper_by_id(id) + print("Retrieved paper with title {}".format(paper.title)) + + query = "MATCH (p:Paper)<-[r]-(a:Person) WHERE ID(p)={id} RETURN a, ID(r)" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + authors = [Person.inflate(row[0]) for row in results] + relationship_ids = [row[1] for row in results] + else: + authors = [] + print("paper author link ids {}".format(relationship_ids)) + print("Found {} authors for paper with id {}".format(len(authors), id)) + + # for rid in relationship_ids: + delete_urls = [reverse('paper_remove_author', kwargs={'id': id, 'rid': rid}) for rid in relationship_ids] + print("author remove urls") + print(delete_urls) + + authors = zip(authors, delete_urls) + + return render(request, + 'paper_authors.html', + {'authors': authors, + 'paper': paper, }) + + +# should limit access to admin users only!! +@staff_member_required +def paper_remove_author(request, id, rid): + print("Paper id {} and edge id {}".format(id, rid)) + + # Cypher query to delete edge of type authors with id equal to rid + query = "MATCH ()-[r:authors]-() WHERE ID(r)={id} DELETE r" + results, meta = db.cypher_query(query, dict(id=rid)) + + return HttpResponseRedirect(reverse("paper_authors", kwargs={'id': id})) + + +def _get_paper_by_id(id): + # Retrieve the paper from the database + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + paper = None + if len(results) > 0: + all_papers = [Paper.inflate(row[0]) for row in results] + paper = all_papers[0] + return paper + + +def paper_detail(request, id): + # Retrieve the paper from the database + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + all_papers = [Paper.inflate(row[0]) for row in results] + paper = all_papers[0] + else: # go back to the paper index page + return render(request, 'papers.html', {'papers': Paper.nodes.all(), 'num_papers': len(Paper.nodes.all())}) + + # Retrieve the paper's authors + authors = get_paper_authors(paper) + # authors is a list of strings so just concatenate the strings. + authors = ", ".join(authors) + + # Retrieve all comments about this paper. + query = "MATCH (:Paper {title: {paper_title}})<--(c:Comment) RETURN c" + results, meta = db.cypher_query(query, dict(paper_title=paper.title)) + if len(results) > 0: + comments = [Comment.inflate(row[0]) for row in results] + num_comments = len(comments) + else: + comments = [] + num_comments = 0 + + # Retrieve the code repos that implement the algorithm(s) in this paper + codes = _get_paper_codes(paper) + + # Retrieve venue where paper was published. + query = "MATCH (:Paper {title: {paper_title}})-->(v:Venue) RETURN v" + results, meta = db.cypher_query(query, dict(paper_title=paper.title)) + if len(results) > 0: + venues = [Venue.inflate(row[0]) for row in results] + venue = venues[0] + else: + venue = None + + request.session['last-viewed-paper'] = id + + ego_network_json = _get_node_ego_network(paper.id, paper.title) + + print("ego_network_json: {}".format(ego_network_json)) + return render(request, + 'paper_detail.html', + {'paper': paper, + 'venue': venue, + 'authors': authors, + 'comments': comments, + 'codes': codes, + 'num_comments': num_comments, + 'ego_network': ego_network_json}) + + +def _get_node_ego_network(id, paper_title): + ''' + Returns a json formatted string of the nodes ego network + :param id: + :return: + ''' + query = "MATCH (s:Paper {title: {paper_title}})-->(t:Paper) RETURN t" + results, meta = db.cypher_query(query, dict(paper_title=paper_title)) + ego_json = '' + if len(results) > 0: + target_papers = [Paper.inflate(row[0]) for row in results] + print("Paper cites {} other papers.".format(len(target_papers))) + ego_json = "{{data : {{id: '{}', title: '{}', href: '{}' }} }}".format(id, + paper_title, + reverse('paper_detail', + kwargs={'id': id})) + for tp in target_papers: + ego_json += ", {{data : {{id: '{}', title: '{}', href: '{}' }} }}".format(tp.id, + tp.title, + reverse('paper_detail', + kwargs={'id': tp.id})) + for tp in target_papers: + ego_json += ",{{data: {{ id: '{}{}', label: '{}', source: {}, target: {} }}}}".format(id, tp.id, 'cites', + id, tp.id) + else: + print("No cited papers found!") + + return '[' + ego_json + ']' + + +def paper_find(request): + message = None + if request.method == 'POST': + form = SearchPapersForm(request.POST) + print("Received POST request") + if form.is_valid(): + english_stopwords = stopwords.words('english') + paper_title = form.cleaned_data['paper_title'].lower() + paper_title_tokens = [w for w in paper_title.split(' ') if not w in english_stopwords] + paper_query = '(?i).*' + '+.*'.join('(' + w + ')' for w in paper_title_tokens) + '+.*' + query = "MATCH (p:Paper) WHERE p.title =~ { paper_query } RETURN p LIMIT 25" + print("Cypher query string {}".format(query)) + results, meta = db.cypher_query(query, dict(paper_query=paper_query)) + if len(results) > 0: + print("Found {} matching papers".format(len(results))) + papers = [Paper.inflate(row[0]) for row in results] + return render(request, 'paper_results.html', {'papers': papers}) + else: + message = "No results found. Please try again!" + + elif request.method == 'GET': + print("Received GET request") + form = SearchPapersForm() + + return render(request, 'paper_find.html', {'form': form, 'message': message}) + + +@login_required +def paper_connect_venue(request, id): + if request.method == 'POST': + form = SearchVenuesForm(request.POST) + if form.is_valid(): + # search the db for the venue + # if venue found, then link with paper and go back to paper view + # if not, ask the user to create a new venue + english_stopwords = stopwords.words('english') + venue_name = form.cleaned_data['venue_name'].lower() + venue_publication_year = form.cleaned_data['venue_publication_year'] + # TO DO: should probably check that data is 4 digits... + venue_name_tokens = [w for w in venue_name.split(' ') if not w in english_stopwords] + venue_query = '(?i).*' + '+.*'.join('(' + w + ')' for w in venue_name_tokens) + '+.*' + query = "MATCH (v:Venue) WHERE v.publication_date =~ '" + venue_publication_year[0:4] + \ + ".*' AND v.name =~ { venue_query } RETURN v" + results, meta = db.cypher_query(query, dict(venue_publication_year=venue_publication_year[0:4], + venue_query=venue_query)) + if len(results) > 0: + venues = [Venue.inflate(row[0]) for row in results] + print("Found {} venues that match".format(len(venues))) + for v in venues: + print("\t{}".format(v)) + + if len(results) > 1: + # ask the user to select one of them + return render(request, 'paper_connect_venue.html', {'form': form, + 'venues': venues, + 'message': 'Found more than one matching venues. Please narrow your search'}) + else: + venue = venues[0] + print('Selected Venue: {}'.format(venue)) + + # retrieve the paper + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + all_papers = [Paper.inflate(row[0]) for row in results] + paper = all_papers[0] + print("Found paper: {}".format(paper.title)) + # check if the paper is connect with a venue; if yes, the remove link to + # venue before adding link to the new venue + query = 'MATCH (p:Paper)-[r:was_published_at]->(v:Venue) where id(p)={id} return v' + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + venues = [Venue.inflate(row[0]) for row in results] + for v in venues: + print("Disconnecting from: {}".format(v)) + paper.was_published_at.disconnect(v) + paper.save() + else: + print("Could not find paper!") + # should not get here since we started from the actual paper...but what if we do end up here? + pass # Should raise an exception but for now just pass + # we have a venue and a paper, so connect them. + print("Citation link not found, adding it!") + messages.add_message(request, messages.INFO, "Link to venue added!") + paper.was_published_at.connect(venue) + return redirect('paper_detail', id=paper.id) + else: + # render new Venue form with the searched name as + message = 'No matching venues found' + + if request.method == 'GET': + form = SearchVenuesForm() + message = None + + return render(request, 'paper_connect_venue.html', {'form': form, 'venues': None, 'message': message}) + + +@login_required +def paper_connect_author_selected(request, id, aid): + + query = "MATCH (p:Paper), (a:Person) WHERE ID(p)={id} AND ID(a)={aid} MERGE (a)-[r:authors]->(p) RETURN r" + results, meta = db.cypher_query(query, dict(id=id, aid=aid)) + + if len(results) > 0: + messages.add_message(request, messages.INFO, "Linked with author.") + else: + messages.add_message(request, messages.INFO, "Link to author failed!") + + return HttpResponseRedirect(reverse("paper_detail", kwargs={'id': id})) + +@login_required +def paper_connect_author(request, id): + if request.method == 'POST': + form = SearchPeopleForm(request.POST) + if form.is_valid(): + # search the db for the person + # if the person is found, then link with paper and go back to paper view + # if not, ask the user to create a new person + name = form.cleaned_data['person_name'] + people_found = _person_find(name) + + if people_found is not None: + print("Found {} people that match".format(len(people_found))) + for person in people_found: + print("\t{} {} {}".format(person.first_name, person.middle_name, person.last_name)) + + if len(people_found) > 0: + # for rid in relationship_ids: + author_connect_urls = [reverse('paper_connect_author_selected', kwargs={'id': id, 'aid': person.id}) for person in people_found] + print("author remove urls") + print(author_connect_urls) + + authors = zip(people_found, author_connect_urls) + + # ask the user to select one of them + return render(request, 'paper_connect_author.html', {'form': form, + 'people': authors, + 'message': ''}) + else: + message = 'No matching people found' + + if request.method == 'GET': + form = SearchPeopleForm() + message = None + + return render(request, 'paper_connect_author.html', {'form': form, 'people': None, 'message': message}) + + +@login_required +def paper_connect_paper(request, id): + """ + View function for connecting a paper with another paper. + + :param request: + :param id: + :return: + """ + message = None + if request.method == 'POST': + form = SearchPapersForm(request.POST) + if form.is_valid(): + # search the db for the person + # if the person is found, then link with paper and go back to paper view + # if not, ask the user to create a new person + paper_title_query = form.cleaned_data['paper_title'] + papers_found = _find_paper(paper_title_query) + + if len(papers_found) > 0: # found more than one matching papers + print("Found {} papers that match".format(len(papers_found))) + for paper in papers_found: + print("\t{}".format(paper.title)) + + if len(papers_found) > 1: + return render(request, 'paper_connect_paper.html', {'form': form, + 'papers': papers_found, + 'message': 'Found more than one matching papers. Please narrow your search'}) + else: + paper_target = papers_found[0] # one person found + print('Selected paper: {}'.format(paper.title)) + + # retrieve the paper + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + all_papers = [Paper.inflate(row[0]) for row in results] + paper_source = all_papers[0] # since we search by id only one paper should have been returned. + print("Found paper: {}".format(paper_source.title)) + # check if the papers are already connected with a cites link; if yes, then + # do nothing. Otherwise, add the link. + query = 'MATCH (q:Paper)<-[r:cites]-(p:Paper) where id(p)={source_id} and id(q)={target_id} return p' + results, meta = db.cypher_query(query, dict(source_id=paper_source.id, target_id=paper_target.id)) + if len(results) == 0: + # papers are not linked so add the edge + print("Citation link not found, adding it!") + paper_source.cites.connect(paper_target) + messages.add_message(request, messages.INFO, "Citation Added!") + else: + print("Citation link found not adding it!") + messages.add_message(request, messages.INFO, "Citation Already Exists!") + else: + print("Could not find paper!") + messages.add_message(request, messages.INFO, "Could not find paper!") + return redirect('paper_detail', id=id) + else: + message = 'No matching papers found' + + if request.method == 'GET': + form = SearchPapersForm() + + return render(request, 'paper_connect_paper.html', {'form': form, 'papers': None, 'message': message}) + + +@login_required +def paper_connect_dataset(request, id): + """ + View function for connecting a paper with a dataset. + + :param request: + :param id: + :return: + """ + if request.method == 'POST': + form = SearchDatasetsForm(request.POST) + if form.is_valid(): + # search the db for the dataset + # if the dataset is found, then link with paper and go back to paper view + # if not, ask the user to create a new dataset + dataset_query_name = form.cleaned_data['name'] + dataset_query_keywords = form.cleaned_data['keywords'] + datasets_found = _dataset_find(dataset_query_name, dataset_query_keywords) + + if len(datasets_found) > 0: # found more than one matching dataset + print("Found {} datasets that match".format(len(datasets_found))) + for dataset in datasets_found: + print("\t{}".format(dataset.name)) + + if len(datasets_found) > 1: + return render(request, 'paper_connect_dataset.html', {'form': form, + 'datasets': datasets_found, + 'message': 'Found more than one matching datasets. Please narrow your search'}) + else: + dataset_target = datasets_found[0] # one person found + print('Selected dataset: {}'.format(dataset_target.name)) + + # retrieve the paper + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + all_papers = [Paper.inflate(row[0]) for row in results] + paper_source = all_papers[0] # since we search by id only one paper should have been returned. + print("Found paper: {}".format(paper_source.title)) + # check if the papers are already connected with a cites link; if yes, then + # do nothing. Otherwise, add the link. + query = 'MATCH (d:Dataset)<-[r:evaluates_on]-(p:Paper) where id(p)={id} and id(d)={dataset_id} return p' + results, meta = db.cypher_query(query, dict(id=id, dataset_id=dataset_target.id)) + if len(results) == 0: + # dataset is not linked with paper so add the edge + paper_source.evaluates_on.connect(dataset_target) + messages.add_message(request, messages.INFO, "Link to dataset added!") + else: + messages.add_message(request, messages.INFO, "Link to dataset already exists!") + else: + print("Could not find paper!") + return redirect('paper_detail', id=id) + + else: + message = 'No matching datasets found' + + if request.method == 'GET': + form = SearchDatasetsForm() + message = None + + return render(request, 'paper_connect_dataset.html', {'form': form, 'datasets': None, 'message': message}) + +@login_required +def paper_connect_code_selected(request, id, cid): + + query = "MATCH (p:Paper), (c:Code) WHERE ID(p)={id} AND ID(c)={cid} MERGE (c)-[r:implements]->(p) RETURN r" + results, meta = db.cypher_query(query, dict(id=id, cid=cid)) + + if len(results) > 0: + messages.add_message(request, messages.INFO, "Linked with code repo.") + else: + messages.add_message(request, messages.INFO, "Link to code repo failed!") + + return HttpResponseRedirect(reverse("paper_detail", kwargs={'id': id})) + + +@login_required +def paper_connect_code(request, id): + """ + View function for connecting a paper with a dataset. + + :param request: + :param id: + :return: + """ + message = '' + if request.method == 'POST': + form = SearchCodesForm(request.POST) + if form.is_valid(): + # search the db for the person + # if the person is found, then link with paper and go back to paper view + # if not, ask the user to create a new person + keywords = form.cleaned_data['keywords'] + codes_found = _code_find(keywords) + + if len(codes_found) > 0: + print("Found {} codes that match".format(len(codes_found))) + for code in codes_found: + print("\t{} {}".format(code.website, code.keywords)) + + if len(codes_found) > 0: + # for rid in relationship_ids: + codes_connect_urls = [reverse('paper_connect_code_selected', kwargs={'id': id, 'cid': code.id}) for code in codes_found] + print(codes_connect_urls) + + codes = zip(codes_found, codes_connect_urls) + + # ask the user to select one of them + return render(request, 'paper_connect_code.html', {'form': form, + 'codes': codes, + 'message': ''}) + else: + message = 'No matching codes found' + + if request.method == 'GET': + form = SearchCodesForm() + message = None + + return render(request, 'paper_connect_code.html', {'form': form, 'codes': None, 'message': message}) + + +@login_required +def paper_update(request, id): + # retrieve paper by ID + # https://github.com/neo4j-contrib/neomodel/issues/199 + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + all_papers = [Paper.inflate(row[0]) for row in results] + paper_inst = all_papers[0] + else: + paper_inst = Paper() + + # if this is POST request then process the Form data + if request.method == 'POST': + + form = PaperForm(request.POST) + if form.is_valid(): + paper_inst.title = form.cleaned_data['title'] + paper_inst.abstract = form.cleaned_data['abstract'] + paper_inst.keywords = form.cleaned_data['keywords'] + paper_inst.download_link = form.cleaned_data['download_link'] + paper_inst.save() + + return HttpResponseRedirect(reverse('papers_index')) + # GET request + else: + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + all_papers = [Paper.inflate(row[0]) for row in results] + paper_inst = all_papers[0] + else: + paper_inst = Paper() + # paper_inst = Paper() + form = PaperForm(initial={'title': paper_inst.title, + 'abstract': paper_inst.abstract, + 'keywords': paper_inst.keywords, + 'download_link': paper_inst.download_link, }) + + return render(request, 'paper_update.html', {'form': form, 'paper': paper_inst}) + + +def _find_paper(query_string): + """ + Helper method to query the DB for a paper based on its title. + :param query_string: The query string, e.g., title of paper to search for + :return: List of papers that match the query or empty list if none match. + """ + papers_found = [] + + english_stopwords = stopwords.words('english') + paper_title = query_string.lower() + paper_title_tokens = [w for w in paper_title.split(' ') if not w in english_stopwords] + paper_query = '(?i).*' + '+.*'.join('(' + w + ')' for w in paper_title_tokens) + '+.*' + query = "MATCH (p:Paper) WHERE p.title =~ { paper_query } RETURN p LIMIT 25" + print("Cypher query string {}".format(query)) + results, meta = db.cypher_query(query, dict(paper_query=paper_query)) + + if len(results) > 0: + papers_found = [Paper.inflate(row[0]) for row in results] + + return papers_found + + +def _add_author(author, paper=None): + """ + Adds author to the DB if author does not already exist and links to paper + as author if paper is not None + :param author: + :param paper: + """ + link_with_paper = False + p = None + people_found = _person_find(author, exact_match=True) + author_name = author.strip().split(' ') + if people_found is None: # not in DB + print("Author {} not in DB".format(author)) + p = Person() + p.first_name = author_name[0] + if len(author_name) > 2: # has middle name(s) + p.middle_name = author_name[1:-1] + else: + p.middle_name = None + p.last_name = author_name[-1] + p.save() # save to DB + link_with_paper = True + elif len(people_found) == 1: + # Exactly one person found. Check if name is an exact match. + p = people_found[0] + # NOTE: The problem with this simple check is that if two people have + # the same name then the wrong person will be linked to the paper. + if p.first_name == author_name[0] and p.last_name == author_name[-1]: + if len(author_name) > 2: + if p.middle_name == author_name[1:-1]: + link_with_paper = True + else: + link_with_paper = True + else: + print("Person with similar but not exactly the same name is already in DB.") + + if link_with_paper and paper is not None: + print("Adding authors link to paper {}".format(paper.title[:50])) + # link author with paper + p.authors.connect(paper) + + +@login_required +def paper_create(request): + user = request.user + print("In paper_create() view.") + message = '' + if request.method == 'POST': + print(" POST") + paper = Paper() + paper.created_by = user.id + form = PaperForm(instance=paper, data=request.POST) + if form.is_valid(): + # Check if the paper already exists in DB + matching_papers = _find_paper(form.cleaned_data['title']) + if len(matching_papers) > 0: # paper in DB already + message = "Paper already exists in Gnosis!" + return render(request, 'paper_results.html', {'papers': matching_papers, 'message': message}) + else: # the paper is not in DB yet. + form.save() # store + # Now, add the authors and link each author to the paper with an "authors" + # type edge. + if request.session.get('from_arxiv', False): + paper_authors = request.session['arxiv_authors'] + for paper_author in paper_authors.split(','): + print("Adding author {}".format(paper_author)) + _add_author(paper_author, paper) + + request.session['from_arxiv'] = False # reset + # go back to paper index page. + # Should this redirect to the page of the new paper just added? + return HttpResponseRedirect(reverse('papers_index')) + else: # GET + print(" GET") + # check if this is a redirect from paper_create_from_arxiv + # if so, then pre-populate the form with the data from arXiv, + # otherwise start with an empty form. + if request.session.get('from_arxiv', False) is True: + title = request.session['arxiv_title'] + abstract = request.session['arxiv_abstract'] + url = request.session['arxiv_url'] + + form = PaperForm(initial={'title': title, + 'abstract': abstract, + 'download_link': url}) + else: + form = PaperForm() + + return render(request, 'paper_form.html', {'form': form, 'message': message}) + + +def get_authors(bs4obj): + """ + Extract authors from arXiv.org paper page + :param bs4obj: + :return: None or a string with comma separated author names from first to last name + """ + authorList = bs4obj.findAll("div", {"class": "authors"}) + if authorList is not None: + if len(authorList) > 1: + # there should be just one but let's just take the first one + authorList = authorList[0] + + # for author in authorList: + # print("type of author {}".format(type(author))) + author_str = authorList[0].get_text() + if author_str.startswith("Authors:"): + author_str = author_str[8:] + return author_str + # authorList is None so return None + return None + + +def get_title(bs4obj): + """ + Extract paper title from arXiv.org paper page. + :param bs4obj: + :return: + """ + titleList = bs4obj.findAll("h1", {"class": "title"}) + if titleList is not None: + if len(titleList) == 0: + return None + else: + if len(titleList) > 1: + print("WARNING: Found more than one title. Returning the first one.") + # return " ".join(titleList[0].get_text().split()[1:]) + title_text = titleList[0].get_text() + if title_text.startswith('Title:'): + return title_text[6:] + else: + return title_text + return None + + +def get_abstract(bs4obj): + """ + Extract paper abstract from arXiv.org paper page. + :param bs4obj: + :return: + """ + abstract = bs4obj.find("blockquote", {"class": "abstract"}) + if abstract is not None: + abstract = " ".join(abstract.get_text().split(' ')[1:]) + return abstract + + +def get_venue(bs4obj): + """ + Extract publication venue from arXiv.org paper page. + :param bs4obj: + :return: + """ + venue = bs4obj.find("td", {"class": "tablecell comments mathjax"}) + if venue is not None: + venue = venue.get_text().split(';')[0] + return venue + + +def get_paper_info(url): + """ + Extract paper information, title, abstract, and authors, from arXiv.org + paper page. + :param url: + :return: + """ + try: + # html = urlopen("http://pythonscraping.com/pages/page1.html") + html = urlopen(url) + except HTTPError as e: + print(e) + except URLError as e: + print(e) + print("The server could not be found.") + else: + bs4obj = BeautifulSoup(html) + # Now, we can access individual element in the page + authors = get_authors(bs4obj) + title = get_title(bs4obj) + abstract = get_abstract(bs4obj) + # venue = get_venue(bs4obj) + return title, authors, abstract + + return None, None, None + + +@login_required +def paper_create_from_arxiv(request): + user = request.user + + if request.method == 'POST': + # create the paper from the extracted data and send to + # paper_form.html asking the user to verify + print("{}".format(request.POST['url'])) + # get the data from arxiv + url = request.POST['url'] + # check if url includes https, and if not add it + if not url.startswith("https://"): + url = "https://" + url + # retrieve paper info. If the information cannot be retrieved from remote + # server, then we will return an error message and redirect to paper_form.html. + title, authors, abstract = get_paper_info(url) + if title is None or authors is None or abstract is None: + form = PaperImportForm() + return render(request, 'paper_form.html', {'form': form, 'message': "Invalid source, please try again."}) + + request.session['from_arxiv'] = True + request.session['arxiv_title'] = title + request.session['arxiv_abstract'] = abstract + request.session['arxiv_url'] = url + request.session['arxiv_authors'] = authors # comma separate list of author names, first to last name + + print("Authors: {}".format(authors)) + + return HttpResponseRedirect(reverse('paper_create')) + else: # GET + request.session['from_arxiv'] = False + form = PaperImportForm() + + return render(request, 'paper_form.html', {'form': form}) + + +def _person_find(person_name, exact_match=False): + """ + Searches the DB for a person whose name matches the given name + :param person_name: + :return: + """ + person_name = person_name.lower() + person_name_tokens = [w for w in person_name.split()] + if exact_match: + if len(person_name_tokens) > 2: + query = ( + "MATCH (p:Person) WHERE LOWER(p.last_name) IN { person_tokens } AND LOWER(p.first_name) IN { person_tokens } AND LOWER(p.middle_name) IN { person_tokens } RETURN p LIMIT 20") + else: + query = ( + "MATCH (p:Person) WHERE LOWER(p.last_name) IN { person_tokens } AND LOWER(p.first_name) IN { person_tokens } RETURN p LIMIT 20") + else: + query = "MATCH (p:Person) WHERE LOWER(p.last_name) IN { person_tokens } OR LOWER(p.first_name) IN { person_tokens } OR LOWER(p.middle_name) IN { person_tokens } RETURN p LIMIT 20" + + results, meta = db.cypher_query(query, dict(person_tokens=person_name_tokens)) + + if len(results) > 0: + print("Found {} matching people".format(len(results))) + people = [Person.inflate(row[0]) for row in results] + return people + else: + return None + + +# +# Dataset Views +# +def datasets(request): + all_datasets = Dataset.nodes.order_by('-publication_date')[:50] + return render(request, 'datasets.html', {'datasets': all_datasets}) + + +def dataset_detail(request, id): + # Retrieve the paper from the database + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + # There should be only one results because ID should be unique. Here we check that at + # least one result has been returned and take the first result as the correct match. + # Now, it should not happen that len(results) > 1 since IDs are meant to be unique. + # For the MVP we are going to ignore the latter case and just continue but ultimately, + # we should be checking for > 1 and failing gracefully. + all_datasets = [Dataset.inflate(row[0]) for row in results] + dataset = all_datasets[0] + else: # go back to the paper index page + return render(request, 'datasets.html', + {'datasets': Dataset.nodes.all(), 'num_datasets': len(Dataset.nodes.all())}) + + # + # TO DO: Retrieve and list all papers that evaluate on this dataset. + # + + request.session['last-viewed-dataset'] = id + + return render(request, + 'dataset_detail.html', + {'dataset': dataset}) + + +def _dataset_find(name, keywords): + """ + Helper method for searching Neo4J DB for a dataset. + + :param name: Dataset name search query + :param keywords: Dataset keywords search query + :return: + """ + dataset_name_tokens = [w for w in name.split()] + dataset_keywords = [w for w in keywords.split()] + datasets = [] + if len(dataset_keywords) > 0 and len(dataset_name_tokens) > 0: + # Search using both the name and the keywords + keyword_query = '(?i).*' + '+.*'.join('(' + w + ')' for w in dataset_keywords) + '+.*' + name_query = '(?i).*' + '+.*'.join('(' + w + ')' for w in dataset_name_tokens) + '+.*' + query = "MATCH (d:Dataset) WHERE d.name =~ { name_query } AND d.keywords =~ { keyword_query} RETURN d LIMIT 25" + results, meta = db.cypher_query(query, dict(name_query=name_query, keyword_query=keyword_query)) + if len(results) > 0: + datasets = [Dataset.inflate(row[0]) for row in results] + return datasets + else: + if len(dataset_keywords) > 0: + # only keywords given + dataset_query = '(?i).*' + '+.*'.join('(' + w + ')' for w in dataset_keywords) + '+.*' + query = "MATCH (d:Dataset) WHERE d.keywords =~ { dataset_query } RETURN d LIMIT 25" + else: + # only name or nothing (will still return all datasets if name and + # keywords fields are left empty and sumbit button is pressed. + dataset_query = '(?i).*' + '+.*'.join('(' + w + ')' for w in dataset_name_tokens) + '+.*' + query = "MATCH (d:Dataset) WHERE d.name =~ { dataset_query } RETURN d LIMIT 25" + # results, meta = db.cypher_query(query, dict(dataset_query=dataset_query)) + + results, meta = db.cypher_query(query, dict(dataset_query=dataset_query)) + if len(results) > 0: + datasets = [Dataset.inflate(row[0]) for row in results] + return datasets + + return datasets # empty list + + +def dataset_find(request): + """ + Searching for a dataset in the DB. + + :param request: + :return: + """ + message = None + if request.method == 'POST': + form = SearchDatasetsForm(request.POST) + print("Received POST request") + if form.is_valid(): + dataset_name = form.cleaned_data['name'].lower() + dataset_keywords = form.cleaned_data['keywords'].lower() # comma separated list + + datasets = _dataset_find(dataset_name, dataset_keywords) + + if len(datasets) > 0: + return render(request, 'datasets.html', {'datasets': datasets}) + else: + message = "No results found. Please try again!" + elif request.method == 'GET': + print("Received GET request") + form = SearchDatasetsForm() + + return render(request, 'dataset_find.html', {'form': form, 'message': message}) + + +@login_required +def dataset_create(request): + user = request.user + + if request.method == 'POST': + dataset = Dataset() + dataset.created_by = user.id + form = DatasetForm(instance=dataset, data=request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('datasets_index')) + else: # GET + form = DatasetForm() + + return render(request, 'dataset_form.html', {'form': form}) + + +@login_required +def dataset_update(request, id): + # retrieve paper by ID + # https://github.com/neo4j-contrib/neomodel/issues/199 + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + datasets = [Dataset.inflate(row[0]) for row in results] + dataset = datasets[0] + else: + dataset = Dataset() + + # if this is POST request then process the Form data + if request.method == 'POST': + form = DatasetForm(request.POST) + if form.is_valid(): + dataset.name = form.cleaned_data['name'] + dataset.keywords = form.cleaned_data['keywords'] + dataset.description = form.cleaned_data['description'] + dataset.publication_date = form.cleaned_data['publication_date'] + dataset.source_type = form.cleaned_data['source_type'] + dataset.website = form.cleaned_data['website'] + dataset.save() + + return HttpResponseRedirect(reverse('datasets_index')) + # GET request + else: + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + datasets = [Dataset.inflate(row[0]) for row in results] + dataset = datasets[0] + else: + dataset = Dataset() + form = DatasetForm(initial={'name': dataset.name, + 'keywords': dataset.keywords, + 'description': dataset.description, + 'publication_date': dataset.publication_date, + 'source_type': dataset.source_type, + 'website': dataset.website, + } + ) + + return render(request, 'dataset_update.html', {'form': form, 'dataset': dataset}) + + +# +# Venue Views +# +def venues(request): + all_venues = Venue.nodes.order_by('-publication_date')[:50] + return render(request, 'venues.html', {'venues': all_venues}) + + +def venue_detail(request, id): + # Retrieve the paper from the database + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + # There should be only one results because ID should be unique. Here we check that at + # least one result has been returned and take the first result as the correct match. + # Now, it should not happen that len(results) > 1 since IDs are meant to be unique. + # For the MVP we are going to ignore the latter case and just continue but ultimately, + # we should be checking for > 1 and failing gracefully. + all_venues = [Venue.inflate(row[0]) for row in results] + venue = all_venues[0] + else: # go back to the paper index page + return render(request, 'venues.html', {'venues': Venue.nodes.all(), 'num_venues': len(Venue.nodes.all())}) + + # + # TO DO: Retrieve all papers published at this venue and list them + # + request.session['last-viewed-venue'] = id + return render(request, + 'venue_detail.html', + {'venue': venue}) + + +def venue_find(request): + """ + Search for venue. + :param request: + :return: + """ + if request.method == 'POST': + form = SearchVenuesForm(request.POST) + if form.is_valid(): + # search the db for the venue + # if venue found, then link with paper and go back to paper view + # if not, ask the user to create a new venue + english_stopwords = stopwords.words('english') + venue_name = form.cleaned_data['venue_name'].lower() + venue_publication_year = form.cleaned_data['venue_publication_year'] + # TO DO: should probably check that data is 4 digits... + venue_name_tokens = [w for w in venue_name.split(' ') if not w in english_stopwords] + venue_query = '(?i).*' + '+.*'.join('(' + w + ')' for w in venue_name_tokens) + '+.*' + query = "MATCH (v:Venue) WHERE v.publication_date =~ '" + venue_publication_year[0:4] + \ + ".*' AND v.name =~ { venue_query } RETURN v" + results, meta = db.cypher_query(query, dict(venue_publication_year=venue_publication_year[0:4], + venue_query=venue_query)) + if len(results) > 0: + venues = [Venue.inflate(row[0]) for row in results] + print("Found {} venues that match".format(len(venues))) + return render(request, 'venues.html', {'venues': venues}) + else: + # render new Venue form with the searched name as + message = 'No matching venues found' + + if request.method == 'GET': + form = SearchVenuesForm() + message = None + + return render(request, 'venue_find.html', {'form': form, 'message': message}) + + +@login_required +def venue_create(request): + user = request.user + + if request.method == 'POST': + venue = Venue() + venue.created_by = user.id + form = VenueForm(instance=venue, data=request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('venues_index')) + else: # GET + form = VenueForm() + + return render(request, 'venue_form.html', {'form': form}) + + +@login_required +def venue_update(request, id): + # retrieve paper by ID + # https://github.com/neo4j-contrib/neomodel/issues/199 + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + venues = [Venue.inflate(row[0]) for row in results] + venue = venues[0] + else: + venue = Venue() + + # if this is POST request then process the Form data + if request.method == 'POST': + form = VenueForm(request.POST) + if form.is_valid(): + venue.name = form.cleaned_data['name'] + venue.publication_date = form.cleaned_data['publication_date'] + venue.type = form.cleaned_data['type'] + venue.publisher = form.cleaned_data['publisher'] + venue.keywords = form.cleaned_data['keywords'] + venue.peer_reviewed = form.cleaned_data['peer_reviewed'] + venue.website = form.cleaned_data['website'] + venue.save() + + return HttpResponseRedirect(reverse('venues_index')) + # GET request + else: + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + venues = [Venue.inflate(row[0]) for row in results] + venue = venues[0] + else: + venue = Venue() + form = VenueForm(initial={'name': venue.name, + 'type': venue.type, + 'publication_date': venue.publication_date, + 'publisher': venue.publisher, + 'keywords': venue.keywords, + 'peer_reviewed': venue.peer_reviewed, + 'website': venue.website, + } + ) + + return render(request, 'venue_update.html', {'form': form, 'venue': venue}) + + +# +# Comment Views +# +@login_required +def comments(request): + """ + We should only show the list of comments if the user is admin. Otherwise, the user should + be redirected to the home page. + :param request: + :return: + """ + # Only superusers can view all the comments + if request.user.is_superuser: + return render(request, 'comments.html', + {'comments': Comment.nodes.all(), 'num_comments': len(Comment.nodes.all())}) + else: + # other users are sent back to the paper index + return HttpResponseRedirect(reverse('papers_index')) + + +@login_required +def comment_detail(request, id): + # Only superusers can view comment details. + if request.user.is_superuser: + return render(request, 'comment_detail.html', {'comment': Comment.nodes.all()}) + else: + # other users are sent back to the papers index + return HttpResponseRedirect(reverse('papers_index')) + + +@login_required +def comment_create(request): + user = request.user + + # Retrieve paper using paper id + paper_id = request.session['last-viewed-paper'] + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=paper_id)) + if len(results) > 0: + all_papers = [Paper.inflate(row[0]) for row in results] + paper = all_papers[0] + else: # just send him to the list of papers + HttpResponseRedirect(reverse('papers_index')) + + if request.method == 'POST': + comment = Comment() + comment.created_by = user.id + comment.author = user.username + form = CommentForm(instance=comment, data=request.POST) + if form.is_valid(): + # add link from new comment to paper + form.save() + comment.discusses.connect(paper) + del request.session['last-viewed-paper'] + return redirect('paper_detail', id=paper_id) + else: # GET + form = CommentForm() + + return render(request, 'comment_form.html', {'form': form}) + + +@login_required +def comment_update(request, id): + # retrieve paper by ID + # https://github.com/neo4j-contrib/neomodel/issues/199 + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + comments = [Comment.inflate(row[0]) for row in results] + comment = comments[0] + else: + comment = Comment() + + # Retrieve paper using paper id + paper_id = request.session['last-viewed-paper'] + + # if this is POST request then process the Form data + if request.method == 'POST': + form = CommentForm(request.POST) + if form.is_valid(): + comment.text = form.cleaned_data['text'] + # comment.author = form.cleaned_data['author'] + comment.save() + # return HttpResponseRedirect(reverse('comments_index')) + del request.session['last-viewed-paper'] + return redirect('paper_detail', id=paper_id) + + # GET request + else: + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + comments = [Comment.inflate(row[0]) for row in results] + comment = comments[0] + else: + comment = Comment() + # form = CommentForm(initial={'author': comment.author, + # 'text': comment.text, + # 'publication_date': comment.publication_date, + # } + # ) + form = CommentForm(initial={'text': comment.text, + 'publication_date': comment.publication_date, + } + ) + + return render(request, 'comment_update.html', {'form': form, 'comment': comment}) + + +# +# Utility Views (admin required) +# +@login_required +def build(request): + try: + d1 = Dataset() + d1.name = 'Yelp' + d1.source_type = 'N' + d1.save() + + v1 = Venue() + v1.name = 'Neural Information Processing Systems' + v1.publication_date = date(2017, 12, 15) + v1.type = 'C' + v1.publisher = 'NIPS Foundation' + v1.keywords = 'machine learning, machine learning, computational neuroscience' + v1.website = 'https://nips.cc' + v1.peer_reviewed = 'Y' + v1.save() + + v2 = Venue() + v2.name = 'International Conference on Machine Learning' + v2.publication_date = date(2016, 5, 24) + v2.type = 'C' + v2.publisher = 'International Machine Learning Society (IMLS)' + v2.keywords = 'machine learning, computer science' + v2.peer_reviewed = 'Y' + v2.website = 'https://icml.cc/2016/' + v2.save() + + p1 = Paper() + p1.title = 'The best paper in the world.' + p1.abstract = 'Abstract goes here' + p1.keywords = 'computer science, machine learning, graphs' + p1.save() + + p1.evaluates_on.connect(d1) + p1.was_published_at.connect(v1) + + p2 = Paper() + p2.title = 'The second best paper in the world.' + p2.abstract = 'Abstract goes here' + p2.keywords = 'statistics, robust methods' + p2.save() + + p2.cites.connect(p1) + p2.was_published_at.connect(v2) + + p3 = Paper() + p3.title = 'I wish I could write a paper with a great title.' + p3.abstract = 'Abstract goes here' + p3.keywords = 'machine learning, neural networks, convolutional neural networks' + p3.save() + + p3.cites.connect(p1) + p3.was_published_at.connect(v1) + + a1 = Person() + a1.first_name = 'Pantelis' + a1.last_name = 'Elinas' + a1.save() + + a1.authors.connect(p1) + + a2 = Person() + a2.first_name = "Ke" + a2.last_name = "Sun" + a2.save() + + a2.authors.connect(p1) + a2.authors.connect(p2) + + a3 = Person() + a3.first_name = "Bill" + a3.last_name = "Gates" + a3.save() + + a3.authors.connect(p3) + a3.advisor_of.connect(a1) + + a4 = Person() + a4.first_name = "Steve" + a4.last_name = "Jobs" + a4.save() + + a4.authors.connect(p2) + a4.authors.connect(p3) + + a4.co_authors_with.connect(a3) + + c1 = Comment() + c1.author = "Pantelis Elinas" + c1.text = "This paper is flawless" + c1.save() + + c1.discusses.connect(p1) + + except Exception: + pass + + num_papers = len(Paper.nodes.all()) + num_people = len(Person.nodes.all()) + + return render(request, 'build.html', {'num_papers': num_papers, 'num_people': num_people}) diff --git a/gnosis/catalog/views/views_codes.py b/gnosis/catalog/views/views_codes.py new file mode 100644 index 0000000..bf94c2c --- /dev/null +++ b/gnosis/catalog/views/views_codes.py @@ -0,0 +1,147 @@ +from django.contrib.auth.decorators import login_required +from django.shortcuts import render +from catalog.models import Code +from catalog.forms import CodeForm +from catalog.forms import SearchCodesForm +from django.urls import reverse +from django.http import HttpResponseRedirect +from neomodel import db + + +# +# Code Views +# +def codes(request): + all_codes = Code.nodes.all() + return render(request, 'codes.html', {'codes': all_codes}) + + +def code_detail(request, id): + # Retrieve the paper from the database + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + # There should be only one result because ID should be unique. Here we check that at + # least one result has been returned and take the first result as the correct match. + # Now, it should not happen that len(results) > 1 since IDs are meant to be unique. + # For the MVP we are going to ignore the latter case and just continue but ultimately, + # we should be checking for > 1 and failing gracefully. + all_codes = [Code.inflate(row[0]) for row in results] + code = all_codes[0] + else: # go back to the paper index page + return render(request, 'codes.html', + {'codes': Code.nodes.all(), 'num_codes': len(Code.nodes.all())}) + + # + # TO DO: Retrieve and list all papers that evaluate on this dataset. + # + + request.session['last-viewed-code'] = id + + return render(request, + 'code_detail.html', + {'code': code}) + + +def _code_find(keywords): + """ + Helper method for searching Neo4J DB for code repo. + + :param keywords: Dataset keywords search query + :return: + """ + code_keywords = [w for w in keywords.split()] + codes = [] + if len(code_keywords) > 0: + # Search using the keywords + keyword_query = '(?i).*' + '+.*'.join('(' + w + ')' for w in code_keywords) + '+.*' + query = "MATCH (d:Code) WHERE d.keywords =~ { keyword_query} RETURN d LIMIT 25" + results, meta = db.cypher_query(query, dict(keyword_query=keyword_query)) + if len(results) > 0: + codes = [Code.inflate(row[0]) for row in results] + + return codes + + +def code_find(request): + """ + Searching for a Code repo in the DB view. + + :param request: + """ + message = None + if request.method == 'POST': + form = SearchCodesForm(request.POST) + print("Received POST request") + if form.is_valid(): + keywords = form.cleaned_data['keywords'].lower() # comma separated list + + codes = _code_find(keywords) + + if len(codes) > 0: + return render(request, 'codes.html', {'codes': codes}) + else: + message = "No results found. Please try again!" + elif request.method == 'GET': + print("Received GET request") + form = SearchCodesForm() + + return render(request, 'dataset_find.html', {'form': form, 'message': message}) + + +@login_required +def code_create(request): + user = request.user + + if request.method == 'POST': + code = Code() + code.created_by = user.id + form = CodeForm(instance=code, data=request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('codes_index')) + else: # GET + form = CodeForm() + + return render(request, 'code_form.html', {'form': form}) + + +@login_required +def code_update(request, id): + # retrieve code node by ID + # https://github.com/neo4j-contrib/neomodel/issues/199 + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + codes = [Code.inflate(row[0]) for row in results] + code = codes[0] + else: + code = Code() + + # if this is POST request then process the Form data + if request.method == 'POST': + form = CodeForm(request.POST) + if form.is_valid(): + code.keywords = form.cleaned_data['keywords'] + code.description = form.cleaned_data['description'] + code.website = form.cleaned_data['website'] + code.save() + + return HttpResponseRedirect(reverse('codes_index')) + # GET request + else: + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + codes = [Code.inflate(row[0]) for row in results] + code = codes[0] + else: + code = Code() + + form = CodeForm(initial={'keywords': code.keywords, + 'description': code.description, + 'website': code.website, + } + ) + + return render(request, 'code_update.html', {'form': form, 'code': code}) diff --git a/gnosis/catalog/views/views_people.py b/gnosis/catalog/views/views_people.py new file mode 100644 index 0000000..274b0b9 --- /dev/null +++ b/gnosis/catalog/views/views_people.py @@ -0,0 +1,154 @@ +from django.contrib.auth.decorators import login_required +from django.shortcuts import render +from catalog.models import Person +from catalog.forms import PersonForm +from catalog.forms import SearchPeopleForm +from django.urls import reverse +from django.http import HttpResponseRedirect +from neomodel import db + + +def _person_find(person_name, exact_match=False): + """ + Searches the DB for a person whose name matches the given name + :param person_name: + :return: + """ + person_name = person_name.lower() + person_name_tokens = [w for w in person_name.split()] + if exact_match: + if len(person_name_tokens) > 2: + query = ("MATCH (p:Person) WHERE LOWER(p.last_name) IN { person_tokens } AND LOWER(p.first_name) IN { person_tokens } AND LOWER(p.middle_name) IN { person_tokens } RETURN p LIMIT 20") + else: + query = ("MATCH (p:Person) WHERE LOWER(p.last_name) IN { person_tokens } AND LOWER(p.first_name) IN { person_tokens } RETURN p LIMIT 20") + else: + query = "MATCH (p:Person) WHERE LOWER(p.last_name) IN { person_tokens } OR LOWER(p.first_name) IN { person_tokens } OR LOWER(p.middle_name) IN { person_tokens } RETURN p LIMIT 20" + + results, meta = db.cypher_query(query, dict(person_tokens=person_name_tokens)) + + if len(results) > 0: + print("Found {} matching people".format(len(results))) + people = [Person.inflate(row[0]) for row in results] + return people + else: + return None + + +def person_find(request): + """ + Searching for a person in the DB. + + :param request: + :return: + """ + message = None + if request.method == 'POST': + form = SearchPeopleForm(request.POST) + print("Received POST request") + if form.is_valid(): + + people = _person_find(form.cleaned_data['person_name']) + if people is not None: + return render(request, 'people.html', {'people': people}) + else: + message = "No results found. Please try again!" + + elif request.method == 'GET': + print("Received GET request") + form = SearchPeopleForm() + + return render(request, 'person_find.html', {'form': form, 'message': message}) + + +# +# Person Views +# +def persons(request): + return render(request, 'people.html', {'people': Person.nodes.all(), 'num_people': len(Person.nodes.all())}) + + +def person_detail(request, id): + # Retrieve the paper from the database + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + # There should be only one results because ID should be unique. Here we check that at + # least one result has been returned and take the first result as the correct match. + # Now, it should not happen that len(results) > 1 since IDs are meant to be unique. + # For the MVP we are going to ignore the latter case and just continue but ultimately, + # we should be checking for > 1 and failing gracefully. + all_people = [Person.inflate(row[0]) for row in results] + person = all_people[0] + else: # go back to the paper index page + return render(request, 'people.html', {'people': Person.nodes.all(), 'num_people': len(Person.nodes.all())}) + + # + # TO DO: Retrieve all papers co-authored by this person and list them; same for + # co-authors and advisees. + # + request.session['last-viewed-person'] = id + return render(request, + 'person_detail.html', + {'person': person}) + + +@login_required +def person_create(request): + user = request.user + + if request.method == 'POST': + person = Person() + person.created_by = user.id + form = PersonForm(instance=person, data=request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('persons_index')) + else: # GET + form = PersonForm() + + return render(request, 'person_form.html', {'form': form}) + + +@login_required +def person_update(request, id): + # retrieve paper by ID + # https://github.com/neo4j-contrib/neomodel/issues/199 + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + all_people = [Person.inflate(row[0]) for row in results] + person_inst = all_people[0] + else: + person_inst = Person() + + # if this is POST request then process the Form data + if request.method == 'POST': + form = PersonForm(request.POST) + if form.is_valid(): + person_inst.first_name = form.cleaned_data['first_name'] + person_inst.middle_name = form.cleaned_data['middle_name'] + person_inst.last_name = form.cleaned_data['last_name'] + person_inst.affiliation = form.cleaned_data['affiliation'] + person_inst.website = form.cleaned_data['website'] + person_inst.save() + + return HttpResponseRedirect(reverse('persons_index')) + # GET request + else: + query = "MATCH (a) WHERE ID(a)={id} RETURN a" + results, meta = db.cypher_query(query, dict(id=id)) + if len(results) > 0: + all_people = [Person.inflate(row[0]) for row in results] + person_inst = all_people[0] + else: + person_inst = Person() + form = PersonForm(initial={'first_name': person_inst.first_name, + 'middle_name': person_inst.middle_name, + 'last_name': person_inst.last_name, + 'affiliation': person_inst.affiliation, + 'website': person_inst.website, + } + ) + + return render(request, 'person_update.html', {'form': form, 'person': person_inst}) + diff --git a/gnosis/gnosis/__init__.py b/gnosis/gnosis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gnosis/gnosis/settings.py b/gnosis/gnosis/settings.py new file mode 100644 index 0000000..df2060c --- /dev/null +++ b/gnosis/gnosis/settings.py @@ -0,0 +1,137 @@ +""" +Django settings for gnosis project. + +Generated by 'django-admin startproject' using Django 2.0.4. + +For more information on this file, see +https://docs.djangoproject.com/en/2.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/2.0/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +NEOMODEL_NEO4J_BOLT_URL = os.environ.get('NEO4J_BOLT_URL', 'bolt://neo4j:GnosisTest00@localhost:7687') + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '6pph^ipk8sow@%nw)s!l@klg4q8lnar2k6k$&e=26pqrm(zg^a' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +SESSION_SAVE_EVERY_REQUEST = True + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'django_neomodel', + 'django_registration', + 'el_pagination', + 'catalog.apps.CatalogConfig', + 'home.apps.HomeConfig', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'gnosis.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': ['./templates', ], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'django.template.context_processors.request', ## For EL-pagination + ], + }, + }, +] + +WSGI_APPLICATION = 'gnosis.wsgi.application' + +# Increase this number when in production. +EL_PAGINATION_PER_PAGE = 3 + +# Database +# https://docs.djangoproject.com/en/2.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/2.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'Australia/Sydney' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.0/howto/static-files/ + +STATIC_URL = '/static/' + +# After user login, she will be re-directed to home page instead of accounts/profile +LOGIN_REDIRECT_URL = '/' + +# This is for checking the password reset email when the email server is not configured +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' \ No newline at end of file diff --git a/gnosis/gnosis/urls.py b/gnosis/gnosis/urls.py new file mode 100644 index 0000000..479cf91 --- /dev/null +++ b/gnosis/gnosis/urls.py @@ -0,0 +1,39 @@ +"""gnosis URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/2.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.views.generic import RedirectView +from django.conf.urls.static import static +from django.conf.urls import include, url +from django.contrib import admin +from django.conf import settings +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), + path('catalog/', include('catalog.urls')), + path('home/', include('home.urls')), + # the next one redirects the root URL to host/catalog/ + # change this as necessary later, maybe make it redirect to a welcome page + path('', RedirectView.as_view(url='/home/')), +] + +# for user authentication +urlpatterns += [ + url(r'^accounts/', include('django_registration.backends.one_step.urls')), + path('accounts/', include('django.contrib.auth.urls')), +] + +# Use static() to add url mapping to serve static files during development (only) +urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/gnosis/gnosis/wsgi.py b/gnosis/gnosis/wsgi.py new file mode 100644 index 0000000..1d0ac3d --- /dev/null +++ b/gnosis/gnosis/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for gnosis project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gnosis.settings") + +application = get_wsgi_application() diff --git a/gnosis/home/__init__.py b/gnosis/home/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gnosis/home/admin.py b/gnosis/home/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/gnosis/home/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/gnosis/home/apps.py b/gnosis/home/apps.py new file mode 100644 index 0000000..90dc713 --- /dev/null +++ b/gnosis/home/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class HomeConfig(AppConfig): + name = 'home' diff --git a/gnosis/home/migrations/__init__.py b/gnosis/home/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gnosis/home/models.py b/gnosis/home/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/gnosis/home/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/gnosis/home/templates/home.html b/gnosis/home/templates/home.html new file mode 100644 index 0000000..2dd4d70 --- /dev/null +++ b/gnosis/home/templates/home.html @@ -0,0 +1,34 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

γνώσις

+ +

A knowledge management system for technical documents.

+ +{% if user.is_authenticated %} +

Hi {{ user.get_username }} (Logout)

+{% else %} +

Hi, login to make full use of γνώσις.

+

If you are new here then why not register a new account!

+{% endif %} + +

Statistics: We have {{ num_papers }} papers and {{ num_people }} people in our DB

+ +

Recently added papers

+
+{% for paper, authors in papers %} + +{% endfor %} + +{% if user.is_superuser %} +

Populate test DB

+{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/home/tests.py b/gnosis/home/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/gnosis/home/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/gnosis/home/urls.py b/gnosis/home/urls.py new file mode 100644 index 0000000..9ec8361 --- /dev/null +++ b/gnosis/home/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.home, name='home'), +] diff --git a/gnosis/home/views.py b/gnosis/home/views.py new file mode 100644 index 0000000..fa98598 --- /dev/null +++ b/gnosis/home/views.py @@ -0,0 +1,31 @@ +from django.shortcuts import render +from catalog.models import Paper, Person +from neomodel import db + + +# Create your views here. +def home(request): + num_papers = len(Paper.nodes.all()) + num_people = len(Person.nodes.all()) + + recent_papers = Paper.nodes.order_by('-created')[:5] + authors = [', '.join(get_paper_authors(paper)) for paper in recent_papers] + + papers = list(zip(recent_papers, authors)) + + return render(request, 'home.html', {'papers': papers, + 'num_papers': num_papers, + 'num_people': num_people}) + + +def get_paper_authors(paper): + query = "MATCH (:Paper {title: {paper_title}})<--(a:Person) RETURN a" + results, meta = db.cypher_query(query, dict(paper_title=paper.title)) + if len(results) > 0: + authors = [Person.inflate(row[0]) for row in results] + else: + authors = [] + # pdb.set_trace() + authors = ['{}. {}'.format(author.first_name[0], author.last_name) for author in authors] + + return authors diff --git a/gnosis/manage.py b/gnosis/manage.py new file mode 100644 index 0000000..97faf2b --- /dev/null +++ b/gnosis/manage.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gnosis.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) diff --git a/gnosis/templates/django_registration/registration_complete.html b/gnosis/templates/django_registration/registration_complete.html new file mode 100644 index 0000000..29dc94a --- /dev/null +++ b/gnosis/templates/django_registration/registration_complete.html @@ -0,0 +1,11 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

Account created successfully.

+ +

Enjoy your stay!

+ + + +{% endblock %} \ No newline at end of file diff --git a/gnosis/templates/django_registration/registration_form.html b/gnosis/templates/django_registration/registration_form.html new file mode 100644 index 0000000..ae9709a --- /dev/null +++ b/gnosis/templates/django_registration/registration_form.html @@ -0,0 +1,17 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +
+ {% csrf_token %} + {% for field in form %} +

+ {{ field.label_tag }}
+ {{ field }} + {% for error in field.errors %} +

{{ error }}

+ {% endfor %} +

+ {% endfor %} + +
+{% endblock %} \ No newline at end of file diff --git a/gnosis/templates/gnosis_theme.html b/gnosis/templates/gnosis_theme.html new file mode 100644 index 0000000..1eb346d --- /dev/null +++ b/gnosis/templates/gnosis_theme.html @@ -0,0 +1,190 @@ + + + + Gnosis Research Paper Management + + + + + + + + + + + + + +
+
+
+
+ {% if user.is_authenticated %} +

+
+
+ Papers   +
+ + + + +
+
+ +
+ People   +
+ + +
+
+ +
+ Venues   +
+ + +
+
+ +
+ Datasets +
+ + +
+
+ +
+ Codes +
+ + +
+
+ +
+ {% else %} +

+
+ + + + + + +
+ Datasets +
+ +
+ Codes +
+ +
+ + {% endif %} +
+
+ {% block content %} + {% endblock %} +
+
+ + + + + +
+
+ +
+
+ {% block footer %} + {% load static %} + CSIRO and Data61 Logos + {% endblock %} +
+ + + diff --git a/gnosis/templates/registration/logged_out.html b/gnosis/templates/registration/logged_out.html new file mode 100644 index 0000000..c28f538 --- /dev/null +++ b/gnosis/templates/registration/logged_out.html @@ -0,0 +1,9 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

Logged out!

+ +Click here to login again. + +{% endblock %} \ No newline at end of file diff --git a/gnosis/templates/registration/login.html b/gnosis/templates/registration/login.html new file mode 100644 index 0000000..e2f98fb --- /dev/null +++ b/gnosis/templates/registration/login.html @@ -0,0 +1,48 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +
+ {% if next %} + {% if user.is_authenticated %} +

Your account doesn't have access to this page. To proceed, + please login with an account that has access.

+ {% else %} +

Log in to your account.

+ {% endif %} + {% endif %} +
+ +
+
+
+ {% csrf_token %} + + + + + + + + + +
+ {% if form.errors %} +

+ Your username and password didn't match. + Please try again. +

+ {% endif %} + + + +
+
+
+ +{# Assumes you setup the password_reset view in your URLconf #} +

Lost password?

+

New User? Register a new account!

+{% endblock %} \ No newline at end of file diff --git a/gnosis/templates/registration/password_reset_complete.html b/gnosis/templates/registration/password_reset_complete.html new file mode 100644 index 0000000..4046735 --- /dev/null +++ b/gnosis/templates/registration/password_reset_complete.html @@ -0,0 +1,9 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + + +

The password has been changed!

+

log in again?

+ +{% endblock %} \ No newline at end of file diff --git a/gnosis/templates/registration/password_reset_confirm.html b/gnosis/templates/registration/password_reset_confirm.html new file mode 100644 index 0000000..0562ff1 --- /dev/null +++ b/gnosis/templates/registration/password_reset_confirm.html @@ -0,0 +1,33 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +{% if validlink %} +

Please enter (and confirm) your new password.

+
+
+ +
+ + + + + + + + + + + + + +
{{ form.new_password1.errors }} + {{ form.new_password1 }}
{{ form.new_password2.errors }} + {{ form.new_password2 }}
+
+{% else %} +

Password reset failed

+

The password reset link was invalid, possibly because it has already been used. Please request a new password reset.

+{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/templates/registration/password_reset_done.html b/gnosis/templates/registration/password_reset_done.html new file mode 100644 index 0000000..7517ff5 --- /dev/null +++ b/gnosis/templates/registration/password_reset_done.html @@ -0,0 +1,8 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +

We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.

+ + +{% endblock %} \ No newline at end of file diff --git a/gnosis/templates/registration/password_reset_email.html b/gnosis/templates/registration/password_reset_email.html new file mode 100644 index 0000000..3142e75 --- /dev/null +++ b/gnosis/templates/registration/password_reset_email.html @@ -0,0 +1,8 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} + +Someone asked for password reset for email {{ email }}. Follow the link below: +{{ protocol}}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} + +{% endblock %} \ No newline at end of file diff --git a/gnosis/templates/registration/password_reset_form.html b/gnosis/templates/registration/password_reset_form.html new file mode 100644 index 0000000..b798bb8 --- /dev/null +++ b/gnosis/templates/registration/password_reset_form.html @@ -0,0 +1,11 @@ +{% extends "gnosis_theme.html" %} + +{% block content %} +

Reset your password.

+
{% csrf_token %} + {% if form.email.errors %} {{ form.email.errors }} {% endif %} +

Enter your email: {{ form.email }}

+ +
+ +{% endblock %} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9d8daba --- /dev/null +++ b/requirements.txt @@ -0,0 +1,29 @@ +beautifulsoup4==4.6.3 +certifi==2018.1.18 +chardet==3.0.4 +confusable-homoglyphs==3.2.0 +cycler==0.10.0 +Django==2.0.4 +django-el-pagination==3.2.4 +django-neomodel==0.0.4 +django-registration==3.0 +docopt==0.6.2 +idna==2.6 +kiwisolver==1.0.1 +matplotlib==2.2.2 +neo4j-driver==1.5.2 +neomodel==3.2.6 +nltk==3.3 +numpy==1.14.2 +pandas==0.22.0 +pipreqs==0.4.9 +py2neo==3.1.2 +pyparsing==2.2.0 +python-dateutil==2.7.2 +pytz==2018.3 +requests==2.18.4 +scikit-learn==0.19.1 +scipy==1.0.1 +six==1.11.0 +urllib3==1.22 +yarg==0.1.9 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d8b9453 --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2018 Data61, CSIRO +# +import setuptools + +DESCRIPTION = "Gnosis web application for paper management and collaboration." +URL = "https://github.com/stellargraph/stellar-gnosis" + +setuptools.setup( + name="gnosis", + version="0.1.0", + author="Pantelis Elinas, Data61, CSIRO", + author_email="pantelis.elinas@data61.csiro.au", + url=URL, + long_description=DESCRIPTION, + long_description_content_type="text/markdown", + python_requires='>3.5.0, <3.7.0', + classifiers=[ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", + "Development Status :: 3 - Alpha", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + ], +) diff --git a/stellar-gnosis b/stellar-gnosis deleted file mode 160000 index 9ac34e9..0000000 --- a/stellar-gnosis +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9ac34e9003bd6088251fdf6d02395a7fc7ad7413 From eff972bd30084dc017a656ce127294d3a52f9d7e Mon Sep 17 00:00:00 2001 From: Zhenghao-Zhao Date: Thu, 28 Mar 2019 17:01:17 +1100 Subject: [PATCH 004/154] add readme --- README.md | Bin 12 -> 146 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/README.md b/README.md index c4063c68ca30b2b2c2124649ea1e911724e8e94a..965140b7a854a2dcf38fbdd449ef257ef83de312 100644 GIT binary patch literal 146 zcmXwx*$IF^3`F0zU@2OPJ$Nj-c;bQDygD0JLdYcZGVfF1I7vwu@J#fkcWxFrn{AcU rF3PDUyDXIEuaR)%wCa%KxI_oCiSD8KXVuJ%`Yl=QmMPbZ@8b6dlQ9^h literal 12 TcmY#ZaL>yx&MekVNi72a7$yWG From a9dc2084bdcfef0dd280a105672ef4a31dcc76ec Mon Sep 17 00:00:00 2001 From: Zhenghao-Zhao Date: Sun, 31 Mar 2019 15:58:52 +1100 Subject: [PATCH 005/154] Update README.md --- README.md | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c4063c6..87b1c5a 100644 --- a/README.md +++ b/README.md @@ -1 +1,107 @@ -# Gnosis-dev \ No newline at end of file +# Gnosis + +**Gnosis** is an online paper management and collaboration tool. + + +## Installation Instructions + +These instructions are a brief overview of the steps required to run the **Gnosis** Django application on a development +server using Django's build in web server, the free version of Neo4j, and sqlite3. + +**Gnosis** is a Python Django web application. You should install a suitable version of Python in +order to proceed. We have tested **Gnosis** with Python version 3.6.5 but any 3.6.* version should +work. + +These installation instructions are for Mac OS. The instructions for any popular Linux distributions should be very +similar. We have not tested **Gnosis** on Windows 10. + +### Install Neo4j + +Download and install the free desktop version of the Neo3j Graph database from [here](https://neo4j.com/download/). + +We have installed and tested Gnosis with the Neo4j community version 3.3.4 although newer version should +work just as well. + +Let's assume that you have successfully installed Neo4j in the directory `/Neo4j/`. You can now start the neo4j server +by executing the command + +`/Neo4j/bin/neo4j start` + +If the database server starts without errors you should see the following message printed in the terminal, + + Started neo4j (pid 23687). It is available at http://localhost:7474/ + There may be a short delay until the server is ready. + +With your web browser, you can now access the Neo4j console by visiting http://localhost:7474/. + +**Important** When you first install Neo4j, you can create a user account and set a password. Remember these as you +will need them later in the instructions. + +### Install Gnosis + +First, you need to either clone or download the code from GitHub. + +To clone the **Gnosis** repo in the directory `/Projects`, change to the latter and then issue +the following command, + + git clone https://github.com/stellargraph/stellar-gnosis.git + +This command will clone the **Gnosis** repo into the directory `/Projects/stellar-gnosis`. + +Create a new Python virtual environment either using `virtualevn` or `conda` for a suitable version +of Python. Let's assume that the new environment is called `gnosis-env`. + +Activate `gnosis-env` and install the library requirements using the following command, + + pip install -r requirements.txt + +**Important:** As the next step you must apply a patch to the django-neomodel library. Locate +the file `/site-packages/django_neomodel/__init__.py` and the method `get_choices(self, include_blank=True)`. + +Then replace the line, + + choices=list(self.choices)if self.choices else [] + +with the following, + + choices=[(k, v)for k, v in self.choices.items()] if self.choices else [] + +Locate the `settings.py` file in the directory `/Projects/stellar-gnosis/gnosis/gnosis/`. You need to set your +Neo4j database password so that Django can access it. Find the line, + + NEOMODEL_NEO4J_BOLT_URL = os.environ.get('NEO4J_BOLT_URL', 'bolt://neo4j:GnosisTest00@localhost:7687') + +The string `bolt://neo4j:GnosisTest00@localhost:7687'` indicates that the default (for development) Neo4j user +name is *neo4j* and the password is `GnosisTest00`. Replace these with the username and password you created earlier +during the Neo4j installation. You do not need a strong password as this is only used for development. You can use the +values in `settings.py` if you want; in this case, you don't have to modify the above line. + +#### Prepare the databases + +Change to the directory `/Projects/stellar-gnosis/gnosis/` where you can find the file `manage.py`. + +Prepare the Neo4j and sqlite3 databases by using the following commands, + + python manage.py install_labels + python manage.py makemigrations + python manage.py migrate + +Create a **Gnosis** administrator account using the below command and following the prompts: + + python manage.py createsuperuser + +You can now start the development server by issuing the command, + + python manage.py runserver + +You can access **Gnosis** running on your local machine by pointing your web browser to `http://127.0.0.1:8000/` + +## License + +Copyright 2010-2019 Commonwealth Scientific and Industrial Research Organisation (CSIRO). + +All Rights Reserved. + +NOTICE: All information contained herein remains the property of the CSIRO. The intellectual and technical concepts +contained herein are proprietary to the CSIRO and are protected by copyright law. Dissemination of this information +or reproduction of this material is strictly forbidden unless prior written permission is obtained from the CSIRO. From 6f99aa319d81fdb1a868c78cfd13d8685ab87ce8 Mon Sep 17 00:00:00 2001 From: Zhenghao-Zhao Date: Tue, 2 Apr 2019 12:42:13 +1100 Subject: [PATCH 006/154] bug fixes --- dist/gnosis-0.1.0-py3.6.egg | Bin 0 -> 1115 bytes gnosis.egg-info/PKG-INFO | 17 +++++++++++++++++ gnosis.egg-info/SOURCES.txt | 6 ++++++ gnosis.egg-info/dependency_links.txt | 1 + gnosis.egg-info/top_level.txt | 1 + gnosis/catalog/models.py | 2 +- 6 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 dist/gnosis-0.1.0-py3.6.egg create mode 100644 gnosis.egg-info/PKG-INFO create mode 100644 gnosis.egg-info/SOURCES.txt create mode 100644 gnosis.egg-info/dependency_links.txt create mode 100644 gnosis.egg-info/top_level.txt diff --git a/dist/gnosis-0.1.0-py3.6.egg b/dist/gnosis-0.1.0-py3.6.egg new file mode 100644 index 0000000000000000000000000000000000000000..5d6ea31e941d8d028e82623557e948144065e3a6 GIT binary patch literal 1115 zcmWIWW@Zs#U|`^2*t4d_FMFPG%oIijhIvd341z#WS9f<^Pd_(*{Qz$WBXrW;yxRsm zt?zk%$o^p1I7jCOo5bM@3YyyzVuOy{tvY*Rjjm7qgr)x+uXjovlu0pKM=$`=%eSctYi> z<)nf>ZCv>}ZfT+o_e&pH;mqEJ{4d&H`wmu!Eybu})V$F{ZhXjwESN8Zu&G`l3-Z{202G2g;-YpG3M@YjNmJu1Iu zXZd|uU3|Wb&Fq8f?Ur;a=Bo8mkF4bL*W1AE&&U&XE;oYrLYac>a_ij_3x- z);n?SF^Q{_7ipFj>=8?z^7bKLuKgx4lbHPlQPy*>eQs2~fAUt(cJa8SGbbc-SKpqf z{_lp#^YSn4TRz{6pKZ`<*zLqB{g!>s;oq6svZWg&tzE;?wAY?F{>x>e!HGn>PY?D? zS|9Of)tda=^*VtwVv5p*iy{}E6VF)t%X5Q{-#|MjzHyiZ@}|4?@Q>zCrs$0u-n3.5.0, <3.7.0 +Description-Content-Type: text/markdown diff --git a/gnosis.egg-info/SOURCES.txt b/gnosis.egg-info/SOURCES.txt new file mode 100644 index 0000000..a9d010c --- /dev/null +++ b/gnosis.egg-info/SOURCES.txt @@ -0,0 +1,6 @@ +README.md +setup.py +gnosis.egg-info/PKG-INFO +gnosis.egg-info/SOURCES.txt +gnosis.egg-info/dependency_links.txt +gnosis.egg-info/top_level.txt \ No newline at end of file diff --git a/gnosis.egg-info/dependency_links.txt b/gnosis.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/gnosis.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/gnosis.egg-info/top_level.txt b/gnosis.egg-info/top_level.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/gnosis.egg-info/top_level.txt @@ -0,0 +1 @@ + diff --git a/gnosis/catalog/models.py b/gnosis/catalog/models.py index fafd42b..1a5f15c 100644 --- a/gnosis/catalog/models.py +++ b/gnosis/catalog/models.py @@ -64,7 +64,7 @@ class Meta: ordering = ['last_name', 'first_name', 'affiliation'] def __str__(self): - if self.middle_name is not None or len(self.middle_name) > 0: + if self.middle_name is not None and len(self.middle_name) > 0: return '{} {} {}'.format(self.first_name, self.middle_name, self.last_name) return '{} {}'.format(self.first_name, self.last_name) From a9eb8630ebfdd4ffb8b213e65e66d652f29b98cc Mon Sep 17 00:00:00 2001 From: Zhenghao-Zhao Date: Tue, 2 Apr 2019 12:51:59 +1100 Subject: [PATCH 007/154] Update README.md --- README.md | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/README.md b/README.md index e69de29..16c6c81 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,107 @@ +# Gnosis + +**Gnosis** is an online paper management and collaboration tool. + + +## Installation Instructions + +These instructions are a brief overview of the steps required to run the **Gnosis** Django application on a development +server using Django's build in web server, the free version of Neo4j, and sqlite3. + +**Gnosis** is a Python Django web application. You should install a suitable version of Python in +order to proceed. We have tested **Gnosis** with Python version 3.6.5 but any 3.6.* version should +work. + +These installation instructions are for Mac OS. The instructions for any popular Linux distributions should be very +similar. We have not tested **Gnosis** on Windows 10. + +### Install Neo4j + +Download and install the free desktop version of the Neo3j Graph database from [here](https://neo4j.com/download/). + +We have installed and tested Gnosis with the Neo4j community version 3.3.4 although newer version should +work just as well. + +Let's assume that you have successfully installed Neo4j in the directory `/Neo4j/`. You can now start the neo4j server +by executing the command + +`/Neo4j/bin/neo4j start` + +If the database server starts without errors you should see the following message printed in the terminal, + + Started neo4j (pid 23687). It is available at http://localhost:7474/ + There may be a short delay until the server is ready. + +With your web browser, you can now access the Neo4j console by visiting http://localhost:7474/. + +**Important** When you first install Neo4j, you can create a user account and set a password. Remember these as you +will need them later in the instructions. + +### Install Gnosis + +First, you need to either clone or download the code from GitHub. + +To clone the **Gnosis** repo in the directory `/Projects`, change to the latter and then issue +the following command, + + git clone https://github.com/stellargraph/stellar-gnosis.git + +This command will clone the **Gnosis** repo into the directory `/Projects/stellar-gnosis`. + +Create a new Python virtual environment either using `virtualevn` or `conda` for a suitable version +of Python. Let's assume that the new environment is called `gnosis-env`. + +Activate `gnosis-env` and install the library requirements using the following command, + + pip install -r requirements.txt + +**Important:** As the next step you must apply a patch to the django-neomodel library. Locate +the file `/site-packages/django_neomodel/__init__.py` and the method `get_choices(self, include_blank=True)`. + +Then replace the line, + + choices=list(self.choices)if self.choices else [] + +with the following, + + choices=[(k, v)for k, v in self.choices.items()] if self.choices else [] + +Locate the `settings.py` file in the directory `/Projects/stellar-gnosis/gnosis/gnosis/`. You need to set your +Neo4j database password so that Django can access it. Find the line, + + NEOMODEL_NEO4J_BOLT_URL = os.environ.get('NEO4J_BOLT_URL', 'bolt://neo4j:GnosisTest00@localhost:7687') + +The string `bolt://neo4j:GnosisTest00@localhost:7687'` indicates that the default (for development) Neo4j user +name is *neo4j* and the password is `GnosisTest00`. Replace these with the username and password you created earlier +during the Neo4j installation. You do not need a strong password as this is only used for development. You can use the +values in `settings.py` if you want; in this case, you don't have to modify the above line. + +#### Prepare the databases + +Change to the directory `/Projects/stellar-gnosis/gnosis/` where you can find the file `manage.py`. + +Prepare the Neo4j and sqlite3 databases by using the following commands, + + python manage.py install_labels + python manage.py makemigrations + python manage.py migrate + +Create a **Gnosis** administrator account using the below command and following the prompts: + + python manage.py createsuperuser + +You can now start the development server by issuing the command, + + python manage.py runserver + +You can access **Gnosis** running on your local machine by pointing your web browser to `http://127.0.0.1:8000/` + +## License + +Copyright 2010-2019 Commonwealth Scientific and Industrial Research Organisation (CSIRO). + +All Rights Reserved. + +NOTICE: All information contained herein remains the property of the CSIRO. The intellectual and technical concepts +contained herein are proprietary to the CSIRO and are protected by copyright law. Dissemination of this information +or reproduction of this material is strictly forbidden unless prior written permission is obtained from the CSIRO. From 15d981469ca87afed106aa70975c32b7bb8217ca Mon Sep 17 00:00:00 2001 From: Zhenghao-Zhao Date: Tue, 2 Apr 2019 13:12:02 +1100 Subject: [PATCH 008/154] update gitignore, remove ignored folders --- .gitignore | 125 ++++++++++++++++++++++++++- dist/gnosis-0.1.0-py3.6.egg | Bin 1115 -> 0 bytes gnosis.egg-info/PKG-INFO | 17 ---- gnosis.egg-info/SOURCES.txt | 6 -- gnosis.egg-info/dependency_links.txt | 1 - gnosis.egg-info/top_level.txt | 1 - 6 files changed, 122 insertions(+), 28 deletions(-) delete mode 100644 dist/gnosis-0.1.0-py3.6.egg delete mode 100644 gnosis.egg-info/PKG-INFO delete mode 100644 gnosis.egg-info/SOURCES.txt delete mode 100644 gnosis.egg-info/dependency_links.txt delete mode 100644 gnosis.egg-info/top_level.txt diff --git a/.gitignore b/.gitignore index 01294de..aee0e03 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,123 @@ -.idea/ -.DS_Store -*.sqlite3 +# Byte-compiled / optimized / DLL files __pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don’t work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ \ No newline at end of file diff --git a/dist/gnosis-0.1.0-py3.6.egg b/dist/gnosis-0.1.0-py3.6.egg deleted file mode 100644 index 5d6ea31e941d8d028e82623557e948144065e3a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1115 zcmWIWW@Zs#U|`^2*t4d_FMFPG%oIijhIvd341z#WS9f<^Pd_(*{Qz$WBXrW;yxRsm zt?zk%$o^p1I7jCOo5bM@3YyyzVuOy{tvY*Rjjm7qgr)x+uXjovlu0pKM=$`=%eSctYi> z<)nf>ZCv>}ZfT+o_e&pH;mqEJ{4d&H`wmu!Eybu})V$F{ZhXjwESN8Zu&G`l3-Z{202G2g;-YpG3M@YjNmJu1Iu zXZd|uU3|Wb&Fq8f?Ur;a=Bo8mkF4bL*W1AE&&U&XE;oYrLYac>a_ij_3x- z);n?SF^Q{_7ipFj>=8?z^7bKLuKgx4lbHPlQPy*>eQs2~fAUt(cJa8SGbbc-SKpqf z{_lp#^YSn4TRz{6pKZ`<*zLqB{g!>s;oq6svZWg&tzE;?wAY?F{>x>e!HGn>PY?D? zS|9Of)tda=^*VtwVv5p*iy{}E6VF)t%X5Q{-#|MjzHyiZ@}|4?@Q>zCrs$0u-n3.5.0, <3.7.0 -Description-Content-Type: text/markdown diff --git a/gnosis.egg-info/SOURCES.txt b/gnosis.egg-info/SOURCES.txt deleted file mode 100644 index a9d010c..0000000 --- a/gnosis.egg-info/SOURCES.txt +++ /dev/null @@ -1,6 +0,0 @@ -README.md -setup.py -gnosis.egg-info/PKG-INFO -gnosis.egg-info/SOURCES.txt -gnosis.egg-info/dependency_links.txt -gnosis.egg-info/top_level.txt \ No newline at end of file diff --git a/gnosis.egg-info/dependency_links.txt b/gnosis.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/gnosis.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/gnosis.egg-info/top_level.txt b/gnosis.egg-info/top_level.txt deleted file mode 100644 index 8b13789..0000000 --- a/gnosis.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ - From 1190c69acc9db000372945f014e34cf715971eb0 Mon Sep 17 00:00:00 2001 From: Zhenghao-Zhao Date: Tue, 2 Apr 2019 13:22:04 +1100 Subject: [PATCH 009/154] Update .gitignore --- .gitignore | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/.gitignore b/.gitignore index 01294de..8e487cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,126 @@ .idea/ .DS_Store *.sqlite3 +# Byte-compiled / optimized / DLL files __pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don’t work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ From 6940408a1085a5db4abbfa157f896b176a99a85f Mon Sep 17 00:00:00 2001 From: Zhenghao-Zhao Date: Tue, 9 Apr 2019 00:28:22 +1000 Subject: [PATCH 010/154] rename branch --- .gitignore | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/.gitignore b/.gitignore index 01294de..4d3dd4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,126 @@ .idea/ .DS_Store *.sqlite3 +# Byte-compiled / optimized / DLL files __pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don’t work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ \ No newline at end of file From bf35ff5edfb0425e0965ae37f99f21345b1b1817 Mon Sep 17 00:00:00 2001 From: PantelisElinas Date: Tue, 9 Apr 2019 10:06:36 +1000 Subject: [PATCH 011/154] Bumped version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c22855b..d642214 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setuptools.setup( name="gnosis", - version="0.2.1", + version="0.3.0b", author="Pantelis Elinas, Data61, CSIRO", author_email="pantelis.elinas@data61.csiro.au", url=URL, From 832288c55ed5e10e91fb9c0eb15e9be092e98df5 Mon Sep 17 00:00:00 2001 From: Zhenghao-Zhao Date: Tue, 9 Apr 2019 20:35:52 +1000 Subject: [PATCH 012/154] updates --- .idea/Gnosis-dev.iml | 21 ++ .idea/encodings.xml | 6 + .idea/inspectionProfiles/Project_Default.xml | 14 ++ .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/workspace.xml | 232 +++++++++++++++++++ 7 files changed, 294 insertions(+) create mode 100644 .idea/Gnosis-dev.iml create mode 100644 .idea/encodings.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml diff --git a/.idea/Gnosis-dev.iml b/.idea/Gnosis-dev.iml new file mode 100644 index 0000000..53328dd --- /dev/null +++ b/.idea/Gnosis-dev.iml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..004e846 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..1bbf15d --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b9951f5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..8bf3d5b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..9c9e972 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - 1554269292495 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From d5647ad4b67cc06a4241c59b0291c9a708db15d1 Mon Sep 17 00:00:00 2001 From: Zhenghao-Zhao Date: Tue, 9 Apr 2019 21:43:55 +1000 Subject: [PATCH 015/154] Update .gitignore --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7085b7b..8e487cd 100644 --- a/.gitignore +++ b/.gitignore @@ -88,7 +88,7 @@ ipython_config.py # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don’t work, or not +# having no cross-platform support, pipenv may install dependencies that don’t work, or not # install all needed dependencies. #Pipfile.lock @@ -124,4 +124,3 @@ dmypy.json # Pyre type checker .pyre/ -__pycache__/ From c0b78dff94d6201b44142c595dcdcf37ae361c0b Mon Sep 17 00:00:00 2001 From: PantelisElinas Date: Wed, 10 Apr 2019 10:43:05 +1000 Subject: [PATCH 016/154] Invalid arXiv URL is now correctly detected and a message informing the user is shown. Also, the import view now uses card format like the rest of the site. --- gnosis/catalog/templates/paper_form.html | 35 ++++++++++++------------ gnosis/catalog/views/views.py | 15 +++++++--- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/gnosis/catalog/templates/paper_form.html b/gnosis/catalog/templates/paper_form.html index 3d1914a..759094a 100644 --- a/gnosis/catalog/templates/paper_form.html +++ b/gnosis/catalog/templates/paper_form.html @@ -1,26 +1,25 @@ {% extends "gnosis_theme.html" %} {% block content %} -
-

New Paper

+
+
+

New Paper

+
+
-
- {% csrf_token %} - - {{ form }} -
-
- -
+ {% if message %} +

{{ message }}

+ {% endif %} -
-

{{ message }}

+
+ {% csrf_token %} + + {{ form }} +
+
+ +
+

*required
- - {% if message %} -

{{ message }}

- {% endif %} -

*required
-
{% endblock %} \ No newline at end of file diff --git a/gnosis/catalog/views/views.py b/gnosis/catalog/views/views.py index 6199bb3..cbf651e 100644 --- a/gnosis/catalog/views/views.py +++ b/gnosis/catalog/views/views.py @@ -986,11 +986,18 @@ def paper_create_from_arxiv(request): # get the data from arxiv url = request.POST["url"] # check if url includes https, and if not add it - if not url.startswith("https://"): + if not url.startswith("https://") and not url.startswith("http://"): url = "https://" + url - # retrieve paper info. If the information cannot be retrieved from remote - # server, then we will return an error message and redirect to paper_form.html. - title, authors, abstract = get_paper_info(url) + + print("Given url: {}".format(url)) + # check if valid url before trying to get the data. + if not url.startswith("https://arxiv.org/abs/") and not url.startswith("http://arxiv.org/abs/"): + title, authors, abstract = None, None, None + else: + # retrieve paper info. If the information cannot be retrieved from remote + # server, then we will return an error message and redirect to paper_form.html. + title, authors, abstract = get_paper_info(url) + if title is None or authors is None or abstract is None: form = PaperImportForm() return render( From 9f900bb463cdff3b170a01c75bbf8123229e652f Mon Sep 17 00:00:00 2001 From: PantelisElinas Date: Wed, 10 Apr 2019 12:53:10 +1000 Subject: [PATCH 017/154] Maximum width set to 1024px so that if the browser window is made larger, the amount of content displayed does not overwhelm the user. --- gnosis/templates/gnosis_theme.html | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gnosis/templates/gnosis_theme.html b/gnosis/templates/gnosis_theme.html index 4caa6ef..e8bd57b 100644 --- a/gnosis/templates/gnosis_theme.html +++ b/gnosis/templates/gnosis_theme.html @@ -11,6 +11,10 @@ -