Skip to content

Commit f471f18

Browse files
committed
make the last changes to make the project requirements
1 parent f336e70 commit f471f18

8 files changed

Lines changed: 159 additions & 28 deletions

File tree

db.sqlite3

0 Bytes
Binary file not shown.

todo/admin.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
from django.contrib import admin
2+
from .models import Todo
23

34
# Register your models here.
5+
admin.site.register(Todo)

todo/apps.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,13 @@
22

33

44
class TodoConfig(AppConfig):
5-
default_auto_field = 'django.db.models.BigAutoField'
6-
name = 'todo'
5+
default_auto_field = "django.db.models.BigAutoField"
6+
name = "todo"
7+
8+
def ready(self):
9+
from django.contrib.auth.models import User
10+
11+
def get_display_name(self):
12+
return self.first_name or self.username
13+
14+
User.add_to_class("__str__", get_display_name)

todo/serializers.py

Lines changed: 90 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
from rest_framework.serializers import ModelSerializer
33
from django.contrib.auth.models import User
44
from django.contrib.auth.password_validation import validate_password
5+
from django.contrib.auth import authenticate
6+
from rest_framework_simplejwt.tokens import RefreshToken
7+
from rest_framework.exceptions import AuthenticationFailed
58

69
from .models import Todo
710

@@ -15,34 +18,103 @@ class Meta:
1518
class TodoDetailSerializer(ModelSerializer):
1619
class Meta:
1720
model = Todo
18-
fields = "__all__"
21+
fields = ["id", "title", "description", "completed"]
1922

2023

