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] Extend Conflict of Interest Management for Articles and Users #4563

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
70 changes: 69 additions & 1 deletion src/core/forms/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
__license__ = "AGPL v3"
__maintainer__ = "Birkbeck Centre for Technology and Publishing"

import re
import uuid
import json

Expand Down Expand Up @@ -186,8 +187,29 @@ def save(self, commit=True):
user.confirmation_code = uuid.uuid4()
user.email_sent = timezone.now()

domain_regex = r'^[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z]{2,}$'
posted_domains = self.cleaned_data['competing_interest_domains'].split(',')
posted_domains = [
domain.lower() for domain in posted_domains
if re.match(domain_regex, domain.strip())
]

if commit:
user.save()

enable_competing_interest_selections = self.journal.get_setting(
'general',
'enable_competing_interest_selections',
)
if enable_competing_interest_selections:
for domain in posted_domains:
new_domain, c = models.EmailDomainCI.objects.get_or_create(name=domain)
user.competing_interest_domains.add(new_domain)

for domain in user.competing_interest_domains.all():
if domain.name not in posted_domains:
user.competing_interest_domains.remove(domain)

if self.cleaned_data.get('register_as_reader') and self.journal:
user.add_account_role(
role_slug="reader",
Expand All @@ -207,12 +229,40 @@ class Meta:
exclude = ('email', 'username', 'activation_code', 'email_sent',
'date_confirmed', 'confirmation_code', 'is_active',
'is_staff', 'is_admin', 'date_joined', 'password',
'is_superuser', 'enable_digest')
'is_superuser', 'enable_digest', 'competing_interest_domains')
widgets = {
'biography': TinyMCE(),
'signature': TinyMCE(),
}

def __init__(self, *args, **kwargs):
self.journal = kwargs.pop('journal', None)
super(EditAccountForm, self).__init__(*args, **kwargs)
if self.journal:

enable_competing_interest_selections = self.journal.get_setting(
'general',
'enable_competing_interest_selections',
)
if enable_competing_interest_selections:
self.fields['competing_interest_domains'] = forms.CharField(
label='Institutional Domains with Conflict of Interest',
required=False,
help_text=_('Hit Enter to add a new domain of an institution with which you have a conflict of interest, e.g., example.com.'),
widget=forms.TextInput(attrs={
'id': 'id_domains',
'hidden': True,
'placeholder': _('example.com'),
})
)

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

if enable_competing_interest_selections:
initial_domains = ','.join(domain.name for domain in account.competing_interest_domains.all())
self.fields['competing_interest_domains'].initial = initial_domains

def save(self, commit=True):
user = super(EditAccountForm, self).save(commit=False)
user.clean()
Expand All @@ -226,6 +276,24 @@ def save(self, commit=True):
if interest.name not in posted_interests:
user.interest.remove(interest)

enable_competing_interest_selections = self.journal.get_setting(
'general',
'enable_competing_interest_selections',
)
if enable_competing_interest_selections:
domain_regex = r'^[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z]{2,}$'
posted_domains = self.cleaned_data['competing_interest_domains'].split(',')
posted_domains = [
domain.lower() for domain in posted_domains
if re.match(domain_regex, domain.strip())
]
for domain in posted_domains:
new_domain, c = models.EmailDomainCI.objects.get_or_create(name=domain)
user.competing_interest_domains.add(new_domain)

for domain in user.competing_interest_domains.all():
if domain.name not in posted_domains:
user.competing_interest_domains.remove(domain)
user.save()

if commit:
Expand Down
8 changes: 8 additions & 0 deletions src/core/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,14 @@ 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_competing_interest_selections',
'object': setting_handler.get_setting('general', 'enable_competing_interest_selections', journal),
},
{
'name': 'enable_custom_editor_assignment',
'object': setting_handler.get_setting('general', 'enable_custom_editor_assignment', journal),
},
]
setting_group = 'general'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.2.16 on 2024-12-18 06:29

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('core', '0099_alter_accountrole_options'),
]

operations = [
migrations.CreateModel(
name='EmailDomainCI',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=250)),
],
),
migrations.AddField(
model_name='account',
name='competing_interest_domains',
field=models.ManyToManyField(blank=True, null=True, to='core.emaildomainci'),
),
]
11 changes: 11 additions & 0 deletions src/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ class Account(AbstractBaseUser, PermissionsMixin):
blank=True,
verbose_name=_("Signature"),
)
competing_interest_domains = models.ManyToManyField('EmailDomainCI', null=True, blank=True)
interest = models.ManyToManyField('Interest', null=True, blank=True)
country = models.ForeignKey(
Country,
Expand Down Expand Up @@ -1901,5 +1902,15 @@ def log_hijack_ended(sender, hijacker, hijacked, request, **kwargs):
)


