BmorricalDjangoWorkflows 2.1.3__tar.gz → 2.1.4__tar.gz

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.
Files changed (25) hide show
  1. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/BmorricalDjangoWorkflows.egg-info/PKG-INFO +15 -1
  2. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/PKG-INFO +15 -1
  3. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/README.md +14 -0
  4. bmorricaldjangoworkflows-2.1.4/VERSION +1 -0
  5. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/django_workflows/users/services/auth_flow.py +33 -5
  6. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/tests/test_auth_flow.py +32 -1
  7. bmorricaldjangoworkflows-2.1.3/VERSION +0 -1
  8. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/BmorricalDjangoWorkflows.egg-info/SOURCES.txt +0 -0
  9. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/BmorricalDjangoWorkflows.egg-info/dependency_links.txt +0 -0
  10. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/BmorricalDjangoWorkflows.egg-info/requires.txt +0 -0
  11. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/BmorricalDjangoWorkflows.egg-info/top_level.txt +0 -0
  12. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/LICENSE +0 -0
  13. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/MANIFEST.in +0 -0
  14. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/django_workflows/__init__.py +0 -0
  15. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/django_workflows/services/__init__.py +0 -0
  16. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/django_workflows/services/email.py +0 -0
  17. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/django_workflows/users/__init__.py +0 -0
  18. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/django_workflows/users/services/__init__.py +0 -0
  19. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/django_workflows/users/urls.py +0 -0
  20. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/django_workflows/users/views.py +0 -0
  21. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/setup.cfg +0 -0
  22. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/setup.py +0 -0
  23. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/tests/__init__.py +0 -0
  24. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/tests/test_email.py +0 -0
  25. {bmorricaldjangoworkflows-2.1.3 → bmorricaldjangoworkflows-2.1.4}/tests/test_user_views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: BmorricalDjangoWorkflows
3
- Version: 2.1.3
3
+ Version: 2.1.4
4
4
  Summary: Reusable Django Workflows
5
5
  Home-page: https://github.com/Bmorrical/django-workflows
6
6
  Author: Bradley Morrical
@@ -78,6 +78,7 @@ WORKFLOWS = {
78
78
  # Optional settings with defaults shown.
79
79
  "COMPANY_NAME": "My Company",
80
80
  "ENABLE_EMAIL_DELIVERY": False,
81
+ "FORGOT_PASSWORD_URL": "https://app.example.com/forgot-password",
81
82
  "RESET_CODE_TTL_MINUTES": 10,
82
83
  "MAX_VERIFY_ATTEMPTS": 3,
83
84
  }
