From 5d632f5d9ec9a371343e474f4759079fb99fead5 Mon Sep 17 00:00:00 2001 From: Vitor Freitas Date: Sat, 28 Aug 2021 18:37:53 -0300 Subject: [PATCH 01/39] Upgrade python and django code --- Makefile | 5 + manage.py | 20 +- parsifal/account_settings/__init__.py | 1 + parsifal/account_settings/apps.py | 7 + parsifal/account_settings/forms.py | 61 +- parsifal/account_settings/models.py | 3 - parsifal/account_settings/urls.py | 39 +- parsifal/account_settings/views.py | 182 +--- parsifal/activities/__init__.py | 1 + parsifal/activities/apps.py | 7 + parsifal/activities/models.py | 29 +- parsifal/activities/urls.py | 15 +- parsifal/activities/views.py | 47 +- parsifal/authentication/__init__.py | 1 + parsifal/authentication/apps.py | 7 + parsifal/authentication/forms.py | 125 ++- parsifal/authentication/models.py | 86 +- parsifal/authentication/views.py | 95 +- parsifal/blog/__init__.py | 1 + parsifal/blog/admin.py | 17 +- parsifal/blog/apps.py | 7 + parsifal/blog/models.py | 27 +- parsifal/blog/urls.py | 12 +- parsifal/blog/views.py | 14 +- parsifal/core/__init__.py | 1 + parsifal/core/admin.py | 13 +- parsifal/core/apps.py | 7 + parsifal/core/models.py | 53 +- parsifal/core/tests/test_views_home.py | 16 +- parsifal/core/views.py | 56 +- parsifal/help/__init__.py | 1 + parsifal/help/admin.py | 42 +- parsifal/help/apps.py | 7 + parsifal/help/models.py | 28 +- parsifal/help/urls.py | 14 +- parsifal/help/views.py | 33 +- parsifal/library/__init__.py | 1 + parsifal/library/apps.py | 7 + parsifal/library/forms.py | 201 +++- parsifal/library/models.py | 170 ++-- parsifal/library/urls.py | 36 +- parsifal/library/views.py | 336 ++++--- parsifal/reviews/__init__.py | 1 + parsifal/reviews/admin.py | 36 +- parsifal/reviews/apps.py | 7 + parsifal/reviews/conducting/__init__.py | 1 + parsifal/reviews/conducting/apps.py | 7 + parsifal/reviews/conducting/models.py | 3 - .../tests/test_views_import_bibtex.py | 16 +- parsifal/reviews/conducting/urls.py | 82 +- parsifal/reviews/conducting/views.py | 921 +++++++++++------- parsifal/reviews/decorators.py | 34 +- parsifal/reviews/forms.py | 38 +- parsifal/reviews/models.py | 445 ++++----- parsifal/reviews/planning/__init__.py | 1 + parsifal/reviews/planning/apps.py | 7 + parsifal/reviews/planning/forms.py | 31 +- parsifal/reviews/planning/models.py | 3 - parsifal/reviews/planning/urls.py | 113 ++- parsifal/reviews/planning/views.py | 530 +++++----- parsifal/reviews/reporting/__init__.py | 1 + parsifal/reviews/reporting/apps.py | 7 + parsifal/reviews/reporting/export.py | 175 ++-- parsifal/reviews/reporting/models.py | 3 - .../reporting/tests/test_views_export.py | 14 +- parsifal/reviews/reporting/urls.py | 11 +- parsifal/reviews/reporting/views.py | 30 +- parsifal/reviews/settings/__init__.py | 1 + parsifal/reviews/settings/apps.py | 7 + parsifal/reviews/settings/forms.py | 15 +- parsifal/reviews/settings/models.py | 3 - parsifal/reviews/settings/views.py | 55 +- parsifal/reviews/urls.py | 25 +- parsifal/reviews/views.py | 124 +-- parsifal/search/admin.py | 3 - parsifal/search/models.py | 3 - parsifal/search/tests.py | 3 - parsifal/search/views.py | 3 - parsifal/settings.py | 124 --- parsifal/{search => settings}/__init__.py | 0 parsifal/settings/base.py | 177 ++++ parsifal/settings/local.py | 18 + parsifal/settings/production.py | 33 + parsifal/settings/tests.py | 16 + parsifal/test_settings.py | 15 - parsifal/urls.py | 139 ++- parsifal/utils/elsevier/client.py | 28 +- parsifal/utils/elsevier/exceptions.py | 14 +- parsifal/wsgi.py | 3 +- pyproject.toml | 24 + requirements.txt | 15 - requirements/base.txt | 12 + .../__init__.py => requirements/local.txt | 0 requirements/production.txt | 3 + requirements/tests.txt | 8 + run_tests.sh | 2 - setup.cfg | 21 + 97 files changed, 2955 insertions(+), 2287 deletions(-) create mode 100644 Makefile create mode 100644 parsifal/account_settings/apps.py create mode 100644 parsifal/activities/apps.py create mode 100644 parsifal/authentication/apps.py create mode 100644 parsifal/blog/apps.py create mode 100644 parsifal/core/apps.py create mode 100644 parsifal/help/apps.py create mode 100644 parsifal/library/apps.py create mode 100644 parsifal/reviews/apps.py create mode 100644 parsifal/reviews/conducting/apps.py create mode 100644 parsifal/reviews/planning/apps.py create mode 100644 parsifal/reviews/reporting/apps.py create mode 100644 parsifal/reviews/settings/apps.py delete mode 100644 parsifal/search/admin.py delete mode 100644 parsifal/search/models.py delete mode 100644 parsifal/search/tests.py delete mode 100644 parsifal/search/views.py delete mode 100644 parsifal/settings.py rename parsifal/{search => settings}/__init__.py (100%) create mode 100644 parsifal/settings/base.py create mode 100644 parsifal/settings/local.py create mode 100644 parsifal/settings/production.py create mode 100644 parsifal/settings/tests.py delete mode 100644 parsifal/test_settings.py create mode 100644 pyproject.toml delete mode 100644 requirements.txt create mode 100644 requirements/base.txt rename parsifal/search/migrations/__init__.py => requirements/local.txt (100%) create mode 100644 requirements/production.txt create mode 100644 requirements/tests.txt delete mode 100755 run_tests.sh create mode 100644 setup.cfg diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..1385e393 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +build: + isort parsifal + black parsifal + flake8 parsifal + ./manage.py makemigrations --check --dry-run --settings=parsifal.test_settings diff --git a/manage.py b/manage.py index 91bd9f5b..0c43efff 100644 --- a/manage.py +++ b/manage.py @@ -1,10 +1,22 @@ #!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" import os import sys -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "parsifal.settings") - - from django.core.management import execute_from_command_line +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'parsifal.settings.local') + 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) + + +if __name__ == '__main__': + main() diff --git a/parsifal/account_settings/__init__.py b/parsifal/account_settings/__init__.py index e69de29b..8fa45492 100644 --- a/parsifal/account_settings/__init__.py +++ b/parsifal/account_settings/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.account_settings.apps.AccountSettingsConfig" diff --git a/parsifal/account_settings/apps.py b/parsifal/account_settings/apps.py new file mode 100644 index 00000000..0aa4205a --- /dev/null +++ b/parsifal/account_settings/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class AccountSettingsConfig(AppConfig): + name = "parsifal.account_settings" + verbose_name = _("Account Settings") diff --git a/parsifal/account_settings/forms.py b/parsifal/account_settings/forms.py index a2bb8120..82e0c0db 100644 --- a/parsifal/account_settings/forms.py +++ b/parsifal/account_settings/forms.py @@ -1,48 +1,69 @@ from django import forms -from django.contrib.auth.models import User from django.contrib.auth.forms import PasswordChangeForm +from django.contrib.auth.models import User +from django.utils.translation import gettext_lazy as _ from parsifal.authentication.models import Profile + class UserEmailForm(forms.ModelForm): - email = forms.CharField(widget=forms.EmailInput(attrs={ 'class': 'form-control' }), - max_length=254, - help_text='This email account will not be publicly available. It is used for your Parsifal account management, such as internal notifications and password reset.') + email = forms.CharField( + widget=forms.EmailInput(attrs={"class": "form-control"}), + max_length=254, + help_text=_( + "This email account will not be publicly available. " + "It is used for your Parsifal account management, " + "such as internal notifications and password reset." + ), + ) class Meta: model = User - fields = ['email',] + fields = ("email",) class ProfileForm(forms.ModelForm): - first_name = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=30, required=False) - last_name = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=30, required=False) - public_email = forms.CharField(widget=forms.EmailInput(attrs={ 'class': 'form-control' }), max_length=254, required=False) - url = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=50, required=False) - institution = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=50, required=False) - location = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=50, required=False) + first_name = forms.CharField( + widget=forms.TextInput(attrs={"class": "form-control"}), max_length=30, required=False + ) + last_name = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}), max_length=30, required=False) + public_email = forms.CharField( + widget=forms.EmailInput(attrs={"class": "form-control"}), max_length=254, required=False + ) + url = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}), max_length=50, required=False) + institution = forms.CharField( + widget=forms.TextInput(attrs={"class": "form-control"}), max_length=50, required=False + ) + location = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}), max_length=50, required=False) def __init__(self, *args, **kwargs): super(ProfileForm, self).__init__(*args, **kwargs) try: - self.fields['first_name'].initial = self.instance.user.first_name - self.fields['last_name'].initial = self.instance.user.last_name + self.fields["first_name"].initial = self.instance.user.first_name + self.fields["last_name"].initial = self.instance.user.last_name except User.DoesNotExist: pass class Meta: model = Profile - fields = ['first_name', 'last_name', 'public_email', 'url', 'institution', 'location'] + fields = ("first_name", "last_name", "public_email", "url", "institution", "location") def save(self, *args, **kwargs): u = self.instance.user - u.first_name = self.cleaned_data['first_name'] - u.last_name = self.cleaned_data['last_name'] + u.first_name = self.cleaned_data["first_name"] + u.last_name = self.cleaned_data["last_name"] u.save() - profile = super(ProfileForm, self).save(*args,**kwargs) + profile = super().save(*args, **kwargs) return profile + class PasswordForm(PasswordChangeForm): - old_password = forms.CharField(label='Old password', widget=forms.PasswordInput(attrs={ 'class': 'form-control' })) - new_password1 = forms.CharField(label='New password', widget=forms.PasswordInput(attrs={ 'class': 'form-control' })) - new_password2 = forms.CharField(label='Confirm password', widget=forms.PasswordInput(attrs={ 'class': 'form-control' })) + old_password = forms.CharField( + label=_("Old password"), widget=forms.PasswordInput(attrs={"class": "form-control"}) + ) + new_password1 = forms.CharField( + label=_("New password"), widget=forms.PasswordInput(attrs={"class": "form-control"}) + ) + new_password2 = forms.CharField( + label=_("Confirm password"), widget=forms.PasswordInput(attrs={"class": "form-control"}) + ) diff --git a/parsifal/account_settings/models.py b/parsifal/account_settings/models.py index 71a83623..e69de29b 100644 --- a/parsifal/account_settings/models.py +++ b/parsifal/account_settings/models.py @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/parsifal/account_settings/urls.py b/parsifal/account_settings/urls.py index 6594778b..8541c0e3 100644 --- a/parsifal/account_settings/urls.py +++ b/parsifal/account_settings/urls.py @@ -1,23 +1,20 @@ -# coding: utf-8 -from django.conf.urls import patterns, include, url +from django.urls import path -urlpatterns = patterns('parsifal.account_settings.views', - url(r'^$', 'settings', name='settings'), - url(r'^profile/$', 'profile', name='profile'), - url(r'^emails/$', 'emails', name='emails'), - url(r'^picture/$', 'picture', name='picture'), - url(r'^password/$', 'password', name='password'), +from parsifal.account_settings import views - url(r'^connections/$', 'connections', name='connections'), - - url(r'^mendeley_connection/$', 'mendeley_connection', name='mendeley_connection'), - url(r'^connect_mendeley/$', 'connect_mendeley', name='connect_mendeley'), - url(r'^disconnect_mendeley/$', 'disconnect_mendeley', name='disconnect_mendeley'), - - url(r'^dropbox_connection/$', 'dropbox_connection', name='dropbox_connection'), - url(r'^connect_dropbox/$', 'connect_dropbox', name='connect_dropbox'), - url(r'^disconnect_dropbox/$', 'disconnect_dropbox', name='disconnect_dropbox'), - - url(r'^upload_picture/$', 'upload_picture', name='upload_picture'), - url(r'^save_uploaded_picture/$', 'save_uploaded_picture', name='save_uploaded_picture'), -) \ No newline at end of file +urlpatterns = [ + path("", views.settings, name="settings"), + path("profile/", views.profile, name="profile"), + path("emails/", views.emails, name="emails"), + path("picture/", views.picture, name="picture"), + path("password/", views.password, name="password"), + path("connections/", views.connections, name="connections"), + path("mendeley_connection/", views.mendeley_connection, name="mendeley_connection"), + path("connect_mendeley/", views.connect_mendeley, name="connect_mendeley"), + path("disconnect_mendeley/", views.disconnect_mendeley, name="disconnect_mendeley"), + path("dropbox_connection/", views.dropbox_connection, name="dropbox_connection"), + path("connect_dropbox/", views.connect_dropbox, name="connect_dropbox"), + path("disconnect_dropbox/", views.disconnect_dropbox, name="disconnect_dropbox"), + path("upload_picture/", views.upload_picture, name="upload_picture"), + path("save_uploaded_picture/", views.save_uploaded_picture, name="save_uploaded_picture"), +] diff --git a/parsifal/account_settings/views.py b/parsifal/account_settings/views.py index 6fed6c40..cc1c6a44 100644 --- a/parsifal/account_settings/views.py +++ b/parsifal/account_settings/views.py @@ -1,87 +1,85 @@ -# coding: utf-8 - import os -from PIL import Image -from mendeley.session import MendeleySession -from oauthlib.oauth2 import TokenExpiredError -from dropbox.client import DropboxClient, DropboxOAuth2Flow -from django.core.urlresolvers import reverse as r -from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden -from django.contrib import messages -from django.template import RequestContext -from django.contrib.auth.models import User -from django.contrib.auth.decorators import login_required -from django.shortcuts import redirect, get_object_or_404, render from django.conf import settings as django_settings +from django.contrib import messages from django.contrib.auth import update_session_auth_hash +from django.contrib.auth.decorators import login_required +from django.http import HttpResponse, HttpResponseBadRequest +from django.shortcuts import redirect, render +from django.urls import reverse as r + +from PIL import Image -from parsifal.account_settings.forms import UserEmailForm, ProfileForm, PasswordForm +from parsifal.account_settings.forms import PasswordForm, ProfileForm, UserEmailForm @login_required def settings(request): - return redirect('/settings/profile/') + return redirect("/settings/profile/") + @login_required def profile(request): - if request.method == 'POST': + if request.method == "POST": form = ProfileForm(request.POST, instance=request.user.profile) if form.is_valid(): form.save() - messages.success(request, u'Your profile were successfully edited.') - return redirect(r('settings:profile')) + messages.success(request, "Your profile were successfully edited.") + return redirect(r("settings:profile")) else: form = ProfileForm(instance=request.user.profile) - return render(request, 'settings/profile.html', { 'form': form }) + return render(request, "settings/profile.html", {"form": form}) + @login_required def picture(request): uploaded_picture = False try: - if request.GET['upload_picture'] == 'uploaded': + if request.GET["upload_picture"] == "uploaded": uploaded_picture = True - except Exception, e: + except Exception: uploaded_picture = False - return render(request, 'settings/picture.html', { 'uploaded_picture': uploaded_picture }) + return render(request, "settings/picture.html", {"uploaded_picture": uploaded_picture}) + @login_required def emails(request): - if request.method == 'POST': + if request.method == "POST": form = UserEmailForm(request.POST, instance=request.user) if form.is_valid(): form.save() - messages.success(request, u'Account Email changed successfully.') + messages.success(request, "Account Email changed successfully.") else: - messages.error(request, u'Please correct the error below.') + messages.error(request, "Please correct the error below.") else: form = UserEmailForm(instance=request.user) - return render(request, 'settings/emails.html', { 'form' : form }) + return render(request, "settings/emails.html", {"form": form}) + @login_required def password(request): - if request.method == 'POST': + if request.method == "POST": form = PasswordForm(request.user, request.POST) if form.is_valid(): form.save() - messages.success(request, u'Password changed successfully.') + messages.success(request, "Password changed successfully.") update_session_auth_hash(request, form.user) else: - messages.error(request, u'Please correct the error below.') + messages.error(request, "Please correct the error below.") else: form = PasswordForm(request.user) - return render(request, 'settings/password.html', { 'form' : form }) + return render(request, "settings/password.html", {"form": form}) @login_required def upload_picture(request): try: - f = request.FILES['picture'] + f = request.FILES["picture"] ext = os.path.splitext(f.name)[1].lower() - valid_extensions = ['.gif', '.png', '.jpg', '.jpeg', '.bmp'] + valid_extensions = [".gif", ".png", ".jpg", ".jpeg", ".bmp"] if ext in valid_extensions: - filename = django_settings.MEDIA_ROOT + '/profile_pictures/' + request.user.username + '_tmp.jpg' - with open(filename, 'wb+') as destination: + filename = django_settings.MEDIA_ROOT + "/profile_pictures/" + request.user.username + "_tmp.jpg" + with open(filename, "wb+") as destination: for chunk in f.chunks(): destination.write(chunk) im = Image.open(filename) @@ -92,119 +90,33 @@ def upload_picture(request): new_size = new_width, new_height im.thumbnail(new_size, Image.ANTIALIAS) im.save(filename) - return redirect('/settings/picture/?upload_picture=uploaded') + return redirect("/settings/picture/?upload_picture=uploaded") else: - messages.error(request, u'Invalid file format.') - except Exception, e: - messages.error(request, u'An expected error occurred.') - return redirect('/settings/picture/') + messages.error(request, "Invalid file format.") + except Exception: + messages.error(request, "An expected error occurred.") + return redirect("/settings/picture/") @login_required def save_uploaded_picture(request): try: - x = int(request.POST['x']) - y = int(request.POST['y']) - w = int(request.POST['w']) - h = int(request.POST['h']) - tmp_filename = django_settings.MEDIA_ROOT + '/profile_pictures/' + request.user.username + '_tmp.jpg' - filename = django_settings.MEDIA_ROOT + '/profile_pictures/' + request.user.username + '.jpg' + x = int(request.POST["x"]) + y = int(request.POST["y"]) + w = int(request.POST["w"]) + h = int(request.POST["h"]) + tmp_filename = django_settings.MEDIA_ROOT + "/profile_pictures/" + request.user.username + "_tmp.jpg" + filename = django_settings.MEDIA_ROOT + "/profile_pictures/" + request.user.username + ".jpg" im = Image.open(tmp_filename) - cropped_im = im.crop((x, y, w+x, h+y)) + cropped_im = im.crop((x, y, w + x, h + y)) cropped_im.thumbnail((200, 200), Image.ANTIALIAS) cropped_im.save(filename) os.remove(tmp_filename) - return HttpResponse(django_settings.MEDIA_URL + 'profile_pictures/' + request.user.username + '.jpg') - except Exception, e: + return HttpResponse(django_settings.MEDIA_URL + "profile_pictures/" + request.user.username + ".jpg") + except Exception: return HttpResponseBadRequest() -def get_dropbox_auth_flow(session): - return DropboxOAuth2Flow( - django_settings.DROPBOX_APP_KEY, - django_settings.DROPBOX_SECRET, - django_settings.DROPBOX_REDIRECT_URI, - session, - 'dropbox-auth-csrf-token') @login_required def connections(request): - return render(request, 'settings/connections.html') - -@login_required -def mendeley_connection(request): - mendeley_profile = request.user.profile.get_mendeley_profile() - mendeley_profile_picture = None - if not mendeley_profile: - mendeley = django_settings.MENDELEY - auth = mendeley.start_authorization_code_flow() - mendeley_auth_url = auth.get_login_url() - else: - mendeley_profile_picture = mendeley_profile.photo.square - mendeley_profile_picture = mendeley_profile_picture.replace('http://', 'https://') - mendeley_auth_url = '' - return render(request, 'settings/includes/mendeley_connection.html', { - 'mendeley_auth_url': mendeley_auth_url, - 'mendeley_profile': mendeley_profile, - 'mendeley_profile_picture': mendeley_profile_picture - }) - -@login_required -def dropbox_connection(request): - dropbox_profile = request.user.profile.get_dropbox_profile() - if not dropbox_profile: - dropbox_auth_url = get_dropbox_auth_flow(request.session).start() - else: - dropbox_auth_url = '' - return render(request, 'settings/includes/dropbox_connection.html', { - 'dropbox_auth_url': dropbox_auth_url, - 'dropbox_profile': dropbox_profile - }) - -@login_required -def connect_mendeley(request): - if 'code' in request.GET and 'state' in request.GET: - code = request.GET.get('code') - state = request.GET.get('state') - mendeley = django_settings.MENDELEY - auth = mendeley.start_authorization_code_flow(state=state) - auth_path = u'{0}?code={1}&state={2}'.format(django_settings.MENDELEY_REDIRECT_URI, code, state) - mendeley_session = auth.authenticate(auth_path) - request.user.profile.set_mendeley_token(mendeley_session.token) - request.user.save() - messages.success(request, 'Your Mendeley account were linked successfully!') - else: - messages.error(request, 'A problem occurred while trying to connect your Mendeley account.') - return redirect(r('settings:connections')) - -@login_required -def disconnect_mendeley(request): - if request.method == 'POST': - request.user.profile.mendeley_token = None - request.user.save() - messages.success(request, 'Your Mendeley account were disconnected successfully!') - return redirect(r('settings:connections')) - -@login_required -def connect_dropbox(request): - try: - access_token, user_id, url_state = get_dropbox_auth_flow(request.session).finish(request.GET) - messages.success(request, u'Your Dropbox account were linked successfully!') - request.user.profile.dropbox_token = access_token - request.user.save() - except DropboxOAuth2Flow.NotApprovedException, e: - messages.warning(request, 'You didn\'t approved Parsifal to connect your Dropbox account.') - except Exception, e: - messages.error(request, 'A problem occurred while trying to connect your Dropbox account. Please try again later.') - return redirect(r('settings:connections')) - -@login_required -def disconnect_dropbox(request): - if request.method == 'POST': - dropbox_token = request.user.profile.dropbox_token - if dropbox_token is not None: - client = DropboxClient(dropbox_token) - client.disable_access_token() - request.user.profile.dropbox_token = None - request.user.save() - messages.success(request, 'Your Dropbox account were disconnected successfully!') - return redirect(r('settings:connections')) + return render(request, "settings/connections.html") diff --git a/parsifal/activities/__init__.py b/parsifal/activities/__init__.py index e69de29b..85dacd56 100644 --- a/parsifal/activities/__init__.py +++ b/parsifal/activities/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.activities.apps.ActivitiesConfig" diff --git a/parsifal/activities/apps.py b/parsifal/activities/apps.py new file mode 100644 index 00000000..74c2f92b --- /dev/null +++ b/parsifal/activities/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class ActivitiesConfig(AppConfig): + name = "parsifal.activities" + verbose_name = _("Activities") diff --git a/parsifal/activities/models.py b/parsifal/activities/models.py index 17da2c30..c432522e 100644 --- a/parsifal/activities/models.py +++ b/parsifal/activities/models.py @@ -1,29 +1,30 @@ -from django.db import models from django.contrib.auth.models import User +from django.db import models +from django.utils.translation import gettext_lazy as _ from parsifal.reviews.models import Review class Activity(models.Model): - FOLLOW = 'F' - COMMENT = 'C' - STAR = 'S' + FOLLOW = "F" + COMMENT = "C" + STAR = "S" ACTIVITY_TYPES = ( - (FOLLOW, 'Follow'), - (COMMENT, 'Comment'), - (STAR, 'Star'), + (FOLLOW, _("Follow")), + (COMMENT, _("Comment")), + (STAR, _("Star")), ) - from_user = models.ForeignKey(User) - to_user = models.ForeignKey(User, related_name="+", null=True) + from_user = models.ForeignKey(User, on_delete=models.CASCADE) + to_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="+", null=True) activity_type = models.CharField(max_length=1, choices=ACTIVITY_TYPES) content = models.CharField(max_length=500, blank=True) - review = models.ForeignKey(Review, null=True) + review = models.ForeignKey(Review, on_delete=models.CASCADE, null=True) date = models.DateTimeField(auto_now_add=True) class Meta: - verbose_name = "Activity" - verbose_name_plural = "Activities" + verbose_name = _("activity") + verbose_name_plural = _("activities") - def __unicode__(self): - return self.activity_type + def __str__(self): + return self.get_activity_type_display() diff --git a/parsifal/activities/urls.py b/parsifal/activities/urls.py index 22cea9d7..35e1a0fc 100644 --- a/parsifal/activities/urls.py +++ b/parsifal/activities/urls.py @@ -1,8 +1,9 @@ -# coding: utf-8 -from django.conf.urls import patterns, include, url +from django.urls import path -urlpatterns = patterns('parsifal.activities.views', - url(r'^follow/$', 'follow', name='follow'), - url(r'^unfollow/$', 'unfollow', name='unfollow'), - url(r'^update_followers_count/$', 'update_followers_count', name='update_followers_count'), -) \ No newline at end of file +from parsifal.activities import views + +urlpatterns = [ + path("follow/", views.follow, name="follow"), + path("unfollow/", views.unfollow, name="unfollow"), + path("update_followers_count/", views.update_followers_count, name="update_followers_count"), +] diff --git a/parsifal/activities/views.py b/parsifal/activities/views.py index 301cdb19..c350336f 100644 --- a/parsifal/activities/views.py +++ b/parsifal/activities/views.py @@ -1,8 +1,8 @@ -from django.template import RequestContext -from django.shortcuts import render, get_object_or_404 -from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect -from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required +from django.contrib.auth.models import User +from django.http import HttpResponse, HttpResponseBadRequest +from django.shortcuts import get_object_or_404, render +from django.utils.translation import gettext from parsifal.activities.models import Activity @@ -10,7 +10,7 @@ @login_required def follow(request): try: - user_id = request.GET['user-id'] + user_id = request.GET["user-id"] to_user = get_object_or_404(User, pk=user_id) from_user = request.user @@ -22,14 +22,14 @@ def follow(request): return HttpResponse() else: return HttpResponseBadRequest() - except: + except Exception: return HttpResponseBadRequest() @login_required def unfollow(request): try: - user_id = request.GET['user-id'] + user_id = request.GET["user-id"] to_user = get_object_or_404(User, pk=user_id) from_user = request.user @@ -41,48 +41,47 @@ def unfollow(request): return HttpResponse() else: return HttpResponseBadRequest() - except: + except Exception: return HttpResponseBadRequest() def update_followers_count(request): try: - user_id = request.GET['user-id'] + user_id = request.GET["user-id"] user = get_object_or_404(User, pk=user_id) followers_count = user.profile.get_followers_count() return HttpResponse(followers_count) except: return HttpResponseBadRequest() + def following(request, username): page_user = get_object_or_404(User, username=username) - page_title = 'following' + page_title = gettext("following") following = page_user.profile.get_following() user_following = None if request.user.is_authenticated(): user_following = request.user.profile.get_following() - return render(request, 'activities/follow.html', { - 'page_user': page_user, - 'page_title': page_title, - 'follow_list': following, - 'user_following': user_following - }) + return render( + request, + "activities/follow.html", + {"page_user": page_user, "page_title": page_title, "follow_list": following, "user_following": user_following}, + ) + def followers(request, username): user = get_object_or_404(User, username=username) - page_title = 'followers' + page_title = gettext("followers") followers = user.profile.get_followers() user_following = None if request.user.is_authenticated(): user_following = request.user.profile.get_following() - return render(request, 'activities/follow.html', { - 'page_user': user, - 'page_title': page_title, - 'follow_list': followers, - 'user_following': user_following - }) - + return render( + request, + "activities/follow.html", + {"page_user": user, "page_title": page_title, "follow_list": followers, "user_following": user_following}, + ) diff --git a/parsifal/authentication/__init__.py b/parsifal/authentication/__init__.py index e69de29b..2f925c5e 100644 --- a/parsifal/authentication/__init__.py +++ b/parsifal/authentication/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.authentication.apps.AuthenticationConfig" diff --git a/parsifal/authentication/apps.py b/parsifal/authentication/apps.py new file mode 100644 index 00000000..224ced90 --- /dev/null +++ b/parsifal/authentication/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class AuthenticationConfig(AppConfig): + name = "parsifal.authentication" + verbose_name = _("Authentication") diff --git a/parsifal/authentication/forms.py b/parsifal/authentication/forms.py index a405d399..078b2e85 100644 --- a/parsifal/authentication/forms.py +++ b/parsifal/authentication/forms.py @@ -1,53 +1,120 @@ from django import forms -from django.core.exceptions import ValidationError from django.contrib.auth.models import User +from django.core.exceptions import ValidationError +from django.utils.translation import gettext, gettext_lazy as _ def ForbiddenUsernamesValidator(value): - forbidden_usernames = ['admin', 'settings', 'news', 'about', 'help', 'signin', 'signup', - 'signout', 'terms', 'privacy', 'cookie', 'new', 'login', 'logout', 'administrator', - 'join', 'account', 'username', 'root', 'blog', 'user', 'users', 'billing', 'subscribe', - 'reviews', 'review', 'blog', 'blogs', 'edit', 'mail', 'email', 'home', 'job', 'jobs', - 'contribute', 'newsletter', 'shop', 'profile', 'register', 'auth', 'authentication', - 'campaign', 'config', 'delete', 'remove', 'forum', 'forums', 'download', 'downloads', - 'contact', 'blogs', 'feed', 'faq', 'intranet', 'log', 'registration', 'search', - 'explore', 'rss', 'support', 'status', 'static', 'media', 'setting', 'css', 'js', - 'follow', 'activity', 'library'] + forbidden_usernames = [ + "admin", + "settings", + "news", + "about", + "help", + "signin", + "signup", + "signout", + "terms", + "privacy", + "cookie", + "new", + "login", + "logout", + "administrator", + "join", + "account", + "username", + "root", + "blog", + "user", + "users", + "billing", + "subscribe", + "reviews", + "review", + "blog", + "blogs", + "edit", + "mail", + "email", + "home", + "job", + "jobs", + "contribute", + "newsletter", + "shop", + "profile", + "register", + "auth", + "authentication", + "campaign", + "config", + "delete", + "remove", + "forum", + "forums", + "download", + "downloads", + "contact", + "blogs", + "feed", + "faq", + "intranet", + "log", + "registration", + "search", + "explore", + "rss", + "support", + "status", + "static", + "media", + "setting", + "css", + "js", + "follow", + "activity", + "library", + ] if value.lower() in forbidden_usernames: - raise ValidationError('This is a reserved word.') + raise ValidationError(gettext("This is a reserved word.")) + def InvalidUsernameValidator(value): - if '@' in value or '+' in value or '-' in value: - raise ValidationError('Enter a valid username.') + if "@" in value or "+" in value or "-" in value: + raise ValidationError(gettext("Enter a valid username.")) + def UniqueEmailValidator(value): if User.objects.filter(email__iexact=value).exists(): - raise ValidationError('User with this Email already exists.') + raise ValidationError(gettext("User with this Email already exists.")) + def UniqueUsernameIgnoreCaseValidator(value): if User.objects.filter(username__iexact=value).exists(): - raise ValidationError('User with this Username already exists.') + raise ValidationError(gettext("User with this Username already exists.")) + class SignUpForm(forms.ModelForm): - password = forms.CharField(widget=forms.PasswordInput()) - confirm_password = forms.CharField(widget=forms.PasswordInput(), label="Confirm your password") - email = forms.CharField(required=True) + password = forms.CharField(label=_("Password"), widget=forms.PasswordInput()) + confirm_password = forms.CharField(label=_("Confirm your password"), widget=forms.PasswordInput()) + email = forms.CharField(label=_("Email"), required=True) class Meta: model = User - exclude = ['last_login', 'date_joined'] + exclude = ("last_login", "date_joined") def __init__(self, *args, **kwargs): - super(SignUpForm, self).__init__(*args, **kwargs) - self.fields['username'].validators.append(ForbiddenUsernamesValidator) - self.fields['username'].validators.append(InvalidUsernameValidator) - self.fields['username'].validators.append(UniqueUsernameIgnoreCaseValidator) - self.fields['email'].validators.append(UniqueEmailValidator) + super().__init__(*args, **kwargs) + self.fields["username"].validators.append(ForbiddenUsernamesValidator) + self.fields["username"].validators.append(InvalidUsernameValidator) + self.fields["username"].validators.append(UniqueUsernameIgnoreCaseValidator) + self.fields["email"].validators.append(UniqueEmailValidator) def clean(self): - super(SignUpForm, self).clean() - password = self.cleaned_data.get('password') - confirm_password = self.cleaned_data.get('confirm_password') + super().clean() + password = self.cleaned_data.get("password") + confirm_password = self.cleaned_data.get("confirm_password") if password and password != confirm_password: - self._errors['password'] = self.error_class(['Passwords don\'t match']) - return self.cleaned_data \ No newline at end of file + self._errors["password"] = self.error_class([gettext("Passwords don't match")]) + return self.cleaned_data diff --git a/parsifal/authentication/models.py b/parsifal/authentication/models.py index 8cafa66b..bfbeaf3b 100644 --- a/parsifal/authentication/models.py +++ b/parsifal/authentication/models.py @@ -1,26 +1,17 @@ import os.path -try: - import cPickle as pickle -except: - import pickle -from mendeley import DefaultStateGenerator -from mendeley.session import MendeleySession -from mendeley.auth import MendeleyAuthorizationCodeAuthenticator, handle_text_response -from oauthlib.oauth2 import TokenExpiredError -from requests_oauthlib import OAuth2Session -from dropbox.client import DropboxClient +from django.conf import settings as django_settings from django.contrib.auth.models import User -from django.db.models.signals import post_save from django.db import models -from django.conf import settings as django_settings +from django.db.models.signals import post_save +from django.utils.translation import gettext_lazy as _ from parsifal.activities.models import Activity from parsifal.reviews.models import Review class Profile(models.Model): - user = models.OneToOneField(User) + user = models.OneToOneField(User, on_delete=models.CASCADE) public_email = models.EmailField(null=True, blank=True) location = models.CharField(max_length=50) url = models.CharField(max_length=50) @@ -29,70 +20,26 @@ class Profile(models.Model): dropbox_token = models.CharField(max_length=2000, null=True, blank=True) class Meta: - db_table = 'auth_profile' - - def set_mendeley_token(self, value): - self.mendeley_token = pickle.dumps(value) - - def get_mendeley_token(self): - try: - return pickle.loads(str(self.mendeley_token)) - except Exception, e: - return None - - def get_mendeley_session(self): - mendeley = django_settings.MENDELEY - token = self.get_mendeley_token() - mendeley_session = None - if token: - mendeley_session = MendeleySession(mendeley, token) - try: - mendeley_session.profiles.me - except TokenExpiredError, e: - authenticator = MendeleyAuthorizationCodeAuthenticator(mendeley, DefaultStateGenerator.generate_state()) - oauth = OAuth2Session(client=authenticator.client, redirect_uri=mendeley.redirect_uri, scope=['all']) - oauth.compliance_hook['access_token_response'] = [handle_text_response] - token = oauth.refresh_token(authenticator.token_url, auth=authenticator.auth, refresh_token=token['refresh_token']) - self.set_mendeley_token(token) - self.user.save() - mendeley_session = MendeleySession(mendeley, token) - except Exception, e: - pass - return mendeley_session - - def get_mendeley_profile(self): - mendeley_session = self.get_mendeley_session() - mendeley_profile = None - if mendeley_session: - mendeley_profile = mendeley_session.profiles.me - return mendeley_profile - - def get_dropbox_profile(self): - dropbox_profile = None - if self.dropbox_token is not None: - client = DropboxClient(self.dropbox_token) - try: - dropbox_profile = client.account_info() - except Exception, e: - pass - return dropbox_profile + verbose_name = _("profile") + verbose_name_plural = _("profiles") + db_table = "auth_profile" def get_url(self): url = self.url if "http://" not in self.url and "https://" not in self.url and len(self.url) > 0: url = "http://" + str(self.url) - return url + return url def get_picture(self): - no_picture = django_settings.STATIC_URL + 'img/user.png' + no_picture = django_settings.STATIC_URL + "img/user.png" try: - filename = django_settings.MEDIA_ROOT + '/profile_pictures/' + self.user.username + '.jpg' - picture_url = django_settings.MEDIA_URL + 'profile_pictures/' + self.user.username + '.jpg' + filename = django_settings.MEDIA_ROOT + "/profile_pictures/" + self.user.username + ".jpg" + picture_url = django_settings.MEDIA_URL + "profile_pictures/" + self.user.username + ".jpg" if os.path.isfile(filename): return picture_url else: return no_picture - except Exception, e: + except Exception: return no_picture def get_screen_name(self): @@ -130,17 +77,22 @@ def get_reviews(self): user_reviews = [] author_reviews = Review.objects.filter(author=self.user) co_author_reviews = Review.objects.filter(co_authors=self.user) - for r in author_reviews: user_reviews.append(r) - for r in co_author_reviews: user_reviews.append(r) + for r in author_reviews: + user_reviews.append(r) + for r in co_author_reviews: + user_reviews.append(r) user_reviews.sort(key=lambda r: r.last_update, reverse=True) return user_reviews + def create_user_profile(sender, instance, created, **kwargs): if created: Profile.objects.create(user=instance) + def save_user_profile(sender, instance, **kwargs): instance.profile.save() + post_save.connect(create_user_profile, sender=User) post_save.connect(save_user_profile, sender=User) diff --git a/parsifal/authentication/views.py b/parsifal/authentication/views.py index 0e493c0d..ecca0822 100644 --- a/parsifal/authentication/views.py +++ b/parsifal/authentication/views.py @@ -1,73 +1,92 @@ -# coding: utf-8 - -from django.core.urlresolvers import reverse from django.contrib import messages -from django.shortcuts import get_object_or_404, render -from django.http import HttpResponseRedirect, HttpResponse -from django.template import RequestContext -from django.contrib.auth.models import User from django.contrib.auth import authenticate, login, logout -from django.contrib.auth.views import password_reset, password_reset_confirm +from django.contrib.auth.models import User +from django.contrib.auth.views import PasswordResetConfirmView, PasswordResetView +from django.http import HttpResponseRedirect +from django.shortcuts import render +from django.urls import reverse +from django.utils.translation import gettext as _ from parsifal.authentication.forms import SignUpForm -from parsifal.reviews.models import Review + +password_reset = PasswordResetView.as_view() +password_reset_confirm = PasswordResetConfirmView.as_view() def signup(request): - if request.method == 'POST': + if request.method == "POST": form = SignUpForm(request.POST) if not form.is_valid(): - messages.add_message(request, messages.ERROR, 'There was some problems while creating your account. Please review some fields before submiting again.') - return render(request, 'auth/signup.html', { 'form': form }) + messages.add_message( + request, + messages.ERROR, + _( + "There was some problems while creating your account. Please review some fields before submiting again." + ), + ) + return render(request, "auth/signup.html", {"form": form}) else: - username = form.cleaned_data.get('username') - email = form.cleaned_data.get('email') - password = form.cleaned_data.get('password') + username = form.cleaned_data.get("username") + email = form.cleaned_data.get("email") + password = form.cleaned_data.get("password") User.objects.create_user(username=username, password=password, email=email) user = authenticate(username=username, password=password) login(request, user) - messages.add_message(request, messages.SUCCESS, 'Your account were successfully created.') - return HttpResponseRedirect('/' + username + '/') + messages.add_message(request, messages.SUCCESS, _("Your account were successfully created.")) + return HttpResponseRedirect("/" + username + "/") else: - return render(request, 'auth/signup.html', { 'form': SignUpForm() }) + return render(request, "auth/signup.html", {"form": SignUpForm()}) + def signin(request): if request.user.is_authenticated(): - return HttpResponseRedirect('/') + return HttpResponseRedirect("/") else: - if request.method == 'POST': - username = request.POST['username'] - password = request.POST['password'] + if request.method == "POST": + username = request.POST["username"] + password = request.POST["password"] user = authenticate(username=username, password=password) if user is not None: if user.is_active: login(request, user) - if 'next' in request.GET: - return HttpResponseRedirect(request.GET['next']) + if "next" in request.GET: + return HttpResponseRedirect(request.GET["next"]) else: - return HttpResponseRedirect('/') + return HttpResponseRedirect("/") else: - messages.add_message(request, messages.ERROR, 'Your account is desactivated.') - return render(request, 'auth/signin.html') + messages.add_message(request, messages.ERROR, _("Your account is deactivated.")) + return render(request, "auth/signin.html") else: - messages.add_message(request, messages.ERROR, 'Username or password invalid.') - return render(request, 'auth/signin.html') + messages.add_message(request, messages.ERROR, _("Username or password invalid.")) + return render(request, "auth/signin.html") else: - return render(request, 'auth/signin.html') + return render(request, "auth/signin.html") + def signout(request): logout(request) - return HttpResponseRedirect('/') + return HttpResponseRedirect("/") + def reset(request): - return password_reset(request, template_name='auth/reset.html', - email_template_name='auth/reset_email.html', - subject_template_name='auth/reset_subject.txt', - post_reset_redirect=reverse('success')) + return password_reset( + request, + template_name="auth/reset.html", + email_template_name="auth/reset_email.html", + subject_template_name="auth/reset_subject.txt", + post_reset_redirect=reverse("success"), + ) + def reset_confirm(request, uidb64=None, token=None): - return password_reset_confirm(request, template_name='auth/reset_confirm.html', - uidb64=uidb64, token=token, post_reset_redirect=reverse('signin')) + return password_reset_confirm( + request, + template_name="auth/reset_confirm.html", + uidb64=uidb64, + token=token, + post_reset_redirect=reverse("signin"), + ) + def success(request): - return render(request, 'auth/success.html') + return render(request, "auth/success.html") diff --git a/parsifal/blog/__init__.py b/parsifal/blog/__init__.py index e69de29b..157437d5 100644 --- a/parsifal/blog/__init__.py +++ b/parsifal/blog/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.blog.apps.BlogConfig" diff --git a/parsifal/blog/admin.py b/parsifal/blog/admin.py index 8eb6d77f..7f5ef10b 100644 --- a/parsifal/blog/admin.py +++ b/parsifal/blog/admin.py @@ -1,17 +1,16 @@ -# coding: utf-8 - from django.contrib import admin from django.template.defaultfilters import slugify from parsifal.blog.models import Entry +@admin.register(Entry) class EntryAdmin(admin.ModelAdmin): - list_display = ['title', 'status', 'summary', 'creation_date', 'last_update', 'created_by', 'edited_by'] - list_filter = ['status',] - search_fields = ['title', 'content', 'created_by__username'] - date_hierarchy = 'start_publication' - fields = ['title', 'content', 'summary', 'status', 'start_publication'] + list_display = ("title", "status", "summary", "creation_date", "last_update", "created_by", "edited_by") + list_filter = ("status",) + search_fields = ("title", "content", "created_by__username") + date_hierarchy = "start_publication" + fields = ("title", "content", "summary", "status", "start_publication") def save_model(self, request, obj, form, change): if not obj.pk: @@ -19,8 +18,6 @@ def save_model(self, request, obj, form, change): obj.save() else: obj.edited_by = request.user - slug_str = "%s %s" % (obj.pk, obj.title.lower()) + slug_str = "%s %s" % (obj.pk, obj.title.lower()) obj.slug = slugify(slug_str) obj.save() - -admin.site.register(Entry, EntryAdmin) \ No newline at end of file diff --git a/parsifal/blog/apps.py b/parsifal/blog/apps.py new file mode 100644 index 00000000..d3e9c132 --- /dev/null +++ b/parsifal/blog/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class BlogConfig(AppConfig): + name = "parsifal.blog" + verbose_name = _("Blog") diff --git a/parsifal/blog/models.py b/parsifal/blog/models.py index 82b64a20..d3b6fd2f 100644 --- a/parsifal/blog/models.py +++ b/parsifal/blog/models.py @@ -1,17 +1,16 @@ -# coding: utf-8 - -from django.db import models from django.contrib.auth.models import User +from django.db import models +from django.utils.translation import gettext_lazy as _ class Entry(models.Model): - DRAFT = 'D' - HIDDEN = 'H' - PUBLISHED = 'P' + DRAFT = "D" + HIDDEN = "H" + PUBLISHED = "P" ENTRY_STATUS = ( - (DRAFT, 'Draft'), - (HIDDEN, 'Hidden'), - (PUBLISHED, 'Published'), + (DRAFT, _("Draft")), + (HIDDEN, _("Hidden")), + (PUBLISHED, _("Published")), ) title = models.CharField(max_length=255) @@ -20,14 +19,14 @@ class Entry(models.Model): summary = models.TextField(max_length=255, null=True, blank=True) status = models.CharField(max_length=10, choices=ENTRY_STATUS) start_publication = models.DateTimeField() - created_by = models.ForeignKey(User) + created_by = models.ForeignKey(User, on_delete=models.PROTECT) creation_date = models.DateTimeField(auto_now_add=True) last_update = models.DateTimeField(auto_now=True) - edited_by = models.ForeignKey(User, null=True, blank=True, related_name="+") + edited_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="+") class Meta: - verbose_name = "Entry" - verbose_name_plural = "Entries" + verbose_name = _("entry") + verbose_name_plural = _("entries") - def __unicode__(self): + def __str__(self): return self.title diff --git a/parsifal/blog/urls.py b/parsifal/blog/urls.py index 4603bedd..b146c1c8 100644 --- a/parsifal/blog/urls.py +++ b/parsifal/blog/urls.py @@ -1,8 +1,8 @@ -# coding: utf-8 +from django.urls import path -from django.conf.urls import patterns, include, url +from parsifal.blog import views -urlpatterns = patterns('parsifal.blog.views', - url(r'^$', 'entries', name='entries'), - url(r'^(?P[-\w]+)/$', 'entry', name='entry'), -) +urlpatterns = [ + path("", views.entries, name="entries"), + path("/", views.entry, name="entry"), +] diff --git a/parsifal/blog/views.py b/parsifal/blog/views.py index cb4666a3..8914d7e3 100644 --- a/parsifal/blog/views.py +++ b/parsifal/blog/views.py @@ -1,11 +1,15 @@ -from django.shortcuts import render, get_object_or_404 +from django.shortcuts import get_object_or_404, render + from parsifal.blog.models import Entry + def entries(request): - entries = Entry.objects.filter(status=Entry.PUBLISHED).order_by('-start_publication',) - return render(request, 'blog/entries.html', { 'entries': entries }) + entries = Entry.objects.filter(status=Entry.PUBLISHED).order_by( + "-start_publication", + ) + return render(request, "blog/entries.html", {"entries": entries}) + def entry(request, slug): blog_entry = get_object_or_404(Entry, slug=slug, status=Entry.PUBLISHED) - return render(request, 'blog/entry.html', { 'entry': blog_entry }) - \ No newline at end of file + return render(request, "blog/entry.html", {"entry": blog_entry}) diff --git a/parsifal/core/__init__.py b/parsifal/core/__init__.py index e69de29b..ae85e4ec 100644 --- a/parsifal/core/__init__.py +++ b/parsifal/core/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.core.apps.CoreConfig" diff --git a/parsifal/core/admin.py b/parsifal/core/admin.py index 2fca28f5..4617b3de 100644 --- a/parsifal/core/admin.py +++ b/parsifal/core/admin.py @@ -6,9 +6,14 @@ class MediaAdmin(admin.ModelAdmin): - list_display = ['name', 'url', 'media_type', 'content_type', 'width', 'height'] - list_filter = ['media_type',] - search_fields = ['name',] - fields = ['name', 'url', 'media_type', 'content', 'content_type', 'width', 'height'] + list_display = ["name", "url", "media_type", "content_type", "width", "height"] + list_filter = [ + "media_type", + ] + search_fields = [ + "name", + ] + fields = ["name", "url", "media_type", "content", "content_type", "width", "height"] + admin.site.register(Media, MediaAdmin) diff --git a/parsifal/core/apps.py b/parsifal/core/apps.py new file mode 100644 index 00000000..3b121e55 --- /dev/null +++ b/parsifal/core/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class CoreConfig(AppConfig): + name = "parsifal.core" + verbose_name = _("Settings") diff --git a/parsifal/core/models.py b/parsifal/core/models.py index a6de134f..75f91999 100644 --- a/parsifal/core/models.py +++ b/parsifal/core/models.py @@ -1,49 +1,50 @@ from django.db import models +from django.utils.translation import gettext_lazy as _ class Media(models.Model): - IMAGE = u'image' - VIDEO = u'video' + IMAGE = "image" + VIDEO = "video" MEDIA_TYPES = ( - (IMAGE, u'Image'), - (VIDEO, u'Video'), - ) - - OG_METATAG = u'' + (IMAGE, _("Image")), + (VIDEO, _("Video")), + ) + + OG_METATAG = '' name = models.CharField(max_length=255) url = models.URLField(max_length=500, null=True, blank=True) media_type = models.CharField(max_length=5, choices=MEDIA_TYPES) - content = models.FileField(upload_to=u'site/', null=True, blank=True) + content = models.FileField(upload_to="site/", null=True, blank=True) content_type = models.CharField(max_length=255, null=True, blank=True) width = models.IntegerField(default=0) height = models.IntegerField(default=0) class Meta: - verbose_name = u'Media' - verbose_name_plural = u'Medias' + verbose_name = _("media file") + verbose_name_plural = _("media files") def __unicode__(self): return self.name def get_fb_og_image_metatags(self): - return u'{0}\n{1}\n{2}\n{3}\n{4}'.format( - self.OG_METATAG.format('image', self.url), - self.OG_METATAG.format('image:secure_url', self.url), - self.OG_METATAG.format('image:type', self.content_type), - self.OG_METATAG.format('image:width', self.width), - self.OG_METATAG.format('image:height', self.height) - ) + return "{0}\n{1}\n{2}\n{3}\n{4}".format( + self.OG_METATAG.format("image", self.url), + self.OG_METATAG.format("image:secure_url", self.url), + self.OG_METATAG.format("image:type", self.content_type), + self.OG_METATAG.format("image:width", self.width), + self.OG_METATAG.format("image:height", self.height), + ) def get_fb_og_video_metatags(self): - return u'{0}\n{1}\n{2}\n{3}\n{4}\n{5}'.format( - self.OG_METATAG.format('video', self.url), - self.OG_METATAG.format('video:secure_url', self.url), - self.OG_METATAG.format('video:type', self.content_type), - self.OG_METATAG.format('video:width', self.width), - self.OG_METATAG.format('video:height', self.height), - self.OG_METATAG.format('image', self.content.url) - ) + return "{0}\n{1}\n{2}\n{3}\n{4}\n{5}".format( + self.OG_METATAG.format("video", self.url), + self.OG_METATAG.format("video:secure_url", self.url), + self.OG_METATAG.format("video:type", self.content_type), + self.OG_METATAG.format("video:width", self.width), + self.OG_METATAG.format("video:height", self.height), + self.OG_METATAG.format("image", self.content.url), + ) def get_fb_og_metatags(self): if self.media_type == self.IMAGE: @@ -51,4 +52,4 @@ def get_fb_og_metatags(self): elif self.media_type == self.VIDEO: return self.get_fb_og_video_metatags() else: - return u'' \ No newline at end of file + return "" diff --git a/parsifal/core/tests/test_views_home.py b/parsifal/core/tests/test_views_home.py index 2b5ec672..456ab5b3 100644 --- a/parsifal/core/tests/test_views_home.py +++ b/parsifal/core/tests/test_views_home.py @@ -1,28 +1,26 @@ -# coding: utf-8 - -from django.test import TestCase from django.contrib.auth.models import User +from django.test import TestCase class HomeUnauthenticatedUser(TestCase): def setUp(self): - self.response = self.client.get('/') + self.response = self.client.get("/") def test_get(self): self.assertEqual(self.response.status_code, 200) def test_template(self): - self.assertTemplateUsed(self.response, 'core/cover.html') + self.assertTemplateUsed(self.response, "core/cover.html") class HomeAuthenticatedUser(TestCase): def setUp(self): - self.user = User.objects.create_user(username='john', password='s3cr3tp4ssw0rd', email='john@doe.com') - self.client.login(username='john', password='s3cr3tp4ssw0rd') - self.response = self.client.get('/') + self.user = User.objects.create_user(username="john", password="s3cr3tp4ssw0rd", email="john@doe.com") + self.client.login(username="john", password="s3cr3tp4ssw0rd") + self.response = self.client.get("/") def test_get(self): self.assertEqual(self.response.status_code, 200) def test_template(self): - self.assertTemplateUsed(self.response, 'core/home.html') + self.assertTemplateUsed(self.response, "core/home.html") diff --git a/parsifal/core/views.py b/parsifal/core/views.py index bd384580..029bc0ff 100644 --- a/parsifal/core/views.py +++ b/parsifal/core/views.py @@ -1,16 +1,13 @@ -# coding: utf-8 - -from django.core.urlresolvers import reverse as r from django.shortcuts import render +from django.urls import reverse from django.utils.html import escape from parsifal.activities.models import Activity from parsifal.blog.models import Entry -from parsifal.reviews.models import Review def get_following_feeds(user): - feeds = [] + feeds = [] try: activities = [] followers = Activity.objects.filter(to_user=user, activity_type=Activity.FOLLOW) @@ -19,45 +16,44 @@ def get_following_feeds(user): following = Activity.objects.filter(from_user=user, activity_type=Activity.FOLLOW) for following_user in following: activities.append(following_user) - initial_activity = Activity.objects.get(from_user=user, to_user=following_user.to_user, activity_type=Activity.FOLLOW) - following_user_activities = Activity.objects.filter(from_user=following_user.to_user, activity_type=Activity.FOLLOW, date__gte=initial_activity.date).exclude(to_user=user) + initial_activity = Activity.objects.get( + from_user=user, to_user=following_user.to_user, activity_type=Activity.FOLLOW + ) + following_user_activities = Activity.objects.filter( + from_user=following_user.to_user, activity_type=Activity.FOLLOW, date__gte=initial_activity.date + ).exclude(to_user=user) for activity in following_user_activities: activities.append(activity) activities.sort(key=lambda a: a.date, reverse=True) for activity in activities: if activity.from_user == user: - activity.message = u'You are now following {2}'.format( - r('reviews', args=(user.username,)), - r('reviews', args=(activity.to_user.username,)), - escape(activity.to_user.profile.get_screen_name()) - ) + activity.message = 'You are now following {2}'.format( + reverse("reviews", args=(user.username,)), + reverse("reviews", args=(activity.to_user.username,)), + escape(activity.to_user.profile.get_screen_name()), + ) else: is_following = activity.to_user.profile.get_screen_name() if activity.to_user == user: - is_following = u'you' - activity.message = u'{1} is now following {3}'.format( - r('reviews', args=(activity.from_user.username,)), + is_following = "you" + activity.message = '{1} is now following {3}'.format( + reverse("reviews", args=(activity.from_user.username,)), escape(activity.from_user.profile.get_screen_name()), - r('reviews', args=(activity.to_user.username,)), - escape(is_following) - ) + reverse("reviews", args=(activity.to_user.username,)), + escape(is_following), + ) feeds.append(activity) - except Exception, e: + except Exception: pass return feeds + def home(request): if request.user.is_authenticated(): user_reviews = request.user.profile.get_reviews() feeds = get_following_feeds(request.user) - try: - latest_news = Entry.objects.filter(status=Entry.PUBLISHED).order_by('-start_publication',)[0] - except: - latest_news = None - return render(request, 'core/home.html', { - 'user_reviews': user_reviews, - 'feeds': feeds, - 'latest_news': latest_news - }) - else: - return render(request, 'core/cover.html') + latest_news = Entry.objects.filter(status=Entry.PUBLISHED).order_by("-start_publication").first() + return render( + request, "core/home.html", {"user_reviews": user_reviews, "feeds": feeds, "latest_news": latest_news} + ) + return render(request, "core/cover.html") diff --git a/parsifal/help/__init__.py b/parsifal/help/__init__.py index e69de29b..e49b317c 100644 --- a/parsifal/help/__init__.py +++ b/parsifal/help/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.help.apps.HelpConfig" diff --git a/parsifal/help/admin.py b/parsifal/help/admin.py index cea2bf7b..332fca6b 100644 --- a/parsifal/help/admin.py +++ b/parsifal/help/admin.py @@ -1,16 +1,34 @@ -# coding: utf-8 - from django.contrib import admin -from django.template.defaultfilters import slugify +from django.utils.text import slugify -from parsifal.help.models import Article, Category, Media +from parsifal.help.models import Article, Category +@admin.register(Article) class ArticleAdmin(admin.ModelAdmin): - list_display = ['title', 'slug', 'category', 'parent', 'is_active', 'views',] - list_filter = ['category',] - search_fields = ['title', 'content',] - fields = ['title', 'description', 'content', 'references', 'category', 'medias', 'parent', 'is_active',] + list_display = ( + "title", + "slug", + "category", + "parent", + "is_active", + "views", + ) + list_filter = ("category",) + search_fields = ( + "title", + "content", + ) + fields = ( + "title", + "description", + "content", + "references", + "category", + "medias", + "parent", + "is_active", + ) def save_model(self, request, obj, form, change): if not obj.pk: @@ -19,9 +37,7 @@ def save_model(self, request, obj, form, change): obj.slug = slugify(obj.title) obj.save() -class CategoryAdmin(admin.ModelAdmin): - list_display = ['name', 'slug',] - -admin.site.register(Article, ArticleAdmin) -admin.site.register(Category, CategoryAdmin) +@admin.register(Category) +class CategoryAdmin(admin.ModelAdmin): + list_display = ("name", "slug") diff --git a/parsifal/help/apps.py b/parsifal/help/apps.py new file mode 100644 index 00000000..84fb3cee --- /dev/null +++ b/parsifal/help/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class HelpConfig(AppConfig): + name = "parsifal.help" + verbose_name = _("Help") diff --git a/parsifal/help/models.py b/parsifal/help/models.py index bd2876d0..cf95d974 100644 --- a/parsifal/help/models.py +++ b/parsifal/help/models.py @@ -1,7 +1,8 @@ -from bs4 import BeautifulSoup - -from django.db import models from django.contrib.auth.models import User +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from bs4 import BeautifulSoup from parsifal.core.models import Media @@ -11,33 +12,34 @@ class Category(models.Model): slug = models.SlugField(max_length=255, null=True) class Meta: - verbose_name = u'Category' - verbose_name_plural = u'Categories' + verbose_name = _("category") + verbose_name_plural = _("categories") - def __unicode__(self): + def __str__(self): return self.name + class Article(models.Model): title = models.CharField(max_length=255, unique=True) slug = models.SlugField(max_length=255, unique=True) description = models.TextField(max_length=500, null=True, blank=True) content = models.TextField(max_length=4000, null=True, blank=True) references = models.TextField(max_length=2000, null=True, blank=True) - category = models.ForeignKey(Category) + category = models.ForeignKey(Category, on_delete=models.PROTECT) views = models.IntegerField(default=0) is_active = models.BooleanField(default=True) - parent = models.ForeignKey(u'Article', null=True, blank=True) + parent = models.ForeignKey("self", on_delete=models.SET_NULL, null=True, blank=True) medias = models.ManyToManyField(Media, blank=True) created_at = models.DateTimeField(auto_now_add=True) - created_by = models.ForeignKey(User, related_name=u'help_article_creation_user') + created_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name="help_article_creation_user") updated_at = models.DateTimeField(auto_now=True) - updated_by = models.ForeignKey(User, null=True, related_name=u'help_article_update_user') + updated_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name="help_article_update_user") class Meta: - verbose_name = u'Article' - verbose_name_plural = u'Articles' + verbose_name = _("article") + verbose_name_plural = _("articles") - def __unicode__(self): + def __str__(self): return self.title def raw_content(self): diff --git a/parsifal/help/urls.py b/parsifal/help/urls.py index 0b940dc2..57ef8b8b 100644 --- a/parsifal/help/urls.py +++ b/parsifal/help/urls.py @@ -1,9 +1,9 @@ -# coding: utf-8 +from django.urls import path -from django.conf.urls import patterns, include, url +from parsifal.help import views -urlpatterns = patterns('parsifal.help.views', - url(r'^$', 'articles', name='articles'), - url(r'^search/$', 'search', name='search'), - url(r'^(?P[-\w]+)/$', 'article', name='article'), -) +urlpatterns = [ + path("", views.articles, name="articles"), + path("search/", views.search, name="search"), + path("/", views.article, name="article"), +] diff --git a/parsifal/help/views.py b/parsifal/help/views.py index e7ae6349..6fe4afeb 100644 --- a/parsifal/help/views.py +++ b/parsifal/help/views.py @@ -1,29 +1,32 @@ -# coding: utf-8 - -from django.core.urlresolvers import reverse as r -from django.shortcuts import render, redirect, get_object_or_404 from django.db.models import Q +from django.shortcuts import get_object_or_404, redirect, render from parsifal.help.models import Article def articles(request): - articles = Article.objects.filter(is_active=True).order_by('category__name', 'title',) - return render(request, 'help/articles.html', { 'articles': articles }) + articles = Article.objects.filter(is_active=True).order_by( + "category__name", + "title", + ) + return render(request, "help/articles.html", {"articles": articles}) + def article(request, slug): article = get_object_or_404(Article, slug=slug, is_active=True) article.views += 1 article.save() - return render(request, 'help/article.html', { 'article': article }) + return render(request, "help/article.html", {"article": article}) + def search(request): - if 'q' in request.GET: - querystring = request.GET.get('q').strip() + if "q" in request.GET: + querystring = request.GET.get("q").strip() if querystring: - articles = Article.objects \ - .filter(is_active=True) \ - .filter(Q(title__icontains=querystring) | Q(content__icontains=querystring)) \ - .order_by('title') - return render(request, 'help/search.html', { 'articles': articles, 'querystring': querystring }) - return redirect(r('help:articles')) \ No newline at end of file + articles = ( + Article.objects.filter(is_active=True) + .filter(Q(title__icontains=querystring) | Q(content__icontains=querystring)) + .order_by("title") + ) + return render(request, "help/search.html", {"articles": articles, "querystring": querystring}) + return redirect("help:articles") diff --git a/parsifal/library/__init__.py b/parsifal/library/__init__.py index e69de29b..3261e117 100644 --- a/parsifal/library/__init__.py +++ b/parsifal/library/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.library.apps.LibraryConfig" diff --git a/parsifal/library/apps.py b/parsifal/library/apps.py new file mode 100644 index 00000000..0d8f57e4 --- /dev/null +++ b/parsifal/library/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class LibraryConfig(AppConfig): + name = "parsifal.library" + verbose_name = _("Library") diff --git a/parsifal/library/forms.py b/parsifal/library/forms.py index 911adb9d..c2628e1b 100644 --- a/parsifal/library/forms.py +++ b/parsifal/library/forms.py @@ -3,75 +3,176 @@ from django import forms from django.contrib.auth.models import User -from parsifal.library.models import SharedFolder, Folder, Document +from parsifal.library.models import Document, Folder, SharedFolder class FolderForm(forms.ModelForm): name = forms.CharField( - widget=forms.TextInput(attrs={ 'class': 'form-control input-sm', 'autocomplete': 'off' }), - max_length=50, - required=True - ) + widget=forms.TextInput(attrs={"class": "form-control input-sm", "autocomplete": "off"}), + max_length=50, + required=True, + ) user = forms.ModelChoiceField(widget=forms.HiddenInput(), queryset=User.objects.all(), required=True) class Meta: model = Folder - fields = ['name', 'user',] + fields = [ + "name", + "user", + ] def clean(self): cleaned_data = super(FolderForm, self).clean() - name = cleaned_data.get('name') - user = cleaned_data.get('user') + name = cleaned_data.get("name") + user = cleaned_data.get("user") if Folder.objects.filter(name=name, user=user).exists(): - self.add_error('name', 'Folder with this name already exists.') + self.add_error("name", "Folder with this name already exists.") class SharedFolderForm(forms.ModelForm): class Meta: model = SharedFolder - fields = ['name',] + fields = [ + "name", + ] + class DocumentForm(forms.ModelForm): - - entry_type = forms.ChoiceField(widget=forms.Select(attrs={ 'class': 'form-control', 'style': 'width: 20%;' }), choices=Document.ENTRY_TYPES) - title = forms.CharField(widget=forms.Textarea(attrs={ 'class': 'form-control', 'rows': '1' }), max_length=255, required=False) - author = forms.CharField(widget=forms.Textarea(attrs={ 'class': 'form-control', 'rows': '1' }), max_length=500, required=False) - abstract = forms.CharField(widget=forms.Textarea(attrs={ 'class': 'form-control', 'rows': '1' }), max_length=4000, required=False) - keywords = forms.CharField(widget=forms.Textarea(attrs={ 'class': 'form-control', 'rows': '1' }), max_length=500, required=False) - year = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control', 'style': 'width: 20%;' }), max_length=10, required=False) - month = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control', 'style': 'width: 20%;' }), max_length=30, required=False) - - booktitle = forms.CharField(label='Book title', widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=255, required=False) - editor = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=255, required=False) - howpublished = forms.CharField(label='How it was published', widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=255, required=False) - journal = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=255, required=False) - url = forms.CharField(label='URL', widget=forms.URLInput(attrs={ 'class': 'form-control' }), max_length=255, required=False) - publisher = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=255, required=False) - pages = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control', 'style': 'width: 20%;' }), max_length=255, required=False) - number = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control', 'style': 'width: 20%;' }), max_length=255, required=False) - volume = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control', 'style': 'width: 20%;' }), max_length=255, required=False) - edition = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control', 'style': 'width: 20%;' }), max_length=255, required=False) - chapter = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control', 'style': 'width: 20%;' }), max_length=255, required=False) - - address = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=255, required=False) - crossref = forms.CharField(label='Cross-reference', widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=255, required=False) - institution = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=255, required=False) - organization = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=255, required=False) - school = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=255, required=False) - series = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=255, required=False) - language = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=255, required=False) - - bibtexkey = forms.CharField(label='BibTeX key', widget=forms.TextInput(attrs={ 'class': 'form-control', 'style': 'width: 20%;' }), max_length=50, required=False) - coden = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control', 'style': 'width: 20%;' }), max_length=255, required=False) - doi = forms.CharField(label='DOI', widget=forms.TextInput(attrs={ 'class': 'form-control', 'style': 'width: 20%;' }), max_length=50, required=False) - isbn = forms.CharField(label='ISBN', widget=forms.TextInput(attrs={ 'class': 'form-control', 'style': 'width: 20%;' }), max_length=30, required=False) - issn = forms.CharField(label='ISSN', widget=forms.TextInput(attrs={ 'class': 'form-control', 'style': 'width: 20%;' }), max_length=30, required=False) - - note = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control' }), max_length=255, required=False) + + entry_type = forms.ChoiceField( + widget=forms.Select(attrs={"class": "form-control", "style": "width: 20%;"}), choices=Document.ENTRY_TYPES + ) + title = forms.CharField( + widget=forms.Textarea(attrs={"class": "form-control", "rows": "1"}), max_length=255, required=False + ) + author = forms.CharField( + widget=forms.Textarea(attrs={"class": "form-control", "rows": "1"}), max_length=500, required=False + ) + abstract = forms.CharField( + widget=forms.Textarea(attrs={"class": "form-control", "rows": "1"}), max_length=4000, required=False + ) + keywords = forms.CharField( + widget=forms.Textarea(attrs={"class": "form-control", "rows": "1"}), max_length=500, required=False + ) + year = forms.CharField( + widget=forms.TextInput(attrs={"class": "form-control", "style": "width: 20%;"}), max_length=10, required=False + ) + month = forms.CharField( + widget=forms.TextInput(attrs={"class": "form-control", "style": "width: 20%;"}), max_length=30, required=False + ) + + booktitle = forms.CharField( + label="Book title", widget=forms.TextInput(attrs={"class": "form-control"}), max_length=255, required=False + ) + editor = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}), max_length=255, required=False) + howpublished = forms.CharField( + label="How it was published", + widget=forms.TextInput(attrs={"class": "form-control"}), + max_length=255, + required=False, + ) + journal = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}), max_length=255, required=False) + url = forms.CharField( + label="URL", widget=forms.URLInput(attrs={"class": "form-control"}), max_length=255, required=False + ) + publisher = forms.CharField( + widget=forms.TextInput(attrs={"class": "form-control"}), max_length=255, required=False + ) + pages = forms.CharField( + widget=forms.TextInput(attrs={"class": "form-control", "style": "width: 20%;"}), max_length=255, required=False + ) + number = forms.CharField( + widget=forms.TextInput(attrs={"class": "form-control", "style": "width: 20%;"}), max_length=255, required=False + ) + volume = forms.CharField( + widget=forms.TextInput(attrs={"class": "form-control", "style": "width: 20%;"}), max_length=255, required=False + ) + edition = forms.CharField( + widget=forms.TextInput(attrs={"class": "form-control", "style": "width: 20%;"}), max_length=255, required=False + ) + chapter = forms.CharField( + widget=forms.TextInput(attrs={"class": "form-control", "style": "width: 20%;"}), max_length=255, required=False + ) + + address = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}), max_length=255, required=False) + crossref = forms.CharField( + label="Cross-reference", + widget=forms.TextInput(attrs={"class": "form-control"}), + max_length=255, + required=False, + ) + institution = forms.CharField( + widget=forms.TextInput(attrs={"class": "form-control"}), max_length=255, required=False + ) + organization = forms.CharField( + widget=forms.TextInput(attrs={"class": "form-control"}), max_length=255, required=False + ) + school = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}), max_length=255, required=False) + series = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}), max_length=255, required=False) + language = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}), max_length=255, required=False) + + bibtexkey = forms.CharField( + label="BibTeX key", + widget=forms.TextInput(attrs={"class": "form-control", "style": "width: 20%;"}), + max_length=50, + required=False, + ) + coden = forms.CharField( + widget=forms.TextInput(attrs={"class": "form-control", "style": "width: 20%;"}), max_length=255, required=False + ) + doi = forms.CharField( + label="DOI", + widget=forms.TextInput(attrs={"class": "form-control", "style": "width: 20%;"}), + max_length=50, + required=False, + ) + isbn = forms.CharField( + label="ISBN", + widget=forms.TextInput(attrs={"class": "form-control", "style": "width: 20%;"}), + max_length=30, + required=False, + ) + issn = forms.CharField( + label="ISSN", + widget=forms.TextInput(attrs={"class": "form-control", "style": "width: 20%;"}), + max_length=30, + required=False, + ) + + note = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}), max_length=255, required=False) class Meta: model = Document - fields = ['entry_type', 'title', 'author', 'abstract', 'keywords', 'year', 'month', 'booktitle', - 'editor', 'howpublished', 'journal', 'url', 'publisher', 'pages', 'number', 'volume', - 'edition', 'chapter', 'address', 'crossref', 'institution', 'organization', 'school', - 'series', 'language', 'bibtexkey', 'coden', 'doi', 'isbn', 'issn', 'note'] + fields = [ + "entry_type", + "title", + "author", + "abstract", + "keywords", + "year", + "month", + "booktitle", + "editor", + "howpublished", + "journal", + "url", + "publisher", + "pages", + "number", + "volume", + "edition", + "chapter", + "address", + "crossref", + "institution", + "organization", + "school", + "series", + "language", + "bibtexkey", + "coden", + "doi", + "isbn", + "issn", + "note", + ] diff --git a/parsifal/library/models.py b/parsifal/library/models.py index 382fd900..00fd71f4 100644 --- a/parsifal/library/models.py +++ b/parsifal/library/models.py @@ -1,113 +1,112 @@ -# coding: utf-8 - -from django.db import models from django.contrib.auth.models import User +from django.db import models from django.utils.text import slugify +from django.utils.translation import gettext_lazy as _ class SharedFolder(models.Model): name = models.CharField(max_length=50) slug = models.SlugField(max_length=255, null=True, blank=True) - users = models.ManyToManyField(User, through='Collaborator', related_name='shared_folders') + users = models.ManyToManyField(User, through="Collaborator", related_name="shared_folders") class Meta: - verbose_name = 'Shared Folder' - verbose_name_plural = 'Shared Folders' - ordering = ('name',) + verbose_name = _("shared folder") + verbose_name_plural = _("shared folders") + ordering = ("name",) - def __unicode__(self): + def __str__(self): return self.name def save(self, *args, **kwargs): if not self.pk: - super(SharedFolder, self).save(*args, **kwargs) + super().save(*args, **kwargs) base_slug = slugify(self.name) if len(base_slug) > 0: - base_slug = slugify(u'{0} {1}'.format(self.name, self.pk)) + base_slug = slugify("{0} {1}".format(self.name, self.pk)) else: base_slug = self.pk i = 0 unique_slug = base_slug while SharedFolder.objects.filter(slug=unique_slug).exists(): i += 1 - unique_slug = u'{0}-{1}'.format(base_slug, i) + unique_slug = "{0}-{1}".format(base_slug, i) self.slug = unique_slug - super(SharedFolder, self).save(*args, **kwargs) + super().save(*args, **kwargs) class Collaborator(models.Model): - READ = 'R' - WRITE = 'W' - ADMIN = 'A' + READ = "R" + WRITE = "W" + ADMIN = "A" ACCESS_TYPES = ( - (READ, 'Read'), - (WRITE, 'Write'), - (ADMIN, 'Admin'), - ) + (READ, _("Read")), + (WRITE, _("Write")), + (ADMIN, _("Admin")), + ) - user = models.ForeignKey(User) - shared_folder = models.ForeignKey(SharedFolder) + user = models.ForeignKey(User, on_delete=models.CASCADE) + shared_folder = models.ForeignKey(SharedFolder, on_delete=models.CASCADE) joined_at = models.DateTimeField(auto_now_add=True) is_owner = models.BooleanField(default=False) access = models.CharField(max_length=1, choices=ACCESS_TYPES, default=READ) class Meta: - verbose_name = 'Collaborator' - verbose_name_plural = 'Collaborators' + verbose_name = _("collaborator") + verbose_name_plural = _("collaborators") def save(self, *args, **kwargs): if self.is_owner: self.access = Collaborator.ADMIN - super(Collaborator, self).save(*args, **kwargs) + super().save(*args, **kwargs) class Document(models.Model): - ARTICLE = 'article' - BOOK = 'book' - BOOKLET = 'booklet' - CONFERENCE = 'conference' - INBOOK = 'inbook' - INCOLLECTION = 'incollection' - INPROCEEDINGS = 'inproceedings' - MANUAL = 'manual' - MASTERSTHESIS = 'mastersthesis' - MISC = 'misc' - PHDTHESIS = 'phdthesis' - PROCEEDINGS = 'proceedings' - TECHREPORT = 'techreport' - UNPUBLISHED = 'unpublished' + ARTICLE = "article" + BOOK = "book" + BOOKLET = "booklet" + CONFERENCE = "conference" + INBOOK = "inbook" + INCOLLECTION = "incollection" + INPROCEEDINGS = "inproceedings" + MANUAL = "manual" + MASTERSTHESIS = "mastersthesis" + MISC = "misc" + PHDTHESIS = "phdthesis" + PROCEEDINGS = "proceedings" + TECHREPORT = "techreport" + UNPUBLISHED = "unpublished" ENTRY_TYPES = ( - (ARTICLE, 'Article'), - (BOOK, 'Book'), - (BOOKLET, 'Booklet'), - (CONFERENCE, 'Conference'), - (INBOOK, 'Inbook'), - (INCOLLECTION, 'Incollection'), - (INPROCEEDINGS, 'Inproceedings'), - (MANUAL, 'Manual'), - (MASTERSTHESIS, 'Master\'s Thesis'), - (MISC, 'Misc'), - (PHDTHESIS, 'Ph.D. Thesis'), - (PROCEEDINGS, 'Proceedings'), - (TECHREPORT, 'Tech Report'), - (UNPUBLISHED, 'Unpublished'), - ) + (ARTICLE, _("Article")), + (BOOK, _("Book")), + (BOOKLET, _("Booklet")), + (CONFERENCE, _("Conference")), + (INBOOK, _("Inbook")), + (INCOLLECTION, _("Incollection")), + (INPROCEEDINGS, _("Inproceedings")), + (MANUAL, _("Manual")), + (MASTERSTHESIS, _("Master's Thesis")), + (MISC, _("Misc")), + (PHDTHESIS, _("Ph.D. Thesis")), + (PROCEEDINGS, _("Proceedings")), + (TECHREPORT, _("Tech Report")), + (UNPUBLISHED, _("Unpublished")), + ) # Bibtex required fields - bibtexkey = models.CharField('Bibtex key', max_length=255, null=True, blank=True) - entry_type = models.CharField('Document type', max_length=13, choices=ENTRY_TYPES, null=True, blank=True) - + bibtexkey = models.CharField("Bibtex key", max_length=255, null=True, blank=True) + entry_type = models.CharField("Document type", max_length=13, choices=ENTRY_TYPES, null=True, blank=True) + # Bibtex base fields address = models.CharField(max_length=2000, null=True, blank=True) author = models.TextField(max_length=1000, null=True, blank=True) booktitle = models.CharField(max_length=1000, null=True, blank=True) chapter = models.CharField(max_length=1000, null=True, blank=True) - crossref = models.CharField('Cross-referenced', max_length=1000, null=True, blank=True) + crossref = models.CharField(_("Cross-referenced"), max_length=1000, null=True, blank=True) edition = models.CharField(max_length=1000, null=True, blank=True) editor = models.CharField(max_length=1000, null=True, blank=True) - howpublished = models.CharField('How it was published', max_length=1000, null=True, blank=True) + howpublished = models.CharField(_("How it was published"), max_length=1000, null=True, blank=True) institution = models.CharField(max_length=1000, null=True, blank=True) journal = models.CharField(max_length=1000, null=True, blank=True) month = models.CharField(max_length=50, null=True, blank=True) @@ -119,68 +118,69 @@ class Document(models.Model): school = models.CharField(max_length=1000, null=True, blank=True) series = models.CharField(max_length=500, null=True, blank=True) title = models.CharField(max_length=1000, null=True, blank=True) - publication_type = models.CharField(max_length=1000, null=True, blank=True) # Type + publication_type = models.CharField(max_length=1000, null=True, blank=True) # Type volume = models.CharField(max_length=1000, null=True, blank=True) year = models.CharField(max_length=50, null=True, blank=True) # Extra fields abstract = models.TextField(max_length=4000, null=True, blank=True) coden = models.CharField(max_length=1000, null=True, blank=True) - doi = models.CharField('DOI', max_length=255, null=True, blank=True) - isbn = models.CharField('ISBN', max_length=255, null=True, blank=True) - issn = models.CharField('ISSN', max_length=255, null=True, blank=True) + doi = models.CharField(_("DOI"), max_length=255, null=True, blank=True) + isbn = models.CharField(_("ISBN"), max_length=255, null=True, blank=True) + issn = models.CharField(_("ISSN"), max_length=255, null=True, blank=True) keywords = models.CharField(max_length=2000, null=True, blank=True) language = models.CharField(max_length=1000, null=True, blank=True) - url = models.CharField('URL', max_length=1000, null=True, blank=True) + url = models.CharField(_("URL"), max_length=1000, null=True, blank=True) # Parsifal management field - user = models.ForeignKey(User, null=True, related_name='documents') - review = models.ForeignKey('reviews.Review', null=True, related_name='documents') - shared_folder = models.ForeignKey(SharedFolder, null=True, related_name='documents') + user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name="documents") + review = models.ForeignKey("reviews.Review", on_delete=models.CASCADE, null=True, related_name="documents") + shared_folder = models.ForeignKey(SharedFolder, on_delete=models.SET_NULL, null=True, related_name="documents") created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: - verbose_name = 'Document' - verbose_name_plural = 'Documents' + verbose_name = _("document") + verbose_name_plural = _("documents") - def __unicode__(self): + def __str__(self): return self.title def document_file_upload_to(instance, filename): - return u'library/{0}/'.format(instance.document.user.pk) + return "library/{0}/".format(instance.document.user.pk) + class DocumentFile(models.Model): - document = models.ForeignKey(Document, related_name='files') - document_file = models.FileField(upload_to='library/') + document = models.ForeignKey(Document, on_delete=models.CASCADE, related_name="files") + document_file = models.FileField(upload_to="library/") filename = models.CharField(max_length=255) size = models.IntegerField(default=0) created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) + updated_at = models.DateTimeField(auto_now=True) class Meta: - verbose_name = 'Document File' - verbose_name_plural = 'Document Files' + verbose_name = _("document file") + verbose_name_plural = _("document files") - def __unicode__(self): + def __str__(self): return self.filename class Folder(models.Model): name = models.CharField(max_length=50) slug = models.SlugField(max_length=255, null=True, blank=True) - user = models.ForeignKey(User, related_name='library_folders') + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="library_folders") documents = models.ManyToManyField(Document) class Meta: - verbose_name = 'Folder' - verbose_name_plural = 'Folders' - ordering = ('name',) - unique_together = (('name', 'user'),) + verbose_name = _("folder") + verbose_name_plural = _("folders") + ordering = ("name",) + unique_together = (("name", "user"),) - def __unicode__(self): + def __str__(self): return self.name def save(self, *args, **kwargs): @@ -188,10 +188,10 @@ def save(self, *args, **kwargs): if len(base_slug) > 0: unique_slug = base_slug else: - base_slug = unique_slug = 'untitled-folder' + base_slug = unique_slug = "untitled-folder" i = 0 while Folder.objects.filter(slug=unique_slug).exists(): i += 1 - unique_slug = u'{0}-{1}'.format(base_slug, i) + unique_slug = "{0}-{1}".format(base_slug, i) self.slug = unique_slug - super(Folder, self).save(*args, **kwargs) + super().save(*args, **kwargs) diff --git a/parsifal/library/urls.py b/parsifal/library/urls.py index 22648149..9f0a5ea8 100644 --- a/parsifal/library/urls.py +++ b/parsifal/library/urls.py @@ -1,20 +1,20 @@ -# coding: utf-8 +from django.urls import path -from django.conf.urls import patterns, include, url +from parsifal.library import views -urlpatterns = patterns('parsifal.library.views', - url(r'^$', 'index', name='index'), - url(r'^list_actions/$', 'list_actions', name='list_actions'), - url(r'^new_folder/$', 'new_folder', name='new_folder'), - url(r'^edit_folder/$', 'edit_folder', name='edit_folder'), - url(r'^folders/(?P[-\w]+)/$', 'folder', name='folder'), - url(r'^new_shared_folder/$', 'new_shared_folder', name='new_shared_folder'), - url(r'^shared/(?P[-\w]+)/$', 'shared_folder', name='shared_folder'), - url(r'^new_document/$', 'new_document', name='new_document'), - url(r'^documents/(?P\d+)/$', 'document', name='document'), - url(r'^move/$', 'move', name='move'), - url(r'^copy/$', 'copy', name='copy'), - url(r'^remove_from_folder/$', 'remove_from_folder', name='remove_from_folder'), - url(r'^delete_documents/$', 'delete_documents', name='delete_documents'), - url(r'^import_bibtex/$', 'import_bibtex', name='import_bibtex'), -) +urlpatterns = [ + path("", views.index, name="index"), + path("list_actions/", views.list_actions, name="list_actions"), + path("new_folder/", views.new_folder, name="new_folder"), + path("edit_folder/", views.edit_folder, name="edit_folder"), + path("folders//", views.folder, name="folder"), + path("new_shared_folder/", views.new_shared_folder, name="new_shared_folder"), + path("shared//", views.shared_folder, name="shared_folder"), + path("new_document/", views.new_document, name="new_document"), + path("documents//", views.document, name="document"), + path("move/", views.move, name="move"), + path("copy/", views.copy, name="copy"), + path("remove_from_folder/", views.remove_from_folder, name="remove_from_folder"), + path("delete_documents/", views.delete_documents, name="delete_documents"), + path("import_bibtex/", views.import_bibtex, name="import_bibtex"), +] diff --git a/parsifal/library/views.py b/parsifal/library/views.py index a3cac42d..cc12bc20 100644 --- a/parsifal/library/views.py +++ b/parsifal/library/views.py @@ -1,37 +1,37 @@ -# coding: utf-8 - import json import os -import bibtexparser -from bibtexparser.bparser import BibTexParser -from bibtexparser.customization import convert_to_unicode -from django.db import transaction from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.core.context_processors import csrf -from django.core.urlresolvers import reverse as r -from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator +from django.db import transaction from django.http import HttpResponse, HttpResponseBadRequest -from django.shortcuts import render, get_object_or_404, redirect -from django.views.decorators.http import require_POST +from django.shortcuts import get_object_or_404, redirect, render +from django.template.context_processors import csrf from django.template.loader import render_to_string -from django.utils.safestring import mark_safe +from django.urls import reverse +from django.utils.translation import gettext as _ +from django.views.decorators.http import require_POST -from parsifal.reviews.models import Review, Article -from parsifal.library.models import Folder, Document -from parsifal.library.forms import FolderForm, DocumentForm, SharedFolderForm +import bibtexparser +from bibtexparser.bparser import BibTexParser +from bibtexparser.customization import convert_to_unicode + +from parsifal.library.forms import DocumentForm, FolderForm, SharedFolderForm +from parsifal.library.models import Document, Folder, SharedFolder +from parsifal.reviews.models import Review def get_order(request): - order = request.GET.get('o', '').lower() - if order in ['title', '-title', 'author', '-author', 'year', '-year']: + order = request.GET.get("o", "").lower() + if order in ["title", "-title", "author", "-author", "year", "-year"]: return order - return 'title' + return "title" + def get_paginated_documents(request, queryset): paginator = Paginator(queryset, 100) - page = request.GET.get('p') + page = request.GET.get("p") try: documents = paginator.page(page) except PageNotAnInteger: @@ -40,30 +40,37 @@ def get_paginated_documents(request, queryset): documents = paginator.page(paginator.num_pages) return documents + def get_filtered_documents(queryset, querystring): if querystring: queryset = queryset.filter(title__icontains=querystring) return queryset + def library(request, documents, querystring, order, active_folder=None): reviews = Review.objects.filter(author=request.user) - folder_form = FolderForm(initial={ 'user': request.user }) + folder_form = FolderForm(initial={"user": request.user}) shared_folder_form = SharedFolderForm() current_full_path = request.get_full_path() - return render(request, 'library/library.html', { - 'reviews': reviews, - 'documents': documents, - 'querystring': querystring, - 'order': order, - 'active_folder': active_folder, - 'folder_form': folder_form, - 'shared_folder_form': shared_folder_form, - 'current_full_path': current_full_path - }) + return render( + request, + "library/library.html", + { + "reviews": reviews, + "documents": documents, + "querystring": querystring, + "order": order, + "active_folder": active_folder, + "folder_form": folder_form, + "shared_folder_form": shared_folder_form, + "current_full_path": current_full_path, + }, + ) + @login_required def index(request): - querystring = request.GET.get('q', '') + querystring = request.GET.get("q", "") order = get_order(request) queryset = Document.objects.filter(user=request.user) queryset = get_filtered_documents(queryset, querystring) @@ -71,24 +78,26 @@ def index(request): documents = get_paginated_documents(request, queryset) return library(request, documents, querystring, order) + @login_required @require_POST def list_actions(request): - action = request.POST.get('action') - if action == 'remove_from_folder': + action = request.POST.get("action") + if action == "remove_from_folder": return remove_from_folder(request) - elif action == 'delete_documents': + elif action == "delete_documents": return delete_documents(request) - elif action == 'move': + elif action == "move": return move(request) - elif action == 'copy': + elif action == "copy": return copy(request) - redirect_to = request.POST.get('redirect', r('library:index')) + redirect_to = request.POST.get("redirect", reverse("library:index")) return redirect(redirect_to) + @login_required def folder(request, slug): - querystring = request.GET.get('q', '') + querystring = request.GET.get("q", "") order = get_order(request) folder = get_object_or_404(Folder, slug=slug) queryset = folder.documents.all() @@ -97,6 +106,7 @@ def folder(request, slug): documents = get_paginated_documents(request, queryset) return library(request, documents, querystring, order, folder) + @login_required @require_POST def new_folder(request): @@ -104,209 +114,221 @@ def new_folder(request): if form.is_valid(): form.instance.user = request.user folder = form.save() - dump = json.dumps({ 'folder': { 'id': folder.id, 'name': folder.name, 'slug': folder.slug } }) - return HttpResponse(dump, content_type='application/json') + dump = json.dumps({"folder": {"id": folder.id, "name": folder.name, "slug": folder.slug}}) + return HttpResponse(dump, content_type="application/json") else: dump = json.dumps(form.errors) - return HttpResponseBadRequest(dump, content_type='application/json') + return HttpResponseBadRequest(dump, content_type="application/json") + @login_required @require_POST def edit_folder(request): - delete_folder = request.POST.get('delete', '') == 'delete' - folder_id = request.POST.get('id') + delete_folder = request.POST.get("delete", "") == "delete" + folder_id = request.POST.get("id") folder = Folder.objects.get(pk=folder_id) if delete_folder: folder.delete() - messages.success(request, u'The folder {0} was deleted successfully!'.format(folder.name)) - return redirect(r('library:index')) - else: + messages.success(request, "The folder {0} was deleted successfully!".format(folder.name)) + return redirect("library:index") + else: form = FolderForm(request.POST, instance=folder) if form.is_valid(): form.instance.user = request.user folder = form.save() - messages.success(request, u'The folder {0} was changed successfully!'.format(folder.name)) + messages.success(request, "The folder {0} was changed successfully!".format(folder.name)) else: - messages.error(request, u'An error ocurred while trying to save folder {0}'.format(folder.name)) - return redirect(r('library:folder', args=(folder.slug,))) + messages.error(request, "An error occurred while trying to save folder {0}".format(folder.name)) + return redirect("library:folder", args=(folder.slug,)) + @login_required def document(request, document_id): document = Document.objects.get(pk=document_id) json_context = {} - if request.method == 'POST': + if request.method == "POST": form = DocumentForm(request.POST, instance=document) if form.is_valid(): document = form.save() - json_context['status'] = 'success' - json_context['html'] = render_to_string('library/partial_document_summary.html', { 'document': document }) - return HttpResponse(json.dumps(json_context), content_type='application/json') + json_context["status"] = "success" + json_context["html"] = render_to_string("library/partial_document_summary.html", {"document": document}) + return HttpResponse(json.dumps(json_context), content_type="application/json") else: - json_context['status'] = 'error' + json_context["status"] = "error" else: form = DocumentForm(instance=document) - json_context['status'] = 'ok' - csrf_token = unicode(csrf(request)['csrf_token']) - json_context['html'] = render_to_string('library/document.html', { 'form': form, 'csrf_token': csrf_token }) - return HttpResponse(json.dumps(json_context), content_type='application/json') + json_context["status"] = "ok" + csrf_token = str(csrf(request)["csrf_token"]) + json_context["html"] = render_to_string("library/document.html", {"form": form, "csrf_token": csrf_token}) + return HttpResponse(json.dumps(json_context), content_type="application/json") + @login_required def new_document(request): json_context = {} - if request.method == 'POST': + if request.method == "POST": form = DocumentForm(request.POST) if form.is_valid(): form.instance.user = request.user - document = form.save() - messages.success(request, 'Document added successfully!') - json_context['status'] = 'success' - json_context['redirect_to'] = r('library:index') + form.save() + messages.success(request, "Document added successfully!") + json_context["status"] = "success" + json_context["redirect_to"] = reverse("library:index") else: - json_context['status'] = 'error' + json_context["status"] = "error" else: form = DocumentForm() - json_context['status'] = 'ok' - csrf_token = unicode(csrf(request)['csrf_token']) - html = render_to_string('library/new_document.html', { 'form': form, 'csrf_token': csrf_token }) - json_context['html'] = html + json_context["status"] = "ok" + csrf_token = str(csrf(request)["csrf_token"]) + html = render_to_string("library/new_document.html", {"form": form, "csrf_token": csrf_token}) + json_context["html"] = html dump = json.dumps(json_context) - return HttpResponse(dump, content_type='application/json') + return HttpResponse(dump, content_type="application/json") + def get_document_verbose_name(documents_size): - document_verbose_name = 'document' + document_verbose_name = "document" if documents_size > 1: - document_verbose_name = 'documents' + document_verbose_name = "documents" return document_verbose_name + @login_required @require_POST def move(request): - move_from_folder_id = request.POST.get('active-folder-id') + move_from_folder_id = request.POST.get("active-folder-id") move_from_folder = Folder.objects.get(pk=move_from_folder_id) - move_to_folder_id = request.POST.get('action-folder-id') + move_to_folder_id = request.POST.get("action-folder-id") move_to_folder = Folder.objects.get(pk=move_to_folder_id) - if request.POST.get('select-all-pages') == 'all': + if request.POST.get("select-all-pages") == "all": documents = move_from_folder.documents.all() else: - document_ids = request.POST.getlist('document') + document_ids = request.POST.getlist("document") documents = Document.objects.filter(id__in=document_ids) - querystring = request.POST.get('querystring', '') + querystring = request.POST.get("querystring", "") documents = get_filtered_documents(documents, querystring) move_to_folder.documents.add(*documents) move_from_folder.documents.remove(*documents) - messages.success(request, u'Documents moved from folder {0} to {1} successfully!'.format(move_from_folder.name, move_to_folder.name)) - redirect_to = request.POST.get('redirect', r('library:index')) + messages.success( + request, + "Documents moved from folder {0} to {1} successfully!".format(move_from_folder.name, move_to_folder.name), + ) + redirect_to = request.POST.get("redirect", reverse("library:index")) return redirect(redirect_to) + @login_required @require_POST def copy(request): - redirect_to = request.POST.get('redirect', r('library:index')) + redirect_to = request.POST.get("redirect", reverse("library:index")) - copy_from_folder_id = request.POST.get('active-folder-id') - copy_to_folder_id = request.POST.get('action-folder-id') + copy_from_folder_id = request.POST.get("active-folder-id") + copy_to_folder_id = request.POST.get("action-folder-id") try: copy_to_folder = Folder.objects.get(pk=copy_to_folder_id) except Folder.DoesNotExist: - messages.error(u'The folder you are trying to copy does not exist.') + messages.error(request, _("The folder you are trying to copy does not exist.")) return redirect(redirect_to) - select_all_pages = request.POST.get('select-all-pages') - document_ids = request.POST.getlist('document') + select_all_pages = request.POST.get("select-all-pages") + document_ids = request.POST.getlist("document") if copy_from_folder_id: try: copy_from_folder = Folder.objects.get(pk=copy_from_folder_id) documents = copy_from_folder.documents.all() except Folder.DoesNotExist: - messages.error(u'The folder you are trying to copy does not exist.') + messages.error(request, _("The folder you are trying to copy does not exist.")) return redirect(redirect_to) else: documents = Document.objects.filter(user=request.user) - if select_all_pages == 'all': - querystring = request.POST.get('querystring', '') + if select_all_pages == "all": + querystring = request.POST.get("querystring", "") documents = get_filtered_documents(documents, querystring) else: documents = documents.filter(id__in=document_ids) copy_to_folder.documents.add(*documents) - messages.success(request, u'Documents copied to folder {0} successfully!'.format(copy_to_folder.name)) + messages.success(request, "Documents copied to folder {0} successfully!".format(copy_to_folder.name)) return redirect(redirect_to) + @login_required @require_POST def remove_from_folder(request): - remove_from_folder_id = request.POST.get('active-folder-id') + remove_from_folder_id = request.POST.get("active-folder-id") folder = Folder.objects.get(pk=remove_from_folder_id) - select_all_pages = request.POST.get('select-all-pages') + select_all_pages = request.POST.get("select-all-pages") - if select_all_pages == 'all': - querystring = request.POST.get('querystring', '') + if select_all_pages == "all": + querystring = request.POST.get("querystring", "") documents = folder.documents.all() documents = get_filtered_documents(documents, querystring) documents_size = documents.count() folder.documents.remove(*documents) else: - documents = request.POST.getlist('document') + documents = request.POST.getlist("document") documents_size = len(documents) folder.documents.remove(*documents) - messages.success(request, u'{0} {1} successfully removed from folder {2}!'.format( - documents_size, - get_document_verbose_name(documents_size), - folder.name) - ) - redirect_to = request.POST.get('redirect', r('library:index')) + messages.success( + request, + "{0} {1} successfully removed from folder {2}!".format( + documents_size, get_document_verbose_name(documents_size), folder.name + ), + ) + redirect_to = request.POST.get("redirect", reverse("library:index")) return redirect(redirect_to) + @login_required @require_POST def delete_documents(request): - select_all_pages = request.POST.get('select-all-pages') - document_ids = request.POST.getlist('document') - folder_id = request.POST.get('active-folder-id') + select_all_pages = request.POST.get("select-all-pages") + document_ids = request.POST.getlist("document") + folder_id = request.POST.get("active-folder-id") if folder_id: folder = Folder.objects.get(pk=folder_id) - if select_all_pages == 'all': + if select_all_pages == "all": documents = folder.documents.all() else: documents = folder.documents.filter(id__in=document_ids) else: - if select_all_pages == 'all': + if select_all_pages == "all": documents = Document.objects.filter(user=request.user) else: documents = Document.objects.filter(user=request.user, id__in=document_ids) - querystring = request.POST.get('querystring', '') + querystring = request.POST.get("querystring", "") documents = get_filtered_documents(documents, querystring) - + documents_size = documents.count() documents.delete() - messages.success(request, u'{0} {1} successfully deleted!'.format( - documents_size, - get_document_verbose_name(documents_size)) - ) - redirect_to = request.POST.get('redirect', r('library:index')) + messages.success( + request, "{0} {1} successfully deleted!".format(documents_size, get_document_verbose_name(documents_size)) + ) + redirect_to = request.POST.get("redirect", reverse("library:index")) return redirect(redirect_to) + @login_required @require_POST def import_bibtex(request): - redirect_to = request.POST.get('redirect', r('library:index')) - bibtex_file = request.FILES['bibtex'] + redirect_to = request.POST.get("redirect", reverse("library:index")) + bibtex_file = request.FILES["bibtex"] ext = os.path.splitext(bibtex_file.name)[1] - valid_extensions = ['.bib', '.bibtex'] + valid_extensions = [".bib", ".bibtex"] - if ext in valid_extensions or bibtex_file.content_type == 'application/x-bibtex': + if ext in valid_extensions or bibtex_file.content_type == "application/x-bibtex": parser = BibTexParser() parser.customization = convert_to_unicode bib_database = bibtexparser.load(bibtex_file, parser=parser) @@ -315,60 +337,61 @@ def import_bibtex(request): with transaction.atomic(): for entry in bib_database.entries: document = Document(user=request.user) - document.bibtexkey = entry.get('id', None) - document.entry_type = entry.get('type', None) - document.address = entry.get('address', entry.get('correspondence_address1', None)) - document.author = entry.get('authors', entry.get('author', None)) - document.booktitle = entry.get('booktitle', None) - document.chapter = entry.get('chapter', None) - document.crossref = entry.get('crossref', None) - document.edition = entry.get('edition', None) - document.editor = entry.get('editor', None) - document.howpublished = entry.get('howpublished', None) - document.institution = entry.get('institution', None) - document.journal = entry.get('journal', None) - document.month = entry.get('month', None) - document.note = entry.get('note', None) - document.number = entry.get('number', None) - document.organization = entry.get('organization', None) - document.pages = entry.get('pages', None) - document.publisher = entry.get('publisher', None) - document.school = entry.get('school', None) - document.series = entry.get('series', None) - document.title = entry.get('title', None) - document.publication_type = entry.get('document_type', entry.get('publication_type', None)) - document.volume = entry.get('volume', None) - document.year = entry.get('year', None) - document.abstract = entry.get('abstract', None) - document.coden = entry.get('coden', None) - document.doi = entry.get('doi', None) - document.isbn = entry.get('isbn', None) - document.issn = entry.get('issn', None) - document.keywords = entry.get('author_keywords', entry.get('keywords', None)) - document.language = entry.get('language', None) - document.url = entry.get('url', entry.get('link', None)) + document.bibtexkey = entry.get("id", None) + document.entry_type = entry.get("type", None) + document.address = entry.get("address", entry.get("correspondence_address1", None)) + document.author = entry.get("authors", entry.get("author", None)) + document.booktitle = entry.get("booktitle", None) + document.chapter = entry.get("chapter", None) + document.crossref = entry.get("crossref", None) + document.edition = entry.get("edition", None) + document.editor = entry.get("editor", None) + document.howpublished = entry.get("howpublished", None) + document.institution = entry.get("institution", None) + document.journal = entry.get("journal", None) + document.month = entry.get("month", None) + document.note = entry.get("note", None) + document.number = entry.get("number", None) + document.organization = entry.get("organization", None) + document.pages = entry.get("pages", None) + document.publisher = entry.get("publisher", None) + document.school = entry.get("school", None) + document.series = entry.get("series", None) + document.title = entry.get("title", None) + document.publication_type = entry.get("document_type", entry.get("publication_type", None)) + document.volume = entry.get("volume", None) + document.year = entry.get("year", None) + document.abstract = entry.get("abstract", None) + document.coden = entry.get("coden", None) + document.doi = entry.get("doi", None) + document.isbn = entry.get("isbn", None) + document.issn = entry.get("issn", None) + document.keywords = entry.get("author_keywords", entry.get("keywords", None)) + document.language = entry.get("language", None) + document.url = entry.get("url", entry.get("link", None)) document.save() documents.append(document) if any(documents): - folder_id = request.POST.get('add-to-folder-id') + folder_id = request.POST.get("add-to-folder-id") if folder_id: try: folder = Folder.objects.get(pk=folder_id) folder.documents.add(*documents) except Folder.DoesNotExist: - messages.error(request, u'Folder does not exists.') - messages.success(request, u'{0} documents imported!'.format(len(documents))) + messages.error(request, "Folder does not exists.") + messages.success(request, "{0} documents imported!".format(len(documents))) else: - messages.warning(request, u'The bibtex file had no valid entry!') + messages.warning(request, "The bibtex file had no valid entry!") else: - messages.error(request, u'Invalid file type. Only .bib or .bibtex files are accepted.') + messages.error(request, "Invalid file type. Only .bib or .bibtex files are accepted.") return redirect(redirect_to) + @login_required def shared_folder(request, slug): - querystring = request.GET.get('q', '') + querystring = request.GET.get("q", "") order = get_order(request) shared_folder = get_object_or_404(SharedFolder, slug=slug) queryset = shared_folder.documents.all() @@ -377,14 +400,15 @@ def shared_folder(request, slug): documents = get_paginated_documents(request, queryset) return library(request, documents, querystring, order, shared_folder) + @login_required @require_POST def new_shared_folder(request): form = SharedFolderForm(request.POST) if form.is_valid(): folder = form.save() - dump = json.dumps({ 'folder': { 'id': folder.id, 'name': folder.name, 'slug': folder.slug } }) - return HttpResponse(dump, content_type='application/json') + dump = json.dumps({"folder": {"id": folder.id, "name": folder.name, "slug": folder.slug}}) + return HttpResponse(dump, content_type="application/json") else: dump = json.dumps(form.errors) - return HttpResponseBadRequest(dump, content_type='application/json') + return HttpResponseBadRequest(dump, content_type="application/json") diff --git a/parsifal/reviews/__init__.py b/parsifal/reviews/__init__.py index e69de29b..971324c1 100644 --- a/parsifal/reviews/__init__.py +++ b/parsifal/reviews/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.reviews.apps.ReviewsConfig" diff --git a/parsifal/reviews/admin.py b/parsifal/reviews/admin.py index 6e145f51..96e7ee95 100644 --- a/parsifal/reviews/admin.py +++ b/parsifal/reviews/admin.py @@ -1,15 +1,33 @@ -# coding: utf-8 - from django.contrib import admin from parsifal.reviews.models import Review +@admin.register(Review) class ReviewAdmin(admin.ModelAdmin): - list_display = ['name', 'title', 'description', 'author', 'create_date', 'last_update',] - search_fields = ['name', 'title', 'description'] - filter_horizontal = ('co_authors',) - fields = ['name', 'title', 'description', 'author', 'objective', 'sources', 'status', 'co_authors', 'quality_assessment_cutoff_score', 'population', 'intervention', 'comparison', 'outcome', 'context',] - - -admin.site.register(Review, ReviewAdmin) + list_display = [ + "name", + "title", + "description", + "author", + "create_date", + "last_update", + ] + search_fields = ["name", "title", "description"] + filter_horizontal = ("co_authors",) + fields = [ + "name", + "title", + "description", + "author", + "objective", + "sources", + "status", + "co_authors", + "quality_assessment_cutoff_score", + "population", + "intervention", + "comparison", + "outcome", + "context", + ] diff --git a/parsifal/reviews/apps.py b/parsifal/reviews/apps.py new file mode 100644 index 00000000..5834cf83 --- /dev/null +++ b/parsifal/reviews/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class ReviewsConfig(AppConfig): + name = "parsifal.reviews" + verbose_name = _("Reviews") diff --git a/parsifal/reviews/conducting/__init__.py b/parsifal/reviews/conducting/__init__.py index e69de29b..57dbe4dd 100644 --- a/parsifal/reviews/conducting/__init__.py +++ b/parsifal/reviews/conducting/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.reviews.conducting.apps.ConductingConfig" diff --git a/parsifal/reviews/conducting/apps.py b/parsifal/reviews/conducting/apps.py new file mode 100644 index 00000000..d2ab0879 --- /dev/null +++ b/parsifal/reviews/conducting/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class ConductingConfig(AppConfig): + name = "parsifal.reviews.conducting" + verbose_name = _("Reviews: Conducting") diff --git a/parsifal/reviews/conducting/models.py b/parsifal/reviews/conducting/models.py index 71a83623..e69de29b 100644 --- a/parsifal/reviews/conducting/models.py +++ b/parsifal/reviews/conducting/models.py @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/parsifal/reviews/conducting/tests/test_views_import_bibtex.py b/parsifal/reviews/conducting/tests/test_views_import_bibtex.py index 455ea89f..243c3bd7 100644 --- a/parsifal/reviews/conducting/tests/test_views_import_bibtex.py +++ b/parsifal/reviews/conducting/tests/test_views_import_bibtex.py @@ -1,21 +1,23 @@ -# coding: utf-8 - -from django.test import TestCase from django.contrib.auth.models import User +from django.test import TestCase from parsifal.reviews.models import Review, Source class ImportBibitexTest(TestCase): - fixtures = ['source_initial_data.json',] + fixtures = [ + "source_initial_data.json", + ] @classmethod def setUpTestData(cls): - cls.user = User.objects.create_user(username='john', email='john.doe@parsif.al', password='123') - cls.review = Review.objects.create(name='test-review', title='Test Review', description='', author=cls.user, objective='') + cls.user = User.objects.create_user(username="john", email="john.doe@parsif.al", password="123") + cls.review = Review.objects.create( + name="test-review", title="Test Review", description="", author=cls.user, objective="" + ) def setUp(self): - self.client.login(username='john', password='123') + self.client.login(username="john", password="123") def test_loaded_fixture(self): self.assertGreater(User.objects.all().count(), 0) diff --git a/parsifal/reviews/conducting/urls.py b/parsifal/reviews/conducting/urls.py index 28f7085b..fbf5dcbb 100644 --- a/parsifal/reviews/conducting/urls.py +++ b/parsifal/reviews/conducting/urls.py @@ -1,36 +1,50 @@ -# coding: utf-8 +from django.urls import path -from django.conf.urls import patterns, include, url +from parsifal.reviews.conducting import views - -urlpatterns = patterns('parsifal.reviews.conducting.views', - url(r'^add_source_string/$', 'add_source_string', name='add_source_string'), - url(r'^save_source_string/$', 'save_source_string', name='save_source_string'), - url(r'^remove_source_string/$', 'remove_source_string', name='remove_source_string'), - url(r'^import_base_string/$', 'import_base_string', name='import_base_string'), - url(r'^search_scopus/$', 'search_scopus', name='search_scopus'), - url(r'^search_science_direct/$', 'search_science_direct', name='search_science_direct'), - url(r'^new_article/$', 'new_article', name='new_article'), - url(r'^import/bibtex_file/$', 'import_bibtex', name='import_bibtex'), - url(r'^import/bibtex_raw_content/$', 'import_bibtex_raw_content', name='import_bibtex_raw_content'), - url(r'^source_articles/$', 'source_articles', name='source_articles'), - url(r'^article_details/$', 'article_details', name='article_details'), - url(r'^find_duplicates/$', 'find_duplicates', name='find_duplicates'), - url(r'^resolve_duplicated/$', 'resolve_duplicated', name='resolve_duplicated'), - url(r'^export_results/$', 'export_results', name='export_results'), - url(r'^resolve_all/$', 'resolve_all', name='resolve_all'), - url(r'^save_article_details/$', 'save_article_details', name='save_article_details'), - url(r'^save_quality_assessment/$', 'save_quality_assessment', name='save_quality_assessment'), - url(r'^quality_assessment_detailed/$', 'quality_assessment_detailed', name='quality_assessment_detailed'), - url(r'^quality_assessment_summary/$', 'quality_assessment_summary', name='quality_assessment_summary'), - url(r'^multiple_articles_action/remove/$', 'multiple_articles_action_remove', name='multiple_articles_action_remove'), - url(r'^multiple_articles_action/accept/$', 'multiple_articles_action_accept', name='multiple_articles_action_accept'), - url(r'^multiple_articles_action/reject/$', 'multiple_articles_action_reject', name='multiple_articles_action_reject'), - url(r'^multiple_articles_action/duplicated/$', 'multiple_articles_action_duplicated', name='multiple_articles_action_duplicated'), - #url(r'^articles/upload/$', 'articles_upload', name='articles_upload'), - url(r'^save_data_extraction/$', 'save_data_extraction', name='save_data_extraction'), - url(r'^save_data_extraction_status/$', 'save_data_extraction_status', name='save_data_extraction_status'), - url(r'^articles_selection_chart/$', 'articles_selection_chart', name='articles_selection_chart'), - url(r'^articles_per_year/$', 'articles_per_year', name='articles_per_year'), - url(r'^export_data_extraction/$', 'export_data_extraction', name='export_data_extraction') -) +urlpatterns = [ + path("add_source_string/", views.add_source_string, name="add_source_string"), + path("save_source_string/", views.save_source_string, name="save_source_string"), + path("remove_source_string/", views.remove_source_string, name="remove_source_string"), + path("import_base_string/", views.import_base_string, name="import_base_string"), + path("search_scopus/", views.search_scopus, name="search_scopus"), + path("search_science_direct/", views.search_science_direct, name="search_science_direct"), + path("new_article/", views.new_article, name="new_article"), + path("import/bibtex_file/", views.import_bibtex, name="import_bibtex"), + path("import/bibtex_raw_content/", views.import_bibtex_raw_content, name="import_bibtex_raw_content"), + path("source_articles/", views.source_articles, name="source_articles"), + path("article_details/", views.article_details, name="article_details"), + path("find_duplicates/", views.find_duplicates, name="find_duplicates"), + path("resolve_duplicated/", views.resolve_duplicated, name="resolve_duplicated"), + path("export_results/", views.export_results, name="export_results"), + path("resolve_all/", views.resolve_all, name="resolve_all"), + path("save_article_details/", views.save_article_details, name="save_article_details"), + path("save_quality_assessment/", views.save_quality_assessment, name="save_quality_assessment"), + path("quality_assessment_detailed/", views.quality_assessment_detailed, name="quality_assessment_detailed"), + path("quality_assessment_summary/", views.quality_assessment_summary, name="quality_assessment_summary"), + path( + "multiple_articles_action/remove/", + views.multiple_articles_action_remove, + name="multiple_articles_action_remove", + ), + path( + "multiple_articles_action/accept/", + views.multiple_articles_action_accept, + name="multiple_articles_action_accept", + ), + path( + "multiple_articles_action/reject/", + views.multiple_articles_action_reject, + name="multiple_articles_action_reject", + ), + path( + "multiple_articles_action/duplicated/", + views.multiple_articles_action_duplicated, + name="multiple_articles_action_duplicated", + ), + path("save_data_extraction/", views.save_data_extraction, name="save_data_extraction"), + path("save_data_extraction_status/", views.save_data_extraction_status, name="save_data_extraction_status"), + path("articles_selection_chart/", views.articles_selection_chart, name="articles_selection_chart"), + path("articles_per_year/", views.articles_per_year, name="articles_per_year"), + path("export_data_extraction/", views.export_data_extraction, name="export_data_extraction"), +] diff --git a/parsifal/reviews/conducting/views.py b/parsifal/reviews/conducting/views.py index 4312a02f..eb7bc183 100644 --- a/parsifal/reviews/conducting/views.py +++ b/parsifal/reviews/conducting/views.py @@ -1,73 +1,87 @@ -# coding: utf-8 - import json import os -import bibtexparser -from bibtexparser.bparser import BibTexParser -from bibtexparser.customization import convert_to_unicode -import xlwt -from django.views.decorators.http import require_POST -from django.core.urlresolvers import reverse as r -from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, Http404 +from django.conf import settings as django_settings from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.contrib.auth.models import User -from django.shortcuts import render_to_response, redirect, get_object_or_404, render -from django.template import RequestContext -from django.conf import settings as django_settings -from django.core.context_processors import csrf from django.db.models import Count +from django.http import Http404, HttpResponse, HttpResponseBadRequest +from django.shortcuts import get_object_or_404, redirect, render +from django.template.context_processors import csrf +from django.urls import reverse as r from django.utils.html import escape +from django.views.decorators.http import require_POST + +import bibtexparser +import xlwt +from bibtexparser.bparser import BibTexParser +from bibtexparser.customization import convert_to_unicode -from parsifal.reviews.models import * -from parsifal.reviews.decorators import main_author_required, author_required +from parsifal.reviews.decorators import author_required +from parsifal.reviews.models import ( + Article, + DataExtraction, + DataExtractionField, + QualityAnswer, + QualityAssessment, + QualityQuestion, + Review, + SearchSession, + SelectionCriteria, + Source, +) from parsifal.utils.elsevier.client import ElsevierClient -from parsifal.utils.elsevier.exceptions import * +from parsifal.utils.elsevier.exceptions import ElsevierInvalidRequest, ElsevierQuotaExceeded @author_required @login_required def conducting(request, username, review_name): - return redirect(r('search_studies', args=(username, review_name))) + return redirect(r("search_studies", args=(username, review_name))) + @author_required @login_required def search_studies(request, username, review_name): review = get_object_or_404(Review, name=review_name, author__username__iexact=username) sessions = review.get_latest_source_search_strings() - add_sources = review.sources.exclude(id__in=sessions.values('source__id')) + add_sources = review.sources.exclude(id__in=sessions.values("source__id")) database_queries = {} try: - scopus = sessions.filter(source__name__iexact='Scopus')[0] - database_queries['scopus'] = scopus + scopus = sessions.filter(source__name__iexact="Scopus")[0] + database_queries["scopus"] = scopus except: pass try: - science_direct = sessions.filter(source__name__iexact='Science@Direct')[0] - database_queries['science_direct'] = science_direct + science_direct = sessions.filter(source__name__iexact="Science@Direct")[0] + database_queries["science_direct"] = science_direct except: pass sources_names = [] for source in review.sources.all(): sources_names.append(source.name.lower()) - return render(request, 'conducting/conducting_search_studies.html', { - 'review': review, - 'add_sources': add_sources, - 'database_queries': database_queries, - 'sources_names': sources_names - }) + return render( + request, + "conducting/conducting_search_studies.html", + { + "review": review, + "add_sources": add_sources, + "database_queries": database_queries, + "sources_names": sources_names, + }, + ) + @author_required @login_required @require_POST def save_source_string(request): try: - review_id = request.POST.get('review-id') - source_id = request.POST.get('source-id') + review_id = request.POST.get("review-id") + source_id = request.POST.get("source-id") review = Review.objects.get(pk=review_id) source = Source.objects.get(pk=source_id) - search_string = request.POST.get('search_string') + search_string = request.POST.get("search_string") try: search_session = review.get_latest_source_search_strings().get(source=source) except SearchSession.DoesNotExist: @@ -78,33 +92,31 @@ def save_source_string(request): except: return HttpResponseBadRequest() + @author_required @login_required @require_POST def remove_source_string(request): + review_id = request.POST.get("review-id") + source_id = request.POST.get("source-id") + review = get_object_or_404(Review, pk=review_id) + source = get_object_or_404(Source, pk=source_id) try: - review_id = request.POST.get('review-id') - source_id = request.POST.get('source-id') - review = Review.objects.get(pk=review_id) - source = Source.objects.get(pk=source_id) - search_string = request.POST.get('search_string') - try: - search_session = review.get_latest_source_search_strings().get(source=source) - search_session.delete() - except SearchSession.DoesNotExist: - pass - messages.success(request, u'{0} search string removed successfully!'.format(source.name)) - except: - messages.error(request, u'{0} search string removed successfully!'.format(source.name)) - return redirect(r('search_studies', args=(review.author.username, review.name))) + search_session = review.get_latest_source_search_strings().get(source=source) + search_session.delete() + except SearchSession.DoesNotExist: + pass + messages.success(request, "{0} search string removed successfully!".format(source.name)) + return redirect(r("search_studies", args=(review.author.username, review.name))) + @author_required @login_required @require_POST def import_base_string(request): try: - review_id = request.POST.get('review-id') - source_id = request.POST.get('source-id') + review_id = request.POST.get("review-id") + source_id = request.POST.get("source-id") review = Review.objects.get(pk=review_id) source = Source.objects.get(pk=source_id) base_search_string = review.get_generic_search_string().search_string @@ -118,30 +130,36 @@ def import_base_string(request): except: return HttpResponseBadRequest() + def elsevier_search(request, database): client = ElsevierClient(django_settings.ELSEVIER_API_KEY) - query = request.GET.get('query', '') - query = ' '.join(query.split()) - count = request.GET.get('count', '25') - start = request.GET.get('start', '0') + query = request.GET.get("query", "") + query = " ".join(query.split()) + count = request.GET.get("count", "25") + start = request.GET.get("start", "0") try: result = {} - if database == 'scopus': - result = client.search_scopus({ 'query': query, 'count': count, 'start': start }) - elif database == 'science_direct': - result = client.search_science_direct({ 'query': query, 'count': count, 'start': start }) + if database == "scopus": + result = client.search_scopus({"query": query, "count": count, "start": start}) + elif database == "science_direct": + result = client.search_science_direct({"query": query, "count": count, "start": start}) data = json.dumps(result) - return HttpResponse(data, content_type='application/json') - except ElsevierInvalidRequest, e: - return HttpResponseBadRequest('Invalid query. Please verify the syntax of your query before executing a new search.') - except ElsevierQuotaExceeded, e: - return HttpResponseBadRequest('Parsifal\'s search quota on Elsevier\'s databases exceeded. Please try again later.') + return HttpResponse(data, content_type="application/json") + except ElsevierInvalidRequest: + return HttpResponseBadRequest( + "Invalid query. Please verify the syntax of your query before executing a new search." + ) + except ElsevierQuotaExceeded: + return HttpResponseBadRequest( + "Parsifal's search quota on Elsevier's databases exceeded. Please try again later." + ) @author_required @login_required def search_scopus(request): - return elsevier_search(request, 'scopus') + return elsevier_search(request, "scopus") + @author_required @login_required @@ -149,45 +167,56 @@ def search_science_direct(request): raise Http404 # return elsevier_search(request, 'science_direct') + @author_required @login_required def import_studies(request, username, review_name): review = Review.objects.get(name=review_name, author__username__iexact=username) sources = [] for source in review.sources.all(): - sources.append({ - 'source': source, - 'count': Article.objects.filter(source=source, review=review).count() - }) - return render(request, 'conducting/conducting_import_studies.html', { - 'review': review, - 'sources': sources - }) + sources.append({"source": source, "count": Article.objects.filter(source=source, review=review).count()}) + return render(request, "conducting/conducting_import_studies.html", {"review": review, "sources": sources}) + @author_required @login_required def study_selection(request, username, review_name): review = Review.objects.get(name=review_name, author__username__iexact=username) try: - active_tab = int(request.GET['source']) - except Exception, e: + active_tab = int(request.GET["source"]) + except Exception: active_tab = -1 add_sources = review.sources.count() import_articles = review.get_source_articles().count() steps_messages = [] - if not add_sources: steps_messages.append(u'Use the planning tab to add sources to your review.'.format(r('protocol', args=(username, review_name)))) - if not import_articles: steps_messages.append(u'Import the studies using the import studies tab.'.format(r('import_studies', args=(username, review_name)))) + if not add_sources: + steps_messages.append( + 'Use the planning tab to add sources to your review.'.format( + r("protocol", args=(username, review_name)) + ) + ) + if not import_articles: + steps_messages.append( + 'Import the studies using the import studies tab.'.format( + r("import_studies", args=(username, review_name)) + ) + ) finished_all_steps = len(steps_messages) == 0 - return render(request, 'conducting/conducting_study_selection.html', { - 'review': review, - 'active_tab': active_tab, - 'steps_messages': steps_messages, - 'finished_all_steps': finished_all_steps - }) + return render( + request, + "conducting/conducting_study_selection.html", + { + "review": review, + "active_tab": active_tab, + "steps_messages": steps_messages, + "finished_all_steps": finished_all_steps, + }, + ) + def build_quality_assessment_table(request, review, order): selected_studies = review.get_accepted_articles().order_by(order) @@ -195,22 +224,30 @@ def build_quality_assessment_table(request, review, order): quality_answers = review.get_quality_assessment_answers() if quality_questions and quality_answers: - str_table = u'' + str_table = "" for study in selected_studies: - str_table += u''' + str_table += """

