Skip to content

Commit

Permalink
Merge branch 'dev' into feature/persistent_shopping_cart
Browse files Browse the repository at this point in the history
  • Loading branch information
michalkrzem authored Nov 25, 2023
2 parents bd129d3 + ac69fee commit f8d966b
Show file tree
Hide file tree
Showing 13 changed files with 1,272 additions and 646 deletions.
31 changes: 29 additions & 2 deletions Dshop/Dshop/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

SITE_ID = 1


INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
Expand All @@ -57,10 +58,11 @@
'django.contrib.sites',
'sorl.thumbnail',
'tinymce',

'rest_framework',
'rest_framework.authtoken',
'drf_spectacular',
] + PROJECT_APPS


MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
Expand Down Expand Up @@ -191,3 +193,28 @@

CART_STORAGE_BACKEND = "dj_shop_cart.storages.DBStorage"

# django-rest-framework
# -------------------------------------------------------------------------------
# django-rest-framework - https://www.django-rest-framework.org/api-guide/settings/
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication",
),
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
"DEFAULT_THROTTLE_RATES": {
# 'registration': '3/day'
},
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}

# drf_spectacular
# -------------------------------------------------------------------------------
# drf_spectacular - https://drf-spectacular.readthedocs.io/en/latest/settings.html#settings
SPECTACULAR_SETTINGS = {
"TITLE": "dshop API",
"DESCRIPTION": "Documentation of API endpoints of dshop",
"VERSION": "1.0.0",
"SERVE_PERMISSIONS": ["rest_framework.permissions.IsAdminUser"],
"SERVE_INCLUDE_SCHEMA": False,
}
Empty file added Dshop/Dshop/tests/__init__.py
Empty file.
17 changes: 13 additions & 4 deletions Dshop/Dshop/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.contrib.sitemaps.views import sitemap
from django.urls import path, include
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
from sitemap import ProductSitemap, StaticViewSitemap, CategorySitemap


sitemaps ={
'products' : ProductSitemap,
'categories': CategorySitemap,
Expand All @@ -37,7 +37,16 @@
path('products/', include('apps.products_catalogue.urls')),
path('payments/', include('apps.payments.urls')),
path('users/', include('apps.users.urls')),
path('tinymce/',include('tinymce.urls')),
path('tinymce/', include('tinymce.urls')),

path('api/users/', include('apps.users.api_urls')),

path("api/schema/", SpectacularAPIView.as_view(), name="api-schema"),
path(
"api/docs/",
SpectacularSwaggerView.as_view(url_name="api-schema"),
name="api-docs",
),
]


Expand Down
9 changes: 9 additions & 0 deletions Dshop/apps/users/api_urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.urls import path

from .api_views import RegistrationViewSet, LoginView

urlpatterns = [
path('register/', RegistrationViewSet.as_view({'post': 'create'}), name='api-register'),
path('login/', LoginView.as_view(), name='api-login'),
]

48 changes: 48 additions & 0 deletions Dshop/apps/users/api_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from django.contrib.auth import get_user_model
from django.contrib.auth import login
from rest_framework import viewsets, mixins, status
from rest_framework.authtoken.models import Token
from rest_framework.generics import GenericAPIView
from rest_framework.permissions import AllowAny
from rest_framework.response import Response

from .models import CustomUser
from .serializers import RegistrationSerializer, LoginSerializer

User = get_user_model()


class RegistrationViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
queryset = User.objects.all()
permission_classes = (AllowAny,)
throttle_scope = 'registration'
serializer_class = RegistrationSerializer

def perform_create(self, serializer):
user = serializer.save()
custom_user = CustomUser(user=user)
custom_user.save()


class LoginView(GenericAPIView):
permission_classes = (AllowAny,)
serializer_class = LoginSerializer
throttle_scope = 'login'

