clinicedc 2.0.7__py3-none-any.whl → 2.0.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of clinicedc might be problematic. Click here for more details.

Files changed (142) hide show
  1. {clinicedc-2.0.7.dist-info → clinicedc-2.0.9.dist-info}/METADATA +4 -3
  2. {clinicedc-2.0.7.dist-info → clinicedc-2.0.9.dist-info}/RECORD +136 -137
  3. {clinicedc-2.0.7.dist-info → clinicedc-2.0.9.dist-info}/WHEEL +1 -1
  4. edc_action_item/auths.py +37 -32
  5. edc_action_item/models/action_model_mixin.py +1 -2
  6. edc_action_item/models/signals.py +22 -23
  7. edc_action_item/site_action_items.py +5 -9
  8. edc_action_item/utils.py +3 -3
  9. edc_adverse_event/auths.py +55 -51
  10. edc_adverse_event/model_mixins/ae_tmg/ae_tmg_methods_model_mixin.py +2 -4
  11. edc_appointment/auths.py +14 -10
  12. edc_appointment/creators/appointment_creator.py +1 -1
  13. edc_appointment/creators/appointments_creator.py +1 -1
  14. edc_appointment/model_mixins/appointment_methods_model_mixin.py +2 -3
  15. edc_appointment/model_mixins/appointment_model_mixin.py +31 -28
  16. edc_appointment/models/appointment.py +1 -1
  17. edc_appointment/utils.py +19 -24
  18. edc_auth/auth_objects/__init__.py +2 -20
  19. edc_auth/auth_objects/default_groups.py +13 -11
  20. edc_auth/auth_objects/default_roles.py +26 -24
  21. edc_auth/auth_updater/auth_updater.py +13 -2
  22. edc_auth/auth_updater/group_updater.py +12 -10
  23. edc_auth/auth_updater/role_updater.py +2 -2
  24. edc_auth/constants.py +10 -0
  25. edc_auth/import_users.py +3 -3
  26. edc_auth/migrations/0036_alter_userprofile_alternate_email_and_more.py +88 -0
  27. edc_auth/models/user_profile.py +14 -11
  28. edc_auth/site_auths.py +80 -67
  29. edc_consent/auths.py +18 -12
  30. edc_constants/constants.py +1 -0
  31. edc_crf/auths.py +5 -0
  32. edc_dashboard/auths.py +10 -6
  33. edc_dashboard/url_config.py +92 -83
  34. edc_dashboard/url_names.py +4 -4
  35. edc_dashboard/view_mixins/url_request_context_mixin.py +6 -5
  36. edc_data_manager/admin/data_query_admin.py +12 -11
  37. edc_data_manager/auths.py +37 -34
  38. edc_data_manager/rule/query_rule_wrapper.py +7 -7
  39. edc_export/archive_exporter.py +3 -2
  40. edc_export/auths.py +32 -28
  41. edc_export/model_exporter/model_exporter.py +4 -1
  42. edc_facility/auths.py +8 -3
  43. edc_facility/facility.py +8 -9
  44. edc_form_label/custom_label_condition.py +11 -8
  45. edc_form_label/form_label.py +1 -1
  46. edc_form_runners/auths.py +11 -6
  47. edc_form_validators/applicable_field_validator.py +7 -6
  48. edc_form_validators/base_form_validator.py +8 -9
  49. edc_form_validators/other_specify_field_validator.py +2 -8
  50. edc_form_validators/required_field_validator.py +19 -16
  51. edc_identifier/research_identifier.py +11 -10
  52. edc_identifier/simple_identifier.py +8 -2
  53. edc_lab/auths.py +26 -23
  54. edc_lab/lab/aliquot_creator.py +5 -8
  55. edc_lab/lab/primary_aliquot.py +14 -5
  56. edc_lab/migrations/0038_alter_aliquot_slug_alter_box_slug_alter_boxitem_slug_and_more.py +112 -0
  57. edc_lab/model_mixins/requisition/requisition_model_mixin.py +6 -8
  58. edc_lab/models/aliquot.py +2 -2
  59. edc_lab/models/manifest/manifest.py +2 -2
  60. edc_lab/models/manifest/manifest_item.py +1 -1
  61. edc_lab_dashboard/auths.py +16 -11
  62. edc_lab_results/calculate_missing.py +8 -8
  63. edc_lab_results/form_validator_mixins/blood_results_form_validator_mixin.py +2 -2
  64. edc_lab_results/get_summary.py +26 -25
  65. edc_lab_results/model_mixins/blood_result_model_mixin.py +2 -0
  66. edc_label/auths.py +6 -1
  67. edc_label/label_template.py +8 -8
  68. edc_list_data/load_model_data.py +3 -3
  69. edc_list_data/post_migrate_signals.py +1 -1
  70. edc_list_data/preload_data.py +2 -2
  71. edc_list_data/row.py +1 -1
  72. edc_list_data/site_list_data.py +6 -5
  73. edc_locator/auths.py +18 -13
  74. edc_metadata/auths.py +11 -7
  75. edc_metadata/metadata/metadata.py +1 -1
  76. edc_metadata/metadata_rules/crf/crf_rule.py +1 -1
  77. edc_metadata/metadata_rules/metadata_rule_evaluator.py +5 -3
  78. edc_metadata/metadata_rules/rule.py +2 -3
  79. edc_metadata/metadata_rules/rule_evaluator.py +1 -1
  80. edc_metadata/model_mixins/updates/updates_crf_metadata_model_mixin.py +7 -4
  81. edc_metadata/model_mixins/updates/updates_requisition_metadata_model_mixin.py +5 -2
  82. edc_metadata/models/signals.py +10 -11
  83. edc_navbar/auths.py +18 -13
  84. edc_notification/auths.py +9 -4
  85. edc_notification/notification/graded_event_notification.py +2 -2
  86. edc_notification/notification/model_notification.py +3 -30
  87. edc_notification/notification/new_model_notification.py +1 -1
  88. edc_notification/notification/notification.py +1 -1
  89. edc_notification/notification/updated_model_notification.py +2 -2
  90. edc_offstudy/auths.py +12 -7
  91. edc_pdutils/df_exporters/csv_model_exporter.py +5 -2
  92. edc_pharmacy/auths.py +19 -15
  93. edc_pharmacy/models/medication/formulation.py +5 -7
  94. edc_pharmacy/prescribe/create_prescription.py +3 -3
  95. edc_pharmacy/utils/confirm_stock.py +1 -1
  96. edc_pharmacy/utils/confirm_stock_at_site.py +1 -1
  97. edc_pharmacy/views/confirmation_at_site_view.py +6 -9
  98. edc_prn/admin_site.py +5 -0
  99. edc_prn/prn.py +10 -11
  100. edc_prn/urls.py +11 -0
  101. edc_protocol_incident/action_items.py +4 -4
  102. edc_protocol_incident/auths.py +27 -20
  103. edc_pylabels/auths.py +6 -1
  104. edc_qareports/auths.py +11 -7
  105. edc_randomization/admin.py +30 -24
  106. edc_randomization/auths.py +12 -7
  107. edc_randomization/randomizer.py +22 -20
  108. edc_randomization/utils.py +17 -16
  109. edc_refusal/auths.py +7 -2
  110. edc_refusal/model_mixins.py +1 -1
  111. edc_registration/auths.py +28 -23
  112. edc_registration/model_mixins/updates_or_creates_registered_subject_model_mixin.py +13 -4
  113. edc_registration/models/registered_subject.py +1 -1
  114. edc_reportable/utils/get_reference_range_collection.py +2 -3
  115. edc_reportable/utils/load_data.py +1 -1
  116. edc_review_dashboard/auths.py +23 -18
  117. edc_screening/age_evaluator.py +3 -3
  118. edc_screening/auths.py +35 -30
  119. edc_screening/eligibility.py +1 -1
  120. edc_screening/gender_evaluator.py +1 -1
  121. edc_screening/model_mixins/eligibility_model_mixin.py +0 -2
  122. edc_screening/model_mixins/screening_methods_model_mixin.py +1 -1
  123. edc_screening/screening_eligibility.py +2 -4
  124. edc_screening/utils.py +9 -9
  125. edc_search/generate_slug.py +26 -0
  126. edc_search/model_mixins.py +10 -21
  127. edc_sites/auths.py +8 -3
  128. edc_subject_dashboard/auths.py +27 -22
  129. edc_timepoint/apps.py +0 -21
  130. edc_unblinding/auths.py +9 -4
  131. edc_utils/__init__.py +3 -1
  132. edc_utils/show_urls.py +29 -2
  133. edc_visit_schedule/auths.py +6 -1
  134. edc_visit_schedule/site_visit_schedules.py +2 -2
  135. edc_visit_tracking/models/signals.py +2 -2
  136. edc_form_label/models.py +0 -0
  137. edc_search/constants.py +0 -1
  138. edc_search/models.py +0 -0
  139. edc_search/search_slug.py +0 -51
  140. edc_search/updater.py +0 -30
  141. edc_search/wsgi.py +0 -7
  142. {clinicedc-2.0.7.dist-info → clinicedc-2.0.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,21 +1,3 @@
1
- from ..constants import (
2
- ACCOUNT_MANAGER,
3
- ACCOUNT_MANAGER_ROLE,
4
- ADMINISTRATION,
5
- AUDITOR,
6
- AUDITOR_ROLE,
7
- CELERY_MANAGER,
8
- CLINIC,
9
- CLINIC_SUPER,
10
- CLINICIAN_ROLE,
11
- CLINICIAN_SUPER_ROLE,
12
- CUSTOM_ROLE,
13
- EVERYONE,
14
- NURSE_ROLE,
15
- PII,
16
- PII_VIEW,
17
- STAFF_ROLE,
18
- )
19
1
  from .codenames import (
20
2
  account_manager,
21
3
  administration,
@@ -25,6 +7,6 @@ from .codenames import (
25
7
  clinic_super,
26
8
  everyone,
27
9
  )
28
- from .default_groups import default_groups
10
+ from .default_groups import get_default_groups
29
11
  from .default_pii_models import default_pii_models
30
- from .default_roles import default_roles
12
+ from .default_roles import get_default_roles
@@ -11,14 +11,16 @@ from ..constants import (
11
11
  )
12
12
  from .codenames import account_manager, administration, celery_manager, everyone
13
13
 
14
- default_groups = {
15
- ACCOUNT_MANAGER: account_manager,
16
- ADMINISTRATION: administration,
17
- AUDITOR: [],
18
- CELERY_MANAGER: celery_manager,
19
- CLINIC: [],
20
- CLINIC_SUPER: [],
21
- EVERYONE: everyone,
22
- PII: [],
23
- PII_VIEW: [],
24
- }
14
+
15
+ def get_default_groups():
16
+ return {
17
+ ACCOUNT_MANAGER: account_manager,
18
+ ADMINISTRATION: administration,
19
+ AUDITOR: [],
20
+ CELERY_MANAGER: celery_manager,
21
+ CLINIC: [],
22
+ CLINIC_SUPER: [],
23
+ EVERYONE: everyone,
24
+ PII: [],
25
+ PII_VIEW: [],
26
+ }
@@ -17,27 +17,29 @@ from ..constants import (
17
17
  STATISTICIAN,
18
18
  )
19
19
 
20
- # Format {ROLE_NAME: [GROUP_NAME, GROUP_NAME, ...]
21
- default_roles: dict[str, list[str]] = {
22
- ACCOUNT_MANAGER_ROLE: [ACCOUNT_MANAGER],
23
- AUDITOR_ROLE: [
24
- AUDITOR,
25
- PII_VIEW,
26
- ],
27
- CLINICIAN_ROLE: [
28
- CLINIC,
29
- PII,
30
- ],
31
- CLINICIAN_SUPER_ROLE: [
32
- CLINIC_SUPER,
33
- CLINIC,
34
- PII,
35
- ],
36
- CUSTOM_ROLE: [],
37
- NURSE_ROLE: [
38
- CLINIC,
39
- PII,
40
- ],
41
- STAFF_ROLE: [ADMINISTRATION, EVERYONE],
42
- STATISTICIAN: [AUDITOR],
43
- }
20
+
21
+ def get_default_roles() -> dict[str, list[str]]:
22
+ """Format {ROLE_NAME: [GROUP_NAME, GROUP_NAME, ...]"""
23
+ return {
24
+ ACCOUNT_MANAGER_ROLE: [ACCOUNT_MANAGER],
25
+ AUDITOR_ROLE: [
26
+ AUDITOR,
27
+ PII_VIEW,
28
+ ],
29
+ CLINICIAN_ROLE: [
30
+ CLINIC,
31
+ PII,
32
+ ],
33
+ CLINICIAN_SUPER_ROLE: [
34
+ CLINIC_SUPER,
35
+ CLINIC,
36
+ PII,
37
+ ],
38
+ CUSTOM_ROLE: [],
39
+ NURSE_ROLE: [
40
+ CLINIC,
41
+ PII,
42
+ ],
43
+ STAFF_ROLE: [ADMINISTRATION, EVERYONE],
44
+ STATISTICIAN: [AUDITOR],
45
+ }
@@ -9,6 +9,7 @@ from django.contrib.auth import get_user_model
9
9
  from django.core.exceptions import ObjectDoesNotExist
10
10
  from django.core.management.color import color_style
11
11
 
12
+ from ..constants import POST_UPDATE_FUNCS_KEY, PRE_UPDATE_FUNCS_KEY
12
13
  from ..site_auths import site_auths
13
14
  from .group_updater import GroupUpdater
14
15
  from .role_updater import RoleUpdater
@@ -17,6 +18,14 @@ style = color_style()
17
18
 
18
19
 
19
20
  class AuthUpdater:
21
+ """A class to update auth.Group, edc_auth.Role, auth.Permissions
22
+ models using the site_auth registry.
23
+
24
+ Called once on application startup. For example::
25
+
26
+ AuthUpdater(verbose=False, warn_only=True)
27
+ """
28
+
20
29
  group_updater_cls = GroupUpdater
21
30
  role_updater_cls = RoleUpdater
22
31
 
@@ -38,8 +47,10 @@ class AuthUpdater:
38
47
  )
39
48
  groups = groups or site_auths.groups
40
49
  pii_models = pii_models or site_auths.pii_models
41
- post_update_funcs = post_update_funcs or site_auths.post_update_funcs
42
- pre_update_funcs = pre_update_funcs or site_auths.pre_update_funcs
50
+ post_update_funcs = post_update_funcs or [f for f in site_auths.post_update_funcs]
51
+ site_auths.registry[POST_UPDATE_FUNCS_KEY] = []
52
+ pre_update_funcs = pre_update_funcs or [f for f in site_auths.pre_update_funcs]
53
+ site_auths.registry[PRE_UPDATE_FUNCS_KEY] = []
43
54
  roles = roles or site_auths.roles
44
55
  self.apps = apps
45
56
  if not self.edc_auth_skip_auth_updater:
@@ -11,7 +11,7 @@ from django.core.exceptions import (
11
11
  )
12
12
  from django.core.management.color import color_style
13
13
 
14
- from ..auth_objects import PII, PII_VIEW
14
+ from ..constants import PII, PII_VIEW
15
15
  from ..utils import make_view_only_group_permissions
16
16
 
17
17
  style = color_style()
@@ -27,7 +27,7 @@ class PermissionsCreatorError(ValidationError):
27
27
  pass
28
28
 
29
29
 
30
- class CodenameDoesNotExist(Exception):
30
+ class CodenameDoesNotExist(Exception): # noqa: N818
31
31
  pass
32
32
 
33
33
 
@@ -87,7 +87,7 @@ class GroupUpdater:
87
87
  group = self.group_model_cls.objects.get(name=group_name)
88
88
  except ObjectDoesNotExist as e:
89
89
  if not create_group:
90
- raise ObjectDoesNotExist(f"{e} Got {group_name}")
90
+ raise ObjectDoesNotExist(f"{e} Got {group_name}") from e
91
91
  group = self.group_model_cls.objects.create(name=group_name)
92
92
  else:
93
93
  group.permissions.clear()
@@ -122,7 +122,7 @@ class GroupUpdater:
122
122
  try:
123
123
  app_label, codename = self.get_from_dotted_codename(dotted_codename)
124
124
  except PermissionsCodenameError as e:
125
- warn(str(e))
125
+ warn(str(e), stacklevel=2)
126
126
  else:
127
127
  # if you add extra codenames you must also add custom
128
128
  # codename tuples to the Permissions model before you
@@ -138,7 +138,7 @@ class GroupUpdater:
138
138
  except ObjectDoesNotExist as e:
139
139
  errmsg = f"{e} Got codename={codename},app_label={app_label}"
140
140
  if not self.warn_only:
141
- raise CodenameDoesNotExist(errmsg)
141
+ raise CodenameDoesNotExist(errmsg) from e
142
142
  warn(style.ERROR(errmsg))
143
143
  except MultipleObjectsReturned as e:
144
144
  if not allow_multiple_objects:
@@ -166,15 +166,17 @@ class GroupUpdater:
166
166
  try:
167
167
  app_label, _codename = codename.split(".")
168
168
  except ValueError as e:
169
- raise PermissionsCodenameError(f"Invalid dotted codename. {e} Got {codename}.")
169
+ raise PermissionsCodenameError(
170
+ f"Invalid dotted codename. {e} Got {codename}."
171
+ ) from e
170
172
  else:
171
173
  try:
172
174
  self.apps.get_app_config(app_label)
173
- except LookupError:
175
+ except LookupError as e:
174
176
  raise PermissionsCodenameError(
175
177
  "Invalid app_label in codename. Expected format "
176
178
  f"'<app_label>.<some_codename>'. Got {codename}."
177
- )
179
+ ) from e
178
180
  prefix = _codename.split("_")[0]
