Skip to content

Commit a7107c2

Browse files
committed
wip #4138
1 parent 4e18f36 commit a7107c2

File tree

29 files changed

+596
-118
lines changed

29 files changed

+596
-118
lines changed

docs/md/sequence-fields.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Sequence fields
2+
3+
A few patterns have become common in Janeway for keeping track of
4+
sequence or order with a `PositiveIntegerField`.
5+
6+
Because we sometimes expose this number to end users in form inputs,
7+
it is worth thinking about the usability of the default value.
8+
The number 1 is low enough to be easy for end users to manipulate.
9+
Zero is sometimes most convenient from a programming perspective,
10+
but avoid it if possible, since it can be counter-intuitive for
11+
non-programmers.
12+
13+
```py
14+
sequence = models.PositiveIntegerField(default=1)
15+
```
16+
17+
Of course, it is best if end users do not have to deal with
18+
this number at all. User interfaces should use accessible buttons that
19+
move things up or down in the sequence. This allows
20+
us to write an algorithm to check that multiple things have not been
21+
given the same sequence, and it keeps the user from having to
22+
recall off-screen information about the order of other items
23+
whilst performing an action.

src/core/admin.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -358,14 +358,28 @@ def _journal(self, obj):
358358
return obj.group.journal if obj else ''
359359

360360

361-
class ContactsAdmin(admin.ModelAdmin):
362-
list_display = ('name', 'email', 'role', 'object', 'sequence')
361+
class ContactPersonAdmin(admin.ModelAdmin):
362+
list_display = ('_name', '_email', 'role', 'object', 'sequence')
363363
list_filter = (admin_utils.GenericRelationJournalFilter,
364364
admin_utils.GenericRelationPressFilter)
365-
search_fields = ('name', 'email', 'role')
365+
search_fields = (
366+
'account__first_name',
367+
'account__middle_name',
368+
'account__last_name',
369+
'account__email',
370+
'role',
371+
)
372+
raw_id_fields = ('account', )
373+
374+
def _name(self, obj):
375+
return obj.account.full_name() if obj and obj.account else ''
376+
377+
def _email(self, obj):
378+
return obj.account.email if obj and obj.account else ''
379+
366380

367381

