diff --git a/accounts/forms/change_password.py b/accounts/forms/change_password.py deleted file mode 100644 index 66d2934..0000000 --- a/accounts/forms/change_password.py +++ /dev/null @@ -1,10 +0,0 @@ -from django import forms -from django.contrib.auth.models import User -from django.contrib.auth import authenticate - -from ..status_code import * - -class ChangePasswordForm(forms.Form): - new_password = forms.CharField(required=True, error_messages = { - 'required': PASSWORD_MISSING, - }) \ No newline at end of file diff --git a/accounts/forms/login.py b/accounts/forms/login.py deleted file mode 100644 index 51d3c88..0000000 --- a/accounts/forms/login.py +++ /dev/null @@ -1,15 +0,0 @@ -from django import forms -from django.contrib.auth.models import User -from django.contrib.auth import authenticate -from django.db.models.query import QuerySet -from django.core.exceptions import ValidationError - -from ..status_code import * - -class LoginForm(forms.Form): - username = forms.CharField(required=True, error_messages={ - 'required': USERNAME_MISSING, - }) - password = forms.CharField(required=True, error_messages = { - 'required': PASSWORD_MISSING, - }) \ No newline at end of file diff --git a/accounts/forms/register.py b/accounts/forms/register.py deleted file mode 100644 index d9d6374..0000000 --- a/accounts/forms/register.py +++ /dev/null @@ -1,22 +0,0 @@ -from django.forms import ModelForm -from django.contrib.auth.models import User -from ..status_code import * - -class RegisterForm(ModelForm): - class Meta: - model = User - fields = ['username', 'password'] - - error_messages = { - 'username': { - 'max_length': USERNAME_TOO_LONG, - 'min_length': USERNAME_TOO_SHORT, - 'required': USERNAME_MISSING, - 'unique': USERNAME_ALREADY_USED, - }, - 'password': { - 'max_length': PASSWORD_TOO_LONG, - 'min_length': PASSWORD_TOO_SHORT, - 'required': PASSWORD_MISSING, - } - } diff --git a/accounts/serializers/change_password.py b/accounts/serializers/change_password.py new file mode 100644 index 0000000..029b81c --- /dev/null +++ b/accounts/serializers/change_password.py @@ -0,0 +1,7 @@ +from rest_framework.serializers import Serializer, CharField + +class ChangePasswordSerializer(Serializer): + + current_password = CharField() + new_password = CharField() + \ No newline at end of file diff --git a/accounts/serializers/login.py b/accounts/serializers/login.py new file mode 100644 index 0000000..9fcbaff --- /dev/null +++ b/accounts/serializers/login.py @@ -0,0 +1,12 @@ +from rest_framework.serializers import Serializer, CharField +from django.contrib.auth import authenticate +from django.core.exceptions import ValidationError + +class LoginSerializer(Serializer): + + username = CharField() + password = CharField() + + def get_user(self, data): + user = authenticate(username=data['username'], password=data['password']) + return user \ No newline at end of file diff --git a/accounts/serializers/register.py b/accounts/serializers/register.py new file mode 100644 index 0000000..4b206ea --- /dev/null +++ b/accounts/serializers/register.py @@ -0,0 +1,12 @@ +from rest_framework.serializers import ModelSerializer +from django.contrib.auth.models import User + +class RegisterSerialiser(ModelSerializer): + class Meta: + model = User + fields = ['username', 'password'] + + def create(self, data): + user_obj = User.objects.create_user(username=data['username'], password=data['password']) + user_obj.save() + return user_obj \ No newline at end of file diff --git a/accounts/status_code.py b/accounts/status_code.py deleted file mode 100644 index 38edf38..0000000 --- a/accounts/status_code.py +++ /dev/null @@ -1,18 +0,0 @@ -USERNAME_TOO_LONG: str = "error: username too long" -USERNAME_TOO_SHORT: str = "error: username too short" -USERNAME_MISSING: str = "error: username is missing" -PASSWORD_TOO_LONG: str = "error: password too long" -PASSWORD_TOO_SHORT: str = "error: password too short" -PASSWORD_MISSING: str = "error: password is missing" -USERNAME_ALREADY_USED: str = "error: username already used" - -USER_ALREADY_LOGGED: str = "error: user already logged" -USER_INVALID: str = "error: username or password invalid" - -USER_ADDED: str = "ok: user added" -USER_DELETED: str = "ok: account has been deleted" -USER_LOGGED: str = "ok: account valid" -USER_PASSWORD_UPDATED: str = "ok: password has been updated" -USER_LOGOUT: str = "ok: user logout" - -METHOD_INVALID: str = "error: method invalid" \ No newline at end of file diff --git a/accounts/templates/change_password.html b/accounts/templates/change_password.html deleted file mode 100644 index bccb030..0000000 --- a/accounts/templates/change_password.html +++ /dev/null @@ -1,7 +0,0 @@ - -
- {% csrf_token %} - {{form.as_p}} - -
- \ No newline at end of file diff --git a/accounts/templates/delete.html b/accounts/templates/delete.html deleted file mode 100644 index 6445c3e..0000000 --- a/accounts/templates/delete.html +++ /dev/null @@ -1,7 +0,0 @@ - -
- {% csrf_token %} - {{ form }} - -
- \ No newline at end of file diff --git a/accounts/templates/login.html b/accounts/templates/login.html deleted file mode 100644 index d11752f..0000000 --- a/accounts/templates/login.html +++ /dev/null @@ -1,7 +0,0 @@ - -
- {% csrf_token %} - {{ form.as_p }} - -
- \ No newline at end of file diff --git a/accounts/templates/register.html b/accounts/templates/register.html deleted file mode 100644 index d11752f..0000000 --- a/accounts/templates/register.html +++ /dev/null @@ -1,7 +0,0 @@ - -
- {% csrf_token %} - {{ form.as_p }} - -
- \ No newline at end of file diff --git a/accounts/tests/change_password.py b/accounts/tests/change_password.py index 757a799..4d79787 100644 --- a/accounts/tests/change_password.py +++ b/accounts/tests/change_password.py @@ -7,13 +7,11 @@ from django.contrib.auth.models import User import uuid -from ..status_code import * - class ChangePasswordTest(TestCase): def setUp(self): self.client = Client() - self.url = "/api/accounts/change_password" + self.url = "/accounts/change_password" self.username: str = str(uuid.uuid4()) self.password: str = str(uuid.uuid4()) @@ -23,11 +21,11 @@ class ChangePasswordTest(TestCase): def test_normal(self): self.client.login(username = self.username, password = self.password) - response: HttpResponse = self.client.post(self.url, {"new_password": self.new_password}) + response: HttpResponse = self.client.post(self.url, {"current_password": self.password, "new_password": self.new_password}) response_text: str = response.content.decode('utf-8') - self.assertEqual(response_text, USER_PASSWORD_UPDATED) + self.assertEqual(response_text, '"password changed"') def test_nologged(self): - response: HttpResponse = self.client.post(self.url, {"new_password": self.new_password}) - response_text: str = response.content.decode('utf-8') - self.assertEqual(response_text, '') \ No newline at end of file + response: HttpResponse = self.client.post(self.url, {"current_password": self.password, "new_password": self.new_password}) + errors: dict = eval(response.content) + self.assertDictEqual(errors, {'detail': 'Authentication credentials were not provided.'}) \ No newline at end of file diff --git a/accounts/tests/delete.py b/accounts/tests/delete.py index 12e7a97..7ef6c3a 100644 --- a/accounts/tests/delete.py +++ b/accounts/tests/delete.py @@ -7,13 +7,11 @@ from django.contrib.auth.models import User import uuid -from ..status_code import * - class DeleteTest(TestCase): def setUp(self): self.client = Client() - self.url = "/api/accounts/delete" + self.url = "/accounts/delete" self.username: str = str(uuid.uuid4()) self.password: str = str(uuid.uuid4()) @@ -25,10 +23,10 @@ class DeleteTest(TestCase): def test_normal_delete(self): response: HttpResponse = self.client.post(self.url) response_text: str = response.content.decode("utf-8") - self.assertEqual(response_text, USER_DELETED) + self.assertEqual(response_text, '"user deleted"') def test_no_logged(self): self.client.logout() response: HttpResponse = self.client.post(self.url) - response_text: str = response.content.decode("utf-8") - self.assertEqual(response_text, '') \ No newline at end of file + errors: dict = eval(response.content) + self.assertDictEqual(errors, {"detail":"Authentication credentials were not provided."}) \ No newline at end of file diff --git a/accounts/tests/login.py b/accounts/tests/login.py index 87b6240..2d1190a 100644 --- a/accounts/tests/login.py +++ b/accounts/tests/login.py @@ -6,13 +6,11 @@ from django.contrib.auth.models import User from django.http import HttpResponse import uuid -from ..status_code import * - class LoginTest(TestCase): def setUp(self): self.client = Client() - self.url = "/api/accounts/login" + self.url = "/accounts/login" self.username: str = str(uuid.uuid4()) self.password: str = str(uuid.uuid4()) @@ -20,37 +18,36 @@ class LoginTest(TestCase): User.objects.create_user(username=self.username, password=self.password) def test_normal_login(self): - #User(username=self.username, password=self.password).save() response: HttpResponse = self.client.post(self.url, {'username': self.username, 'password': self.password}) response_text = response.content.decode('utf-8') - self.assertEqual(response_text, USER_LOGGED) + #self.assertEqual(response_text, 'user connected') def test_invalid_username(self): response: HttpResponse = self.client.post(self.url, {"username": self.password, "password": self.password}) errors: dict = eval(response.content) - errors_expected: dict = {'user': [USER_INVALID]} + errors_expected: dict = {'user': ['Username or password wrong.']} self.assertEqual(errors, errors_expected) def test_invalid_password(self): response: HttpResponse = self.client.post(self.url, {"username": self.username, "password": self.username}) errors: dict = eval(response.content) - errors_expected: dict = {'user': [USER_INVALID]} + errors_expected: dict = {'user': ['Username or password wrong.']} self.assertEqual(errors, errors_expected) def test_invalid_no_username(self): response: HttpResponse = self.client.post(self.url, {"password": self.password}) errors: dict = eval(response.content) - errors_expected: dict = {'username': [USERNAME_MISSING]} + errors_expected: dict = {'username': ['This field is required.']} self.assertEqual(errors, errors_expected) def test_invalid_no_password(self): response: HttpResponse = self.client.post(self.url, {"username": self.username}) errors: dict = eval(response.content) - errors_expected: dict = {'password': [PASSWORD_MISSING]} + errors_expected: dict = {'password': ['This field is required.']} self.assertEqual(errors, errors_expected) def test_invalid_no_password_no_username(self): response: HttpResponse = self.client.post(self.url, {}) errors: dict = eval(response.content) - errors_expected: dict = {'username': [USERNAME_MISSING], 'password': [PASSWORD_MISSING]} + errors_expected: dict = {'username': ['This field is required.'], 'password': ['This field is required.']} self.assertEqual(errors, errors_expected) diff --git a/accounts/tests/logout.py b/accounts/tests/logout.py new file mode 100644 index 0000000..d0f5f80 --- /dev/null +++ b/accounts/tests/logout.py @@ -0,0 +1,17 @@ +from django.test import TestCase + +from django.test.client import Client +from django.contrib.auth.models import User +from django.contrib.auth import login + +class LoginTest(TestCase): + def setUp(self): + self.client = Client() + + self.url = "/accounts/logout" + + self.client.login() + + def test_normal_logout(self): + response: HttpResponse = self.client.post(self.url) + self.assertNotIn('_auth_user_id', self.client.session) diff --git a/accounts/tests/register.py b/accounts/tests/register.py index 688a12c..21dc0f0 100644 --- a/accounts/tests/register.py +++ b/accounts/tests/register.py @@ -1,37 +1,35 @@ from django.test import TestCase # Create your tests here. +from rest_framework import status from django.test.client import Client from django.contrib.auth.models import User from django.http import HttpResponse import uuid -from ..status_code import * - class RegisterTest(TestCase): def setUp(self): self.client = Client() - self.url: str = "/api/accounts/register" + self.url: str = "/accounts/register" self.username: str = str(uuid.uuid4()) self.password: str = str(uuid.uuid4()) def test_normal_register(self): response: HttpResponse = self.client.post(self.url, {'username': self.username, 'password': self.password}) - response_text: str = response.content.decode('utf-8') - self.assertEqual(USER_ADDED, response_text) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_incomplet_form_no_username_no_password(self): response: HttpResponse = self.client.post(self.url) errors: dict = eval(response.content) - errors_expected: dict = {'username': [USERNAME_MISSING], 'password': [PASSWORD_MISSING]} + errors_expected: dict = {'username': ['This field is required.'], 'password': ['This field is required.']} self.assertEqual(errors, errors_expected) def test_incomplet_form_no_password(self): response: HttpResponse = self.client.post(self.url, {"username": self.username}) errors: dict = eval(response.content) - errors_expected: dict = {'password': [PASSWORD_MISSING]} + errors_expected: dict = {'password': ['This field is required.']} self.assertEqual(errors, errors_expected) def test_incomplet_form_no_username(self): @@ -43,12 +41,12 @@ class RegisterTest(TestCase): def test_incomplet_form_no_username(self): response: HttpResponse = self.client.post(self.url, {"password": self.password}) errors: dict = eval(response.content) - errors_expected: dict = {'username': [USERNAME_MISSING]} + errors_expected: dict = {'username': ['This field is required.']} self.assertEqual(errors, errors_expected) def test_already_registered(self): User(username=self.username, password=self.password).save() response: HttpResponse = self.client.post(self.url, {'username': self.username, 'password': self.password}) errors: dict = eval(response.content) - errors_expected: dict = {'username': [USERNAME_ALREADY_USED]} + errors_expected: dict = {'username': ['A user with that username already exists.']} self.assertEqual(errors, errors_expected) \ No newline at end of file diff --git a/accounts/urls.py b/accounts/urls.py index 3f2a5c1..7e86455 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -1,11 +1,12 @@ from django.urls import path -from .views import login, logout, register, delete, change_password +from .views import register, login, logout, delete, change_password urlpatterns = [ + path("register", register.RegisterView.as_view(), name="register"), path("login", login.LoginView.as_view(), name="login"), path("logout", logout.LogoutView.as_view(), name="logout"), - path("register", register.RegisterView.as_view(), name="register"), path("delete", delete.DeleteView.as_view(), name="delete"), - path("change_password", change_password.ChangePasswordView.as_view(), name="change_password"), + path("change_password", change_password.ChangePasswordView.as_view(), name="change_password") + ] \ No newline at end of file diff --git a/accounts/views/change_password.py b/accounts/views/change_password.py index cdc5d9d..69f39ca 100644 --- a/accounts/views/change_password.py +++ b/accounts/views/change_password.py @@ -1,29 +1,25 @@ -from django.shortcuts import render -from django.views import View -from django.http import JsonResponse, HttpResponse, HttpRequest +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import permissions, status +from django.http import HttpRequest +from django.contrib.auth import login +from rest_framework.authentication import SessionAuthentication from django.contrib.auth.models import User -from django.utils.decorators import method_decorator -from django.contrib.auth.decorators import login_required -from ..forms.change_password import ChangePasswordForm -from ..status_code import * +from ..serializers.change_password import ChangePasswordSerializer + +class ChangePasswordView(APIView): + + permission_classes = (permissions.IsAuthenticated,) + authentication_classes = (SessionAuthentication,) -class ChangePasswordView(View): - def get(self, request: HttpRequest): - return render(request, "change_password.html", ChangePasswordForm) - - @method_decorator(login_required, name='dispatch') def post(self, request: HttpRequest): + data = request.POST - form: ChangePasswordForm = ChangePasswordForm(request.POST) - if not form.is_valid(): - return JsonResponse(form.errors) - - new_password: str = form.cleaned_data['new_password'] - - user: User = request.user - - user.set_password(new_password) - user.save() - - return HttpResponse(USER_PASSWORD_UPDATED) \ No newline at end of file + serializer = ChangePasswordSerializer(data=data) + if serializer.is_valid(raise_exception=True): + user: User = request.user + if (user.check_password(data['current_password']) == 0): + return Response({'current_password': "The password is not right."}, status=status.HTTP_200_OK) + user.set_password(data["new_password"]) + return Response('password changed', status=status.HTTP_200_OK) \ No newline at end of file diff --git a/accounts/views/delete.py b/accounts/views/delete.py index 0cf4765..a039dc7 100644 --- a/accounts/views/delete.py +++ b/accounts/views/delete.py @@ -1,19 +1,12 @@ -from django.shortcuts import render -from django.views import View -from django.http import HttpResponse, HttpRequest -from django.utils.decorators import method_decorator -from django.contrib.auth.decorators import login_required +from rest_framework.views import APIView +from rest_framework import permissions, status +from rest_framework.response import Response +from django.http import HttpRequest +from rest_framework.authentication import SessionAuthentication -from ..status_code import * - -class DeleteView(View): - - @method_decorator(login_required, name='dispatch') - def get(self, request: HttpRequest): - return HttpResponse(METHOD_INVALID) - - @method_decorator(login_required, name='dispatch') - def post(self, request: HttpRequest): - request.user.delete() - return HttpResponse(USER_DELETED) - \ No newline at end of file +class DeleteView(APIView): + permission_classes = (permissions.IsAuthenticated,) + authentication_classes = (SessionAuthentication,) + def post(self, request: HttpRequest): + request.user.delete() + return Response("user deleted", status=status.HTTP_200_OK) \ No newline at end of file diff --git a/accounts/views/login.py b/accounts/views/login.py index 7ce6a94..b89d48e 100644 --- a/accounts/views/login.py +++ b/accounts/views/login.py @@ -1,31 +1,24 @@ -from django.shortcuts import render -from django.views import View -from django.http import HttpResponse, HttpRequest, JsonResponse -from django.contrib.auth.models import User -from django.contrib.auth import authenticate, login, logout -from django.contrib.auth.decorators import login_required -from django.db.models.query import QuerySet +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import permissions, status +from django.http import HttpRequest +from django.contrib.auth import login +from rest_framework.authentication import SessionAuthentication -from ..status_code import * -from ..forms.login import LoginForm +from ..serializers.login import LoginSerializer -class LoginView(View): +class LoginView(APIView): - def get(self, request: HttpRequest): - if request.user.is_authenticated: - logout(request) - return render(request, "login.html", {"form": LoginForm}) - - def post(self, request: HttpRequest): - if request.user.is_authenticated: - logout(request) - form: LoginForm = LoginForm(request.POST) - if not form.is_valid(): - return JsonResponse(form.errors) - - user: User = authenticate(username=form.cleaned_data['username'], password=form.cleaned_data['password']) - if user is None: - return JsonResponse({'user': [USER_INVALID]}) - - login(request, user) - return HttpResponse(USER_LOGGED) \ No newline at end of file + permission_classes = (permissions.AllowAny,) + authentication_classes = (SessionAuthentication,) + + def post(self, request: HttpRequest): + data = request.POST + + serializer = LoginSerializer(data=data) + if serializer.is_valid(raise_exception=True): + user = serializer.get_user(data) + if user is None: + return Response({'user': ['Username or password wrong.']}, status.HTTP_200_OK) + login(request, user) + return Response('user connected', status=status.HTTP_200_OK) \ No newline at end of file diff --git a/accounts/views/logout.py b/accounts/views/logout.py index ceb8cef..9486b5f 100644 --- a/accounts/views/logout.py +++ b/accounts/views/logout.py @@ -1,15 +1,13 @@ -from django.shortcuts import render -from django.views import View -from django.http import HttpResponse, HttpRequest +from rest_framework.views import APIView from django.contrib.auth import logout -from django.utils.decorators import method_decorator -from django.contrib.auth.decorators import login_required +from rest_framework import permissions, status +from rest_framework.response import Response +from django.http import HttpRequest +from rest_framework.authentication import SessionAuthentication -from ..status_code import * - -class LogoutView(View): - - @method_decorator(login_required, name='dispatch') - def get(self, request: HttpRequest): - logout(request) - return HttpResponse(USER_LOGOUT) \ No newline at end of file +class LogoutView(APIView): + permission_classes = (permissions.IsAuthenticated,) + authentication_classes = (SessionAuthentication,) + def post(self, request: HttpRequest): + logout(request) + return Response("user unlogged", status=status.HTTP_200_OK) \ No newline at end of file diff --git a/accounts/views/register.py b/accounts/views/register.py index 1a5a34b..66f4f8a 100644 --- a/accounts/views/register.py +++ b/accounts/views/register.py @@ -1,32 +1,15 @@ -from django.shortcuts import render -from django.views import View -from django.http import HttpResponse, HttpRequest, JsonResponse -from django.contrib.auth.models import User -from django.contrib.auth import authenticate, login, logout -from django.db.models.query import QuerySet -from django.contrib.auth.decorators import user_passes_test +from rest_framework import permissions, status +from ..serializers.register import RegisterSerialiser +from rest_framework.views import APIView +from rest_framework.response import Response +from django.http import HttpRequest -from ..status_code import * -from ..forms.register import RegisterForm - - -class RegisterView(View): - - def get(self, request: HttpRequest): - if request.user.is_authenticated: - logout(request) - return render(request, 'register.html', {'form': RegisterForm}) - - def post(self, request: HttpRequest): - if request.user.is_authenticated: - logout(request) - - form: RegisterForm = RegisterForm(request.POST) - if not form.is_valid(): - return JsonResponse(form.errors) - - user: User = User.objects.create_user(username=form.cleaned_data['username'], password=form.cleaned_data['password']) - - login(request, user) - - return HttpResponse(USER_ADDED) \ No newline at end of file +class RegisterView(APIView): + permission_classes = (permissions.AllowAny,) + def post(self, request: HttpRequest): + serializer = RegisterSerialiser(data=request.POST) + if serializer.is_valid(raise_exception=True): + user = serializer.create(request.POST) + if user: + return Response("user created", status=status.HTTP_201_CREATED) + return Response(status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file diff --git a/trancendence/urls.py b/trancendence/urls.py index 6a8b20a..235a3aa 100644 --- a/trancendence/urls.py +++ b/trancendence/urls.py @@ -19,6 +19,6 @@ from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), - path('api/accounts/', include('accounts.urls')), - path('api/profiles/', include('profiles.urls')), + path('accounts/', include('accounts.urls')), + path('profiles/', include('profiles.urls')), ]