{0} ({4}){1}

- '''.format(escape(study.title), study.get_score(), study.id, unicode(csrf(request)['csrf_token']), escape(study.year)) + """.format( + escape(study.title), study.get_score(), study.id, str(csrf(request)["csrf_token"]), escape(study.year) + ) quality_assessment = study.get_quality_assesment() for question in quality_questions: - str_table += u''' - ''' + str_table += ( + ''' + """ + ) try: question_answer = quality_assessment.filter(question__id=question.id).get() @@ -218,17 +255,26 @@ def build_quality_assessment_table(request, review, order): question_answer = None for answer in quality_answers: - selected_answer = '' + selected_answer = "" if question_answer is not None: if answer.id == question_answer.answer.id: - selected_answer = ' selected-answer' - str_table += u'''''' - str_table += u'''''' - - str_table += u'''
''' + escape(question.description) + '''
""" + + escape(question.description) + + """''' + escape(answer.description) + '''
''' + selected_answer = " selected-answer" + str_table += ( + """""" + + escape(answer.description) + + """""" + ) + str_table += """""" + + str_table += """""" return str_table else: - return '' + return "" + @author_required @login_required @@ -243,135 +289,195 @@ def quality_assessment(request, username, review_name): steps_messages = [] - if not add_sources: steps_messages.append('Use the planning tab to add sources to your review.') - if not import_articles: steps_messages.append('Import the studies using the import studies tab.') - if not select_articles: steps_messages.append('Classify the imported studies using the study selection tab.') - if not create_questions: steps_messages.append('Create quality assessment questions using the planning tab.') - if not create_answers: steps_messages.append('Create quality assessment answers using the planning tab.') + if not add_sources: + steps_messages.append( + 'Use the planning tab to add sources to your review.' + ) + if not import_articles: + steps_messages.append( + 'Import the studies using the import studies tab.' + ) + if not select_articles: + steps_messages.append( + 'Classify the imported studies using the study selection tab.' + ) + if not create_questions: + steps_messages.append( + 'Create quality assessment questions using the planning tab.' + ) + if not create_answers: + steps_messages.append( + 'Create quality assessment answers using the planning tab.' + ) finished_all_steps = len(steps_messages) == 0 - order = 'title' - if 'order' in request.GET: - order = request.GET.get('order') - elif 'quality_assessment_order' in request.session: - order = request.session.get('quality_assessment_order') - if order not in ('title', '-title', 'year', '-year'): - order = 'title' - request.session['quality_assessment_order'] = order + order = "title" + if "order" in request.GET: + order = request.GET.get("order") + elif "quality_assessment_order" in request.session: + order = request.session.get("quality_assessment_order") + if order not in ("title", "-title", "year", "-year"): + order = "title" + request.session["quality_assessment_order"] = order quality_assessment_table = build_quality_assessment_table(request, review, order) - return render(request, 'conducting/conducting_quality_assessment.html', { - 'review': review, - 'steps_messages': steps_messages, - 'quality_assessment_table': quality_assessment_table, - 'finished_all_steps': finished_all_steps, - 'order': order - }) + return render( + request, + "conducting/conducting_quality_assessment.html", + { + "review": review, + "steps_messages": steps_messages, + "quality_assessment_table": quality_assessment_table, + "finished_all_steps": finished_all_steps, + "order": order, + }, + ) + def build_data_extraction_field_row(article, field): - str_field = u'' + str_field = "" try: extraction = DataExtraction.objects.get(article=article, field=field) - except Exception, e: + except Exception: extraction = None if field.field_type == DataExtractionField.BOOLEAN_FIELD: - true = u'' - false = u'' + true = "" + false = "" if extraction != None: - if extraction.get_value() == True: - true = u' selected' - elif extraction.get_value() == False: - false = u' selected' + if extraction.get_value(): + true = " selected" + else: + false = " selected" - str_field = u''' - '''.format(true, false) + """.format( + true, false + ) elif field.field_type == DataExtractionField.DATE_FIELD: - if extraction != None: + if extraction is not None: value = extraction.get_date_value_as_string() else: - value = u'' - str_field = u''.format(article.id, field.id, escape(value)) + value = "" + str_field = ''.format( + article.id, field.id, escape(value) + ) elif field.field_type == DataExtractionField.SELECT_ONE_FIELD: - str_field = u''' + """.format( + article.id, field.id + ) for value in field.get_select_values(): - if extraction != None and extraction.get_value() != None and extraction.get_value().id == value.id: - selected = ' selected' + if extraction is not None and extraction.get_value() is not None and extraction.get_value().id == value.id: + selected = " selected" else: - selected = '' - str_field += u''''''.format(value.id, selected, escape(value.value)) - str_field += u'' + selected = "" + str_field += """""".format(value.id, selected, escape(value.value)) + str_field += "" elif field.field_type == DataExtractionField.SELECT_MANY_FIELD: for value in field.get_select_values(): if extraction != None and value in extraction.get_value(): - checked = ' checked' + checked = " checked" else: - checked = '' - str_field += u' '.format(article.id, field.id, value.id, checked, escape(value.value)) + checked = "" + str_field += ' '.format( + article.id, field.id, value.id, checked, escape(value.value) + ) elif field.field_type == DataExtractionField.STRING_FIELD: - value = '' + value = "" if extraction != None: value = extraction.get_value() - str_field = u''.format(article.id, field.id, escape(value)) + str_field = ''.format( + article.id, field.id, escape(value) + ) else: - value = '' - if extraction != None: + value = "" + if extraction is not None: value = extraction.get_value() - str_field = u''.format(article.id, field.id, escape(value)) + str_field = ''.format( + article.id, field.id, escape(value) + ) return str_field + def build_data_extraction_table(review, is_finished): selected_studies = review.get_final_selection_articles() - if is_finished != None: + if is_finished is not None: selected_studies = selected_studies.filter(finished_data_extraction=is_finished) data_extraction_fields = review.get_data_extraction_fields() has_quality_assessment = review.has_quality_assessment_checklist() if selected_studies and data_extraction_fields: - str_table = u'
' + str_table = '
' for study in selected_studies: if has_quality_assessment: - str_table += u'''
+ str_table += """

{0} - {1}'''.format(escape(study.title), study.get_score()) + {1}""".format( + escape(study.title), study.get_score() + ) if study.finished_data_extraction: - str_table += u' mark as undone' + str_table += ' mark as undone' else: - str_table += u' mark as done' + str_table += ' mark as done' - str_table += u'