21-
class RegisterSerializer(ModelSerializer):
22-
password2 = serializers.CharField(
24+
class RegisterSerializer(serializers.Serializer):
25+
# password2 = serializers.CharField(
26+
# write_only=True, required=True, style={"input_type": "password"}
27+
# )
28+
name = serializers.CharField(required=True, write_only=True)
29+
email = serializers.EmailField(required=True)
30+
password = serializers.CharField(
2331
write_only=True, required=True, style={"input_type": "password"}
2432
)
2533

26-
class Meta:
27-
model = User
28-
fields = ["username", "email", "password", "password2"]
29-
extra_kwargs = {
30-
"password": {"write_only": True, "style": {"input_type": "password"}},
31-
"email": {"required": True},
32-
"username": {"required": True},
33-
}
34+
# class Meta:
35+
# model = User
36+
# fields = [
37+
# # "name",
38+
# "email",
39+
# "password",
40+
# # "password2",
41+
# ]
42+
# extra_kwargs = {
43+
# "password": {"write_only": True, "style": {"input_type": "password"}},
44+
# "email": {"required": True},
45+
# # "name": {"required": True},
46+
# }
3447

3548
def create(self, validated_data):
36-
user = User.objects.create(
37-
username=validated_data["username"],
49+
user = User.objects.create_user(
50+
username=validated_data["email"],
3851
email=validated_data["email"],
3952
password=validated_data["password"],
53+
first_name=validated_data["name"],
4054
)
4155
return user
4256

57+
def validate_email(self, value):
58+
email_lower = value.lower()
59+
if User.objects.filter(username=email_lower).exists():
60+
raise serializers.ValidationError("A user with this email already exists.")
61+
return value
62+
63+
def to_representation(self, instance):
64+
return {
65+
"name": instance.first_name,
66+
"email": instance.email,
67+
}
68+
69+
# def validate(self, attrs):
70+
# password2 = attrs.pop("password2")
71+
# if attrs["password"] != password2:
72+
# raise serializers.ValidationError({"password": "Passwords do not match."})
73+
# validate_password(attrs["password"], user=User(username=attrs["username"]))
74+
# return attrs
75+
76+
77+
# add email serializer to make user login with email and password
78+
class EmailLoginSerializer(ModelSerializer):
79+
class Meta:
80+
model = User
81+
fields = ["email", "password"]
82+
83+
email = serializers.EmailField(required=True)
84+
password = serializers.CharField(required=True, write_only=True)
85+
4386
def validate(self, attrs):
44-
password2 = attrs.pop("password2")
45-
if attrs["password"] != password2:
46-
raise serializers.ValidationError({"password": "Passwords do not match."})
47-
validate_password(attrs["password"], user=User(username=attrs["username"]))
48-
return attrs
87+
email = attrs.get("email")
88+
password = attrs.get("password")
89+
90+
if not email or not password:
91+
raise AuthenticationFailed("Email and password are required.")
92+
93+
user = authenticate(email=email, password=password)
94+
95+
# Find the user by their email
96+
# try:
97+
# user = User.objects.get(email=email)
98+
# except User.DoesNotExist:
99+
# raise AuthenticationFailed(
100+
# "No active account found with the given credentials"
101+
# )
102+
103+
# Authentication
104+
authenticated_user = authenticate(username=email, password=password)
105+
106+
if not authenticated_user:
107+
raise AuthenticationFailed(
108+
"No active account found with the given credentials"
109+
)
110+
111+
if not authenticated_user.is_active:
112+
raise AuthenticationFailed("User account is disabled.")
113+
114+
refresh = RefreshToken.for_user(authenticated_user)
115+
116+
return {
117+
"refresh": str(refresh),
118+
"access": str(refresh.access_token),
119+
"user": authenticated_user,
120+
}

todo/urls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
urlpatterns = [
7-
path("", views.TodoListCreateAPIView.as_view(), name="todo-list"),
7+
path("todos/", views.TodoListCreateAPIView.as_view(), name="todo-list"),
88
path(
99
"todos/<int:todo_id>/",
1010
views.TodoRetrieveUpdateDestroyAPIView.as_view(),

todo/views.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,19 @@
44
from rest_framework import status, generics, permissions
55
from django_filters.rest_framework import DjangoFilterBackend
66
from django.contrib.auth.models import User
7+
from django.contrib.auth import login
8+
from rest_framework.filters import (
9+
SearchFilter,
10+
OrderingFilter,
11+
)
712

813
from .models import Todo
9-
from .serializers import TodoSerializer, TodoDetailSerializer, RegisterSerializer
14+
from .serializers import (
15+
TodoSerializer,
16+
TodoDetailSerializer,
17+
RegisterSerializer,
18+
EmailLoginSerializer,
19+
)
1020
from .filters import TodoFilter
1121

1222

@@ -16,10 +26,20 @@ class TodoListCreateAPIView(generics.ListCreateAPIView):
1626
# queryset = Todo.objects.all()
1727
serializer_class = TodoSerializer
1828
filterset_class = TodoFilter
19-
filter_backends = [DjangoFilterBackend]
2029
permission_classes = [permissions.IsAuthenticated]
30+
filter_backends = filter_backends = [
31+
DjangoFilterBackend,
32+
SearchFilter,
33+
OrderingFilter,
34+
]
35+
36+
search_fields = ["title", "description"]
37+
ordering_fields = ["date_created", "title", "completed"]
38+
ordering = ["-date_created"]
2139

2240
def get_queryset(self):
41+
if self.request.user.is_superuser:
42+
return Todo.objects.all()
2343
return Todo.objects.filter(owner=self.request.user)
2444

2545
def perform_create(self, serializer):
@@ -78,3 +98,18 @@ class RegisterView(generics.CreateAPIView):
7898
queryset = User.objects.all()
7999
serializer_class = RegisterSerializer
80100
permission_classes = [permissions.AllowAny]
101+
102+
103+
class EmailLoginView(generics.CreateAPIView):
104+
permission_classes = [permissions.AllowAny]
105+
serializer_class = EmailLoginSerializer
106+
107+
def post(self, request, *args, **kwargs):
108+
serializer = EmailLoginSerializer(data=request.data)
109+
serializer.is_valid(raise_exception=True)
110+
111+
validated_data = serializer.validated_data
112+
user = validated_data.pop("user")
113+
login(request, user)
114+
115+
return Response(serializer.validated_data, status=status.HTTP_200_OK)

todoapi/settings.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
'django.contrib.staticfiles',
4040

4141
'rest_framework',
42-
'todo',
42+
'todo.apps.TodoConfig',
4343
'django_filters',
4444
'rest_framework_simplejwt',
4545
]
@@ -139,6 +139,19 @@
139139
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
140140
"PAGE_SIZE": 10,
141141
"DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
142+
# --- THROTTLING ---
143+
"DEFAULT_THROTTLE_CLASSES": [
144+
# This one is for anonymous users (e.g., login/register)
145+
"rest_framework.throttling.AnonRateThrottle",
146+
# This one is for logged-in users
147+
"rest_framework.throttling.UserRateThrottle",
148+
],
149+
"DEFAULT_THROTTLE_RATES": {
150+
# Limit for anonymous users
151+
"anon": "20/minute",
152+
# Limit for logged-in users
153+
"user": "1000/day",
154+
},
142155
}
143156

144157
LOGIN_REDIRECT_URL = '/'

todoapi/urls.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@
1919
from django.urls import path, include
2020
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
2121

22-
from todo.views import RegisterView
22+
from todo.views import RegisterView, EmailLoginView
2323

2424
urlpatterns = [
2525
path("admin/", admin.site.urls),
2626
path("", include("todo.urls")),
2727
path("api-auth/", include("rest_framework.urls")),
28-
path("auth/register/", RegisterView.as_view(), name='user-register'),
29-
path("auth/login/", TokenObtainPairView.as_view(), name='token-obtain-pair'),
30-
path("auth/refresh/", TokenRefreshView.as_view(), name='token-refresh'),
28+
path("register/", RegisterView.as_view(), name="user-register"),
29+
# path("auth/login/", TokenObtainPairView.as_view(), name='token-obtain-pair'),
30+
path("login/", EmailLoginView.as_view(), name="token_obtain_pair"),
31+
path("refresh/", TokenRefreshView.as_view(), name="token-refresh"),
3132
]

0 commit comments

Comments
 (0)