diff --git a/affiliations/forms.py b/affiliations/forms.py index 41b281de326282a01d7c56c6ac7318af868402a0..05dc8c902e3192f72a16a1d2992c4119471bf644 100644 --- a/affiliations/forms.py +++ b/affiliations/forms.py @@ -9,6 +9,8 @@ from django_countries import countries from django_countries.fields import LazyTypedChoiceField from django_countries.widgets import CountrySelectWidget +from ajax_select.fields import AutoCompleteSelectField + from common.widgets import DateWidget from .models import Affiliation, Institution @@ -117,3 +119,11 @@ class InstitutionMergeForm(forms.ModelForm): institution=old_institution).update(institution=self.instance) old_institution.delete() return self.instance + + +class InstitutionOrganizationSelectForm(forms.ModelForm): + organization = AutoCompleteSelectField('organization_lookup') + + class Meta: + model = Institution + fields = [] diff --git a/affiliations/migrations/0003_institution_organization.py b/affiliations/migrations/0003_institution_organization.py new file mode 100644 index 0000000000000000000000000000000000000000..169975618df4f7454d6c1c52bf9a8cfbc488171e --- /dev/null +++ b/affiliations/migrations/0003_institution_organization.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2019-03-29 06:44 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('organizations', '0010_auto_20190223_1406'), + ('affiliations', '0002_auto_20171229_1435'), + ] + + operations = [ + migrations.AddField( + model_name='institution', + name='organization', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='institutions', to='organizations.Organization'), + ), + ] diff --git a/affiliations/models.py b/affiliations/models.py index 5bb05f92ecc0971936f30f21818613315aa7372c..ed9e2d280e00ddef8f6fe5a089849e6fd18ef92d 100644 --- a/affiliations/models.py +++ b/affiliations/models.py @@ -22,6 +22,8 @@ class Institution(models.Model): acronym = models.CharField(max_length=16, blank=True) country = CountryField() type = models.CharField(max_length=16, choices=INSTITUTION_TYPES, default=TYPE_UNIVERSITY) + organization = models.ForeignKey('organizations.Organization', on_delete=models.CASCADE, + blank=True, null=True) objects = InstitutionQuerySet.as_manager() diff --git a/affiliations/templates/affiliations/institution_link_organization.html b/affiliations/templates/affiliations/institution_link_organization.html new file mode 100644 index 0000000000000000000000000000000000000000..d6413203d9946a246b2e11209fb99c4e73cc0044 --- /dev/null +++ b/affiliations/templates/affiliations/institution_link_organization.html @@ -0,0 +1,45 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: link Institution to Organization{% endblock pagetitle %} + +{% load bootstrap %} + +{% block breadcrumb_items %} +{{ block.super }} +<span class="breadcrumb-item">{{ funder.name }}</span> +{% endblock %} + +{% block content %} + +<h1>Institution: link to Organization</h1> +<div class="row"> + <div class="col-4"> + <table class="table"> + <tbody> + <tr><td>Name:</td><td>{{ institution.name }}</td></tr> + <tr><td>Acronym:</td><td>{{ institution.acronym }}</td></tr> + <tr><td>Country:</td><td>{{ institution.get_country_display }}</td></tr> + <tr><td>Type:</td><td>{{ institution.get_type_display }}</td></tr> + <tr><td>Organization:</td><td>{{ institution.organization }}</td></tr> + </tbody> + </table> + </div> + <div class="col-6"> + <h3>Link to:</h3> + <form action="{% url 'affiliations:link_to_organization' pk=institution.pk %}" method="post"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" value="Link" class="btn btn-primary"> + </form> + </div> + <div class="col-2"> + <p>Can't find it in the selector? <a href="{% url 'organizations:organization_create' %}" target="_blank">Add a new organization to our database</a> (opens in new window)</p> + </div> +</div> + +{% endblock content %} + +{% block footer_script %} +{{ block.super }} +{{ form.media }} +{% endblock footer_script %} diff --git a/affiliations/templates/affiliations/institutions_without_organization_list.html b/affiliations/templates/affiliations/institutions_without_organization_list.html new file mode 100644 index 0000000000000000000000000000000000000000..2c48b44eb60b0a06a46a409d31e59ac8a66eb956 --- /dev/null +++ b/affiliations/templates/affiliations/institutions_without_organization_list.html @@ -0,0 +1,36 @@ +{% extends 'affiliations/base.html' %} + + +{% block pagetitle %}: Institutions{% endblock pagetitle %} + + +{% block breadcrumb_items %} + <span class="breadcrumb-item">Institutions without Organization</span> +{% endblock %} + +{% block content %} + +<h1 class="highlight">Institutions without Organization</h1> + +{% if is_paginated %} + {% include 'partials/pagination.html' with page_obj=page_obj %} +{% endif %} + +<ul> + {% for institution in object_list %} + <li> + {% if perms.scipost.can_manage_affiliations %} + <a href="{% url 'affiliations:link_to_organization' pk=institution.pk %}">Link to Org</a>  + {% endif %} + <a href="{{ institution.get_absolute_url }}">{{ institution }}</a> + </li> + {% empty %} + <li><em>There are no Institutions without an Organization.</em><li> + {% endfor %} +</ul> +{% if is_paginated %} + {% include 'partials/pagination.html' with page_obj=page_obj %} +{% endif %} + + +{% endblock content %} diff --git a/affiliations/urls.py b/affiliations/urls.py index 5221f5d8ea7ab48e491885a2f2cacac743ff0bf4..0acd96419799dddcee893c7109974bf32913149c 100644 --- a/affiliations/urls.py +++ b/affiliations/urls.py @@ -14,4 +14,10 @@ urlpatterns = [ name='institution_edit'), url(r'^(?P<institution_id>[0-9]+)/merge$', views.merge_institutions, name='merge_institutions'), + url(r'^institutions_without_organization/$', + views.InstitutionWithoutOrganizationListView.as_view(), + name='institutions_without_organization'), + url(r'^(?P<pk>[0-9]+)/link_to_organization/$', + views.LinkInstitutionToOrganizationView.as_view(), + name='link_to_organization'), ] diff --git a/affiliations/views.py b/affiliations/views.py index 3107fdf2cfda6f972a654697dafaac8ff7db4d97..6e5b0698dae442c5be6a80d34128c5b1eba2edeb 100644 --- a/affiliations/views.py +++ b/affiliations/views.py @@ -5,14 +5,16 @@ __license__ = "AGPL v3" from django.shortcuts import redirect from django.contrib import messages from django.contrib.auth.decorators import permission_required -from django.urls import reverse +from django.urls import reverse, reverse_lazy from django.utils.decorators import method_decorator from django.views.generic.detail import DetailView from django.views.generic.edit import UpdateView from django.views.generic.list import ListView from django.shortcuts import get_object_or_404 -from .forms import InstitutionMergeForm +from scipost.mixins import PermissionsMixin + +from .forms import InstitutionMergeForm, InstitutionOrganizationSelectForm from .models import Institution @@ -59,3 +61,24 @@ def merge_institutions(request, institution_id): a=form.cleaned_data.get('institution', '?'), b=institution)) return redirect(reverse('affiliations:institution_edit', args=(institution.id,))) + + +class InstitutionWithoutOrganizationListView(ListView): + queryset = Institution.objects.filter(organization=None) + paginate_by = 20 + template_name = 'affiliations/institutions_without_organization_list.html' + + +class LinkInstitutionToOrganizationView(PermissionsMixin, UpdateView): + """ + For an existing Institution instance, specify the link to an Organization. + """ + permission_required = 'scipost.can_manage_affiliations' + model = Institution + form_class = InstitutionOrganizationSelectForm + template_name = 'affiliations/institution_link_organization.html' + success_url = reverse_lazy('affiliations:institutions_without_organization') + + def form_valid(self, form): + form.instance.organization = form.cleaned_data['organization'] + return super().form_valid(form) diff --git a/organizations/templates/organizations/organization_list.html b/organizations/templates/organizations/organization_list.html index 4cce72c17bbb585be1092f2a5172fbaf4ce6ab1d..eca989ccb45d1f9a0f2c485fbccfebce1757b9d2 100644 --- a/organizations/templates/organizations/organization_list.html +++ b/organizations/templates/organizations/organization_list.html @@ -36,6 +36,7 @@ $(document).ready(function($) { <li><a href="{% url 'organizations:dashboard' %}">Go to the dashboard</a></li> <li><a href="{% url 'organizations:organization_create' %}">Create a new Organization instance</a></li> <li><a href="{% url 'funders:funders_dashboard' %}">Link Funders to Organizations</a> ({{ nr_funders_wo_organization }} found in need of linking)</li> + <li><a href="{% url 'affiliations:institutions_without_organization' %}">Link (deprecated) affiliations.Institutions to Organizations</a> ({{ nr_institutions_wo_organization }} found in need of linking)</li> </ul> {% endif %} </div> diff --git a/organizations/views.py b/organizations/views.py index 78d73153fd2b314985a98ebc5db0f60f57751cb8..ee7e856be60608d4865329f549d61d8672ff3a0c 100644 --- a/organizations/views.py +++ b/organizations/views.py @@ -23,6 +23,7 @@ from .forms import OrganizationEventForm, ContactPersonForm,\ NewContactForm, ContactActivationForm, ContactRoleForm from .models import Organization, OrganizationEvent, ContactPerson, Contact, ContactRole +from affiliations.models import Institution from funders.models import Funder from mails.utils import DirectMailUtil from mails.views import MailEditorSubview @@ -70,6 +71,9 @@ class OrganizationListView(PaginationMixin, ListView): context = super().get_context_data(*args, **kwargs) if self.request.user.has_perm('scipost.can_manage_organizations'): context['nr_funders_wo_organization'] = Funder.objects.filter(organization=None).count() + if self.request.user.has_perm('scipost.can_manage_organizations'): + context['nr_institutions_wo_organization'] = Institution.objects.filter( + organization=None).count() context['pubyears'] = range(int(timezone.now().strftime('%Y')), 2015, -1) context['countrycodes'] = [code['country'] for code in list( Organization.objects.all().distinct('country').values('country'))] diff --git a/profiles/migrations/0021_auto_20190329_0744.py b/profiles/migrations/0021_auto_20190329_0744.py new file mode 100644 index 0000000000000000000000000000000000000000..28fd2593584769f68a491869f264b9dbcfb70ed7 --- /dev/null +++ b/profiles/migrations/0021_auto_20190329_0744.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2019-03-29 06:44 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0020_auto_20190327_1713'), + ] + + operations = [ + 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'), ('unspecified', 'Unspecified')], default='unspecified', help_text='Select the most suitable category', max_length=64), + ), + ]