' - str_table += u'
'.format(study.id) + str_table += "
" + str_table += '
'.format(study.id) else: - str_table += u'''
+ str_table += """

{1}

-
'''.format(study.id, escape(study.title)) +
""".format( + study.id, escape(study.title) + ) for field in data_extraction_fields: - str_table += u'''
+ str_table += """
{2}
- '''.format(field.id, escape(field.description), build_data_extraction_field_row(study, field)) - str_table += u'
' + """.format( + field.id, escape(field.description), build_data_extraction_field_row(study, field) + ) + str_table += "
" str_table += "
" return str_table else: - return u'' + return "" + @author_required @login_required @@ -385,39 +491,71 @@ def data_extraction(request, username, review_name): steps_messages = [] - if not add_sources: steps_messages.append('Use the planning tab to add sources to your review.') - if not import_articles: steps_messages.append('Import the studies using the import studies tab.') - if not select_articles: steps_messages.append('Classify the imported studies using the study selection tab.') - if not create_fields: steps_messages.append('Create data extraction fields using the planning tab.') + if not add_sources: + steps_messages.append( + 'Use the planning tab to add sources to your review.' + ) + if not import_articles: + steps_messages.append( + 'Import the studies using the import studies tab.' + ) + if not select_articles: + steps_messages.append( + 'Classify the imported studies using the study selection tab.' + ) + if not create_fields: + steps_messages.append( + 'Create data extraction fields using the planning tab.' + ) finished_all_steps = not steps_messages - tab = request.GET.get('tab', 'todo') - if tab not in ['todo', 'done', 'all']: - tab = 'todo' + tab = request.GET.get("tab", "todo") + if tab not in ["todo", "done", "all"]: + tab = "todo" is_finished = None - if tab == 'todo': + if tab == "todo": is_finished = False - elif tab == 'done': + elif tab == "done": is_finished = True - elif tab == 'all': + elif tab == "all": is_finished = None try: data_extraction_table = build_data_extraction_table(review, is_finished) - except Exception, e: - raise e - data_extraction_table = '