368-
class ContactAdmin(admin.ModelAdmin):
382+
class ContactMessageAdmin(admin.ModelAdmin):
369383
list_display = ('subject', 'sender', 'recipient',
370384
'client_ip', 'date_sent', 'object')
371385
list_filter = (admin_utils.GenericRelationJournalFilter,
@@ -515,8 +529,8 @@ def _person(self, obj):
515529
(models.Workflow, WorkflowAdmin),
516530
(models.WorkflowLog, WorkflowLogAdmin),
517531
(models.LoginAttempt, LoginAttemptAdmin),
518-
(models.Contacts, ContactsAdmin),
519-
(models.Contact, ContactAdmin),
532+
(models.ContactPerson, ContactPersonAdmin),
533+
(models.ContactMessage, ContactMessageAdmin),
520534
(models.AccessRequest, AccessRequestAdmin),
521535
(models.Organization, OrganizationAdmin),
522536
(models.OrganizationName, OrganizationNameAdmin),

src/core/forms/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
GetResetTokenForm,
2121
JournalArticleForm,
2222
JournalAttributeForm,
23-
JournalContactForm,
23+
ContactPersonForm,
2424
JournalImageForm,
2525
JournalStylingForm,
2626
JournalSubmissionForm,

src/core/forms/forms.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,19 +84,25 @@ def clean(self):
8484
return cleaned_data
8585

8686

87-
class JournalContactForm(JanewayTranslationModelForm):
87+
class ContactPersonForm(JanewayTranslationModelForm):
8888
def __init__(self, *args, **kwargs):
8989
next_sequence = kwargs.pop('next_sequence', None)
90-
super(JournalContactForm, self).__init__(*args, **kwargs)
90+
super().__init__(*args, **kwargs)
9191
if next_sequence:
9292
self.fields['sequence'].initial = next_sequence
9393

9494
class Meta:
95-
model = models.Contacts
96-
fields = ('name', 'email', 'role', 'sequence',)
95+
model = models.ContactPerson
96+
fields = ('role', 'sequence',)
9797
exclude = ('content_type', 'object_id',)
9898

9999

100+
class JournalContactForm(ContactPersonForm):
101+
def __init__(self, *args, **kwargs):
102+
return DeprecationWarning("Use ContactPersonForm instead.")
103+
super().__init__(*args, **kwargs)
104+
105+
100106
class EditorialGroupForm(JanewayTranslationModelForm):
101107

102108
def __init__(self, *args, **kwargs):

src/core/include_urls.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,32 @@
174174
re_path(r'^manager/article/images/edit/(?P<article_pk>\d+)/$', core_views.article_image_edit,
175175
name='core_article_image_edit'),
176176

177-
# Journal Contacts
178-
re_path(r'^manager/contacts/$', core_views.contacts, name='core_journal_contacts'),
179-
re_path(r'^manager/contacts/add/$', core_views.edit_contacts, name='core_new_journal_contact'),
180-
re_path(r'^manager/contacts/(?P<contact_id>\d+)/$', core_views.edit_contacts, name='core_journal_contact'),
181-
re_path(r'^manager/contacts/order/$', core_views.contacts_order, name='core_journal_contacts_order'),
177+
# Contact People
178+
re_path(
179+
r'^manager/contacts/$',
180+
core_views.contact_people,
181+
name='core_contact_people',
182+
),
183+
re_path(
184+
r'^manager/contacts/order/$',
185+
core_views.contact_people_reorder,
186+
name='core_contact_people_reorder',
187+
),
188+
re_path(
189+
r'^manager/contacts/search/$',
190+
core_views.PotentialContactListView.as_view(),
191+
name='core_contact_person_search',
192+
),
193+
re_path(
194+
r'^manager/contacts/add/(?P<account_id>\d+)/$',
195+
core_views.contact_person_create,
196+
name='core_contact_person_create',
197+
),
198+
re_path(
199+
r'^manager/contacts/(?P<contact_person_id>\d+)/$',
200+
core_views.contact_person_update,
201+
name='core_contact_person_update',
202+
),
182203

183204
# Editorial Team
184205
re_path(r'^manager/editorial/$', core_views.editorial_team, name='core_editorial_team'),

src/core/middleware.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,9 @@ def process_request(request):
194194
'core_confirm_account', 'core_orcid_registration', 'core_get_reset_token', 'core_reset_password',
195195
'core_edit_profile', 'core_logout', 'press_cover_download', 'core_manager_index',
196196
'django_summernote-editor', 'django_summernote-upload_attachment', 'cms_index', 'cms_page_new',
197-
'cms_page_edit', 'cms_page', 'cms_nav', 'website_index', 'core_journal_contacts',
198-
'core_journal_contact', 'core_journal_contacts_order', 'contact', 'core_edit_settings_group',
197+
'cms_page_edit', 'cms_page', 'cms_nav', 'website_index', 'core_contact_people',
198+
'core_contact_person_search', 'core_contact_person_create', 'core_contact_person_update',
199+
'core_contact_people_reorder', 'contact', 'core_edit_settings_group',
199200
]
200201
allowed_pattern = 'press_'
201202

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Generated by Django 4.2.21 on 2025-07-07 16:18
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
from django.utils import translation
6+
import django.db.models.deletion
7+
8+
9+
def connect_contact_person_to_account(apps, schema_editor):
10+
ContactPerson = apps.get_model("core", "ContactPerson")
11+
Account = apps.get_model("core", "Account")
12+
languages = ['cy', 'de', 'en', 'en-us', 'en_us', 'es', 'fr', 'it', 'nl']
13+
for contact_person in ContactPerson.objects.all():
14+
account, created = Account.objects.get_or_create(
15+
username=contact_person.email.lower(),
16+
defaults={
17+
'first_name': ' '.join(contact_person.name.split()[:-1]),
18+
'last_name': contact_person.name.split()[-1],
19+
'email': contact_person.email,
20+
}
21+
)
22+
contact_person.account = account
23+
for language in languages:
24+
with translation.override(language):
25+
language_var = "name_{}".format(language)
26+
setattr(contact_person, language_var, '')
27+
contact_person.email = ''
28+
contact_person.save()
29+
30+
31+
class Migration(migrations.Migration):
32+
33+
dependencies = [
34+
('contenttypes', '0002_remove_content_type_name'),
35+
('core', '0107_alter_controlledaffiliation_options_and_more'),
36+
]
37+
38+
operations = [
39+
migrations.RenameModel(
40+
old_name='Contact',
41+
new_name='ContactMessage',
42+
),
43+
migrations.RenameModel(
44+
old_name='Contacts',
45+
new_name='ContactPerson',
46+
),
47+
migrations.AddField(
48+
model_name='contactperson',
49+
name='account',
50+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
51+
),
52+
migrations.AlterField(
53+
model_name='contactperson',
54+
name='email',
55+
field=models.EmailField(blank=True, help_text="The 'email' field is deprecated. Use 'account.email'.", max_length=254),
56+
),
57+
migrations.AlterField(
58+
model_name='contactperson',
59+
name='name',
60+
field=models.CharField(blank=True, help_text="The 'name' field is deprecated. Use 'account.full_name'.", max_length=300),
61+
),
62+
migrations.AlterField(
63+
model_name='contactperson',
64+
name='name_cy',
65+
field=models.CharField(blank=True, help_text="The 'name' field is deprecated. Use 'account.full_name'.", max_length=300, null=True),
66+
),
67+
migrations.AlterField(
68+
model_name='contactperson',
69+
name='name_de',
70+
field=models.CharField(blank=True, help_text="The 'name' field is deprecated. Use 'account.full_name'.", max_length=300, null=True),
71+
),
72+
migrations.AlterField(
73+
model_name='contactperson',
74+
name='name_en',
75+
field=models.CharField(blank=True, help_text="The 'name' field is deprecated. Use 'account.full_name'.", max_length=300, null=True),
76+
),
77+
migrations.AlterField(
78+
model_name='contactperson',
79+
name='name_en_us',
80+
field=models.CharField(blank=True, help_text="The 'name' field is deprecated. Use 'account.full_name'.", max_length=300, null=True),
81+
),
82+
migrations.AlterField(
83+
model_name='contactperson',
84+
name='name_es',
85+
field=models.CharField(blank=True, help_text="The 'name' field is deprecated. Use 'account.full_name'.", max_length=300, null=True),
86+
),
87+
migrations.AlterField(
88+
model_name='contactperson',
89+
name='name_fr',
90+
field=models.CharField(blank=True, help_text="The 'name' field is deprecated. Use 'account.full_name'.", max_length=300, null=True),
91+
),
92+
migrations.AlterField(
93+
model_name='contactperson',
94+
name='name_nl',
95+
field=models.CharField(blank=True, help_text="The 'name' field is deprecated. Use 'account.full_name'.", max_length=300, null=True),
96+
),
97+
migrations.AlterModelOptions(
98+
name='contactmessage',
99+
options={},
100+
),
101+
migrations.AlterModelOptions(
102+
name='contactperson',
103+
options={'ordering': ('sequence',)},
104+
),
105+
migrations.RunPython(
106+
connect_contact_person_to_account,
107+
reverse_code=migrations.RunPython.noop,
108+
)
109+
]