@@ -86,8 +87,21 @@ WORKFLOWS = {
86
87
  Notes:
87
88
 
88
89
  - `USER_META_MODEL` must be a dotted import path such as `myapp.models.UserMeta`.
90
+ - `FORGOT_PASSWORD_URL` is used in the admin-created account email to link users into the host app's forgot-password flow.
91
+ - Host apps will usually source `FORGOT_PASSWORD_URL` from an environment variable in their own settings module.
89
92
  - If `ENABLE_EMAIL_DELIVERY` is true, configure the email transport environment variables used by the mail service.
90
93
 
94
+ Example host-project wiring:
95
+
96
+ ```python
97
+ import os
98
+
99
+ WORKFLOWS = {
100
+ "USER_META_MODEL": "users.models_user_meta.UserMeta",
101
+ "FORGOT_PASSWORD_URL": os.getenv("FORGOT_PASSWORD_URL", ""),
102
+ }
103
+ ```
104
+
91
105
  ## Email Delivery Environment Variables
92
106
 
93
107
  When email delivery is enabled, set:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: BmorricalDjangoWorkflows
3
- Version: 2.1.3
3
+ Version: 2.1.4
4
4
  Summary: Reusable Django Workflows
5
5
  Home-page: https://github.com/Bmorrical/django-workflows
6
6
  Author: Bradley Morrical
@@ -78,6 +78,7 @@ WORKFLOWS = {
78
78
  # Optional settings with defaults shown.
79
79
  "COMPANY_NAME": "My Company",
80
80
  "ENABLE_EMAIL_DELIVERY": False,
81
+ "FORGOT_PASSWORD_URL": "https://app.example.com/forgot-password",
81
82
  "RESET_CODE_TTL_MINUTES": 10,
82
83
  "MAX_VERIFY_ATTEMPTS": 3,
83
84
  }
@@ -86,8 +87,21 @@ WORKFLOWS = {
86
87
  Notes:
87
88
 
88
89
  - `USER_META_MODEL` must be a dotted import path such as `myapp.models.UserMeta`.
90
+ - `FORGOT_PASSWORD_URL` is used in the admin-created account email to link users into the host app's forgot-password flow.
91
+ - Host apps will usually source `FORGOT_PASSWORD_URL` from an environment variable in their own settings module.
89
92
  - If `ENABLE_EMAIL_DELIVERY` is true, configure the email transport environment variables used by the mail service.
90
93
 
94
+ Example host-project wiring:
95
+
96
+ ```python
97
+ import os
98
+
99
+ WORKFLOWS = {
100
+ "USER_META_MODEL": "users.models_user_meta.UserMeta",
101
+ "FORGOT_PASSWORD_URL": os.getenv("FORGOT_PASSWORD_URL", ""),
102
+ }
103
+ ```
104
+
91
105
  ## Email Delivery Environment Variables
92
106
 
93
107
  When email delivery is enabled, set:
@@ -54,6 +54,7 @@ WORKFLOWS = {
54
54
  # Optional settings with defaults shown.
55
55
  "COMPANY_NAME": "My Company",
56
56
  "ENABLE_EMAIL_DELIVERY": False,
57
+ "FORGOT_PASSWORD_URL": "https://app.example.com/forgot-password",
57
58
  "RESET_CODE_TTL_MINUTES": 10,
58
59
  "MAX_VERIFY_ATTEMPTS": 3,
59
60
  }
@@ -62,8 +63,21 @@ WORKFLOWS = {
62
63
  Notes:
63
64
 
64
65
  - `USER_META_MODEL` must be a dotted import path such as `myapp.models.UserMeta`.
66
+ - `FORGOT_PASSWORD_URL` is used in the admin-created account email to link users into the host app's forgot-password flow.
67
+ - Host apps will usually source `FORGOT_PASSWORD_URL` from an environment variable in their own settings module.
65
68
  - If `ENABLE_EMAIL_DELIVERY` is true, configure the email transport environment variables used by the mail service.
66
69
 
70
+ Example host-project wiring:
71
+
72
+ ```python
73
+ import os
74
+
75
+ WORKFLOWS = {
76
+ "USER_META_MODEL": "users.models_user_meta.UserMeta",
77
+ "FORGOT_PASSWORD_URL": os.getenv("FORGOT_PASSWORD_URL", ""),
78
+ }
79
+ ```
80
+
67
81
  ## Email Delivery Environment Variables
68
82
 
69
83
  When email delivery is enabled, set:
@@ -0,0 +1 @@
1
+ 2.1.4
@@ -26,6 +26,7 @@ _ALLOWED_CODE_CHARS = [
26
26
  ]
27
27
  _LOG_DEV_CODES_SETTING = "LOG_VERIFICATION_CODES_IN_DEBUG"
28
28
  _EMAIL_DELIVERY_ENABLED_SETTING = "ENABLE_EMAIL_DELIVERY"
29
+ _FORGOT_PASSWORD_URL_SETTING = "FORGOT_PASSWORD_URL"
29
30
 
30
31
 
31
32
  def generate_verification_code(*, length: int = 6) -> str:
@@ -314,6 +315,32 @@ def _get_force_password_reset(user: Any) -> bool:
314
315
  return False
315
316
 
316
317
 
318
+ def _build_admin_setup_email_message(
319
+ *,
320
+ first_name: str,
321
+ last_name: str,
322
+ company_name: str,
323
+ forgot_password_url: Any,
324
+ ) -> str:
325
+ forgot_password_instructions = (
326
+ 'Use the "Forgot Password" flow to establish your password and login.<br><br>'
327
+ )
328
+ normalized_forgot_password_url = str(forgot_password_url or "").strip()
329
+
330
+ if normalized_forgot_password_url:
331
+ forgot_password_instructions = (
332
+ f'Click the link <a href="{normalized_forgot_password_url}">{normalized_forgot_password_url}</a> to use the '
333
+ '"Forgot Password" flow to establish your password and login.<br><br>'
334
+ )
335
+
336
+ return (
337
+ f"Hello {first_name} {last_name},<br><br>"
338
+ 'You have been added as a registered user by an administrator. '
339
+ f"{forgot_password_instructions}"
340
+ f"Thank you!<br><br>{company_name}"
341
+ )
342
+
343
+
317
344
  def get_user_details(user: Any) -> dict[str, Any]:
318
345
  return {
319
346
  "id": user.id,
@@ -505,6 +532,7 @@ def admin_register_user_and_send_setup_email(
505
532
  User = get_user_model()
506
533
 
507
534
  company_name = _get_workflow_setting("COMPANY_NAME", "")
535
+ forgot_password_url = _get_workflow_setting(_FORGOT_PASSWORD_URL_SETTING, "")
508
536
  enable_email_delivery = _is_enabled(
509
537
  _get_workflow_setting(_EMAIL_DELIVERY_ENABLED_SETTING, False)
510
538
  )
@@ -537,11 +565,11 @@ def admin_register_user_and_send_setup_email(
537
565
  )
538
566
  _sync_user_groups(user=user, groups=validated_groups)
539
567
 
540
- message = (
541
- f"Hello {first_name} {last_name},<br><br>"
542
- 'You have been added as a registered user by an administrator. '
543
- 'Use the "Forgot Password" flow to establish your password and login.<br><br>'
544
- f"Thank you!<br><br>{company_name}"
568
+ message = _build_admin_setup_email_message(
569
+ first_name=first_name,
570
+ last_name=last_name,
571
+ company_name=company_name,
572
+ forgot_password_url=forgot_password_url,
545
573
  )
546
574
  except IntegrityError:
547
575
  logger.exception("Admin registration failed due to IntegrityError for email=%s", email)
@@ -47,6 +47,7 @@ class AuthFlowTests(TestCase):
47
47
  defaults = {
48
48
  "COMPANY_NAME": "Acme Co",
49
49
  "ENABLE_EMAIL_DELIVERY": False,
50
+ "FORGOT_PASSWORD_URL": "",
50
51
  "RESET_CODE_TTL_MINUTES": 10,
51
52
  "MAX_VERIFY_ATTEMPTS": 3,
52
53
  }
@@ -75,6 +76,35 @@ class AuthFlowTests(TestCase):
75
76
  self.assertIsInstance(password, str)
76
77
  self.assertTrue(password)
77
78
 
79
+ def test_build_admin_setup_email_message_includes_forgot_password_link_when_configured(self):
80
+ message = auth_flow._build_admin_setup_email_message(
81
+ first_name="New",
82
+ last_name="User",
83
+ company_name="Reusable Inc",
84
+ forgot_password_url="https://app.example.com/forgot-password",
85
+ )
86
+
87
+ self.assertIn(
88
+ 'Click the link <a href="https://app.example.com/forgot-password">https://app.example.com/forgot-password</a> to use the '
89
+ '"Forgot Password" flow to establish your password and login.<br><br>',
90
+ message,
91
+ )
92
+ self.assertIn("Reusable Inc", message)
93
+
94
+ def test_build_admin_setup_email_message_falls_back_when_url_missing(self):
95
+ message = auth_flow._build_admin_setup_email_message(
96
+ first_name="New",
97
+ last_name="User",
98
+ company_name="Reusable Inc",
99
+ forgot_password_url=" ",
100
+ )
101
+
102
+ self.assertIn(
103
+ 'Use the "Forgot Password" flow to establish your password and login.<br><br>',
104
+ message,
105
+ )
106
+ self.assertNotIn("href=", message)
107
+
78
108
  @patch.object(auth_flow.logger, "isEnabledFor", return_value=True)
79
109
  @patch.object(auth_flow.logger, "debug")
80
110
  @patch.object(auth_flow, "_get_workflow_setting", return_value=True)
@@ -757,6 +787,7 @@ class AuthFlowTests(TestCase):
757
787
  mock_get_setting.side_effect = self._settings_getter(
758
788
  ENABLE_EMAIL_DELIVERY=True,
759
789
  COMPANY_NAME="Reusable Inc",
790
+ FORGOT_PASSWORD_URL="https://app.example.com/forgot-password",
760
791
  )
761
792
  mock_send_email.return_value = SimpleNamespace(status_code=200, text="ok")
762
793
 
@@ -807,7 +838,7 @@ class AuthFlowTests(TestCase):
807
838
  self.assertEqual(kwargs["to"], ["new@example.com"])
808
839
  self.assertEqual(kwargs["subject"], "Your account has been created")
809
840
  self.assertIn(
810
- 'You have been added as a registered user by an administrator. Use the "Forgot Password" flow to establish your password and login.',
841
+ 'You have been added as a registered user by an administrator. Click the link <a href="https://app.example.com/forgot-password">https://app.example.com/forgot-password</a> to use the "Forgot Password" flow to establish your password and login.',
811
842
  kwargs["html"],
812
843
  )
813
844
  self.assertIn("Reusable Inc", kwargs["html"])
@@ -1 +0,0 @@
1
- 2.1.3