Something went wrong while rendering the data extraction form.

' - - return render(request, 'conducting/conducting_data_extraction.html', { - 'review': review, - 'steps_messages': steps_messages, - 'data_extraction_table': data_extraction_table, - 'finished_all_steps': finished_all_steps, - 'tab': tab - }) + except Exception: + data_extraction_table = "

Something went wrong while rendering the data extraction form.

" + + return render( + request, + "conducting/conducting_data_extraction.html", + { + "review": review, + "steps_messages": steps_messages, + "data_extraction_table": data_extraction_table, + "finished_all_steps": finished_all_steps, + "tab": tab, + }, + ) + def bibtex_to_article_object(bib_database, review, source): articles = [] @@ -425,27 +563,48 @@ def bibtex_to_article_object(bib_database, review, source): for entry in bib_database.entries: article = Article() try: - if 'id' in entry: article.bibtex_key = entry['id'][:100] - if 'title' in entry: article.title = entry['title'][:1000] - if 'journal' in entry: article.journal = entry['journal'][:1000] - if 'year' in entry: article.year = entry['year'][:10] - if 'author' in entry: article.author = entry['author'][:1000] - if 'abstract' in entry: article.abstract = entry['abstract'][:4000] - if 'pages' in entry: article.pages = entry['pages'][:20] - if 'volume' in entry: article.volume = entry['volume'][:100] - if 'type' in entry: article.document_type = entry['type'][:100] - elif 'document_type' in entry: article.document_type = entry['document_type'][:100] - if 'doi' in entry: article.doi = entry['doi'][:50] - if 'link' in entry: article.url = entry['link'][:500] - elif 'url' in entry: article.url = entry['url'][:500] - if 'affiliation' in entry: article.affiliation = entry['affiliation'][:500] - if 'author_keywords' in entry: article.author_keywords = entry['author_keywords'][:500] - if 'keywords' in entry: article.keywords = entry['keywords'][:500] - elif 'keyword' in entry: article.keywords = entry['keyword'][:500] - if 'publisher' in entry: article.publisher = entry['publisher'][:100] - if 'issn' in entry: article.issn = entry['issn'][:50] - if 'language' in entry: article.language = entry['language'][:50] - if 'note' in entry: article.note = entry['note'][:500] + if "id" in entry: + article.bibtex_key = entry["id"][:100] + if "title" in entry: + article.title = entry["title"][:1000] + if "journal" in entry: + article.journal = entry["journal"][:1000] + if "year" in entry: + article.year = entry["year"][:10] + if "author" in entry: + article.author = entry["author"][:1000] + if "abstract" in entry: + article.abstract = entry["abstract"][:4000] + if "pages" in entry: + article.pages = entry["pages"][:20] + if "volume" in entry: + article.volume = entry["volume"][:100] + if "type" in entry: + article.document_type = entry["type"][:100] + elif "document_type" in entry: + article.document_type = entry["document_type"][:100] + if "doi" in entry: + article.doi = entry["doi"][:50] + if "link" in entry: + article.url = entry["link"][:500] + elif "url" in entry: + article.url = entry["url"][:500] + if "affiliation" in entry: + article.affiliation = entry["affiliation"][:500] + if "author_keywords" in entry: + article.author_keywords = entry["author_keywords"][:500] + if "keywords" in entry: + article.keywords = entry["keywords"][:500] + elif "keyword" in entry: + article.keywords = entry["keyword"][:500] + if "publisher" in entry: + article.publisher = entry["publisher"][:100] + if "issn" in entry: + article.issn = entry["issn"][:50] + if "language" in entry: + article.language = entry["language"][:50] + if "note" in entry: + article.note = entry["note"][:500] article.review = review article.source = source except: @@ -453,6 +612,7 @@ def bibtex_to_article_object(bib_database, review, source): articles.append(article) return articles + def _import_articles(request, source, articles): if any(articles): success = 0 @@ -465,45 +625,50 @@ def _import_articles(request, source, articles): except: error = error + 1 if success > 0: - messages.success(request, u'{0} articles successfully imported to {1}!'.format(success, source.name)) + messages.success(request, "{0} articles successfully imported to {1}!".format(success, source.name)) if error > 0: - messages.warning(request, u'{0} articles could not be imported because of invalid format or invalid utf-8 string.'.format(error)) + messages.warning( + request, + "{0} articles could not be imported because of invalid format or invalid utf-8 string.".format(error), + ) else: - messages.warning(request, u'The bibtex file had no valid entry!') + messages.warning(request, "The bibtex file had no valid entry!") + @author_required @login_required @require_POST def import_bibtex(request): - review_id = request.POST['review-id'] - source_id = request.POST['source-id'] + review_id = request.POST["review-id"] + source_id = request.POST["source-id"] review = Review.objects.get(pk=review_id) source = Source.objects.get(pk=source_id) - bibtex_file = request.FILES['bibtex'] + bibtex_file = request.FILES["bibtex"] ext = os.path.splitext(bibtex_file.name)[1] - valid_extensions = ['.bib', '.bibtex'] + valid_extensions = [".bib", ".bibtex"] - if ext in valid_extensions or bibtex_file.content_type == 'application/x-bibtex': + if ext in valid_extensions or bibtex_file.content_type == "application/x-bibtex": parser = BibTexParser() parser.customization = convert_to_unicode bib_database = bibtexparser.load(bibtex_file, parser=parser) articles = bibtex_to_article_object(bib_database, review, source) _import_articles(request, source, articles) else: - messages.error(request, u'Invalid file type. Only .bib or .bibtex files are accepted.') + messages.error(request, "Invalid file type. Only .bib or .bibtex files are accepted.") + + return redirect(r("import_studies", args=(review.author.username, review.name))) - return redirect(r('import_studies', args=(review.author.username, review.name))) @author_required @login_required @require_POST def import_bibtex_raw_content(request): - review_id = request.POST.get('review-id') - source_id = request.POST.get('source-id') - bibtex_file = request.POST.get('bibtex_file') + review_id = request.POST.get("review-id") + source_id = request.POST.get("source-id") + bibtex_file = request.POST.get("bibtex_file") review = Review.objects.get(pk=review_id) source = Source.objects.get(pk=source_id) @@ -514,41 +679,48 @@ def import_bibtex_raw_content(request): articles = bibtex_to_article_object(bib_database, review, source) _import_articles(request, source, articles) - return redirect(r('import_studies', args=(review.author.username, review.name))) + return redirect(r("import_studies", args=(review.author.username, review.name))) + @author_required @login_required def source_articles(request): - review_id = request.GET['review-id'] - source_id = request.GET['source-id'] + review_id = request.GET["review-id"] + source_id = request.GET["source-id"] review = Review.objects.get(pk=review_id) - if source_id != 'None': + if source_id != "None": articles = review.get_source_articles(source_id) source = Source.objects.get(pk=source_id) else: articles = review.get_source_articles() source = Source() - return render(request, 'conducting/partial_conducting_articles.html', {'review': review, 'source': source, 'articles': articles}) + return render( + request, + "conducting/partial_conducting_articles.html", + {"review": review, "source": source, "articles": articles}, + ) @author_required @login_required def article_details(request): - review_id = request.GET['review-id'] - article_id = request.GET['article-id'] + review_id = request.GET["review-id"] + article_id = request.GET["article-id"] review = get_object_or_404(Review, pk=review_id) article = get_object_or_404(Article, pk=article_id, review_id=review_id) - user = request.user mendeley_files = [] - if user.profile.mendeley_token: - mendeley_files = user.profile.get_mendeley_session().files.list().items - context = RequestContext(request, { 'review': review, 'article': article, 'mendeley_files': mendeley_files }) - return render_to_response('conducting/partial_conducting_article_details.html', context) -''' + return render( + request, + "conducting/partial_conducting_article_details.html", + {"review": review, "article": article, "mendeley_files": mendeley_files}, + ) + + +""" @author_required @login_required def articles_upload(request): @@ -577,16 +749,18 @@ def articles_upload(request): return HttpResponseBadRequest() else: return HttpResponseBadRequest() -''' +""" + + def build_article_table_row(article): - name = '' + name = "" if article.created_by: name = escape(article.created_by.profile.get_screen_name()) - date = '' + date = "" if article.created_at: - date = article.created_at.strftime('%d %b %Y %H:%M:%S') + date = article.created_at.strftime("%d %b %Y %H:%M:%S") - row = u''' + row = """ {2} {3} @@ -596,61 +770,63 @@ def build_article_table_row(article): {7} {8} {9} - '''.format(article.id, - article.status, - escape(article.bibtex_key), - escape(article.title), - escape(article.author), - escape(article.journal), - escape(article.year), - name, - date, - article.get_status_html()) + """.format( + article.id, + article.status, + escape(article.bibtex_key), + escape(article.title), + escape(article.author), + escape(article.journal), + escape(article.year), + name, + date, + article.get_status_html(), + ) return row @author_required @login_required def save_article_details(request): - if request.method == 'POST': + if request.method == "POST": try: - article_id = request.POST['article-id'] - review_id = request.POST['review-id'] + article_id = request.POST["article-id"] + review_id = request.POST["review-id"] - if article_id != 'None': + if article_id != "None": article = get_object_or_404(Article, pk=article_id, review_id=review_id) else: - source_id = request.POST['source-id'] + source_id = request.POST["source-id"] review = get_object_or_404(Review, pk=review_id) source = get_object_or_404(Source, pk=source_id) article = Article(review=review, source=source) - article.bibtex_key = request.POST['bibtex-key'][:100] - article.title = request.POST['title'][:1000] - article.author = request.POST['author'][:1000] - article.journal = request.POST['journal'][:1000] - article.year = request.POST['year'][:10] - article.pages = request.POST['pages'][:20] - article.volume = request.POST['volume'][:100] - article.author = request.POST['author'][:1000] - article.abstract = request.POST['abstract'][:4000] - article.document_type = request.POST['document-type'][:100] - article.doi = request.POST['doi'][:50] - article.url = request.POST['url'][:500] - article.affiliation = request.POST['affiliation'][:500] - article.author_keywords = request.POST['author_keywords'][:500] - article.keywords = request.POST['keywords'][:500] - article.publisher = request.POST['publisher'][:100] - article.issn = request.POST['issn'][:50] - article.language = request.POST['language'][:50] - article.note = request.POST['note'][:500] - - article.comments = request.POST['comments'][:4000] - status = request.POST['status'][:1] + article.bibtex_key = request.POST["bibtex-key"][:100] + article.title = request.POST["title"][:1000] + article.author = request.POST["author"][:1000] + article.journal = request.POST["journal"][:1000] + article.year = request.POST["year"][:10] + article.pages = request.POST["pages"][:20] + article.volume = request.POST["volume"][:100] + article.author = request.POST["author"][:1000] + article.abstract = request.POST["abstract"][:4000] + article.document_type = request.POST["document-type"][:100] + article.doi = request.POST["doi"][:50] + article.url = request.POST["url"][:500] + article.affiliation = request.POST["affiliation"][:500] + article.author_keywords = request.POST["author_keywords"][:500] + article.keywords = request.POST["keywords"][:500] + article.publisher = request.POST["publisher"][:100] + article.issn = request.POST["issn"][:50] + article.language = request.POST["language"][:50] + article.note = request.POST["note"][:500] + + article.comments = request.POST["comments"][:4000] + status = request.POST["status"][:1] if status in (Article.UNCLASSIFIED, Article.REJECTED, Article.ACCEPTED, Article.DUPLICATED): article.status = status - selection_criteria_id = request.POST.get('selection_criteria') + selection_criteria_id = request.POST.get("selection_criteria") try: selection_criteria = SelectionCriteria.objects.get(pk=selection_criteria_id) article.selection_criteria = selection_criteria @@ -666,13 +842,14 @@ def save_article_details(request): else: return HttpResponseBadRequest() + @author_required @login_required def save_quality_assessment(request): try: - article_id = request.POST['article-id'] - question_id = request.POST['question-id'] - answer_id = request.POST['answer-id'] + article_id = request.POST["article-id"] + question_id = request.POST["question-id"] + answer_id = request.POST["answer-id"] article = Article.objects.get(pk=article_id) question = QualityQuestion.objects.get(pk=question_id) @@ -691,12 +868,15 @@ def save_quality_assessment(request): @login_required def quality_assessment_detailed(request): try: - review_id = request.GET['review-id'] + review_id = request.GET["review-id"] review = Review.objects.get(pk=review_id) - order = request.session.get('quality_assessment_order', 'title') + order = request.session.get("quality_assessment_order", "title") quality_assessment_table = build_quality_assessment_table(request, review, order) - context = RequestContext(request, {'review': review, 'quality_assessment_table': quality_assessment_table, 'order': order}) - return render_to_response('conducting/partial_conducting_quality_assessment_detailed.html', context) + return render( + request, + "conducting/partial_conducting_quality_assessment_detailed.html", + {"review": review, "quality_assessment_table": quality_assessment_table, "order": order}, + ) except: return HttpResponseBadRequest() @@ -705,10 +885,9 @@ def quality_assessment_detailed(request): @login_required def quality_assessment_summary(request): try: - review_id = request.GET['review-id'] + review_id = request.GET["review-id"] review = Review.objects.get(pk=review_id) - context = RequestContext(request, {'review': review, }) - return render_to_response('conducting/partial_conducting_quality_assessment_summary.html', context) + return render(request, "conducting/partial_conducting_quality_assessment_summary.html", {"review": review}) except: return HttpResponseBadRequest() @@ -717,8 +896,8 @@ def quality_assessment_summary(request): @login_required def multiple_articles_action_remove(request): try: - article_ids = request.POST['article_ids'] - article_ids_list = article_ids.split('|') + article_ids = request.POST["article_ids"] + article_ids_list = article_ids.split("|") if article_ids_list: Article.objects.filter(pk__in=article_ids_list).delete() return HttpResponse() @@ -730,8 +909,8 @@ def multiple_articles_action_remove(request): @login_required def multiple_articles_action_accept(request): try: - article_ids = request.POST['article_ids'] - article_ids_list = article_ids.split('|') + article_ids = request.POST["article_ids"] + article_ids_list = article_ids.split("|") if article_ids_list: Article.objects.filter(pk__in=article_ids_list).update(status=Article.ACCEPTED) return HttpResponse() @@ -743,8 +922,8 @@ def multiple_articles_action_accept(request): @login_required def multiple_articles_action_reject(request): try: - article_ids = request.POST['article_ids'] - article_ids_list = article_ids.split('|') + article_ids = request.POST["article_ids"] + article_ids_list = article_ids.split("|") if article_ids_list: Article.objects.filter(pk__in=article_ids_list).update(status=Article.REJECTED) return HttpResponse() @@ -756,8 +935,8 @@ def multiple_articles_action_reject(request): @login_required def multiple_articles_action_duplicated(request): try: - article_ids = request.POST['article_ids'] - article_ids_list = article_ids.split('|') + article_ids = request.POST["article_ids"] + article_ids_list = article_ids.split("|") if article_ids_list: Article.objects.filter(pk__in=article_ids_list).update(status=Article.DUPLICATED) return HttpResponse() @@ -769,9 +948,9 @@ def multiple_articles_action_duplicated(request): @login_required def save_data_extraction(request): try: - article_id = request.POST['article-id'] - field_id = request.POST['field-id'] - value = request.POST['value'] + article_id = request.POST["article-id"] + field_id = request.POST["field-id"] + value = request.POST["value"] article = Article.objects.get(pk=article_id) field = DataExtractionField.objects.get(pk=field_id) @@ -782,39 +961,40 @@ def save_data_extraction(request): return HttpResponse() else: return HttpResponseBadRequest() - except Exception, e: + except Exception as e: return HttpResponseBadRequest(e) + @author_required @login_required def save_data_extraction_status(request): try: - article_id = request.POST.get('article-id') - action = request.POST.get('action') - is_finished = (action == 'mark_as_done') + article_id = request.POST.get("article-id") + action = request.POST.get("action") + is_finished = action == "mark_as_done" article = Article.objects.get(pk=article_id) article.finished_data_extraction = is_finished article.save() return HttpResponse() - except Exception, e: + except Exception as e: return HttpResponseBadRequest(e) + @author_required @login_required def find_duplicates(request): - review_id = request.GET['review-id'] + review_id = request.GET["review-id"] review = Review.objects.get(pk=review_id) duplicates = review.get_duplicate_articles() - context = RequestContext(request, {'duplicates': duplicates}) - return render_to_response('conducting/partial_conducting_find_duplicates.html', context) + return render(request, "conducting/partial_conducting_find_duplicates.html", {"duplicates": duplicates}) @author_required @login_required def resolve_duplicated(request): try: - article_id = request.POST['article-id'] + article_id = request.POST["article-id"] article = Article.objects.get(pk=article_id) if article.review.is_author_or_coauthor(request.user): article.status = Article.DUPLICATED @@ -822,7 +1002,7 @@ def resolve_duplicated(request): return HttpResponse() else: return HttpResponseBadRequest() - except Exception, e: + except Exception: return HttpResponseBadRequest() @@ -831,7 +1011,7 @@ def resolve_duplicated(request): def resolve_all(request): try: article_id_list = [] - review_id = request.POST['review-id'] + review_id = request.POST["review-id"] review = Review.objects.get(pk=review_id) duplicates = review.get_duplicate_articles() for duplicate in duplicates: @@ -839,61 +1019,63 @@ def resolve_all(request): duplicate[i].status = Article.DUPLICATED duplicate[i].save() article_id_list.append(str(duplicate[i].id)) - return HttpResponse(','.join(article_id_list)) - except Exception, e: + return HttpResponse(",".join(article_id_list)) + except Exception: return HttpResponseBadRequest() @author_required @login_required def new_article(request): - review_id = request.GET['review-id'] - source_id = request.GET['source-id'] - + review_id = request.GET["review-id"] + source_id = request.GET["source-id"] review = Review.objects.get(pk=review_id) source = Source.objects.get(pk=source_id) - article = Article(review=review, source=source) + return render(request, "conducting/partial_conducting_article_details.html", {"article": article}) - context = RequestContext(request, {'article': article}) - return render_to_response('conducting/partial_conducting_article_details.html', context) @author_required @login_required def data_analysis(request, username, review_name): review = get_object_or_404(Review, name=review_name, author__username__iexact=username) - return render(request, 'conducting/conducting_data_analysis.html', { 'review': review }) + return render(request, "conducting/conducting_data_analysis.html", {"review": review}) + def articles_selection_chart(request): - review_id = request.GET['review-id'] + review_id = request.GET["review-id"] review = Review.objects.get(pk=review_id) selected_articles = review.get_accepted_articles() articles = [] for source in review.sources.all(): count = review.get_source_articles(source.id).count() accepted_count = selected_articles.filter(source__id=source.id).count() - articles.append(source.name + ':' + str(count) + ':' + str(accepted_count)) - return HttpResponse(','.join(articles)) + articles.append(source.name + ":" + str(count) + ":" + str(accepted_count)) + return HttpResponse(",".join(articles)) + def articles_per_year(request): - review_id = request.GET['review-id'] + review_id = request.GET["review-id"] review = get_object_or_404(Review, pk=review_id) - final_articles = review.get_final_selection_articles().values('year').annotate(count=Count('year')).order_by('-year') + final_articles = ( + review.get_final_selection_articles().values("year").annotate(count=Count("year")).order_by("-year") + ) articles = [] for article in final_articles: try: - articles.append(article['year'] + ':' + str(article['count'])) + articles.append(article["year"] + ":" + str(article["count"])) except Exception: pass - return HttpResponse(','.join(articles)) + return HttpResponse(",".join(articles)) + @author_required @login_required @require_POST def add_source_string(request): - review_id = request.POST.get('review-id') + review_id = request.POST.get("review-id") review = get_object_or_404(Review, pk=review_id) - source_ids = request.POST.getlist('source') + source_ids = request.POST.getlist("source") for source_id in source_ids: try: source = Source.objects.get(pk=source_id) @@ -904,58 +1086,58 @@ def add_source_string(request): except Source.DoesNotExist: pass review.save() - messages.success(request, 'Sources search string successfully added to the review!') - return redirect(r('search_studies', args=(review.author.username, review.name))) + messages.success(request, "Sources search string successfully added to the review!") + return redirect(r("search_studies", args=(review.author.username, review.name))) @author_required @login_required @require_POST def export_results(request): - review_id = request.POST.get('review-id') + review_id = request.POST.get("review-id") review = get_object_or_404(Review, pk=review_id) articles = review.get_source_articles() - response = HttpResponse(content_type='application/ms-excel') - response['Content-Disposition'] = 'attachment; filename=articles.xls' - wb = xlwt.Workbook(encoding='utf-8') + response = HttpResponse(content_type="application/ms-excel") + response["Content-Disposition"] = "attachment; filename=articles.xls" + wb = xlwt.Workbook(encoding="utf-8") ws = wb.add_sheet("Articles") row_num = 0 columns = [ - ('bibtex_key', 2000), - ('title', 2000), - ('author', 2000), - ('journal', 2000), - ('year', 2000), - ('source', 2000), - ('pages', 2000), - ('volume', 2000), - ('abstract', 2000), - ('document_type', 2000), - ('doi', 2000), - ('url', 2000), - ('affiliation', 2000), - ('author_keywords', 2000), - ('keywords', 2000), - ('publisher', 2000), - ('issn', 2000), - ('language', 2000), - ('note', 2000), - ('selection_criteria', 2000), - ('created_at', 2000), - ('updated_at', 2000), - ('created_by', 2000), - ('updated_by', 2000), - ('status', 2000), - ('comments', 2000), + ("bibtex_key", 2000), + ("title", 2000), + ("author", 2000), + ("journal", 2000), + ("year", 2000), + ("source", 2000), + ("pages", 2000), + ("volume", 2000), + ("abstract", 2000), + ("document_type", 2000), + ("doi", 2000), + ("url", 2000), + ("affiliation", 2000), + ("author_keywords", 2000), + ("keywords", 2000), + ("publisher", 2000), + ("issn", 2000), + ("language", 2000), + ("note", 2000), + ("selection_criteria", 2000), + ("created_at", 2000), + ("updated_at", 2000), + ("created_by", 2000), + ("updated_by", 2000), + ("status", 2000), + ("comments", 2000), ] font_style = xlwt.XFStyle() font_style.font.bold = True - for col_num in xrange(len(columns)): + for col_num in range(len(columns)): ws.write(row_num, col_num, columns[col_num][0], font_style) # set column width ws.col(col_num).width = columns[col_num][1] @@ -971,7 +1153,7 @@ def export_results(request): article.author, article.journal, article.year, - (article.source.name if article.source else ''), + (article.source.name if article.source else ""), article.pages, article.volume, article.abstract, @@ -985,18 +1167,18 @@ def export_results(request): article.issn, article.language, article.note, - (article.selection_criteria.description if article.selection_criteria else '' ), + (article.selection_criteria.description if article.selection_criteria else ""), article.created_at.replace(tzinfo=None), article.updated_at.replace(tzinfo=None), - (article.created_by.username if article.created_by else ''), - (article.updated_by.username if article.updated_by else ''), + (article.created_by.username if article.created_by else ""), + (article.updated_by.username if article.updated_by else ""), article.get_status_display(), article.comments, ] - for col_num in xrange(len(row)): + for col_num in range(len(row)): ws.write(row_num, col_num, row[col_num], font_style) - except Exception, e: - ws.write(row_num, 0, u'Error: {0}'.format(e.message), font_style) + except Exception as e: + ws.write(row_num, 0, "Error: {0}".format(e), font_style) row_num += 1 @@ -1008,21 +1190,21 @@ def export_results(request): @login_required @require_POST def export_data_extraction(request): - review_id = request.POST.get('review-id') + review_id = request.POST.get("review-id") review = get_object_or_404(Review, pk=review_id) selected_studies = review.get_final_selection_articles() data_extraction_fields = review.get_data_extraction_fields() - response = HttpResponse(content_type='application/ms-excel') - response['Content-Disposition'] = 'attachment; filename=data_extraction.xls' - wb = xlwt.Workbook(encoding='utf-8') + response = HttpResponse(content_type="application/ms-excel") + response["Content-Disposition"] = "attachment; filename=data_extraction.xls" + wb = xlwt.Workbook(encoding="utf-8") ws = wb.add_sheet("Articles") row_num = 0 columns = [ - ('article', 2000), + ("article", 2000), ] for field in data_extraction_fields: columns.append((field.description, 2000)) @@ -1030,7 +1212,7 @@ def export_data_extraction(request): font_style = xlwt.XFStyle() font_style.font.bold = True - for col_num in xrange(len(columns)): + for col_num in range(len(columns)): ws.write(row_num, col_num, columns[col_num][0], font_style) # set column width ws.col(col_num).width = columns[col_num][1] @@ -1040,9 +1222,10 @@ def export_data_extraction(request): for article in selected_studies: try: - row = [article.title,] + row = [ + article.title, + ] for field in data_extraction_fields: - field_value = None try: de = DataExtraction.objects.get(article=article, field=field) if de.field.field_type == DataExtractionField.DATE_FIELD: @@ -1052,19 +1235,19 @@ def export_data_extraction(request): if select_one_field: field_value = select_one_field.value else: - field_value = '' + field_value = "" elif de.field.field_type == DataExtractionField.SELECT_MANY_FIELD: - field_value = ', '.join(de._get_select_many_value().values_list('value', flat=True)) + field_value = ", ".join(de._get_select_many_value().values_list("value", flat=True)) else: field_value = de.value except DataExtraction.DoesNotExist: - field_value = '' + field_value = "" row.append(field_value) - for col_num in xrange(len(row)): + for col_num in range(len(row)): ws.write(row_num, col_num, row[col_num], font_style) - except Exception, e: - ws.write(row_num, 0, u'Error: {0}'.format(e.message), font_style) + except Exception as e: + ws.write(row_num, 0, "Error: {0}".format(e), font_style) row_num += 1 diff --git a/parsifal/reviews/decorators.py b/parsifal/reviews/decorators.py index 448d9ba6..4ec52fdf 100644 --- a/parsifal/reviews/decorators.py +++ b/parsifal/reviews/decorators.py @@ -1,16 +1,13 @@ -from functools import wraps - -from django.core.urlresolvers import reverse -from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest, Http404 +from django.http import Http404, HttpResponseBadRequest, HttpResponseForbidden from parsifal.reviews.models import Review def main_author_required(f): def wrap(request, *args, **kwargs): - if 'review_name' in kwargs and 'username' in kwargs: + if "review_name" in kwargs and "username" in kwargs: try: - review = Review.objects.get(name=kwargs['review_name'], author__username__iexact=kwargs['username']) + review = Review.objects.get(name=kwargs["review_name"], author__username__iexact=kwargs["username"]) if review.author.id == request.user.id: return f(request, *args, **kwargs) else: @@ -19,10 +16,10 @@ def wrap(request, *args, **kwargs): raise Http404 else: try: - review_id = request.POST['review-id'] + review_id = request.POST["review-id"] except: try: - review_id = request.GET['review-id'] + review_id = request.GET["review-id"] except: return HttpResponseBadRequest() @@ -31,15 +28,17 @@ def wrap(request, *args, **kwargs): return f(request, *args, **kwargs) else: return HttpResponseForbidden() - wrap.__doc__=f.__doc__ - wrap.__name__=f.__name__ + + wrap.__doc__ = f.__doc__ + wrap.__name__ = f.__name__ return wrap + def author_required(f): def wrap(request, *args, **kwargs): - if 'review_name' in kwargs and 'username' in kwargs: + if "review_name" in kwargs and "username" in kwargs: try: - review = Review.objects.get(name=kwargs['review_name'], author__username__iexact=kwargs['username']) + review = Review.objects.get(name=kwargs["review_name"], author__username__iexact=kwargs["username"]) if review.is_author_or_coauthor(request.user): return f(request, *args, **kwargs) else: @@ -48,10 +47,10 @@ def wrap(request, *args, **kwargs): raise Http404 else: try: - review_id = request.POST['review-id'] + review_id = request.POST["review-id"] except: try: - review_id = request.GET['review-id'] + review_id = request.GET["review-id"] except: return HttpResponseBadRequest() review = Review.objects.get(pk=review_id) @@ -59,6 +58,7 @@ def wrap(request, *args, **kwargs): return f(request, *args, **kwargs) else: return HttpResponseForbidden() - wrap.__doc__=f.__doc__ - wrap.__name__=f.__name__ - return wrap \ No newline at end of file + + wrap.__doc__ = f.__doc__ + wrap.__name__ = f.__name__ + return wrap diff --git a/parsifal/reviews/forms.py b/parsifal/reviews/forms.py index 2d53bb6d..889a87e5 100644 --- a/parsifal/reviews/forms.py +++ b/parsifal/reviews/forms.py @@ -5,28 +5,38 @@ class CreateReviewForm(forms.ModelForm): title = forms.CharField( - widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Systematic literature review\'s title' }), - max_length=255) + widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "Systematic literature review's title"}), + max_length=255, + ) description = forms.CharField( - widget=forms.Textarea(attrs={ 'class': 'form-control', 'placeholder': 'Give a brief description of your systematic literature review' }), - max_length=500, - help_text='Try to keep it short, max 500 characters :)', - required=False) + widget=forms.Textarea( + attrs={ + "class": "form-control", + "placeholder": "Give a brief description of your systematic literature review", + } + ), + max_length=500, + help_text="Try to keep it short, max 500 characters :)", + required=False, + ) class Meta: model = Review - fields = ['title', 'description',] + fields = [ + "title", + "description", + ] class ReviewForm(forms.ModelForm): - title = forms.CharField( - widget=forms.TextInput(attrs={ 'class': 'form-control' }), - max_length=255) + title = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}), max_length=255) description = forms.CharField( - widget=forms.Textarea(attrs={ 'class': 'form-control expanding', 'rows': '4' }), - max_length=500, - required=False) + widget=forms.Textarea(attrs={"class": "form-control expanding", "rows": "4"}), max_length=500, required=False + ) class Meta: model = Review - fields = ['title', 'description',] + fields = [ + "title", + "description", + ] diff --git a/parsifal/reviews/models.py b/parsifal/reviews/models.py index c9d79d58..a6c145f1 100644 --- a/parsifal/reviews/models.py +++ b/parsifal/reviews/models.py @@ -1,13 +1,12 @@ -# coding: utf-8 - import datetime -from django.utils import timezone -from django.utils.html import escape +from django.contrib.auth.models import User from django.db import models from django.db.models import Sum -from django.contrib.auth.models import User -from django.template.defaultfilters import slugify +from django.urls import reverse +from django.utils.html import escape +from django.utils.text import slugify +from django.utils.translation import gettext_lazy as _ from parsifal.library.models import Document @@ -18,38 +17,38 @@ class Source(models.Model): is_default = models.BooleanField(default=False) class Meta: - verbose_name = u'Source' - verbose_name_plural = u'Sources' - ordering = ('name',) + verbose_name = _("Source") + verbose_name_plural = _("Sources") + ordering = ("name",) - def __unicode__(self): + def __str__(self): return self.name def set_url(self, value): - if 'http://' not in value and 'https://' not in value and len(value) > 0: - self.url = u'http://{0}'.format(value) + if "http://" not in value and "https://" not in value and len(value) > 0: + self.url = "http://{0}".format(value) else: self.url = value class Review(models.Model): - UNPUBLISHED = u'U' - PUBLISHED = u'P' + UNPUBLISHED = "U" + PUBLISHED = "P" REVIEW_STATUS = ( - (UNPUBLISHED, u'Unpublished'), - (PUBLISHED, u'Published'), - ) + (UNPUBLISHED, "Unpublished"), + (PUBLISHED, "Published"), + ) name = models.SlugField(max_length=255) title = models.CharField(max_length=255) description = models.CharField(max_length=500, null=True, blank=True) - author = models.ForeignKey(User) + author = models.ForeignKey(User, on_delete=models.CASCADE) create_date = models.DateTimeField(auto_now_add=True) last_update = models.DateTimeField(auto_now=True) objective = models.TextField(max_length=1000) sources = models.ManyToManyField(Source) status = models.CharField(max_length=1, choices=REVIEW_STATUS, default=UNPUBLISHED) - co_authors = models.ManyToManyField(User, related_name='co_authors') + co_authors = models.ManyToManyField(User, related_name="co_authors") quality_assessment_cutoff_score = models.FloatField(default=0.0) population = models.CharField(max_length=200, blank=True) intervention = models.CharField(max_length=200, blank=True) @@ -58,26 +57,25 @@ class Review(models.Model): context = models.CharField(max_length=200, blank=True) class Meta: - verbose_name = u'Review' - verbose_name_plural = u'Reviews' - unique_together = (('name', 'author'),) + verbose_name = "Review" + verbose_name_plural = "Reviews" + unique_together = (("name", "author"),) - def __unicode__(self): + def __str__(self): return self.name def get_absolute_url(self): - from django.core.urlresolvers import reverse - return reverse('review', args=(str(self.author.username), str(self.name))) + return reverse("review", args=(str(self.author.username), str(self.name))) def get_questions(self): questions = Question.objects.filter(review__id=self.id) return questions def get_inclusion_criterias(self): - return SelectionCriteria.objects.filter(review__id=self.id, criteria_type='I') + return SelectionCriteria.objects.filter(review__id=self.id, criteria_type="I") def get_exclusion_criterias(self): - return SelectionCriteria.objects.filter(review__id=self.id, criteria_type='E') + return SelectionCriteria.objects.filter(review__id=self.id, criteria_type="E") def get_keywords(self): return Keyword.objects.filter(review__id=self.id, synonym_of=None) @@ -98,32 +96,30 @@ def get_generic_search_string(self): return search_string def get_latest_source_search_strings(self): - return self.searchsession_set.exclude(source=None).order_by('source__name') + return self.searchsession_set.exclude(source=None).order_by("source__name") def get_source_articles(self, source_id=None): - queryset = Article.objects \ - .filter(review__id=self.id) \ - .select_related('created_by__profile') + queryset = Article.objects.filter(review__id=self.id).select_related("created_by__profile") if source_id is not None: queryset = queryset.filter(source__id=source_id) return queryset def get_duplicate_articles(self): - articles = Article.objects.filter(review__id=self.id).exclude(status=Article.DUPLICATED).order_by('title') + articles = Article.objects.filter(review__id=self.id).exclude(status=Article.DUPLICATED).order_by("title") grouped_articles = dict() for article in articles: slug = slugify(article.title) if slug not in grouped_articles.keys(): - grouped_articles[slug] = { 'size': 0, 'articles': list() } - grouped_articles[slug]['size'] += 1 - grouped_articles[slug]['articles'].append(article) + grouped_articles[slug] = {"size": 0, "articles": list()} + grouped_articles[slug]["size"] += 1 + grouped_articles[slug]["articles"].append(article) duplicates = list() - for slug, data in grouped_articles.iteritems(): - if data['size'] > 1: - duplicates.append(data['articles']) + for slug, data in grouped_articles.items(): + if data["size"] > 1: + duplicates.append(data["articles"]) return duplicates @@ -158,7 +154,7 @@ def get_quality_assessment_answers(self): def calculate_quality_assessment_max_score(self): try: questions_count = QualityQuestion.objects.filter(review__id=self.id).count() - higher_weight_answer = QualityAnswer.objects.filter(review__id=self.id).order_by('-weight')[:1].get() + higher_weight_answer = QualityAnswer.objects.filter(review__id=self.id).order_by("-weight")[:1].get() if questions_count and higher_weight_answer: return questions_count * higher_weight_answer.weight else: @@ -168,17 +164,17 @@ def calculate_quality_assessment_max_score(self): class Question(models.Model): - review = models.ForeignKey(Review, related_name='research_questions') + review = models.ForeignKey(Review, on_delete=models.CASCADE, related_name="research_questions") question = models.CharField(max_length=500) - parent_question = models.ForeignKey('self', null=True, related_name='+') + parent_question = models.ForeignKey("self", on_delete=models.CASCADE, null=True, related_name="+") order = models.IntegerField(default=0) class Meta: - verbose_name = u'Question' - verbose_name_plural = u'Questions' - ordering = ('order',) + verbose_name = "Question" + verbose_name_plural = "Questions" + ordering = ("order",) - def __unicode__(self): + def __str__(self): return self.question def get_child_questions(self): @@ -186,107 +182,108 @@ def get_child_questions(self): class SelectionCriteria(models.Model): - INCLUSION = u'I' - EXCLUSION = u'E' + INCLUSION = "I" + EXCLUSION = "E" SELECTION_TYPES = ( - (INCLUSION, u'Inclusion'), - (EXCLUSION, u'Exclusion'), - ) + (INCLUSION, "Inclusion"), + (EXCLUSION, "Exclusion"), + ) - review = models.ForeignKey(Review) + review = models.ForeignKey(Review, on_delete=models.CASCADE) criteria_type = models.CharField(max_length=1, choices=SELECTION_TYPES) description = models.CharField(max_length=200) class Meta: - verbose_name = u'Selection Criteria' - verbose_name_plural = u'Selection Criterias' - ordering = ('description',) + verbose_name = "Selection Criteria" + verbose_name_plural = "Selection Criterias" + ordering = ("description",) - def __unicode__(self): + def __str__(self): return self.description def save(self, *args, **kwargs): self.description = self.description[:200] - super(SelectionCriteria, self).save(*args, **kwargs) + super().save(*args, **kwargs) class SearchSession(models.Model): - review = models.ForeignKey(Review) - source = models.ForeignKey(Source, null=True) + review = models.ForeignKey(Review, on_delete=models.CASCADE) + source = models.ForeignKey(Source, on_delete=models.CASCADE, null=True) search_string = models.TextField(max_length=10000) version = models.IntegerField(default=1) - def __unicode__(self): + def __str__(self): return self.search_string def search_string_as_html(self): escaped_string = escape(self.search_string) - html = escaped_string.replace(' OR ', ' OR ').replace(' AND ', ' AND ') + html = escaped_string.replace(" OR ", " OR ").replace(" AND ", " AND ") return html def search_result_file_upload_to(instance, filename): - return u'reviews/{0}/search_result/'.format(instance.review.pk) + return "reviews/{0}/search_result/".format(instance.review.pk) + class SearchResult(models.Model): - review = models.ForeignKey(Review) - source = models.ForeignKey(Source) - search_session = models.ForeignKey(SearchSession, null=True) + review = models.ForeignKey(Review, on_delete=models.CASCADE) + source = models.ForeignKey(Source, on_delete=models.CASCADE) + search_session = models.ForeignKey(SearchSession, on_delete=models.CASCADE, null=True) imported_file = models.FileField(upload_to=search_result_file_upload_to, null=True) documents = models.ManyToManyField(Document) class StudySelection(models.Model): - review = models.ForeignKey(Review) - user = models.ForeignKey(User, null=True) + review = models.ForeignKey(Review, on_delete=models.CASCADE) + user = models.ForeignKey(User, on_delete=models.CASCADE, null=True) has_finished = models.BooleanField(default=False) - def __unicode__(self): + def __str__(self): if self.user: - selection = u'{0}\'s Selection'.format(self.user.username) + selection = "{0}'s Selection".format(self.user.username) else: - selection = u'Final Selection' - return u'{0} ({1})'.format(selection, self.review.title) + selection = "Final Selection" + return "{0} ({1})".format(selection, self.review.title) class Study(models.Model): - UNCLASSIFIED = u'U' - REJECTED = u'R' - ACCEPTED = u'A' - DUPLICATED = u'D' + UNCLASSIFIED = "U" + REJECTED = "R" + ACCEPTED = "A" + DUPLICATED = "D" STUDY_STATUS = ( - (UNCLASSIFIED, u'Unclassified'), - (REJECTED, u'Rejected'), - (ACCEPTED, u'Accepted'), - (DUPLICATED, u'Duplicated'), - ) - study_selection = models.ForeignKey(StudySelection, related_name=u'studies') - document = models.ForeignKey(Document) - source = models.ForeignKey(Source, null=True) + (UNCLASSIFIED, "Unclassified"), + (REJECTED, "Rejected"), + (ACCEPTED, "Accepted"), + (DUPLICATED, "Duplicated"), + ) + study_selection = models.ForeignKey(StudySelection, on_delete=models.CASCADE, related_name="studies") + document = models.ForeignKey(Document, on_delete=models.CASCADE) + source = models.ForeignKey(Source, on_delete=models.CASCADE, null=True) status = models.CharField(max_length=1, choices=STUDY_STATUS, default=UNCLASSIFIED) updated_at = models.DateTimeField(auto_now=True) comments = models.TextField(max_length=2000, blank=True, null=True) class Article(models.Model): - UNCLASSIFIED = u'U' - REJECTED = u'R' - ACCEPTED = u'A' - DUPLICATED = u'D' + UNCLASSIFIED = "U" + REJECTED = "R" + ACCEPTED = "A" + DUPLICATED = "D" ARTICLE_STATUS = ( - (UNCLASSIFIED, u'Unclassified'), - (REJECTED, u'Rejected'), - (ACCEPTED, u'Accepted'), - (DUPLICATED, u'Duplicated'), - ) + (UNCLASSIFIED, "Unclassified"), + (REJECTED, "Rejected"), + (ACCEPTED, "Accepted"), + (DUPLICATED, "Duplicated"), + ) - review = models.ForeignKey(Review) + review = models.ForeignKey(Review, on_delete=models.CASCADE) bibtex_key = models.CharField(max_length=100) title = models.CharField(max_length=1000, null=True, blank=True) author = models.CharField(max_length=1000, null=True, blank=True) journal = models.CharField(max_length=1000, null=True, blank=True) year = models.CharField(max_length=10, null=True, blank=True) - source = models.ForeignKey(Source, null=True) + source = models.ForeignKey(Source, on_delete=models.CASCADE, null=True) pages = models.CharField(max_length=20, null=True, blank=True) volume = models.CharField(max_length=100, null=True, blank=True) abstract = models.TextField(max_length=4000, null=True, blank=True) @@ -306,135 +303,140 @@ class Article(models.Model): selection_criteria = models.ForeignKey(SelectionCriteria, null=True, blank=True, on_delete=models.SET_NULL) created_at = models.DateTimeField(auto_now_add=True, blank=True, null=True) updated_at = models.DateTimeField(auto_now=True, blank=True, null=True) - created_by = models.ForeignKey(User, null=True, blank=True, related_name='articles_created', on_delete=models.SET_NULL) - updated_by = models.ForeignKey(User, null=True, blank=True, related_name='articles_updated', on_delete=models.SET_NULL) + created_by = models.ForeignKey( + User, null=True, blank=True, related_name="articles_created", on_delete=models.SET_NULL + ) + updated_by = models.ForeignKey( + User, null=True, blank=True, related_name="articles_updated", on_delete=models.SET_NULL + ) class Meta: - verbose_name = 'Article' - verbose_name_plural = 'Articles' + verbose_name = "Article" + verbose_name_plural = "Articles" - def __unicode__(self): + def __str__(self): return self.title def get_score(self): - score = QualityAssessment.objects.filter(article__id=self.id).aggregate(Sum('answer__weight')) - if score['answer__weight__sum'] == None: + score = QualityAssessment.objects.filter(article__id=self.id).aggregate(Sum("answer__weight")) + if score["answer__weight__sum"] is None: return 0.0 - return score['answer__weight__sum'] + return score["answer__weight__sum"] def get_quality_assesment(self): quality_assessments = QualityAssessment.objects.filter(article__id=self.id) return quality_assessments def get_status_html(self): - label = { Article.UNCLASSIFIED: 'default', Article.REJECTED: 'danger', Article.ACCEPTED: 'success', Article.DUPLICATED: 'warning' } - return u'{1}'.format(label[self.status], self.get_status_display()) + label = { + Article.UNCLASSIFIED: "default", + Article.REJECTED: "danger", + Article.ACCEPTED: "success", + Article.DUPLICATED: "warning", + } + return '{1}'.format(label[self.status], self.get_status_display()) class Keyword(models.Model): - POPULATION = u'P' - INTERVENTION = u'I' - COMPARISON = u'C' - OUTCOME = u'O' + POPULATION = "P" + INTERVENTION = "I" + COMPARISON = "C" + OUTCOME = "O" RELATED_TO = ( - (POPULATION, u'Population'), - (INTERVENTION, u'Intervention'), - (COMPARISON, u'Comparison'), - (OUTCOME, u'Outcome'), - ) + (POPULATION, "Population"), + (INTERVENTION, "Intervention"), + (COMPARISON, "Comparison"), + (OUTCOME, "Outcome"), + ) - review = models.ForeignKey(Review, related_name='keywords') + review = models.ForeignKey(Review, on_delete=models.CASCADE, related_name="keywords") description = models.CharField(max_length=200) - synonym_of = models.ForeignKey('self', null=True, related_name='synonyms') + synonym_of = models.ForeignKey("self", on_delete=models.CASCADE, null=True, related_name="synonyms") related_to = models.CharField(max_length=1, choices=RELATED_TO, blank=True) class Meta: - verbose_name = u'Keyword' - verbose_name_plural = u'Keywords' - ordering = ('description',) + verbose_name = "Keyword" + verbose_name_plural = "Keywords" + ordering = ("description",) - def __unicode__(self): + def __str__(self): return self.description def save(self, *args, **kwargs): self.description = self.description[:200] - super(Keyword, self).save(*args, **kwargs) + super().save(*args, **kwargs) def get_synonyms(self): return Keyword.objects.filter(review__id=self.review.id, synonym_of__id=self.id) class QualityAnswer(models.Model): - SUGGESTED_ANSWERS = ( - ('Yes', 1.0), - ('Partially', 0.5), - ('No', 0.0) - ) + SUGGESTED_ANSWERS = (("Yes", 1.0), ("Partially", 0.5), ("No", 0.0)) - review = models.ForeignKey(Review) + review = models.ForeignKey(Review, on_delete=models.CASCADE) description = models.CharField(max_length=255) weight = models.FloatField() class Meta: - verbose_name = 'Quality Assessment Answer' - verbose_name_plural = 'Quality Assessment Answers' - ordering = ('-weight',) + verbose_name = "Quality Assessment Answer" + verbose_name_plural = "Quality Assessment Answers" + ordering = ("-weight",) - def __unicode__(self): + def __str__(self): return self.description class QualityQuestion(models.Model): - review = models.ForeignKey(Review) + review = models.ForeignKey(Review, on_delete=models.CASCADE) description = models.CharField(max_length=255) order = models.IntegerField(default=0) class Meta: - verbose_name = 'Quality Assessment Question' - verbose_name_plural = 'Quality Assessment Questions' - ordering = ('order',) + verbose_name = "Quality Assessment Question" + verbose_name_plural = "Quality Assessment Questions" + ordering = ("order",) - def __unicode__(self): + def __str__(self): return self.description class QualityAssessment(models.Model): - user = models.ForeignKey(User, null=True) - article = models.ForeignKey(Article) - question = models.ForeignKey(QualityQuestion) - answer = models.ForeignKey(QualityAnswer, null=True) + user = models.ForeignKey(User, on_delete=models.CASCADE, null=True) + article = models.ForeignKey(Article, on_delete=models.CASCADE) + question = models.ForeignKey(QualityQuestion, on_delete=models.CASCADE) + answer = models.ForeignKey(QualityAnswer, on_delete=models.CASCADE, null=True) - def __unicode__(self): - return str(self.article.id) + ' ' + str(self.question.id) + def __str__(self): + return str(self.article.id) + " " + str(self.question.id) class DataExtractionField(models.Model): - BOOLEAN_FIELD = 'B' - STRING_FIELD = 'S' - FLOAT_FIELD = 'F' - INTEGER_FIELD = 'I' - DATE_FIELD = 'D' - SELECT_ONE_FIELD = 'O' - SELECT_MANY_FIELD = 'M' + BOOLEAN_FIELD = "B" + STRING_FIELD = "S" + FLOAT_FIELD = "F" + INTEGER_FIELD = "I" + DATE_FIELD = "D" + SELECT_ONE_FIELD = "O" + SELECT_MANY_FIELD = "M" FIELD_TYPES = ( - (BOOLEAN_FIELD, 'Boolean Field'), - (STRING_FIELD, 'String Field'), - (FLOAT_FIELD, 'Float Field'), - (INTEGER_FIELD, 'Integer Field'), - (DATE_FIELD, 'Date Field'), - (SELECT_ONE_FIELD, 'Select One Field'), - (SELECT_MANY_FIELD, 'Select Many Field'), - ) - - review = models.ForeignKey(Review) + (BOOLEAN_FIELD, "Boolean Field"), + (STRING_FIELD, "String Field"), + (FLOAT_FIELD, "Float Field"), + (INTEGER_FIELD, "Integer Field"), + (DATE_FIELD, "Date Field"), + (SELECT_ONE_FIELD, "Select One Field"), + (SELECT_MANY_FIELD, "Select Many Field"), + ) + + review = models.ForeignKey(Review, on_delete=models.CASCADE) description = models.CharField(max_length=255) field_type = models.CharField(max_length=1, choices=FIELD_TYPES) order = models.IntegerField(default=0) class Meta: - verbose_name = 'Data Extraction Field' - verbose_name_plural = 'Data Extraction Fields' - ordering = ('order',) + verbose_name = "Data Extraction Field" + verbose_name_plural = "Data Extraction Fields" + ordering = ("order",) def get_select_values(self): return DataExtractionLookup.objects.filter(field__id=self.id) @@ -444,90 +446,89 @@ def is_select_field(self): class DataExtractionLookup(models.Model): - field = models.ForeignKey(DataExtractionField) + field = models.ForeignKey(DataExtractionField, on_delete=models.CASCADE) value = models.CharField(max_length=1000) class Meta: - verbose_name = 'Lookup Value' - verbose_name_plural = 'Lookup Values' - ordering = ('value',) + verbose_name = "Lookup Value" + verbose_name_plural = "Lookup Values" + ordering = ("value",) - def __unicode__(self): + def __str__(self): return self.value class DataExtraction(models.Model): - user = models.ForeignKey(User, null=True) - article = models.ForeignKey(Article) - field = models.ForeignKey(DataExtractionField) + user = models.ForeignKey(User, on_delete=models.CASCADE, null=True) + article = models.ForeignKey(Article, on_delete=models.CASCADE) + field = models.ForeignKey(DataExtractionField, on_delete=models.CASCADE) value = models.TextField(blank=True, null=True) select_values = models.ManyToManyField(DataExtractionLookup) def _set_boolean_value(self, value): if value: - if value in ['True', 'False']: + if value in ["True", "False"]: self.value = value else: raise ValueError('Expected values: "True" or "False"') else: - self.value = '' + self.value = "" def _set_string_value(self, value): - try: - self.value = value.strip() - except Exception, e: - raise e + self.value = value.strip() def _set_float_value(self, value): try: if value: - _value = value.replace(',', '.') + _value = value.replace(",", ".") self.value = float(_value) else: - self.value = '' - except: - raise Exception('Invalid input for ' + self.field.description + ' field. Expected value: floating point number. Please use dot instead of comma.') + self.value = "" + except Exception: + raise Exception( + "Invalid input for " + + self.field.description + + " field. Expected value: floating point number. Please use dot instead of comma." + ) def _set_integer_value(self, value): try: if value: - _value = value.replace(',', '.') + _value = value.replace(",", ".") self.value = int(float(_value)) else: - self.value = '' - except: - raise Exception('Invalid input for ' + self.field.description + ' field. Expected value: integer number.') + self.value = "" + except Exception: + raise Exception("Invalid input for " + self.field.description + " field. Expected value: integer number.") def _set_date_value(self, value): try: if value: - _value = datetime.datetime.strptime(value, '%m/%d/%Y').date() + _value = datetime.datetime.strptime(value, "%m/%d/%Y").date() self.value = str(_value) else: - self.value = '' - except: - raise Exception('Invalid input for ' + self.field.description + ' field. Expected value: date. Please use the format MM/DD/YYYY.') + self.value = "" + except Exception: + raise Exception( + "Invalid input for " + + self.field.description + + " field. Expected value: date. Please use the format MM/DD/YYYY." + ) def _set_select_one_value(self, value): - try: - self.value = '' - self.select_values.clear() - if value: - _value = DataExtractionLookup.objects.get(pk=value) - self.select_values.add(_value) - except Exception, e: - raise e + self.value = "" + self.select_values.clear() + if value: + _value = DataExtractionLookup.objects.get(pk=value) + self.select_values.add(_value) def _set_select_many_value(self, value): - try: - self.value = '' - _value = DataExtractionLookup.objects.get(pk=value) - if _value in self.select_values.all(): - self.select_values.remove(_value) - else: - self.select_values.add(_value) - except Exception, e: - raise e + self.value = "" + _value = DataExtractionLookup.objects.get(pk=value) + if _value in self.select_values.all(): + self.select_values.remove(_value) + else: + self.select_values.add(_value) def set_value(self, value): set_value_functions = { @@ -543,14 +544,14 @@ def set_value(self, value): def _get_boolean_value(self): try: - if self.value == 'True': + if self.value == "True": return True - elif self.value == 'False': + elif self.value == "False": return False else: - return '' - except Exception, e: - return '' + return "" + except Exception: + return "" def _get_string_value(self): return self.value @@ -558,34 +559,34 @@ def _get_string_value(self): def _get_float_value(self): try: return float(self.value) - except Exception, e: - return '' + except Exception: + return "" def _get_integer_value(self): try: return int(self.value) - except Exception, e: - return '' + except Exception: + return "" def _get_date_value(self): try: - if self.value != '': - return datetime.datetime.strptime(self.value, '%Y-%m-%d').date() + if self.value != "": + return datetime.datetime.strptime(self.value, "%Y-%m-%d").date() else: - return '' - except Exception, e: - return '' + return "" + except Exception: + return "" def _get_select_one_value(self): try: return self.select_values.all()[0] - except Exception, e: + except Exception: return None def _get_select_many_value(self): try: return self.select_values.all() - except Exception, e: + except Exception: return [] def get_value(self): @@ -605,6 +606,6 @@ def get_value(self): def get_date_value_as_string(self): try: value = self.get_value() - return value.strftime('%m/%d/%Y') - except Exception, e: - return '' + return value.strftime("%m/%d/%Y") + except Exception: + return "" diff --git a/parsifal/reviews/planning/__init__.py b/parsifal/reviews/planning/__init__.py index e69de29b..2b16017b 100644 --- a/parsifal/reviews/planning/__init__.py +++ b/parsifal/reviews/planning/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.reviews.planning.apps.PlanningConfig" diff --git a/parsifal/reviews/planning/apps.py b/parsifal/reviews/planning/apps.py new file mode 100644 index 00000000..c554bae7 --- /dev/null +++ b/parsifal/reviews/planning/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class PlanningConfig(AppConfig): + name = "parsifal.reviews.planning" + verbose_name = _("Reviews: Planning") diff --git a/parsifal/reviews/planning/forms.py b/parsifal/reviews/planning/forms.py index 478ed9c9..92a031e9 100644 --- a/parsifal/reviews/planning/forms.py +++ b/parsifal/reviews/planning/forms.py @@ -4,31 +4,28 @@ class KeywordForm(forms.ModelForm): - description = forms.CharField( - widget=forms.TextInput(attrs={ 'class': 'form-control' }), - max_length=200, - required=True - ) + widget=forms.TextInput(attrs={"class": "form-control"}), max_length=200, required=True + ) related_to = forms.ChoiceField( - widget=forms.Select(attrs={ 'class': 'form-control' }), - choices=Keyword.RELATED_TO, - required=False - ) + widget=forms.Select(attrs={"class": "form-control"}), choices=Keyword.RELATED_TO, required=False + ) class Meta: model = Keyword - fields = ['description', 'related_to', ] + fields = [ + "description", + "related_to", + ] -class SynonymForm(forms.ModelForm): +class SynonymForm(forms.ModelForm): description = forms.CharField( - widget=forms.TextInput(attrs={ 'class': 'form-control input-sm' }), - max_length=200, - required=True - ) + widget=forms.TextInput(attrs={"class": "form-control input-sm"}), max_length=200, required=True + ) class Meta: model = Keyword - fields = ['description',] - + fields = [ + "description", + ] diff --git a/parsifal/reviews/planning/models.py b/parsifal/reviews/planning/models.py index 71a83623..e69de29b 100644 --- a/parsifal/reviews/planning/models.py +++ b/parsifal/reviews/planning/models.py @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/parsifal/reviews/planning/urls.py b/parsifal/reviews/planning/urls.py index 3ccfc2a6..08984b8a 100644 --- a/parsifal/reviews/planning/urls.py +++ b/parsifal/reviews/planning/urls.py @@ -1,44 +1,73 @@ -# coding: utf-8 +from django.urls import path -from django.conf.urls import patterns, include, url +from parsifal.reviews.planning import views - -urlpatterns = patterns('parsifal.reviews.planning.views', - url(r'^save_source/$', 'save_source', name='save_source'), - url(r'^remove_source/$', 'remove_source_from_review', name='remove_source_from_review'), - url(r'^suggested_sources/$', 'suggested_sources', name='suggested_sources'), - url(r'^add_suggested_sources/$', 'add_suggested_sources', name='add_suggested_sources'), - url(r'^save_question/$', 'save_question', name='save_question'), - url(r'^save_question_order/$', 'save_question_order', name='save_question_order'), - url(r'^save_picoc/$', 'save_picoc', name='save_picoc'), - url(r'^add_or_edit_question/$', 'add_or_edit_question', name='add_or_edit_question'), - url(r'^remove_question/$', 'remove_question', name='remove_question'), - url(r'^save_objective/$', 'save_objective', name='save_objective'), - url(r'^add_criteria/$', 'add_criteria', name='add_criteria'), - url(r'^remove_criteria/$', 'remove_criteria', name='remove_criteria'), - - url(r'^import_pico_keywords/$', 'import_pico_keywords', name='import_pico_keywords'), - url(r'^add_keyword/$', 'add_keyword', name='add_keyword'), - url(r'^edit_keyword/$', 'edit_keyword', name='edit_keyword'), - url(r'^remove_keyword/$', 'remove_keyword', name='remove_keyword'), - - url(r'^add_quality_assessment_question/$', 'add_quality_assessment_question', name='add_quality_assessment_question'), - url(r'^edit_quality_assessment_question/$', 'edit_quality_assessment_question', name='edit_quality_assessment_question'), - url(r'^save_quality_assessment_question/$', 'save_quality_assessment_question', name='save_quality_assessment_question'), - url(r'^save_quality_assessment_question_order/$', 'save_quality_assessment_question_order', name='save_quality_assessment_question_order'), - url(r'^remove_quality_assessment_question/$', 'remove_quality_assessment_question', name='remove_quality_assessment_question'), - url(r'^add_quality_assessment_answer/$', 'add_quality_assessment_answer', name='add_quality_assessment_answer'), - url(r'^edit_quality_assessment_answer/$', 'edit_quality_assessment_answer', name='edit_quality_assessment_answer'), - url(r'^save_quality_assessment_answer/$', 'save_quality_assessment_answer', name='save_quality_assessment_answer'), - url(r'^remove_quality_assessment_answer/$', 'remove_quality_assessment_answer', name='remove_quality_assessment_answer'), - url(r'^add_suggested_answer/$', 'add_suggested_answer', name='add_suggested_answer'), - url(r'^add_new_data_extraction_field/$', 'add_new_data_extraction_field', name='add_new_data_extraction_field'), - url(r'^edit_data_extraction_field/$', 'edit_data_extraction_field', name='edit_data_extraction_field'), - url(r'^save_data_extraction_field/$', 'save_data_extraction_field', name='save_data_extraction_field'), - url(r'^save_data_extraction_field_order/$', 'save_data_extraction_field_order', name='save_data_extraction_field_order'), - url(r'^remove_data_extraction_field/$', 'remove_data_extraction_field', name='remove_data_extraction_field'), - url(r'^calculate_max_score/$', 'calculate_max_score', name='calculate_max_score'), - url(r'^save_cutoff_score/$', 'save_cutoff_score', name='save_cutoff_score'), - url(r'^generate_search_string/$', 'generate_search_string', name='generate_search_string'), - url(r'^save_generic_search_string/$', 'save_generic_search_string', name='save_generic_search_string'), -) +urlpatterns = [ + path("save_source/", views.save_source, name="save_source"), + path("remove_source/", views.remove_source_from_review, name="remove_source_from_review"), + path("suggested_sources/", views.suggested_sources, name="suggested_sources"), + path("add_suggested_sources/", views.add_suggested_sources, name="add_suggested_sources"), + path("save_question/", views.save_question, name="save_question"), + path("save_question_order/", views.save_question_order, name="save_question_order"), + path("save_picoc/", views.save_picoc, name="save_picoc"), + path("add_or_edit_question/", views.add_or_edit_question, name="add_or_edit_question"), + path("remove_question/", views.remove_question, name="remove_question"), + path("save_objective/", views.save_objective, name="save_objective"), + path("add_criteria/", views.add_criteria, name="add_criteria"), + path("remove_criteria/", views.remove_criteria, name="remove_criteria"), + path("import_pico_keywords/", views.import_pico_keywords, name="import_pico_keywords"), + path("add_keyword/", views.add_keyword, name="add_keyword"), + path("edit_keyword/", views.edit_keyword, name="edit_keyword"), + path("remove_keyword/", views.remove_keyword, name="remove_keyword"), + path( + "add_quality_assessment_question/", + views.add_quality_assessment_question, + name="add_quality_assessment_question", + ), + path( + "edit_quality_assessment_question/", + views.edit_quality_assessment_question, + name="edit_quality_assessment_question", + ), + path( + "save_quality_assessment_question/", + views.save_quality_assessment_question, + name="save_quality_assessment_question", + ), + path( + "save_quality_assessment_question_order/", + views.save_quality_assessment_question_order, + name="save_quality_assessment_question_order", + ), + path( + "remove_quality_assessment_question/", + views.remove_quality_assessment_question, + name="remove_quality_assessment_question", + ), + path("add_quality_assessment_answer/", views.add_quality_assessment_answer, name="add_quality_assessment_answer"), + path( + "edit_quality_assessment_answer/", views.edit_quality_assessment_answer, name="edit_quality_assessment_answer" + ), + path( + "save_quality_assessment_answer/", views.save_quality_assessment_answer, name="save_quality_assessment_answer" + ), + path( + "remove_quality_assessment_answer/", + views.remove_quality_assessment_answer, + name="remove_quality_assessment_answer", + ), + path("add_suggested_answer/", views.add_suggested_answer, name="add_suggested_answer"), + path("add_new_data_extraction_field/", views.add_new_data_extraction_field, name="add_new_data_extraction_field"), + path("edit_data_extraction_field/", views.edit_data_extraction_field, name="edit_data_extraction_field"), + path("save_data_extraction_field/", views.save_data_extraction_field, name="save_data_extraction_field"), + path( + "save_data_extraction_field_order/", + views.save_data_extraction_field_order, + name="save_data_extraction_field_order", + ), + path("remove_data_extraction_field/", views.remove_data_extraction_field, name="remove_data_extraction_field"), + path("calculate_max_score/", views.calculate_max_score, name="calculate_max_score"), + path("save_cutoff_score/", views.save_cutoff_score, name="save_cutoff_score"), + path("generate_search_string/", views.generate_search_string, name="generate_search_string"), + path("save_generic_search_string/", views.save_generic_search_string, name="save_generic_search_string"), +] diff --git a/parsifal/reviews/planning/views.py b/parsifal/reviews/planning/views.py index b82eef8a..d2c08dd4 100644 --- a/parsifal/reviews/planning/views.py +++ b/parsifal/reviews/planning/views.py @@ -1,88 +1,101 @@ -# coding: utf-8 - -import time import json -from django.db import transaction -from django.core.urlresolvers import reverse as r -from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.contrib.auth.models import User +from django.db import transaction from django.forms.formsets import formset_factory from django.forms.models import inlineformset_factory -from django.shortcuts import render, render_to_response, redirect, get_object_or_404 +from django.http import HttpResponse, HttpResponseBadRequest +from django.shortcuts import get_object_or_404, redirect, render from django.template import RequestContext from django.template.loader import render_to_string -from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden -from django.views.decorators.http import require_POST +from django.urls import reverse as r from django.utils.html import escape -from parsifal.reviews.models import * +from parsifal.reviews.decorators import author_required +from parsifal.reviews.models import ( + DataExtraction, + DataExtractionField, + DataExtractionLookup, + Keyword, + QualityAnswer, + QualityQuestion, + Question, + Review, + SelectionCriteria, + Source, +) from parsifal.reviews.planning.forms import KeywordForm, SynonymForm -from parsifal.reviews.decorators import main_author_required, author_required @author_required @login_required def planning(request, username, review_name): - return redirect(r('protocol', args=(username, review_name))) + return redirect(r("protocol", args=(username, review_name))) + @author_required @login_required def protocol(request, username, review_name): review = get_object_or_404(Review, name=review_name, author__username__iexact=username) - return render(request, 'planning/protocol.html', { 'review': review }) + return render(request, "planning/protocol.html", {"review": review}) + @author_required @login_required def quality_assessment_checklist(request, username, review_name): review = get_object_or_404(Review, name=review_name, author__username__iexact=username) - return render(request, 'planning/quality_assessment_checklist.html', { 'review': review }) + return render(request, "planning/quality_assessment_checklist.html", {"review": review}) + @author_required @login_required def data_extraction_form(request, username, review_name): review = get_object_or_404(Review, name=review_name, author__username__iexact=username) empty_field = DataExtractionField() - return render(request, 'planning/data_extraction_form.html', { 'review': review, 'empty_field': empty_field }) + return render(request, "planning/data_extraction_form.html", {"review": review, "empty_field": empty_field}) -''' - OBJECTIVE FUNCTIONS -''' +# ============================================================================== +# OBJECTIVE +# ============================================================================== + @author_required @login_required def save_objective(request): try: - review_id = request.POST['review-id'] - objective = request.POST['objective'] + review_id = request.POST["review-id"] + objective = request.POST["objective"] review = Review.objects.get(pk=review_id) if len(objective) > 1000: - return HttpResponseBadRequest('The review objectives should not exceed 1000 characters. The given objectives have %s characters.' % len(objective)) + return HttpResponseBadRequest( + "The review objectives should not exceed 1000 characters. The given objectives have %s characters." + % len(objective) + ) else: review.objective = objective review.save() - return HttpResponse('Your review have been saved successfully!') + return HttpResponse("Your review have been saved successfully!") except: return HttpResponseBadRequest() -''' - QUESTION FUNCTIONS -''' +# ============================================================================== +# QUESTION +# ============================================================================== + @author_required @login_required def save_question(request): - ''' - Function used via Ajax request only. - This function takes a review question form and save on the database - ''' + """ + Function used via Ajax request only. + This function takes a review question form and save on the database + """ try: - review_id = request.POST['review-id'] - question_id = request.POST['question-id'] - description = request.POST['description'] + review_id = request.POST["review-id"] + question_id = request.POST["question-id"] + description = request.POST["description"] review = Review.objects.get(pk=review_id) try: question = Question.objects.get(pk=question_id) @@ -90,20 +103,20 @@ def save_question(request): question = Question(review=review) question.question = description[:500] question.save() - context = RequestContext(request, {'question':question}) - return render_to_response('planning/partial_planning_question.html', context) + return render(request, "planning/partial_planning_question.html", {"question": question}) except: return HttpResponseBadRequest() + @author_required @login_required def save_question_order(request): try: - orders = request.POST.get('orders') - question_orders = orders.split(',') + orders = request.POST.get("orders") + question_orders = orders.split(",") for question_order in question_orders: if question_order: - question_id, order = question_order.split(':') + question_id, order = question_order.split(":") question = Question.objects.get(pk=question_id) question.order = order question.save() @@ -111,23 +124,23 @@ def save_question_order(request): except: return HttpResponseBadRequest() + @author_required @login_required def add_or_edit_question(request): - ''' - Function used via Ajax request only. - This functions adds a new secondary question to the review. - ''' + """ + Function used via Ajax request only. + This functions adds a new secondary question to the review. + """ try: - review_id = request.POST['review-id'] - question_id = request.POST['question-id'] + review_id = request.POST["review-id"] + question_id = request.POST["question-id"] review = Review.objects.get(pk=review_id) try: question = Question.objects.get(pk=question_id) except: question = Question(review=review) - context = RequestContext(request, {'question':question}) - return render_to_response('planning/partial_planning_question_form.html', context) + return render(request, "planning/partial_planning_question_form.html", {"question": question}) except: return HttpResponseBadRequest() @@ -135,16 +148,16 @@ def add_or_edit_question(request): @author_required @login_required def remove_question(request): - ''' - Function used via Ajax request only. - This function removes a secondary question from the review. - ''' + """ + Function used via Ajax request only. + This function removes a secondary question from the review. + """ try: - review_id = request.POST['review-id'] - question_id = request.POST['question-id'] - if question_id != 'None': + review_id = request.POST["review-id"] + question_id = request.POST["question-id"] + if question_id != "None": try: - question = Question.objects.get(pk=question_id) + question = Question.objects.get(pk=question_id, review_id=review_id) question.delete() except Question.DoesNotExist: return HttpResponseBadRequest() @@ -153,42 +166,50 @@ def remove_question(request): return HttpResponseBadRequest() -''' - PICOC FUNCTIONS -''' +# ============================================================================== +# PICOC +# ============================================================================== + @author_required @login_required def save_picoc(request): try: - review_id = request.POST['review-id'] + review_id = request.POST["review-id"] review = Review.objects.get(pk=review_id) - review.population = request.POST['population'][:200] - review.intervention = request.POST['intervention'][:200] - review.comparison = request.POST['comparison'][:200] - review.outcome = request.POST['outcome'][:200] - review.context = request.POST['context'][:200] + review.population = request.POST["population"][:200] + review.intervention = request.POST["intervention"][:200] + review.comparison = request.POST["comparison"][:200] + review.outcome = request.POST["outcome"][:200] + review.context = request.POST["context"][:200] review.save() return HttpResponse() - except Exception, e: + except Exception: return HttpResponseBadRequest() -''' - KEYWORDS/SYNONYM FUNCTIONS -''' +# ============================================================================== +# KEYWORDS/SYNONYM +# ============================================================================== + def extract_keywords(review, pico): - if pico == Keyword.POPULATION: keywords = review.population - elif pico == Keyword.INTERVENTION: keywords = review.intervention - elif pico == Keyword.COMPARISON: keywords = review.comparison - elif pico == Keyword.OUTCOME: keywords = review.outcome - keyword_list = keywords.split(',') + if pico == Keyword.POPULATION: + keywords = review.population + elif pico == Keyword.INTERVENTION: + keywords = review.intervention + elif pico == Keyword.COMPARISON: + keywords = review.comparison + elif pico == Keyword.OUTCOME: + keywords = review.outcome + else: + keywords = "" + keyword_list = keywords.split(",") keyword_objects = [] for term in keyword_list: if len(term) > 0: keyword = Keyword(review=review, description=term.strip(), related_to=pico) - if keyword.description not in review.get_keywords().values_list('description', flat=True): + if keyword.description not in review.get_keywords().values_list("description", flat=True): keyword.save() keyword_objects.append(keyword) return keyword_objects @@ -198,7 +219,7 @@ def extract_keywords(review, pico): @login_required def import_pico_keywords(request): try: - review_id = request.GET['review-id'] + review_id = request.GET["review-id"] review = Review.objects.get(pk=review_id) keywords = [] @@ -207,11 +228,11 @@ def import_pico_keywords(request): keywords += extract_keywords(review, Keyword.COMPARISON) keywords += extract_keywords(review, Keyword.OUTCOME) - html = u'' + html = "" for keyword in keywords: - context = RequestContext(request, { 'keyword': keyword }) - html += render_to_string('planning/partial_keyword.html', context) + context = RequestContext(request, {"keyword": keyword}) + html += render_to_string("planning/partial_keyword.html", context) return HttpResponse(html) except: return HttpResponseBadRequest() @@ -221,10 +242,9 @@ def import_pico_keywords(request): @login_required def remove_keyword(request): try: - review_id = request.GET['review-id'] - keyword_id = request.GET['keyword-id'] - review = Review.objects.get(pk=review_id) - keyword = Keyword.objects.get(pk=keyword_id) + review_id = request.GET["review-id"] + keyword_id = request.GET["keyword-id"] + keyword = Keyword.objects.get(pk=keyword_id, review_id=review_id) synonyms = Keyword.objects.filter(synonym_of__id=keyword_id) for synonym in synonyms: synonym.delete() @@ -233,17 +253,18 @@ def remove_keyword(request): except: return HttpResponseBadRequest() + @transaction.atomic @author_required @login_required def add_keyword(request): - review_id = request.GET.get('review-id', request.POST.get('review-id')) + review_id = request.GET.get("review-id", request.POST.get("review-id")) review = Review.objects.get(pk=review_id) SynonymFormSet = formset_factory(SynonymForm) response = {} - if request.method == 'POST': + if request.method == "POST": form = KeywordForm(request.POST) - formset = SynonymFormSet(request.POST, prefix='synonym') + formset = SynonymFormSet(request.POST, prefix="synonym") if form.is_valid() and formset.is_valid(): form.instance.review = review keyword = form.save() @@ -252,41 +273,38 @@ def add_keyword(request): form.instance.review = review form.instance.synonym_of = keyword form.save() - context = RequestContext(request, { 'keyword': keyword }) - response['status'] = 'ok' - response['html'] = render_to_string('planning/partial_keyword.html', context) + context = RequestContext(request, {"keyword": keyword}) + response["status"] = "ok" + response["html"] = render_to_string("planning/partial_keyword.html", context) dump = json.dumps(response) - return HttpResponse(dump, content_type='application/json') + return HttpResponse(dump, content_type="application/json") else: - response['status'] = 'validation_error' + response["status"] = "validation_error" else: form = KeywordForm() - formset = SynonymFormSet(prefix='synonym') - response['status'] = 'new' - context = RequestContext(request, { - 'review': review, - 'form': form, - 'formset': formset - }) - response['html'] = render_to_string('planning/partial_keyword_form.html', context) + formset = SynonymFormSet(prefix="synonym") + response["status"] = "new" + context = RequestContext(request, {"review": review, "form": form, "formset": formset}) + response["html"] = render_to_string("planning/partial_keyword_form.html", context) dump = json.dumps(response) - return HttpResponse(dump, content_type='application/json') + return HttpResponse(dump, content_type="application/json") + @transaction.atomic @author_required @login_required def edit_keyword(request): - review_id = request.GET.get('review-id', request.POST.get('review-id')) - keyword_id = request.GET.get('keyword-id', request.POST.get('keyword-id')) + review_id = request.GET.get("review-id", request.POST.get("review-id")) + keyword_id = request.GET.get("keyword-id", request.POST.get("keyword-id")) review = get_object_or_404(Review, pk=review_id) keyword = get_object_or_404(Keyword, pk=keyword_id, review_id=review_id) SynonymFormSet = inlineformset_factory(Keyword, Keyword, SynonymForm, extra=1) response = {} - if request.method == 'POST': + if request.method == "POST": form = KeywordForm(request.POST, instance=keyword) - formset = SynonymFormSet(request.POST, instance=keyword, prefix='synonym') + formset = SynonymFormSet(request.POST, instance=keyword, prefix="synonym") if form.is_valid() and formset.is_valid(): form.instance.review = review keyword = form.save() @@ -294,59 +312,57 @@ def edit_keyword(request): form.instance.review = review form.instance.synonym_of = keyword formset.save() - context = RequestContext(request, { 'keyword': keyword }) - response['status'] = 'ok' - response['html'] = render_to_string('planning/partial_keyword.html', context) + response["status"] = "ok" + response["html"] = render_to_string("planning/partial_keyword.html", {"keyword": keyword}, request) dump = json.dumps(response) - return HttpResponse(dump, content_type='application/json') + return HttpResponse(dump, content_type="application/json") else: - response['status'] = 'validation_error' + response["status"] = "validation_error" else: form = KeywordForm(instance=keyword) - formset = SynonymFormSet(instance=keyword, prefix='synonym') - response['status'] = 'new' - context = RequestContext(request, { - 'review': review, - 'form': form, - 'formset': formset - }) - response['html'] = render_to_string('planning/partial_keyword_form.html', context) + formset = SynonymFormSet(instance=keyword, prefix="synonym") + response["status"] = "new" + response["html"] = render_to_string( + "planning/partial_keyword_form.html", {"review": review, "form": form, "formset": formset}, request + ) dump = json.dumps(response) - return HttpResponse(dump, content_type='application/json') + return HttpResponse(dump, content_type="application/json") + + +# ============================================================================== +# SEARCH STRING +# ============================================================================== -''' - SEARCH STRING FUNCTIONS -''' @author_required @login_required def generate_search_string(request): - ''' - Function used via Ajax request only. - Still have to refactor this function. This is just a first approach. - ''' - review_id = request.GET['review-id'] + """ + Function used via Ajax request only. + Still have to refactor this function. This is just a first approach. + """ + review_id = request.GET["review-id"] review = Review.objects.get(pk=review_id) keywords = [] for key, value in Keyword.RELATED_TO: synonyms = [] for keyword in review.keywords.filter(related_to=key, synonym_of=None): - synonyms.append(u'"{0}"'.format(keyword.description)) + synonyms.append('"{0}"'.format(keyword.description)) for synonym in keyword.synonyms.all(): - synonyms.append(u'"{0}"'.format(synonym.description)) + synonyms.append('"{0}"'.format(synonym.description)) if any(synonyms): - keywords.append(u'({0})'.format(u' OR '.join(synonyms))) + keywords.append("({0})".format(" OR ".join(synonyms))) - return HttpResponse(' AND '.join(keywords)) + return HttpResponse(" AND ".join(keywords)) @author_required @login_required def save_generic_search_string(request): try: - review_id = request.POST['review-id'] - search_string = request.POST['search-string'] + review_id = request.POST["review-id"] + search_string = request.POST["search-string"] review = Review.objects.get(pk=review_id) generic_search_string = review.get_generic_search_string() generic_search_string.search_string = search_string @@ -356,16 +372,17 @@ def save_generic_search_string(request): return HttpResponseBadRequest() -''' - SOURCE FUNCTIONS -''' +# ============================================================================== +# SOURCE +# ============================================================================== + def html_source(source): - html = '' + escape(source.name) + '' + html = '' + escape(source.name) + "" if source.url: - html += '' + escape(source.url) + '' + html += '' + escape(source.url) + "" else: - html += '' + escape(source.url) + '' + html += "" + escape(source.url) + "" if source.is_default: html += ' - '''.format(quality_answer.id, quality_answer.description, quality_answer.weight) + """.format( + quality_answer.id, quality_answer.description, quality_answer.weight + ) return HttpResponse(html_answers) else: return HttpResponseBadRequest() @@ -692,7 +722,7 @@ def add_suggested_answer(request): @login_required def calculate_max_score(request): try: - review_id = request.GET['review-id'] + review_id = request.GET["review-id"] review = Review.objects.get(pk=review_id) max_score = review.calculate_quality_assessment_max_score() return HttpResponse(max_score) @@ -704,51 +734,50 @@ def calculate_max_score(request): @login_required def save_cutoff_score(request): try: - review_id = request.GET['review-id'] - cutoff_score = request.GET['cutoff-score'] + review_id = request.GET["review-id"] + cutoff_score = request.GET["cutoff-score"] review = Review.objects.get(pk=review_id) review.quality_assessment_cutoff_score = float(cutoff_score) review.save() - return HttpResponse('Cutoff score saved successfully!') + return HttpResponse("Cutoff score saved successfully!") except: - return HttpResponseBadRequest('Invalid value.') + return HttpResponseBadRequest("Invalid value.") + +# ===================================================================================================================== +# DATA EXTRACTION +# ===================================================================================================================== -''' - DATA EXTRACTION FUNCTIONS -''' @author_required @login_required def add_new_data_extraction_field(request): field = DataExtractionField() - context = RequestContext(request, {'field': field}) - return render_to_response('planning/partial_data_extraction_field_form.html', context) + return render(request, "planning/partial_data_extraction_field_form.html", {"field": field}) @author_required @login_required def edit_data_extraction_field(request): - field_id = request.GET['field-id'] + field_id = request.GET["field-id"] field = DataExtractionField.objects.get(pk=field_id) - context = RequestContext(request, {'field': field}) - return render_to_response('planning/partial_data_extraction_field_form.html', context) + return render(request, "planning/partial_data_extraction_field_form.html", {"field": field}) @author_required @login_required def save_data_extraction_field(request): try: - review_id = request.POST['review-id'] - description = request.POST['description'] - field_type = request.POST['field-type'] - lookup_values = request.POST['lookup-values'] - field_id = request.POST['field-id'] + review_id = request.POST["review-id"] + description = request.POST["description"] + field_type = request.POST["field-type"] + lookup_values = request.POST["lookup-values"] + field_id = request.POST["field-id"] if not field_type and not description: - return HttpResponseBadRequest('Description and Type are required fields.') + return HttpResponseBadRequest("Description and Type are required fields.") - lookup_values = lookup_values.split('\n') + lookup_values = lookup_values.split("\n") lookup_values = list(set(lookup_values)) for i in range(0, len(lookup_values)): @@ -756,7 +785,7 @@ def save_data_extraction_field(request): review = Review.objects.get(pk=review_id) - if field_id == 'None': + if field_id == "None": field = DataExtractionField(review=review) else: field = DataExtractionField.objects.get(pk=field_id) @@ -764,10 +793,10 @@ def save_data_extraction_field(request): try: data_extractions = DataExtraction.objects.filter(field__id=field_id) for data_extraction in data_extractions: - data_extraction.value = '' + data_extraction.value = "" data_extraction.select_values.clear() data_extraction.save() - except Exception, e: + except Exception: pass field.description = description @@ -777,17 +806,14 @@ def save_data_extraction_field(request): if field.is_select_field(): for value in lookup_values: if value: - lookup_value, created = DataExtractionLookup.objects.get_or_create(field=field, value=value) - + DataExtractionLookup.objects.get_or_create(field=field, value=value) for select_value in field.get_select_values(): if select_value.value not in lookup_values: select_value.delete() else: for select_value in field.get_select_values(): select_value.delete() - - context = RequestContext(request, {'field': field}) - return render_to_response('planning/partial_data_extraction_field.html', context) + return render(request, "planning/partial_data_extraction_field.html", {"field": field}) except: return HttpResponseBadRequest() @@ -796,11 +822,11 @@ def save_data_extraction_field(request): @login_required def save_data_extraction_field_order(request): try: - orders = request.POST.get('orders') - field_orders = orders.split(',') + orders = request.POST.get("orders") + field_orders = orders.split(",") for field_order in field_orders: if field_order: - field_id, order = field_order.split(':') + field_id, order = field_order.split(":") field = DataExtractionField.objects.get(pk=field_id) field.order = order field.save() @@ -813,7 +839,7 @@ def save_data_extraction_field_order(request): @login_required def remove_data_extraction_field(request): try: - field_id = request.GET['field-id'] + field_id = request.GET["field-id"] field = DataExtractionField.objects.get(pk=field_id) select_values = field.get_select_values() for select_value in select_values: diff --git a/parsifal/reviews/reporting/__init__.py b/parsifal/reviews/reporting/__init__.py index e69de29b..10dcb1ae 100644 --- a/parsifal/reviews/reporting/__init__.py +++ b/parsifal/reviews/reporting/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.reviews.reporting.apps.ReportingConfig" diff --git a/parsifal/reviews/reporting/apps.py b/parsifal/reviews/reporting/apps.py new file mode 100644 index 00000000..1df90d17 --- /dev/null +++ b/parsifal/reviews/reporting/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class ReportingConfig(AppConfig): + name = "parsifal.reviews.reporting" + verbose_name = _("Reviews: Reporting") diff --git a/parsifal/reviews/reporting/export.py b/parsifal/reviews/reporting/export.py index f002fd13..c5d72b7b 100644 --- a/parsifal/reviews/reporting/export.py +++ b/parsifal/reviews/reporting/export.py @@ -1,179 +1,154 @@ -# coding: utf-8 - from docx import Document -from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.enum.text import WD_PARAGRAPH_ALIGNMENT def export_review_to_docx(review, sections): document = Document() - if 'name' in sections: + if "name" in sections: h = document.add_heading(review.title, level=1) - h.alignment = WD_ALIGN_PARAGRAPH.CENTER - document.add_paragraph('') + h.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + document.add_paragraph("") - if 'authors' in sections: + if "authors" in sections: authors = list() authors.append(review.author.profile.get_screen_name()) for author in review.co_authors.all(): authors.append(author.profile.get_screen_name()) - p = document.add_paragraph(', '.join(authors)) - p.alignment = WD_ALIGN_PARAGRAPH.CENTER - document.add_paragraph('') + p = document.add_paragraph(", ".join(authors)) + p.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + document.add_paragraph("") - if 'description' in sections: + if "description" in sections: if review.description: document.add_paragraph(review.description) - - document.add_heading('Planning', level=2) + document.add_heading("Planning", level=2) if review.objective: document.add_paragraph(review.objective) - ''' - PICOC - ''' - if 'picoc' in sections: - document.add_heading('PICOC', level=3) + # PICOC + if "picoc" in sections: + document.add_heading("PICOC", level=3) - p = document.add_paragraph('', style='List Bullet') - p.add_run('Population: ').bold = True + p = document.add_paragraph("", style="List Bullet") + p.add_run("Population: ").bold = True p.add_run(review.population) - p = document.add_paragraph('', style='List Bullet') - p.add_run('Intervention: ').bold = True + p = document.add_paragraph("", style="List Bullet") + p.add_run("Intervention: ").bold = True p.add_run(review.intervention) - p = document.add_paragraph('', style='List Bullet') - p.add_run('Comparison: ').bold = True + p = document.add_paragraph("", style="List Bullet") + p.add_run("Comparison: ").bold = True p.add_run(review.comparison) - p = document.add_paragraph('', style='List Bullet') - p.add_run('Outcome: ').bold = True + p = document.add_paragraph("", style="List Bullet") + p.add_run("Outcome: ").bold = True p.add_run(review.outcome) - p = document.add_paragraph('', style='List Bullet') - p.add_run('Context: ').bold = True + p = document.add_paragraph("", style="List Bullet") + p.add_run("Context: ").bold = True p.add_run(review.context) - ''' - Research Questions - ''' - if 'research_questions' in sections: - document.add_heading('Research Questions', level=3) + # Research Questions + if "research_questions" in sections: + document.add_heading("Research Questions", level=3) for question in review.research_questions.all(): - document.add_paragraph(question.question, style='List Number') + document.add_paragraph(question.question, style="List Number") - ''' - Keywords and Synonym - ''' - if 'keywords_synonyms' in sections: - document.add_heading('Keywords and Synonyms', level=3) + # Keywords and Synonym + if "keywords_synonyms" in sections: + document.add_heading("Keywords and Synonyms", level=3) table = document.add_table(rows=1, cols=2) hdr_cells = table.rows[0].cells - hdr_cells[0].text = 'Keyword' - hdr_cells[1].text = 'Synonyms' + hdr_cells[0].text = "Keyword" + hdr_cells[1].text = "Synonyms" for keyword in review.get_keywords(): row_cells = table.add_row().cells row_cells[0].text = keyword.description - row_cells[1].text = ', '.join(keyword.synonyms.all().values_list('description', flat=True)) + row_cells[1].text = ", ".join(keyword.synonyms.all().values_list("description", flat=True)) - ''' - Search String - ''' - if 'search_string' in sections: - document.add_heading('Search String', level=3) + # Search String + if "search_string" in sections: + document.add_heading("Search String", level=3) document.add_paragraph(review.get_generic_search_string().search_string) - ''' - Sources - ''' - if 'sources' in sections: - document.add_heading('Sources', level=3) + # Sources + if "sources" in sections: + document.add_heading("Sources", level=3) for source in review.sources.all(): text = source.name if source.url: - text = u'{0} ({1})'.format(source.name, source.url) - document.add_paragraph(text, style='List Bullet') + text = "{0} ({1})".format(source.name, source.url) + document.add_paragraph(text, style="List Bullet") - ''' - Selection Criteria - ''' - if 'selection_criteria' in sections: - document.add_heading('Selection Criteria', level=3) + # Selection Criteria + if "selection_criteria" in sections: + document.add_heading("Selection Criteria", level=3) p = document.add_paragraph() - p.add_run('Inclusion Criteria:').bold = True + p.add_run("Inclusion Criteria:").bold = True for criteria in review.get_inclusion_criterias(): - document.add_paragraph(criteria.description, style='List Bullet') + document.add_paragraph(criteria.description, style="List Bullet") p = document.add_paragraph() - p.add_run('Exclusion Criteria:').bold = True + p.add_run("Exclusion Criteria:").bold = True for criteria in review.get_exclusion_criterias(): - document.add_paragraph(criteria.description, style='List Bullet') + document.add_paragraph(criteria.description, style="List Bullet") - ''' - Quality Assessment Checklist - ''' - if 'quality_assessment_checklist' in sections: - document.add_heading('Quality Assessment Checklist', level=3) + # Quality Assessment Checklist + if "quality_assessment_checklist" in sections: + document.add_heading("Quality Assessment Checklist", level=3) p = document.add_paragraph() - p.add_run('Questions:').bold = True + p.add_run("Questions:").bold = True for quality_question in review.get_quality_assessment_questions(): - document.add_paragraph(quality_question.description, style='List Bullet') + document.add_paragraph(quality_question.description, style="List Bullet") p = document.add_paragraph() - p.add_run('Answers:').bold = True + p.add_run("Answers:").bold = True for quality_answer in review.get_quality_assessment_answers(): - document.add_paragraph(quality_answer.description, style='List Bullet') + document.add_paragraph(quality_answer.description, style="List Bullet") - ''' - Data Extraction Form - ''' - if 'data_extraction_form' in sections: - document.add_heading('Data Extraction Form', level=3) + # Data Extraction Form + if "data_extraction_form" in sections: + document.add_heading("Data Extraction Form", level=3) for field in review.get_data_extraction_fields(): - document.add_paragraph(field.description, style='List Bullet') - - ''' - Conducting - ''' - - document.add_heading('Conducting', level=2) + document.add_paragraph(field.description, style="List Bullet") - ''' - Digital Libraries Search Strings - ''' + # Conducting + document.add_heading("Conducting", level=2) - if 'source_search_strings' in sections: - document.add_heading('Digital Libraries Search Strings', level=3) + # Digital Libraries Search Strings + if "source_search_strings" in sections: + document.add_heading("Digital Libraries Search Strings", level=3) for search_session in review.get_latest_source_search_strings(): p = document.add_paragraph() - p.add_run(u'{0}:'.format(search_session.source.name)).bold = True + p.add_run("{0}:".format(search_session.source.name)).bold = True document.add_paragraph(search_session.search_string) document.add_paragraph() - if 'number_imported_studies' in sections: - document.add_heading('Imported Studies', level=3) + if "number_imported_studies" in sections: + document.add_heading("Imported Studies", level=3) for source in review.sources.all(): - p = document.add_paragraph(style='List Bullet') - p.add_run(u'{0}: '.format(source.name)).bold = True + p = document.add_paragraph(style="List Bullet") + p.add_run("{0}: ".format(source.name)).bold = True count = review.article_set.filter(source=source).count() p.add_run(str(count)) - if 'quality_assessment' in sections: - document.add_heading('Quality Assessment', level=3) + if "quality_assessment" in sections: + document.add_heading("Quality Assessment", level=3) - if 'data_extraction' in sections: - document.add_heading('Data Extraction', level=3) + if "data_extraction" in sections: + document.add_heading("Data Extraction", level=3) - if 'data_analysis' in sections: - document.add_heading('Data Analysis', level=3) + if "data_analysis" in sections: + document.add_heading("Data Analysis", level=3) return document diff --git a/parsifal/reviews/reporting/models.py b/parsifal/reviews/reporting/models.py index 71a83623..e69de29b 100644 --- a/parsifal/reviews/reporting/models.py +++ b/parsifal/reviews/reporting/models.py @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/parsifal/reviews/reporting/tests/test_views_export.py b/parsifal/reviews/reporting/tests/test_views_export.py index 455ea89f..523652e1 100644 --- a/parsifal/reviews/reporting/tests/test_views_export.py +++ b/parsifal/reviews/reporting/tests/test_views_export.py @@ -1,21 +1,25 @@ # coding: utf-8 -from django.test import TestCase from django.contrib.auth.models import User +from django.test import TestCase from parsifal.reviews.models import Review, Source class ImportBibitexTest(TestCase): - fixtures = ['source_initial_data.json',] + fixtures = [ + "source_initial_data.json", + ] @classmethod def setUpTestData(cls): - cls.user = User.objects.create_user(username='john', email='john.doe@parsif.al', password='123') - cls.review = Review.objects.create(name='test-review', title='Test Review', description='', author=cls.user, objective='') + cls.user = User.objects.create_user(username="john", email="john.doe@parsif.al", password="123") + cls.review = Review.objects.create( + name="test-review", title="Test Review", description="", author=cls.user, objective="" + ) def setUp(self): - self.client.login(username='john', password='123') + self.client.login(username="john", password="123") def test_loaded_fixture(self): self.assertGreater(User.objects.all().count(), 0) diff --git a/parsifal/reviews/reporting/urls.py b/parsifal/reviews/reporting/urls.py index 71c56477..f826c5b8 100644 --- a/parsifal/reviews/reporting/urls.py +++ b/parsifal/reviews/reporting/urls.py @@ -1,8 +1,7 @@ -# coding: utf-8 +from django.urls import path -from django.conf.urls import patterns, include, url +from parsifal.reviews.reporting import views - -urlpatterns = patterns('parsifal.reviews.reporting.views', - url(r'^download_docx/$', 'download_docx', name='download_docx'), -) +urlpatterns = [ + path("download_docx/", views.download_docx, name="download_docx"), +] diff --git a/parsifal/reviews/reporting/views.py b/parsifal/reviews/reporting/views.py index 074d3c66..256716c3 100644 --- a/parsifal/reviews/reporting/views.py +++ b/parsifal/reviews/reporting/views.py @@ -1,34 +1,42 @@ -# coding: utf-8 - from django.contrib.auth.decorators import login_required -from django.core.urlresolvers import reverse as r -from django.shortcuts import render, redirect, get_object_or_404 from django.http import HttpResponse +from django.shortcuts import get_object_or_404, redirect, render +from django.urls import reverse as r -from parsifal.reviews.models import * from parsifal.reviews.decorators import author_required +from parsifal.reviews.models import Review from parsifal.reviews.reporting.export import export_review_to_docx @author_required @login_required def reporting(request, username, review_name): - return redirect(r('export', args=(username, review_name,))) + return redirect( + r( + "export", + args=( + username, + review_name, + ), + ) + ) + @author_required @login_required def export(request, username, review_name): review = get_object_or_404(Review, name=review_name, author__username__iexact=username) - return render(request, 'reporting/export.html', { 'review': review }) + return render(request, "reporting/export.html", {"review": review}) + @author_required @login_required def download_docx(request): - review_id = request.GET.get('review-id') + review_id = request.GET.get("review-id") review = get_object_or_404(Review, pk=review_id) - sections = request.GET.getlist('export') + sections = request.GET.getlist("export") document = export_review_to_docx(review, sections) - response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document') - response['Content-Disposition'] = u'attachment; filename={0}.docx'.format(review.name) + response = HttpResponse(content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document") + response["Content-Disposition"] = "attachment; filename={0}.docx".format(review.name) document.save(response) return response diff --git a/parsifal/reviews/settings/__init__.py b/parsifal/reviews/settings/__init__.py index e69de29b..8ab472e7 100644 --- a/parsifal/reviews/settings/__init__.py +++ b/parsifal/reviews/settings/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.reviews.settings.apps.SettingsConfig" diff --git a/parsifal/reviews/settings/apps.py b/parsifal/reviews/settings/apps.py new file mode 100644 index 00000000..3793bff0 --- /dev/null +++ b/parsifal/reviews/settings/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class SettingsConfig(AppConfig): + name = "parsifal.reviews.settings" + verbose_name = _("Reviews: Settings") diff --git a/parsifal/reviews/settings/forms.py b/parsifal/reviews/settings/forms.py index 3ddd6e6b..1c5d8ec5 100644 --- a/parsifal/reviews/settings/forms.py +++ b/parsifal/reviews/settings/forms.py @@ -1,5 +1,3 @@ -# coding: utf-8 - from django import forms from parsifal.reviews.models import Review @@ -7,11 +5,14 @@ class ReviewSettingsForm(forms.ModelForm): name = forms.SlugField( - widget=forms.TextInput(attrs={ 'class': 'form-control' }), - label='URL', - help_text='Only letters, numbers, underscores or hyphens are allowed.', - max_length=255) + widget=forms.TextInput(attrs={"class": "form-control"}), + label="URL", + help_text="Only letters, numbers, underscores or hyphens are allowed.", + max_length=255, + ) class Meta: model = Review - fields = ['name',] + fields = [ + "name", + ] diff --git a/parsifal/reviews/settings/models.py b/parsifal/reviews/settings/models.py index 71a83623..e69de29b 100644 --- a/parsifal/reviews/settings/models.py +++ b/parsifal/reviews/settings/models.py @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/parsifal/reviews/settings/views.py b/parsifal/reviews/settings/views.py index fb871fa8..72970a5f 100644 --- a/parsifal/reviews/settings/views.py +++ b/parsifal/reviews/settings/views.py @@ -1,17 +1,14 @@ -# coding: utf-8 - -from django.core.urlresolvers import reverse as r -from django.contrib.auth.decorators import login_required -from django.shortcuts import render_to_response, redirect, render -from django.template import RequestContext -from django.http import HttpResponse, HttpResponseBadRequest -from django.template.defaultfilters import slugify from django.contrib import messages +from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User +from django.http import HttpResponseBadRequest +from django.shortcuts import redirect, render +from django.urls import reverse as r +from django.utils.text import slugify from django.views.decorators.http import require_POST -from parsifal.reviews.models import Review from parsifal.reviews.decorators import main_author_required +from parsifal.reviews.models import Review from parsifal.reviews.settings.forms import ReviewSettingsForm @@ -19,7 +16,7 @@ @login_required def settings(request, username, review_name): review = Review.objects.get(name=review_name, author__username__iexact=username) - if request.method == 'POST': + if request.method == "POST": form = ReviewSettingsForm(request.POST, instance=review) if form.is_valid(): name = slugify(form.instance.name) @@ -28,31 +25,28 @@ def settings(request, username, review_name): i = 0 while Review.objects.filter(name=unique_name, author__username=review.author.username): i = i + 1 - unique_name = u'{0}-{1}'.format(name, i) + unique_name = "{0}-{1}".format(name, i) form.instance.name = unique_name review = form.save() - messages.success(request, u'Review was saved successfully.') - return redirect(r('settings', args=(review.author.username, unique_name))) + messages.success(request, "Review was saved successfully.") + return redirect(r("settings", args=(review.author.username, unique_name))) else: form = ReviewSettingsForm(instance=review) - return render(request, 'settings/review_settings.html', { - 'review': review, - 'form': form - }) + return render(request, "settings/review_settings.html", {"review": review, "form": form}) @main_author_required @login_required def transfer(request): try: - review_id = request.POST['review-id'] - transfer_user_username = request.POST['transfer-user'] + review_id = request.POST["review-id"] + transfer_user_username = request.POST["transfer-user"] review = Review.objects.get(pk=review_id) try: transfer_user = User.objects.get(username=transfer_user_username) - except Exception, e: - messages.warning(request, 'User not found.') - return redirect('settings', review.author.username, review.name) + except Exception: + messages.warning(request, "User not found.") + return redirect("settings", review.author.username, review.name) current_user = request.user if current_user != transfer_user: @@ -61,27 +55,26 @@ def transfer(request): review.author = transfer_user review.co_authors.add(current_user) review.save() - return redirect('review', review.author.username, review.name) + return redirect("review", review.author.username, review.name) else: - messages.warning(request, 'Hey! You can\'t transfer the review to yourself.') - return redirect('settings', review.author.username, review.name) + messages.warning(request, "Hey! You can't transfer the review to yourself.") + return redirect("settings", review.author.username, review.name) - except Exception, e: - return HttpResponseBadRequest('Something went wrong.') + except Exception: + return HttpResponseBadRequest("Something went wrong.") @main_author_required @login_required @require_POST def delete(request): - review_id = request.POST.get('review-id') + review_id = request.POST.get("review-id") review = Review.objects.get(pk=review_id) - username = review.author.username sources = review.sources.all() for source in sources: if not source.is_default: review.sources.remove(source) source.delete() review.delete() - messages.success(request, u'The review was deleted successfully.') - return redirect(r('reviews', args=(review.author.username,))) + messages.success(request, "The review was deleted successfully.") + return redirect(r("reviews", args=(review.author.username,))) diff --git a/parsifal/reviews/urls.py b/parsifal/reviews/urls.py index b529ab68..90fa73e8 100644 --- a/parsifal/reviews/urls.py +++ b/parsifal/reviews/urls.py @@ -1,15 +1,14 @@ -# coding: utf-8 +from django.urls import include, path -from django.conf.urls import patterns, include, url +from parsifal.reviews import views - -urlpatterns = patterns('parsifal.reviews.views', - url(r'^new/$', 'new', name='new'), - url(r'^add_author/$', 'add_author_to_review', name='add_author_to_review'), - url(r'^remove_author/$', 'remove_author_from_review', name='remove_author_from_review'), - url(r'^save_description/$', 'save_description', name='save_description'), - url(r'^leave/$', 'leave', name='leave'), - url(r'^planning/', include('parsifal.reviews.planning.urls', namespace='planning')), - url(r'^conducting/', include('parsifal.reviews.conducting.urls', namespace='conducting')), - url(r'^reporting/', include('parsifal.reviews.reporting.urls', namespace='reporting')), -) +urlpatterns = [ + path("new/", views.new, name="new"), + path("add_author/", views.add_author_to_review, name="add_author_to_review"), + path("remove_author/", views.remove_author_from_review, name="remove_author_from_review"), + path("save_description/", views.save_description, name="save_description"), + path("leave/", views.leave, name="leave"), + path("planning/", include("parsifal.reviews.planning.urls", namespace="planning")), + path("conducting/", include("parsifal.reviews.conducting.urls", namespace="conducting")), + path("reporting/", include("parsifal.reviews.reporting.urls", namespace="reporting")), +] diff --git a/parsifal/reviews/views.py b/parsifal/reviews/views.py index 04c38228..0ed8dd09 100644 --- a/parsifal/reviews/views.py +++ b/parsifal/reviews/views.py @@ -1,21 +1,16 @@ -# coding: utf-8 - -from django.core.urlresolvers import reverse as r -from django.template.defaultfilters import slugify -from django.db.models import Q -from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden from django.contrib import messages from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User -from django.shortcuts import render_to_response, redirect, get_object_or_404, render -from django.template import RequestContext -from django.utils.html import escape -from django.views.decorators.http import require_POST from django.core.mail import EmailMultiAlternatives +from django.http import HttpResponse, HttpResponseBadRequest +from django.shortcuts import get_object_or_404, redirect, render +from django.urls import reverse as r +from django.utils.text import slugify +from django.views.decorators.http import require_POST -from parsifal.reviews.models import Review -from parsifal.reviews.decorators import main_author_required, author_required +from parsifal.reviews.decorators import author_required, main_author_required from parsifal.reviews.forms import CreateReviewForm, ReviewForm +from parsifal.reviews.models import Review def reviews(request, username): @@ -30,18 +25,22 @@ def reviews(request, username): user_reviews = user.profile.get_reviews() - context = RequestContext(request, { - 'user_reviews': user_reviews, - 'page_user': user, - 'is_following': is_following, - 'following_count': following_count, - 'followers_count': followers_count - }) - return render_to_response('reviews/reviews.html', context) + return render( + request, + "reviews/reviews.html", + { + "user_reviews": user_reviews, + "page_user": user, + "is_following": is_following, + "following_count": following_count, + "followers_count": followers_count, + }, + ) + @login_required def new(request): - if request.method == 'POST': + if request.method == "POST": form = CreateReviewForm(request.POST) if form.is_valid(): form.instance.author = request.user @@ -51,40 +50,37 @@ def new(request): i = 0 while Review.objects.filter(name=unique_name, author__username=request.user.username): i = i + 1 - unique_name = u'{0}-{1}'.format(name, i) + unique_name = "{0}-{1}".format(name, i) form.instance.name = unique_name review = form.save() - messages.success(request, u'Review created successfully.') - return redirect(r('review', args=(review.author.username, review.name))) + messages.success(request, "Review created successfully.") + return redirect(r("review", args=(review.author.username, review.name))) else: form = CreateReviewForm() - return render(request, 'reviews/new.html', { 'form': form }) + return render(request, "reviews/new.html", {"form": form}) @author_required @login_required def review(request, username, review_name): review = Review.objects.get(name=review_name, author__username__iexact=username) - if request.method == 'POST': + if request.method == "POST": form = ReviewForm(request.POST, instance=review) if form.is_valid(): review = form.save() - messages.success(request, u'Review was saved successfully.') - return redirect(r('review', args=(review.author.username, review.name))) + messages.success(request, "Review was saved successfully.") + return redirect(r("review", args=(review.author.username, review.name))) else: form = ReviewForm(instance=review) - return render(request, 'reviews/review.html', { - 'review': review, - 'form': form - }) + return render(request, "reviews/review.html", {"review": review, "form": form}) @main_author_required @login_required @require_POST def add_author_to_review(request): - emails = request.POST.getlist('users') - review_id = request.POST.get('review-id') + emails = request.POST.getlist("users") + review_id = request.POST.get("review-id") review = get_object_or_404(Review, pk=review_id) authors_added = [] authors_invited = [] @@ -100,35 +96,41 @@ def add_author_to_review(request): except User.DoesNotExist: authors_invited.append(email) - subject = u'{0} wants to add you as co-author on the systematic literature review {1}'.format(inviter_name, review.title) - from_email = u'{0} via Parsifal '.format(inviter_name) - - text_content = u'''Hi {0}, - {1} invited you to a Parsifal Systematic Literature Review called "{2}". - View the review at https://parsif.al/{3}/{4}/'''.format(email, inviter_name, review.title, request.user.username, review.name) + subject = "{0} wants to add you as co-author on the systematic literature review {1}".format( + inviter_name, review.title + ) + from_email = "{0} via Parsifal ".format(inviter_name) + + text_content = """Hi {0}, + {1} invited you to a Parsifal Systematic Literature Review called "{2}". + View the review at https://parsif.al/{3}/{4}/""".format( + email, inviter_name, review.title, request.user.username, review.name + ) - html_content = u'''