src/core/models.py

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1662,29 +1662,56 @@ def __str__(self):
16621662
return f'{self.user} in {self.group}'
16631663

16641664

1665-
class Contacts(models.Model):
1665+
class ContactPerson(models.Model):
16661666
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE,
16671667
related_name='contact_content_type', null=True)
16681668
object_id = models.PositiveIntegerField(blank=True, null=True)
16691669
object = GenericForeignKey('content_type', 'object_id')
16701670

1671-
name = models.CharField(max_length=300)
1672-
email = models.EmailField()
1671+
account = models.ForeignKey(
1672+
Account,
1673+
on_delete=models.CASCADE,
1674+
blank=True,
1675+
null=True,
1676+
)
16731677
role = models.CharField(max_length=200)
1674-
sequence = models.PositiveIntegerField(default=999)
1678+
sequence = models.PositiveIntegerField(default=1)
1679+
1680+
name = models.CharField(
1681+
max_length=300,
1682+
blank=True,
1683+
help_text="The 'name' field is deprecated. Use 'account.full_name'.",
1684+
)
1685+
email = models.EmailField(
1686+
blank=True,
1687+
help_text="The 'email' field is deprecated. Use 'account.email'.",
1688+
)
16751689

16761690
class Meta:
1677-
# This verbose name will hopefully more clearly
1678-
# distinguish this model from the below model `Contact`
1679-
# in the admin area.
1680-
verbose_name_plural = 'contacts'
1681-
ordering = ('sequence', 'name')
1691+
ordering = ('sequence',)
1692+
verbose_name_plural = 'contact people'
16821693

16831694
def __str__(self):
1684-
return "{0}, {1} - {2}".format(self.name, self.object, self.role)
1695+
name = self.account.full_name() if self.account else ''
1696+
return "{0}, {1} - {2}".format(name, self.object, self.role)
1697+
1698+
def __getattribute__(self, name):
1699+
if name == "name":
1700+
warnings.warn(
1701+
"The 'name' field is deprecated. Use 'account.full_name'.",
1702+
DeprecationWarning,
1703+
stacklevel=2,
1704+
)
1705+
elif name == "email":
1706+
warnings.warn(
1707+
"The 'email' field is deprecated. Use 'account.email'.",
1708+
DeprecationWarning,
1709+
stacklevel=2,
1710+
)
1711+
return super().__getattribute__(name)
16851712

16861713

1687-
class Contact(models.Model):
1714+
class ContactMessage(models.Model):
16881715
recipient = models.EmailField(max_length=200, verbose_name=_('Who would you like to contact?'))
16891716
sender = models.EmailField(max_length=200, verbose_name=_('Your contact email address'))
16901717
subject = models.CharField(max_length=300, verbose_name=_('Subject'))
@@ -1697,12 +1724,6 @@ class Contact(models.Model):
16971724
object_id = models.PositiveIntegerField(blank=True, null=True)
16981725
object = GenericForeignKey('content_type', 'object_id')
16991726

1700-
class Meta:
1701-
# This verbose name will hopefully more clearly
1702-
# distinguish this model from the above model `Contacts`
1703-
# in the admin area.
1704-
verbose_name_plural = 'contact messages'
1705-
17061727

17071728
class DomainAlias(AbstractSiteModel):
17081729
redirect = models.BooleanField(

src/core/translation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ class EditorialGroupTranslationOptions(TranslationOptions):
1313
fields = ('name', 'description',)
1414

1515

16-
@register(models.Contacts)
16+
@register(models.ContactPerson)
1717
class ContactTranslationOptions(TranslationOptions):
1818
fields = ('name', 'role',)

0 commit comments

Comments
 (0)