diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py index 7d5cb452e8c2a242f72eeaa562e17d5a9394876e..48703843ab5b2b5fa7b96b1c8f70df626e513196 100644 --- a/SciPost_v1/settings/base.py +++ b/SciPost_v1/settings/base.py @@ -105,6 +105,7 @@ INSTALLED_APPS = ( 'submissions', 'theses', 'virtualmeetings', + 'organizations', 'proceedings', 'production', 'profiles', diff --git a/SciPost_v1/urls.py b/SciPost_v1/urls.py index c70408eb1afba5e6ae9d8adb61b2354a0f096637..a8c09ab1b766c4a5dc1257e25b12c8fddf700166 100644 --- a/SciPost_v1/urls.py +++ b/SciPost_v1/urls.py @@ -48,6 +48,7 @@ urlpatterns = [ url(r'^meetings/', include('virtualmeetings.urls', namespace="virtualmeetings")), url(r'^news/', include('news.urls', namespace="news")), url(r'^notifications/', include('notifications.urls', namespace="notifications")), + url(r'^organizations/', include('organizations.urls', namespace="organizations")), url(r'^petitions/', include('petitions.urls', namespace="petitions")), url(r'^preprints/', include('preprints.urls', namespace="preprints")), url(r'^proceedings/', include('proceedings.urls', namespace="proceedings")), diff --git a/organizations/__init__.py b/organizations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/organizations/admin.py b/organizations/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..8d33bd306da296a3d6f985c8f8377092312dde73 --- /dev/null +++ b/organizations/admin.py @@ -0,0 +1,15 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django.contrib import admin + +from .models import Organization + + + +class OrganizationAdmin(admin.ModelAdmin): + search_fields = ['name', 'acronym'] + + +admin.site.register(Organization, OrganizationAdmin) diff --git a/organizations/apps.py b/organizations/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..fe588afadb395afd1f7558f333825fbfc57fe295 --- /dev/null +++ b/organizations/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class OrganizationsConfig(AppConfig): + name = 'organizations' diff --git a/organizations/constants.py b/organizations/constants.py new file mode 100644 index 0000000000000000000000000000000000000000..06ecf272b554cd307f0c89e52bf3be7d34c81b02 --- /dev/null +++ b/organizations/constants.py @@ -0,0 +1,65 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +import datetime + +ORGTYPE_RESEARCH_INSTITUTE = 'ResearchRnstitute' +ORGTYPE_INTERNATIONAL_FUNDING_AGENCY = 'InternationalFundingAgency' +ORGTYPE_NATIONAL_FUNDING_AGENCY = 'NationalFundingAgency' +ORGTYPE_FUNDING_AGENCY_INITIATIVE = 'FundingAgencyInitiative' +ORGTYPE_NATIONAL_LABORATORY = 'NationalLaboratory' +ORGTYPE_NATIONAL_LIBRARY = 'NationalLibrary' +ORGTYPE_NATIONAL_ACADEMY = 'NationalAcademy' +ORGTYPE_UNIVERSITY_LIBRARY = 'UniversityLibrary' +ORGTYPE_RESEARCH_LIBRARY = 'ResearchLibrary' +ORGTYPE_PROFESSIONAL_SOCIETY = 'ProfessionalSociety' +ORGTYPE_INTERNATIONAL_CONSORTIUM = 'InternationalConsortium' +ORGTYPE_NATIONAL_CONSORTIUM = 'NationalConsortium' +ORGTYPE_FOUNDATION = 'Foundation' +ORGTYPE_GOVERNMENT_INTERNATIONAL = 'GovernmentInternational' +ORGTYPE_GOVERNMENT_NATIONAL = 'GovernmentNational' +ORGTYPE_GOVERNMENT_PROVINCIAL = 'GovernmentProvincial' +ORGTYPE_GOVERNMENT_REGIONAL = 'GovernmentRegional' +ORGTYPE_GOVERNMENT_MUNICIPAL = 'GovernmentMunicipal' +ORGTYPE_GOVERNMENTAL_MINISTRY = 'GovernmentalMinistry' +ORGTYPE_GOVERNMENTAL_OFFICE = 'GovernmentalOffice' +ORGTYPE_BUSINESS_CORPORATION = 'BusinessCorporation' +ORGTYPE_INDIVIDUAL_BENEFACTOR = 'IndividualBenefactor' +ORGTYPE_PRIVATE_BENEFACTOR = 'PrivateBenefactor' + +ORGANIZATION_TYPES = ( + (ORGTYPE_RESEARCH_INSTITUTE, 'Research Institute'), + (ORGTYPE_INTERNATIONAL_FUNDING_AGENCY, 'International Funding Agency'), + (ORGTYPE_NATIONAL_FUNDING_AGENCY, 'National Funding Agency'), + (ORGTYPE_FUNDING_AGENCY_INITIATIVE, 'Funding Agency Initiative'), + (ORGTYPE_NATIONAL_LABORATORY, 'National Laboratory'), + (ORGTYPE_NATIONAL_LIBRARY, 'National Library'), + (ORGTYPE_NATIONAL_ACADEMY, 'National Academy'), + (ORGTYPE_UNIVERSITY_LIBRARY, 'University (and its Library)'), + (ORGTYPE_RESEARCH_LIBRARY, 'Research Library'), + (ORGTYPE_PROFESSIONAL_SOCIETY, 'Professional Society'), + (ORGTYPE_INTERNATIONAL_CONSORTIUM, 'International Consortium'), + (ORGTYPE_NATIONAL_CONSORTIUM, 'National Consortium'), + (ORGTYPE_FOUNDATION, 'Foundation'), + (ORGTYPE_GOVERNMENT_INTERNATIONAL, 'Government (international)'), + (ORGTYPE_GOVERNMENT_NATIONAL, 'Government (national)'), + (ORGTYPE_GOVERNMENT_PROVINCIAL, 'Government (provincial)'), + (ORGTYPE_GOVERNMENT_REGIONAL, 'Government (regional)'), + (ORGTYPE_GOVERNMENT_MUNICIPAL, 'Government (municipal)'), + (ORGTYPE_GOVERNMENTAL_MINISTRY, 'Governmental Ministry'), + (ORGTYPE_GOVERNMENTAL_OFFICE, 'Governmental Office'), + (ORGTYPE_BUSINESS_CORPORATION, 'Business Corporation'), + (ORGTYPE_INDIVIDUAL_BENEFACTOR, 'Individual Benefactor'), + (ORGTYPE_PRIVATE_BENEFACTOR, 'Private Benefactor'), +) + +ORGSTATUS_ACTIVE = 'Active' +ORGSTATUS_SUPERSEDED = 'Superseded' +ORGSTATUS_OBSOLETE = 'Obsolete' + +ORGANIZATION_STATUSES = ( + (ORGSTATUS_ACTIVE, 'Active'), + (ORGSTATUS_SUPERSEDED, 'Superseded'), + (ORGSTATUS_OBSOLETE, 'Obsolete'), +) diff --git a/organizations/migrations/0001_initial.py b/organizations/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..3b440146b940e65f5cdd0e22433fc29e721c213a --- /dev/null +++ b/organizations/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-09-22 11:21 +from __future__ import unicode_literals + +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion +import django_countries.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Organization', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('orgtype', models.CharField(choices=[('ResearchRnstitute', 'Research Institute'), ('InternationalFundingAgency', 'International Funding Agency'), ('NationalFundingAgency', 'National Funding Agency'), ('FundingAgencyInitiative', 'Funding Agency Initiative'), ('NationalLaboratory', 'National Laboratory'), ('NationalLibrary', 'National Library'), ('NationalAcademy', 'National Academy'), ('UniversityLibrary', 'University (and its Library)'), ('ResearchLibrary', 'Research Library'), ('ProfessionalSociety', 'Professional Society'), ('InternationalConsortium', 'International Consortium'), ('NationalConsortium', 'National Consortium'), ('Foundation', 'Foundation'), ('GovernmentInternational', 'Government (international)'), ('GovernmentNational', 'Government (national)'), ('GovernmentProvincial', 'Government (provincial)'), ('GovernmentRegional', 'Government (regional)'), ('GovernmentMunicipal', 'Government (municipal)'), ('GovernmentalMinistry', 'Governmental Ministry'), ('GovernmentalOffice', 'Governmental Office'), ('BusinessCorporation', 'Business Corporation'), ('IndividualBenefactor', 'Individual Benefactor'), ('PrivateBenefactor', 'Private Benefactor')], max_length=32)), + ('status', models.CharField(choices=[('Active', 'Active'), ('Superseded', 'Superseded'), ('Obsolete', 'Obsolete')], default='Active', max_length=32)), + ('name', models.CharField(help_text='Western version of name', max_length=256)), + ('name_original', models.CharField(blank=True, help_text='Name (in original language)', max_length=256)), + ('acronym', models.CharField(blank=True, help_text='Acronym or short name', max_length=64)), + ('country', django_countries.fields.CountryField(max_length=2)), + ('address', models.TextField(blank=True)), + ('logo', models.ImageField(blank=True, upload_to='organizations/logos/')), + ('css_class', models.CharField(blank=True, max_length=256, verbose_name='Additional logo CSS class')), + ('grid_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default={}, null=True)), + ('crossref_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default={}, null=True)), + ('cf_nr_associated_publications', models.PositiveIntegerField(blank=True, help_text='NB: nr_associated_publications is a calculated field. Do not modify.', null=True)), + ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='organizations.Organization')), + ('superseded_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='organizations.Organization')), + ], + options={ + 'ordering': ['country', 'name'], + }, + ), + ] diff --git a/organizations/migrations/__init__.py b/organizations/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/organizations/models.py b/organizations/models.py new file mode 100644 index 0000000000000000000000000000000000000000..c657d6939f83bf69fbfb133372abf37356ab3157 --- /dev/null +++ b/organizations/models.py @@ -0,0 +1,137 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django.contrib.postgres.fields import JSONField +from django.db import models +from django.db.models import Sum +from django.utils import timezone +from django.urls import reverse + +from django_countries.fields import CountryField + +from .constants import ORGANIZATION_TYPES, ORGANIZATION_STATUSES, ORGSTATUS_ACTIVE + +from journals.models import Publication, PublicationAuthorsTable, OrgPubFraction + +class Organization(models.Model): + """ + An Organization instance is any type of administrative unit which SciPost + can interact with. Example types include universities, funding agencies, + research institutes etc. + + All instances can only be created by SciPost administration-level personnel, + and can thus be considered as verified. + + These objects are meant to be internally linked to all other types of + (possibly user-defined) objects used throughout the site (such as Institutions, + Partners, Affiliations, Funders etc). This enables relating all of SciPost's + services to the organizations which are impacted by its activities. + + The data here is also meant to be cross-linked to external databases, + for example the Global Research Identifier Database (GRID), Crossref, + ORCID etc. + """ + orgtype = models.CharField(max_length=32, choices=ORGANIZATION_TYPES) + status = models.CharField(max_length=32, choices=ORGANIZATION_STATUSES, + default=ORGSTATUS_ACTIVE) + name = models.CharField(max_length=256, + help_text="Western version of name") + name_original = models.CharField(max_length=256, blank=True, + help_text="Name (in original language)") + acronym = models.CharField(max_length=64, blank=True, + help_text='Acronym or short name') + country = CountryField() + address = models.TextField(blank=True) + logo = models.ImageField(upload_to='organizations/logos/', blank=True) + css_class = models.CharField(max_length=256, blank=True, + verbose_name="Additional logo CSS class") + grid_json = JSONField(default={}, blank=True, null=True) # JSON data from GRID + crossref_json = JSONField(default={}, blank=True, null=True) # JSON data from Crossref + parent = models.ForeignKey('self', blank=True, null=True, + on_delete=models.SET_NULL, related_name='children') + superseded_by = models.ForeignKey('self', blank=True, null=True, + on_delete=models.SET_NULL) + # Calculated fields (to save CPU; field name always starts with cf_) + # Number of associated publications; needs to be updated when any related + # affiliation, grant or f + cf_nr_associated_publications = models.PositiveIntegerField( + blank=True, null=True, + help_text='NB: nr_associated_publications is a calculated field. Do not modify.') + + class Meta: + ordering = ['country', 'name'] + + def __str__(self): + return self.name + + @property + def full_name(self): + full_name_str = "" + if self.name_original: + full_name_str += "%s / " % self.name_original + full_name_str += "%s" % self + return full_name_str + + @property + def full_name_with_acronym(self): + full_name_str = "" + if self.acronym: + full_name_str += "[%s] " % self.acronym + return full_name_str + self.full_name + + def get_absolute_url(self): + return reverse('partners:organization_details', args=(self.id,)) + + def get_publications(self): + return Publication.objects.filter( + models.Q(authors__affiliations__in=[self]) | + models.Q(grants__funder__organization=self) | + models.Q(funders_generic__organization=self)).distinct() + + def count_publications(self): + return self.get_publications().count() + + def update_cf_nr_associated_publications(self): + """ + Update the calculated field Organization:cf_nr_associated_publications. + """ + self.cf_nr_associated_publications = self.count_publications() + self.save() + + def pubfractions_in_year(self, year): + """ + Returns the sum of pubfractions for the given year. + """ + return OrgPubFraction.objects.filter( + organization=self, + publication__publication_date__year=year + ).aggregate(Sum('fraction'))['fraction__sum'] + + def get_contributor_authors(self): + return self.publicationauthorstable_set.select_related( + 'contributor').order_by('contributor__user__last_name') + + def get_unregistered_authors(self): + return self.publicationauthorstable_set.select_related( + 'unregistered_author').order_by('unregistered_author__last_name') + + @property + def has_current_agreement(self): + """ + Check if this organization has a current Partnership agreement. + """ + if not self.partner: + return False + return self.partner.agreements.now_active().exists() + + def get_total_contribution_obtained(self, n_years_past=None): + """ + Computes the contribution obtained from this organization, + summed over all time. + """ + contrib = 0 + now = timezone.now().date() + for agreement in self.partner.agreements.all(): + contrib += agreement.offered_yearly_contribution * int(agreement.duration.days/365) + return contrib diff --git a/organizations/templates/organizations/_organization_card.html b/organizations/templates/organizations/_organization_card.html new file mode 100644 index 0000000000000000000000000000000000000000..502d3455988f94e8eb11d8fbbf3dbdf2074a61cf --- /dev/null +++ b/organizations/templates/organizations/_organization_card.html @@ -0,0 +1,144 @@ +{% load bootstrap %} + +{% load organizations_extras %} + +<div class="card-body"> + + <div class="row"> + <div class="col-12"> + <ul class="nav nav-tabs" id="organization-{{ org.id }}-tab" role="tablist"> + {% if perms.scipost.can_manage_organizations %} + <li class="nav-item"> + <a class="nav-link" id="details-{{ org.id }}-tab" data-toggle="tab" href="#details-{{ org.id }}" role="tab" aria-controls="details-{{ org.id }}" aria-selected="true">Details</a> + </li> + {% endif %} + <li class="nav-item"> + <a class="nav-link active" id="publications-{{ org.id }}-tab" data-toggle="tab" href="#publications-{{ org.id }}" role="tab" aria-controls="publications-{{ org.id }}" aria-selected="true">Publications & PubFractions</a> + </li> + <li class="nav-item"> + <a class="nav-link" id="authors-{{ org.id }}-tab" data-toggle="tab" href="#authors-{{ org.id }}" role="tab" aria-controls="authors-{{ org.id }}" aria-selected="true">Associated Authors</a> + </li> + <li class="nav-item"> + <a class="nav-link" id="funders-{{ org.id }}-tab" data-toggle="tab" href="#funders-{{ org.id }}" role="tab" aria-controls="funders-{{ org.id }}" aria-selected="true">Funder instances</a> + </li> + <li class="nav-item"> + <a class="nav-link" id="partnership-{{ org.id }}-tab" data-toggle="tab" href="#partnership-{{ org.id }}" role="tab" aria-controls="partnership-{{ org.id }}" aria-selected="true">Partnership history</a> + </li> + {% if perms.scipost.can_manage_organizations %} + <li class="nav-item"> + <a class="nav-link" id="manage-{{ org.id }}-tab" data-toggle="tab" href="#manage-{{ org.id }}" role="tab" aria-controls="manage-{{ org.id }}" aria-selected="true">Manage</a> + </li> + {% endif %} + </ul> + + <div class="tab-content" id="organization-{{ org.id }}-tab"> + + {% if perms.scipost.can_manage_organizations %} + <div class="tab-pane pt-4" id="details-{{ org.id }}" role="tabpanel" aria-labelledby="details-{{ org.id }}-tab"> + <h3>Details:</h3> + {% include 'organizations/_organization_details_contents.html' with org=org %} + </div> + {% endif %} + + <div class="tab-pane show active pt-4" id="publications-{{ org.id }}" role="tabpanel" aria-labelledby="publications-{{ org.id }}-tab"> + <h3>Publications associated to this Organization <span class="text-muted small">(with total PubFractions <i class="fa fa-info-circle" data-toggle="tooltip" data-html="true" title="" data-original-title="Fraction of a publication's funding/institutional support associated to a given Organization"></i>)</span>:</h3> + {% for pubyear in pubyears %} + <h4>{{ pubyear }} <span class="text-muted small">(total pubfractions: {{ org|pubfractions_in_year:pubyear }})</span></h4> + <ul> + {% for publication in org.get_publications %} + {% if publication.publication_date|date:'Y'|add:"0" == pubyear %} + <li> + <a href="{{ publication.get_absolute_url }}">{{ publication.title }}</a> + <br>by {{ publication.author_list }}, + <br>{{ publication.citation }} + </li> + {% endif %} + {% endfor %} + </ul> + {% endfor %} + </div> + + <div class="tab-pane pt-4" id="authors-{{ org.id }}" role="tabpanel" aria-labelledby="authors-{{ org.id }}-tab"> + <h3>Associated Authors:</h3> + <div class="row"> + <div class="col-6"> + <h4>Registered Contributors:</h4> + <ul> + {% for author in org.get_contributor_authors %} + <li><a href="{{ author.get_absolute_url }}">{{ author }}</a></li> + {% empty %} + <li>No Contributors found</li> + {% endfor %} + </ul> + </div> + <div class="col-6"> + <h4>Unregistered:</h4> + <ul> + {% for author in org.get_unregistered_authors %} + <li>{{ author }}</li> + {% empty %} + <li>No unregistered author found</li> + {% endfor %} + </ul> + </div> + </div> + </div> + + <div class="tab-pane pt-4" id="funders-{{ org.id }}" role="tabpanel" aria-labelledby="funders-{{ org.id }}-tab"> + <h3>Funder instances (from FundRef) associated to this Organization:</h3> + <ul> + {% for funder in org.funder_set.all %} + <li>{{ funder }}</li> + {% empty %} + <li>No FundRef instance found</li> + {% endfor %} + </ul> + </div> + + <div class="tab-pane pt-4" id="partnership-{{ org.id }}" role="tabpanel" aria-labelledby="partnership-{{ org.id }}-tab"> + <h3>Partnership history:</h3> + {% with agreement=org.partner.get_latest_active_agreement %} + {% if agreement %} + <p>This organization is currently a SciPost Supporting Partner.</p> + {% endif %} + {% endwith %} + {% if org.partner.agreements %} + <table class="table"> + <thead> + <th>Status</th> + <th>Duration</th> + <th>Start date</th> + <th>Contribution/year</th> + </thead> + <tbody> + {% for agreement in org.partner.agreements.all %} + <tr> + <td>{{ agreement.get_status_display }}</td> + <td>{{ agreement.get_duration_display }}</td> + <td>{{ agreement.start_date }}</td> + <td>€ {{ agreement.offered_yearly_contribution }}</td> + </tr> + {% endfor %} + <tr style="border-top: 2px solid black"> + <td colspan="2"></td><td>Total contribution obtained:</td><td>€ {{ org.get_total_contribution_obtained }}</td></tr> + </tbody> + </table> + {% else %} + <p>This organization has <span class="text-danger">not yet</span> been a SciPost Supporting Partner.</p> + {% endif %} + </div> + + <div class="tab-pane pt-4" id="manage-{{ org.id }}" role="tabpanel" aria-labelledby="manage-{{ org.id }}-tab"> + {% if perms.scipost.can_manage_organizations %} + <h3>Manage this organization:</h3> + <ul> + <li><a href="{% url 'organizations:organization_update' pk=org.id %}">Update</a></li> + <li><a href="{% url 'organizations:organization_delete' pk=org.id %}">Delete</a></li> + </ul> + <hr/> + {% endif %} + </div> + </div> + </div> + </div> +</div> diff --git a/organizations/templates/organizations/_organization_details_contents.html b/organizations/templates/organizations/_organization_details_contents.html new file mode 100644 index 0000000000000000000000000000000000000000..88fb765d1d0766da467c6591c9d01cbc6f3ced9e --- /dev/null +++ b/organizations/templates/organizations/_organization_details_contents.html @@ -0,0 +1,39 @@ +<div class="row"> + <div class="col-6"> + <table class="table"> + <tr> + <td>Name</td><td>{{ org.name }}</td> + </tr> + {% if org.name_original %} + <tr> + <td>Name (original vn)</td><td>{{ org.name_original }}</td> + </tr> + {% endif %} + <tr> + <td>Acronym</td><td>{{ org.acronym }}</td> + </tr> + <tr> + <td>Country</td><td>{{ org.get_country_display }}</td> + </tr> + <tr> + <td>Address</td><td>{{ org.address }}</td> + </tr> + <tr> + <td>Type</td><td>{{ org.get_orgtype_display }}</td> + </tr> + <tr> + <td>Status</td><td>{{ org.get_status_display }}</td> + </tr> + {% if org.superseded_by %} + <tr> + <td>Superseded by</td><td>{{ org.superseded_by.get_absolute_url }}</td> + </tr> + {% endif %} + </table> + </div> + <div class="col-6"> + {% if org.logo %} + <img class="d-flex mr-3 {{ org.css_class }}" src="{{ org.logo.url }}" alt="image"/> + {% endif %} + </div> +</div> diff --git a/organizations/templates/organizations/base.html b/organizations/templates/organizations/base.html new file mode 100644 index 0000000000000000000000000000000000000000..d90989f645ce5c61b35e66a2aecc7914abc8d157 --- /dev/null +++ b/organizations/templates/organizations/base.html @@ -0,0 +1,15 @@ +{% extends 'scipost/base.html' %} + +{% block body_class %}{{ block.super }} organizations{% endblock %} + +{% block breadcrumb %} + <div class="container-outside header"> + <div class="container"> + <nav class="breadcrumb hidden-sm-down"> + {% block breadcrumb_items %} + <a href="{% url 'organizations:organizations' %}" class="breadcrumb-item">Organizations</a> + {% endblock %} + </nav> + </div> + </div> +{% endblock %} diff --git a/organizations/templates/organizations/organization_confirm_delete.html b/organizations/templates/organizations/organization_confirm_delete.html new file mode 100644 index 0000000000000000000000000000000000000000..8ea21f23cfe9abbc543a21f2c0eabb1e5701e5aa --- /dev/null +++ b/organizations/templates/organizations/organization_confirm_delete.html @@ -0,0 +1,25 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: Delete Organization{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Delete Organization</h1> + {{ object }} + </div> +</div> +<div class="row"> + <div class="col-12"> + <form method="post"> + {% csrf_token %} + <h3 class="mb-2">Are you sure you want to delete this Organization?</h3> + <input type="submit" class="btn btn-danger" value="Yes, delete it" /> + </form> + </ul> + </div> +</div> + +{% endblock content %} diff --git a/organizations/templates/organizations/organization_create.html b/organizations/templates/organizations/organization_create.html new file mode 100644 index 0000000000000000000000000000000000000000..c95ac6966833b1f9d9408f0bfdab13f4468263a6 --- /dev/null +++ b/organizations/templates/organizations/organization_create.html @@ -0,0 +1,16 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: Create Organization{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + <form action="{% url 'organizations:organization_create' %}" method="post" enctype="multipart/form-data"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" value="Submit" class="btn btn-primary"> + </div> +</div> +{% endblock content %} diff --git a/organizations/templates/organizations/organization_detail.html b/organizations/templates/organizations/organization_detail.html new file mode 100644 index 0000000000000000000000000000000000000000..7c8c16a839a42ab73ab66d6baab223b712fe1d33 --- /dev/null +++ b/organizations/templates/organizations/organization_detail.html @@ -0,0 +1,24 @@ +{% extends 'organizations/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: Organization details{% endblock pagetitle %} + +{% block breadcrumb_items %} +{{ block.super }} +<a href="{% url 'organizations:organization_list' %}" class="breadcrumb-item">Organizations</a> +<span class="breadcrumb-item">{{ organization }}</span> +{% endblock %} + +{% block content %} + +<h1 class="highlight">{{ organization }}</h1> + +<div class="row"> + <div class="col-12"> + {% include 'organizations/_organization_card.html' with org=organization pubyears=pubyears %} + </div> +</div> + + +{% endblock content %} diff --git a/organizations/templates/organizations/organization_list.html b/organizations/templates/organizations/organization_list.html new file mode 100644 index 0000000000000000000000000000000000000000..0195cbd0b181ac5c58d151be704d3e66fef2a125 --- /dev/null +++ b/organizations/templates/organizations/organization_list.html @@ -0,0 +1,117 @@ +{% extends 'partners/base.html' %} + +{% block pagetitle %}: Organizations{% endblock pagetitle %} + +{% load staticfiles %} +{% load partners_extras %} + +{% block headsup %} +<script type="text/javascript"> +$(document).ready(function($) { + $(".table-row").click(function() { + window.document.location = $(this).data("href"); + }); +}); +</script> +{% endblock headsup %} + +{% block breadcrumb_items %} +{{ block.super }} +<span class="breadcrumb-item">Organizations</span> +{% endblock %} + +{% block content %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Organizations</h1> + {% if perms.scipost.can_manage_organizations %} + <h3>Management actions:</h3> + <ul> + <li><a href="{% url 'partners: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> + </ul> + {% endif %} + </div> +</div> + + +<div class="row"> + <div class="col-12"> + <h3>This page lists Organizations which have benefitted from SciPost's publishing activities.</h3> + </div> +</div> + + +<div class="row"> + <div class="col-5"> + <p>Organizations are linked through being in a publication's author affiliations, grant-giving agencies or explicit support acknowledgements.</p> + <p>For each Organization, the NAP (number of associated publications) is given (you can order in decreasing/increasing NAP using the header arrows).</p> + </div> + <div class="col-1"></div> + <div class="col-5"> + <p>Click on a row to see more details about the Organization, including per-year breakdowns of:</p> + <ul> + <li>associated publications</li> + <li>associated support fractions</li> + <li>associated authors</li> + <li>partnership history</li> + </ul> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <table class="table table-hover mb-5"> + <thead class="thead-default"> + <tr> + <th><a href="?order_by=country">Country</a></th> + <th><a href="?order_by=name">Name</a> <small>[acronym]</small></th> + <th>NAP <i class="fa fa-info-circle" data-toggle="tooltip" data-html="true" title="" data-original-title="Number of associated publications<br/>For details, click on the Organization and consult the Associated Publications tab"></i> + {% if request.GET.ordering != 'asc' %}</a> <a href="?order_by=nap&ordering=asc"><i class="fa fa-sort-asc"></i></a>{% endif %} +{% if request.GET.ordering != 'desc' %}<a href="?order_by=nap&ordering=desc"><i class="fa fa-sort-desc"></i></a>{% endif %} +</th> + <th>Partner?</th> + </tr> + </thead> + <tbody> + {% for org in object_list %} + <tr class="table-row" data-href="{% url 'partners:organization_details' pk=org.id %}" target="_blank" style="cursor: pointer;"> + <td><img src="{{ org.country.flag }}" alt="{{ org.country }} flag"/> <span class="text-muted"><small>[{{ org.country }}]</small></span> {{ org.get_country_display }}</td> + <td> + {{ org.full_name }} <small>{% if org.acronym %}[{{ org.acronym }}]{% endif %}</small> + {% if org.parent %} + <small class="text-muted"><p>Parent: {{ org.parent }}</p></small> + {% endif %} + {% if org.children.all %} + <small class="text-muted"> + <p>Parent of: + {% for child in org.children.all %} + {{ child }}{% if not forloop.last %}, {% endif %} + {% endfor %} + </p> + </small> + {% endif %} + {% if org.superseded_by %} + <small class="text-muted"><p>Superseded by {{ org.superseded_by }}</p></small> + {% endif %} + </td> + <td>{{ org.cf_nr_associated_publications }}</td> + {% if org.has_current_agreement %} + <td class="bg-success">Yes</td> + {% elif org.partner.agreements %} + <td class="bg-primary text-white">Due for renewal</td> + {% else %} + <td class="bg-warning">Not yet</td> + {% endif %} + <td></td> + </tr> + {% empty %} + <tr><td colspan="4">No organizations found</td></tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + + +{% endblock content %} diff --git a/organizations/templates/organizations/organization_update.html b/organizations/templates/organizations/organization_update.html new file mode 100644 index 0000000000000000000000000000000000000000..2f101032dcbd303b1d0d015accacde4d67d484c4 --- /dev/null +++ b/organizations/templates/organizations/organization_update.html @@ -0,0 +1,16 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: Update Organization{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + <form action="{% url 'partners:organization_update' pk=object.id %}" method="post" enctype="multipart/form-data"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" value="Submit" class="btn btn-primary"> + </div> +</div> +{% endblock content %} diff --git a/organizations/tests.py b/organizations/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6 --- /dev/null +++ b/organizations/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/organizations/urls.py b/organizations/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..ecfa582c482686c60c16c2661649dd6864209707 --- /dev/null +++ b/organizations/urls.py @@ -0,0 +1,35 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django.conf.urls import url + +from . import views + +urlpatterns = [ + url( + r'^$', + views.OrganizationListView.as_view(), + name='organizations' + ), + url( + r'^add/$', + views.OrganizationCreateView.as_view(), + name='organization_create' + ), + url( + r'^(?P<pk>[0-9]+)/update/$', + views.OrganizationUpdateView.as_view(), + name='organization_update' + ), + url( + r'^(?P<pk>[0-9]+)/delete/$', + views.OrganizationDeleteView.as_view(), + name='organization_delete' + ), + url( + r'^(?P<pk>[0-9]+)/$', + views.OrganizationDetailView.as_view(), + name='organization_details' + ), +] diff --git a/organizations/views.py b/organizations/views.py new file mode 100644 index 0000000000000000000000000000000000000000..121015f60daf9a904ace6e12d749d1cd312b2f41 --- /dev/null +++ b/organizations/views.py @@ -0,0 +1,79 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django.core.urlresolvers import reverse_lazy +from django.shortcuts import render +from django.utils import timezone +from django.views.generic.detail import DetailView +from django.views.generic.edit import CreateView, UpdateView, DeleteView +from django.views.generic.list import ListView + +from .models import Organization + +from scipost.mixins import PermissionsMixin + + +class OrganizationCreateView(PermissionsMixin, CreateView): + """ + Create a new Organization. + """ + permission_required = 'scipost.can_manage_organizations' + model = Organization + fields = '__all__' + template_name = 'organizations/organization_create.html' + success_url = reverse_lazy('organizations:organizations') + + +class OrganizationUpdateView(PermissionsMixin, UpdateView): + """ + Update an Organization. + """ + permission_required = 'scipost.can_manage_organizations' + model = Organization + fields = '__all__' + template_name = 'organizations/organization_update.html' + success_url = reverse_lazy('organizations:organizations') + + +class OrganizationDeleteView(PermissionsMixin, DeleteView): + """ + Delete an Organization. + """ + permission_required = 'scipost.can_manage_organizations' + model = Organization + success_url = reverse_lazy('organizations:organizations') + + +class OrganizationListView(ListView): + model = Organization + + def get_context_data(self, *args, **kwargs): + 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() + context['pubyears'] = range(int(timezone.now().strftime('%Y')), 2015, -1) + return context + + def get_queryset(self): + qs = super().get_queryset() + order_by = self.request.GET.get('order_by') + ordering = self.request.GET.get('ordering') + if order_by == 'country': + qs = qs.order_by('country') + elif order_by == 'name': + qs = qs.order_by('name') + elif order_by == 'nap': + qs = qs.order_by('cf_nr_associated_publications') + if ordering == 'desc': + qs = qs.reverse() + return qs + + +class OrganizationDetailView(DetailView): + model = Organization + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) + context['pubyears'] = range(int(timezone.now().strftime('%Y')), 2015, -1) + return context