179
181
  if prefix not in self.codename_prefixes:
180
182
  raise PermissionsCodenameError(
@@ -244,7 +246,7 @@ class GroupUpdater:
244
246
  except ObjectDoesNotExist as e:
245
247
  raise CodenameDoesNotExist(
246
248
  f"Unable to verify codename. {e} Got '{app_label}.{codename}'"
247
- )
249
+ ) from e
248
250
  except MultipleObjectsReturned as e:
249
251
  self.delete_and_raise_on_duplicate_codenames(codename, app_label, exception=e)
250
252
  return permission
@@ -267,7 +269,7 @@ class GroupUpdater:
267
269
  try:
268
270
  value, name = codename_tpl
269
271
  except ValueError as e:
270
- raise ValueError(f"{e} Got {codename_tpl}")
272
+ raise ValueError(f"{e} Got {codename_tpl}") from e
271
273
  _app_label, codename = value.split(".")
272
274
  if app_label and _app_label != app_label:
273
275
  raise PermissionsCreatorError(
@@ -56,11 +56,11 @@ class RoleUpdater:
56
56
  for group_name in groups:
57
57
  try:
58
58
  group = self.group_model_cls.objects.get(name=group_name)
59
- except ObjectDoesNotExist:
59
+ except ObjectDoesNotExist as e:
60
60
  raise RoleUpdaterError(
61
61
  "Invalid group specified for role. "
62
62
  f"`{group_name}` is not a group. See role `{role}`."
63
- )
63
+ ) from e
64
64
  # group = self.group_model_cls.objects.create(name=group_name)
