diff --git a/api/common/validators.py b/api/common/validators.py index c7d8d9e..bafcb22 100644 --- a/api/common/validators.py +++ b/api/common/validators.py @@ -6,4 +6,12 @@ def is_phone(val: str): Validates a phone number """ if not val.isnumeric() or len(val) != 10: - raise ValidationError(f'"{val}" is not a valid number') + raise ValidationError(f'"Phone: {val}" is not a valid number') + + +def is_national_id(val: str): + """ + Validates an egyptian national id + """ + if not val.isnumeric() or len(val) != 14: + raise ValidationError(f'"ID: {val}" is not a valid national id') diff --git a/api/users/admin.py b/api/users/admin.py index 33614eb..1d9d7f3 100644 --- a/api/users/admin.py +++ b/api/users/admin.py @@ -17,7 +17,7 @@ class UserAdmin(auth_admin.UserAdmin): add_form = UserAdminCreationForm fieldsets = ( (None, {"fields": ("username", "password")}), - (_("Personal info"), {"fields": ("name", "email", "location")}), + (_("Personal info"), {"fields": ("name", "email", "location", "national_id")}), ( _("Permissions"), { diff --git a/api/users/apis.py b/api/users/apis.py index 58ed28d..6aaecf8 100644 --- a/api/users/apis.py +++ b/api/users/apis.py @@ -1,11 +1,12 @@ +from django.shortcuts import get_object_or_404 from rest_framework import permissions, serializers, status from rest_framework.response import Response from rest_framework.views import APIView from api.common.permissions import IsVerified -from api.common.utils import get_object, inline_serializer +from api.common.utils import inline_serializer from api.users.models import User -from api.users.services import create_user, update_user +from api.users.services import create_user, set_national_id, update_user class CreateUserApi(APIView): @@ -47,7 +48,7 @@ class OutputSerializer(serializers.Serializer): ) def get(self, request, user_id): - user = get_object(User, id=user_id) + user = get_object_or_404(User, id=user_id) serializer = self.OutputSerializer(user) return Response(serializer.data) @@ -80,3 +81,20 @@ def post(self, request, user_id): data=serializer.validated_data, ) return Response(status=status.HTTP_200_OK) + + +class SetNationalIdApi(APIView): + class InputSerializer(serializers.Serializer): + national_id = serializers.CharField() + + def post(self, request, user_id): + serializer = self.InputSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + + user = get_object_or_404(User, pk=user_id) + set_national_id( + user=user, + data=serializer.validated_data, + ) + + return Response(status=status.HTTP_200_OK) diff --git a/api/users/migrations/0001_initial.py b/api/users/migrations/0001_initial.py index 16fd85c..ed130ea 100644 --- a/api/users/migrations/0001_initial.py +++ b/api/users/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.13 on 2022-04-19 22:39 +# Generated by Django 3.2.13 on 2022-05-10 19:09 import api.common.validators import django.contrib.auth.models @@ -12,7 +12,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('locations', '0001_initial'), + ('locations', '0003_alter_location_address'), ('auth', '0012_alter_user_first_name_max_length'), ] @@ -24,14 +24,14 @@ class Migration(migrations.Migration): ('password', models.CharField(max_length=128, verbose_name='password')), ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('name', models.CharField(max_length=256)), - ('email', models.EmailField(blank=True, max_length=254, null=True)), ('username', models.CharField(max_length=10, unique=True, validators=[api.common.validators.is_phone])), ('id_exp_date', models.DateTimeField(blank=True, null=True)), - ('id_photo_url', models.ImageField(blank=True, upload_to='id-photos/')), + ('national_id', models.CharField(blank=True, max_length=14, null=True, unique=True, validators=[api.common.validators.is_national_id])), ('firebase_token', models.CharField(blank=True, max_length=256, unique=True)), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), ('location', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user', to='locations.location')), diff --git a/api/users/migrations/0002_remove_user_id_photo_url.py b/api/users/migrations/0002_remove_user_id_photo_url.py deleted file mode 100644 index fe85649..0000000 --- a/api/users/migrations/0002_remove_user_id_photo_url.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 3.2.13 on 2022-04-21 21:04 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('users', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='user', - name='id_photo_url', - ), - ] diff --git a/api/users/migrations/0003_alter_user_email.py b/api/users/migrations/0003_alter_user_email.py deleted file mode 100644 index a5e1547..0000000 --- a/api/users/migrations/0003_alter_user_email.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.13 on 2022-05-08 04:16 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('users', '0002_remove_user_id_photo_url'), - ] - - operations = [ - migrations.AlterField( - model_name='user', - name='email', - field=models.EmailField(blank=True, max_length=254, verbose_name='email address'), - ), - ] diff --git a/api/users/models.py b/api/users/models.py index 407d940..54d079d 100644 --- a/api/users/models.py +++ b/api/users/models.py @@ -3,7 +3,7 @@ from django.urls import reverse from django.utils import timezone -from api.common.validators import is_phone +from api.common.validators import is_national_id, is_phone from api.locations.models import Location @@ -15,6 +15,13 @@ class User(AbstractUser): username = models.CharField(max_length=10, unique=True, validators=[is_phone]) id_exp_date = models.DateTimeField(null=True, blank=True) + national_id = models.CharField( + max_length=14, + unique=True, + null=True, + blank=True, + validators=[is_national_id], + ) firebase_token = models.CharField(max_length=256, unique=True, blank=True) diff --git a/api/users/services.py b/api/users/services.py index a7d0dc2..1f73066 100644 --- a/api/users/services.py +++ b/api/users/services.py @@ -46,11 +46,7 @@ def create_user( @transaction.atomic -def update_user( - *, - user: User, - data: Dict, -) -> User: +def update_user(*, user: User, data: Dict) -> User: non_side_effect_fields = ["name", "firebase_token"] user, _ = model_update( @@ -64,3 +60,13 @@ def update_user( update_location(location=user.location, data=location_data) return user + + +def set_national_id(*, user: User, data: Dict) -> None: + national_id = data.get("national_id") + user.national_id = national_id + + user.full_clean() + user.save() + + return None diff --git a/api/users/urls.py b/api/users/urls.py index 49d7a43..01897e7 100644 --- a/api/users/urls.py +++ b/api/users/urls.py @@ -1,10 +1,11 @@ from django.urls import path -from api.users.apis import CreateUserApi, DetailUserApi, UpdateUserApi +from api.users.apis import CreateUserApi, DetailUserApi, SetNationalIdApi, UpdateUserApi app_name = "users" urlpatterns = [ path("create/", CreateUserApi.as_view(), name="create_user"), path("/", DetailUserApi.as_view(), name="get_user"), path("/update/", UpdateUserApi.as_view(), name="update_user"), + path("/set/id/", SetNationalIdApi.as_view(), name="set_id"), ]