diff --git a/profiles/admin.py b/profiles/admin.py index 1b54b2c2a72c5d9253e0c89c62ec59d0c5cce768..d0e860a7fbdcafc4a33b4e80d029d04ef8a3dd3a 100644 --- a/profiles/admin.py +++ b/profiles/admin.py @@ -4,7 +4,7 @@ __license__ = "AGPL v3" from django.contrib import admin -from .models import Profile, ProfileEmail, ProfileNonDuplicates +from .models import Profile, ProfileEmail, ProfileNonDuplicates, Affiliation class ProfileEmailInline(admin.TabularInline): @@ -12,10 +12,15 @@ class ProfileEmailInline(admin.TabularInline): extra = 0 +class AffiliationInline(admin.TabularInline): + model = Affiliation + extra = 0 + + class ProfileAdmin(admin.ModelAdmin): list_display = ['__str__', 'email', 'discipline', 'expertises', 'has_active_contributor'] search_fields = ['first_name', 'last_name', 'emails__email', 'orcid_id'] - inlines = [ProfileEmailInline] + inlines = [ProfileEmailInline, AffiliationInline] admin.site.register(Profile, ProfileAdmin) diff --git a/profiles/constants.py b/profiles/constants.py index b9ac6f92693bab316ddad1cc133615f6deb7a857..29bdde8df464a1d2c73f693e5b4c975dd44de17a 100644 --- a/profiles/constants.py +++ b/profiles/constants.py @@ -36,5 +36,5 @@ AFFILIATION_CATEGORIES = ( (AFFILIATION_CATEGORY_EMPLOYED_PhD, 'PhD candidate'), (AFFILIATION_CATEGORY_ASSOCIATE_SCIENTIST, 'Associate Scientist'), (AFFILIATION_CATEGORY_CONSULTANT, 'Consultant'), - (AFFILIATION_CATEGORY_VISITOR, 'Visotor'), + (AFFILIATION_CATEGORY_VISITOR, 'Visitor'), ) diff --git a/profiles/forms.py b/profiles/forms.py index dfba810721e71273c78feab0bfdf397948b161e2..adc7c9ad5315b26ff86583712f300245c29577e1 100644 --- a/profiles/forms.py +++ b/profiles/forms.py @@ -5,6 +5,8 @@ __license__ = "AGPL v3" from django import forms from django.shortcuts import get_object_or_404 +from ajax_select.fields import AutoCompleteSelectField + from common.forms import ModelChoiceFieldwithid from invitations.models import RegistrationInvitation from journals.models import UnregisteredAuthor @@ -12,7 +14,7 @@ from ontology.models import Topic from scipost.models import Contributor from submissions.models import RefereeInvitation -from .models import Profile, ProfileEmail +from .models import Profile, ProfileEmail, Affiliation class ProfileForm(forms.ModelForm): @@ -165,3 +167,16 @@ class ProfileEmailForm(forms.ModelForm): """Save to a profile.""" self.instance.profile = self.profile return super().save() + + +class AffiliationForm(forms.ModelForm): + organization = AutoCompleteSelectField('organization_lookup') + + class Meta: + model = Affiliation + fields = ['profile', 'organization', 'category', + 'description', 'date_from', 'date_until'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['profile'].widget = forms.HiddenInput() diff --git a/profiles/migrations/0019_auto_20190327_1520.py b/profiles/migrations/0019_auto_20190327_1520.py new file mode 100644 index 0000000000000000000000000000000000000000..ee2a8810c796494d4e7f5922013c2019a266a264 --- /dev/null +++ b/profiles/migrations/0019_auto_20190327_1520.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2019-03-27 14:20 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0018_affiliation'), + ] + + operations = [ + migrations.AlterModelOptions( + name='affiliation', + options={'ordering': ['profile__last_name', 'profile__first_name', 'date_until']}, + ), + migrations.AlterField( + model_name='affiliation', + name='category', + field=models.CharField(choices=[('employed_prof_full', 'Full Professor'), ('employed_prof_associate', 'Associate Professor'), ('employed_prof_assistant', 'Assistant Professor'), ('employed_prof_emeritus', 'Emeritus Professor'), ('employed_permanent_staff', 'Permanent Staff'), ('employed_fixed_term_staff', 'Fixed Term Staff'), ('employed_tenure_track', 'Tenure Tracker'), ('employed_postdoc', 'Postdoctoral Researcher'), ('employed_phd', 'PhD candidate'), ('associate_scientist', 'Associate Scientist'), ('consultant', 'Consultant'), ('visitor', 'Visitor')], help_text='Select the most suitable category', max_length=64), + ), + ] diff --git a/profiles/migrations/0020_auto_20190327_1713.py b/profiles/migrations/0020_auto_20190327_1713.py new file mode 100644 index 0000000000000000000000000000000000000000..b9d9b127c257ee4098f097bc1ac9dcb57d979ca3 --- /dev/null +++ b/profiles/migrations/0020_auto_20190327_1713.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2019-03-27 16:13 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0019_auto_20190327_1520'), + ] + + operations = [ + migrations.AlterModelOptions( + name='affiliation', + options={'ordering': ['profile__last_name', 'profile__first_name', '-date_until']}, + ), + ] diff --git a/profiles/models.py b/profiles/models.py index fa47729d4545f403ca4f2ce84c053022d3419184..e553074e76e6483586ca83e95113199e16934d38 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -199,8 +199,8 @@ class Affiliation(models.Model): class Meta: default_related_name = 'affiliations' - ordering = ['profile__user__last_name', 'profile__user__first_name', - 'date_until'] + ordering = ['profile__last_name', 'profile__first_name', + '-date_until'] def __str__(self): return '{ profile }, { organization } [{ date_from } to { date_until }]'.format( diff --git a/profiles/templates/profiles/affiliation_form.html b/profiles/templates/profiles/affiliation_form.html new file mode 100644 index 0000000000000000000000000000000000000000..aefa75e6b83bba23ec9df9ebe3f3920dc0fcd408 --- /dev/null +++ b/profiles/templates/profiles/affiliation_form.html @@ -0,0 +1,46 @@ +{% extends 'profiles/base.html' %} + +{% load bootstrap %} + +{% load scipost_extras %} + +{% block breadcrumb_items %} + {{ block.super }} + <span class="breadcrumb-item">{% if form.instance.id %}Update{% else %}Add new{% endif %} Affiliation</span> +{% endblock %} + +{% block pagetitle %}: Affiliation{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + <h3 class="highlight">Add a new Affiliation to your Profile</h3> + <p class="text-danger">Don't find the organization you need in our list? Please <a href="{% url 'helpdesk:ticket_create' %}">create a Ticket</a> providing us with the details, we'll get back to you!</p> + <form action="" method="post"> + {% csrf_token %} + {{ form|bootstrap }} + + {% if form.errors %} + {% for field in form %} + {% for error in field.errors %} + <div class="alert alert-danger"> + <strong>{{ field.name }} - {{ error|escape }}</strong> + </div> + {% endfor %} + {% endfor %} + {% for error in form.non_field_errors %} + <div class="alert alert-danger"> + <strong>{{ error|escape }}</strong> + </div> + {% endfor %} + {% endif %} + <input type="submit" value="Submit" class="btn btn-primary"> + </form> + </div> +</div> +{% endblock content %} + +{% block footer_script %} + {{ block.super }} + {{ form.media }} +{% endblock footer_script %} diff --git a/profiles/urls.py b/profiles/urls.py index 5fc21faf8468a389094722ae9bd8478528b61237..da204b485c9a871d3640f5d541d18a2bb02ea925 100644 --- a/profiles/urls.py +++ b/profiles/urls.py @@ -72,4 +72,9 @@ urlpatterns = [ views.delete_profile_email, name='delete_profile_email' ), + url( + r'^(?P<profile_id>[0-9]+)/affiliation/add/$', + views.AffiliationCreateView.as_view(), + name='affiliation_create' + ), ] diff --git a/profiles/views.py b/profiles/views.py index 33aee843b4fb8fe2244c4c8ca20c079d7654b5e4..feec4b2c9814aae2a28b26793db5c46647035a57 100644 --- a/profiles/views.py +++ b/profiles/views.py @@ -3,6 +3,7 @@ __license__ = "AGPL v3" from django.contrib import messages +from django.contrib.auth.mixins import UserPassesTestMixin from django.core.urlresolvers import reverse, reverse_lazy from django.db import transaction from django.db.models import Q @@ -24,8 +25,8 @@ from invitations.models import RegistrationInvitation from journals.models import UnregisteredAuthor from submissions.models import RefereeInvitation -from .models import Profile, ProfileEmail -from .forms import ProfileForm, ProfileMergeForm, ProfileEmailForm +from .models import Profile, ProfileEmail, Affiliation +from .forms import ProfileForm, ProfileMergeForm, ProfileEmailForm, AffiliationForm @@ -377,3 +378,37 @@ def delete_profile_email(request, email_id): profile_email.delete() messages.success(request, 'Email deleted') return redirect(profile_email.profile.get_absolute_url()) + + +class AffiliationCreateView(UserPassesTestMixin, CreateView): + model = Affiliation + form_class = AffiliationForm + template_name = 'profiles/affiliation_form.html' + + def test_func(self): + """ + Allow creating an Affiliation if user is Admin, EdAdmin or is + the Contributor to which this Profile is related. + """ + if self.request.user.has_perm('scipost.can_create_profiles'): + return True + profile = get_object_or_404(Profile, pk=self.kwargs.get('profile_id')) + return self.request.user.contributor.profile is profile + + def get_initial(self, *args, **kwargs): + initial = super().get_initial(*args, **kwargs) + profile = get_object_or_404(Profile, pk=self.kwargs.get('profile_id')) + initial.update({ + 'profile': profile + }) + return initial + + def get_success_url(self): + """ + If request.user is Admin or EdAdmin, redirect to profile detail view. + Otherwise if request.user is Profile owner, return to personal page. + """ + if self.request.user.has_perm('scipost.can_create_profiles'): + return reverse_lazy('profiles:profile_detail', + kwargs={'pk': self.object.profile.id}) + return reverse_lazy('scipost:personal_page') diff --git a/scipost/templates/partials/scipost/personal_page/account.html b/scipost/templates/partials/scipost/personal_page/account.html index 164aac577ebe00b85d1662a9edd084502dec7aeb..d5f9bad3eae9a5fed2eb1f430cea1afc2bfadced 100644 --- a/scipost/templates/partials/scipost/personal_page/account.html +++ b/scipost/templates/partials/scipost/personal_page/account.html @@ -146,6 +146,37 @@ </div> </div> +<div class="row"> + <div class="col-12"> + <h3>Your Affiliations:</h3> + <ul> + <li><a href="{% url 'profiles:affiliation_create' profile_id=contributor.profile.id %}">Add a new Affiliation</a></li> + </ul> + <table class="table"> + <thead class="thead-default"> + <tr> + <th>Organization</th> + <th>Category</th> + <th>From</th> + <th>Until</th> + </tr> + </thead> + <tbody> + {% for aff in contributor.profile.affiliations.all %} + <tr> + <td>{{ aff.organization }}<br/> {{ aff.description }}</td> + <td>{{ aff.get_category_display }}</td> + <td>{{ aff.date_from|date:'Y-m-d' }}</td> + <td>{{ aff.date_until|date:'Y-m-d' }}</td> + </tr> + {% empty %} + <tr><td colspan="4">No Affiliation has been defined</td></tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + {% if unavailability_form %} <hr> <div class="row">