Skip to content
This repository has been archived by the owner on Apr 21, 2024. It is now read-only.

Commit

Permalink
views and serializers (#11)
Browse files Browse the repository at this point in the history
+ created routes
+ created views
+ changed user type storing way
+ added CI checks for Flake8 and Black
+ added object permissions
+ added jwt 


Co-authored-by: Pawel Krasicki <https://github.com/krasickiPawel>
Co-authored-by: Krystian Ogonowski <[email protected]>
  • Loading branch information
krasickiPawel and KrystianOg authored Oct 19, 2022
1 parent c06ecf4 commit 301bed5
Show file tree
Hide file tree
Showing 18 changed files with 438 additions and 123 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/django.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Django CI

on:
push:
branches:
- '*'
pull_request:
branches:
- master

jobs:
build:
runs-on: ubuntu-latest
strategy:
max-parallel: 1
matrix:
python-version: [ '3.10.0' ]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Flake8 action
uses: py-actions/flake8@v2
with:
config: "../setup.cfg"
- name: Black Check
uses: jpetrucciani/[email protected]
with:
path: 'api'
black_flags: '--config pyproject.toml'
10 changes: 8 additions & 2 deletions api/apps/authentication/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
from django.contrib import admin

from .models import User
from guardian.admin import GuardedModelAdmin


@admin.register(User)
class UserAdmin(GuardedModelAdmin):
pass


admin.site.register(User)
# admin.site.register(User)
84 changes: 57 additions & 27 deletions api/apps/authentication/managers.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,61 @@
from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.models import Group, Permission


class UserManager(BaseUserManager):
class UserManager(BaseUserManager):
def create_user(self, email, password=None):
if email is None:
raise ValueError("Users must have an email address")

user = self.model(
email=self.normalize_email(email),
)

user.set_password(password)
user.save(using=self._db)
return user

def create_superuser(self, email, password):
if password is None:
raise TypeError("Superusers must have a password.")
if email is None:
raise TypeError("Superusers must have an email address.")

user = self.create_user(email, password)
user.is_superuser = True
user.is_staff = True
user.UserType = user.UserType.PATIENT
user.save(using=self._db)
return user

# TODO: maybe add a method to create a doctor or a patient
def create_user(self, email, password=None):
if email is None:
raise ValueError("Users must have an email address")

user = self.model(
email=self.normalize_email(email),
)
user.set_password(password)
user.save(using=self._db)

try:
user.groups.add(Group.objects.get(name="Patient"))
except Group.DoesNotExist:
Group.objects.create(name="Patient")
user.groups.add(Group.objects.get(name="Patient"))

user.user_permissions.add(Permission.objects.get(codename="add_request"))

return user

def create_doctor(self, email, password):
if password is None:
raise TypeError("Doctors must have a password.")

user = self.create_user(email, password)

try:
user.groups.add(Group.objects.get(name="Doctor"))
except Group.DoesNotExist:
Group.objects.create(name="Doctor")
user.groups.add(Group.objects.get(name="Doctor"))

user.user_permissions.add(Permission.objects.get(codename="add_appointment"))
user.save(using=self._db)
return user

def create_superuser(self, email, password):
if password is None:
raise TypeError("Superusers must have a password.")
if email is None:
raise TypeError("Superusers must have an email address.")

user = self.create_user(email, password)
user.is_superuser = True
user.is_staff = True

try:
user.groups.add(Group.objects.get(name="Admin"))
except Group.DoesNotExist:
Group.objects.create(name="Admin")
user.groups.add(Group.objects.get(name="Admin"))

user.save(using=self._db)
return user

# TODO: maybe add a method to create a doctor or a patient
17 changes: 17 additions & 0 deletions api/apps/authentication/migrations/0002_remove_user_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.1.2 on 2022-10-16 17:00

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("authentication", "0001_initial"),
]

operations = [
migrations.RemoveField(
model_name="user",
name="type",
),
]
30 changes: 16 additions & 14 deletions api/apps/authentication/models.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.db import models
from rest_framework_simplejwt.tokens import RefreshToken

from .managers import UserManager
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token


class User(AbstractBaseUser, PermissionsMixin):
class UserType(models.IntegerChoices):
ADMIN = (1, "Admin")
MODERATOR = (2, "Moderator")
DOCTOR = (3, "Doctor")
PATIENT = (4, "Patient")

# base fields
is_verified = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
type = models.IntegerField(choices=UserType.choices, default=UserType.PATIENT)

# personal information
email = models.EmailField(max_length=320, unique=True)
first_name = models.CharField(max_length=63, blank=True, null=True)
Expand All @@ -34,15 +28,23 @@ class UserType(models.IntegerChoices):
def __str__(self):
return self.email

@property
def username(self):
return self.first_name + " " + self.last_name or self.email.split("@")[0]
def get_username(self):
return (
self.first_name + " " + self.last_name
if self.first_name and self.last_name
else self.email.split("@")[0]
)

def get_token(self):
refresh = RefreshToken.for_user(self)

@property
def type_detail(self):
return self.get_type_display()
return {
"refresh": str(refresh),
"access": str(refresh.access_token),
}


# Create auth token post User save
@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Expand Down
16 changes: 12 additions & 4 deletions api/apps/authentication/serializers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from django.contrib.auth.hashers import make_password
from rest_framework import serializers
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

