django-content-studio 1.0.0b12__py3-none-any.whl → 1.0.0b14__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.
- content_studio/__init__.py +1 -1
- content_studio/contrib/__init__.py +0 -0
- content_studio/contrib/password_reset/__init__.py +1 -0
- content_studio/contrib/password_reset/apps.py +6 -0
- content_studio/contrib/password_reset/migrations/0001_initial.py +52 -0
- content_studio/contrib/password_reset/migrations/__init__.py +0 -0
- content_studio/contrib/password_reset/models.py +39 -0
- content_studio/contrib/password_reset/serializers.py +15 -0
- content_studio/contrib/password_reset/urls.py +30 -0
- content_studio/contrib/password_reset/views.py +118 -0
- content_studio/dashboard/scheduled_tasks.py +43 -0
- content_studio/locale/nl/LC_MESSAGES/django.mo +0 -0
- content_studio/locale/nl/LC_MESSAGES/django.po +49 -0
- content_studio/login_backends/username_password.py +0 -16
- content_studio/settings.py +1 -0
- content_studio/static/content_studio/assets/index.css +1 -1
- content_studio/static/content_studio/assets/index.js +92 -73
- content_studio/static/content_studio/locales/en/translation.json +25 -0
- content_studio/static/content_studio/locales/nl/translation.json +25 -0
- content_studio/templates/content_studio/index.html +7 -7
- content_studio/views.py +8 -0
- {django_content_studio-1.0.0b12.dist-info → django_content_studio-1.0.0b14.dist-info}/METADATA +1 -1
- {django_content_studio-1.0.0b12.dist-info → django_content_studio-1.0.0b14.dist-info}/RECORD +25 -13
- {django_content_studio-1.0.0b12.dist-info → django_content_studio-1.0.0b14.dist-info}/LICENSE +0 -0
- {django_content_studio-1.0.0b12.dist-info → django_content_studio-1.0.0b14.dist-info}/WHEEL +0 -0
content_studio/__init__.py
CHANGED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
default_app_config = "content_studio.contrib.apps.Config"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Generated by Django 6.0.1 on 2026-02-04 09:42
|
|
2
|
+
|
|
3
|
+
import content_studio.contrib.password_reset.models
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
initial = True
|
|
10
|
+
|
|
11
|
+
dependencies = []
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.CreateModel(
|
|
15
|
+
name="PasswordResetCode",
|
|
16
|
+
fields=[
|
|
17
|
+
(
|
|
18
|
+
"id",
|
|
19
|
+
models.BigAutoField(
|
|
20
|
+
auto_created=True,
|
|
21
|
+
primary_key=True,
|
|
22
|
+
serialize=False,
|
|
23
|
+
verbose_name="ID",
|
|
24
|
+
),
|
|
25
|
+
),
|
|
26
|
+
(
|
|
27
|
+
"email",
|
|
28
|
+
models.EmailField(
|
|
29
|
+
editable=False, max_length=255, verbose_name="Email address"
|
|
30
|
+
),
|
|
31
|
+
),
|
|
32
|
+
(
|
|
33
|
+
"code",
|
|
34
|
+
models.CharField(
|
|
35
|
+
default=content_studio.contrib.password_reset.models.generate_code,
|
|
36
|
+
editable=False,
|
|
37
|
+
max_length=6,
|
|
38
|
+
verbose_name="Code",
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
(
|
|
42
|
+
"created_at",
|
|
43
|
+
models.DateTimeField(auto_now_add=True, verbose_name="Created at"),
|
|
44
|
+
),
|
|
45
|
+
],
|
|
46
|
+
options={
|
|
47
|
+
"verbose_name": "Password reset code",
|
|
48
|
+
"verbose_name_plural": "Password reset codes",
|
|
49
|
+
"db_table": "dcs_password_reset_code",
|
|
50
|
+
},
|
|
51
|
+
),
|
|
52
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import random
|
|
3
|
+
|
|
4
|
+
from django.db import models
|
|
5
|
+
from django.utils import timezone
|
|
6
|
+
|
|
7
|
+
from content_studio.settings import cs_settings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def generate_code():
|
|
11
|
+
return "".join(str(random.randint(0, 9)) for _ in range(6))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PasswordResetCode(models.Model):
|
|
15
|
+
class Meta:
|
|
16
|
+
db_table = "dcs_password_reset_code"
|
|
17
|
+
verbose_name = "Password reset code"
|
|
18
|
+
verbose_name_plural = "Password reset codes"
|
|
19
|
+
|
|
20
|
+
email = models.EmailField(
|
|
21
|
+
max_length=255, verbose_name="Email address", editable=False
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
code = models.CharField(
|
|
25
|
+
max_length=6, verbose_name="Code", default=generate_code, editable=False
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created at")
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def expired(self):
|
|
32
|
+
return (
|
|
33
|
+
self.created_at
|
|
34
|
+
+ datetime.timedelta(minutes=cs_settings.PASSWORD_RESET_EXPIRATION_TIME)
|
|
35
|
+
< timezone.now()
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def __str__(self):
|
|
39
|
+
return self.email
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from rest_framework import serializers
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PasswordResetRequestSerializer(serializers.Serializer):
|
|
5
|
+
email = serializers.EmailField()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CodeValidationSerializer(serializers.Serializer):
|
|
9
|
+
code = serializers.CharField(max_length=6)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PasswordResetSubmissionSerializer(serializers.Serializer):
|
|
13
|
+
code = serializers.CharField(max_length=6)
|
|
14
|
+
email = serializers.EmailField()
|
|
15
|
+
password = serializers.CharField(min_length=8)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
URL configuration for cms project.
|
|
3
|
+
|
|
4
|
+
The `urlpatterns` list routes URLs to views. For more information please see:
|
|
5
|
+
https://docs.djangoproject.com/en/4.2/topics/http/urls/
|
|
6
|
+
Examples:
|
|
7
|
+
Function views
|
|
8
|
+
1. Add an import: from my_app import views
|
|
9
|
+
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
|
10
|
+
Class-based views
|
|
11
|
+
1. Add an import: from other_app.views import Home
|
|
12
|
+
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
|
13
|
+
Including another URLconf
|
|
14
|
+
1. Import the include() function: from django.urls import include, path
|
|
15
|
+
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from django.urls import path
|
|
19
|
+
|
|
20
|
+
from .views import (
|
|
21
|
+
PasswordResetRequestView,
|
|
22
|
+
PasswordResetSubmissionView,
|
|
23
|
+
CodeValidationView,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
urlpatterns = [
|
|
27
|
+
path("api/password-reset/request", PasswordResetRequestView.as_view()),
|
|
28
|
+
path("api/password-reset/submit", PasswordResetSubmissionView.as_view()),
|
|
29
|
+
path("api/password-reset/code", CodeValidationView.as_view()),
|
|
30
|
+
]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from django.contrib.auth import get_user_model
|
|
2
|
+
from django.core.mail import send_mail
|
|
3
|
+
from django.utils.translation import gettext_lazy as _
|
|
4
|
+
from rest_framework import status
|
|
5
|
+
from rest_framework.exceptions import ValidationError
|
|
6
|
+
from rest_framework.permissions import AllowAny
|
|
7
|
+
from rest_framework.response import Response
|
|
8
|
+
from rest_framework.views import APIView
|
|
9
|
+
|
|
10
|
+
from .models import PasswordResetCode
|
|
11
|
+
from .serializers import (
|
|
12
|
+
PasswordResetRequestSerializer,
|
|
13
|
+
CodeValidationSerializer,
|
|
14
|
+
PasswordResetSubmissionSerializer,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PasswordResetRequestView(APIView):
|
|
19
|
+
permission_classes = [AllowAny]
|
|
20
|
+
|
|
21
|
+
def post(self, request):
|
|
22
|
+
serializer = PasswordResetRequestSerializer(data=request.data)
|
|
23
|
+
|
|
24
|
+
serializer.is_valid(raise_exception=True)
|
|
25
|
+
|
|
26
|
+
email = serializer.validated_data["email"]
|
|
27
|
+
|
|
28
|
+
# Delete any existing codes for this email.
|
|
29
|
+
PasswordResetCode.objects.filter(email=email).delete()
|
|
30
|
+
|
|
31
|
+
reset = PasswordResetCode.objects.create(email=email)
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
self.send_email(reset)
|
|
35
|
+
except Exception as e:
|
|
36
|
+
reset.delete()
|
|
37
|
+
print(e)
|
|
38
|
+
|
|
39
|
+
return Response(status=status.HTTP_202_ACCEPTED)
|
|
40
|
+
|
|
41
|
+
def send_email(self, reset: PasswordResetCode):
|
|
42
|
+
send_mail(
|
|
43
|
+
from_email=None,
|
|
44
|
+
subject=_("Your password reset code"),
|
|
45
|
+
message=_("Your password reset code is: ") + reset.code,
|
|
46
|
+
recipient_list=[reset.email],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class CodeValidationView(APIView):
|
|
51
|
+
permission_classes = [AllowAny]
|
|
52
|
+
|
|
53
|
+
def post(self, request):
|
|
54
|
+
serializer = CodeValidationSerializer(data=request.data)
|
|
55
|
+
|
|
56
|
+
serializer.is_valid(raise_exception=True)
|
|
57
|
+
|
|
58
|
+
code = serializer.validated_data["code"]
|
|
59
|
+
|
|
60
|
+
existing = PasswordResetCode.objects.filter(code=code).first()
|
|
61
|
+
|
|
62
|
+
if not existing:
|
|
63
|
+
raise ValidationError("Invalid code.")
|
|
64
|
+
|
|
65
|
+
if existing.expired:
|
|
66
|
+
existing.delete()
|
|
67
|
+
raise ValidationError("Expired code.")
|
|
68
|
+
|
|
69
|
+
return Response(status=status.HTTP_204_NO_CONTENT)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class PasswordResetSubmissionView(APIView):
|
|
73
|
+
permission_classes = [AllowAny]
|
|
74
|
+
|
|
75
|
+
def post(self, request):
|
|
76
|
+
serializer = PasswordResetSubmissionSerializer(data=request.data)
|
|
77
|
+
|
|
78
|
+
serializer.is_valid(raise_exception=True)
|
|
79
|
+
|
|
80
|
+
code = serializer.validated_data["code"]
|
|
81
|
+
email = serializer.validated_data["email"]
|
|
82
|
+
password = serializer.validated_data["password"]
|
|
83
|
+
|
|
84
|
+
existing = PasswordResetCode.objects.filter(code=code, email=email).first()
|
|
85
|
+
|
|
86
|
+
if not existing:
|
|
87
|
+
raise ValidationError("Invalid code.")
|
|
88
|
+
|
|
89
|
+
if existing.expired:
|
|
90
|
+
existing.delete()
|
|
91
|
+
raise ValidationError("Expired code.")
|
|
92
|
+
|
|
93
|
+
user_model = get_user_model()
|
|
94
|
+
user = user_model.objects.filter(email=email).first()
|
|
95
|
+
|
|
96
|
+
if not user:
|
|
97
|
+
raise ValidationError("Invalid email.")
|
|
98
|
+
|
|
99
|
+
user.set_password(password)
|
|
100
|
+
|
|
101
|
+
existing.delete()
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
self.send_email(email)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
print(e)
|
|
107
|
+
|
|
108
|
+
return Response(status=status.HTTP_204_NO_CONTENT)
|
|
109
|
+
|
|
110
|
+
def send_email(self, email):
|
|
111
|
+
send_mail(
|
|
112
|
+
from_email=None,
|
|
113
|
+
subject=_("Your password has been reset"),
|
|
114
|
+
message=_(
|
|
115
|
+
"Your password has been reset. If you did not do this, please contact your administrator."
|
|
116
|
+
),
|
|
117
|
+
recipient_list=[email],
|
|
118
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
|
+
from rest_framework import serializers
|
|
3
|
+
|
|
4
|
+
from ..dashboard import BaseWidget
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ScheduledTaskSerializer(serializers.Serializer):
|
|
8
|
+
title = serializers.CharField()
|
|
9
|
+
description = serializers.CharField(allow_blank=True, required=False)
|
|
10
|
+
last_run_at = serializers.DateTimeField(allow_null=True)
|
|
11
|
+
next_run_at = serializers.DateTimeField(allow_null=True)
|
|
12
|
+
duration = serializers.IntegerField(allow_null=True)
|
|
13
|
+
status = serializers.ChoiceField(
|
|
14
|
+
choices=["RUNNING", "SCHEDULED", "SUCCESS", "FAILURE"]
|
|
15
|
+
)
|
|
16
|
+
error_message = serializers.CharField(
|
|
17
|
+
allow_blank=True, allow_null=True, required=False
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ScheduledTasksWidgetSerializer(serializers.Serializer):
|
|
22
|
+
title = serializers.CharField(
|
|
23
|
+
default=_("Scheduled tasks"), allow_blank=True, required=False
|
|
24
|
+
)
|
|
25
|
+
description = serializers.CharField(
|
|
26
|
+
default=_("Monitor and manage automated tasks"),
|
|
27
|
+
allow_blank=True,
|
|
28
|
+
required=False,
|
|
29
|
+
)
|
|
30
|
+
tasks = serializers.ListField(child=ScheduledTaskSerializer())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ScheduledTasksWidget(BaseWidget):
|
|
34
|
+
"""
|
|
35
|
+
Widget for showing a list of scheduled tasks.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
name = "ScheduledTasksWidget"
|
|
39
|
+
|
|
40
|
+
col_span = 2
|
|
41
|
+
|
|
42
|
+
def get_data(self, request):
|
|
43
|
+
raise NotImplementedError("You need to implement get_data for your widget.")
|
|
Binary file
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# SOME DESCRIPTIVE TITLE.
|
|
2
|
+
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
3
|
+
# This file is distributed under the same license as the PACKAGE package.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
5
|
+
#
|
|
6
|
+
#, fuzzy
|
|
7
|
+
msgid ""
|
|
8
|
+
msgstr ""
|
|
9
|
+
"Project-Id-Version: PACKAGE VERSION\n"
|
|
10
|
+
"Report-Msgid-Bugs-To: \n"
|
|
11
|
+
"POT-Creation-Date: 2026-02-04 11:15+0100\n"
|
|
12
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
13
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
15
|
+
"Language: \n"
|
|
16
|
+
"MIME-Version: 1.0\n"
|
|
17
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
18
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
19
|
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
20
|
+
|
|
21
|
+
#: contrib/password_reset/views.py:44
|
|
22
|
+
msgid "Your password reset code"
|
|
23
|
+
msgstr "Je wachtwoord-herstelcode"
|
|
24
|
+
|
|
25
|
+
#: contrib/password_reset/views.py:45
|
|
26
|
+
msgid "Your password reset code is: "
|
|
27
|
+
msgstr "Je wachtwoord-herstelcode is: "
|
|
28
|
+
|
|
29
|
+
#: contrib/password_reset/views.py:100
|
|
30
|
+
msgid "Your password has been reset"
|
|
31
|
+
msgstr "Je wachtwoord is gewijzigd"
|
|
32
|
+
|
|
33
|
+
#: contrib/password_reset/views.py:102
|
|
34
|
+
msgid ""
|
|
35
|
+
"Your password has been reset. If you did not do this, please contact your "
|
|
36
|
+
"administrator."
|
|
37
|
+
msgstr "Je wachtwoord is gewijzigd. Neem contact op met je administrator als jij dit niet hebt gedaan."
|
|
38
|
+
|
|
39
|
+
#: dashboard/scheduled_tasks.py:23
|
|
40
|
+
msgid "Scheduled tasks"
|
|
41
|
+
msgstr "Geplande taken"
|
|
42
|
+
|
|
43
|
+
#: dashboard/scheduled_tasks.py:26
|
|
44
|
+
msgid "Monitor and manage automated tasks"
|
|
45
|
+
msgstr "Monitor en beheer geautomatiseerde taken"
|
|
46
|
+
|
|
47
|
+
#: views.py:188
|
|
48
|
+
msgid "Content"
|
|
49
|
+
msgstr "Content"
|
|
@@ -64,19 +64,3 @@ class UsernamePasswordBackend:
|
|
|
64
64
|
Returns the user if successful, None otherwise.
|
|
65
65
|
"""
|
|
66
66
|
return authenticate(username=username, password=password)
|
|
67
|
-
|
|
68
|
-
def request_password_reset(self, username):
|
|
69
|
-
"""
|
|
70
|
-
Sends a password reset email.
|
|
71
|
-
"""
|
|
72
|
-
raise NotImplemented(
|
|
73
|
-
"You need to implement a method for sending a password reset token."
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
def complete_password_reset(self, reset_token, new_password):
|
|
77
|
-
"""
|
|
78
|
-
Sets the new password based on the reset token.
|
|
79
|
-
"""
|
|
80
|
-
raise NotImplemented(
|
|
81
|
-
"You need to implement a method for validating a reset token and setting a new password."
|
|
82
|
-
)
|