django-solomon 0.3.0__tar.gz → 0.4.0__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 (58) hide show
  1. {django_solomon-0.3.0 → django_solomon-0.4.0}/CHANGELOG.md +6 -0
  2. {django_solomon-0.3.0 → django_solomon-0.4.0}/PKG-INFO +17 -14
  3. {django_solomon-0.3.0 → django_solomon-0.4.0}/README.md +15 -13
  4. {django_solomon-0.3.0 → django_solomon-0.4.0}/pyproject.toml +1 -0
  5. django_solomon-0.4.0/src/django_solomon/migrations/0003_add_unique_constraint_auth_user_email.py +43 -0
  6. {django_solomon-0.3.0 → django_solomon-0.4.0}/tests/test_models.py +49 -2
  7. {django_solomon-0.3.0 → django_solomon-0.4.0}/.forgejo/workflows/release.yml +0 -0
  8. {django_solomon-0.3.0 → django_solomon-0.4.0}/.forgejo/workflows/tests.yml +0 -0
  9. {django_solomon-0.3.0 → django_solomon-0.4.0}/.gitignore +0 -0
  10. {django_solomon-0.3.0 → django_solomon-0.4.0}/.pre-commit-config.yaml +0 -0
  11. {django_solomon-0.3.0 → django_solomon-0.4.0}/.readthedocs.yml +0 -0
  12. {django_solomon-0.3.0 → django_solomon-0.4.0}/LICENSE +0 -0
  13. {django_solomon-0.3.0 → django_solomon-0.4.0}/docs/changelog.md +0 -0
  14. {django_solomon-0.3.0 → django_solomon-0.4.0}/docs/contributing.md +0 -0
  15. {django_solomon-0.3.0 → django_solomon-0.4.0}/docs/index.md +0 -0
  16. {django_solomon-0.3.0 → django_solomon-0.4.0}/docs/installation.md +0 -0
  17. {django_solomon-0.3.0 → django_solomon-0.4.0}/docs/requirements.txt +0 -0
  18. {django_solomon-0.3.0 → django_solomon-0.4.0}/docs/settings.md +0 -0
  19. {django_solomon-0.3.0 → django_solomon-0.4.0}/docs/templates.md +0 -0
  20. {django_solomon-0.3.0 → django_solomon-0.4.0}/justfile +0 -0
  21. {django_solomon-0.3.0 → django_solomon-0.4.0}/mkdocs.yml +0 -0
  22. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/__init__.py +0 -0
  23. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/admin.py +0 -0
  24. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/apps.py +0 -0
  25. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/backends.py +0 -0
  26. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/config.py +0 -0
  27. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/forms.py +0 -0
  28. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/locale/de/LC_MESSAGES/django.po +0 -0
  29. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/locale/en/LC_MESSAGES/django.po +0 -0
  30. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/management/__init__.py +0 -0
  31. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/management/commands/__init__.py +0 -0
  32. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/migrations/0001_initial.py +0 -0
  33. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/migrations/0002_magiclink_ip_address.py +0 -0
  34. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/migrations/__init__.py +0 -0
  35. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/models.py +0 -0
  36. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/py.typed +0 -0
  37. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/templates/django_solomon/base/invalid_magic_link.html +0 -0
  38. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/templates/django_solomon/base/login_form.html +0 -0
  39. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/templates/django_solomon/base/magic_link_sent.html +0 -0
  40. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/templates/django_solomon/email/magic_link.mjml +0 -0
  41. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/templates/django_solomon/email/magic_link.txt +0 -0
  42. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/urls.py +0 -0
  43. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/utilities.py +0 -0
  44. {django_solomon-0.3.0 → django_solomon-0.4.0}/src/django_solomon/views.py +0 -0
  45. {django_solomon-0.3.0 → django_solomon-0.4.0}/tests/.gitignore +0 -0
  46. {django_solomon-0.3.0 → django_solomon-0.4.0}/tests/__init__.py +0 -0
  47. {django_solomon-0.3.0 → django_solomon-0.4.0}/tests/conftest.py +0 -0
  48. {django_solomon-0.3.0 → django_solomon-0.4.0}/tests/settings.py +0 -0
  49. {django_solomon-0.3.0 → django_solomon-0.4.0}/tests/templates/base.html +0 -0
  50. {django_solomon-0.3.0 → django_solomon-0.4.0}/tests/test_admin.py +0 -0
  51. {django_solomon-0.3.0 → django_solomon-0.4.0}/tests/test_backends.py +0 -0
  52. {django_solomon-0.3.0 → django_solomon-0.4.0}/tests/test_config.py +0 -0
  53. {django_solomon-0.3.0 → django_solomon-0.4.0}/tests/test_forms.py +0 -0
  54. {django_solomon-0.3.0 → django_solomon-0.4.0}/tests/test_urls.py +0 -0
  55. {django_solomon-0.3.0 → django_solomon-0.4.0}/tests/test_utilities.py +0 -0
  56. {django_solomon-0.3.0 → django_solomon-0.4.0}/tests/test_views.py +0 -0
  57. {django_solomon-0.3.0 → django_solomon-0.4.0}/tox.ini +0 -0
  58. {django_solomon-0.3.0 → django_solomon-0.4.0}/uv.lock +0 -0
