diff --git a/.gitignore b/.gitignore index a2274b00d145b376cee7323396849040f0118584..2673ca550fb5d82633cc80cf1c145f3c9b1d157e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ __pycache__ *.json +!**/fixtures/*.json *~ @@ -14,4 +15,4 @@ SCIPOST_JOURNALS UPLOADS docs/_build -local_files \ No newline at end of file +local_files diff --git a/comments/forms.py b/comments/forms.py index 0e94f326ed13e5e2388230d1c5d4cdf41a19fecf..d87f969a38eb8bb7c03fba8d8fc7bfb632141207 100644 --- a/comments/forms.py +++ b/comments/forms.py @@ -62,7 +62,6 @@ class CommentForm(forms.ModelForm): ) - class VetCommentForm(forms.Form): action_option = forms.ChoiceField(widget=forms.RadioSelect, choices=COMMENT_ACTION_CHOICES, required=True, label='Action') diff --git a/comments/models.py b/comments/models.py index d18801033f5fa367ab1b16bfbf812dd0cb0e25fa..1e6c1c5d1da23a916f201a3e664b24e001ab0d7d 100644 --- a/comments/models.py +++ b/comments/models.py @@ -33,15 +33,11 @@ COMMENT_STATUS = ( ) comment_status_dict = dict(COMMENT_STATUS) + class Comment(models.Model): """ A Comment is an unsollicited note, submitted by a Contributor, on a particular publication or in reply to an earlier Comment. """ - # status: - # 1: vetted - # 0: unvetted - # -1: rejected (unclear) - # -2: rejected (incorrect) - # -3: rejected (not useful) + status = models.SmallIntegerField(default=0) vetted_by = models.ForeignKey(Contributor, blank=True, null=True, on_delete=models.CASCADE, diff --git a/journals/models.py b/journals/models.py index 8581d9d7915b7f7f4ad6acc16d0d5b2097ad3ed7..7afb01756af51cc4ac083b00d0fbff342e9d2b27 100644 --- a/journals/models.py +++ b/journals/models.py @@ -22,9 +22,11 @@ SCIPOST_JOURNALS = ( ) journals_dict = dict(SCIPOST_JOURNALS) + class JournalNameError(Exception): def __init__(self, name): self.name = name + def __str__(self): return self.name diff --git a/requirements.txt b/requirements.txt index 1dbfb212397ecfd37a21f1fac65f3bafd5975002..b25c7e2fba57bc091dcc0578fac5910b75817117 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,14 +10,18 @@ django-mptt==0.8.6 django-simple-captcha==0.5.3 djangorestframework==3.5.3 docutils==0.12 +factory-boy==2.7.0 +fake-factory==0.7.2 feedparser==5.2.1 imagesize==0.7.1 Jinja2==2.8 Markdown==2.6.7 MarkupSafe==0.23 +pep8==1.7.0 Pillow==3.4.2 psycopg2==2.6.2 Pygments==2.1.3 +python-dateutil==2.6.0 pytz==2016.7 requests==2.12.1 six==1.10.0 diff --git a/scipost/factories.py b/scipost/factories.py new file mode 100644 index 0000000000000000000000000000000000000000..80a4c045804aa2cf133823d882323c7d20013927 --- /dev/null +++ b/scipost/factories.py @@ -0,0 +1,41 @@ +import factory + +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group, User + +from .models import Contributor + + +class ContributorFactory(factory.django.DjangoModelFactory): + class Meta: + model = Contributor + + title = "MR" + user = factory.SubFactory(UserFactory, contributor=None) + status = 1 + vetted_by = factory.SubFactory(ContributorFactory) + + +class UserFactory(factory.django.DjangoModelFactory): + class Meta: + model = get_user_model() + + username = factory.Faker('user_name') + password = factory.Faker('password') + email = factory.Faker('safe_email') + first_name = factory.Faker('first_name') + last_name = factory.Faker('last_name') + # When user object is created, associate new Contributor object to it. + contributor = factory.RelatedFactory(ContributorFactory, 'user') + + @factory.post_generation + def groups(self, create, extracted, **kwargs): + # If the object is not saved, we cannot use many-to-many relationship. + if not create: + return + # If group objects were passed in, use those. + if extracted: + for group in extracted: + self.groups.add(group) + else: + self.groups.add(Group.objects.get(name="Registered Contributors")) diff --git a/scipost/fixtures/contributors.json b/scipost/fixtures/contributors.json new file mode 100644 index 0000000000000000000000000000000000000000..a4863604278b1460829d963d108b4db098579aef --- /dev/null +++ b/scipost/fixtures/contributors.json @@ -0,0 +1,32 @@ +[ + { + "model": "auth.user", + "pk": 1, + "fields": { + "password": "pbkdf2_sha256$30000$iqtXX60Ahqcx$IKfNZNSMbSca/agzPXHTdEej3dXhQi1sK/MCrBTnuW4=", + "last_login": null, + "is_superuser": false, + "username": "Test", + "first_name": "Firstname", + "last_name": "Testuser", + "email": "testuser@test.com", + "is_staff": false, + "is_active": true, + "date_joined": "2016-12-14T20:41:31.282Z", + "groups": [ + 6 + ], + "user_permissions": [] + } + }, + { + "model": "scipost.contributor", + "pk": 2, + "fields": { + "user": 1, + "status": 1, + "title": "MR", + "vetted_by": 2 + } + } +] diff --git a/scipost/fixtures/groups.json b/scipost/fixtures/groups.json new file mode 100644 index 0000000000000000000000000000000000000000..82fafdc4738001941947ba92ecf8d9a5749ad11d --- /dev/null +++ b/scipost/fixtures/groups.json @@ -0,0 +1,116 @@ +[ +{ + "model": "auth.group", + "pk": 1, + "fields": { + "name": "SciPost Administrators", + "permissions": [ + 143, + 130, + 131, + 148, + 128, + 147, + 139, + 137, + 140, + 126, + 138, + 142 + ] + } +}, +{ + "model": "auth.group", + "pk": 2, + "fields": { + "name": "Advisory Board", + "permissions": [ + 128 + ] + } +}, +{ + "model": "auth.group", + "pk": 3, + "fields": { + "name": "Editorial Administrators", + "permissions": [ + 143, + 148, + 147, + 149, + 142 + ] + } +}, +{ + "model": "auth.group", + "pk": 4, + "fields": { + "name": "Editorial College", + "permissions": [ + 144, + 145, + 142, + 132 + ] + } +}, +{ + "model": "auth.group", + "pk": 5, + "fields": { + "name": "Vetting Editors", + "permissions": [ + 139, + 137, + 140, + 138 + ] + } +}, +{ + "model": "auth.group", + "pk": 6, + "fields": { + "name": "Registered Contributors", + "permissions": [ + 134, + 146, + 135, + 136, + 133, + 141 + ] + } +}, +{ + "model": "auth.group", + "pk": 7, + "fields": { + "name": "Testers", + "permissions": [] + } +}, +{ + "model": "auth.group", + "pk": 8, + "fields": { + "name": "Ambassadors", + "permissions": [ + 128 + ] + } +}, +{ + "model": "auth.group", + "pk": 9, + "fields": { + "name": "Junior Ambassadors", + "permissions": [ + 127 + ] + } +} +] diff --git a/scipost/fixtures/permissions.json b/scipost/fixtures/permissions.json new file mode 100644 index 0000000000000000000000000000000000000000..a718c3476c978d97c20a15d8b81f01ac0500596f --- /dev/null +++ b/scipost/fixtures/permissions.json @@ -0,0 +1,1343 @@ +[ +{ + "model": "auth.permission", + "pk": 1, + "fields": { + "name": "Can add log entry", + "content_type": 1, + "codename": "add_logentry" + } +}, +{ + "model": "auth.permission", + "pk": 2, + "fields": { + "name": "Can change log entry", + "content_type": 1, + "codename": "change_logentry" + } +}, +{ + "model": "auth.permission", + "pk": 3, + "fields": { + "name": "Can delete log entry", + "content_type": 1, + "codename": "delete_logentry" + } +}, +{ + "model": "auth.permission", + "pk": 4, + "fields": { + "name": "Can add user", + "content_type": 2, + "codename": "add_user" + } +}, +{ + "model": "auth.permission", + "pk": 5, + "fields": { + "name": "Can change user", + "content_type": 2, + "codename": "change_user" + } +}, +{ + "model": "auth.permission", + "pk": 6, + "fields": { + "name": "Can delete user", + "content_type": 2, + "codename": "delete_user" + } +}, +{ + "model": "auth.permission", + "pk": 7, + "fields": { + "name": "Can add group", + "content_type": 3, + "codename": "add_group" + } +}, +{ + "model": "auth.permission", + "pk": 8, + "fields": { + "name": "Can change group", + "content_type": 3, + "codename": "change_group" + } +}, +{ + "model": "auth.permission", + "pk": 9, + "fields": { + "name": "Can delete group", + "content_type": 3, + "codename": "delete_group" + } +}, +{ + "model": "auth.permission", + "pk": 10, + "fields": { + "name": "Can add permission", + "content_type": 4, + "codename": "add_permission" + } +}, +{ + "model": "auth.permission", + "pk": 11, + "fields": { + "name": "Can change permission", + "content_type": 4, + "codename": "change_permission" + } +}, +{ + "model": "auth.permission", + "pk": 12, + "fields": { + "name": "Can delete permission", + "content_type": 4, + "codename": "delete_permission" + } +}, +{ + "model": "auth.permission", + "pk": 13, + "fields": { + "name": "Can add content type", + "content_type": 5, + "codename": "add_contenttype" + } +}, +{ + "model": "auth.permission", + "pk": 14, + "fields": { + "name": "Can change content type", + "content_type": 5, + "codename": "change_contenttype" + } +}, +{ + "model": "auth.permission", + "pk": 15, + "fields": { + "name": "Can delete content type", + "content_type": 5, + "codename": "delete_contenttype" + } +}, +{ + "model": "auth.permission", + "pk": 16, + "fields": { + "name": "Can add session", + "content_type": 6, + "codename": "add_session" + } +}, +{ + "model": "auth.permission", + "pk": 17, + "fields": { + "name": "Can change session", + "content_type": 6, + "codename": "change_session" + } +}, +{ + "model": "auth.permission", + "pk": 18, + "fields": { + "name": "Can delete session", + "content_type": 6, + "codename": "delete_session" + } +}, +{ + "model": "auth.permission", + "pk": 19, + "fields": { + "name": "Can add captcha store", + "content_type": 7, + "codename": "add_captchastore" + } +}, +{ + "model": "auth.permission", + "pk": 20, + "fields": { + "name": "Can change captcha store", + "content_type": 7, + "codename": "change_captchastore" + } +}, +{ + "model": "auth.permission", + "pk": 21, + "fields": { + "name": "Can delete captcha store", + "content_type": 7, + "codename": "delete_captchastore" + } +}, +{ + "model": "auth.permission", + "pk": 22, + "fields": { + "name": "Can add user object permission", + "content_type": 8, + "codename": "add_userobjectpermission" + } +}, +{ + "model": "auth.permission", + "pk": 23, + "fields": { + "name": "Can change user object permission", + "content_type": 8, + "codename": "change_userobjectpermission" + } +}, +{ + "model": "auth.permission", + "pk": 24, + "fields": { + "name": "Can delete user object permission", + "content_type": 8, + "codename": "delete_userobjectpermission" + } +}, +{ + "model": "auth.permission", + "pk": 25, + "fields": { + "name": "Can add group object permission", + "content_type": 9, + "codename": "add_groupobjectpermission" + } +}, +{ + "model": "auth.permission", + "pk": 26, + "fields": { + "name": "Can change group object permission", + "content_type": 9, + "codename": "change_groupobjectpermission" + } +}, +{ + "model": "auth.permission", + "pk": 27, + "fields": { + "name": "Can delete group object permission", + "content_type": 9, + "codename": "delete_groupobjectpermission" + } +}, +{ + "model": "auth.permission", + "pk": 28, + "fields": { + "name": "Can add commentary", + "content_type": 10, + "codename": "add_commentary" + } +}, +{ + "model": "auth.permission", + "pk": 29, + "fields": { + "name": "Can change commentary", + "content_type": 10, + "codename": "change_commentary" + } +}, +{ + "model": "auth.permission", + "pk": 30, + "fields": { + "name": "Can delete commentary", + "content_type": 10, + "codename": "delete_commentary" + } +}, +{ + "model": "auth.permission", + "pk": 31, + "fields": { + "name": "Can add comment", + "content_type": 11, + "codename": "add_comment" + } +}, +{ + "model": "auth.permission", + "pk": 32, + "fields": { + "name": "Can change comment", + "content_type": 11, + "codename": "change_comment" + } +}, +{ + "model": "auth.permission", + "pk": 33, + "fields": { + "name": "Can delete comment", + "content_type": 11, + "codename": "delete_comment" + } +}, +{ + "model": "auth.permission", + "pk": 34, + "fields": { + "name": "Can add issue", + "content_type": 12, + "codename": "add_issue" + } +}, +{ + "model": "auth.permission", + "pk": 35, + "fields": { + "name": "Can change issue", + "content_type": 12, + "codename": "change_issue" + } +}, +{ + "model": "auth.permission", + "pk": 36, + "fields": { + "name": "Can delete issue", + "content_type": 12, + "codename": "delete_issue" + } +}, +{ + "model": "auth.permission", + "pk": 37, + "fields": { + "name": "Can add journal", + "content_type": 13, + "codename": "add_journal" + } +}, +{ + "model": "auth.permission", + "pk": 38, + "fields": { + "name": "Can change journal", + "content_type": 13, + "codename": "change_journal" + } +}, +{ + "model": "auth.permission", + "pk": 39, + "fields": { + "name": "Can delete journal", + "content_type": 13, + "codename": "delete_journal" + } +}, +{ + "model": "auth.permission", + "pk": 40, + "fields": { + "name": "Can add unregistered author", + "content_type": 14, + "codename": "add_unregisteredauthor" + } +}, +{ + "model": "auth.permission", + "pk": 41, + "fields": { + "name": "Can change unregistered author", + "content_type": 14, + "codename": "change_unregisteredauthor" + } +}, +{ + "model": "auth.permission", + "pk": 42, + "fields": { + "name": "Can delete unregistered author", + "content_type": 14, + "codename": "delete_unregisteredauthor" + } +}, +{ + "model": "auth.permission", + "pk": 43, + "fields": { + "name": "Can add deposit", + "content_type": 15, + "codename": "add_deposit" + } +}, +{ + "model": "auth.permission", + "pk": 44, + "fields": { + "name": "Can change deposit", + "content_type": 15, + "codename": "change_deposit" + } +}, +{ + "model": "auth.permission", + "pk": 45, + "fields": { + "name": "Can delete deposit", + "content_type": 15, + "codename": "delete_deposit" + } +}, +{ + "model": "auth.permission", + "pk": 46, + "fields": { + "name": "Can add publication", + "content_type": 16, + "codename": "add_publication" + } +}, +{ + "model": "auth.permission", + "pk": 47, + "fields": { + "name": "Can change publication", + "content_type": 16, + "codename": "change_publication" + } +}, +{ + "model": "auth.permission", + "pk": 48, + "fields": { + "name": "Can delete publication", + "content_type": 16, + "codename": "delete_publication" + } +}, +{ + "model": "auth.permission", + "pk": 49, + "fields": { + "name": "Can add volume", + "content_type": 17, + "codename": "add_volume" + } +}, +{ + "model": "auth.permission", + "pk": 50, + "fields": { + "name": "Can change volume", + "content_type": 17, + "codename": "change_volume" + } +}, +{ + "model": "auth.permission", + "pk": 51, + "fields": { + "name": "Can delete volume", + "content_type": 17, + "codename": "delete_volume" + } +}, +{ + "model": "auth.permission", + "pk": 52, + "fields": { + "name": "Can add news item", + "content_type": 18, + "codename": "add_newsitem" + } +}, +{ + "model": "auth.permission", + "pk": 53, + "fields": { + "name": "Can change news item", + "content_type": 18, + "codename": "change_newsitem" + } +}, +{ + "model": "auth.permission", + "pk": 54, + "fields": { + "name": "Can delete news item", + "content_type": 18, + "codename": "delete_newsitem" + } +}, +{ + "model": "auth.permission", + "pk": 55, + "fields": { + "name": "Can add precooked email", + "content_type": 19, + "codename": "add_precookedemail" + } +}, +{ + "model": "auth.permission", + "pk": 56, + "fields": { + "name": "Can change precooked email", + "content_type": 19, + "codename": "change_precookedemail" + } +}, +{ + "model": "auth.permission", + "pk": 57, + "fields": { + "name": "Can delete precooked email", + "content_type": 19, + "codename": "delete_precookedemail" + } +}, +{ + "model": "auth.permission", + "pk": 58, + "fields": { + "name": "Can add affiliation object", + "content_type": 20, + "codename": "add_affiliationobject" + } +}, +{ + "model": "auth.permission", + "pk": 59, + "fields": { + "name": "Can change affiliation object", + "content_type": 20, + "codename": "change_affiliationobject" + } +}, +{ + "model": "auth.permission", + "pk": 60, + "fields": { + "name": "Can delete affiliation object", + "content_type": 20, + "codename": "delete_affiliationobject" + } +}, +{ + "model": "auth.permission", + "pk": 61, + "fields": { + "name": "Can add registration invitation", + "content_type": 21, + "codename": "add_registrationinvitation" + } +}, +{ + "model": "auth.permission", + "pk": 62, + "fields": { + "name": "Can change registration invitation", + "content_type": 21, + "codename": "change_registrationinvitation" + } +}, +{ + "model": "auth.permission", + "pk": 63, + "fields": { + "name": "Can delete registration invitation", + "content_type": 21, + "codename": "delete_registrationinvitation" + } +}, +{ + "model": "auth.permission", + "pk": 64, + "fields": { + "name": "Can add spb membership agreement", + "content_type": 22, + "codename": "add_spbmembershipagreement" + } +}, +{ + "model": "auth.permission", + "pk": 65, + "fields": { + "name": "Can change spb membership agreement", + "content_type": 22, + "codename": "change_spbmembershipagreement" + } +}, +{ + "model": "auth.permission", + "pk": 66, + "fields": { + "name": "Can delete spb membership agreement", + "content_type": 22, + "codename": "delete_spbmembershipagreement" + } +}, +{ + "model": "auth.permission", + "pk": 67, + "fields": { + "name": "Can add supporting partner", + "content_type": 23, + "codename": "add_supportingpartner" + } +}, +{ + "model": "auth.permission", + "pk": 68, + "fields": { + "name": "Can change supporting partner", + "content_type": 23, + "codename": "change_supportingpartner" + } +}, +{ + "model": "auth.permission", + "pk": 69, + "fields": { + "name": "Can delete supporting partner", + "content_type": 23, + "codename": "delete_supportingpartner" + } +}, +{ + "model": "auth.permission", + "pk": 70, + "fields": { + "name": "Can add arc", + "content_type": 24, + "codename": "add_arc" + } +}, +{ + "model": "auth.permission", + "pk": 71, + "fields": { + "name": "Can change arc", + "content_type": 24, + "codename": "change_arc" + } +}, +{ + "model": "auth.permission", + "pk": 72, + "fields": { + "name": "Can delete arc", + "content_type": 24, + "codename": "delete_arc" + } +}, +{ + "model": "auth.permission", + "pk": 73, + "fields": { + "name": "Can add graph", + "content_type": 25, + "codename": "add_graph" + } +}, +{ + "model": "auth.permission", + "pk": 74, + "fields": { + "name": "Can view graph", + "content_type": 25, + "codename": "view_graph" + } +}, +{ + "model": "auth.permission", + "pk": 75, + "fields": { + "name": "Can change graph", + "content_type": 25, + "codename": "change_graph" + } +}, +{ + "model": "auth.permission", + "pk": 76, + "fields": { + "name": "Can delete graph", + "content_type": 25, + "codename": "delete_graph" + } +}, +{ + "model": "auth.permission", + "pk": 77, + "fields": { + "name": "Can add contributor", + "content_type": 26, + "codename": "add_contributor" + } +}, +{ + "model": "auth.permission", + "pk": 78, + "fields": { + "name": "Can change contributor", + "content_type": 26, + "codename": "change_contributor" + } +}, +{ + "model": "auth.permission", + "pk": 79, + "fields": { + "name": "Can delete contributor", + "content_type": 26, + "codename": "delete_contributor" + } +}, +{ + "model": "auth.permission", + "pk": 80, + "fields": { + "name": "Can add authorship claim", + "content_type": 27, + "codename": "add_authorshipclaim" + } +}, +{ + "model": "auth.permission", + "pk": 81, + "fields": { + "name": "Can change authorship claim", + "content_type": 27, + "codename": "change_authorshipclaim" + } +}, +{ + "model": "auth.permission", + "pk": 82, + "fields": { + "name": "Can delete authorship claim", + "content_type": 27, + "codename": "delete_authorshipclaim" + } +}, +{ + "model": "auth.permission", + "pk": 83, + "fields": { + "name": "Can add unavailability period", + "content_type": 28, + "codename": "add_unavailabilityperiod" + } +}, +{ + "model": "auth.permission", + "pk": 84, + "fields": { + "name": "Can change unavailability period", + "content_type": 28, + "codename": "change_unavailabilityperiod" + } +}, +{ + "model": "auth.permission", + "pk": 85, + "fields": { + "name": "Can delete unavailability period", + "content_type": 28, + "codename": "delete_unavailabilityperiod" + } +}, +{ + "model": "auth.permission", + "pk": 86, + "fields": { + "name": "Can add list", + "content_type": 29, + "codename": "add_list" + } +}, +{ + "model": "auth.permission", + "pk": 87, + "fields": { + "name": "Can view list", + "content_type": 29, + "codename": "view_list" + } +}, +{ + "model": "auth.permission", + "pk": 88, + "fields": { + "name": "Can change list", + "content_type": 29, + "codename": "change_list" + } +}, +{ + "model": "auth.permission", + "pk": 89, + "fields": { + "name": "Can delete list", + "content_type": 29, + "codename": "delete_list" + } +}, +{ + "model": "auth.permission", + "pk": 90, + "fields": { + "name": "Can add node", + "content_type": 30, + "codename": "add_node" + } +}, +{ + "model": "auth.permission", + "pk": 91, + "fields": { + "name": "Can view node", + "content_type": 30, + "codename": "view_node" + } +}, +{ + "model": "auth.permission", + "pk": 92, + "fields": { + "name": "Can change node", + "content_type": 30, + "codename": "change_node" + } +}, +{ + "model": "auth.permission", + "pk": 93, + "fields": { + "name": "Can delete node", + "content_type": 30, + "codename": "delete_node" + } +}, +{ + "model": "auth.permission", + "pk": 94, + "fields": { + "name": "Can add draft invitation", + "content_type": 31, + "codename": "add_draftinvitation" + } +}, +{ + "model": "auth.permission", + "pk": 95, + "fields": { + "name": "Can change draft invitation", + "content_type": 31, + "codename": "change_draftinvitation" + } +}, +{ + "model": "auth.permission", + "pk": 96, + "fields": { + "name": "Can delete draft invitation", + "content_type": 31, + "codename": "delete_draftinvitation" + } +}, +{ + "model": "auth.permission", + "pk": 97, + "fields": { + "name": "Can add team", + "content_type": 32, + "codename": "add_team" + } +}, +{ + "model": "auth.permission", + "pk": 98, + "fields": { + "name": "Can view team", + "content_type": 32, + "codename": "view_team" + } +}, +{ + "model": "auth.permission", + "pk": 99, + "fields": { + "name": "Can change team", + "content_type": 32, + "codename": "change_team" + } +}, +{ + "model": "auth.permission", + "pk": 100, + "fields": { + "name": "Can delete team", + "content_type": 32, + "codename": "delete_team" + } +}, +{ + "model": "auth.permission", + "pk": 101, + "fields": { + "name": "Can add remark", + "content_type": 33, + "codename": "add_remark" + } +}, +{ + "model": "auth.permission", + "pk": 102, + "fields": { + "name": "Can change remark", + "content_type": 33, + "codename": "change_remark" + } +}, +{ + "model": "auth.permission", + "pk": 103, + "fields": { + "name": "Can delete remark", + "content_type": 33, + "codename": "delete_remark" + } +}, +{ + "model": "auth.permission", + "pk": 104, + "fields": { + "name": "Can add editorial communication", + "content_type": 34, + "codename": "add_editorialcommunication" + } +}, +{ + "model": "auth.permission", + "pk": 105, + "fields": { + "name": "Can change editorial communication", + "content_type": 34, + "codename": "change_editorialcommunication" + } +}, +{ + "model": "auth.permission", + "pk": 106, + "fields": { + "name": "Can delete editorial communication", + "content_type": 34, + "codename": "delete_editorialcommunication" + } +}, +{ + "model": "auth.permission", + "pk": 107, + "fields": { + "name": "Can add referee invitation", + "content_type": 35, + "codename": "add_refereeinvitation" + } +}, +{ + "model": "auth.permission", + "pk": 108, + "fields": { + "name": "Can change referee invitation", + "content_type": 35, + "codename": "change_refereeinvitation" + } +}, +{ + "model": "auth.permission", + "pk": 109, + "fields": { + "name": "Can delete referee invitation", + "content_type": 35, + "codename": "delete_refereeinvitation" + } +}, +{ + "model": "auth.permission", + "pk": 110, + "fields": { + "name": "Can add submission", + "content_type": 36, + "codename": "add_submission" + } +}, +{ + "model": "auth.permission", + "pk": 111, + "fields": { + "name": "Can change submission", + "content_type": 36, + "codename": "change_submission" + } +}, +{ + "model": "auth.permission", + "pk": 112, + "fields": { + "name": "Can delete submission", + "content_type": 36, + "codename": "delete_submission" + } +}, +{ + "model": "auth.permission", + "pk": 113, + "fields": { + "name": "Can take editorial actions", + "content_type": 36, + "codename": "can_take_editorial_actions" + } +}, +{ + "model": "auth.permission", + "pk": 114, + "fields": { + "name": "Can add report", + "content_type": 37, + "codename": "add_report" + } +}, +{ + "model": "auth.permission", + "pk": 115, + "fields": { + "name": "Can change report", + "content_type": 37, + "codename": "change_report" + } +}, +{ + "model": "auth.permission", + "pk": 116, + "fields": { + "name": "Can delete report", + "content_type": 37, + "codename": "delete_report" + } +}, +{ + "model": "auth.permission", + "pk": 117, + "fields": { + "name": "Can add eic recommendation", + "content_type": 38, + "codename": "add_eicrecommendation" + } +}, +{ + "model": "auth.permission", + "pk": 118, + "fields": { + "name": "Can change eic recommendation", + "content_type": 38, + "codename": "change_eicrecommendation" + } +}, +{ + "model": "auth.permission", + "pk": 119, + "fields": { + "name": "Can delete eic recommendation", + "content_type": 38, + "codename": "delete_eicrecommendation" + } +}, +{ + "model": "auth.permission", + "pk": 120, + "fields": { + "name": "Can add editorial assignment", + "content_type": 39, + "codename": "add_editorialassignment" + } +}, +{ + "model": "auth.permission", + "pk": 121, + "fields": { + "name": "Can change editorial assignment", + "content_type": 39, + "codename": "change_editorialassignment" + } +}, +{ + "model": "auth.permission", + "pk": 122, + "fields": { + "name": "Can delete editorial assignment", + "content_type": 39, + "codename": "delete_editorialassignment" + } +}, +{ + "model": "auth.permission", + "pk": 123, + "fields": { + "name": "Can add thesis link", + "content_type": 40, + "codename": "add_thesislink" + } +}, +{ + "model": "auth.permission", + "pk": 124, + "fields": { + "name": "Can change thesis link", + "content_type": 40, + "codename": "change_thesislink" + } +}, +{ + "model": "auth.permission", + "pk": 125, + "fields": { + "name": "Can delete thesis link", + "content_type": 40, + "codename": "delete_thesislink" + } +}, +{ + "model": "auth.permission", + "pk": 126, + "fields": { + "name": "Can vet registration requests", + "content_type": 26, + "codename": "can_vet_registration_requests" + } +}, +{ + "model": "auth.permission", + "pk": 127, + "fields": { + "name": "Can draft registration invitations", + "content_type": 26, + "codename": "can_draft_registration_invitations" + } +}, +{ + "model": "auth.permission", + "pk": 128, + "fields": { + "name": "Can manage registration invitations", + "content_type": 26, + "codename": "can_manage_registration_invitations" + } +}, +{ + "model": "auth.permission", + "pk": 129, + "fields": { + "name": "Can invite Fellows", + "content_type": 26, + "codename": "can_invite_Fellows" + } +}, +{ + "model": "auth.permission", + "pk": 130, + "fields": { + "name": "Can email group members", + "content_type": 26, + "codename": "can_email_group_members" + } +}, +{ + "model": "auth.permission", + "pk": 131, + "fields": { + "name": "Can email particulars", + "content_type": 26, + "codename": "can_email_particulars" + } +}, +{ + "model": "auth.permission", + "pk": 132, + "fields": { + "name": "Can view By-laws of Editorial College", + "content_type": 26, + "codename": "view_bylaws" + } +}, +{ + "model": "auth.permission", + "pk": 133, + "fields": { + "name": "Can submit Comments", + "content_type": 26, + "codename": "can_submit_comments" + } +}, +{ + "model": "auth.permission", + "pk": 134, + "fields": { + "name": "Can express opinion on Comments", + "content_type": 26, + "codename": "can_express_opinion_on_comments" + } +}, +{ + "model": "auth.permission", + "pk": 135, + "fields": { + "name": "Can request opening of Commentara Pages", + "content_type": 26, + "codename": "can_request_commentary_pages" + } +}, +{ + "model": "auth.permission", + "pk": 136, + "fields": { + "name": "Can request Thesis Links", + "content_type": 26, + "codename": "can_request_thesislinks" + } +}, +{ + "model": "auth.permission", + "pk": 137, + "fields": { + "name": "Can vet Commentary page requests", + "content_type": 26, + "codename": "can_vet_commentary_requests" + } +}, +{ + "model": "auth.permission", + "pk": 138, + "fields": { + "name": "Can vet Thesis Link requests", + "content_type": 26, + "codename": "can_vet_thesislink_requests" + } +}, +{ + "model": "auth.permission", + "pk": 139, + "fields": { + "name": "Can vet Authorship claims", + "content_type": 26, + "codename": "can_vet_authorship_claims" + } +}, +{ + "model": "auth.permission", + "pk": 140, + "fields": { + "name": "Can vet submitted Comments", + "content_type": 26, + "codename": "can_vet_comments" + } +}, +{ + "model": "auth.permission", + "pk": 141, + "fields": { + "name": "Can submit manuscript", + "content_type": 26, + "codename": "can_submit_manuscript" + } +}, +{ + "model": "auth.permission", + "pk": 142, + "fields": { + "name": "Can view Submissions Pool", + "content_type": 26, + "codename": "can_view_pool" + } +}, +{ + "model": "auth.permission", + "pk": 143, + "fields": { + "name": "Can assign incoming Submissions to potential Editor-in-charge", + "content_type": 26, + "codename": "can_assign_submissions" + } +}, +{ + "model": "auth.permission", + "pk": 144, + "fields": { + "name": "Can take charge (become Editor-in-charge) of submissions", + "content_type": 26, + "codename": "can_take_charge_of_submissions" + } +}, +{ + "model": "auth.permission", + "pk": 145, + "fields": { + "name": "Can vet submitted Reports", + "content_type": 26, + "codename": "can_vet_submitted_reports" + } +}, +{ + "model": "auth.permission", + "pk": 146, + "fields": { + "name": "Can act as a referee and submit reports on Submissions", + "content_type": 26, + "codename": "can_referee" + } +}, +{ + "model": "auth.permission", + "pk": 147, + "fields": { + "name": "Can prepare recommendations for voting", + "content_type": 26, + "codename": "can_prepare_recommendations_for_voting" + } +}, +{ + "model": "auth.permission", + "pk": 148, + "fields": { + "name": "Can fix the College voting decision", + "content_type": 26, + "codename": "can_fix_College_decision" + } +}, +{ + "model": "auth.permission", + "pk": 149, + "fields": { + "name": "Can publish accepted submission", + "content_type": 26, + "codename": "can_publish_accepted_submission" + } +} +] diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py index b1bec7e869bd53965dfc402c1d8f29b86eb9634b..86a4ef7af77102d1c21cb930873bd6a59ee4d58f 100644 --- a/scipost/management/commands/add_groups_and_permissions.py +++ b/scipost/management/commands/add_groups_and_permissions.py @@ -5,21 +5,22 @@ from django.contrib.contenttypes.models import ContentType from scipost.models import Contributor + class Command(BaseCommand): help = 'Defines groups and permissions' - + def add_arguments(self, parser): """Append arguments optionally for setup of Contributor roles.""" - parser.add_argument('-u', '--setup-user', metavar='<username>', type=str, required=False, + parser.add_argument('-u', '--setup-user', metavar='<username>', type=str, required=False, help='Username to make registered contributor') - parser.add_argument('-a', '--make-admin', required=False, action='store_true', + parser.add_argument('-a', '--make-admin', required=False, action='store_true', help='Grant admin permissions to user (superuser only)') - parser.add_argument('-t', '--make-tester', required=False, action='store_true', + parser.add_argument('-t', '--make-tester', required=False, action='store_true', help='Grant test permissions to user') def handle(self, *args, **options): """Append all user Groups and setup a Contributor roles to user.""" - + # Create Groups SciPostAdmin, created = Group.objects.get_or_create(name='SciPost Administrators') AdvisoryBoard, created = Group.objects.get_or_create(name='Advisory Board') @@ -203,7 +204,7 @@ class Command(BaseCommand): ) self.stdout.write(self.style.SUCCESS('Successfully created groups and permissions.')) - + if options['setup_user']: # Username is given, check options try: @@ -222,8 +223,8 @@ class Command(BaseCommand): elif options['make_admin']: # Make admin failed, user not a superuser self.stdout.write(self.style.WARNING('User %s is not a superuser.' % user)) - + if options['make_tester']: # Setup test contributor user.groups.add(Testers) - self.stdout.write(self.style.SUCCESS('Successfully made %s tester.' % user)) \ No newline at end of file + self.stdout.write(self.style.SUCCESS('Successfully made %s tester.' % user)) diff --git a/scipost/management/commands/populate_db.py b/scipost/management/commands/populate_db.py new file mode 100644 index 0000000000000000000000000000000000000000..26e2d1a3ac72011e41781b943cedfd7e015b137d --- /dev/null +++ b/scipost/management/commands/populate_db.py @@ -0,0 +1,20 @@ +from django.core.management.base import BaseCommand +from django.contrib.auth.models import User + +from ...models import Contributor + + +class Command(BaseCommand): + def add_arguments(self, parser): + parser.add_argument( + '--username', type=str, required=True, + help='Username of user to use for contributor model') + + def create_contributor(self, username): + user = User.objects.get(username=username) + contributor = Contributor(user=user, status=1, title="MR") + contributor.vetted_by = contributor + contributor.save() + + def handle(self, *args, **options): + self.create_contributor(options['username']) diff --git a/scipost/models.py b/scipost/models.py index e55e21212f59b31cd8e58c8d81ae2679b3549f51..59afc30d53d40172bfd37ea69989e8943afe2cef 100644 --- a/scipost/models.py +++ b/scipost/models.py @@ -225,7 +225,6 @@ class Contributor(models.Model): default=True, verbose_name="I accept to receive SciPost emails") - def __str__(self): return '%s, %s' % (self.user.last_name, self.user.first_name) diff --git a/submissions/forms.py b/submissions/forms.py index 210a0adcb339cca19e8a4aa0c37eeab21a3c6d89..e299e58cd601c661b44c9bcd3adb8148729ed554 100644 --- a/submissions/forms.py +++ b/submissions/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.core.validators import RegexValidator #from django.contrib.auth.models import User, Group from .models import * @@ -20,10 +21,22 @@ class SubmissionSearchForm(forms.Form): ############################### class SubmissionIdentifierForm(forms.Form): - identifier = forms.CharField(widget=forms.TextInput( - {'label': 'arXiv identifier', - 'placeholder': 'new style (with version nr) ####.####(#)v#(#)', - 'cols': 20})) + identifier = forms.CharField( + widget=forms.TextInput( + {'label': 'arXiv identifier', + 'placeholder': 'new style (with version nr) ####.####(#)v#(#)', + 'cols': 20} + ), + validators=[ + RegexValidator( + regex="^[0-9]{4,}.[0-9]{4,5}v[0-9]{1,2}$", + message='The identifier you entered is improperly formatted ' + '(did you forget the version number?)', + code='invalid_identifier' + ), + ]) + + class SubmissionForm(forms.ModelForm): class Meta: diff --git a/submissions/models.py b/submissions/models.py index ae7648b5b84926795a557f723aa99621ec117cf0..68a3d1e867b2720f97fd109dee8cd1d8a18d681a 100644 --- a/submissions/models.py +++ b/submissions/models.py @@ -57,18 +57,6 @@ SUBMISSION_STATUS_PUBLICLY_UNLISTED = [ 'withdrawn', ] -# SUBMISSION_ACTION_REQUIRED = ( -# ('assign_EIC', 'Editor-in-charge to be assigned'), -# # ('Fellow_accepts_or_refuse_assignment', 'Fellow must accept or refuse assignment'), -# ('EIC_runs_refereeing_round', 'Editor-in-charge to run refereeing round (inviting referees)'), -# ('EIC_closes_refereeing_round', 'Editor-in-charge to close refereeing round'), -# ('EIC_invites_author_response', 'Editor-in-charge invites authors to complete their replies'), -# ('EIC_formulates_editorial_recommendation', -# 'Editor-in-charge to formulate editorial recommendation'), -# ('EC_ratification', 'Editorial College ratifies editorial recommendation'), -# ('Decision_to_authors', 'Editor-in-charge forwards decision to authors'), -# ) - SUBMISSION_TYPE = ( ('Letter', 'Letter (broad-interest breakthrough results)'), ('Article', 'Article (in-depth reports on specialized research)'), @@ -78,6 +66,7 @@ submission_type_dict = dict(SUBMISSION_TYPE) class Submission(models.Model): + # Main submission fields is_current = models.BooleanField(default=True) is_resubmission = models.BooleanField(default=False) submitted_by = models.ForeignKey(Contributor, on_delete=models.CASCADE) @@ -89,13 +78,12 @@ class Submission(models.Model): blank=True, null=True, default=None) discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, default='physics') domain = models.CharField(max_length=3, choices=SCIPOST_JOURNALS_DOMAINS) -# specialization = models.CharField(max_length=1, choices=SCIPOST_JOURNALS_SPECIALIZATIONS) subject_area = models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS, verbose_name='Primary subject area', default='Phys:QP') secondary_areas = ChoiceArrayField( models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS), blank=True, null=True) - status = models.CharField(max_length=30, choices=SUBMISSION_STATUS) # set by Editors + status = models.CharField(max_length=30, choices=SUBMISSION_STATUS) # set by Editors author_comments = models.TextField(blank=True, null=True) list_of_changes = models.TextField(blank=True, null=True) remarks_for_editors = models.TextField(blank=True, null=True) @@ -106,17 +94,22 @@ class Submission(models.Model): open_for_commenting = models.BooleanField(default=False) title = models.CharField(max_length=300) author_list = models.CharField(max_length=1000, verbose_name="author list") + # Authors which have been mapped to contributors: - authors = models.ManyToManyField (Contributor, blank=True, related_name='authors_sub') - authors_claims = models.ManyToManyField (Contributor, blank=True, - related_name='authors_sub_claims') - authors_false_claims = models.ManyToManyField (Contributor, blank=True, - related_name='authors_sub_false_claims') + authors = models.ManyToManyField(Contributor, blank=True, related_name='authors_sub') + authors_claims = models.ManyToManyField(Contributor, blank=True, + related_name='authors_sub_claims') + authors_false_claims = models.ManyToManyField(Contributor, blank=True, + related_name='authors_sub_false_claims') abstract = models.TextField() + + # Arxiv identifiers with/without version number arxiv_identifier_w_vn_nr = models.CharField(max_length=15, default='0000.00000v0') arxiv_identifier_wo_vn_nr = models.CharField(max_length=10, default='0000.00000') arxiv_vn_nr = models.PositiveSmallIntegerField(default=1) arxiv_link = models.URLField(verbose_name='arXiv link (including version nr)') + + # Metadata metadata = JSONField(default={}, blank=True, null=True) submission_date = models.DateField(verbose_name='submission date') latest_activity = models.DateTimeField(default=timezone.now) @@ -126,7 +119,7 @@ class Submission(models.Model): ('can_take_editorial_actions', 'Can take editorial actions'), ) - def __str__ (self): + def __str__(self): header = (self.arxiv_identifier_w_vn_nr + ', ' + self.title[:30] + ' by ' + self.author_list[:30]) if self.is_current: diff --git a/submissions/services.py b/submissions/services.py new file mode 100644 index 0000000000000000000000000000000000000000..bd16fb2ae70960507034ca3e46f8b6780a85fe87 --- /dev/null +++ b/submissions/services.py @@ -0,0 +1,86 @@ +# Module for making external api calls as needed in the submissions cycle +import feedparser + +from .models import * + + +class ArxivCaller(): + def lookup_article(identifier): + # Pre-checks + if same_version_exists(identifier) + return False, "This preprint version has already been submitted to SciPost." + + # Split the given identifier in an article identifier and version number + identifier_without_vn_nr = identifier.rpartition('v')[0] + arxiv_vn_nr = int(identifier.rpartition('v')[2]) + + resubmission = False + if previous_submission_undergoing_refereeing(identifier): + errormessage = '<p>There exists a preprint with this arXiv identifier ' + 'but an earlier version number, which is still undergoing ' + 'peer refereeing.</p>' + '<p>A resubmission can only be performed after request ' + 'from the Editor-in-charge. Please wait until the ' + 'closing of the previous refereeing round and ' + 'formulation of the Editorial Recommendation ' + 'before proceeding with a resubmission.</p>' + return False, errormessage + + # Arxiv query + queryurl = ('http://export.arxiv.org/api/query?id_list=%s' + % identifier) + arxiv_response = feedparser.parse(queryurl) + + # Check if response has at least one entry + if not 'entries' in arxiv_response + errormessage = 'Bad response from Arxiv.' + return False, errormessage + + # Check if preprint exists + if not preprint_exists(arxiv_response) + errormessage = 'A preprint associated to this identifier does not exist.' + return False, errormessage + + # Check via journal ref if already published + arxiv_journal_ref = published_journal_ref + if arxiv_journal_ref + errormessage = 'This paper has been published as ' + arxiv_journal_ref + + '. You cannot submit it to SciPost anymore.' + return False, resubmission + + # Check via DOI if already published + arxiv_doi = published_journal_ref + if arxiv_doi + errormessage = 'This paper has been published under DOI ' + arxiv_doi + + '. You cannot submit it to SciPost anymore.' + return False, errormessage + + return arxiv_response, "" + + + def same_version_exists(identifier): + return Submission.objects.filter(arxiv_identifier_w_vn_nr=identifier).exists() + + def previous_submission_undergoing_refereeing(identifier): + previous_submissions = Submission.objects.filter( + arxiv_identifier_wo_vn_nr=identifier).order_by('-arxiv_vn_nr') + + if previous_submissions: + return not previous_submissions[0].status == 'revision_requested' + else: + return False + + def preprint_exists(arxiv_response): + return 'title' in arxiv_response['entries'][0] + + def published_journal_ref(arxiv_response): + if 'arxiv_journal_ref' in arxiv_response['entries'][0] + return arxiv_response['entries'][0]['arxiv_journal_ref'] + else: + return False + + def published_DOI(arxiv_response): + if 'arxiv_doi' in arxiv_response['entries'][0] + return arxiv_response['entries'][0]['arxiv_doi'] + else: + return False diff --git a/submissions/tests.py b/submissions/tests/test_models.py similarity index 55% rename from submissions/tests.py rename to submissions/tests/test_models.py index 7ce503c2dd97ba78597f6ff6e4393132753573f6..2e9cb5f6ba351402af656aec1be5d9ac257bc5c0 100644 --- a/submissions/tests.py +++ b/submissions/tests/test_models.py @@ -1,3 +1 @@ from django.test import TestCase - -# Create your tests here. diff --git a/submissions/tests/test_views.py b/submissions/tests/test_views.py new file mode 100644 index 0000000000000000000000000000000000000000..f35d237bde6def6e0755612febe441d1d10d6d63 --- /dev/null +++ b/submissions/tests/test_views.py @@ -0,0 +1,27 @@ +from django.test import TestCase +from django.test import Client + +from submissions.views import * + + + +class PrefillUsingIdentifierTest(TestCase): + fixtures = ['permissions', 'groups', 'contributors'] + + def test_retrieving_existing_arxiv_paper(self): + client = Client() + client.login(username="Test", password="testpw") + + response = client.post('/submissions/prefill_using_identifier', + {'identifier': '1512.00030v1'}) + + self.assertEqual(response.status_code, 200) + + def test_still_200_ok_if_identifier_is_wrong(self): + client = Client() + client.login(username="Test", password="testpw") + + response = client.post('/submissions/prefill_using_identifier', + {'identifier': '1512.00030'}) + + self.assertEqual(response.status_code, 200) diff --git a/submissions/views.py b/submissions/views.py index d3e7718ce4628b68cd323ac4758a668fecf86c91..f7a4ac054a33887fecb3f5c76662125ea437275f 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -44,25 +44,15 @@ def prefill_using_identifier(request): if request.method == "POST": identifierform = SubmissionIdentifierForm(request.POST) if identifierform.is_valid(): - # we allow 1 or 2 digits for version - identifierpattern = re.compile("^[0-9]{4,}.[0-9]{4,5}v[0-9]{1,2}$") - errormessage = '' - if not identifierpattern.match(identifierform.cleaned_data['identifier']): - errormessage = ('The identifier you entered is improperly formatted ' - '(did you forget the version number?)') - elif (Submission.objects - #.filter(arxiv_link__contains=identifierform.cleaned_data['identifier']) - .filter(arxiv_identifier_w_vn_nr=identifierform.cleaned_data['identifier']) - .exists()): - errormessage = 'This preprint version has already been submitted to SciPost.' - if errormessage != '': + # Perform Arxiv query and check if results are OK for submission + metadata, errormessage = lookup_article(identifierform.cleaned_data['identifier']) + + if not metadata: form = SubmissionForm() return render(request, 'submissions/submit_manuscript.html', {'identifierform': identifierform, 'form': form, 'errormessage': errormessage}) - # Otherwise we query arXiv for the information: - identifier_without_vn_nr = identifierform.cleaned_data['identifier'].rpartition('v')[0] - arxiv_vn_nr = int(identifierform.cleaned_data['identifier'].rpartition('v')[2]) + is_resubmission = False resubmessage = '' previous_submissions = Submission.objects.filter( @@ -95,6 +85,7 @@ def prefill_using_identifier(request): errormessage = 'A preprint associated to this identifier does not exist.' except: pass + # If paper has been published, should comment on published version try: arxiv_journal_ref = arxivquery['entries'][0]['arxiv_journal_ref'] @@ -113,22 +104,7 @@ def prefill_using_identifier(request): context = {'identifierform': identifierform, 'form': form, 'errormessage': errormessage} return render(request, 'submissions/submit_manuscript.html', context) - # otherwise prefill the form: - # metadata = arxivquery - # title = arxivquery['entries'][0]['title'] - # authorlist = arxivquery['entries'][0]['authors'][0]['name'] - # for author in arxivquery['entries'][0]['authors'][1:]: - # authorlist += ', ' + author['name'] - # arxiv_link = arxivquery['entries'][0]['id'] - # abstract = arxivquery['entries'][0]['summary'] - # form = SubmissionForm( - # initial={'is_resubmission': is_resubmission, - # 'metadata': metadata, - # 'title': title, 'author_list': authorlist, - # 'arxiv_identifier_w_vn_nr': identifierform.cleaned_data['identifier'], - # 'arxiv_identifier_wo_vn_nr': identifier_without_vn_nr, - # 'arxiv_vn_nr': arxiv_vn_nr, - # 'arxiv_link': arxiv_link, 'abstract': abstract}) + metadata = arxivquery title = arxivquery['entries'][0]['title'] authorlist = arxivquery['entries'][0]['authors'][0]['name'] @@ -148,7 +124,6 @@ def prefill_using_identifier(request): initialdata['submission_type'] = previous_submissions[0].submission_type initialdata['discipline'] = previous_submissions[0].discipline initialdata['domain'] = previous_submissions[0].domain -# initialdata['specialization'] = previous_submissions[0].specialization initialdata['subject_area'] = previous_submissions[0].subject_area initialdata['secondary_areas'] = previous_submissions[0].secondary_areas initialdata['referees_suggested'] = previous_submissions[0].referees_suggested @@ -165,7 +140,9 @@ def prefill_using_identifier(request): 'errormessage': errormessage,} return render(request, 'submissions/submit_manuscript.html', context) else: - pass + form = SubmissionForm() + return render(request, 'submissions/submit_manuscript.html', + {'identifierform': identifierform, 'form': form}) return redirect(reverse('submissions:submit_manuscript')) @@ -185,7 +162,7 @@ def submit_manuscript(request): return render(request, 'submissions/submit_manuscript.html', {'identifierform': identifierform, 'form': form, 'errormessage': errormessage}) - submission = Submission ( + submission = Submission( is_current = True, is_resubmission = form.cleaned_data['is_resubmission'], submitted_by = submitted_by, diff --git a/theses/factories.py b/theses/factories.py new file mode 100644 index 0000000000000000000000000000000000000000..19489c3c15babf86f0939a1a261c234fb7285560 --- /dev/null +++ b/theses/factories.py @@ -0,0 +1,18 @@ +import factory +from .models import ThesisLink +from scipost.factories import ContributorFactory + + +class ThesisLinkFactory(factory.django.DjangoModelFactory): + class Meta: + model = ThesisLink + + requested_by = factory.SubFactory(ContributorFactory) + type = ThesisLink.MASTER_THESIS + title = factory.Sequence(lambda n: "thesis {0}".format(n)) + pub_link = factory.Faker('uri') + author = factory.Faker('name') + supervisor = factory.Faker('name') + institution = factory.Faker('company') + defense_date = factory.Faker('date_time_this_century') + abstract = factory.Faker('text') diff --git a/theses/forms.py b/theses/forms.py index 95849d3b3d8bd0bb00ca8de031ee2326950a7651..2193a767c32084fbfdd4f5e7b34143408a68e58d 100644 --- a/theses/forms.py +++ b/theses/forms.py @@ -14,6 +14,7 @@ THESIS_REFUSAL_CHOICES = ( (-2, 'the external link to this thesis does not work'), ) + class RequestThesisLinkForm(forms.ModelForm): class Meta: model = ThesisLink @@ -27,6 +28,7 @@ class RequestThesisLinkForm(forms.ModelForm): self.fields['pub_link'].widget.attrs.update({'placeholder': 'Full URL'}) self.fields['abstract'].widget.attrs.update({'cols': 100}) + class VetThesisLinkForm(forms.Form): action_option = forms.ChoiceField(widget=forms.RadioSelect, choices=THESIS_ACTION_CHOICES, @@ -35,6 +37,7 @@ class VetThesisLinkForm(forms.Form): email_response_field = forms.CharField(widget=forms.Textarea( attrs={'rows': 5, 'cols': 40}), label='Justification (optional)', required=False) + class ThesisLinkSearchForm(forms.Form): author = forms.CharField(max_length=100, required=False, label="Author") title_keyword = forms.CharField(max_length=100, label="Title", required=False) diff --git a/theses/models.py b/theses/models.py index 37fcb52f07fd7f09fc90c3a53982bd18c9d162e8..139948b9175deb6bc62fdc098bef6d3fc4a4ef86 100644 --- a/theses/models.py +++ b/theses/models.py @@ -8,15 +8,18 @@ from .models import * from journals.models import * from scipost.models import * -THESIS_TYPES = ( - ('MA', 'Master\'s'), - ('PhD', 'Ph.D.'), - ('Hab', 'Habilitation'), - ) -thesis_type_dict = dict(THESIS_TYPES) - class ThesisLink(models.Model): + MASTER_THESIS = 'MA' + PHD_THESIS = 'PhD' + HABILITATION_THESIS = 'Hab' + THESIS_TYPES = ( + (MASTER_THESIS, 'Master\'s'), + (PHD_THESIS, 'Ph.D.'), + (HABILITATION_THESIS, 'Habilitation'), + ) + THESIS_TYPES_DICT = dict(THESIS_TYPES) + """ An URL pointing to a thesis """ requested_by = models.ForeignKey( Contributor, blank=True, null=True, @@ -26,13 +29,13 @@ class ThesisLink(models.Model): vetted_by = models.ForeignKey( Contributor, blank=True, null=True, on_delete=models.CASCADE) - type = models.CharField(max_length=3, choices=THESIS_TYPES) + type = models.CharField(choices=THESIS_TYPES, max_length=3) discipline = models.CharField( max_length=20, choices=SCIPOST_DISCIPLINES, default='physics') domain = models.CharField( max_length=3, choices=SCIPOST_JOURNALS_DOMAINS, - blank=True) + blank=False) subject_area = models.CharField( max_length=10, choices=SCIPOST_SUBJECT_AREAS, @@ -85,7 +88,7 @@ class ThesisLink(models.Model): header += '<td>(not claimed)</td>' header += ( '</tr>' - '<tr><td>Type: </td><td></td><td>' + thesis_type_dict[self.type] + + '<tr><td>Type: </td><td></td><td>' + self.THESIS_TYPES_DICT[self.type] + '</td></tr>' '<tr><td>Discipline: </td><td></td><td>' + disciplines_dict[self.discipline] + '</td></tr>' @@ -110,11 +113,13 @@ class ThesisLink(models.Model): 'pub_link': self.pub_link, 'institution': self.institution, 'supervisor': self.supervisor, 'defense_date': self.defense_date, 'latest_activity': self.latest_activity.strftime('%Y-%m-%d %H:%M')}) + print(subject_areas_dict) + print(self.subject_area in subject_areas_dict) header = ( '<li><div class="flex-container">' '<div class="flex-whitebox0"><p><a href="/thesis/{{ id }}" ' 'class="pubtitleli">{{ title }}</a></p>' - '<p>' + thesis_type_dict[self.type] + ' thesis by {{ author }} ' + '<p>' + self.THESIS_TYPES_DICT[self.type] + ' thesis by {{ author }} ' '(supervisor(s): {{ supervisor }}) in ' + disciplines_dict[self.discipline] + ', ' + journals_domains_dict[self.domain] + ' ' + @@ -133,7 +138,7 @@ class ThesisLink(models.Model): '<li><div class="flex-container">' '<div class="flex-whitebox0"><p><a href="/thesis/{{ id }}" ' 'class="pubtitleli">{{ title }}</a></p>' - '<p>' + thesis_type_dict[self.type] + + '<p>' + self.THESIS_TYPES_DICT[self.type] + ' thesis by {{ author }} </div></div></li>') template = Template(header) return template.render(context) diff --git a/theses/tests.py b/theses/test_forms.py similarity index 55% rename from theses/tests.py rename to theses/test_forms.py index 7ce503c2dd97ba78597f6ff6e4393132753573f6..2e9cb5f6ba351402af656aec1be5d9ac257bc5c0 100644 --- a/theses/tests.py +++ b/theses/test_forms.py @@ -1,3 +1 @@ from django.test import TestCase - -# Create your tests here. diff --git a/theses/test_models.py b/theses/test_models.py new file mode 100644 index 0000000000000000000000000000000000000000..4308ab25f1d22c56e33cb5047cb6f571fe080bf7 --- /dev/null +++ b/theses/test_models.py @@ -0,0 +1,15 @@ +import re + +from django.test import TestCase +from django.core.exceptions import ValidationError + +from .models import ThesisLink +from .factories import ThesisLinkFactory + + +class ThesisLinkTestCase(TestCase): + def test_domain_cannot_be_blank(self): + thesis_link = ThesisLinkFactory() + thesis_link.domain = "" + self.assertRaisesRegexp(ValidationError, re.compile(r'domain'), + thesis_link.full_clean) diff --git a/theses/test_views.py b/theses/test_views.py new file mode 100644 index 0000000000000000000000000000000000000000..e1c9c7982e1cd36b83d213a82f3794ec860a05f5 --- /dev/null +++ b/theses/test_views.py @@ -0,0 +1,10 @@ +from django.test import TestCase +from django.test.client import Client + + +class TestThesisDetail(TestCase): + + def test_acknowledges_after_submitting_comment(self): + client = Client() + response = client.post('/theses/1') + self.assertEqual(response.get('location'), 'bladiebla') diff --git a/theses/urls.py b/theses/urls.py index 59758c05e57add2fa5e09e8fdcca125810b36e36..88bd434545a5636f9b09e832bbd7e24b91bf6d12 100644 --- a/theses/urls.py +++ b/theses/urls.py @@ -7,9 +7,10 @@ urlpatterns = [ # Thesis Links url(r'^$', views.theses, name='theses'), url(r'^browse/(?P<discipline>[a-z]+)/(?P<nrweeksback>[0-9]+)/$', views.browse, name='browse'), - #url(r'^thesis/(?P<thesislink_id>[0-9]+)/$', views.thesis_detail, name='thesis'), url(r'^(?P<thesislink_id>[0-9]+)/$', views.thesis_detail, name='thesis'), url(r'^request_thesislink$', views.request_thesislink, name='request_thesislink'), - url(r'^vet_thesislink_requests$', views.vet_thesislink_requests, name='vet_thesislink_requests'), - url(r'^vet_thesislink_request_ack/(?P<thesislink_id>[0-9]+)$', views.vet_thesislink_request_ack, name='vet_thesislink_request_ack'), + url(r'^vet_thesislink_requests$', views.vet_thesislink_requests, + name='vet_thesislink_requests'), + url(r'^vet_thesislink_request_ack/(?P<thesislink_id>[0-9]+)$', + views.vet_thesislink_request_ack, name='vet_thesislink_request_ack'), ] diff --git a/theses/views.py b/theses/views.py index 882c2f192f88353debfe4235bbcf6154ab763b27..7a2a2309d363eae341a18e0a62c4470c6f5b1cdf 100644 --- a/theses/views.py +++ b/theses/views.py @@ -17,7 +17,8 @@ from comments.models import Comment from comments.forms import CommentForm from scipost.forms import TITLE_CHOICES, AuthenticationForm -title_dict = dict(TITLE_CHOICES) # Convert titles for use in emails + +title_dict = dict(TITLE_CHOICES) # Convert titles for use in emails ################ # Theses @@ -30,23 +31,23 @@ def request_thesislink(request): form = RequestThesisLinkForm(request.POST) if form.is_valid(): contributor = Contributor.objects.get(user=request.user) - thesislink = ThesisLink ( - requested_by = contributor, - type = form.cleaned_data['type'], - discipline = form.cleaned_data['discipline'], - domain = form.cleaned_data['domain'], - subject_area = form.cleaned_data['subject_area'], - title = form.cleaned_data['title'], - author = form.cleaned_data['author'], - supervisor = form.cleaned_data['supervisor'], - institution = form.cleaned_data['institution'], - defense_date = form.cleaned_data['defense_date'], - pub_link = form.cleaned_data['pub_link'], - abstract = form.cleaned_data['abstract'], - latest_activity = timezone.now(), - ) + thesislink = ThesisLink( + requested_by=contributor, + type=form.cleaned_data['type'], + discipline=form.cleaned_data['discipline'], + domain=form.cleaned_data['domain'], + subject_area=form.cleaned_data['subject_area'], + title=form.cleaned_data['title'], + author=form.cleaned_data['author'], + supervisor=form.cleaned_data['supervisor'], + institution=form.cleaned_data['institution'], + defense_date=form.cleaned_data['defense_date'], + pub_link=form.cleaned_data['pub_link'], + abstract=form.cleaned_data['abstract'], + latest_activity=timezone.now(), + ) thesislink.save() - #return HttpResponseRedirect('request_thesislink_ack') + # return HttpResponseRedirect('request_thesislink_ack') context = {'ack_header': 'Thank you for your request for a Thesis Link', 'ack_message': 'Your request will soon be handled by an Editor. ', 'followup_message': 'Return to your ', @@ -61,11 +62,13 @@ def request_thesislink(request): @permission_required('scipost.can_vet_thesislink_requests', raise_exception=True) def vet_thesislink_requests(request): contributor = Contributor.objects.get(user=request.user) - thesislink_to_vet = ThesisLink.objects.filter(vetted=False).first() # only handle one at a time + thesislink_to_vet = ThesisLink.objects.filter( + vetted=False).first() # only handle one at a time form = VetThesisLinkForm() - context = {'contributor': contributor, 'thesislink_to_vet': thesislink_to_vet, 'form': form } + context = {'contributor': contributor, 'thesislink_to_vet': thesislink_to_vet, 'form': form} return render(request, 'theses/vet_thesislink_requests.html', context) + @permission_required('scipost.can_vet_thesislink_requests', raise_exception=True) def vet_thesislink_request_ack(request, thesislink_id): if request.method == 'POST': @@ -112,8 +115,8 @@ def vet_thesislink_request_ack(request, thesislink_id): ['theses@scipost.org'], reply_to=['theses@scipost.org']) # Don't send email yet... only when option 1 has succeeded! - #emailmessage.send(fail_silently=False) - context = {'form': form2 } + # emailmessage.send(fail_silently=False) + context = {'form': form2} return render(request, 'theses/request_thesislink.html', context) elif form.cleaned_data['action_option'] == '2': email_text = ('Dear ' + title_dict[thesislink.requested_by.title] + ' ' @@ -124,7 +127,8 @@ def vet_thesislink_request_ack(request, thesislink_id): + form.cleaned_data['refusal_reason'] + '.\n\nThank you for your interest, \nThe SciPost Team.') if form.cleaned_data['email_response_field']: - email_text += '\n\nFurther explanations: ' + form.cleaned_data['email_response_field'] + email_text += '\n\nFurther explanations: ' + \ + form.cleaned_data['email_response_field'] emailmessage = EmailMessage('SciPost Thesis Link', email_text, 'SciPost Theses <theses@scipost.org>', [thesislink.requested_by.user.email], @@ -133,8 +137,6 @@ def vet_thesislink_request_ack(request, thesislink_id): emailmessage.send(fail_silently=False) thesislink.delete() - #context = {'thesislink_id': thesislink_id } - #return render(request, 'theses/vet_thesislink_request_ack.html', context) context = {'ack_header': 'Thesis Link request vetted.', 'followup_message': 'Return to the ', 'followup_link': reverse('theses:vet_thesislink_requests'), @@ -152,7 +154,7 @@ def theses(request): abstract__icontains=form.cleaned_data['abstract_keyword'], supervisor__icontains=form.cleaned_data['supervisor'], vetted=True, - ) + ) thesislink_search_list.order_by('-pub_date') else: thesislink_search_list = [] @@ -165,7 +167,7 @@ def theses(request): .filter(vetted=True, latest_activity__gte=timezone.now() + datetime.timedelta(days=-7))) context = {'form': form, 'thesislink_search_list': thesislink_search_list, - 'thesislink_recent_list': thesislink_recent_list } + 'thesislink_recent_list': thesislink_recent_list} return render(request, 'theses/theses.html', context) @@ -179,11 +181,11 @@ def browse(request, discipline, nrweeksback): abstract__icontains=form.cleaned_data['abstract_keyword'], supervisor__icontains=form.cleaned_data['supervisor'], vetted=True, - ) + ) thesislink_search_list.order_by('-pub_date') else: thesislink_search_list = [] - context = {'form': form, 'thesislink_search_list': thesislink_search_list } + context = {'form': form, 'thesislink_search_list': thesislink_search_list} return HttpResponseRedirect(request, 'theses/theses.html', context) else: form = ThesisLinkSearchForm() @@ -192,7 +194,7 @@ def browse(request, discipline, nrweeksback): latest_activity__gte=timezone.now() + datetime.timedelta(weeks=-int(nrweeksback)))) context = {'form': form, 'discipline': discipline, 'nrweeksback': nrweeksback, - 'thesislink_browse_list': thesislink_browse_list } + 'thesislink_browse_list': thesislink_browse_list} return render(request, 'theses/theses.html', context) @@ -203,26 +205,27 @@ def thesis_detail(request, thesislink_id): form = CommentForm(request.POST) if form.is_valid(): author = Contributor.objects.get(user=request.user) - newcomment = Comment ( - thesislink = thesislink, - author = author, - is_rem = form.cleaned_data['is_rem'], - is_que = form.cleaned_data['is_que'], - is_ans = form.cleaned_data['is_ans'], - is_obj = form.cleaned_data['is_obj'], - is_rep = form.cleaned_data['is_rep'], - is_val = form.cleaned_data['is_val'], - is_lit = form.cleaned_data['is_lit'], - is_sug = form.cleaned_data['is_sug'], - comment_text = form.cleaned_data['comment_text'], - remarks_for_editors = form.cleaned_data['remarks_for_editors'], - date_submitted = timezone.now(), - ) - newcomment.save() + new_comment = Comment( + thesislink=thesislink, + author=author, + is_rem=form.cleaned_data['is_rem'], + is_que=form.cleaned_data['is_que'], + is_ans=form.cleaned_data['is_ans'], + is_obj=form.cleaned_data['is_obj'], + is_rep=form.cleaned_data['is_rep'], + is_val=form.cleaned_data['is_val'], + is_lit=form.cleaned_data['is_lit'], + is_sug=form.cleaned_data['is_sug'], + comment_text=form.cleaned_data['comment_text'], + remarks_for_editors=form.cleaned_data['remarks_for_editors'], + date_submitted=timezone.now(), + ) + new_comment.save() author.nr_comments = Comment.objects.filter(author=author).count() author.save() request.session['thesislink_id'] = thesislink_id - return HttpResponseRedirect(reverse('comments:comment_submission_ack')) + context = {} + return render(request, 'scipost/acknowledgement.html', context) else: form = CommentForm()