Skip to content

Commit

Permalink
- added base model, response
Browse files Browse the repository at this point in the history
- added auth
  • Loading branch information
bumper-bugra-caglar committed Dec 4, 2023
1 parent f8f0dd8 commit a165d99
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 155 deletions.
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.9
FROM python:3.8.14

ENV PYTHONUNBUFFERED 1

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ django-timezone-field==6.1.0
djangorestframework==3.14.0
djangorestframework-jwt==1.11.0
djangorestframework-simplejwt==5.3.0
drf-yasg==1.21.7
exceptiongroup==1.2.0
factory-boy==3.3.0
Faker==20.1.0
Expand All @@ -37,7 +38,6 @@ Jinja2==3.1.2
kombu==5.3.4
MarkupSafe==2.1.3
model-bakery==1.17.0
numpy==1.26.2
openapi-codec==1.3.2
packaging==23.2
Pillow==10.1.0
Expand Down
27 changes: 27 additions & 0 deletions skillforge/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from rest_framework import status
from rest_framework.response import Response


class BaseResponse:
def __init__(self, status_code=status.HTTP_200_OK, message="", data=None):
self.status_code = status_code
self.message = message
self.data = data

def success(self):
return Response({
"success": status.HTTP_201_CREATED,
"message": self.message
}, self.status_code)

def error(self):
return Response({
"success": False,
"message": self.message,
}, self.status_code)

def success_with_data(self):
return Response({
"success": self.success,
"data": self.data
}, self.status_code)
7 changes: 7 additions & 0 deletions skillforge/generics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from rest_framework.pagination import PageNumberPagination


class StandardResultsSetPagination(PageNumberPagination):
page_size = 25
page_size_query_param = 'page_size'
max_page_size = 1000
13 changes: 13 additions & 0 deletions skillforge/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.db import models

import datetime


class SkillForgeBaseQuerySet(models.QuerySet):
def update(self, *args, **kwargs):
if 'last_updated' not in kwargs:
kwargs['last_updated'] = datetime.datetime.now()
return super().update(**kwargs)

class BaseModel(models.Model):
pass
13 changes: 10 additions & 3 deletions skillforge/settings.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Django settings for skillforge project.
Generated by 'django-admin startproject' using Django 4.2.7.
Generated by 'django-admin startproject' using Django 4.2.7 LTS.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/
Expand Down Expand Up @@ -44,13 +44,19 @@
'rest_framework',
'rest_framework_swagger',
'django_filters',
'rest_framework_simplejwt'
'rest_framework_simplejwt',
'drf_yasg',
]

LOCAL_APPS = [
'user'
'user',
]

if DEBUG:
THIRD_PARTY_APPS += [
"django_extensions",
]

INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS

MIDDLEWARE = [
Expand Down Expand Up @@ -176,6 +182,7 @@
"sentry-trace",
)


# Celery
# ------------------------------------------------------------------------------
# BROKER_URL = os.environ.get("BROKER_URL", "amqp://guest:guest@localhost//")
43 changes: 43 additions & 0 deletions user/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Generated by Django 4.2.7 on 2023-12-03 22:17

import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.utils.timezone
import uuid


class Migration(migrations.Migration):

initial = True

dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]

