Skip to content

Commit e3ab66a

Browse files
[ENG-10774] Provide extra options to populate newly created templates and selective existing templates (#11698)
* Add options to sync notification templates: restore one or all * Fix formatting of template name input in management commands
1 parent d959d1c commit e3ab66a

3 files changed

Lines changed: 80 additions & 36 deletions

File tree

admin/management/views.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,17 @@ def post(self, request):
179179
class SyncNotificationTemplates(ManagementCommandPermissionView):
180180

181181
def post(self, request):
182-
populate_notification_types()
182+
run_type = request.POST.get('run_type')
183+
if run_type == 'restore_one':
184+
template_name = request.POST.get('template_name')
185+
if not template_name:
186+
messages.error(request, 'A template name must be specified when restoring one template. Check your inputs and try again')
187+
return redirect(reverse('management:commands'))
188+
populate_notification_types(restore_one=template_name)
189+
elif run_type == 'restore_all':
190+
populate_notification_types(restore_all=True)
191+
else:
192+
populate_notification_types()
183193
messages.success(request, 'Notification templates have been successfully synced.')
184194
return redirect(reverse('management:commands'))
185195

admin/templates/management/commands.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,16 @@ <h4><u>Sync Notification Templates</u></h4>
160160
<form method="post"
161161
action="{% url 'management:sync_notification_templates'%}">
162162
{% csrf_token %}
163+
164+
<label for="run_type">Select a command behavior:</label>
165+
<select id="run_type" name="run_type">
166+
<option value="">Default: Only upload new templates</option>
167+
<option value="restore_one">Restore specific template</option>
168+
<option value="restore_all">Restore all templates</option>
169+
</select><br>
170+
171+
<label>Template name (when restoring one):</label> <input type="text" name="template_name"/><br>
172+
163173
<nav>
164174
<input class="btn btn-success" type="submit" value="Run" />
165175
</nav>
Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import sys
22
import yaml
3-
from django.apps import apps
43
from waffle import switch_is_active
54
from osf import features
65

@@ -19,7 +18,7 @@
1918
'email_transactional': 'instantly',
2019
}
2120

22-
def populate_notification_types(*args, **kwargs):
21+
def populate_notification_types(*args, restore_one=None, restore_all=False, **kwargs):
2322
if kwargs.get('sender'): # exists when called as a post_migrate signal
2423
if not switch_is_active(features.POPULATE_NOTIFICATION_TYPES):
2524
if 'pytest' not in sys.modules:
@@ -28,64 +27,89 @@ def populate_notification_types(*args, **kwargs):
2827
logger.info('Populating notification types...')
2928
from django.contrib.contenttypes.models import ContentType
3029
from osf.models.notification_type import NotificationType
30+
3131
try:
3232
with open(settings.NOTIFICATION_TYPES_YAML) as stream:
3333
notification_types = yaml.safe_load(stream)
34-
for notification_type in notification_types['notification_types']:
35-
notification_type.pop('__docs__', None)
36-
notification_type.pop('tests', None)
37-
object_content_type_model_name = notification_type.pop('object_content_type_model_name')
34+
35+
notification_types_dict = {
36+
nt['name']: nt for nt in notification_types['notification_types']
37+
}
38+
39+
all_names = set(notification_types_dict.keys())
40+
existing_names = set(
41+
NotificationType.objects.values_list('name', flat=True)
42+
)
43+
44+
if restore_one:
45+
if restore_one not in notification_types_dict:
46+
raise ValueError(f'Notification type "{restore_one}" not found in YAML')
47+
names_to_process = {restore_one}
48+
49+
elif restore_all:
50+
names_to_process = all_names
51+
52+
else:
53+
names_to_process = all_names - existing_names
54+
55+
logger.info(f'Processing {len(names_to_process)} notification types')
56+
57+
for name in names_to_process:
58+
raw_nt = notification_types_dict[name].copy()
59+
60+
raw_nt.pop('__docs__', None)
61+
raw_nt.pop('tests', None)
62+
63+
object_content_type_model_name = raw_nt.pop('object_content_type_model_name')
3864

3965
if object_content_type_model_name == 'desk':
4066
content_type = None
41-
elif object_content_type_model_name == 'osfuser':
42-
OSFUser = apps.get_model('osf', 'OSFUser')
43-
content_type = ContentType.objects.get_for_model(OSFUser)
44-
elif object_content_type_model_name == 'preprint':
45-
Preprint = apps.get_model('osf', 'Preprint')
46-
content_type = ContentType.objects.get_for_model(Preprint)
47-
elif object_content_type_model_name == 'collectionsubmission':
48-
CollectionSubmission = apps.get_model('osf', 'CollectionSubmission')
49-
content_type = ContentType.objects.get_for_model(CollectionSubmission)
50-
elif object_content_type_model_name == 'abstractprovider':
51-
AbstractProvider = apps.get_model('osf', 'abstractprovider')
52-
content_type = ContentType.objects.get_for_model(AbstractProvider)
53-
elif object_content_type_model_name == 'osfuser':
54-
OSFUser = apps.get_model('osf', 'OSFUser')
55-
content_type = ContentType.objects.get_for_model(OSFUser)
56-
elif object_content_type_model_name == 'draftregistration':
57-
DraftRegistration = apps.get_model('osf', 'DraftRegistration')
58-
content_type = ContentType.objects.get_for_model(DraftRegistration)
5967
else:
6068
try:
61-
content_type = ContentType.objects.get(
62-
app_label='osf',
63-
model=object_content_type_model_name
64-
)
69+
content_type = ContentType.objects.get_by_natural_key(app_label='osf', model=object_content_type_model_name)
6570
except ContentType.DoesNotExist:
6671
raise ValueError(f'No content type for osf.{object_content_type_model_name}')
6772

68-
template_path = notification_type.pop('template')
73+
template_path = raw_nt.pop('template')
74+
template = None
75+
6976
if template_path:
7077
with open(template_path) as stream:
7178
template = stream.read()
7279

7380
nt, _ = NotificationType.objects.update_or_create(
74-
name=notification_type['name'],
75-
defaults=notification_type,
81+
name=name,
82+
defaults=raw_nt,
7683
)
84+
7785
nt.object_content_type = content_type
78-
if not nt.template or settings.DEV_MODE:
86+
if template:
7987
nt.template = template
88+
8089
nt.save()
90+
8191
except ProgrammingError:
8292
logger.info('Notification types failed potential side effect of reverse migration')
8393
logger.info('Finished populating notification types.')
8494

85-
8695
class Command(BaseCommand):
87-
help = 'Population notification types.'
96+
help = 'Populate notification types.'
97+
98+
def add_arguments(self, parser):
99+
parser.add_argument(
100+
'--restore-all',
101+
action='store_true',
102+
help='Restore all templates from files'
103+
)
104+
parser.add_argument(
105+
'--restore',
106+
type=str,
107+
help='Restore specific template by name'
108+
)
88109

89110
def handle(self, *args, **options):
90111
with transaction.atomic():
91-
populate_notification_types(args, options)
112+
populate_notification_types(
113+
restore_all=options['restore_all'],
114+
restore_one=options['restore']
115+
)

0 commit comments

Comments
 (0)