Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TGDK][Feature] Add Research Topics System to Improve Editor and Reviewer Assignments #4560

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ django-settings-export==1.2.1
django-recaptcha==3.0.0
django-simple-math-captcha==2.0.1
django-simple-history==3.3.0
django-select2==8.1.2
django-summernote==0.8.20.0
django-tinymce==3.7.1
djangorestframework==3.15.2
Expand Down
2 changes: 2 additions & 0 deletions src/core/forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
SectionForm,
SettingEmailForm,
SimpleTinyMCEForm,
TopicForm,
TopicGroupForm,
UserCreationFormExtended,
XSLFileForm,
)
129 changes: 129 additions & 0 deletions src/core/forms/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
__license__ = "AGPL v3"
__maintainer__ = "Birkbeck Centre for Technology and Publishing"

import re
import uuid
import json

from django import forms
from django.db import transaction
from django_select2.forms import Select2MultipleWidget
from django.db.models import Q
from django.forms.fields import Field
from django.utils import timezone
Expand Down Expand Up @@ -202,6 +205,20 @@ class EditAccountForm(forms.ModelForm):

interests = forms.CharField(required=False)

primary_study_topic = forms.ModelMultipleChoiceField(
queryset=models.Topics.objects.none(),
widget=Select2MultipleWidget,
required=False,
label=_('Primary Research Topics')
)

secondary_study_topic = forms.ModelMultipleChoiceField(
queryset=models.Topics.objects.none(),
widget=Select2MultipleWidget,
required=False,
label=_('Secondary Research Topics')
)

class Meta:
model = models.Account
exclude = ('email', 'username', 'activation_code', 'email_sent',
Expand All @@ -211,8 +228,39 @@ class Meta:
widgets = {
'biography': TinyMCE(),
'signature': TinyMCE(),
'study_topic': Select2MultipleWidget,
}

def __init__(self, *args, **kwargs):
self.journal = kwargs.pop('journal', None)
super(EditAccountForm, self).__init__(*args, **kwargs)
if self.journal:
topics_queryset = models.Topics.objects.filter(
journal=self.journal,
).order_by('group__pretty_name', 'pretty_name')

self.fields['primary_study_topic'].queryset = topics_queryset
self.fields['secondary_study_topic'].queryset = topics_queryset

if 'instance' in kwargs:
account = kwargs['instance']

self.fields['primary_study_topic'].initial = account.topics('PR')
self.fields['secondary_study_topic'].initial = account.topics('SE')

study_topic_choices = [
(
group.pretty_name,
[
(topic.id, topic.pretty_name)
for topic in models.Topics.objects.filter(group=group).order_by('pretty_name')
]
)
for group in models.TopicGroup.objects.all()
]
self.fields['primary_study_topic'].choices = study_topic_choices
self.fields['secondary_study_topic'].choices = study_topic_choices

def save(self, commit=True):
user = super(EditAccountForm, self).save(commit=False)
user.clean()
Expand All @@ -231,6 +279,35 @@ def save(self, commit=True):
if commit:
user.save()

selected_primary_topic = set(self.cleaned_data['primary_study_topic'])
selected_secondary_topics = set(self.cleaned_data['secondary_study_topic'])

existing_topics = models.AccountTopic.objects.filter(account=user)

with transaction.atomic():

for topic in selected_secondary_topics:
models.AccountTopic.objects.update_or_create(
account=user,
topic=topic,
defaults={'topic_type': models.AccountTopic.SECONDARY}
)

for topic in selected_primary_topic:
models.AccountTopic.objects.update_or_create(
account=user,
topic=topic,
defaults={'topic_type': models.AccountTopic.PRIMARY}
)

for account_topic in existing_topics.filter(topic_type=models.AccountTopic.PRIMARY):
if account_topic.topic not in selected_primary_topic:
account_topic.delete()

for account_topic in existing_topics.filter(topic_type=models.AccountTopic.SECONDARY):
if account_topic.topic not in selected_secondary_topics:
account_topic.delete()

return user


Expand Down Expand Up @@ -496,6 +573,58 @@ def __init__(self, *args, **kwargs):
self.fields['editors'].required = False


class TopicForm(forms.ModelForm):
class Meta:
model = models.Topics
fields = ['pretty_name', 'description', 'group']
labels = {
'pretty_name': 'Name',
'description': 'Description',
'group': 'Topic Group',
}

def __init__(self, *args, **kwargs):
request = kwargs.pop('request', None)
super(TopicForm, self).__init__(*args, **kwargs)
if request:
self.fields['group'].queryset = request.journal.topic_groups()
self.fields['group'].label_from_instance = lambda obj: "%s" % obj.pretty_name


class TopicGroupForm(forms.ModelForm):
class Meta:
model = models.TopicGroup
fields = ['pretty_name', 'description']
labels = {
'pretty_name': 'Name',
'description': 'Description',
}

def __init__(self, *args, **kwargs):
super(TopicGroupForm, self).__init__(*args, **kwargs)

def formatted_name(self, pretty_name: str):
return re.sub(r'\s+', '_', re.sub(r'[()]', '', pretty_name)).lower()

def save(self, commit=True, request=None):
topic_group = super(TopicGroupForm, self).save(commit=False)
if request:
topic_group_pretty_name = topic_group.pretty_name
topic_group.journal = request.journal
topic_group.name = self.formatted_name(topic_group_pretty_name)
if commit:
topic_group.save()
default_topic_name = f'{topic_group_pretty_name} (others)'
models.Topics.objects.create(
pretty_name=default_topic_name,
name=self.formatted_name(default_topic_name),
journal=request.journal,
group=topic_group,
description='another topics'
)
return topic_group


class QuickUserForm(forms.ModelForm):
class Meta:
model = models.Account
Expand Down
17 changes: 17 additions & 0 deletions src/core/include_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
path('workflow/', include('workflow.urls')),
path('discussion/', include('discussion.urls')),
path('oidc/', include('mozilla_django_oidc.urls')),
path('select2/', include('django_select2.urls')),

# Root Site URLS
re_path(r'^$', press_views.index, name='website_index'),
Expand Down Expand Up @@ -177,6 +178,22 @@
re_path(r'^manager/sections/(?P<section_id>\d+)/articles/$',
core_views.section_articles, name='core_manager_section_articles'),

# Journal Topics
re_path(r'^manager/topics/$',
core_views.topic_list, name='core_manager_topics'),
re_path(r'^manager/topics/add/$',
core_views.manage_topic, name='core_manager_topic_add'),
re_path(r'^manager/topics/group/add/$',
core_views.manage_topic_group, name='core_manager_topic_group_add'),
re_path(r'^manager/topics/(?P<topic_id>\d+)/$',
core_views.manage_topic, name='core_manager_topic'),
re_path(r'^manager/topics/group/(?P<topic_group_id>\d+)/$',
core_views.manage_topic_group, name='core_manager_topic_group'),
re_path(r'^manager/topics/(?P<topic_id>\d+)/accounts/$',
core_views.topic_accounts, name='core_manager_topic_accounts'),
re_path(r'^manager/topics/(?P<topic_id>\d+)/articles/$',
core_views.topic_articles, name='core_manager_topic_articles'),

# Pinned Articles
re_path(r'^manager/articles/pinned/$',
core_views.pinned_articles, name='core_pinned_articles'),
Expand Down
1 change: 1 addition & 0 deletions src/core/janeway_global_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@

# 3rd Party
'mozilla_django_oidc',
'django_select2',
'django_summernote',
'tinymce',
'bootstrap4',
Expand Down
8 changes: 8 additions & 0 deletions src/core/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,10 @@ def get_settings_to_edit(display_group, journal, user):
'name': 'draft_decisions',
'object': setting_handler.get_setting('general', 'draft_decisions', journal),
},
{
'name': 'enable_study_topics',
'object': setting_handler.get_setting('general', 'enable_study_topics', journal),
},
{
'name': 'default_review_form',
'object': setting_handler.get_setting('general', 'default_review_form', journal),
Expand Down Expand Up @@ -424,6 +428,10 @@ def get_settings_to_edit(display_group, journal, user):
'name': 'display_completed_reviews_in_additional_rounds_text',
'object': setting_handler.get_setting('general', 'display_completed_reviews_in_additional_rounds_text', journal),
},
{
'name': 'enable_custom_editor_assignment',
'object': setting_handler.get_setting('general', 'enable_custom_editor_assignment', journal),
},
]
setting_group = 'general'