Hi {0},

+ html_content = """

Hi {0},

{1} invited you to a Parsifal Systematic Literature Review called "{2}".

View the review at https://parsif.al/{3}/{4}/

Sincerely,

-

The Parsifal Team

'''.format(email, inviter_name, review.title, request.user.username, review.name) +

The Parsifal Team

""".format( + email, inviter_name, review.title, request.user.username, review.name + ) msg = EmailMultiAlternatives(subject, text_content, from_email, [email]) - msg.attach_alternative(html_content, 'text/html') + msg.attach_alternative(html_content, "text/html") msg.send() review.save() if not authors_added and not authors_invited: - messages.info(request, u'No author invited or added to the review. Nothing really changed.') - + messages.info(request, "No author invited or added to the review. Nothing really changed.") + if authors_added: - messages.success(request, u'The authors {0} were added successfully.'.format(u', '.join(authors_added))) + messages.success(request, "The authors {0} were added successfully.".format(", ".join(authors_added))) if authors_invited: - messages.success(request, u'{0} were invited successfully.'.format(u', '.join(authors_invited))) + messages.success(request, "{0} were invited successfully.".format(", ".join(authors_invited))) - return redirect(r('review', args=(review.author.username, review.name))) + return redirect(r("review", args=(review.author.username, review.name))) @main_author_required @@ -136,14 +138,14 @@ def add_author_to_review(request): @require_POST def remove_author_from_review(request): try: - author_id = request.POST.get('user-id') - review_id = request.POST.get('review-id') + author_id = request.POST.get("user-id") + review_id = request.POST.get("review-id") author = User.objects.get(pk=author_id) review = Review.objects.get(pk=review_id) review.co_authors.remove(author) review.save() return HttpResponse() - except: + except Exception: return HttpResponseBadRequest() @@ -151,24 +153,28 @@ def remove_author_from_review(request): @login_required def save_description(request): try: - review_id = request.POST['review-id'] - description = request.POST['description'] + review_id = request.POST["review-id"] + description = request.POST["description"] review = Review.objects.get(pk=review_id) if len(description) > 500: - return HttpResponseBadRequest('The review description should not exceed 500 characters. The given description have %s characters.' % len(description)) + return HttpResponseBadRequest( + "The review description should not exceed 500 characters. The given description have %s characters." + % len(description) + ) else: review.description = description review.save() - return HttpResponse('Your review has been saved successfully!') - except: + return HttpResponse("Your review has been saved successfully!") + except Exception: return HttpResponseBadRequest() + @author_required @login_required def leave(request): - review_id = request.POST.get('review-id') + review_id = request.POST.get("review-id") review = get_object_or_404(Review, pk=review_id) review.co_authors.remove(request.user) review.save() - messages.add_message(request, messages.SUCCESS, u'You successfully left the review {0}.'.format(review.title)) - return redirect('/' + request.user.username + '/') + messages.add_message(request, messages.SUCCESS, "You successfully left the review {0}.".format(review.title)) + return redirect("/" + request.user.username + "/") diff --git a/parsifal/search/admin.py b/parsifal/search/admin.py deleted file mode 100644 index 8c38f3f3..00000000 --- a/parsifal/search/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/parsifal/search/models.py b/parsifal/search/models.py deleted file mode 100644 index 71a83623..00000000 --- a/parsifal/search/models.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/parsifal/search/tests.py b/parsifal/search/tests.py deleted file mode 100644 index 7ce503c2..00000000 --- a/parsifal/search/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/parsifal/search/views.py b/parsifal/search/views.py deleted file mode 100644 index 91ea44a2..00000000 --- a/parsifal/search/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/parsifal/settings.py b/parsifal/settings.py deleted file mode 100644 index b8994768..00000000 --- a/parsifal/settings.py +++ /dev/null @@ -1,124 +0,0 @@ -from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS as TCP -from unipath import Path -import dj_database_url -from decouple import config, Csv -from mendeley import Mendeley - -SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') -CSRF_COOKIE_SECURE = config('CSRF_COOKIE_SECURE', default=True, cast=bool) -SESSION_COOKIE_SECURE = config('SESSION_COOKIE_SECURE', default=True, cast=bool) - -PROJECT_DIR = Path(__file__).parent - -DEBUG = config('DEBUG', default=False, cast=bool) -TEMPLATE_DEBUG = DEBUG - -DATABASES = { - 'default': dj_database_url.config( - default = config('DATABASE_URL')) -} - -ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv()) - -ADMINS = ( - ('Vitor Freitas', 'vitorfs@gmail.com'), -) - -MANAGERS = ADMINS - -TIME_ZONE = 'UTC' -LANGUAGE_CODE = 'en-us' - -USE_I18N = True -USE_L10N = True -USE_TZ = True - -MEDIA_ROOT = PROJECT_DIR.parent.parent.child('media') -MEDIA_URL = '/media/' -FILE_UPLOAD_TEMP_DIR = '/tmp/' -FILE_UPLOAD_PERMISSIONS = 0644 -FILE_UPLOAD_MAX_MEMORY_SIZE = 33554432 - -STATIC_ROOT = PROJECT_DIR.parent.parent.child('static') -STATIC_URL = '/static/' -STATICFILES_DIRS = ( - PROJECT_DIR.child('static'), -) - -SECRET_KEY = config('SECRET_KEY') - -MIDDLEWARE_CLASSES = ( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django.middleware.security.SecurityMiddleware', -) - -ROOT_URLCONF = 'parsifal.urls' - -WSGI_APPLICATION = 'parsifal.wsgi.application' - -TEMPLATE_DIRS = ( - PROJECT_DIR.child('templates'), -) - -TEMPLATE_CONTEXT_PROCESSORS = TCP + ( - 'django.core.context_processors.request', -) - -INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.admin', - 'django.contrib.humanize', - - 'parsifal.reviews', - 'parsifal.reviews.planning', - 'parsifal.reviews.conducting', - 'parsifal.reviews.reporting', - 'parsifal.reviews.settings', - 'parsifal.account_settings', - 'parsifal.activities', - 'parsifal.authentication', - 'parsifal.blog', - 'parsifal.core', - 'parsifal.help', - 'parsifal.library', - 'parsifal.search', -) - -LOGIN_URL = '/signin/' -LOGOUT_URL = '/signout/' - -EMAIL_BACKEND = config('EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend') -EMAIL_FILE_PATH = PROJECT_DIR.parent.child('maildumps') -EMAIL_HOST = config('EMAIL_HOST') -EMAIL_PORT = config('EMAIL_PORT', cast=int) -EMAIL_HOST_USER = config('EMAIL_HOST_USER') -EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD') -EMAIL_USE_TLS = config('EMAIL_USE_TLS', cast=bool) -DEFAULT_FROM_EMAIL = 'Parsifal Team ' -EMAIL_SUBJECT_PREFIX = '[Parsifal] ' -SERVER_EMAIL = 'application@parsif.al' - -MENDELEY_ID = config('MENDELEY_ID', cast=int) -MENDELEY_SECRET = config('MENDELEY_SECRET') -MENDELEY_REDIRECT_URI = config('MENDELEY_REDIRECT_URI') -MENDELEY = Mendeley(MENDELEY_ID, client_secret=MENDELEY_SECRET, redirect_uri=MENDELEY_REDIRECT_URI) - -DROPBOX_APP_KEY = config('DROPBOX_APP_KEY') -DROPBOX_SECRET = config('DROPBOX_SECRET') -DROPBOX_REDIRECT_URI = config('DROPBOX_REDIRECT_URI') - -ELSEVIER_API_KEY = config('ELSEVIER_API_KEY') - -ABSOLUTE_URL_OVERRIDES = { - 'auth.user': lambda u: '/%s/' % u.username, -} diff --git a/parsifal/search/__init__.py b/parsifal/settings/__init__.py similarity index 100% rename from parsifal/search/__init__.py rename to parsifal/settings/__init__.py diff --git a/parsifal/settings/base.py b/parsifal/settings/base.py new file mode 100644 index 00000000..06c758ee --- /dev/null +++ b/parsifal/settings/base.py @@ -0,0 +1,177 @@ +from pathlib import Path + +from django.utils.translation import gettext_lazy as _ + +import dj_database_url +from decouple import Csv, config + +# ============================================================================== +# CORE SETTINGS +# ============================================================================== + +BASE_DIR = Path(__file__).resolve(strict=True).parent.parent + +SECRET_KEY = config("SECRET_KEY", default="parsifal.settings.local") + +DEBUG = config("DEBUG", default=True, cast=bool) + +ALLOWED_HOSTS = config("ALLOWED_HOSTS", default="127.0.0.1,localhost", cast=Csv()) + +ADMINS = (("Vitor Freitas", "vitorfs@gmail.com"),) + +MANAGERS = ADMINS + +INSTALLED_APPS = ( + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.admin", + "django.contrib.humanize", + "parsifal.reviews", + "parsifal.reviews.planning", + "parsifal.reviews.conducting", + "parsifal.reviews.reporting", + "parsifal.reviews.settings", + "parsifal.account_settings", + "parsifal.activities", + "parsifal.authentication", + "parsifal.blog", + "parsifal.core", + "parsifal.help", + "parsifal.library", + "parsifal.search", +) + +INTERNAL_IPS = ["127.0.0.1"] + +ROOT_URLCONF = "parsifal.urls" + +WSGI_APPLICATION = "parsifal.wsgi.application" + +SITE_ID = 1 + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + + +# ============================================================================== +# MIDDLEWARE SETTINGS +# ============================================================================== + +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", + "django.contrib.redirects.middleware.RedirectFallbackMiddleware", +] + + +# ============================================================================== +# TEMPLATES SETTINGS +# ============================================================================== + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [str(BASE_DIR / "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", + ], + }, + }, +] + +# ============================================================================== +# DATABASES SETTINGS +# ============================================================================== + +DATABASES = { + "default": dj_database_url.config( + default=config("DATABASE_URL", default="postgres://richardwagner:holygrail@localhost:5432/parsifal"), + conn_max_age=600, + ) +} + +# ============================================================================== +# AUTHENTICATION AND AUTHORIZATION SETTINGS +# ============================================================================== + +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", + }, +] + +ABSOLUTE_URL_OVERRIDES = { + "auth.user": lambda u: "/%s/" % u.username, +} + +LOGIN_URL = "/signin/" +LOGOUT_URL = "/signout/" + + +# ============================================================================== +# INTERNATIONALIZATION AND LOCALIZATION SETTINGS +# ============================================================================== + +LANGUAGE_CODE = config("LANGUAGE_CODE", default="en-us") + +TIME_ZONE = config("TIME_ZONE", default="UTC") + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + +LANGUAGES = (("en-us", _("English")), ("pt-br", _("Brazilian Portuguese"))) + +LOCALE_PATHS = [str(BASE_DIR / "locale")] + + +# ============================================================================== +# STATIC FILES SETTINGS +# ============================================================================== + +STATIC_URL = "/static/" +STATIC_ROOT = BASE_DIR.parent.parent / "static" +STATICFILES_DIRS = [str(BASE_DIR / "static")] +STATICFILES_FINDERS = ( + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", +) + + +# ============================================================================== +# MEDIA FILES SETTINGS +# ============================================================================== + +MEDIA_URL = "/media/" +MEDIA_ROOT = BASE_DIR.parent.parent / "media/" +DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage" + + +# ============================================================================== +# THIRD-PARTY APPS +# ============================================================================== + +ELSEVIER_API_KEY = config("ELSEVIER_API_KEY") diff --git a/parsifal/settings/local.py b/parsifal/settings/local.py new file mode 100644 index 00000000..8d1c0020 --- /dev/null +++ b/parsifal/settings/local.py @@ -0,0 +1,18 @@ +# flake8: noqa + +from .base import * + +# ============================================================================== +# CORE SETTINGS +# ============================================================================== + +INSTALLED_APPS += ["debug_toolbar"] + +MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") + + +# ============================================================================== +# EMAIL SETTINGS +# ============================================================================== + +EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" diff --git a/parsifal/settings/production.py b/parsifal/settings/production.py new file mode 100644 index 00000000..ba600088 --- /dev/null +++ b/parsifal/settings/production.py @@ -0,0 +1,33 @@ +# flake8: noqa + +from .base import * + +# ============================================================================== +# SECURITY SETTINGS +# ============================================================================== + +CSRF_COOKIE_SECURE = True +CSRF_COOKIE_HTTPONLY = True + +SECURE_HSTS_SECONDS = 60 * 60 * 24 * 7 * 52 # one year +SECURE_HSTS_INCLUDE_SUBDOMAINS = True +SECURE_SSL_REDIRECT = True +SECURE_BROWSER_XSS_FILTER = True +SECURE_CONTENT_TYPE_NOSNIFF = True +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") + +SESSION_COOKIE_SECURE = True + + +# ============================================================================== +# EMAIL SETTINGS +# ============================================================================== + +DEFAULT_FROM_EMAIL = "Parsifal Team " +EMAIL_SUBJECT_PREFIX = "[Parsifal] " +SERVER_EMAIL = "application@parsif.al" +EMAIL_HOST = config("EMAIL_HOST") +EMAIL_PORT = config("EMAIL_PORT", cast=int) +EMAIL_HOST_USER = config("EMAIL_HOST_USER") +EMAIL_HOST_PASSWORD = config("EMAIL_HOST_PASSWORD") +EMAIL_USE_TLS = config("EMAIL_USE_TLS", cast=bool) diff --git a/parsifal/settings/tests.py b/parsifal/settings/tests.py new file mode 100644 index 00000000..6d476a31 --- /dev/null +++ b/parsifal/settings/tests.py @@ -0,0 +1,16 @@ +# flake8: noqa + +from .base import * + +PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] + + +class DisableMigrations: + def __contains__(self, item): + return True + + def __getitem__(self, item): + return None + + +MIGRATION_MODULES = DisableMigrations() diff --git a/parsifal/test_settings.py b/parsifal/test_settings.py deleted file mode 100644 index 96eb330c..00000000 --- a/parsifal/test_settings.py +++ /dev/null @@ -1,15 +0,0 @@ -from settings import * - -DATABASES['default'] = { 'ENGINE': 'django.db.backends.sqlite3' } - -PASSWORD_HASHERS = ( - 'django.contrib.auth.hashers.MD5PasswordHasher', -) - -class DisableMigrations(object): - def __contains__(self, item): - return True - def __getitem__(self, item): - return 'notmigrations' - -MIGRATION_MODULES = DisableMigrations() diff --git a/parsifal/urls.py b/parsifal/urls.py index e8740a13..fc3e4179 100644 --- a/parsifal/urls.py +++ b/parsifal/urls.py @@ -1,61 +1,108 @@ # coding: utf-8 -from django.conf.urls import patterns, include, url from django.conf import settings +from django.conf.urls import include, patterns, url from django.conf.urls.static import static -from django.views.generic import TemplateView from django.contrib import admin - +from django.views.generic import TemplateView admin.autodiscover() -urlpatterns = patterns('parsifal', - url(r'^$', 'core.views.home', name='home'), - url(r'^about/$', TemplateView.as_view(template_name='core/about.html'), name='about'), - url(r'^signup/$', 'authentication.views.signup', name='signup'), - url(r'^signin/$', 'authentication.views.signin', name='signin'), - url(r'^signout/$', 'authentication.views.signout', name='signout'), - url(r'^reset/$', 'authentication.views.reset', name='reset'), - url(r'^reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', 'authentication.views.reset_confirm', name='password_reset_confirm'), - url(r'^success/$', 'authentication.views.success', name='success'), - url(r'^reviews/', include('parsifal.reviews.urls', namespace='reviews')), - url(r'^activity/', include('parsifal.activities.urls', namespace='activities')), - url(r'^blog/', include('parsifal.blog.urls', namespace='blog')), - url(r'^help/', include('parsifal.help.urls', namespace='help')), - url(r'^library/', include('parsifal.library.urls', namespace='library')), - url(r'^settings/', include('parsifal.account_settings.urls', namespace='settings')), - url(r'^review_settings/transfer/$', 'reviews.settings.views.transfer', name='transfer_review'), - url(r'^review_settings/delete/$', 'reviews.settings.views.delete', name='delete_review'), - url(r'^admin/', include(admin.site.urls)), - url(r'^sitemap.xml$', TemplateView.as_view(template_name='sitemap.xml', content_type='application/xml')), - url(r'^robots.txt$', TemplateView.as_view(template_name='robots.txt', content_type='text/plain')), - url(r'^(?P[^/]+)/following/$', 'activities.views.following', name='following'), - url(r'^(?P[^/]+)/followers/$', 'activities.views.followers', name='followers'), - +urlpatterns = patterns( + "parsifal", + url(r"^$", "core.views.home", name="home"), + url(r"^about/$", TemplateView.as_view(template_name="core/about.html"), name="about"), + url(r"^signup/$", "authentication.views.signup", name="signup"), + url(r"^signin/$", "authentication.views.signin", name="signin"), + url(r"^signout/$", "authentication.views.signout", name="signout"), + url(r"^reset/$", "authentication.views.reset", name="reset"), + url( + r"^reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$", + "authentication.views.reset_confirm", + name="password_reset_confirm", + ), + url(r"^success/$", "authentication.views.success", name="success"), + url(r"^reviews/", include("parsifal.reviews.urls", namespace="reviews")), + url(r"^activity/", include("parsifal.activities.urls", namespace="activities")), + url(r"^blog/", include("parsifal.blog.urls", namespace="blog")), + url(r"^help/", include("parsifal.help.urls", namespace="help")), + url(r"^library/", include("parsifal.library.urls", namespace="library")), + url(r"^settings/", include("parsifal.account_settings.urls", namespace="settings")), + url(r"^review_settings/transfer/$", "reviews.settings.views.transfer", name="transfer_review"), + url(r"^review_settings/delete/$", "reviews.settings.views.delete", name="delete_review"), + url(r"^admin/", include(admin.site.urls)), + url(r"^sitemap.xml$", TemplateView.as_view(template_name="sitemap.xml", content_type="application/xml")), + url(r"^robots.txt$", TemplateView.as_view(template_name="robots.txt", content_type="text/plain")), + url(r"^(?P[^/]+)/following/$", "activities.views.following", name="following"), + url(r"^(?P[^/]+)/followers/$", "activities.views.followers", name="followers"), # Review URLs - url(r'^(?P[^/]+)/(?P[^/]+)/$', 'reviews.views.review', name='review'), - url(r'^(?P[^/]+)/(?P[^/]+)/settings/$', 'reviews.settings.views.settings', name='settings'), - + url(r"^(?P[^/]+)/(?P[^/]+)/$", "reviews.views.review", name="review"), + url(r"^(?P[^/]+)/(?P[^/]+)/settings/$", "reviews.settings.views.settings", name="settings"), # Planning Phase - url(r'^(?P[^/]+)/(?P[^/]+)/planning/$', 'reviews.planning.views.planning', name='planning'), - url(r'^(?P[^/]+)/(?P[^/]+)/planning/protocol/$', 'reviews.planning.views.protocol', name='protocol'), - url(r'^(?P[^/]+)/(?P[^/]+)/planning/quality/$', 'reviews.planning.views.quality_assessment_checklist', name='quality_assessment_checklist'), - url(r'^(?P[^/]+)/(?P[^/]+)/planning/extraction/$', 'reviews.planning.views.data_extraction_form', name='data_extraction_form'), - + url(r"^(?P[^/]+)/(?P[^/]+)/planning/$", "reviews.planning.views.planning", name="planning"), + url( + r"^(?P[^/]+)/(?P[^/]+)/planning/protocol/$", + "reviews.planning.views.protocol", + name="protocol", + ), + url( + r"^(?P[^/]+)/(?P[^/]+)/planning/quality/$", + "reviews.planning.views.quality_assessment_checklist", + name="quality_assessment_checklist", + ), + url( + r"^(?P[^/]+)/(?P[^/]+)/planning/extraction/$", + "reviews.planning.views.data_extraction_form", + name="data_extraction_form", + ), # Conducting Phase - url(r'^(?P[^/]+)/(?P[^/]+)/conducting/$', 'reviews.conducting.views.conducting', name='conducting'), - url(r'^(?P[^/]+)/(?P[^/]+)/conducting/search/$', 'reviews.conducting.views.search_studies', name='search_studies'), - url(r'^(?P[^/]+)/(?P[^/]+)/conducting/import/$', 'reviews.conducting.views.import_studies', name='import_studies'), - url(r'^(?P[^/]+)/(?P[^/]+)/conducting/studies/$', 'reviews.conducting.views.study_selection', name='study_selection'), - url(r'^(?P[^/]+)/(?P[^/]+)/conducting/quality/$', 'reviews.conducting.views.quality_assessment', name='quality_assessment'), - url(r'^(?P[^/]+)/(?P[^/]+)/conducting/extraction/$', 'reviews.conducting.views.data_extraction', name='data_extraction'), - url(r'^(?P[^/]+)/(?P[^/]+)/conducting/analysis/$', 'reviews.conducting.views.data_analysis', name='data_analysis'), - + url( + r"^(?P[^/]+)/(?P[^/]+)/conducting/$", + "reviews.conducting.views.conducting", + name="conducting", + ), + url( + r"^(?P[^/]+)/(?P[^/]+)/conducting/search/$", + "reviews.conducting.views.search_studies", + name="search_studies", + ), + url( + r"^(?P[^/]+)/(?P[^/]+)/conducting/import/$", + "reviews.conducting.views.import_studies", + name="import_studies", + ), + url( + r"^(?P[^/]+)/(?P[^/]+)/conducting/studies/$", + "reviews.conducting.views.study_selection", + name="study_selection", + ), + url( + r"^(?P[^/]+)/(?P[^/]+)/conducting/quality/$", + "reviews.conducting.views.quality_assessment", + name="quality_assessment", + ), + url( + r"^(?P[^/]+)/(?P[^/]+)/conducting/extraction/$", + "reviews.conducting.views.data_extraction", + name="data_extraction", + ), + url( + r"^(?P[^/]+)/(?P[^/]+)/conducting/analysis/$", + "reviews.conducting.views.data_analysis", + name="data_analysis", + ), # Reporting Phase - url(r'^(?P[^/]+)/(?P[^/]+)/reporting/$', 'reviews.reporting.views.reporting', name='reporting'), - url(r'^(?P[^/]+)/(?P[^/]+)/reporting/export/$', 'reviews.reporting.views.export', name='export'), - - url(r'^(?P[^/]+)/$', 'reviews.views.reviews', name='reviews'), + url( + r"^(?P[^/]+)/(?P[^/]+)/reporting/$", + "reviews.reporting.views.reporting", + name="reporting", + ), + url( + r"^(?P[^/]+)/(?P[^/]+)/reporting/export/$", + "reviews.reporting.views.export", + name="export", + ), + url(r"^(?P[^/]+)/$", "reviews.views.reviews", name="reviews"), ) if settings.DEBUG: diff --git a/parsifal/utils/elsevier/client.py b/parsifal/utils/elsevier/client.py index b5ada503..b230d74f 100644 --- a/parsifal/utils/elsevier/client.py +++ b/parsifal/utils/elsevier/client.py @@ -1,30 +1,25 @@ import requests -from exceptions import * +from parsifal.utils.elsevier.exceptions import ElsevierException, ElsevierInvalidRequest, ElsevierQuotaExceeded class ElsevierClient(object): - def __init__(self, - api_key, - host='http://api.elsevier.com/content'): + def __init__(self, api_key, host="http://api.elsevier.com/content"): self.api_key = api_key self.host = host - def _request(self, endpoint, params, requestType='GET'): - response= None - if self.api_key is not None: - url = u'{0}{1}'.format(self.host, endpoint) - params['apiKey'] = self.api_key - if requestType == 'POST': + def _request(self, endpoint, params, requestType="GET"): + if self.api_key: + url = "{0}{1}".format(self.host, endpoint) + params["apiKey"] = self.api_key + if requestType == "POST": response = requests.post(url, params) else: response = requests.get(url, params) - else: - raise ElsevierException('No API Key.') - return self._parse_response(response) + return self._parse_response(response) + raise ElsevierException("No API Key.") def _parse_response(self, response): - print response.status_code if response.status_code == 200: return response.json() elif response.status_code == 400: @@ -32,9 +27,8 @@ def _parse_response(self, response): elif response.status_code == 429: raise ElsevierQuotaExceeded - def search_scopus(self, params): - return self._request('/search/scopus', params) + return self._request("/search/scopus", params) def search_science_direct(self, params): - return self._request('/search/scidir', params) + return self._request("/search/scidir", params) diff --git a/parsifal/utils/elsevier/exceptions.py b/parsifal/utils/elsevier/exceptions.py index c6789d95..30cc6b5e 100644 --- a/parsifal/utils/elsevier/exceptions.py +++ b/parsifal/utils/elsevier/exceptions.py @@ -1,14 +1,18 @@ class ElsevierException(Exception): pass + class ElsevierInvalidRequest(ElsevierException): - ''' + """ Status Code 400 - ''' + """ + pass + class ElsevierQuotaExceeded(ElsevierException): - ''' + """ Status Code 429 - ''' - pass \ No newline at end of file + """ + + pass diff --git a/parsifal/wsgi.py b/parsifal/wsgi.py index 8379524f..a00d9c9c 100644 --- a/parsifal/wsgi.py +++ b/parsifal/wsgi.py @@ -1,6 +1,7 @@ import os -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "parsifal.settings") from django.core.wsgi import get_wsgi_application +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "parsifal.settings.production") + application = get_wsgi_application() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..2db5001e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[tool.black] +line-length = 119 +target-version = ['py36', 'py37', 'py38'] +include = '\.pyi?$' +exclude = ''' +/( + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + + # The following are specific to Black, you probably don't want those. + | blib2to3 + | tests/data + | profiling + | migrations +)/ +''' diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 134bf93e..00000000 --- a/requirements.txt +++ /dev/null @@ -1,15 +0,0 @@ -Django==1.8.3 -Pillow==2.6.2 -Unipath==1.1 -bibtexparser==0.6.0 -dj-database-url==0.3.0 -django-extensions==1.5.5 -requests==2.7.0 -dropbox==2.2.0 -mendeley==0.3.2 -psycopg2==2.6 -python-decouple==2.3 -python-docx==0.8.5 -pytz==2015.4 -beautifulsoup4==4.3.2 -xlwt==1.1.2 \ No newline at end of file diff --git a/requirements/base.txt b/requirements/base.txt new file mode 100644 index 00000000..31adaa0c --- /dev/null +++ b/requirements/base.txt @@ -0,0 +1,12 @@ +Django==3.2.6 +Pillow==8.3.1 +bibtexparser==1.2.0 +dj-database-url==0.5.0 +django-extensions==3.1.3 +requests==2.26.0 +psycopg2-binary==2.9.1 +python-decouple==3.4 +python-docx==0.8.11 +pytz==2021.1 +beautifulsoup4==4.9.3 +xlwt==1.3.0 diff --git a/parsifal/search/migrations/__init__.py b/requirements/local.txt similarity index 100% rename from parsifal/search/migrations/__init__.py rename to requirements/local.txt diff --git a/requirements/production.txt b/requirements/production.txt new file mode 100644 index 00000000..b0dcc818 --- /dev/null +++ b/requirements/production.txt @@ -0,0 +1,3 @@ +-r base.txt + +gunicorn==20.1.0 diff --git a/requirements/tests.txt b/requirements/tests.txt new file mode 100644 index 00000000..f935fb34 --- /dev/null +++ b/requirements/tests.txt @@ -0,0 +1,8 @@ +-r base.txt + +coverage==5.5 +factory-boy==3.2.0 +flake8==3.9.2 +isort==5.9.3 +black==21.7b0 +tox==3.24.3 diff --git a/run_tests.sh b/run_tests.sh deleted file mode 100755 index 77fb9a80..00000000 --- a/run_tests.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -python manage.py test --settings=parsifal.test_settings --verbosity=2 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..1c4651ff --- /dev/null +++ b/setup.cfg @@ -0,0 +1,21 @@ +[flake8] +exclude = .git,.tox,*/migrations/* +max-line-length = 119 + +[coverage:run] +branch = true +source = parsifal +omit = */migrations/*,*/tests/* + +[isort] +force_grid_wrap=0 +use_parentheses=True +combine_as_imports = true +include_trailing_comma = true +line_length = 119 +multi_line_output = 3 +skip = migrations +default_section = THIRDPARTY +known_first_party = parsifal +known_django = django +sections=FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER From 4e78c571c8420a3b61c9e095ad4602d9826a033f Mon Sep 17 00:00:00 2001 From: Vitor Freitas Date: Sat, 28 Aug 2021 20:19:24 -0300 Subject: [PATCH 02/39] Fix pep8 issues --- Makefile | 2 +- manage.py | 0 parsifal/account_settings/urls.py | 8 +- parsifal/activities/urls.py | 2 + parsifal/activities/views.py | 2 +- parsifal/authentication/models.py | 2 +- parsifal/authentication/views.py | 3 +- parsifal/blog/urls.py | 2 + parsifal/help/urls.py | 2 + parsifal/library/urls.py | 2 + parsifal/reviews/conducting/urls.py | 2 + parsifal/reviews/conducting/views.py | 48 +++++---- parsifal/reviews/decorators.py | 8 +- parsifal/reviews/models.py | 2 +- parsifal/reviews/planning/urls.py | 2 + parsifal/reviews/planning/views.py | 68 ++++++------ parsifal/reviews/reporting/urls.py | 2 + parsifal/reviews/urls.py | 2 + parsifal/settings/base.py | 1 - parsifal/urls.py | 151 +++++++++++++-------------- requirements/local.txt | 4 + 21 files changed, 165 insertions(+), 150 deletions(-) mode change 100644 => 100755 manage.py diff --git a/Makefile b/Makefile index 1385e393..2f98518a 100644 --- a/Makefile +++ b/Makefile @@ -2,4 +2,4 @@ build: isort parsifal black parsifal flake8 parsifal - ./manage.py makemigrations --check --dry-run --settings=parsifal.test_settings + ./manage.py makemigrations --check --dry-run --settings=parsifal.settings.tests diff --git a/manage.py b/manage.py old mode 100644 new mode 100755 diff --git a/parsifal/account_settings/urls.py b/parsifal/account_settings/urls.py index 8541c0e3..d752a6ef 100644 --- a/parsifal/account_settings/urls.py +++ b/parsifal/account_settings/urls.py @@ -2,6 +2,8 @@ from parsifal.account_settings import views +app_name = "account_settings" + urlpatterns = [ path("", views.settings, name="settings"), path("profile/", views.profile, name="profile"), @@ -9,12 +11,6 @@ path("picture/", views.picture, name="picture"), path("password/", views.password, name="password"), path("connections/", views.connections, name="connections"), - path("mendeley_connection/", views.mendeley_connection, name="mendeley_connection"), - path("connect_mendeley/", views.connect_mendeley, name="connect_mendeley"), - path("disconnect_mendeley/", views.disconnect_mendeley, name="disconnect_mendeley"), - path("dropbox_connection/", views.dropbox_connection, name="dropbox_connection"), - path("connect_dropbox/", views.connect_dropbox, name="connect_dropbox"), - path("disconnect_dropbox/", views.disconnect_dropbox, name="disconnect_dropbox"), path("upload_picture/", views.upload_picture, name="upload_picture"), path("save_uploaded_picture/", views.save_uploaded_picture, name="save_uploaded_picture"), ] diff --git a/parsifal/activities/urls.py b/parsifal/activities/urls.py index 35e1a0fc..924aed62 100644 --- a/parsifal/activities/urls.py +++ b/parsifal/activities/urls.py @@ -2,6 +2,8 @@ from parsifal.activities import views +app_name = "activities" + urlpatterns = [ path("follow/", views.follow, name="follow"), path("unfollow/", views.unfollow, name="unfollow"), diff --git a/parsifal/activities/views.py b/parsifal/activities/views.py index c350336f..02f61e34 100644 --- a/parsifal/activities/views.py +++ b/parsifal/activities/views.py @@ -51,7 +51,7 @@ def update_followers_count(request): user = get_object_or_404(User, pk=user_id) followers_count = user.profile.get_followers_count() return HttpResponse(followers_count) - except: + except Exception: return HttpResponseBadRequest() diff --git a/parsifal/authentication/models.py b/parsifal/authentication/models.py index bfbeaf3b..6ee0df6f 100644 --- a/parsifal/authentication/models.py +++ b/parsifal/authentication/models.py @@ -48,7 +48,7 @@ def get_screen_name(self): return self.user.get_full_name() else: return self.user.username - except: + except Exception: return self.user.username def get_followers(self): diff --git a/parsifal/authentication/views.py b/parsifal/authentication/views.py index ecca0822..b37a1f64 100644 --- a/parsifal/authentication/views.py +++ b/parsifal/authentication/views.py @@ -21,7 +21,8 @@ def signup(request): request, messages.ERROR, _( - "There was some problems while creating your account. Please review some fields before submiting again." + "There was some problems while creating your account. " + "Please review some fields before submiting again." ), ) return render(request, "auth/signup.html", {"form": form}) diff --git a/parsifal/blog/urls.py b/parsifal/blog/urls.py index b146c1c8..9e5b54ed 100644 --- a/parsifal/blog/urls.py +++ b/parsifal/blog/urls.py @@ -2,6 +2,8 @@ from parsifal.blog import views +app_name = "blog" + urlpatterns = [ path("", views.entries, name="entries"), path("/", views.entry, name="entry"), diff --git a/parsifal/help/urls.py b/parsifal/help/urls.py index 57ef8b8b..a22dac6e 100644 --- a/parsifal/help/urls.py +++ b/parsifal/help/urls.py @@ -2,6 +2,8 @@ from parsifal.help import views +app_name = "help" + urlpatterns = [ path("", views.articles, name="articles"), path("search/", views.search, name="search"), diff --git a/parsifal/library/urls.py b/parsifal/library/urls.py index 9f0a5ea8..096f9ec8 100644 --- a/parsifal/library/urls.py +++ b/parsifal/library/urls.py @@ -2,6 +2,8 @@ from parsifal.library import views +app_name = "library" + urlpatterns = [ path("", views.index, name="index"), path("list_actions/", views.list_actions, name="list_actions"), diff --git a/parsifal/reviews/conducting/urls.py b/parsifal/reviews/conducting/urls.py index fbf5dcbb..ae1bf224 100644 --- a/parsifal/reviews/conducting/urls.py +++ b/parsifal/reviews/conducting/urls.py @@ -2,6 +2,8 @@ from parsifal.reviews.conducting import views +app_name = "conducting" + urlpatterns = [ path("add_source_string/", views.add_source_string, name="add_source_string"), path("save_source_string/", views.save_source_string, name="save_source_string"), diff --git a/parsifal/reviews/conducting/views.py b/parsifal/reviews/conducting/views.py index eb7bc183..84668bb3 100644 --- a/parsifal/reviews/conducting/views.py +++ b/parsifal/reviews/conducting/views.py @@ -50,12 +50,12 @@ def search_studies(request, username, review_name): try: scopus = sessions.filter(source__name__iexact="Scopus")[0] database_queries["scopus"] = scopus - except: + except Exception: pass try: science_direct = sessions.filter(source__name__iexact="Science@Direct")[0] database_queries["science_direct"] = science_direct - except: + except Exception: pass sources_names = [] for source in review.sources.all(): @@ -89,7 +89,7 @@ def save_source_string(request): search_session.search_string = search_string search_session.save() return HttpResponse() - except: + except Exception: return HttpResponseBadRequest() @@ -127,7 +127,7 @@ def import_base_string(request): search_session.search_string = base_search_string search_session.save() return HttpResponse(base_search_string) - except: + except Exception: return HttpResponseBadRequest() @@ -251,7 +251,7 @@ def build_quality_assessment_table(request, review, order): try: question_answer = quality_assessment.filter(question__id=question.id).get() - except: + except Exception: question_answer = None for answer in quality_answers: @@ -367,7 +367,7 @@ def build_data_extraction_field_row(article, field): if field.field_type == DataExtractionField.BOOLEAN_FIELD: true = "" false = "" - if extraction != None: + if extraction is not None: if extraction.get_value(): true = " selected" else: @@ -405,17 +405,19 @@ def build_data_extraction_field_row(article, field): elif field.field_type == DataExtractionField.SELECT_MANY_FIELD: for value in field.get_select_values(): - if extraction != None and value in extraction.get_value(): + if extraction is not None and value in extraction.get_value(): checked = " checked" else: checked = "" - str_field += ' '.format( - article.id, field.id, value.id, checked, escape(value.value) + str_field += ( + ' ".format(article.id, field.id, value.id, checked, escape(value.value)) ) elif field.field_type == DataExtractionField.STRING_FIELD: value = "" - if extraction != None: + if extraction is not None: value = extraction.get_value() str_field = ''.format( article.id, field.id, escape(value) @@ -449,9 +451,9 @@ def build_data_extraction_table(review, is_finished): ) if study.finished_data_extraction: - str_table += ' mark as undone' + str_table += ' mark as undone' # noqa else: - str_table += ' mark as done' + str_table += ' mark as done' # noqa str_table += "
" str_table += '
'.format(study.id) @@ -607,7 +609,7 @@ def bibtex_to_article_object(bib_database, review, source): article.note = entry["note"][:500] article.review = review article.source = source - except: + except Exception: continue articles.append(article) return articles @@ -622,7 +624,7 @@ def _import_articles(request, source, articles): article.created_by = request.user article.save() success = success + 1 - except: + except Exception: error = error + 1 if success > 0: messages.success(request, "{0} articles successfully imported to {1}!".format(success, source.name)) @@ -830,14 +832,14 @@ def save_article_details(request): try: selection_criteria = SelectionCriteria.objects.get(pk=selection_criteria_id) article.selection_criteria = selection_criteria - except: + except Exception: article.selection_criteria = None article.updated_by = request.user article.save() return HttpResponse(build_article_table_row(article)) - except: + except Exception: return HttpResponseBadRequest() else: return HttpResponseBadRequest() @@ -860,7 +862,7 @@ def save_quality_assessment(request): quality_assessment.save() return HttpResponse(article.get_score()) - except: + except Exception: return HttpResponseBadRequest() @@ -877,7 +879,7 @@ def quality_assessment_detailed(request): "conducting/partial_conducting_quality_assessment_detailed.html", {"review": review, "quality_assessment_table": quality_assessment_table, "order": order}, ) - except: + except Exception: return HttpResponseBadRequest() @@ -888,7 +890,7 @@ def quality_assessment_summary(request): review_id = request.GET["review-id"] review = Review.objects.get(pk=review_id) return render(request, "conducting/partial_conducting_quality_assessment_summary.html", {"review": review}) - except: + except Exception: return HttpResponseBadRequest() @@ -901,7 +903,7 @@ def multiple_articles_action_remove(request): if article_ids_list: Article.objects.filter(pk__in=article_ids_list).delete() return HttpResponse() - except: + except Exception: return HttpResponseBadRequest() @@ -914,7 +916,7 @@ def multiple_articles_action_accept(request): if article_ids_list: Article.objects.filter(pk__in=article_ids_list).update(status=Article.ACCEPTED) return HttpResponse() - except: + except Exception: return HttpResponseBadRequest() @@ -927,7 +929,7 @@ def multiple_articles_action_reject(request): if article_ids_list: Article.objects.filter(pk__in=article_ids_list).update(status=Article.REJECTED) return HttpResponse() - except: + except Exception: return HttpResponseBadRequest() @@ -940,7 +942,7 @@ def multiple_articles_action_duplicated(request): if article_ids_list: Article.objects.filter(pk__in=article_ids_list).update(status=Article.DUPLICATED) return HttpResponse() - except: + except Exception: return HttpResponseBadRequest() diff --git a/parsifal/reviews/decorators.py b/parsifal/reviews/decorators.py index 4ec52fdf..43066d29 100644 --- a/parsifal/reviews/decorators.py +++ b/parsifal/reviews/decorators.py @@ -17,10 +17,10 @@ def wrap(request, *args, **kwargs): else: try: review_id = request.POST["review-id"] - except: + except Exception: try: review_id = request.GET["review-id"] - except: + except Exception: return HttpResponseBadRequest() review = Review.objects.get(pk=review_id) @@ -48,10 +48,10 @@ def wrap(request, *args, **kwargs): else: try: review_id = request.POST["review-id"] - except: + except Exception: try: review_id = request.GET["review-id"] - except: + except Exception: return HttpResponseBadRequest() review = Review.objects.get(pk=review_id) if review.is_author_or_coauthor(request.user): diff --git a/parsifal/reviews/models.py b/parsifal/reviews/models.py index a6c145f1..90486a59 100644 --- a/parsifal/reviews/models.py +++ b/parsifal/reviews/models.py @@ -159,7 +159,7 @@ def calculate_quality_assessment_max_score(self): return questions_count * higher_weight_answer.weight else: return 0.0 - except: + except Exception: return 0.0 diff --git a/parsifal/reviews/planning/urls.py b/parsifal/reviews/planning/urls.py index 08984b8a..d67871eb 100644 --- a/parsifal/reviews/planning/urls.py +++ b/parsifal/reviews/planning/urls.py @@ -2,6 +2,8 @@ from parsifal.reviews.planning import views +app_name = "planning" + urlpatterns = [ path("save_source/", views.save_source, name="save_source"), path("remove_source/", views.remove_source_from_review, name="remove_source_from_review"), diff --git a/parsifal/reviews/planning/views.py b/parsifal/reviews/planning/views.py index d2c08dd4..c9e2acf6 100644 --- a/parsifal/reviews/planning/views.py +++ b/parsifal/reviews/planning/views.py @@ -76,7 +76,7 @@ def save_objective(request): review.objective = objective review.save() return HttpResponse("Your review have been saved successfully!") - except: + except Exception: return HttpResponseBadRequest() @@ -99,12 +99,12 @@ def save_question(request): review = Review.objects.get(pk=review_id) try: question = Question.objects.get(pk=question_id) - except: + except Exception: question = Question(review=review) question.question = description[:500] question.save() return render(request, "planning/partial_planning_question.html", {"question": question}) - except: + except Exception: return HttpResponseBadRequest() @@ -121,7 +121,7 @@ def save_question_order(request): question.order = order question.save() return HttpResponse() - except: + except Exception: return HttpResponseBadRequest() @@ -138,10 +138,10 @@ def add_or_edit_question(request): review = Review.objects.get(pk=review_id) try: question = Question.objects.get(pk=question_id) - except: + except Exception: question = Question(review=review) return render(request, "planning/partial_planning_question_form.html", {"question": question}) - except: + except Exception: return HttpResponseBadRequest() @@ -162,7 +162,7 @@ def remove_question(request): except Question.DoesNotExist: return HttpResponseBadRequest() return HttpResponse() - except: + except Exception: return HttpResponseBadRequest() @@ -234,7 +234,7 @@ def import_pico_keywords(request): context = RequestContext(request, {"keyword": keyword}) html += render_to_string("planning/partial_keyword.html", context) return HttpResponse(html) - except: + except Exception: return HttpResponseBadRequest() @@ -250,7 +250,7 @@ def remove_keyword(request): synonym.delete() keyword.delete() return HttpResponse() - except: + except Exception: return HttpResponseBadRequest() @@ -368,7 +368,7 @@ def save_generic_search_string(request): generic_search_string.search_string = search_string generic_search_string.save() return HttpResponse() - except: + except Exception: return HttpResponseBadRequest() @@ -384,9 +384,9 @@ def html_source(source): else: html += "" + escape(source.url) + "" if source.is_default: - html += ' - +
diff --git a/parsifal/authentication/templates/auth/reset_confirm.html b/parsifal/apps/authentication/templates/auth/reset_confirm.html similarity index 92% rename from parsifal/authentication/templates/auth/reset_confirm.html rename to parsifal/apps/authentication/templates/auth/reset_confirm.html index b61db464..4c5e6726 100644 --- a/parsifal/authentication/templates/auth/reset_confirm.html +++ b/parsifal/apps/authentication/templates/auth/reset_confirm.html @@ -3,7 +3,7 @@ {% block title %}Parsifal - Password Reset{% endblock title %} {% block content %} - {% if validlink %} + {% if validlink %}
@@ -11,8 +11,8 @@

