diff --git a/backend/api/migrations/0002_user_resources_alter_user_role.py b/backend/api/migrations/0002_user_resources_alter_user_role.py new file mode 100644 index 0000000..553d827 --- /dev/null +++ b/backend/api/migrations/0002_user_resources_alter_user_role.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.5 on 2023-11-09 07:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("api", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="user", + name="resources", + field=models.ManyToManyField(related_name="users", to="api.resource"), + ), + migrations.AlterField( + model_name="user", + name="role", + field=models.ForeignKey( + default=3, on_delete=django.db.models.deletion.DO_NOTHING, to="api.role" + ), + ), + ] diff --git a/backend/api/models/user.py b/backend/api/models/user.py index 28fd0a0..a32aca1 100644 --- a/backend/api/models/user.py +++ b/backend/api/models/user.py @@ -1,10 +1,13 @@ from django.contrib.auth.models import AbstractUser from django.db import models -from .role import Role + +from api.models.resource import Resource +from api.models.role import Role class User(AbstractUser): email = models.EmailField(unique=True) otp = models.CharField(max_length=6, blank=True, null=True) otp_send_time = models.DateTimeField(blank=True, null=True) - role = models.ForeignKey(Role, on_delete=models.SET_NULL, null=True, blank=True) \ No newline at end of file + role = models.ForeignKey(Role, on_delete=models.DO_NOTHING, default=3) + resources = models.ManyToManyField(Resource, related_name="users") diff --git a/backend/api/schemas/user.py b/backend/api/schemas/user.py index 486eb79..3a22b5b 100644 --- a/backend/api/schemas/user.py +++ b/backend/api/schemas/user.py @@ -1,49 +1,54 @@ -from ninja import Schema -from pydantic import BaseModel +from ninja import ModelSchema, Schema +from pydantic import EmailStr + +from api.models.resource import Resource +from api.models.role import Role +from api.models.user import User class UserIn(Schema): - """ - This schema is using for getting the input data for the User model. - """ username: str - email: str + email: EmailStr password: str - role_id:int + role_id: int = 3 + resource_ids: list[int] = None -class UserTestIn(Schema): - """ - This schema is using for getting the input data for the User model. - """ - id : int - username: str - email: str - password: str - +class UserRoleOut(ModelSchema): + class Config: + model = Role + model_fields = ["id", "name"] -class UserOut(Schema): - """ - This schema is using for returning the output of the User - """ - id: int - username: str - email: str +class UserResourceOut(ModelSchema): + class Config: + model = Resource + model_fields = ["id", "name"] + + +class UserOut(ModelSchema): + role: UserRoleOut + resources: list[UserResourceOut] = None + + class Config: + model = User + model_exclude = ["password", "role", "resources"] class UserForgotPassword(Schema): """ This schema is using for getting the input data for Forgot Password """ - email: str + + email: EmailStr class VerifyOtpIn(Schema): """ This schema is using for getting the input data for Verify Otp """ - email:str + + email: EmailStr otp: int @@ -51,6 +56,7 @@ class UpdatePasswordIn(Schema): """ This schema is using for getting the input data for Update password, This is using when user forgot password. """ + id: int password: str @@ -59,5 +65,6 @@ class ChangePasswordIn(Schema): """ This schema is using for getting the input data for change password, This is using when user is login and want to change password """ + current_password: str new_password: str diff --git a/backend/api/tests/conftest.py b/backend/api/tests/conftest.py index bb3eea0..b8c4ac3 100644 --- a/backend/api/tests/conftest.py +++ b/backend/api/tests/conftest.py @@ -3,14 +3,14 @@ from django.core.management import call_command from django.test import Client from ninja_jwt.tokens import RefreshToken -from api.tests.factories import UserFactory client = Client() # @pytest.fixture(scope="session") @pytest.fixture() -def user(): +def user(load_specific_fixtures): + load_specific_fixtures(["roles"]) return get_user_model().objects.create_user( username="testuser", password="testpass", email="test@example.com" ) @@ -49,6 +49,7 @@ def test_change_password_data(): "new_password": "updatedtestpass", } + @pytest.fixture def test_update_password_data(): return { diff --git a/backend/api/tests/test_user.py b/backend/api/tests/test_user.py index d56374b..4c44d4f 100644 --- a/backend/api/tests/test_user.py +++ b/backend/api/tests/test_user.py @@ -2,18 +2,13 @@ import pytest -from api.tests.factories import UserFactory,UserCreateFactory - -from django.urls import reverse - - - +from api.tests.factories import UserCreateFactory, UserFactory @pytest.mark.django_db def test_create_user(api_client, load_specific_fixtures): - load_specific_fixtures(["role"]) - data = UserFactory.build(role_id=1).dict() + load_specific_fixtures(["roles"]) + data = UserFactory.build(role_id=1, resource_ids=[]).dict() response = api_client.post( "/api/users/", json.dumps(data, default=str), content_type="application/json" ) @@ -32,21 +27,20 @@ def test_get_user(api_client): instance = UserCreateFactory.create() print(vars(instance)) # data = instance.dict() - response = api_client.get( - f'/api/users/{instance.id}' - ) + response = api_client.get(f"/api/users/{instance.id}") print(response.content) assert response.status_code == 200 - @pytest.mark.django_db def test_update_user(api_client): instance = UserCreateFactory.create() print(vars(instance)) data = instance.__dict__ response = api_client.put( - f'/api/users/{instance.id}', json.dumps(data, default=str), content_type="application/json" + f"/api/users/{instance.id}", + json.dumps(data, default=str), + content_type="application/json", ) print(response.content) assert response.status_code == 200 @@ -57,17 +51,17 @@ def test_delete_user(api_client): instance = UserCreateFactory.create() print(vars(instance)) data = instance.__dict__ - response = api_client.delete( - f'/api/users/{instance.id}' - ) + response = api_client.delete(f"/api/users/{instance.id}") print(response.content) assert response.status_code == 204 @pytest.mark.django_db -def test_change_user_password(api_client,test_change_password_data): +def test_change_user_password(api_client, test_change_password_data): response = api_client.put( - '/api/users/change-password/', json.dumps(test_change_password_data, default=str), content_type="application/json" + "/api/users/change-password/", + json.dumps(test_change_password_data, default=str), + content_type="application/json", ) data = response.json() assert response.status_code == 200 @@ -75,19 +69,18 @@ def test_change_user_password(api_client,test_change_password_data): @pytest.mark.django_db -def test_update_user_password(api_client,test_update_password_data): +def test_update_user_password(api_client, test_update_password_data): response = api_client.post( - '/api/users/update-password', json.dumps(test_update_password_data, default=str), content_type="application/json" + "/api/users/update-password", + json.dumps(test_update_password_data, default=str), + content_type="application/json", ) - + assert response.status_code == 200 @pytest.mark.django_db def test_get_current_user(api_client): - response = api_client.get( - '/api/users/me/' - ) - - assert response.status_code == 200 + response = api_client.get("/api/users/me/") + assert response.status_code == 200 diff --git a/backend/api/views/user.py b/backend/api/views/user.py index dc7188f..df21ce4 100644 --- a/backend/api/views/user.py +++ b/backend/api/views/user.py @@ -34,9 +34,16 @@ def register_user(request, user_in: UserIn): password=user_in.password, role_id=user_in.role_id, ) + if user_in.resource_ids: + user.resources.set(user_in.resource_ids) return user +@user_no_auth_router.get("/check-first-user", response=dict[str, bool]) +def check_first_user(request): + return {"is_first_user": User.objects.count() == 0} + + @user_no_auth_router.post("/forgot-password") def forgot_password(request, user_in: UserForgotPassword): email = user_in.email @@ -85,7 +92,7 @@ def change_password(request, change_password: ChangePasswordIn): return {"message": "Current password is incorrect"} -class UserViewSet(ModelViewSet): +class UserViewSetAuth(ModelViewSet): model_class = User # AbstractModelView subclasses can be used as-is @@ -96,4 +103,4 @@ class UserViewSet(ModelViewSet): # The register_routes method must be called to register the routes with the router -UserViewSet.register_routes(user_auth_router) +UserViewSetAuth.register_routes(user_auth_router) diff --git a/backend/fixtures/role.json b/backend/fixtures/role.json deleted file mode 100644 index 3a6807c..0000000 --- a/backend/fixtures/role.json +++ /dev/null @@ -1,18 +0,0 @@ -[{ - "model": "api.role", - "pk": 1, - "fields": { - "name": "Admin", - "created_at": "2023-08-19T21:46:44.097Z", - "updated_at": "2023-08-19T21:46:44.097Z", - "can_delete": true - } -}, { - "model": "api.role", - "pk": 2, - "fields": { - "name": "Customer", - "created_at": "2023-08-19T21:46:51.501Z", - "updated_at": "2023-08-19T21:46:51.501Z" - } -}] \ No newline at end of file diff --git a/backend/fixtures/roles.json b/backend/fixtures/roles.json new file mode 100644 index 0000000..ec04cfb --- /dev/null +++ b/backend/fixtures/roles.json @@ -0,0 +1,39 @@ +[ + { + "model": "api.role", + "pk": 1, + "fields": { + "name": "Administrator", + "created_at": "2023-08-19T21:46:44.097Z", + "updated_at": "2023-08-19T21:46:44.097Z", + "can_delete": true + } + }, + { + "model": "api.role", + "pk": 2, + "fields": { + "name": "Planner", + "created_at": "2023-08-19T21:46:51.501Z", + "updated_at": "2023-08-19T21:46:51.501Z" + } + }, + { + "model": "api.role", + "pk": 3, + "fields": { + "name": "Operator", + "created_at": "2023-08-19T21:46:51.501Z", + "updated_at": "2023-08-19T21:46:51.501Z" + } + }, + { + "model": "api.role", + "pk": 4, + "fields": { + "name": "Read-Only", + "created_at": "2023-08-19T21:46:51.501Z", + "updated_at": "2023-08-19T21:46:51.501Z" + } + } +] diff --git a/backend/requirements.txt b/backend/requirements.txt index aeb864f..13f7e03 100755 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -14,4 +14,5 @@ pytest-django==4.5.2 polyfactory==2.9.0 django-ordered-model==3.7.4 psycopg2==2.9.9 -factory-boy==3.3.0 \ No newline at end of file +factory-boy==3.3.0 +email-validator==2.0.0.post2 \ No newline at end of file