@@ -5,6 +5,12 @@ All notable changes to django-solomon will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.0] - Unreleased
9
+
10
+ ### Added
11
+
12
+ - Unique constraint on auth.User email field (case-insensitive)
13
+
8
14
  ## [0.3.0] - 2025-04-20
9
15
 
10
16
  ### Added
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-solomon
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: A Django app for passwordless authentication using magic links.
5
5
  Project-URL: Home, https://django-solomon.rtfd.io/
6
6
  Project-URL: Documentation, https://django-solomon.rtfd.io/
7
7
  Project-URL: Repository, https://codeberg.org/oliverandrich/django-solomon
8
+ Project-URL: Issue Tracker, https://codeberg.org/oliverandrich/django-solomon/issues
8
9
  Author-email: Oliver Andrich <oliver@andrich.me>
9
10
  License: MIT License
10
11
 
@@ -52,6 +53,7 @@ Description-Content-Type: text/markdown
52
53
  # django-solomon
53
54
 
54
55
  [![PyPI version](https://img.shields.io/pypi/v/django-solomon.svg)](https://pypi.org/project/django-solomon/)
56
+ ![PyPI - License](https://img.shields.io/pypi/l/django-solomon)
55
57
  [![Python versions](https://img.shields.io/pypi/pyversions/django-solomon.svg)](https://pypi.org/project/django-solomon/)
56
58
  [![Django versions](https://img.shields.io/pypi/djversions/django-solomon.svg)](https://pypi.org/project/django-solomon/)
57
59
  [![Documentation Status](https://readthedocs.org/projects/django-solomon/badge/?version=latest)](https://django-solomon.rtfd.io/en/latest/?badge=latest)
@@ -70,6 +72,7 @@ A Django app for passwordless authentication using magic links.
70
72
  - Privacy-focused IP anonymization
71
73
  - Customizable templates for emails and pages
72
74
  - Compatible with Django's authentication system
75
+ - Enforces unique email addresses for users (case-insensitive)
73
76
 
74
77
  ## Installation
75
78
 
@@ -134,19 +137,19 @@ DEFAULT_FROM_EMAIL = 'your-email@example.com'
134
137
 
135
138
  django-solomon provides several settings that you can customize in your Django settings file:
136
139
 
137
- | Setting | Default | Description |
138
- |---------------------------------------|-------------------------------------------------|----------------------------------------------------------------------------------------|
139
- | `SOLOMON_LINK_EXPIRATION` | `300` | The expiration time for magic links in seconds |
140
- | `SOLOMON_ONLY_ONE_LINK_ALLOWED` | `True` | If enabled, only one active magic link is allowed per user |
141
- | `SOLOMON_CREATE_USER_IF_NOT_FOUND` | `False` | If enabled, creates a new user when a magic link is requested for a non-existent email |
142
- | `SOLOMON_LOGIN_REDIRECT_URL` | `settings.LOGIN_REDIRECT_URL` | The URL to redirect to after successful authentication |
143
- | `SOLOMON_ALLOW_ADMIN_LOGIN` | `True` | If enabled, allows superusers to log in using magic links |
144
- | `SOLOMON_ALLOW_STAFF_LOGIN` | `True` | If enabled, allows staff users to log in using magic links |
145
- | `SOLOMON_MAIL_TEXT_TEMPLATE` | `"django_solomon/email/magic_link.txt"` | The template to use for plain text magic link emails |
146
- | `SOLOMON_MAIL_MJML_TEMPLATE` | `"django_solomon/email/magic_link.mjml"` | The template to use for HTML magic link emails (MJML format) |
147
- | `SOLOMON_LOGIN_FORM_TEMPLATE` | `"django_solomon/base/login_form.html"` | The template to use for the login form page |
148
- | `SOLOMON_INVALID_MAGIC_LINK_TEMPLATE` | `"django_solomon/base/invalid_magic_link.html"` | The template to use for the invalid magic link page |
149
- | `SOLOMON_MAGIC_LINK_SENT_TEMPLATE` | `"django_solomon/base/magic_link_sent.html"` | The template to use for the magic link sent confirmation page |
140
+ | Setting | Default | Description |
141
+ |---------------------------------------|-------------------------------------------------|-----------------------------------------------------------------------------------------|
142
+ | `SOLOMON_LINK_EXPIRATION` | `300` | The expiration time for magic links in seconds |
143
+ | `SOLOMON_ONLY_ONE_LINK_ALLOWED` | `True` | If enabled, only one active magic link is allowed per user |
144
+ | `SOLOMON_CREATE_USER_IF_NOT_FOUND` | `False` | If enabled, creates a new user when a magic link is requested for a non-existent email |
145
+ | `SOLOMON_LOGIN_REDIRECT_URL` | `settings.LOGIN_REDIRECT_URL` | The URL to redirect to after successful authentication |
146
+ | `SOLOMON_ALLOW_ADMIN_LOGIN` | `True` | If enabled, allows superusers to log in using magic links |
147
+ | `SOLOMON_ALLOW_STAFF_LOGIN` | `True` | If enabled, allows staff users to log in using magic links |
148
+ | `SOLOMON_MAIL_TEXT_TEMPLATE` | `"django_solomon/email/magic_link.txt"` | The template to use for plain text magic link emails |
149
+ | `SOLOMON_MAIL_MJML_TEMPLATE` | `"django_solomon/email/magic_link.mjml"` | The template to use for HTML magic link emails (MJML format) |
150
+ | `SOLOMON_LOGIN_FORM_TEMPLATE` | `"django_solomon/base/login_form.html"` | The template to use for the login form page |
151
+ | `SOLOMON_INVALID_MAGIC_LINK_TEMPLATE` | `"django_solomon/base/invalid_magic_link.html"` | The template to use for the invalid magic link page |
152
+ | `SOLOMON_MAGIC_LINK_SENT_TEMPLATE` | `"django_solomon/base/magic_link_sent.html"` | The template to use for the magic link sent confirmation page |
150
153
  | `SOLOMON_ENFORCE_SAME_IP` | `False` | If enabled, validates that magic links are used from the same IP they were created from |
151
154
  | `SOLOMON_ANONYMIZE_IP` | `True` | If enabled, anonymizes IP addresses before storing them (removes last octet for IPv4) |
152
155
 
@@ -1,6 +1,7 @@
1
1
  # django-solomon
2
2
 
3
3
  [![PyPI version](https://img.shields.io/pypi/v/django-solomon.svg)](https://pypi.org/project/django-solomon/)
4
+ ![PyPI - License](https://img.shields.io/pypi/l/django-solomon)
4
5
  [![Python versions](https://img.shields.io/pypi/pyversions/django-solomon.svg)](https://pypi.org/project/django-solomon/)
5
6
  [![Django versions](https://img.shields.io/pypi/djversions/django-solomon.svg)](https://pypi.org/project/django-solomon/)
6
7
  [![Documentation Status](https://readthedocs.org/projects/django-solomon/badge/?version=latest)](https://django-solomon.rtfd.io/en/latest/?badge=latest)
@@ -19,6 +20,7 @@ A Django app for passwordless authentication using magic links.
19
20
  - Privacy-focused IP anonymization
20
21
  - Customizable templates for emails and pages
21
22
  - Compatible with Django's authentication system
23
+ - Enforces unique email addresses for users (case-insensitive)
22
24
 
23
25
  ## Installation
24
26
 
@@ -83,19 +85,19 @@ DEFAULT_FROM_EMAIL = 'your-email@example.com'
83
85
 
84
86
  django-solomon provides several settings that you can customize in your Django settings file:
85
87
 
86
- | Setting | Default | Description |
87
- |---------------------------------------|-------------------------------------------------|----------------------------------------------------------------------------------------|
88
- | `SOLOMON_LINK_EXPIRATION` | `300` | The expiration time for magic links in seconds |
89
- | `SOLOMON_ONLY_ONE_LINK_ALLOWED` | `True` | If enabled, only one active magic link is allowed per user |
90
- | `SOLOMON_CREATE_USER_IF_NOT_FOUND` | `False` | If enabled, creates a new user when a magic link is requested for a non-existent email |
91
- | `SOLOMON_LOGIN_REDIRECT_URL` | `settings.LOGIN_REDIRECT_URL` | The URL to redirect to after successful authentication |
92
- | `SOLOMON_ALLOW_ADMIN_LOGIN` | `True` | If enabled, allows superusers to log in using magic links |
93
- | `SOLOMON_ALLOW_STAFF_LOGIN` | `True` | If enabled, allows staff users to log in using magic links |
94
- | `SOLOMON_MAIL_TEXT_TEMPLATE` | `"django_solomon/email/magic_link.txt"` | The template to use for plain text magic link emails |
95
- | `SOLOMON_MAIL_MJML_TEMPLATE` | `"django_solomon/email/magic_link.mjml"` | The template to use for HTML magic link emails (MJML format) |
96
- | `SOLOMON_LOGIN_FORM_TEMPLATE` | `"django_solomon/base/login_form.html"` | The template to use for the login form page |
97
- | `SOLOMON_INVALID_MAGIC_LINK_TEMPLATE` | `"django_solomon/base/invalid_magic_link.html"` | The template to use for the invalid magic link page |
98
- | `SOLOMON_MAGIC_LINK_SENT_TEMPLATE` | `"django_solomon/base/magic_link_sent.html"` | The template to use for the magic link sent confirmation page |
88
+ | Setting | Default | Description |
89
+ |---------------------------------------|-------------------------------------------------|-----------------------------------------------------------------------------------------|
90
+ | `SOLOMON_LINK_EXPIRATION` | `300` | The expiration time for magic links in seconds |
91
+ | `SOLOMON_ONLY_ONE_LINK_ALLOWED` | `True` | If enabled, only one active magic link is allowed per user |
92
+ | `SOLOMON_CREATE_USER_IF_NOT_FOUND` | `False` | If enabled, creates a new user when a magic link is requested for a non-existent email |
93
+ | `SOLOMON_LOGIN_REDIRECT_URL` | `settings.LOGIN_REDIRECT_URL` | The URL to redirect to after successful authentication |
94
+ | `SOLOMON_ALLOW_ADMIN_LOGIN` | `True` | If enabled, allows superusers to log in using magic links |
95
+ | `SOLOMON_ALLOW_STAFF_LOGIN` | `True` | If enabled, allows staff users to log in using magic links |
96
+ | `SOLOMON_MAIL_TEXT_TEMPLATE` | `"django_solomon/email/magic_link.txt"` | The template to use for plain text magic link emails |
97
+ | `SOLOMON_MAIL_MJML_TEMPLATE` | `"django_solomon/email/magic_link.mjml"` | The template to use for HTML magic link emails (MJML format) |
98
+ | `SOLOMON_LOGIN_FORM_TEMPLATE` | `"django_solomon/base/login_form.html"` | The template to use for the login form page |
99
+ | `SOLOMON_INVALID_MAGIC_LINK_TEMPLATE` | `"django_solomon/base/invalid_magic_link.html"` | The template to use for the invalid magic link page |
100
+ | `SOLOMON_MAGIC_LINK_SENT_TEMPLATE` | `"django_solomon/base/magic_link_sent.html"` | The template to use for the magic link sent confirmation page |
99
101
  | `SOLOMON_ENFORCE_SAME_IP` | `False` | If enabled, validates that magic links are used from the same IP they were created from |
100
102
  | `SOLOMON_ANONYMIZE_IP` | `True` | If enabled, anonymizes IP addresses before storing them (removes last octet for IPv4) |
101
103
 
@@ -33,6 +33,7 @@ dependencies = [
33
33
  Home = "https://django-solomon.rtfd.io/"
34
34
  Documentation = "https://django-solomon.rtfd.io/"
35
35
  Repository = "https://codeberg.org/oliverandrich/django-solomon"
36
+ "Issue Tracker" = "https://codeberg.org/oliverandrich/django-solomon/issues"
36
37
 
37
38
  [dependency-groups]
38
39
  dev = [
@@ -0,0 +1,43 @@
1
+ from django.db import migrations, models
2
+ from django.db.models.functions import Lower
3
+
4
+
5
+ # Copyright (c) 2023 Carlton Gibson
6
+ # This migration is taken from the fantastic project `django-unique-user-email` created by Carlton Gibson.
7
+ # https://github.com/carltongibson/django-unique-user-email/
8
+
9
+
10
+ class CustomAddConstraint(migrations.AddConstraint):
11
+ """
12
+ Override app_label to target auth.User
13
+ """
14
+
15
+ def state_forwards(self, app_label, state):
16
+ state.add_constraint("auth", self.model_name_lower, self.constraint)
17
+
18
+ def database_forwards(self, app_label, schema_editor, from_state, to_state):
19
+ model = to_state.apps.get_model("auth", self.model_name)
20
+ if self.allow_migrate_model(schema_editor.connection.alias, model):
21
+ schema_editor.add_constraint(model, self.constraint)
22
+
23
+ def database_backwards(self, app_label, schema_editor, from_state, to_state):
24
+ model = to_state.apps.get_model("auth", self.model_name)
25
+ if self.allow_migrate_model(schema_editor.connection.alias, model):
26
+ schema_editor.remove_constraint(model, self.constraint)
27
+
28
+
29
+ class Migration(migrations.Migration):
30
+ dependencies = [
31
+ ("django_solomon", "0002_magiclink_ip_address"),
32
+ ("auth", "0012_alter_user_first_name_max_length"),
33
+ ]
34
+
35
+ operations = [
36
+ CustomAddConstraint(
37
+ model_name="user",
38
+ constraint=models.UniqueConstraint(
39
+ Lower("email"),
40
+ name="unique_user_email"
41
+ ),
42
+ ),
43
+ ]
@@ -1,8 +1,10 @@
1
+ from datetime import timedelta
2
+
1
3
  import pytest
4
+ from django.contrib.auth import get_user_model
2
5
  from django.db import IntegrityError
3
- from django.utils import timezone
4
6
  from django.test import override_settings
5
- from datetime import timedelta
7
+ from django.utils import timezone
6
8
 
7
9
  from django_solomon.models import BlacklistedEmail, MagicLink
8
10
 
@@ -214,3 +216,48 @@ class TestMagicLink:
214
216
  link.refresh_from_db()
215
217
  assert link.token == "custom-token"
216
218
  assert abs((link.expires_at - expiration).total_seconds()) < 1 # Allow for small time differences
219
+
220
+
221
+ @pytest.mark.django_db
222
+ class TestUserEmailUniqueConstraint:
223
+ """Tests for the unique constraint on auth.User email field."""
224
+
225
+ def test_user_email_unique_constraint(self, user):
226
+ """Test that the email field on auth.User has a unique constraint."""
227
+ User = get_user_model()
228
+
229
+ # The user fixture already creates a user with email "test@example.com"
230
+ # Attempting to create another user with the same email should raise an IntegrityError
231
+ with pytest.raises(IntegrityError):
232
+ User.objects.create_user(
233
+ username="another_user",
234
+ email="test@example.com", # Same email as the fixture user
235
+ password="password456",
236
+ )
237
+
238
+ def test_user_email_unique_constraint_case_insensitive(self, user):
239
+ """Test that the email uniqueness is case-insensitive."""
240
+ User = get_user_model()
241
+
242
+ # The user fixture already creates a user with email "test@example.com"
243
+ # Attempting to create another user with the same email in different case should fail
244
+ # because the constraint is case-insensitive
245
+ with pytest.raises(IntegrityError):
246
+ User.objects.create_user(
247
+ username="another_user",
248
+ email="TEST@example.com", # Same email as the fixture user but with different case
249
+ password="password456",
250
+ )
251
+
252
+ def test_different_emails_allowed(self, user):
253
+ """Test that users with different emails can be created."""
254
+ User = get_user_model()
255
+
256
+ # Create a user with a different email
257
+ new_user = User.objects.create_user(
258
+ username="another_user", email="different@example.com", password="password456"
259
+ )
260
+
261
+ # Verify the user was created
262
+ assert new_user.pk is not None
263
+ assert new_user.email == "different@example.com"
File without changes
File without changes
File without changes
File without changes