dj-tinyuser 0.0.1__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.
Files changed (97) hide show
  1. dj_tinyuser/__init__.py +0 -0
  2. dj_tinyuser/account_adapter.py +90 -0
  3. dj_tinyuser/admin.py +31 -0
  4. dj_tinyuser/api/__init__.py +1 -0
  5. dj_tinyuser/api/apps.py +9 -0
  6. dj_tinyuser/api/locale/de/LC_MESSAGES/django.po +43 -0
  7. dj_tinyuser/api/permissions.py +24 -0
  8. dj_tinyuser/api/serializers.py +208 -0
  9. dj_tinyuser/api/tests/__init__.py +1 -0
  10. dj_tinyuser/api/tests/test_invitations_api.py +182 -0
  11. dj_tinyuser/api/tests/test_profile_api.py +1 -0
  12. dj_tinyuser/api/tests/test_user_api.py +261 -0
  13. dj_tinyuser/api/urls.py +19 -0
  14. dj_tinyuser/api/views.py +65 -0
  15. dj_tinyuser/apps.py +7 -0
  16. dj_tinyuser/context_processors.py +31 -0
  17. dj_tinyuser/exceptions.py +6 -0
  18. dj_tinyuser/forms.py +96 -0
  19. dj_tinyuser/global_templates/__init__.py +3 -0
  20. dj_tinyuser/global_templates/account/login.html +1 -0
  21. dj_tinyuser/global_templates/account/password_reset.html +1 -0
  22. dj_tinyuser/global_templates/account/signup.html +1 -0
  23. dj_tinyuser/global_templates/allauth/layouts/base.html +1 -0
  24. dj_tinyuser/input_files/style.scss +27 -0
  25. dj_tinyuser/locale/de/LC_MESSAGES/django.po +880 -0
  26. dj_tinyuser/logging.py +11 -0
  27. dj_tinyuser/managers.py +64 -0
  28. dj_tinyuser/migrations/0001_initial.py +331 -0
  29. dj_tinyuser/migrations/__init__.py +0 -0
  30. dj_tinyuser/mixins/htmx.py +20 -0
  31. dj_tinyuser/models/__init__.py +11 -0
  32. dj_tinyuser/models/fields.py +58 -0
  33. dj_tinyuser/models/tinyuser.py +125 -0
  34. dj_tinyuser/models/userprofile.py +216 -0
  35. dj_tinyuser/settings.py +72 -0
  36. dj_tinyuser/sidebar.py +55 -0
  37. dj_tinyuser/static/tinyuser/css/html.css +28 -0
  38. dj_tinyuser/templates/tinyuser/bootstrap5/account/login.dj.html +68 -0
  39. dj_tinyuser/templates/tinyuser/bootstrap5/account/password_reset.dj.html +19 -0
  40. dj_tinyuser/templates/tinyuser/bootstrap5/account/signup.dj.html +24 -0
  41. dj_tinyuser/templates/tinyuser/bootstrap5/base.dj.html +31 -0
  42. dj_tinyuser/templates/tinyuser/bootstrap5/index.html +10 -0
  43. dj_tinyuser/templates/tinyuser/bootstrap5/invite/failed.html +9 -0
  44. dj_tinyuser/templates/tinyuser/bootstrap5/invite/hx/failed.html +14 -0
  45. dj_tinyuser/templates/tinyuser/bootstrap5/invite/hx/invite.html +26 -0
  46. dj_tinyuser/templates/tinyuser/bootstrap5/invite/hx/success.html +14 -0
  47. dj_tinyuser/templates/tinyuser/bootstrap5/invite/invite.html +23 -0
  48. dj_tinyuser/templates/tinyuser/bootstrap5/invite/success.html +9 -0
  49. dj_tinyuser/templates/tinyuser/bootstrap5/profile/edit.html +10 -0
  50. dj_tinyuser/templates/tinyuser/bootstrap5/profile/hx/edit.html +151 -0
  51. dj_tinyuser/templates/tinyuser/bootstrap5/profile/hx/profile.html +120 -0
  52. dj_tinyuser/templates/tinyuser/bootstrap5/profile/hx/settings.html +27 -0
  53. dj_tinyuser/templates/tinyuser/bootstrap5/profile/profile.html +46 -0
  54. dj_tinyuser/templates/tinyuser/bootstrap5/profile/settings.html +18 -0
  55. dj_tinyuser/templates/tinyuser/bootstrap5/snippets/account/form +9 -0
  56. dj_tinyuser/templates/tinyuser/bs5docs/account/login.dj.html +66 -0
  57. dj_tinyuser/templates/tinyuser/bs5docs/account/signup.dj.html +26 -0
  58. dj_tinyuser/templates/tinyuser/html/account/login.dj.html +58 -0
  59. dj_tinyuser/templates/tinyuser/html/account/password_reset.dj.html +20 -0
  60. dj_tinyuser/templates/tinyuser/html/account/signup.dj.html +22 -0
  61. dj_tinyuser/templates/tinyuser/html/base.dj.html +27 -0
  62. dj_tinyuser/templates/tinyuser/html/index.html +7 -0
  63. dj_tinyuser/templates/tinyuser/html/invite/failed.html +9 -0
  64. dj_tinyuser/templates/tinyuser/html/invite/hx/failed.html +0 -0
  65. dj_tinyuser/templates/tinyuser/html/invite/hx/invite.html +0 -0
  66. dj_tinyuser/templates/tinyuser/html/invite/hx/success.html +0 -0
  67. dj_tinyuser/templates/tinyuser/html/invite/invite.html +0 -0
  68. dj_tinyuser/templates/tinyuser/html/invite/success.html +0 -0
  69. dj_tinyuser/templates/tinyuser/html/logout/navbar.dj.html +5 -0
  70. dj_tinyuser/templates/tinyuser/html/profile/edit.html +0 -0
  71. dj_tinyuser/templates/tinyuser/html/profile/hx/edit.html +0 -0
  72. dj_tinyuser/templates/tinyuser/html/profile/hx/profile.html +0 -0
  73. dj_tinyuser/templates/tinyuser/html/profile/hx/settings.html +0 -0
  74. dj_tinyuser/templates/tinyuser/html/profile/profile.html +0 -0
  75. dj_tinyuser/templates/tinyuser/html/profile/settings.html +0 -0
  76. dj_tinyuser/templates/tinyuser/html/theme/nav.dj.html +20 -0
  77. dj_tinyuser/templates.json +39 -0
  78. dj_tinyuser/templates.yml +23 -0
  79. dj_tinyuser/templatetags/__init__.py +0 -0
  80. dj_tinyuser/templatetags/tinyuser.py +14 -0
  81. dj_tinyuser/tests/__init__.py +0 -0
  82. dj_tinyuser/tests/test_tinyuser_model.py +46 -0
  83. dj_tinyuser/tests/test_userprofile_model.py +56 -0
  84. dj_tinyuser/urls.py +22 -0
  85. dj_tinyuser/utils.py +18 -0
  86. dj_tinyuser/views/__init__.py +12 -0
  87. dj_tinyuser/views/dashboard.py +0 -0
  88. dj_tinyuser/views/index.py +15 -0
  89. dj_tinyuser/views/invite.py +141 -0
  90. dj_tinyuser/views/profile.py +470 -0
  91. dj_tinyuser-0.0.1.dist-info/METADATA +194 -0
  92. dj_tinyuser-0.0.1.dist-info/RECORD +97 -0
  93. dj_tinyuser-0.0.1.dist-info/WHEEL +4 -0
  94. dj_tinyuser-0.0.1.dist-info/licenses/LICENSE +12 -0
  95. dj_tinyuser-0.0.1.dist-info/licenses/LICENSE.django-project +12 -0
  96. dj_tinyuser-0.0.1.dist-info/licenses/LICENSE.django-project.md +15 -0
  97. dj_tinyuser-0.0.1.dist-info/licenses/LICENSE.md +15 -0