Expand Down
57 changes: 57 additions & 0 deletions src/core/migrations/0100_topicgroup_topics_accounttopic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Generated by Django 4.2.16 on 2024-12-10 10:11

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('journal', '0066_issue_type_bleach_20240507_1359'),
('core', '0099_alter_accountrole_options'),
]

operations = [
migrations.CreateModel(
name='TopicGroup',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('pretty_name', models.CharField(max_length=100)),
('description', models.TextField(blank=True, null=True)),
('journal', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='journal.journal')),
],
options={
'verbose_name_plural': 'study topic groups for articles and accounts',
'unique_together': {('name', 'journal')},
},
),
migrations.CreateModel(
name='Topics',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('pretty_name', models.CharField(max_length=100)),
('description', models.TextField(blank=True, null=True)),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.topicgroup')),
('journal', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='journal.journal')),
],
options={
'verbose_name_plural': 'study topics for articles and accounts',
'unique_together': {('name', 'journal', 'group')},
},
),
migrations.CreateModel(
name='AccountTopic',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('topic_type', models.CharField(choices=[('PR', 'Primary'), ('SE', 'Secondary')], default='PR', max_length=2)),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('topic', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.topics')),
],
options={
'unique_together': {('account', 'topic')},
},
),
]
18 changes: 18 additions & 0 deletions src/core/migrations/0101_account_study_topic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.16 on 2024-12-15 10:41

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('core', '0100_topicgroup_topics_accounttopic'),
]

operations = [
migrations.AddField(
model_name='account',
name='study_topic',
field=models.ManyToManyField(blank=True, null=True, through='core.AccountTopic', to='core.topics'),
),
]
Loading