operations = [
migrations.CreateModel(
name='User',
fields=[
('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')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('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')),
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('email', models.EmailField(max_length=200, 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')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={
'db_table': 'user',
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]
222 changes: 81 additions & 141 deletions user/v1/api.py
Original file line number Diff line number Diff line change
@@ -1,142 +1,82 @@
import datetime

from django.contrib import auth
from django.contrib.auth.password_validation import validate_password
from django.db.models import Q
from rest_framework import serializers
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.generics import get_object_or_404
from rest_framework.validators import UniqueValidator
from rest_framework_simplejwt.exceptions import TokenError
from rest_framework_simplejwt.tokens import RefreshToken

# Bugra Ahmet Caglar
from drf_yasg.utils import swagger_auto_schema

from skillforge.generics import StandardResultsSetPagination
from rest_framework import status
from rest_framework.permissions import (AllowAny, IsAuthenticated)
from rest_framework.generics import (
CreateAPIView, RetrieveAPIView, ListAPIView
)
from rest_framework_jwt.settings import api_settings

from skillforge.constants import BaseResponse
from user.models import User


class UserLoginSerializer(serializers.ModelSerializer):
email = serializers.CharField(max_length=255, min_length=3, read_only=True)
username = serializers.CharField(max_length=255, min_length=3)
password = serializers.CharField(max_length=128, write_only=True)

class Meta:
model = User
fields = ['email', 'password', 'username', 'data']

def validate(self, attrs):
username = attrs.get('username', '')
password = attrs.get('password', '')
try:
instance = User.objects.get(Q(email=username) | Q(username=username))
except:
raise AuthenticationFailed('Invalid credential, try again')
user = auth.authenticate(username=instance.username, password=password)
if not user:
raise AuthenticationFailed('Invalid credential, try again')
user.last_login = datetime.datetime.now()
user.save()
return {
'email': user.email,
'username': user.username,
'data': user.data
}


class UserDetailSerializer(serializers.ModelSerializer):

class Meta:
model = User
fields = (
'id',
'username',
'email',
'first_name',
'last_name',
'date_joined',
'last_login',
)


class UserListSerializer(serializers.ModelSerializer):
email = serializers.EmailField(read_only=True)
username = serializers.CharField(read_only=True)
first_name = serializers.CharField(read_only=True)
last_name = serializers.CharField(read_only=True)
date_joined = serializers.DateTimeField(read_only=True)
last_login = serializers.DateTimeField(read_only=True)

class Meta:
model = User
fields = ['email', 'username', 'first_name', 'last_name', 'date_joined', 'last_login']


class UserLogoutSerializer(serializers.Serializer):
refresh = serializers.CharField()

default_error_messages = {
'bad_token': 'Token is expired or invalid'
}

def validate(self, attrs):
self.token = attrs['refresh']
return attrs

def save(self, **kwargs):
try:
RefreshToken(self.token).blacklist()
except TokenError:
self.fail('bad_token')


class UserRegisterSerializer(serializers.ModelSerializer):
email = serializers.EmailField(
required=True,
validators=[UniqueValidator(queryset=User.objects.all())]
)
password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
password2 = serializers.CharField(write_only=True, required=True)

class Meta:
model = User
fields = ('username', 'password', 'password2', 'email', 'first_name', 'last_name')
extra_kwargs = {
'first_name': {'required': False},
'last_name': {'required': False}
}

def validate(self, attrs):
if attrs['password'] != attrs['password2']:
raise serializers.ValidationError({"password": "Password fields didn't match."})
return attrs

def create(self, validated_data):
user = User.objects.create(
username=validated_data['username'],
email=validated_data['email'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name']
)
user.set_password(validated_data['password'])
user.save()
return user


class UserForgetPasswordSerializer(serializers.Serializer):
email = serializers.EmailField(required=True)

def validate(self, attrs):
get_object_or_404(User, email=attrs['email'])
return attrs


class UserResetPasswordSerializer(serializers.ModelSerializer):
password = serializers.CharField(required=True, validators=[validate_password])
password2 = serializers.CharField(write_only=True, required=True)

class Meta:
model = User
fields = ('password', 'password2')

def validate(self, attrs):
if attrs['password'] != attrs['password2']:
raise serializers.ValidationError({"password": "Password fields didn't match."})
return attrs
from user.v1.serializers import (
UserLoginSerializer, UserRegisterSerializer,
UserLogoutSerializer, UserListSerializer
)

JWT_PAYLOAD_HANDLER = api_settings.JWT_PAYLOAD_HANDLER
JWT_ENCODE_HANDLER = api_settings.JWT_ENCODE_HANDLER


class UserLoginAPIView(CreateAPIView):
permission_classes = [AllowAny]
serializer_class = UserLoginSerializer

@swagger_auto_schema(operation_summary="User login API")
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
return BaseResponse(data=serializer.data, status_code=status.HTTP_200_OK).success_with_data()


class UserRegistrationAPIView(CreateAPIView):
serializer_class = UserRegisterSerializer
permission_classes = [AllowAny]

@swagger_auto_schema(operation_summary="Create a new user API")
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return BaseResponse(
message="User created successfully.",
status_code=status.HTTP_201_CREATED
).success()


class UserDetailAPIView(RetrieveAPIView):
permission_classes = [AllowAny]
serializer_class = UserListSerializer
queryset = User.objects.all()
lookup_field = 'id'


class UserLogoutAPIView(CreateAPIView):
permission_classes = [IsAuthenticated]
serializer_class = UserLogoutSerializer

@swagger_auto_schema(operation_summary="Logout api")
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return BaseResponse(
message="Successfully logged out.", status_code=status.HTTP_200_OK
).success()


class UserListAPIView(ListAPIView):
permission_classes = [AllowAny]
serializer_class = UserListSerializer
pagination_class = StandardResultsSetPagination
queryset = User.objects.filter(is_active=True)
lookup_field = 'id'

def list(self, request, *args, **kwargs):
serializer_data = self.serializer_class(self.queryset, many=True).data
return BaseResponse(
data=serializer_data,
status_code=status.HTTP_200_OK
).success_with_data()
1 change: 0 additions & 1 deletion user/v1/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import datetime
from abc import ABC

from django.contrib import auth
from django.contrib.auth.password_validation import validate_password
Expand Down
Loading

0 comments on commit a165d99

Please sign in to comment.