@@ -0,0 +1,261 @@
1
+ """Tests for the tinyuser API."""
2
+
3
+ from django.contrib.auth import get_user_model
4
+ from django.test import TestCase
5
+ from django.urls import reverse
6
+ from rest_framework import status
7
+ from rest_framework.test import APIClient
8
+
9
+ CREATE_USER_URL = reverse("tinyuser.api:user-create")
10
+ TOKEN_URL = reverse("tinyuser.api:token")
11
+ ME_URL = reverse("tinyuser.api:me")
12
+
13
+
14
+ def create_user(**params):
15
+ """Create a new user with given parameters."""
16
+ return get_user_model().objects.create_user(**params)
17
+
18
+
19
+ class PublicUserApiTests(TestCase):
20
+ """Tests the public features of the tinyuser user API."""
21
+
22
+ def setUp(self):
23
+ """Set up PublicUserApiTests."""
24
+ self.client = APIClient()
25
+ self.user_model = get_user_model()
26
+
27
+ def test_create_user_success(self):
28
+ """Test creating a user is successful."""
29
+ payload = {
30
+ "email": "test@example.com",
31
+ "password": "testpass123",
32
+ "username": "TestUser",
33
+ }
34
+ res = self.client.post(CREATE_USER_URL, payload, format="json")
35
+
36
+ self.assertEqual(res.status_code, status.HTTP_201_CREATED)
37
+ self.assertNotIn("password", res.data)
38
+
39
+ user = get_user_model().objects.get(email=payload["email"])
40
+
41
+ self.assertTrue(user.check_password(payload["password"]))
42
+
43
+ def test_create_user_with_existing_email_error(self):
44
+ """Test creating a user with an existing email fails."""
45
+ payload1 = {
46
+ "email": "test@example.com",
47
+ "password": "testpass123",
48
+ "username": "TestUser",
49
+ }
50
+ payload2 = {
51
+ "email": "test@example.com",
52
+ "password": "testpass456",
53
+ "username": "AnotherUser",
54
+ }
55
+
56
+ create_user(**payload1)
57
+ res = self.client.post(CREATE_USER_URL, payload2)
58
+
59
+ self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
60
+
61
+ def test_create_user_with_existing_username_error(self):
62
+ """Test creating a user with an existing username fails."""
63
+ payload1 = {
64
+ "email": "test@example.com",
65
+ "password": "testpass123",
66
+ "username": "TestUser",
67
+ }
68
+ payload2 = {
69
+ "email": "test2@example.com",
70
+ "password": "testpass456",
71
+ "username": "TestUser",
72
+ }
73
+
74
+ create_user(**payload1)
75
+ res = self.client.post(CREATE_USER_URL, payload2)
76
+
77
+ self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
78
+
79
+ def test_create_user_with_short_password_error(self):
80
+ """Test creating a user with a short password fails."""
81
+ payload = {
82
+ "email": "test@example.com",
83
+ "password": "pw",
84
+ "username": "TestUser",
85
+ }
86
+
87
+ res = self.client.post(CREATE_USER_URL, payload)
88
+ self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
89
+
90
+ email_exists = get_user_model().objects.filter(email=payload["email"]).exists()
91
+ self.assertFalse(email_exists)
92
+
93
+ def test_create_user_with_missing_fields_error(self):
94
+ """Test creating a user with missing fields fails."""
95
+ payload1 = {
96
+ "email": "test@xeample.com",
97
+ "password": "testpass123",
98
+ }
99
+ payload2 = {
100
+ "username": "TestUser",
101
+ "password": "testpass123",
102
+ }
103
+ res1 = self.client.post(CREATE_USER_URL, payload1)
104
+ res2 = self.client.post(CREATE_USER_URL, payload2)
105
+
106
+ self.assertEqual(res1.status_code, status.HTTP_400_BAD_REQUEST)
107
+ self.assertEqual(res2.status_code, status.HTTP_400_BAD_REQUEST)
108
+
109
+ def test_create_user_with_invalid_email_error(self):
110
+ """Test creating a user with an invalid email fails."""
111
+ payload = {
112
+ "email": "invalid-email",
113
+ "password": "testpass123",
114
+ "username": "TestUser",
115
+ }
116
+
117
+ res = self.client.post(CREATE_USER_URL, payload)
118
+
119
+ self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
120
+
121
+ def test_authentication_required_for_me_endpoint(self):
122
+ """Test that authentication is required for the me endpoint."""
123
+ res = self.client.get(ME_URL)
124
+
125
+ self.assertEqual(res.status_code, status.HTTP_401_UNAUTHORIZED)
126
+
127
+
128
+ class PrivateUserApiTests(TestCase):
129
+ """Tests the private features of the tinyuser user API."""
130
+
131
+ def setUp(self):
132
+ """Set up PrivateUserApiTests."""
133
+ self.user = create_user(
134
+ email="test@example.com",
135
+ password="testpass123",
136
+ username="TestUser",
137
+ )
138
+ self.client = APIClient()
139
+ self.client.force_authenticate(user=self.user)
140
+
141
+ def test_retrieve_user_profile_success(self):
142
+ """Test retrieving profile for logged in user."""
143
+ res = self.client.get(ME_URL)
144
+
145
+ self.assertEqual(res.status_code, status.HTTP_200_OK)
146
+ self.assertEqual(res.data["email"], self.user.email)
147
+ self.assertEqual(res.data["username"], self.user.username)
148
+ self.assertNotIn("password", res.data)
149
+
150
+ def test_post_me_not_allowed(self):
151
+ """Test that POST is not allowed on the me endpoint."""
152
+ res = self.client.post(ME_URL, {})
153
+ self.assertEqual(res.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
154
+
155
+ def test_patch_user_success(self):
156
+ """Test updating the user data for authenticated user."""
157
+ payload = {
158
+ "password": "newPassword123",
159
+ "username": "NewUsername",
160
+ }
161
+
162
+ res = self.client.patch(ME_URL, payload)
163
+ self.assertEqual(res.status_code, status.HTTP_200_OK)
164
+
165
+ self.user.refresh_from_db()
166
+
167
+ self.assertEqual(self.user.username, payload["username"])
168
+ # Password should not be updated here since it's not included in the response data
169
+ self.assertTrue(self.user.check_password(payload["password"]))
170
+
171
+ self.assertEqual(res.data["email"], self.user.email)
172
+ self.assertEqual(res.data["username"], self.user.username)
173
+ self.assertNotIn("password", res.data)
174
+
175
+ def test_patch_user_update_email_not_allowed(self):
176
+ """Test that updating the email is not allowed."""
177
+ payload = {"email": "new.email@example.com"}
178
+ res = self.client.patch(ME_URL, payload)
179
+ self.user.refresh_from_db()
180
+ self.assertEqual(self.user.email, "test@example.com")
181
+ self.assertEqual(res.status_code, status.HTTP_200_OK)
182
+
183
+ def test_patch_user_update_username_allowed(self):
184
+ """Test that updating the username is allowed."""
185
+ payload = {"username": "NewUsername"}
186
+ res = self.client.patch(ME_URL, payload)
187
+ self.user.refresh_from_db()
188
+ self.assertEqual(res.status_code, status.HTTP_200_OK)
189
+ self.assertEqual(res.data["username"], "NewUsername")
190
+
191
+
192
+ class TokenAPITests(TestCase):
193
+ """Tests for the user authentication token API."""
194
+
195
+ def setUp(self):
196
+ """Set up TokenAPITests."""
197
+ self.client = APIClient()
198
+ self.user_model = get_user_model()
199
+
200
+ def test_create_token_for_user_valid_credentials(self):
201
+ """Test that a token is created for the user with valid credentials."""
202
+ payload = {
203
+ "email": "test@example.com",
204
+ "password": "testpass123",
205
+ }
206
+ create_user(email=payload["email"], password=payload["password"], username="TestUser")
207
+ res = self.client.post(TOKEN_URL, payload)
208
+
209
+ self.assertIn("token", res.data)
210
+ self.assertEqual(res.status_code, status.HTTP_200_OK)
211
+
212
+ def test_create_token_for_user_invalid_credentials(self):
213
+ """Test that a token is not created if invalid credentials are given."""
214
+ payload = {
215
+ "email": "test@example.com",
216
+ "password": "testpass123",
217
+ }
218
+ create_user(email=payload["email"], password=payload["password"], username="TestUser")
219
+
220
+ payload["password"] = "wrongpass"
221
+ res = self.client.post(TOKEN_URL, payload)
222
+ self.assertNotIn("token", res.data)
223
+ self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
224
+
225
+ payload["email"] = "nonexistent@example.com"
226
+ res = self.client.post(TOKEN_URL, payload)
227
+ self.assertNotIn("token", res.data)
228
+ self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
229
+
230
+ def test_create_token_for_user_missing_fields(self):
231
+ """Test that email and password are required to create a token."""
232
+ user_data = {
233
+ "email": "test@example.com",
234
+ "password": "testpass123",
235
+ "username": "TestUser",
236
+ }
237
+ create_user(**user_data)
238
+
239
+ res = self.client.post(TOKEN_URL, {"email": "", "password": ""})
240
+ self.assertNotIn("token", res.data)
241
+ self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
242
+
243
+ res = self.client.post(TOKEN_URL, {"email": user_data["email"], "password": ""})
244
+ self.assertNotIn("token", res.data)
245
+ self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
246
+
247
+ res = self.client.post(TOKEN_URL, {"email": "", "password": user_data["password"]})
248
+ self.assertNotIn("token", res.data)
249
+ self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
250
+
251
+ res = self.client.post(TOKEN_URL, {"password": user_data["password"]})
252
+ self.assertNotIn("token", res.data)
253
+ self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
254
+
255
+ res = self.client.post(TOKEN_URL, {"email": user_data["email"]})
256
+ self.assertNotIn("token", res.data)
257
+ self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
258
+
259
+ res = self.client.post(TOKEN_URL, {})
260
+ self.assertNotIn("token", res.data)
261
+ self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
@@ -0,0 +1,19 @@
1
+ from django.urls import include, path
2
+ from rest_framework.routers import DefaultRouter
3
+
4
+ from . import views
5
+
6
+ app_name = "tinyuser.api"
7
+
8
+ router = DefaultRouter()
9
+ # You can register your viewsets here, e.g.:
10
+ # from .views import UserViewSet
11
+ # router.register(r'users', UserViewSet)
12
+
13
+ urlpatterns = [
14
+ path("", include(router.urls)),
15
+ path("accounts/create/", views.CreateUserView.as_view(), name="user-create"),
16
+ path("accounts/me/", views.ManageUserView.as_view(), name="me"),
17
+ path("accounts/token/", views.CreateTokenView.as_view(), name="token"),
18
+ path("accounts/invite/", views.InviteView.as_view(), name="invite"),
19
+ ]
@@ -0,0 +1,65 @@
1
+ """Views for the user API."""
2
+
3
+ from rest_framework import authentication, generics, permissions
4
+ from rest_framework.authtoken.views import ObtainAuthToken
5
+ from rest_framework.exceptions import PermissionDenied
6
+ from rest_framework.settings import api_settings
7
+
8
+ from dj_tinyuser import settings
9
+ from dj_tinyuser.api.permissions import IsInvitor
10
+ from dj_tinyuser.api.serializers import ( # UserProfileSerializer,
11
+ AuthTokenSerializer,
12
+ InvitationSerializer,
13
+ SafeUserSerializer,
14
+ UserSerializer,
15
+ )
16
+
17
+ # from django.contrib.auth import get_user_model
18
+
19
+
20
+ class CreateUserView(generics.CreateAPIView):
21
+ """Create a new user in the system."""
22
+
23
+ #: serializer_class: The serializer class to use for validating
24
+ #: and deserializing input, and for serializing output.
25
+ serializer_class = UserSerializer
26
+
27
+ #: authentication_classes: The authentication classes to use for
28
+ #: this view. In this case, no authentication is required to create a user.
29
+ permission_classes = (permissions.AllowAny,)
30
+
31
+ def perform_create(self, serializer):
32
+ """Override the default create behavior to use the create_user method."""
33
+ if not settings.ALLOW_SIGNUP:
34
+ raise PermissionDenied("Signup is not allowed.")
35
+
36
+ super().perform_create(serializer)
37
+ serializer.save()
38
+
39
+
40
+ class ManageUserView(generics.RetrieveUpdateDestroyAPIView):
41
+ """Manage the authenticated user."""
42
+
43
+ serializer_class = SafeUserSerializer
44
+ authentication_classes = (authentication.TokenAuthentication,)
45
+ permission_classes = (permissions.IsAuthenticated,)
46
+
47
+ def get_object(self):
48
+ """Retrieve and return the authenticated user."""
49
+ return self.request.user
50
+
51
+
52
+ class CreateTokenView(ObtainAuthToken):
53
+ """Create a new auth token for user."""
54
+
55
+ serializer_class = AuthTokenSerializer
56
+ renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
57
+ permission_classes = (permissions.AllowAny,)
58
+
59
+
60
+ class InviteView(generics.CreateAPIView):
61
+ """Invite a new user to the system."""
62
+
63
+ serializer_class = InvitationSerializer
64
+ renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
65
+ permission_classes = (IsInvitor,)
dj_tinyuser/apps.py ADDED
@@ -0,0 +1,7 @@
1
+ from django.apps import AppConfig
2
+ from django.utils.translation import gettext_lazy as _
3
+
4
+
5
+ class DjangoTinyuserConfig(AppConfig):
6
+ name = "dj_tinyuser"
7
+ verbose_name = _("TinyUser")
@@ -0,0 +1,31 @@
1
+ from dj_templates.theme import ThemeManager
2
+ from django.http import HttpRequest
3
+
4
+ # from django.utils.translation import gettext as _
5
+ from dj_tinyuser import settings
6
+
7
+
8
+ def tinyuser(request: HttpRequest):
9
+
10
+ theme = ThemeManager.get_global().get_theme(request=request)
11
+
12
+ try:
13
+ base_template = theme.get_template("base_template")
14
+ except LookupError:
15
+ base_template = theme.get_template("theme/base")
16
+
17
+ tinyuser_templates = {
18
+ "base_template": base_template,
19
+ "login_template": theme.get_template("tinyuser/account/login"),
20
+ "password_reset_template": theme.get_template("tinyuser/account/password_reset"),
21
+ "profile_template": theme.get_template("tinyuser/account/profile"),
22
+ "settings_template": theme.get_template("tinyuser/account/settings"),
23
+ "signup_template": theme.get_template("tinyuser/account/signup"),
24
+ }
25
+
26
+ return {
27
+ "socialaccount_enabled": settings.SOCIALACCOUNT_ENABLED,
28
+ "can_sign_up": settings.ALLOW_SIGNUP,
29
+ "invite_allow_all_authenticated_users": settings.INVITE_ALLOW_ALL_AUTHENTICATED_USERS,
30
+ "tinyuser_templates": tinyuser_templates,
31
+ }
@@ -0,0 +1,6 @@
1
+ class FriendshipPending(Exception):
2
+ pass
3
+
4
+
5
+ class FriendshipAlreadyExists(Exception):
6
+ pass
dj_tinyuser/forms.py ADDED
@@ -0,0 +1,96 @@
1
+ from django import forms
2
+ from django.core.files.images import ImageFile
3
+ from django.core.files.uploadedfile import UploadedFile
4
+
5
+ from dj_tinyuser.models import UserProfile, UserProfileSettings
6
+
7
+
8
+ class ProfileForm(forms.ModelForm):
9
+ class Meta:
10
+ model = UserProfile
11
+ fields = [
12
+ "first_name",
13
+ "last_name",
14
+ "location",
15
+ "birth_date",
16
+ "website",
17
+ "mastodon_url",
18
+ "bio",
19
+ ]
20
+
21
+ def __init__(self, *args, **kwargs):
22
+ avatar_small = kwargs.pop("avatar_small", None)
23
+ avatar_medium = kwargs.pop("avatar_medium", None)
24
+ avatar_large = kwargs.pop("avatar_large", None)
25
+ avatar_full = kwargs.pop("avatar_full", None)
26
+
27
+ super().__init__(*args, **kwargs)
28
+
29
+ self.fields["avatar_small"] = forms.ImageField(
30
+ label="Avatar (Small)",
31
+ required=False,
32
+ widget=forms.ClearableFileInput(attrs={"accept": "image/*"}),
33
+ initial=avatar_small,
34
+ )
35
+
36
+ self.fields["avatar_medium"] = forms.ImageField(
37
+ label="Avatar (Medium)",
38
+ required=False,
39
+ widget=forms.ClearableFileInput(attrs={"accept": "image/*"}),
40
+ initial=avatar_medium,
41
+ )
42
+
43
+ self.fields["avatar_large"] = forms.ImageField(
44
+ label="Avatar (Large)",
45
+ required=False,
46
+ widget=forms.ClearableFileInput(attrs={"accept": "image/*"}),
47
+ initial=avatar_large,
48
+ )
49
+
50
+ self.fields["avatar_full"] = forms.ImageField(
51
+ label="Avatar (Full)",
52
+ required=False,
53
+ widget=forms.ClearableFileInput(attrs={"accept": "image/*"}),
54
+ initial=avatar_full,
55
+ )
56
+
57
+ def clean_avatar_small(self):
58
+ "print the uploaded file for debugging purposes"
59
+ avatar = self.cleaned_data.get("avatar_small")
60
+ if avatar and isinstance(avatar, UploadedFile):
61
+ if avatar.content_type not in ["image/jpeg", "image/png"]:
62
+ raise forms.ValidationError("Only JPEG and PNG images are allowed.")
63
+ if avatar.size > 2.5 * 1024 * 1024: # Limit to 2.5MB
64
+ raise forms.ValidationError("Avatar file size must be under 2.5MB.")
65
+ elif avatar and not isinstance(avatar, ImageFile):
66
+ raise forms.ValidationError("Invalid file type for avatar.")
67
+
68
+ return avatar
69
+
70
+
71
+ class ProfileSettingsForm(forms.ModelForm):
72
+ class Meta:
73
+ model = UserProfileSettings
74
+ fields = [
75
+ "is_enabled",
76
+ "key",
77
+ "show_avatar",
78
+ "show_email",
79
+ "show_full_name",
80
+ "show_birth_date",
81
+ "show_location",
82
+ "show_bio",
83
+ "show_mastodon_url",
84
+ "show_website",
85
+ ]
86
+
87
+
88
+ class InviteForm(forms.Form):
89
+ email = forms.EmailField(label="Email", max_length=255)
90
+
91
+ def clean_email(self):
92
+ email = self.cleaned_data["email"]
93
+ # Check if the email is already associated with an existing user
94
+ if UserProfile.objects.filter(user__email=email).exists():
95
+ raise forms.ValidationError("A user with this email already exists.")
96
+ return email
@@ -0,0 +1,3 @@
1
+ from pathlib import Path
2
+
3
+ TEMPLATES_PATH = Path(__file__).resolve().parent
@@ -0,0 +1 @@
1
+ {% extends tinyuser_templates.login_template %}
@@ -0,0 +1 @@
1
+ {% extends tinyuser_templates.password_reset_template %}
@@ -0,0 +1 @@
1
+ {% extends tinyuser_templates.signup_template %}
@@ -0,0 +1 @@
1
+ {% extends base_template %}
@@ -0,0 +1,27 @@
1
+ @import "../../node_modules/bootstrap/scss/bootstrap";
2
+ // Update the import directory to point to it‘s location within node_modules
3
+ $bootstrap-icons-font-dir: "../../node_modules/bootstrap-icons/font/fonts";
4
+
5
+ // Import the Sass files as usual
6
+ @import "../../node_modules/bootstrap-icons/font/bootstrap-icons";
7
+
8
+ .login-card {
9
+ max-width: 400px;
10
+ background-color: $body-bg-dark;
11
+ color: $light-text-emphasis-dark;
12
+ }
13
+
14
+ .login-card .card-header {
15
+ background-color: $body-bg-dark;
16
+ color: $light-text-emphasis-dark;
17
+ text-align: center;
18
+ font-size: 1.5rem;
19
+ font-weight: bold;
20
+ }
21
+
22
+ .login-card .card-body {
23
+ padding: 2rem;
24
+ text-align: center;
25
+ color: $light-text-emphasis-dark;
26
+ background-color: $body-bg-dark;
27
+ }