class EmailDomainCI(models.Model):
name = models.CharField(max_length=250)

def __str__(self):
return u'%s' % self.name

def __repr__(self):
return u'%s' % self.name


hijack_started.connect(log_hijack_started)
hijack_ended.connect(log_hijack_ended)
15 changes: 8 additions & 7 deletions src/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ def edit_profile(request):
:return: HttpResponse object
"""
user = request.user
form = forms.EditAccountForm(instance=user)
form = forms.EditAccountForm(instance=user, journal=request.journal)
send_reader_notifications = False
if request.journal:
send_reader_notifications = setting_handler.get_setting(
Expand Down Expand Up @@ -519,7 +519,7 @@ def edit_profile(request):
)

elif 'edit_profile' in request.POST:
form = forms.EditAccountForm(request.POST, request.FILES, instance=user)
form = forms.EditAccountForm(request.POST, request.FILES, instance=user, journal=request.journal)

if form.is_valid():
form.save()
Expand Down Expand Up @@ -1208,7 +1208,7 @@ def add_user(request):
:param request: HttpRequest object
:return: HttpResponse object
"""
form = forms.EditAccountForm()
form = forms.EditAccountForm(journal=request.journal)
registration_form = forms.AdminUserForm(active='add', request=request)
return_url = request.GET.get('return', None)
role = request.GET.get('role', None)
Expand All @@ -1232,7 +1232,8 @@ def add_user(request):
form = forms.EditAccountForm(
request.POST,
request.FILES,
instance=new_user
instance=new_user,
journal=request.journal,
)

if form.is_valid():
Expand All @@ -1251,7 +1252,7 @@ def add_user(request):
else:
# If the registration form is not valid,
# we need to add post data to the Edit form for display.
form = forms.EditAccountForm(request.POST)
form = forms.EditAccountForm(request.POST, journal=request.journal)

template = 'core/manager/users/edit.html'
context = {
Expand All @@ -1271,11 +1272,11 @@ def user_edit(request, user_id):
:return: HttpResponse object
"""
user = models.Account.objects.get(pk=user_id)
form = forms.EditAccountForm(instance=user)
form = forms.EditAccountForm(instance=user, journal=request.journal)
registration_form = forms.AdminUserForm(instance=user, request=request)

if request.POST:
form = forms.EditAccountForm(request.POST, request.FILES, instance=user)
form = forms.EditAccountForm(request.POST, request.FILES, instance=user, journal=request.journal)
registration_form = forms.AdminUserForm(request.POST, instance=user, request=request)

if form.is_valid() and registration_form.is_valid():
Expand Down
14 changes: 13 additions & 1 deletion src/journal/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
Subquery,
Value,
TextField,
Q,
F,
ExpressionWrapper,
DateTimeField
DateTimeField,
Count,
)
from django.db.models.functions import Concat, Coalesce
from django.db.models.signals import post_save, m2m_changed
Expand Down Expand Up @@ -482,6 +484,16 @@ def journal_users(self, objects=True):
def editorial_groups(self):
return core_models.EditorialGroup.objects.filter(journal=self)

def editorial_members(self):
return core_models.Account.objects.filter(
accountrole__journal=self,
is_active=True,
).annotate(
role_count=Count('accountrole')
).filter(
Q(role_count__gt=1) | ~Q(accountrole__role__slug='author')
).distinct()

@property
def editor_emails(self):
editor_roles = core_models.AccountRole.objects.filter(role__slug='editor', journal=self)
Expand Down
38 changes: 38 additions & 0 deletions src/review/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,44 @@ def check_for_potential_errors(self):
return potential_errors


class EditorAssignmentForm(core_forms.ConfirmableIfErrorsForm):
editor = forms.ModelChoiceField(queryset=None)
date_due = forms.DateField(required=False)

def __init__(self, *args, **kwargs):
self.journal = kwargs.pop('journal', None)
self.article = kwargs.pop('article')
self.editors = kwargs.pop('editors')

super(EditorAssignmentForm, self).__init__(*args, **kwargs)

if self.editors:
self.fields['editor'].queryset = self.editors

def clean(self):
cleaned_data = super().clean()
return cleaned_data

def save(self, commit=True, request=None):
editor = self.cleaned_data['editor']

if request:
editor_assignment = models.EditorAssignment(
article=self.article,
editor=editor,
)

if editor_assignment.editor.is_editor(request):
editor_assignment.editor_type = 'editor'
elif editor_assignment.editor.is_section_editor(request):
editor_assignment.editor_type = 'section-editor'

if commit:
editor_assignment.save()

return editor_assignment


class BulkReviewAssignmentForm(forms.ModelForm):
template = forms.CharField(
widget=TinyMCE,
Expand Down
Loading