Expand All @@ -14,13 +13,12 @@ def get_token(cls, user):
token["email"] = user.email
token["first_name"] = user.first_name
token["last_name"] = user.last_name
token["type"] = user.type
# ...

return token


class RegisterUserSerializer(serializers.ModelSerializer):
class RegisterPatientSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["email", "password"]
Expand All @@ -37,7 +35,17 @@ def validate_password(self, value: str) -> str:
if password != password2:
raise serializers.ValidationError("Passwords do not match")

return make_password(value)
return value

def create(self, validated_data):
patient = User.objects.create_user(**validated_data)
return patient


class RegisterDoctorSerializer(RegisterPatientSerializer):
def create(self, validated_data):
doctor = User.objects.create_doctor(**validated_data)
return doctor


class ChangePasswordSerializer(serializers.Serializer): # noqa
Expand Down
14 changes: 12 additions & 2 deletions api/apps/authentication/urls.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
from django.urls import path, include
from rest_framework_simplejwt.views import TokenRefreshView
from .views import MyTokenObtainPairView, SignUpViewset, ChangePasswordViewSet
from .views import (
MyTokenObtainPairView,
ChangePasswordViewSet,
GoogleSignInView,
SignUpDoctorViewSet,
SignUpPatientViewSet,
)

auth_urlpatterns = [
path("token/", MyTokenObtainPairView.as_view(), name="token_obtain_pair"), # to sign in
path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
path("signup/", SignUpViewset.as_view({"post": "create"}), name="signup"), # to sign up
path("signup/", SignUpPatientViewSet.as_view({"post": "create"}), name="signup"), # to sign up
path(
"signup/doctor/", SignUpDoctorViewSet.as_view({"post": "create"}), name="signup-doctor"
), # to sign up
path(
"change-password/",
ChangePasswordViewSet.as_view({"patch": "update"}),
name="update_password",
),
path("google/signin/", GoogleSignInView.as_view({"get": "retrieve"}), name="google_signin"),
]

urlpatterns = [
Expand Down
62 changes: 54 additions & 8 deletions api/apps/authentication/views.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,53 @@
from rest_framework.mixins import (
CreateModelMixin,
UpdateModelMixin
)
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, RetrieveModelMixin
from rest_framework.viewsets import GenericViewSet
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.response import Response
from rest_framework import status
from rest_framework_simplejwt.views import TokenObtainPairView

from api.apps.authentication.serializers import MyTokenObtainPairSerializer, RegisterUserSerializer, \
ChangePasswordSerializer
from api import settings
from api.apps.authentication.models import User

from api.apps.authentication.serializers import (
MyTokenObtainPairSerializer,
RegisterPatientSerializer,
ChangePasswordSerializer,
RegisterDoctorSerializer,
)

from google.oauth2 import id_token
from google.auth.transport import requests


# for jwt tokens
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
permission_classes = [AllowAny]


# TODO: remove response data when 201
class SignUpViewset(CreateModelMixin, GenericViewSet):
serializer_class = RegisterUserSerializer
class SignUpPatientViewSet(CreateModelMixin, GenericViewSet):
serializer_class = RegisterPatientSerializer
permission_classes = [AllowAny]

def get_serializer_context(self):
return {
"password2": self.request.data.get("password2"),
}

def create(self, request, *args, **kwargs):
response = super().create(request, args, kwargs)
return Response({}, status=response.status_code, headers=response.headers)


class SignUpDoctorViewSet(SignUpPatientViewSet):
serializer_class = RegisterDoctorSerializer
permission_classes = [AllowAny]

def create(self, request, *args, **kwargs):
response = super().create(request, args, kwargs)
return Response({}, status=response.status_code, headers=response.headers)


class ChangePasswordViewSet(UpdateModelMixin, GenericViewSet):
serializer_class = ChangePasswordSerializer
Expand All @@ -42,3 +63,28 @@ def update(self, request, *args, **kwargs):
return Response({"status": "password set"}, status=status.HTTP_200_OK)

return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class GoogleSignInView(RetrieveModelMixin, GenericViewSet):
def retrieve(self, request, *args, **kwargs):
token = request.headers.get("Authorization")

try:
idinfo = id_token.verify_oauth2_token(
token, requests.Request(), settings.GOOGLE_CLIENT_ID, clock_skew_in_seconds=4
)
email = idinfo["email"]
given_name = idinfo["given_name"]
family_name = idinfo["family_name"]

if User.objects.filter(email=email).exists():
token = User.objects.get(email=email).get_token()
return Response(token, status=status.HTTP_200_OK)
else:
user = User.objects.create_user(
email=email, first_name=given_name, last_name=family_name
)
user.save()
return Response(user.get_token(), status=status.HTTP_201_CREATED)
except ValueError:
return Response({"error": "Invalid Google token"}, status=status.HTTP_403_FORBIDDEN)
13 changes: 10 additions & 3 deletions api/apps/core/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from django.contrib import admin

from .models import Appointment, Request
from guardian.admin import GuardedModelAdmin


@admin.register(Appointment)
class AppointmentAdmin(GuardedModelAdmin):
pass


admin.site.register(Appointment)
admin.site.register(Request)
@admin.register(Request)
class RequestAdmin(GuardedModelAdmin):
pass
Loading

0 comments on commit 301bed5

Please sign in to comment.