Confirm Password Reset

-
- {% csrf_token %} + + {% csrf_token %}
@@ -36,4 +36,4 @@

Confirm Password Reset

{% else %}

Invalid password reset link.

{% endif %} -{% endblock content %} \ No newline at end of file +{% endblock content %} diff --git a/parsifal/authentication/templates/auth/reset_email.html b/parsifal/apps/authentication/templates/auth/reset_email.html similarity index 100% rename from parsifal/authentication/templates/auth/reset_email.html rename to parsifal/apps/authentication/templates/auth/reset_email.html diff --git a/parsifal/authentication/templates/auth/reset_subject.txt b/parsifal/apps/authentication/templates/auth/reset_subject.txt similarity index 100% rename from parsifal/authentication/templates/auth/reset_subject.txt rename to parsifal/apps/authentication/templates/auth/reset_subject.txt diff --git a/parsifal/authentication/templates/auth/signin.html b/parsifal/apps/authentication/templates/auth/signin.html similarity index 94% rename from parsifal/authentication/templates/auth/signin.html rename to parsifal/apps/authentication/templates/auth/signin.html index 50839f07..0ae07c80 100644 --- a/parsifal/authentication/templates/auth/signin.html +++ b/parsifal/apps/authentication/templates/auth/signin.html @@ -18,7 +18,7 @@