65
65
  role.groups.add(group)
66
66
  index += 1
edc_auth/constants.py CHANGED
@@ -19,3 +19,13 @@ CUSTOM_ROLE = "custom"
19
19
  NURSE_ROLE = "research_nurse"
20
20
  STAFF_ROLE = "staff"
21
21
  STATISTICIAN = "statistician"
22
+
23
+ # keys for the site_auths registry
24
+ UPDATE_ROLES_KEY = "update_roles"
25
+ UPDATE_GROUPS_KEY = "update_groups"
26
+ PRE_UPDATE_FUNCS_KEY = "pre_update_funcs"
27
+ POST_UPDATE_FUNCS_KEY = "post_update_funcs"
28
+ GROUPS_KEY = "groups"
29
+ ROLES_KEY = "roles"
30
+ PII_MODELS_KEY = "pii_models"
31
+ CUSTOM_PERMISSIONS_TUPLES_KEY = "custom_permissions_tuples"
edc_auth/import_users.py CHANGED
@@ -183,9 +183,9 @@ class UserImporter:
183
183
  self.update_user_sites(site_names or [])
184
184
  self.update_user_roles(role_names or [STAFF_ROLE])
185
185
  self.user.save()
186
- self.user.userprofile.job_title = self.job_title
187
- self.user.userprofile.mobile = self.mobile
188
- self.user.userprofile.alternate_email = self.alternate_email
186
+ self.user.userprofile.job_title = self.job_title or ""
187
+ self.user.userprofile.mobile = self.mobile or ""
188
+ self.user.userprofile.alternate_email = self.alternate_email or ""
189
189
  self.user.userprofile.save()