def post(self, request):
serializer = self.get_serializer(data=self.request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data.get('user')
login(request, user)

token, _ = Token.objects.get_or_create(user=user)
if token:
data = {
"token": token.key,
"username": user.username,
"email": user.email
}
return Response(data, status=status.HTTP_200_OK)
else:
msg = {'detail': _('Unable to retrieve user auth token')}
return Response(msg, status=status.HTTP_400_BAD_REQUEST)
7 changes: 7 additions & 0 deletions Dshop/apps/users/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import pytest
from rest_framework.test import APIClient


@pytest.fixture
def api_client():
return APIClient()
51 changes: 51 additions & 0 deletions Dshop/apps/users/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from django.contrib.auth import authenticate, get_user_model
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.serializers import ValidationError

User = get_user_model()


class RegistrationSerializer(serializers.ModelSerializer):
"""
Serializer related to registration of a new user
"""
password = serializers.CharField(style={'input_type': 'password'})
password_again = serializers.CharField(style={'input_type': 'password'})

class Meta:
model = User
fields = ('username', 'email', 'password', 'password_again')

def validate(self, attrs):
if attrs['password'] and attrs['password_again']:
if attrs['password'] != attrs['password_again']:
raise ValidationError(_('Passwords does not match!'))
return attrs

def save(self, **kwargs):
validated_data = self.validated_data
return User.objects.create_user(
username=validated_data['username'],
email=validated_data['email'],
password=validated_data['password']
)


class LoginSerializer(serializers.Serializer):
"""
Login Serializer for User model
"""
username = serializers.CharField()
password = serializers.CharField(style={'input_type': 'password'})

def validate(self, attrs):
username = attrs.pop('username')
password = attrs.pop('password')

user = authenticate(username=username, password=password)

if not user:
raise ValidationError(_('Unable to log in with provided credentials'))
attrs['user'] = user
return attrs
3 changes: 0 additions & 3 deletions Dshop/apps/users/tests.py

This file was deleted.

Empty file.
89 changes: 89 additions & 0 deletions Dshop/apps/users/tests/test_api_login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import json

import pytest
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework import status
from rest_framework.authtoken.models import Token

User = get_user_model()


@pytest.fixture
def login_url():
return reverse('api-login')


@pytest.fixture
def login_data():
return {
'username': 'testuser',
'password': 'testpassword',
}


@pytest.fixture
def user_instance(login_data):
return User.objects.create_user(**login_data)


@pytest.fixture
def user_instance_token(user_instance):
return Token.objects.get_or_create(user=user_instance)[0]


@pytest.mark.django_db
def test_login_success(api_client, login_url, login_data, user_instance, user_instance_token):
response = api_client.post(login_url, login_data, format='json')
content = json.loads(response.content.decode('utf-8'))
assert response.status_code == status.HTTP_200_OK
assert content['username'] == user_instance.username
assert content['email'] == user_instance.email
assert content['token'] == user_instance_token.key


@pytest.mark.django_db
def test_login_empty_data(api_client, login_url):
response = api_client.post(login_url, {}, format='json')
field_errors = json.loads(response.content).keys()
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert len(field_errors) == 2
assert "username" in field_errors
assert "password" in field_errors


@pytest.mark.django_db
def test_login_with_only_username(api_client, login_url, user_instance):
response = api_client.post(login_url, {'username': 'testuser'}, format='json')
field_errors = json.loads(response.content).keys()
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert len(field_errors) == 1
assert "password" in field_errors


@pytest.mark.django_db
def test_login_with_only_password(api_client, login_url, user_instance):
response = api_client.post(login_url, {'password': 'testpassword'}, format='json')
field_errors = json.loads(response.content).keys()
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert len(field_errors) == 1
assert "username" in field_errors


@pytest.mark.django_db
def test_login_with_username_and_empty_password(api_client, login_url, login_data, user_instance):
login_data['password'] = ''
response = api_client.post(login_url, login_data, format='json')
field_errors = json.loads(response.content).keys()
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert len(field_errors) == 1
assert "password" in field_errors


@pytest.mark.django_db
def test_login_wrong_password(api_client, login_url, login_data, user_instance):
login_data['password'] = 'different_password'
response = api_client.post(login_url, login_data, format='json')
field_errors = json.loads(response.content).keys()
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert len(field_errors) == 1
Loading

0 comments on commit f8d966b

Please sign in to comment.