Sign in

- + {% csrf_token %}
@@ -30,7 +30,7 @@

Sign in

Forgot your password? - +
diff --git a/parsifal/authentication/templates/auth/signup.html b/parsifal/apps/authentication/templates/auth/signup.html similarity index 100% rename from parsifal/authentication/templates/auth/signup.html rename to parsifal/apps/authentication/templates/auth/signup.html diff --git a/parsifal/authentication/templates/auth/success.html b/parsifal/apps/authentication/templates/auth/success.html similarity index 100% rename from parsifal/authentication/templates/auth/success.html rename to parsifal/apps/authentication/templates/auth/success.html diff --git a/parsifal/authentication/views.py b/parsifal/apps/authentication/views.py similarity index 98% rename from parsifal/authentication/views.py rename to parsifal/apps/authentication/views.py index 8d0415b5..eb3e9307 100644 --- a/parsifal/authentication/views.py +++ b/parsifal/apps/authentication/views.py @@ -7,7 +7,7 @@ from django.urls import reverse from django.utils.translation import gettext as _ -from parsifal.authentication.forms import SignUpForm +from parsifal.apps.authentication.forms import SignUpForm password_reset = PasswordResetView.as_view() password_reset_confirm = PasswordResetConfirmView.as_view() diff --git a/parsifal/apps/blog/__init__.py b/parsifal/apps/blog/__init__.py new file mode 100644 index 00000000..cee4d881 --- /dev/null +++ b/parsifal/apps/blog/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.apps.blog.apps.BlogConfig" diff --git a/parsifal/blog/admin.py b/parsifal/apps/blog/admin.py similarity index 94% rename from parsifal/blog/admin.py rename to parsifal/apps/blog/admin.py index 7f5ef10b..d9415728 100644 --- a/parsifal/blog/admin.py +++ b/parsifal/apps/blog/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from django.template.defaultfilters import slugify -from parsifal.blog.models import Entry +from parsifal.apps.blog.models import Entry @admin.register(Entry) diff --git a/parsifal/blog/apps.py b/parsifal/apps/blog/apps.py similarity index 82% rename from parsifal/blog/apps.py rename to parsifal/apps/blog/apps.py index d3e9c132..1574b9e8 100644 --- a/parsifal/blog/apps.py +++ b/parsifal/apps/blog/apps.py @@ -3,5 +3,5 @@ class BlogConfig(AppConfig): - name = "parsifal.blog" + name = "parsifal.apps.blog" verbose_name = _("Blog") diff --git a/parsifal/blog/migrations/0001_initial.py b/parsifal/apps/blog/migrations/0001_initial.py similarity index 100% rename from parsifal/blog/migrations/0001_initial.py rename to parsifal/apps/blog/migrations/0001_initial.py diff --git a/parsifal/blog/migrations/0002_auto_20150623_1104.py b/parsifal/apps/blog/migrations/0002_auto_20150623_1104.py similarity index 100% rename from parsifal/blog/migrations/0002_auto_20150623_1104.py rename to parsifal/apps/blog/migrations/0002_auto_20150623_1104.py diff --git a/parsifal/blog/migrations/0003_auto_20150624_0615.py b/parsifal/apps/blog/migrations/0003_auto_20150624_0615.py similarity index 100% rename from parsifal/blog/migrations/0003_auto_20150624_0615.py rename to parsifal/apps/blog/migrations/0003_auto_20150624_0615.py diff --git a/parsifal/blog/migrations/0004_auto_20150624_0617.py b/parsifal/apps/blog/migrations/0004_auto_20150624_0617.py similarity index 100% rename from parsifal/blog/migrations/0004_auto_20150624_0617.py rename to parsifal/apps/blog/migrations/0004_auto_20150624_0617.py diff --git a/parsifal/blog/migrations/0005_auto_20210829_0005.py b/parsifal/apps/blog/migrations/0005_auto_20210829_0005.py similarity index 100% rename from parsifal/blog/migrations/0005_auto_20210829_0005.py rename to parsifal/apps/blog/migrations/0005_auto_20210829_0005.py diff --git a/parsifal/core/migrations/__init__.py b/parsifal/apps/blog/migrations/__init__.py similarity index 100% rename from parsifal/core/migrations/__init__.py rename to parsifal/apps/blog/migrations/__init__.py diff --git a/parsifal/blog/models.py b/parsifal/apps/blog/models.py similarity index 100% rename from parsifal/blog/models.py rename to parsifal/apps/blog/models.py diff --git a/parsifal/blog/templates/blog/entries.html b/parsifal/apps/blog/templates/blog/entries.html similarity index 100% rename from parsifal/blog/templates/blog/entries.html rename to parsifal/apps/blog/templates/blog/entries.html diff --git a/parsifal/blog/templates/blog/entry.html b/parsifal/apps/blog/templates/blog/entry.html similarity index 100% rename from parsifal/blog/templates/blog/entry.html rename to parsifal/apps/blog/templates/blog/entry.html diff --git a/parsifal/blog/urls.py b/parsifal/apps/blog/urls.py similarity index 81% rename from parsifal/blog/urls.py rename to parsifal/apps/blog/urls.py index 9e5b54ed..07d936e9 100644 --- a/parsifal/blog/urls.py +++ b/parsifal/apps/blog/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from parsifal.blog import views +from parsifal.apps.blog import views app_name = "blog" diff --git a/parsifal/blog/views.py b/parsifal/apps/blog/views.py similarity index 92% rename from parsifal/blog/views.py rename to parsifal/apps/blog/views.py index e1358a48..3c038c08 100644 --- a/parsifal/blog/views.py +++ b/parsifal/apps/blog/views.py @@ -1,6 +1,6 @@ from django.shortcuts import get_object_or_404, render -from parsifal.blog.models import Entry +from parsifal.apps.blog.models import Entry def entries(request): diff --git a/parsifal/apps/core/__init__.py b/parsifal/apps/core/__init__.py new file mode 100644 index 00000000..66c290d5 --- /dev/null +++ b/parsifal/apps/core/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.apps.core.apps.CoreConfig" diff --git a/parsifal/core/admin.py b/parsifal/apps/core/admin.py similarity index 89% rename from parsifal/core/admin.py rename to parsifal/apps/core/admin.py index 4617b3de..bff2711b 100644 --- a/parsifal/core/admin.py +++ b/parsifal/apps/core/admin.py @@ -2,7 +2,7 @@ from django.contrib import admin -from parsifal.core.models import Media +from parsifal.apps.core.models import Media class MediaAdmin(admin.ModelAdmin): diff --git a/parsifal/core/apps.py b/parsifal/apps/core/apps.py similarity index 82% rename from parsifal/core/apps.py rename to parsifal/apps/core/apps.py index 3b121e55..9d775d7b 100644 --- a/parsifal/core/apps.py +++ b/parsifal/apps/core/apps.py @@ -3,5 +3,5 @@ class CoreConfig(AppConfig): - name = "parsifal.core" + name = "parsifal.apps.core" verbose_name = _("Settings") diff --git a/parsifal/core/migrations/0001_initial.py b/parsifal/apps/core/migrations/0001_initial.py similarity index 100% rename from parsifal/core/migrations/0001_initial.py rename to parsifal/apps/core/migrations/0001_initial.py diff --git a/parsifal/core/migrations/0002_auto_20210829_0005.py b/parsifal/apps/core/migrations/0002_auto_20210829_0005.py similarity index 100% rename from parsifal/core/migrations/0002_auto_20210829_0005.py rename to parsifal/apps/core/migrations/0002_auto_20210829_0005.py diff --git a/parsifal/core/tests/__init__.py b/parsifal/apps/core/migrations/__init__.py similarity index 100% rename from parsifal/core/tests/__init__.py rename to parsifal/apps/core/migrations/__init__.py diff --git a/parsifal/core/models.py b/parsifal/apps/core/models.py similarity index 100% rename from parsifal/core/models.py rename to parsifal/apps/core/models.py diff --git a/parsifal/core/templates/core/about.html b/parsifal/apps/core/templates/core/about.html similarity index 100% rename from parsifal/core/templates/core/about.html rename to parsifal/apps/core/templates/core/about.html diff --git a/parsifal/core/templates/core/cover.html b/parsifal/apps/core/templates/core/cover.html similarity index 100% rename from parsifal/core/templates/core/cover.html rename to parsifal/apps/core/templates/core/cover.html diff --git a/parsifal/core/templates/core/home.html b/parsifal/apps/core/templates/core/home.html similarity index 100% rename from parsifal/core/templates/core/home.html rename to parsifal/apps/core/templates/core/home.html diff --git a/parsifal/core/templates/core/support.html b/parsifal/apps/core/templates/core/support.html similarity index 100% rename from parsifal/core/templates/core/support.html rename to parsifal/apps/core/templates/core/support.html diff --git a/parsifal/help/migrations/__init__.py b/parsifal/apps/core/tests/__init__.py similarity index 100% rename from parsifal/help/migrations/__init__.py rename to parsifal/apps/core/tests/__init__.py diff --git a/parsifal/core/tests/test_views_home.py b/parsifal/apps/core/tests/test_views_home.py similarity index 100% rename from parsifal/core/tests/test_views_home.py rename to parsifal/apps/core/tests/test_views_home.py diff --git a/parsifal/core/views.py b/parsifal/apps/core/views.py similarity index 91% rename from parsifal/core/views.py rename to parsifal/apps/core/views.py index a013caf4..17c6bf3f 100644 --- a/parsifal/core/views.py +++ b/parsifal/apps/core/views.py @@ -2,8 +2,8 @@ from django.urls import reverse from django.utils.html import escape -from parsifal.activities.models import Activity -from parsifal.blog.models import Entry +from parsifal.apps.activities.models import Activity +from parsifal.apps.blog.models import Entry def get_following_feeds(user): @@ -58,6 +58,8 @@ def home(request): feeds = get_following_feeds(request.user) latest_news = Entry.objects.filter(status=Entry.PUBLISHED).order_by("-start_publication").first() return render( - request, "core/home.html", {"user_reviews": user_reviews, "feeds": feeds, "latest_news": latest_news} + request, + "core/home.html", + {"user_reviews": user_reviews, "feeds": feeds, "latest_news": latest_news}, ) return render(request, "core/cover.html") diff --git a/parsifal/apps/help/__init__.py b/parsifal/apps/help/__init__.py new file mode 100644 index 00000000..e088af7c --- /dev/null +++ b/parsifal/apps/help/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.apps.help.apps.HelpConfig" diff --git a/parsifal/help/admin.py b/parsifal/apps/help/admin.py similarity index 93% rename from parsifal/help/admin.py rename to parsifal/apps/help/admin.py index 332fca6b..a66e45b5 100644 --- a/parsifal/help/admin.py +++ b/parsifal/apps/help/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from django.utils.text import slugify -from parsifal.help.models import Article, Category +from parsifal.apps.help.models import Article, Category @admin.register(Article) diff --git a/parsifal/help/apps.py b/parsifal/apps/help/apps.py similarity index 82% rename from parsifal/help/apps.py rename to parsifal/apps/help/apps.py index 84fb3cee..68fa2dd7 100644 --- a/parsifal/help/apps.py +++ b/parsifal/apps/help/apps.py @@ -3,5 +3,5 @@ class HelpConfig(AppConfig): - name = "parsifal.help" + name = "parsifal.apps.help" verbose_name = _("Help") diff --git a/parsifal/help/migrations/0001_initial.py b/parsifal/apps/help/migrations/0001_initial.py similarity index 100% rename from parsifal/help/migrations/0001_initial.py rename to parsifal/apps/help/migrations/0001_initial.py diff --git a/parsifal/help/migrations/0002_article_parent.py b/parsifal/apps/help/migrations/0002_article_parent.py similarity index 100% rename from parsifal/help/migrations/0002_article_parent.py rename to parsifal/apps/help/migrations/0002_article_parent.py diff --git a/parsifal/help/migrations/0003_auto_20150623_2128.py b/parsifal/apps/help/migrations/0003_auto_20150623_2128.py similarity index 100% rename from parsifal/help/migrations/0003_auto_20150623_2128.py rename to parsifal/apps/help/migrations/0003_auto_20150623_2128.py diff --git a/parsifal/help/migrations/0004_auto_20150624_0637.py b/parsifal/apps/help/migrations/0004_auto_20150624_0637.py similarity index 100% rename from parsifal/help/migrations/0004_auto_20150624_0637.py rename to parsifal/apps/help/migrations/0004_auto_20150624_0637.py diff --git a/parsifal/help/migrations/0005_category_slug.py b/parsifal/apps/help/migrations/0005_category_slug.py similarity index 100% rename from parsifal/help/migrations/0005_category_slug.py rename to parsifal/apps/help/migrations/0005_category_slug.py diff --git a/parsifal/help/migrations/0006_auto_20150710_0819.py b/parsifal/apps/help/migrations/0006_auto_20150710_0819.py similarity index 100% rename from parsifal/help/migrations/0006_auto_20150710_0819.py rename to parsifal/apps/help/migrations/0006_auto_20150710_0819.py diff --git a/parsifal/help/migrations/0007_auto_20150710_0827.py b/parsifal/apps/help/migrations/0007_auto_20150710_0827.py similarity index 100% rename from parsifal/help/migrations/0007_auto_20150710_0827.py rename to parsifal/apps/help/migrations/0007_auto_20150710_0827.py diff --git a/parsifal/help/migrations/0008_auto_20150710_0829.py b/parsifal/apps/help/migrations/0008_auto_20150710_0829.py similarity index 100% rename from parsifal/help/migrations/0008_auto_20150710_0829.py rename to parsifal/apps/help/migrations/0008_auto_20150710_0829.py diff --git a/parsifal/help/migrations/0009_auto_20150710_0839.py b/parsifal/apps/help/migrations/0009_auto_20150710_0839.py similarity index 100% rename from parsifal/help/migrations/0009_auto_20150710_0839.py rename to parsifal/apps/help/migrations/0009_auto_20150710_0839.py diff --git a/parsifal/help/migrations/0010_auto_20210829_0005.py b/parsifal/apps/help/migrations/0010_auto_20210829_0005.py similarity index 100% rename from parsifal/help/migrations/0010_auto_20210829_0005.py rename to parsifal/apps/help/migrations/0010_auto_20210829_0005.py diff --git a/parsifal/library/migrations/__init__.py b/parsifal/apps/help/migrations/__init__.py similarity index 100% rename from parsifal/library/migrations/__init__.py rename to parsifal/apps/help/migrations/__init__.py diff --git a/parsifal/help/models.py b/parsifal/apps/help/models.py similarity index 97% rename from parsifal/help/models.py rename to parsifal/apps/help/models.py index b83a9313..50d3e587 100644 --- a/parsifal/help/models.py +++ b/parsifal/apps/help/models.py @@ -4,7 +4,7 @@ from bs4 import BeautifulSoup -from parsifal.core.models import Media +from parsifal.apps.core.models import Media class Category(models.Model): diff --git a/parsifal/help/templates/help/article.html b/parsifal/apps/help/templates/help/article.html similarity index 100% rename from parsifal/help/templates/help/article.html rename to parsifal/apps/help/templates/help/article.html diff --git a/parsifal/help/templates/help/articles.html b/parsifal/apps/help/templates/help/articles.html similarity index 100% rename from parsifal/help/templates/help/articles.html rename to parsifal/apps/help/templates/help/articles.html diff --git a/parsifal/help/templates/help/search.html b/parsifal/apps/help/templates/help/search.html similarity index 100% rename from parsifal/help/templates/help/search.html rename to parsifal/apps/help/templates/help/search.html diff --git a/parsifal/help/urls.py b/parsifal/apps/help/urls.py similarity index 85% rename from parsifal/help/urls.py rename to parsifal/apps/help/urls.py index a22dac6e..15e9523a 100644 --- a/parsifal/help/urls.py +++ b/parsifal/apps/help/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from parsifal.help import views +from parsifal.apps.help import views app_name = "help" diff --git a/parsifal/help/views.py b/parsifal/apps/help/views.py similarity index 96% rename from parsifal/help/views.py rename to parsifal/apps/help/views.py index eaabd409..4f912467 100644 --- a/parsifal/help/views.py +++ b/parsifal/apps/help/views.py @@ -1,7 +1,7 @@ from django.db.models import F, Q from django.shortcuts import get_object_or_404, redirect, render -from parsifal.help.models import Article +from parsifal.apps.help.models import Article def articles(request): diff --git a/parsifal/apps/library/__init__.py b/parsifal/apps/library/__init__.py new file mode 100644 index 00000000..c6f080d6 --- /dev/null +++ b/parsifal/apps/library/__init__.py @@ -0,0 +1 @@ +default_app_config = "parsifal.apps.library.apps.LibraryConfig" diff --git a/parsifal/library/apps.py b/parsifal/apps/library/apps.py similarity index 81% rename from parsifal/library/apps.py rename to parsifal/apps/library/apps.py index 0d8f57e4..0a5dd930 100644 --- a/parsifal/library/apps.py +++ b/parsifal/apps/library/apps.py @@ -3,5 +3,5 @@ class LibraryConfig(AppConfig): - name = "parsifal.library" + name = "parsifal.apps.library" verbose_name = _("Library") diff --git a/parsifal/library/forms.py b/parsifal/apps/library/forms.py similarity index 98% rename from parsifal/library/forms.py rename to parsifal/apps/library/forms.py index c2628e1b..46974439 100644 --- a/parsifal/library/forms.py +++ b/parsifal/apps/library/forms.py @@ -3,7 +3,7 @@ from django import forms from django.contrib.auth.models import User -from parsifal.library.models import Document, Folder, SharedFolder +from parsifal.apps.library.models import Document, Folder, SharedFolder class FolderForm(forms.ModelForm): diff --git a/parsifal/library/migrations/0001_initial.py b/parsifal/apps/library/migrations/0001_initial.py similarity index 98% rename from parsifal/library/migrations/0001_initial.py rename to parsifal/apps/library/migrations/0001_initial.py index a198ccd1..eb115181 100644 --- a/parsifal/library/migrations/0001_initial.py +++ b/parsifal/apps/library/migrations/0001_initial.py @@ -4,7 +4,7 @@ from django.db import models, migrations from django.conf import settings import django.db.models.deletion -import parsifal.library.models +import parsifal.apps.library.models class Migration(migrations.Migration): @@ -65,7 +65,7 @@ class Migration(migrations.Migration): name='DocumentFile', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('document_file', models.FileField(upload_to=parsifal.library.models.document_file_upload_to)), + ('document_file', models.FileField(upload_to=parsifal.apps.library.models.document_file_upload_to)), ('filename', models.CharField(max_length=255)), ('size', models.IntegerField(default=0)), ('created_at', models.DateTimeField(auto_now_add=True)), diff --git a/parsifal/library/migrations/0002_auto_20150625_2141.py b/parsifal/apps/library/migrations/0002_auto_20150625_2141.py similarity index 100% rename from parsifal/library/migrations/0002_auto_20150625_2141.py rename to parsifal/apps/library/migrations/0002_auto_20150625_2141.py diff --git a/parsifal/library/migrations/0003_folder_slug.py b/parsifal/apps/library/migrations/0003_folder_slug.py similarity index 100% rename from parsifal/library/migrations/0003_folder_slug.py rename to parsifal/apps/library/migrations/0003_folder_slug.py diff --git a/parsifal/library/migrations/0004_auto_20150626_0841.py b/parsifal/apps/library/migrations/0004_auto_20150626_0841.py similarity index 100% rename from parsifal/library/migrations/0004_auto_20150626_0841.py rename to parsifal/apps/library/migrations/0004_auto_20150626_0841.py diff --git a/parsifal/library/migrations/0005_auto_20150626_1649.py b/parsifal/apps/library/migrations/0005_auto_20150626_1649.py similarity index 100% rename from parsifal/library/migrations/0005_auto_20150626_1649.py rename to parsifal/apps/library/migrations/0005_auto_20150626_1649.py diff --git a/parsifal/library/migrations/0006_auto_20150627_1007.py b/parsifal/apps/library/migrations/0006_auto_20150627_1007.py similarity index 100% rename from parsifal/library/migrations/0006_auto_20150627_1007.py rename to parsifal/apps/library/migrations/0006_auto_20150627_1007.py diff --git a/parsifal/library/migrations/0007_folder_documents.py b/parsifal/apps/library/migrations/0007_folder_documents.py similarity index 100% rename from parsifal/library/migrations/0007_folder_documents.py rename to parsifal/apps/library/migrations/0007_folder_documents.py diff --git a/parsifal/library/migrations/0008_auto_20150702_2047.py b/parsifal/apps/library/migrations/0008_auto_20150702_2047.py similarity index 100% rename from parsifal/library/migrations/0008_auto_20150702_2047.py rename to parsifal/apps/library/migrations/0008_auto_20150702_2047.py diff --git a/parsifal/library/migrations/0009_auto_20150706_0952.py b/parsifal/apps/library/migrations/0009_auto_20150706_0952.py similarity index 100% rename from parsifal/library/migrations/0009_auto_20150706_0952.py rename to parsifal/apps/library/migrations/0009_auto_20150706_0952.py diff --git a/parsifal/library/migrations/0010_auto_20150706_0954.py b/parsifal/apps/library/migrations/0010_auto_20150706_0954.py similarity index 100% rename from parsifal/library/migrations/0010_auto_20150706_0954.py rename to parsifal/apps/library/migrations/0010_auto_20150706_0954.py diff --git a/parsifal/library/migrations/0011_auto_20150706_0957.py b/parsifal/apps/library/migrations/0011_auto_20150706_0957.py similarity index 100% rename from parsifal/library/migrations/0011_auto_20150706_0957.py rename to parsifal/apps/library/migrations/0011_auto_20150706_0957.py diff --git a/parsifal/library/migrations/0012_auto_20150710_1544.py b/parsifal/apps/library/migrations/0012_auto_20150710_1544.py similarity index 100% rename from parsifal/library/migrations/0012_auto_20150710_1544.py rename to parsifal/apps/library/migrations/0012_auto_20150710_1544.py diff --git a/parsifal/library/migrations/0013_auto_20150710_1614.py b/parsifal/apps/library/migrations/0013_auto_20150710_1614.py similarity index 100% rename from parsifal/library/migrations/0013_auto_20150710_1614.py rename to parsifal/apps/library/migrations/0013_auto_20150710_1614.py diff --git a/parsifal/library/migrations/0014_auto_20150807_1551.py b/parsifal/apps/library/migrations/0014_auto_20150807_1551.py similarity index 100% rename from parsifal/library/migrations/0014_auto_20150807_1551.py rename to parsifal/apps/library/migrations/0014_auto_20150807_1551.py diff --git a/parsifal/library/migrations/0015_auto_20151009_1543.py b/parsifal/apps/library/migrations/0015_auto_20151009_1543.py similarity index 100% rename from parsifal/library/migrations/0015_auto_20151009_1543.py rename to parsifal/apps/library/migrations/0015_auto_20151009_1543.py diff --git a/parsifal/library/migrations/0016_sharedfolder_users.py b/parsifal/apps/library/migrations/0016_sharedfolder_users.py similarity index 100% rename from parsifal/library/migrations/0016_sharedfolder_users.py rename to parsifal/apps/library/migrations/0016_sharedfolder_users.py diff --git a/parsifal/library/migrations/0017_auto_20151009_1717.py b/parsifal/apps/library/migrations/0017_auto_20151009_1717.py similarity index 100% rename from parsifal/library/migrations/0017_auto_20151009_1717.py rename to parsifal/apps/library/migrations/0017_auto_20151009_1717.py diff --git a/parsifal/library/migrations/0018_auto_20210829_0005.py b/parsifal/apps/library/migrations/0018_auto_20210829_0005.py similarity index 100% rename from parsifal/library/migrations/0018_auto_20210829_0005.py rename to parsifal/apps/library/migrations/0018_auto_20210829_0005.py diff --git a/parsifal/reviews/conducting/tests/__init__.py b/parsifal/apps/library/migrations/__init__.py similarity index 100% rename from parsifal/reviews/conducting/tests/__init__.py rename to parsifal/apps/library/migrations/__init__.py diff --git a/parsifal/library/models.py b/parsifal/apps/library/models.py similarity index 100% rename from parsifal/library/models.py rename to parsifal/apps/library/models.py diff --git a/parsifal/library/static/js/library.js b/parsifal/apps/library/static/js/library.js similarity index 100% rename from parsifal/library/static/js/library.js rename to parsifal/apps/library/static/js/library.js diff --git a/parsifal/library/templates/library/document.html b/parsifal/apps/library/templates/library/document.html similarity index 100% rename from parsifal/library/templates/library/document.html rename to parsifal/apps/library/templates/library/document.html diff --git a/parsifal/library/templates/library/edit_folder.html b/parsifal/apps/library/templates/library/edit_folder.html similarity index 100% rename from parsifal/library/templates/library/edit_folder.html rename to parsifal/apps/library/templates/library/edit_folder.html diff --git a/parsifal/library/templates/library/includes/menu.html b/parsifal/apps/library/templates/library/includes/menu.html similarity index 94% rename from parsifal/library/templates/library/includes/menu.html rename to parsifal/apps/library/templates/library/includes/menu.html index 4901a824..f1147c89 100644 --- a/parsifal/library/templates/library/includes/menu.html +++ b/parsifal/apps/library/templates/library/includes/menu.html @@ -12,7 +12,7 @@

Add folder…

@@ -28,7 +28,7 @@

Add shared folder…

diff --git a/parsifal/library/templates/library/includes/toolbar.html b/parsifal/apps/library/templates/library/includes/toolbar.html similarity index 100% rename from parsifal/library/templates/library/includes/toolbar.html rename to parsifal/apps/library/templates/library/includes/toolbar.html diff --git a/parsifal/library/templates/library/library.html b/parsifal/apps/library/templates/library/library.html similarity index 90% rename from parsifal/library/templates/library/library.html rename to parsifal/apps/library/templates/library/library.html index 3ef4306e..fd6b2676 100644 --- a/parsifal/library/templates/library/library.html +++ b/parsifal/apps/library/templates/library/library.html @@ -11,7 +11,7 @@ {% endblock %} {% block javascript %} - + {% endblock %} {% block mustache %} @@ -32,18 +32,18 @@