190
190
 
191
191
  self.site_names = (
@@ -0,0 +1,88 @@
1
+ # Generated by Django 5.2.6 on 2025-09-22 12:31
2
+
3
+ import django.core.validators
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ("edc_auth", "0035_alter_edcpermissions_device_created_and_more"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name="userprofile",
16
+ name="alternate_email",
17
+ field=models.EmailField(
18
+ blank=True,
19
+ default="",
20
+ max_length=254,
21
+ verbose_name="Alternate email address",
22
+ ),
23
+ ),
24
+ migrations.AlterField(
25
+ model_name="userprofile",
26
+ name="clinic_label_printer",
27
+ field=models.CharField(
28
+ blank=True,
29
+ default="",
30
+ help_text='Change in <a href="/edc_label/">Edc Label Administration</a>',
31
+ max_length=100,
32
+ ),
33
+ ),
34
+ migrations.AlterField(
35
+ model_name="userprofile",
36
+ name="export_format",
37
+ field=models.CharField(
38
+ blank=True,
39
+ choices=[
40
+ ("CSV", "CSV (delimited by pipe `|`)"),
41
+ (114, "Stata v10 or later"),
42
+ (117, "Stata v13 or later"),
43
+ (118, "Stata v14 or later"),
44
+ (119, "Stata v15 or later"),
45
+ ],
46
+ default="CSV",
47
+ help_text="Note: requires export permissions",
48
+ max_length=25,
49
+ verbose_name="Export format",
50
+ ),
51
+ ),
52
+ migrations.AlterField(
53
+ model_name="userprofile",
54
+ name="job_title",
55
+ field=models.CharField(blank=True, default="", max_length=100),
56
+ ),
57
+ migrations.AlterField(
58
+ model_name="userprofile",
59
+ name="lab_label_printer",
60
+ field=models.CharField(
61
+ blank=True,
62
+ default="",
63
+ help_text='Change in <a href="/edc_label/">Edc Label Administration</a>',
64
+ max_length=100,
65
+ ),
66
+ ),
67
+ migrations.AlterField(
68
+ model_name="userprofile",
69
+ name="mobile",
70
+ field=models.CharField(
71
+ blank=True,
72
+ default="",
73
+ help_text="e.g. +1234567890",
74
+ max_length=25,
75
+ validators=[django.core.validators.RegexValidator(regex="^\\+\\d+")],
76
+ ),
77
+ ),
78
+ migrations.AlterField(
79
+ model_name="userprofile",
80
+ name="print_server",
81
+ field=models.CharField(
82
+ blank=True,
83
+ default="",
84
+ help_text='Change in <a href="/edc_label/">Edc Label Administration</a>',
85
+ max_length=100,
86
+ ),
87
+ ),
88
+ ]
@@ -11,7 +11,7 @@ from edc_export.choices import EXPORT_FORMATS
11
11
  from edc_export.constants import CSV
