diff --git a/colleges/converters.py b/colleges/converters.py new file mode 100644 index 0000000000000000000000000000000000000000..5bc78f5a5e212157720b0c697a836e08a583856b --- /dev/null +++ b/colleges/converters.py @@ -0,0 +1,20 @@ +__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django.urls.converters import SlugConverter + +from colleges.models import College + + +class CollegeSlugConverter(SlugConverter): + + def to_python(self, value): + try: + return College.objects.get(slug=value).slug + except College.DoesNotExist: + return ValueError + return value + + def to_url(self, value): + return value diff --git a/colleges/migrations/0024_college_slug.py b/colleges/migrations/0024_college_slug.py new file mode 100644 index 0000000000000000000000000000000000000000..9d161c3fd6740a6c2aabc416c86e95eff75dc809 --- /dev/null +++ b/colleges/migrations/0024_college_slug.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.16 on 2020-09-26 03:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('colleges', '0023_auto_20200925_1632'), + ] + + operations = [ + migrations.AddField( + model_name='college', + name='slug', + field=models.SlugField(allow_unicode=True, blank=True), + ), + ] diff --git a/colleges/migrations/0025_populate_college_slug.py b/colleges/migrations/0025_populate_college_slug.py new file mode 100644 index 0000000000000000000000000000000000000000..46508d24cc3c74b7ea81512bd53dbd20abadad8c --- /dev/null +++ b/colleges/migrations/0025_populate_college_slug.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.16 on 2020-09-26 03:56 + +from django.db import migrations +from django.utils.text import slugify + + +def populate_college_slug(apps, schema_editor): + College = apps.get_model('colleges.College') + + for c in College.objects.all(): + c.slug = slugify(c.name.partition('(')[0].strip()) + c.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('colleges', '0024_college_slug'), + ] + + operations = [ + migrations.RunPython(populate_college_slug, + reverse_code=migrations.RunPython.noop), + ] diff --git a/colleges/migrations/0026_auto_20200926_0606.py b/colleges/migrations/0026_auto_20200926_0606.py new file mode 100644 index 0000000000000000000000000000000000000000..69d9594a5d7faca7df224198c178e8e4e5744757 --- /dev/null +++ b/colleges/migrations/0026_auto_20200926_0606.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.16 on 2020-09-26 04:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('colleges', '0025_populate_college_slug'), + ] + + operations = [ + migrations.AlterField( + model_name='college', + name='slug', + field=models.SlugField(allow_unicode=True, unique=True), + ), + ] diff --git a/colleges/models/college.py b/colleges/models/college.py index 51a64d7222a06872b7207b069951dc6569e3d30a..3d4eafab718e79c61cfe4907e5020c9586136fbd 100644 --- a/colleges/models/college.py +++ b/colleges/models/college.py @@ -34,6 +34,11 @@ class College(models.Model): related_name='colleges' ) + slug = models.SlugField( + unique=True, + allow_unicode=True + ) + order = models.PositiveSmallIntegerField() class Meta: @@ -57,7 +62,7 @@ class College(models.Model): @property def specialties(self): - return Specialty.objects.filter(journals__college__pk=self.id) + return Specialty.objects.filter(journals__college__pk=self.id).distinct() @property def is_field_wide(self): diff --git a/colleges/static/colleges/colleges.js b/colleges/static/colleges/colleges.js index 8dc9da577207516df5f31d69101acea79486096e..07715dc47c39bac7beef86ee800aba1168c1b423 100644 --- a/colleges/static/colleges/colleges.js +++ b/colleges/static/colleges/colleges.js @@ -10,17 +10,17 @@ $(function() { // Reset active search after closing the box if(!el.is(':visible')) { - $('.all-specializations .specialization') + $('.all-specialties .specialty') .removeClass('active-search') - .trigger('search-specialization'); + .trigger('search-specialty'); } }); - // Hover/Click class to Contributors on hovering specializations - $('.all-specializations .specialization') + // Hover/Click class to Contributors on hovering specialties + $('.all-specialties .specialty') .on('mouseover', function() { - var code = $(this).attr('data-specialization'); - $('.single[data-specialization="'+code+'"]') + var code = $(this).attr('data-specialty'); + $('.single[data-specialty="'+code+'"]') .parents('.contributor') .addClass('hover-active'); }) @@ -31,13 +31,13 @@ $(function() { // Remove hover-class $(this) .toggleClass('active-search') - .trigger('search-specialization'); + .trigger('search-specialty'); }) - .on('search-specialization', function() { - // Reset: searching multiple specializations is not supported + .on('search-specialty', function() { + // Reset: searching multiple specialties is not supported $('.search-contributors.active-search').removeClass('active-search'); $('.contributor.active').removeClass('active'); - $('.specialization.active-search').not(this).removeClass('active-search'); + $('.specialty.active-search').not(this).removeClass('active-search'); var el = $(this); if( el.hasClass('active-search') ) { @@ -45,10 +45,63 @@ $(function() { $('.search-contributors').addClass('active-search'); // Add class to specialized Contributors - var code = el.attr('data-specialization'); - $('.single[data-specialization="' + code + '"]') + var code = el.attr('data-specialty'); + $('.single[data-specialty="' + code + '"]') .parents('.contributor') .addClass('active'); } }); + + + // // Toggle Specialization codes block + // $('[data-toggle="toggle-show"]').on('click', function(){ + // var el = $($(this).attr('data-target')); + // el.toggle(); + + // // Switch texts of link + // $('[data-toggle="toggle-show"]').toggle(); + + // // Reset active search after closing the box + // if(!el.is(':visible')) { + // $('.all-specializations .specialization') + // .removeClass('active-search') + // .trigger('search-specialization'); + // } + // }); + + // // Hover/Click class to Contributors on hovering specializations + // $('.all-specializations .specialization') + // .on('mouseover', function() { + // var code = $(this).attr('data-specialization'); + // $('.single[data-specialization="'+code+'"]') + // .parents('.contributor') + // .addClass('hover-active'); + // }) + // .on('mouseleave', function() { + // $('.contributor.hover-active').removeClass('hover-active'); + // }) + // .on('click', function() { + // // Remove hover-class + // $(this) + // .toggleClass('active-search') + // .trigger('search-specialization'); + // }) + // .on('search-specialization', function() { + // // Reset: searching multiple specializations is not supported + // $('.search-contributors.active-search').removeClass('active-search'); + // $('.contributor.active').removeClass('active'); + // $('.specialization.active-search').not(this).removeClass('active-search'); + + // var el = $(this); + // if( el.hasClass('active-search') ) { + // // Add general 'click-active' class + // $('.search-contributors').addClass('active-search'); + + // // Add class to specialized Contributors + // var code = el.attr('data-specialization'); + // $('.single[data-specialization="' + code + '"]') + // .parents('.contributor') + // .addClass('active'); + // } + // }); }); diff --git a/colleges/templates/colleges/college_detail.html b/colleges/templates/colleges/college_detail.html index eaa01886282591617a0c75b0ea3e337f2a0cc445..6a23f0ea02ddd4b894439f3a67031b552e9744f7 100644 --- a/colleges/templates/colleges/college_detail.html +++ b/colleges/templates/colleges/college_detail.html @@ -8,10 +8,10 @@ {% block breadcrumb_items %} {{ block.super }} <a href="{% url 'colleges:colleges' %}" class="breadcrumb-item">Colleges</a> - <span class="breadcrumb-item">Editorial College ({{ discipline|get_discipline_display }})</span> + <span class="breadcrumb-item">{{ college }}</span> {% endblock %} -{% block pagetitle %}: College ({{ discipline|get_discipline_display }}){% endblock pagetitle %} +{% block pagetitle %}: {{ college }}{% endblock pagetitle %} {% block content %} @@ -26,27 +26,21 @@ {% endif %} - <h2 class="highlight">Editorial College ({{ discipline|get_discipline_display }})</h2> + <h2 class="highlight">{{ college }}</h2> <div class="row"> <div class="col-12"> - <button class="btn btn-primary" data-toggle="toggle-show" data-target="#specializations-{{ discipline }}">Select by specialization</button> - <button class="btn btn-primary" style="display: none;" data-toggle="toggle-show" data-target="#specializations-{{ discipline }}">Show full list of Fellows in {{ discipline }}</button> - <div id="specializations-{{ discipline }}" class="card bg-white border-default all-specializations mt-2" style="display: none"> + <button class="btn btn-primary" data-toggle="toggle-show" data-target="#specialties-{{ college.acad_field }}">Select by specialty</button> + <button class="btn btn-primary" style="display: none;" data-toggle="toggle-show" data-target="#specialties-{{ college.acad_field }}">Show full list of Fellows</button> + <div id="specialties-{{ college.acad_field }}" class="card bg-white border-default all-specialties mt-2" style="display: none"> <div class="card-body"> <p><em class="text-muted mt-2">Hover to highlight or click to select</em></p> <div class="row"> <div class="col-md-6"> - {% with total_count=object_list|length %} - {% for code in specializations %} - <div class="specialization" data-specialization="{{ code.0|lower }}">{{ code.0 }} - {{ code.1 }}</div> - {% if forloop.counter|is_modulo_one_half:total_count %} - </div> - <div class="col-md-6"> - {% endif %} - {% endfor %} - {% endwith %} + {% for specialty in college.specialties.all %} + <div class="specialty m-1" data-specialty="{{ specialty.slug }}">{{ specialty.code }} - {{ specialty }}</div> + {% endfor %} </div> </div> </div> @@ -54,10 +48,10 @@ </div> </div> - <div class="row search-contributors" data-contributors="{{ discipline|lower }}"> + <div class="row search-contributors" data-contributors="{{ college.acad_field.slug }}"> <div class="col-12"> <div class="card-columns"> - {% for fellowship in object_list %} + {% for fellowship in college.fellowships.active.regular %} <div class="card contributor mb-1"> {% include 'scipost/_contributor_short.html' with fellowship=fellowship %} </div> diff --git a/colleges/templates/colleges/colleges.html b/colleges/templates/colleges/college_list.html similarity index 74% rename from colleges/templates/colleges/colleges.html rename to colleges/templates/colleges/college_list.html index dd0b93c433b11e13b521bceaab7a2a6f19f9e321..2424e8f3077bd304fe0c9a58475691aba00ea5d8 100644 --- a/colleges/templates/colleges/colleges.html +++ b/colleges/templates/colleges/college_list.html @@ -56,26 +56,26 @@ </tr> </thead> <tbody> - {% for branch in scipost_disciplines %} - {% if branch.0 != 'Multidisciplinary' %} - {% with object_list|fellowships_in_branch:branch.0 as fellowships_branch %} - <tr> - <td class="align-middle"> - {{ branch.0 }} - </td> - <td> - {% for discipline in branch.1 %} - {% with fellowships_branch|fellowships_in_discipline:discipline.0 as fellowships_disc %} - {% if fellowships_disc|length > 0 %} - <a href={% url 'colleges:college_detail' discipline=discipline.0 %}><button type="button" class="btn btn-primary btn-sm"><small>{{ discipline.1 }}</small></button></a> + {% for branch in branches %} + {% if branch.name != 'Multidisciplinary' %} + <tr> + <td class="align-middle"> + {{ branch.name }} + </td> + <td> + {% for acad_field in branch.academic_fields.all %} + {% if acad_field.colleges.all|length > 0 %} + {% for college in acad_field.colleges.all %} + {% if college.fellowships.all|length > 0 %} + <a href={% url 'colleges:college_detail' slug=college.slug %}><button type="button" class="btn btn-primary btn-sm m-1"><small>{{ college.name }}</small></button></a> {% else %} - <button type="button" class="btn btn-sm btn-outline-secondary m-1"><small><em>{{ discipline.1 }}</em></small></button> + <button type="button" class="btn btn-sm btn-outline-secondary m-1"><small><em>{{ acad_field }}</em></small></button> {% endif %} - {% endwith %} - {% endfor %} - </td> - </tr> - {% endwith %} + {% endfor %} + {% endif %} + {% endfor %} + </td> + </tr> {% endif %} {% endfor %} </tbody> diff --git a/colleges/urls.py b/colleges/urls.py index 0241278f714a41a59be61fd23e788a527f78cdcb..9f78f2dfeccdf9111aa0d7eb02ddc89f9e76002f 100644 --- a/colleges/urls.py +++ b/colleges/urls.py @@ -3,23 +3,27 @@ __license__ = "AGPL v3" from django.conf.urls import url +from django.urls import path, register_converter from submissions.constants import SUBMISSIONS_COMPLETE_REGEX from . import views +from .converters import CollegeSlugConverter + +register_converter(CollegeSlugConverter, 'college_slug') app_name = 'colleges' urlpatterns = [ # Editorial Colleges: public view - url( - r'^$', - views.EditorialCollegesView.as_view(), + path( + '', + views.CollegeListView.as_view(), name='colleges' ), - url( - r'^detail/(?P<discipline>[a-zA-Z]+)/$', - views.EditorialCollegeDetailView.as_view(), + path( + '<college_slug:slug>', + views.CollegeDetailView.as_view(), name='college_detail' ), # Fellowships diff --git a/colleges/views.py b/colleges/views.py index a5b37f079a465500710c40a1007bb3d157b11650..5b89eb74bd24c0ba101483d8c1ec4f0631e61794 100644 --- a/colleges/views.py +++ b/colleges/views.py @@ -27,45 +27,29 @@ from .forms import FellowshipForm, FellowshipRemoveSubmissionForm,\ FellowshipRemoveProceedingsForm, FellowshipAddProceedingsForm, SubmissionAddVotingFellowForm,\ FellowVotingRemoveSubmissionForm,\ PotentialFellowshipForm, PotentialFellowshipStatusForm, PotentialFellowshipEventForm -from .models import Fellowship, PotentialFellowship, PotentialFellowshipEvent +from .models import College, Fellowship, PotentialFellowship, PotentialFellowshipEvent from scipost.constants import SCIPOST_SUBJECT_AREAS, subject_areas_raw_dict, specializations_dict from scipost.mixins import PermissionsMixin, PaginationMixin, RequestViewMixin from scipost.models import Contributor from mails.views import MailView +from ontology.models import Branch -class EditorialCollegesView(ListView): - model = Fellowship - template_name = 'colleges/colleges.html' - - def get_queryset(self): - queryset = Fellowship.objects.active().regular() - return queryset +class CollegeListView(ListView): + model = College def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) - context['fellowships'] = Fellowship.objects.active().regular() + context['branches'] = Branch.objects.all() return context -class EditorialCollegeDetailView(ListView): - model = Fellowship +class CollegeDetailView(DetailView): + model = College template_name = 'colleges/college_detail.html' - def get_queryset(self): - queryset = Fellowship.objects.active().regular().filter( - contributor__profile__discipline=self.kwargs.get('discipline')) - return queryset - - def get_context_data(self, *args, **kwargs): - context = super().get_context_data(*args, **kwargs) - discipline = self.kwargs.get('discipline') - context['discipline'] = discipline - context['specializations'] = specializations_dict[discipline] - return context - class FellowshipCreateView(PermissionsMixin, CreateView): """ diff --git a/journals/migrations/0096_journal_specialties.py b/journals/migrations/0096_journal_specialties.py new file mode 100644 index 0000000000000000000000000000000000000000..dcfb7000547c9633372515fad5e999e3f8d44e7f --- /dev/null +++ b/journals/migrations/0096_journal_specialties.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.16 on 2020-09-26 05:21 + +from django.db import migrations + + +def populate_journal_specialties(apps, schema_editor): + Journal = apps.get_model('journals.Journal') + + for journal in Journal.objects.all(): + journal.specialties.set(journal.college.acad_field.specialties.all()) + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0095_auto_20200924_2044'), + ] + + operations = [ + migrations.RunPython(populate_journal_specialties, + reverse_code=migrations.RunPython.noop), + ] diff --git a/ontology/models/specialty.py b/ontology/models/specialty.py index 1c554e64e0c57f8cfe66fb057e4006debb3812f5..a2b5eddfe3a691ceee38e02045f071b6c0f3e984 100644 --- a/ontology/models/specialty.py +++ b/ontology/models/specialty.py @@ -42,3 +42,10 @@ class Specialty(models.Model): def __str__(self): return self.name + + @property + def code(self): + """ + Capitalized letter code representing the specialty. + """ + return self.slug.partition('-')[2].upper() diff --git a/profiles/migrations/0031_auto_20200926_1147.py b/profiles/migrations/0031_auto_20200926_1147.py new file mode 100644 index 0000000000000000000000000000000000000000..c5e4adfec5ed30a6022bb4c1767d348be0739fa3 --- /dev/null +++ b/profiles/migrations/0031_auto_20200926_1147.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.16 on 2020-09-26 09:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('ontology', '0007_Branch_Field_Specialty'), + ('profiles', '0030_auto_20191017_0949'), + ] + + operations = [ + migrations.AddField( + model_name='profile', + name='acad_field', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='profiles', to='ontology.AcademicField'), + ), + migrations.AddField( + model_name='profile', + name='specialties', + field=models.ManyToManyField(blank=True, related_name='profiles', to='ontology.Specialty'), + ), + ] diff --git a/profiles/migrations/0032_populate_profile_acad_field_specialties.py b/profiles/migrations/0032_populate_profile_acad_field_specialties.py new file mode 100644 index 0000000000000000000000000000000000000000..545334a1dd0d9443c854ee1abd269a22247fb2ee --- /dev/null +++ b/profiles/migrations/0032_populate_profile_acad_field_specialties.py @@ -0,0 +1,37 @@ +# Generated by Django 2.2.16 on 2020-09-26 09:55 + +from django.db import migrations +from django.utils.text import slugify + + +def populate_acad_field_specialty(apps, schema_editor): + Profile = apps.get_model('profiles.Profile') + AcademicField = apps.get_model('ontology', 'AcademicField') + Specialty = apps.get_model('ontology', 'Specialty') + + for p in Profile.objects.all(): + p.acad_field = AcademicField.objects.get(slug=p.discipline) + # Fish out specialties from profile.expertises: + if p.expertises: + for e in p.expertises: + p.specialties.add(Specialty.objects.get(slug=slugify(e.replace(':', '-')))) + # Fish out specialties from profile.contributor.expertises, if contributor exists: + try: + if p.contributor.expertises: + for e in p.contributor.expertises: + p.specialties.add(Specialty.objects.get(slug=slugify(e.replace(':', '-')))) + except (TypeError, Profile.contributor.RelatedObjectDoesNotExist): + pass + p.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0031_auto_20200926_1147'), + ] + + operations = [ + migrations.RunPython(populate_acad_field_specialty, + reverse_code=migrations.RunPython.noop), + ] diff --git a/profiles/models.py b/profiles/models.py index 5c4c9ce9c7eda5e3a26a3763387b8f46c5cfbc90..683807d5da5f53c57d17363dcb4a5a01773ebd22 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -60,8 +60,22 @@ class Profile(models.Model): blank=True, validators=[orcid_validator]) webpage = models.URLField(max_length=300, blank=True) - # Topics for semantic linking - topics = models.ManyToManyField('ontology.Topic', blank=True) + # Ontology-based semantic linking + acad_field = models.ForeignKey( + 'ontology.AcademicField', + blank=True, null=True, + on_delete=models.PROTECT, + related_name='profiles' + ) + specialties = models.ManyToManyField( + 'ontology.Specialty', + blank=True, + related_name='profiles' + ) + topics = models.ManyToManyField( + 'ontology.Topic', + blank=True + ) # Preferences for interactions with SciPost: accepts_SciPost_emails = models.BooleanField(default=True) diff --git a/scipost/static/scipost/assets/css/_colleges.scss b/scipost/static/scipost/assets/css/_colleges.scss index 21f125d4a92d1225f0706750eef54b19e598ed1e..d8c5812e1cb5b8ad6bede49e6fcf627e31e45727 100644 --- a/scipost/static/scipost/assets/css/_colleges.scss +++ b/scipost/static/scipost/assets/css/_colleges.scss @@ -19,10 +19,11 @@ } } -.specialization { +.specialty { &.active-search, &:hover { cursor: pointer; color: $scipost-lightblue; + font-weight: bold; } } diff --git a/scipost/templates/scipost/_contributor_short.html b/scipost/templates/scipost/_contributor_short.html index c24d93ed92b8dbfc6ffeff8c17827aa454fdef19..8bc652fc75be181da4bc12d97b10157f1a8c6108 100644 --- a/scipost/templates/scipost/_contributor_short.html +++ b/scipost/templates/scipost/_contributor_short.html @@ -16,7 +16,7 @@ <span class="text-muted">({{ fellowship.contributor.affiliations.active.first.institution.name }})</span> {% endif %} <div> - {% for expertise in fellowship.contributor.profile.expertises %} - <div class="single d-inline" data-specialization="{{ expertise|lower }}" data-toggle="tooltip" data-placement="bottom" title="{{ expertise|get_specialization_display }}">{{ expertise|get_specialization_code }}</div> + {% for specialty in fellowship.contributor.profile.specialties.all %} + <div class="single d-inline" data-specialty="{{ specialty.slug }}" data-toggle="tooltip" data-placement="bottom" title="{{ specialty }}">{{ specialty.code }}</div> {% endfor %} </div> diff --git a/templates/email/potentialfellowships/invite_potential_fellow_initial.html b/templates/email/potentialfellowships/invite_potential_fellow_initial.html index dcd6dd49e16db3b0bb5ea0b181ea9c3713c3dc51..19a39b24193ad9455d59d314a770b31d44bdc20e 100644 --- a/templates/email/potentialfellowships/invite_potential_fellow_initial.html +++ b/templates/email/potentialfellowships/invite_potential_fellow_initial.html @@ -2,9 +2,9 @@ <p>Hopefully you're aware of <a href="https://scipost.org{% url 'scipost:index' %}">SciPost</a> and of its mission to establish a healthier community-run, open and not-for-profit infrastructure for scientific publishing (see our <a href="https://scipost.org{% url 'scipost:about' %}">about page</a> for a quick introduction or reminder).</p> -<p>Having successfully initiated our activities in the field of Physics, we would now like to bring the benefits of our approach to scientists in other fields of science, in particular {{ object.profile.get_discipline_display }}. We are therefore launching <a href="https://scipost.org{% url 'journals:journals' %}?field={{ object.profile.discipline }}">new journals in {{ object.profile.get_discipline_display }}</a>, aiming to achieve the same success we have had with our <a href="https://scipost.org{% url 'journals:journals' %}?field=physics">Physics journals</a>. +<p>Having successfully initiated our activities in the field of Physics, we would now like to bring the benefits of our approach to scientists in other fields of science, in particular {{ object.profile.acad_field }}. We are therefore launching <a href="https://scipost.org{% url 'journals:journals' %}?field={{ object.profile.acad_field }}">new journals in {{ object.profile.acad_field }}</a>, aiming to achieve the same success we have had with our <a href="https://scipost.org{% url 'journals:journals' %}?field=physics">Physics journals</a>. -<p>On behalf of the SciPost Foundation and in view of your professional expertise and reputation, I hereby would like to invite you to join the Editorial College ({{ object.profile.get_discipline_display }}) by becoming one of our Editorial Fellows.</p> +<p>On behalf of the SciPost Foundation and in view of your professional expertise and reputation, I hereby would like to invite you to join the {{ object.college }} by becoming one of our Editorial Fellows.</p> <p>Academic reputation is the most important criterion guiding our considerations of who should belong to our Editorial Colleges. The current list of Colleges and their Fellows can be found at <a href="https://scipost.org{% url 'colleges:colleges' %}">this page</a>. Our ambition is none other than to assemble the most reputed editorial team of any publishing system available worldwide.</p>