12
12
  from edc_notification.model_mixins import NotificationUserProfileModelMixin
13
13
 
14
- from ..auth_objects import CUSTOM_ROLE, STAFF_ROLE
14
+ from ..constants import CUSTOM_ROLE, STAFF_ROLE
15
15
  from .role import Role
16
16
 
17
17
 
@@ -32,21 +32,21 @@ class UserProfile(NotificationUserProfileModelMixin, models.Model):
32
32
 
33
33
  sites = models.ManyToManyField(Site, blank=True)
34
34
 
35
- job_title = models.CharField(max_length=100, null=True, blank=True)
35
+ job_title = models.CharField(max_length=100, default="", blank=True)
36
36
 
37
- alternate_email = models.EmailField(_("Alternate email address"), blank=True, null=True)
37
+ alternate_email = models.EmailField(_("Alternate email address"), blank=True, default="")
38
38
 
39
39
  mobile = models.CharField(
40
40
  max_length=25,
41
41
  validators=[RegexValidator(regex=r"^\+\d+")],
42
- null=True,
42
+ default="",
43
43
  blank=True,
44
44
  help_text="e.g. +1234567890",
45
45
  )
46
46
 
47
47
  clinic_label_printer = models.CharField(
48
48
  max_length=100,
49
- null=True,
49
+ default="",
50
50
  blank=True,
51
51
  help_text=format_html(
52
52
  'Change in <a href="{href}">{label}</a>',
@@ -57,7 +57,7 @@ class UserProfile(NotificationUserProfileModelMixin, models.Model):
57
57
 
58
58
  lab_label_printer = models.CharField(
59
59
  max_length=100,
60
- null=True,
60
+ default="",
61
61
  blank=True,
62
62
  help_text=format_html(
63
63
  'Change in <a href="{href}">{label}</a>',
@@ -68,7 +68,7 @@ class UserProfile(NotificationUserProfileModelMixin, models.Model):
68
68
 
69
69
  print_server = models.CharField(
70
70
  max_length=100,
71
- null=True,
71
+ default="",
72
72
  blank=True,
73
73
  help_text=format_html(
74
74
  'Change in <a href="{href}">{label}</a>',
@@ -82,7 +82,6 @@ class UserProfile(NotificationUserProfileModelMixin, models.Model):
82
82
  max_length=25,
83
83
  choices=EXPORT_FORMATS,
84
84
  default=CSV,
85
- null=True,
86
85
  blank=True,
87
86
  help_text="Note: requires export permissions",
88
87
  )
@@ -101,9 +100,13 @@ class UserProfile(NotificationUserProfileModelMixin, models.Model):
101
100
  group_names = [group.name for group in self.user.groups.all()]
102
101
  add_group_names = []
103
102
  for role in self.roles.all():
104
- for group in role.groups.all():
105
- if group.name not in group_names:
106
- add_group_names.append(group.name)
103
+ add_group_names.extend(
104
+ [
105
+ group.name
106
+ for group in role.groups.all()
107
+ if group.name not in group_names
108
+ ]
109
+ )
107
110
  add_group_names = list(set(add_group_names))
108
111
  for name in add_group_names:
109
112
  self.user.groups.add(Group.objects.get(name=name))