From 0c3fd4ed0c6ab9ad428bf44b009286f8419943b8 Mon Sep 17 00:00:00 2001 From: Stephen Nwankwo Date: Thu, 6 Jul 2023 12:13:03 +0100 Subject: [PATCH 1/7] [chore]: Add new config settings --- config/settings.py | 41 ++++++++++++++++++++++++++++++++++++++++- config/urls.py | 19 ++----------------- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/config/settings.py b/config/settings.py index 5c262d6..1feba6a 100644 --- a/config/settings.py +++ b/config/settings.py @@ -10,6 +10,7 @@ https://docs.djangoproject.com/en/4.2/ref/settings/ """ +from datetime import timedelta from pathlib import Path from decouple import config @@ -21,7 +22,9 @@ # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-m!7$8b+rxdc9)rpphn7pp$y=rumb0357oxpio$&2bltsgf&gm(' +SECRET_KEY = config('SECRET_KEY') + +# JWT_SECRET = "elonistheonetruegod" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -40,6 +43,14 @@ 'django.contrib.staticfiles', 'treblle', + 'corsheaders', + 'rest_framework', + 'rest_framework.authtoken', + 'rest_framework_simplejwt', + 'djoser', + + 'authme', + 'cars', ] MIDDLEWARE = [ @@ -52,6 +63,7 @@ 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'treblle.middleware.TreblleMiddleware', + # 'corsheaders.middleware.CorsMiddleware', ] ROOT_URLCONF = 'config.urls' @@ -85,6 +97,28 @@ } } +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ], + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAuthenticated', + ], +} + +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(hours=1), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), + 'AUTH_HEADER_TYPES': ('Bearer',), + 'USER_ID_FIELD': 'email', + 'USER_ID_CLAIM': 'email', +} + +AUTHENTICATION_BACKENDS = [ + 'authme.backends.EmailBackend', + 'django.contrib.auth.backends.ModelBackend', +] + # Password validation # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators @@ -131,3 +165,8 @@ 'api_key': config('TREBLLE_API_KEY'), 'project_id': config('TREBLLE_PROJECT_ID'), } + +AUTH_USER_MODEL = 'authme.CustomUser' + +CORS_ORIGIN_ALLOW_ALL = True +CORS_ALLOW_CREDENTIALS = True \ No newline at end of file diff --git a/config/urls.py b/config/urls.py index 74e9efc..ab3bc4b 100644 --- a/config/urls.py +++ b/config/urls.py @@ -1,19 +1,3 @@ -""" -URL configuration for config project. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/4.2/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 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 from django.urls.conf import include @@ -24,7 +8,8 @@ "api/v1/", include( [ - + path("auth/", include("authme.urls")), + path("", include("cars.urls")), ] ), ), From 892ee33c1dc03e5ceadb279f709d20854aa467af Mon Sep 17 00:00:00 2001 From: Stephen Nwankwo Date: Thu, 6 Jul 2023 12:13:41 +0100 Subject: [PATCH 2/7] [chore]: Add authentication app --- authme/__init__.py | 0 authme/admin.py | 3 + authme/apps.py | 6 + authme/backends.py | 22 ++++ authme/migrations/0001_initial.py | 120 ++++++++++++++++++ .../0002_alter_customuser_last_login.py | 19 +++ authme/migrations/__init__.py | 0 authme/models.py | 47 +++++++ authme/permissions.py | 25 ++++ authme/serializers.py | 80 ++++++++++++ authme/tests.py | 3 + authme/urls.py | 12 ++ authme/views.py | 52 ++++++++ 13 files changed, 389 insertions(+) create mode 100644 authme/__init__.py create mode 100644 authme/admin.py create mode 100644 authme/apps.py create mode 100644 authme/backends.py create mode 100644 authme/migrations/0001_initial.py create mode 100644 authme/migrations/0002_alter_customuser_last_login.py create mode 100644 authme/migrations/__init__.py create mode 100644 authme/models.py create mode 100644 authme/permissions.py create mode 100644 authme/serializers.py create mode 100644 authme/tests.py create mode 100644 authme/urls.py create mode 100644 authme/views.py diff --git a/authme/__init__.py b/authme/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authme/admin.py b/authme/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/authme/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/authme/apps.py b/authme/apps.py new file mode 100644 index 0000000..319bc78 --- /dev/null +++ b/authme/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AuthmeConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "authme" diff --git a/authme/backends.py b/authme/backends.py new file mode 100644 index 0000000..e15e04c --- /dev/null +++ b/authme/backends.py @@ -0,0 +1,22 @@ +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import ModelBackend + + +class EmailBackend(ModelBackend): + def authenticate(self, request, email=None, password=None, **kwargs): + UserModel = get_user_model() + try: + user = UserModel.objects.get(email=email) + except UserModel.DoesNotExist: + return None + else: + if user.check_password(password): + return user + return None + + def get_user(self, user_id): + UserModel = get_user_model() + try: + return UserModel.objects.get(pk=user_id) + except UserModel.DoesNotExist: + return None diff --git a/authme/migrations/0001_initial.py b/authme/migrations/0001_initial.py new file mode 100644 index 0000000..7121fc8 --- /dev/null +++ b/authme/migrations/0001_initial.py @@ -0,0 +1,120 @@ +# Generated by Django 4.2.3 on 2023-07-06 03:21 + +import uuid + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ] + + operations = [ + migrations.CreateModel( + name="CustomUser", + fields=[ + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=False, 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", + ), + ), + ( + "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, + primary_key=True, + serialize=False, + unique=True, + ), + ), + ("created", models.DateTimeField(auto_now_add=True)), + ("last_updated", models.DateTimeField(auto_now=True)), + ( + "email", + models.EmailField( + max_length=254, unique=True, verbose_name="email address" + ), + ), + ( + "username", + models.CharField( + max_length=150, unique=True, verbose_name="username" + ), + ), + ( + "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={ + "verbose_name": "user", + "verbose_name_plural": "users", + }, + ), + ] diff --git a/authme/migrations/0002_alter_customuser_last_login.py b/authme/migrations/0002_alter_customuser_last_login.py new file mode 100644 index 0000000..e645bf6 --- /dev/null +++ b/authme/migrations/0002_alter_customuser_last_login.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.3 on 2023-07-06 03:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("authme", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="customuser", + name="last_login", + field=models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ] diff --git a/authme/migrations/__init__.py b/authme/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authme/models.py b/authme/models.py new file mode 100644 index 0000000..e7cb703 --- /dev/null +++ b/authme/models.py @@ -0,0 +1,47 @@ +from django.contrib.auth.models import AbstractUser, BaseUserManager +from django.contrib.auth.validators import UnicodeUsernameValidator +from django.db import models +from django.utils.translation import gettext_lazy as _ +from utils.models import TrackObjectStateMixin + + +class CustomUserManager(BaseUserManager): + def create_user( + self, + email, + password=None, + username: str = None, + is_staff: bool = False, + is_superuser: bool = False, + **extra_fields + ): + if not email: + raise ValueError("The Email field must be set") + + email = self.normalize_email(email) + user = self.model(email=email, **extra_fields) + user.set_password(password) + user.username = username + user.is_active = True + user.is_staff = is_staff + user.is_superuser = is_superuser + user.save(using=self._db) + return user + + +class CustomUser(TrackObjectStateMixin, AbstractUser): + email = models.EmailField(_("email address"), unique=True) + username = models.CharField(_("username"), max_length=150, unique=True) + # username = None + + USERNAME_FIELD = "email" + REQUIRED_FIELDS = ["username"] + + objects = CustomUserManager() + + class Meta: + verbose_name = _("user") + verbose_name_plural = _("users") + + def __str__(self): + return self.email diff --git a/authme/permissions.py b/authme/permissions.py new file mode 100644 index 0000000..ad7ce90 --- /dev/null +++ b/authme/permissions.py @@ -0,0 +1,25 @@ +from rest_framework.permissions import BasePermission + + +class IsAdminUserOrReadOnly(BasePermission): + def has_permission(self, request, view): + if request.method == "POST": + return request.user.is_superuser + if request.method == "PUT": + return request.user.is_superuser + if request.method == "DELETE": + return request.user.is_superuser + return True + + +class ProtectAllMethods(BasePermission): + def has_permission(self, request, view): + if request.method == "GET": + return request.user.is_superuser + if request.method == "POST": + return request.user.is_superuser + if request.method == "PUT": + return request.user.is_superuser + if request.method == "DELETE": + return request.user.is_superuser + return True diff --git a/authme/serializers.py b/authme/serializers.py new file mode 100644 index 0000000..88b9a74 --- /dev/null +++ b/authme/serializers.py @@ -0,0 +1,80 @@ +from django.contrib.auth import authenticate +from rest_framework import serializers +from rest_framework_simplejwt.tokens import RefreshToken + +from .models import CustomUser + + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = CustomUser + fields = ["email"] + + +class RegisterSerializer(serializers.ModelSerializer): + password1 = serializers.CharField(write_only=True, required=True, min_length=8) + password2 = serializers.CharField(write_only=True, required=True, min_length=8) + + class Meta: + model = CustomUser + fields = ["email", "username", "password1", "password2"] + + def validate(self, attrs): + if attrs["password1"] != attrs["password2"]: + raise serializers.ValidationError("Passwords do not match.") + return attrs + + def create(self, validated_data): + user = CustomUser.objects.create_user( + username=validated_data["username"], + email=validated_data["email"], + password=validated_data["password1"], + ) + return user + + +# class LoginSerializer(serializers.Serializer): +# email = serializers.EmailField() +# password = serializers.CharField(write_only=True, min_length=8) + +# def validate(self, data): +# email = data.get('email') +# password = data.get('password') + +# if email and password: +# user = authenticate(email=email, password=password) +# if user: +# if not user.is_active: +# raise serializers.ValidationError('User account is disabled.') +# data['user'] = user +# else: +# raise serializers.ValidationError('Unable to log in with provided credentials.') +# else: +# raise serializers.ValidationError('Must include "email" and "password" fields.') + +# return data + +# class TokenSerializer(serializers.Serializer): +# refresh = serializers.CharField() +# access = serializers.CharField() + + +class UserDetailsSerializer(serializers.ModelSerializer): + class Meta: + model = CustomUser + exclude = [ + "password", + # "is_staff", + # "is_superuser", + "groups", + "user_permissions", + ] + read_only_fields = [ + "email", + "last_updated", + "created", + "uuid", + "date_joined", + "is_active", + "last_login", + ] diff --git a/authme/tests.py b/authme/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/authme/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/authme/urls.py b/authme/urls.py new file mode 100644 index 0000000..6e9174c --- /dev/null +++ b/authme/urls.py @@ -0,0 +1,12 @@ +from django.urls import path +from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView + +# from .views import RegisterView, LoginView, TokenRefreshView, UserDetailsView +from .views import RegisterView, UserDetailsView + +urlpatterns = [ + path("register/", RegisterView.as_view(), name="register"), + path("login/", TokenObtainPairView.as_view(), name="login"), + path("token/refresh/", TokenRefreshView.as_view(), name="token-refresh"), + path("users/me/", UserDetailsView.as_view(), name="user-details"), +] diff --git a/authme/views.py b/authme/views.py new file mode 100644 index 0000000..b79b8fd --- /dev/null +++ b/authme/views.py @@ -0,0 +1,52 @@ +from rest_framework import status, views, viewsets +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework_simplejwt.tokens import RefreshToken + +from .models import CustomUser +from .serializers import RegisterSerializer, UserDetailsSerializer + + +class RegisterView(views.APIView): + permission_classes = [] + + def post(self, request, format=None): + serializer = RegisterSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(status=status.HTTP_201_CREATED) + + +# class LoginView(views.APIView): +# permission_classes = [] +# def post(self, request, format=None): +# data = request.data +# content_type = request.content_type + +# if content_type == 'application/x-www-form-urlencoded': +# data = dict(request.data) + +# serializer = LoginSerializer(data=data) +# serializer.is_valid(raise_exception=True) +# # serializer.save() +# user = serializer.validated_data +# refresh = RefreshToken.for_user(user) +# token_serializer = TokenSerializer({'refresh': str(refresh), 'access': str(refresh.access_token)}) + +# return Response(token_serializer.data) + +# class TokenRefreshView(views.APIView): +# def post(self, request, format=None): +# serializer = TokenSerializer(data=request.data) +# serializer.is_valid(raise_exception=True) +# refresh = RefreshToken(serializer.validated_data['refresh']) +# token_serializer = TokenSerializer({'refresh': str(refresh), 'access': str(refresh.access_token)}) +# return Response(token_serializer.data) + + +class UserDetailsView(views.APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, format=None): + serializer = UserDetailsSerializer(request.user) + return Response(serializer.data) From 0a079e5a266a43f29e10ee35c8ef40ecb520d59d Mon Sep 17 00:00:00 2001 From: Stephen Nwankwo Date: Thu, 6 Jul 2023 12:14:43 +0100 Subject: [PATCH 3/7] [chore]: Add app to manage autogo car services --- cars/__init__.py | 0 cars/admin.py | 3 + cars/apps.py | 6 + cars/migrations/0001_initial.py | 212 ++++++++++++++++++ ...s_created_reviews_last_updated_and_more.py | 35 +++ cars/migrations/0003_cars_booked_by.py | 19 ++ cars/migrations/__init__.py | 0 cars/models/__init__.py | 0 cars/models/brands.py | 16 ++ cars/models/cars.py | 41 ++++ cars/models/categories.py | 10 + cars/models/features.py | 10 + cars/models/rented_cars.py | 21 ++ cars/models/reviews.py | 18 ++ cars/serializers/__init__.py | 0 cars/serializers/brands.py | 18 ++ cars/serializers/cars.py | 83 +++++++ cars/serializers/categories.py | 18 ++ cars/serializers/features.py | 18 ++ cars/serializers/reviews.py | 30 +++ cars/tests.py | 3 + cars/urls.py | 34 +++ cars/views/__init__.py | 0 cars/views/brands.py | 24 ++ cars/views/cars.py | 95 ++++++++ cars/views/categories.py | 22 ++ cars/views/features.py | 24 ++ cars/views/reviews.py | 53 +++++ 28 files changed, 813 insertions(+) create mode 100644 cars/__init__.py create mode 100644 cars/admin.py create mode 100644 cars/apps.py create mode 100644 cars/migrations/0001_initial.py create mode 100644 cars/migrations/0002_reviews_created_reviews_last_updated_and_more.py create mode 100644 cars/migrations/0003_cars_booked_by.py create mode 100644 cars/migrations/__init__.py create mode 100644 cars/models/__init__.py create mode 100644 cars/models/brands.py create mode 100644 cars/models/cars.py create mode 100644 cars/models/categories.py create mode 100644 cars/models/features.py create mode 100644 cars/models/rented_cars.py create mode 100644 cars/models/reviews.py create mode 100644 cars/serializers/__init__.py create mode 100644 cars/serializers/brands.py create mode 100644 cars/serializers/cars.py create mode 100644 cars/serializers/categories.py create mode 100644 cars/serializers/features.py create mode 100644 cars/serializers/reviews.py create mode 100644 cars/tests.py create mode 100644 cars/urls.py create mode 100644 cars/views/__init__.py create mode 100644 cars/views/brands.py create mode 100644 cars/views/cars.py create mode 100644 cars/views/categories.py create mode 100644 cars/views/features.py create mode 100644 cars/views/reviews.py diff --git a/cars/__init__.py b/cars/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cars/admin.py b/cars/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/cars/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/cars/apps.py b/cars/apps.py new file mode 100644 index 0000000..9401e5b --- /dev/null +++ b/cars/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CarsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "cars" diff --git a/cars/migrations/0001_initial.py b/cars/migrations/0001_initial.py new file mode 100644 index 0000000..1cb896e --- /dev/null +++ b/cars/migrations/0001_initial.py @@ -0,0 +1,212 @@ +# Generated by Django 4.2.3 on 2023-07-05 13:55 + +import uuid + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Brand", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + primary_key=True, + serialize=False, + unique=True, + ), + ), + ("created", models.DateTimeField(auto_now_add=True)), + ("last_updated", models.DateTimeField(auto_now=True)), + ( + "name", + models.CharField(max_length=255, unique=True, verbose_name="Name"), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="Cars", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + primary_key=True, + serialize=False, + unique=True, + ), + ), + ("created", models.DateTimeField(auto_now_add=True)), + ("last_updated", models.DateTimeField(auto_now=True)), + ( + "name", + models.CharField(max_length=255, unique=True, verbose_name="Name"), + ), + ("description", models.TextField(max_length=255)), + ( + "price", + models.DecimalField(decimal_places=2, max_digits=1000000000000), + ), + ("pickup_time", models.DateTimeField()), + ("dropoff_time", models.DateTimeField()), + ( + "pickup_location", + models.CharField( + max_length=255, unique=True, verbose_name="Pickup Location" + ), + ), + ( + "dropoff_location", + models.CharField( + max_length=255, unique=True, verbose_name="Dropoff Location" + ), + ), + ("available", models.BooleanField(default=True)), + ( + "brand", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="cars.brand" + ), + ), + ], + options={ + "ordering": ["-created"], + }, + ), + migrations.CreateModel( + name="Category", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + primary_key=True, + serialize=False, + unique=True, + ), + ), + ("created", models.DateTimeField(auto_now_add=True)), + ("last_updated", models.DateTimeField(auto_now=True)), + ( + "name", + models.CharField(max_length=255, unique=True, verbose_name="Name"), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="Features", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + primary_key=True, + serialize=False, + unique=True, + ), + ), + ("created", models.DateTimeField(auto_now_add=True)), + ("last_updated", models.DateTimeField(auto_now=True)), + ( + "name", + models.CharField(max_length=255, unique=True, verbose_name="Name"), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="Reviews", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("content", models.TextField()), + ("rating", models.DecimalField(decimal_places=1, max_digits=2)), + ( + "car", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="reviews", + to="cars.cars", + ), + ), + ( + "user", + models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.CreateModel( + name="RentedCars", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + primary_key=True, + serialize=False, + unique=True, + ), + ), + ("created", models.DateTimeField(auto_now_add=True)), + ("last_updated", models.DateTimeField(auto_now=True)), + ( + "car", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="cars.cars" + ), + ), + ], + options={ + "ordering": ["-last_updated"], + }, + ), + migrations.AddField( + model_name="cars", + name="category", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="cars.category" + ), + ), + migrations.AddField( + model_name="cars", + name="features", + field=models.ManyToManyField(to="cars.features"), + ), + migrations.AddField( + model_name="cars", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), + ), + ] diff --git a/cars/migrations/0002_reviews_created_reviews_last_updated_and_more.py b/cars/migrations/0002_reviews_created_reviews_last_updated_and_more.py new file mode 100644 index 0000000..28f27b5 --- /dev/null +++ b/cars/migrations/0002_reviews_created_reviews_last_updated_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.3 on 2023-07-06 06:05 + +import uuid + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("cars", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="reviews", + name="created", + field=models.DateTimeField( + auto_now_add=True, default=django.utils.timezone.now + ), + preserve_default=False, + ), + migrations.AddField( + model_name="reviews", + name="last_updated", + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name="reviews", + name="id", + field=models.UUIDField( + default=uuid.uuid4, primary_key=True, serialize=False, unique=True + ), + ), + ] diff --git a/cars/migrations/0003_cars_booked_by.py b/cars/migrations/0003_cars_booked_by.py new file mode 100644 index 0000000..685ec74 --- /dev/null +++ b/cars/migrations/0003_cars_booked_by.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.3 on 2023-07-06 10:06 + +import uuid + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("cars", "0002_reviews_created_reviews_last_updated_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="cars", + name="booked_by", + field=models.UUIDField(blank=True, default=uuid.uuid4, unique=True), + ), + ] diff --git a/cars/migrations/__init__.py b/cars/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cars/models/__init__.py b/cars/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cars/models/brands.py b/cars/models/brands.py new file mode 100644 index 0000000..9156df0 --- /dev/null +++ b/cars/models/brands.py @@ -0,0 +1,16 @@ +from django.db import models +from django.urls.base import reverse +from utils.models import TrackObjectStateMixin + + +class Brand(TrackObjectStateMixin): + name = models.CharField(max_length=255, unique=True, verbose_name="Name") + + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse( + "cars:brand-detail", + kwargs={"id": self.id}, + ) diff --git a/cars/models/cars.py b/cars/models/cars.py new file mode 100644 index 0000000..d685da8 --- /dev/null +++ b/cars/models/cars.py @@ -0,0 +1,41 @@ +import uuid + +from django.contrib.auth import get_user_model +from django.db import models +from django.urls.base import reverse +from utils.models import TrackObjectStateMixin + +User = get_user_model() + + +class Cars(TrackObjectStateMixin): + user = models.ForeignKey(User, on_delete=models.CASCADE) + name = models.CharField( + max_length=255, blank=False, unique=True, verbose_name="Name" + ) + description = models.TextField(max_length=255, blank=False) + price = models.DecimalField(blank=False, max_digits=1000000000000, decimal_places=2) + brand = models.ForeignKey("Brand", on_delete=models.CASCADE) + category = models.ForeignKey("Category", on_delete=models.CASCADE) + features = models.ManyToManyField("Features") + pickup_time = models.DateTimeField() + dropoff_time = models.DateTimeField() + pickup_location = models.CharField( + max_length=255, unique=True, verbose_name="Pickup Location" + ) + dropoff_location = models.CharField( + max_length=255, unique=True, verbose_name="Dropoff Location" + ) + available = models.BooleanField(default=True) + booked_by = models.UUIDField(default=uuid.uuid4, unique=True, blank=True) + + OWNER_FIELD = "user" + + class Meta: + ordering = ["-created"] + + def get_reviews(self): + return self.reviews.all() + + def __str__(self): + return f"{self.brand} {self.name}" diff --git a/cars/models/categories.py b/cars/models/categories.py new file mode 100644 index 0000000..a99be24 --- /dev/null +++ b/cars/models/categories.py @@ -0,0 +1,10 @@ +from django.db import models +from django.urls.base import reverse +from utils.models import TrackObjectStateMixin + + +class Category(TrackObjectStateMixin): + name = models.CharField(max_length=255, unique=True, verbose_name="Name") + + def __str__(self): + return self.name diff --git a/cars/models/features.py b/cars/models/features.py new file mode 100644 index 0000000..2b5bac5 --- /dev/null +++ b/cars/models/features.py @@ -0,0 +1,10 @@ +from django.db import models +from django.urls.base import reverse +from utils.models import TrackObjectStateMixin + + +class Features(TrackObjectStateMixin): + name = models.CharField(max_length=255, unique=True, verbose_name="Name") + + def __str__(self): + return self.name diff --git a/cars/models/rented_cars.py b/cars/models/rented_cars.py new file mode 100644 index 0000000..0cf535d --- /dev/null +++ b/cars/models/rented_cars.py @@ -0,0 +1,21 @@ +from django.contrib.auth import get_user_model +from django.db import models +from django.urls.base import reverse +from utils.models import TrackObjectStateMixin + +User = get_user_model() + +from .cars import Cars + + +class RentedCars(TrackObjectStateMixin): + car = models.ForeignKey("Cars", on_delete=models.CASCADE) + # user = models.ForeignKey(User, on_delete=models.CASCADE) # Add this line + + # OWNER_FIELD = "user" + + class Meta: + ordering = ["-last_updated"] + + def __str__(self): + return self.name diff --git a/cars/models/reviews.py b/cars/models/reviews.py new file mode 100644 index 0000000..ffbe882 --- /dev/null +++ b/cars/models/reviews.py @@ -0,0 +1,18 @@ +from django.contrib.auth import get_user_model +from django.db import models +from django.urls.base import reverse +from utils.models import TrackObjectStateMixin + +from .cars import Cars + +User = get_user_model() + + +class Reviews(TrackObjectStateMixin): + car = models.ForeignKey("Cars", on_delete=models.CASCADE, related_name="reviews") + user = models.ForeignKey(User, on_delete=models.CASCADE, editable=False) + content = models.TextField() + rating = models.DecimalField(max_digits=2, decimal_places=1) + + def __str__(self): + return f"Review for {self.car} by {self.user}" diff --git a/cars/serializers/__init__.py b/cars/serializers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cars/serializers/brands.py b/cars/serializers/brands.py new file mode 100644 index 0000000..f1caa20 --- /dev/null +++ b/cars/serializers/brands.py @@ -0,0 +1,18 @@ +from django.shortcuts import get_object_or_404 +from rest_framework import serializers + +from ..models.brands import Brand +from ..models.cars import Cars +from ..models.categories import Category +from ..models.features import Features +from ..models.rented_cars import RentedCars +from ..models.reviews import Reviews + + +class BrandSerializer(serializers.ModelSerializer): + cars = serializers.SlugRelatedField(many=True, read_only=True, slug_field="name") + + class Meta: + model = Brand + read_only_fields = ("id",) + fields = "__all__" diff --git a/cars/serializers/cars.py b/cars/serializers/cars.py new file mode 100644 index 0000000..dd80179 --- /dev/null +++ b/cars/serializers/cars.py @@ -0,0 +1,83 @@ +from django.shortcuts import get_object_or_404 +from rest_framework import serializers + +from ..models.brands import Brand +from ..models.cars import Cars +from ..models.categories import Category +from ..models.features import Features +from ..models.rented_cars import RentedCars +from ..models.reviews import Reviews +from .reviews import ReviewsSerializer + + +class CarsSerializer(serializers.ModelSerializer): + user = serializers.ReadOnlyField(source="user.id") + features = serializers.SlugRelatedField( + many=True, queryset=Features.objects.all(), slug_field="name" + ) + brand = serializers.SlugRelatedField( + queryset=Brand.objects.all(), slug_field="name" + ) + category = serializers.SlugRelatedField( + queryset=Category.objects.all(), slug_field="name" + ) + reviews = ReviewsSerializer(many=True, read_only=True) + + class Meta: + model = Cars + fields = [ + "id", + "user", + "name", + "description", + "price", + "brand", + "category", + "features", + "pickup_time", + "dropoff_time", + "pickup_location", + "dropoff_location", + "available", + "reviews", + "available", + ] + read_only_fields = ("id",) + # extra_kwargs = {"reviews": {"required": False, "allow_null": True}} + # lookup_field = "id" + # extra_kwargs = { + # "url": {"lookup_field": ("id")}, + # } + + +class CarRentalSerializer(serializers.ModelSerializer): + # price = serializers.SlugRelatedField( + # many=True, + # queryset=Cars.objects.all(), + # slug_field='price' + # ) + class Meta: + model = RentedCars + fields = ("__all__",) + read_only_fields = ("id",) + lookup_field = "id" + extra_kwargs = { + "url": {"lookup_field": ("id")}, + } + + def create(self, validated_data): + return RentedCars.objects.create(**validated_data) + + +class RentedCarsSerializer(serializers.ModelSerializer): + cars = CarsSerializer( + # many=True, + read_only=True, + # slug_field='name' + ) + + class Meta: + model = RentedCars + fields = "__all__" + # read_only_fields = ("id",) + # fields = ("car", "tracking_number",) diff --git a/cars/serializers/categories.py b/cars/serializers/categories.py new file mode 100644 index 0000000..857423e --- /dev/null +++ b/cars/serializers/categories.py @@ -0,0 +1,18 @@ +from django.shortcuts import get_object_or_404 +from rest_framework import serializers + +from ..models.brands import Brand +from ..models.cars import Cars +from ..models.categories import Category +from ..models.features import Features +from ..models.rented_cars import RentedCars +from ..models.reviews import Reviews + + +class CategorySerializer(serializers.ModelSerializer): + cars = serializers.SlugRelatedField(many=True, read_only=True, slug_field="name") + + class Meta: + model = Category + read_only_fields = ("id",) + fields = "__all__" diff --git a/cars/serializers/features.py b/cars/serializers/features.py new file mode 100644 index 0000000..7dc2852 --- /dev/null +++ b/cars/serializers/features.py @@ -0,0 +1,18 @@ +from django.shortcuts import get_object_or_404 +from rest_framework import serializers + +from ..models.brands import Brand +from ..models.cars import Cars +from ..models.categories import Category +from ..models.features import Features +from ..models.rented_cars import RentedCars +from ..models.reviews import Reviews + + +class FeaturesSerializer(serializers.ModelSerializer): + cars = serializers.SlugRelatedField(many=True, read_only=True, slug_field="name") + + class Meta: + model = Features + read_only_fields = ("id",) + fields = "__all__" diff --git a/cars/serializers/reviews.py b/cars/serializers/reviews.py new file mode 100644 index 0000000..584fdae --- /dev/null +++ b/cars/serializers/reviews.py @@ -0,0 +1,30 @@ +from django.shortcuts import get_object_or_404 +from rest_framework import serializers + +from ..models.brands import Brand +from ..models.cars import Cars +from ..models.categories import Category +from ..models.features import Features +from ..models.rented_cars import RentedCars +from ..models.reviews import Reviews + +# class ReviewsSerializer(serializers.ModelSerializer): +# car = serializers.SlugRelatedField( +# many=True, +# read_only=True, +# slug_field='name' +# ) + +# class Meta: +# model = Reviews +# read_only_fields = ("id",) +# fields = "__all__" + + +class ReviewsSerializer(serializers.ModelSerializer): + user = serializers.ReadOnlyField(source="user.id") + + class Meta: + model = Reviews + fields = ["id", "user", "content", "rating"] + read_only_fields = ["user"] diff --git a/cars/tests.py b/cars/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/cars/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/cars/urls.py b/cars/urls.py new file mode 100644 index 0000000..386ee38 --- /dev/null +++ b/cars/urls.py @@ -0,0 +1,34 @@ +from django.urls import include, path +from rest_framework.routers import DefaultRouter + +from .views.brands import BrandView +from .views.cars import CarDetail, CarList, CarRentView, RentedCarsView # CarsView, +from .views.categories import CategoryView +from .views.features import FeaturesView +from .views.reviews import ReviewDetail, ReviewList, ReviewsView + +router = DefaultRouter() +router.register(r"cars", CarList, basename="cars") +router.register(r"reviews", ReviewList, basename="reviews") + +urlpatterns = [ + path("", include(router.urls)), + path("features/", FeaturesView.as_view(), name="features-list-create"), + path("categories/", CategoryView.as_view(), name="category-list-create"), + path("brands/", BrandView.as_view(), name="brand-list-create"), + # path("reviews/", ReviewsView.as_view(), name="review-list-create"), + # -------------------- + path("cars//rent/", CarRentView.as_view(), name="book-car"), + path("rented-cars/", RentedCarsView.as_view(), name="booked-cars"), + # ----------------------- + # path('cars//', CarDetail.as_view(), name='car-detail'), + # ------------------------ + path( + "cars//reviews/", + CarList.as_view({"get": "reviews", "post": "add_reviews"}), + name="car-reviews", + ), + path("cars//reviews//", ReviewDetail.as_view(), name="review-detail"), + # ------------------------- + # path('cars//features/', ReviewList.as_view(), name='review-list'), +] diff --git a/cars/views/__init__.py b/cars/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cars/views/brands.py b/cars/views/brands.py new file mode 100644 index 0000000..38917f3 --- /dev/null +++ b/cars/views/brands.py @@ -0,0 +1,24 @@ +from authme.permissions import IsAdminUserOrReadOnly +from django.shortcuts import get_object_or_404 + +# from requests import Response +from rest_framework import generics, permissions, status, viewsets +from rest_framework.response import Response +from rest_framework.views import APIView + +from ..models.brands import Brand +from ..models.cars import Cars +from ..models.categories import Category +from ..models.features import Features +from ..models.rented_cars import RentedCars +from ..models.reviews import Reviews +from ..serializers.brands import BrandSerializer +from ..serializers.categories import CategorySerializer +from ..serializers.features import FeaturesSerializer +from ..serializers.reviews import ReviewsSerializer + + +class BrandView(generics.ListCreateAPIView): + permission_classes = [permissions.IsAuthenticated & IsAdminUserOrReadOnly] + queryset = Brand.objects.all() + serializer_class = BrandSerializer diff --git a/cars/views/cars.py b/cars/views/cars.py new file mode 100644 index 0000000..0dc79ec --- /dev/null +++ b/cars/views/cars.py @@ -0,0 +1,95 @@ +from authme.permissions import IsAdminUserOrReadOnly +from django.shortcuts import get_object_or_404 +from rest_framework import generics, permissions, status, viewsets +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.views import APIView + +from ..models.brands import Brand +from ..models.cars import Cars +from ..models.categories import Category +from ..models.features import Features +from ..models.rented_cars import RentedCars +from ..models.reviews import Reviews +from ..serializers.brands import BrandSerializer +from ..serializers.cars import CarRentalSerializer, CarsSerializer, RentedCarsSerializer +from ..serializers.categories import CategorySerializer +from ..serializers.features import FeaturesSerializer +from ..serializers.reviews import ReviewsSerializer + +# class CarsView(generics.ListCreateAPIView): +# permission_classes = [] +# queryset = Cars.objects.all() +# # queryset = Cars.objects.filter(available=True) +# serializer_class = CarsSerializer + +# class CarList(generics.ListCreateAPIView): +# permission_classes = [] +# queryset = Cars.objects.all() +# serializer_class = CarsSerializer + + +class CarList(viewsets.ModelViewSet): + permission_classes = [permissions.IsAuthenticated] + queryset = Cars.objects.filter(available=True) + serializer_class = CarsSerializer + + @action(detail=True, methods=["get"]) + def reviews(self, request, pk=None): + car = self.get_object() + reviews = car.get_reviews() + serializer = ReviewsSerializer(reviews, many=True) + return Response(serializer.data) + + @action(detail=True, methods=["post"]) + def add_reviews(self, request, pk=None): + car = self.get_object() + serializer = ReviewsSerializer(data=request.data) + if serializer.is_valid(): + serializer.save(user=request.user, car=car) + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + def perform_create(self, serializer): + serializer.save(user=self.request.user) + + +class CarDetail(generics.RetrieveUpdateDestroyAPIView): + permission_classes = [permissions.IsAuthenticated] + queryset = Cars.objects.all() + serializer_class = CarsSerializer + + +class RentedCarsView(generics.ListAPIView): + permission_classes = [IsAdminUserOrReadOnly] + serializer_class = RentedCarsSerializer + + def get_queryset(self): + return RentedCars.objects.all() + + +class UserRentedCarsView(generics.ListAPIView): + permission_classes = [IsAdminUserOrReadOnly] + serializer_class = RentedCarsSerializer + + def get_queryset(self): + return Cars.objects.filter(booked_by=self.request.user) + + +class CarRentView(generics.GenericAPIView): + permission_classes = [permissions.IsAuthenticated] + serializer_class = CarsSerializer + + def post(self, request, car_id): + try: + car = Cars.objects.get(pk=car_id, available=True) + car.available = False + car.booked_by = request.user + car.save() + # rented_car = RentedCars.objects.create(car=car, user=request.user) + rented_car = RentedCars.objects.create(car=car) + serializer = CarsSerializer(car) + message = "Car booked successfully. Proceed to the pickup location." + return Response({"car": serializer.data, "message": message}) + except Cars.DoesNotExist: + return Response({"error": "Car not found."}, status=404) diff --git a/cars/views/categories.py b/cars/views/categories.py new file mode 100644 index 0000000..1d29864 --- /dev/null +++ b/cars/views/categories.py @@ -0,0 +1,22 @@ +from authme.permissions import IsAdminUserOrReadOnly +from rest_framework import generics, permissions, status, viewsets +from rest_framework.response import Response +from rest_framework.views import APIView + +from ..models.brands import Brand +from ..models.cars import Cars +from ..models.categories import Category +from ..models.features import Features +from ..models.rented_cars import RentedCars +from ..models.reviews import Reviews +from ..serializers.brands import BrandSerializer +from ..serializers.cars import CarRentalSerializer, CarsSerializer, RentedCarsSerializer +from ..serializers.categories import CategorySerializer +from ..serializers.features import FeaturesSerializer +from ..serializers.reviews import ReviewsSerializer + + +class CategoryView(generics.ListCreateAPIView): + permission_classes = [permissions.IsAuthenticated & IsAdminUserOrReadOnly] + queryset = Category.objects.all() + serializer_class = CategorySerializer diff --git a/cars/views/features.py b/cars/views/features.py new file mode 100644 index 0000000..dcaa850 --- /dev/null +++ b/cars/views/features.py @@ -0,0 +1,24 @@ +from authme.permissions import IsAdminUserOrReadOnly +from rest_framework import generics, permissions, status, viewsets +from rest_framework.response import Response +from rest_framework.views import APIView + +from ..models.brands import Brand +from ..models.cars import Cars +from ..models.categories import Category +from ..models.features import Features +from ..models.rented_cars import RentedCars +from ..models.reviews import Reviews +from ..serializers.brands import BrandSerializer +from ..serializers.cars import CarRentalSerializer, CarsSerializer, RentedCarsSerializer +from ..serializers.categories import CategorySerializer +from ..serializers.features import FeaturesSerializer +from ..serializers.reviews import ReviewsSerializer + + +class FeaturesView(generics.ListCreateAPIView): + permission_classes = [permissions.IsAuthenticated & IsAdminUserOrReadOnly] + queryset = Features.objects.all() + serializer_class = FeaturesSerializer + # filter_backends = (filters.DjangoFilterBackend,) + # filter_class = LibraryFilter diff --git a/cars/views/reviews.py b/cars/views/reviews.py new file mode 100644 index 0000000..a22c773 --- /dev/null +++ b/cars/views/reviews.py @@ -0,0 +1,53 @@ +from authme.permissions import IsAdminUserOrReadOnly, ProtectAllMethods +from django.shortcuts import get_object_or_404 +from rest_framework import generics, permissions, status, viewsets +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.views import APIView + +from ..models.brands import Brand +from ..models.cars import Cars +from ..models.categories import Category +from ..models.features import Features +from ..models.rented_cars import RentedCars +from ..models.reviews import Reviews +from ..serializers.brands import BrandSerializer +from ..serializers.cars import CarRentalSerializer, CarsSerializer, RentedCarsSerializer +from ..serializers.categories import CategorySerializer +from ..serializers.features import FeaturesSerializer +from ..serializers.reviews import ReviewsSerializer + + +class ReviewsView(generics.ListCreateAPIView): + permission_classes = [] + queryset = Reviews.objects.all() + serializer_class = ReviewsSerializer + + +class ReviewList(viewsets.ModelViewSet): + permission_classes = [permissions.IsAuthenticated] + queryset = Reviews.objects.all() + serializer_class = ReviewsSerializer + + def perform_create(self, serializer): + # serializer.save(user=self.request.user) + serializer.save() + + +class ReviewDetail(generics.RetrieveUpdateDestroyAPIView): + permission_classes = [ProtectAllMethods] + serializer_class = ReviewsSerializer + + def get_queryset(self): + car_id = self.kwargs.get("car_id") + review_id = self.kwargs.get("pk") + try: + car = Cars.objects.get(id=car_id) + return Reviews.objects.filter(id=review_id, car=car) + except Cars.DoesNotExist: + return Reviews.objects.none() + + def get_object(self): + queryset = self.get_queryset() + obj = generics.get_object_or_404(queryset) + return obj From 68b24c9b59d2a13691a7f753d10817e129ce8c44 Mon Sep 17 00:00:00 2001 From: Stephen Nwankwo Date: Thu, 6 Jul 2023 12:15:25 +0100 Subject: [PATCH 4/7] [chore]: Add utilities dirrectory --- utils/models.py | 12 ++++++++++++ utils/serializers.py | 11 +++++++++++ 2 files changed, 23 insertions(+) create mode 100644 utils/models.py create mode 100644 utils/serializers.py diff --git a/utils/models.py b/utils/models.py new file mode 100644 index 0000000..8564bee --- /dev/null +++ b/utils/models.py @@ -0,0 +1,12 @@ +import uuid + +from django.db import models + + +class TrackObjectStateMixin(models.Model): + id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True) + created = models.DateTimeField(auto_now_add=True) + last_updated = models.DateTimeField(auto_now=True) + + class Meta: + abstract = True diff --git a/utils/serializers.py b/utils/serializers.py new file mode 100644 index 0000000..2243bca --- /dev/null +++ b/utils/serializers.py @@ -0,0 +1,11 @@ +from rest_framework import serializers + + +class BaseSerializer(serializers.ModelSerializer): + class Meta: + fields = "__all__" + lookup_fields = "pk" + read_only_fields = ("pk", "uuid") + extra_kwargs = { + "url": {"lookup_field": ("pk")}, + } From 638bf56ff3a5777e19196c55a1b24400b05d4af3 Mon Sep 17 00:00:00 2001 From: Stephen Nwankwo Date: Thu, 6 Jul 2023 12:17:23 +0100 Subject: [PATCH 5/7] [chore]: update dependencies and env variables --- .env.example | 1 + Pipfile | 9 ++ Pipfile.lock | 382 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 388 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index 67f04cd..cb529d4 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ TREBLLE_PROJECT_ID='add project id without quote' TREBLLE_API_KEY='add api key without quote' +SECRET_KEY="add django secret key without quote" \ No newline at end of file diff --git a/Pipfile b/Pipfile index 681bd24..b27d7a8 100644 --- a/Pipfile +++ b/Pipfile @@ -7,6 +7,15 @@ name = "pypi" django = "*" treblle = "*" python-decouple = "*" +pyjwt = "*" +black = "*" +django-cors-headers = "==1.1.0" +djangorestframework = "*" +djoser = "*" +pandas = "*" +install = "*" +djangorestframework-simplejwt = "*" +isort = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 9e2c7f3..bac1578 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f4a80c15610102e4f2b3498a3e7017931f315a9001ad7ae5082520a5e1a62224" + "sha256": "f940c7c2ab3d7de835bf66a8f624bbae29f1db042b467f5eafb22531b38430ee" }, "pipfile-spec": 6, "requires": { @@ -24,6 +24,37 @@ "markers": "python_version >= '3.7'", "version": "==3.7.2" }, + "black": { + "hashes": [ + "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5", + "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915", + "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326", + "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940", + "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b", + "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30", + "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c", + "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c", + "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab", + "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27", + "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2", + "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961", + "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9", + "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb", + "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70", + "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331", + "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2", + "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266", + "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d", + "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6", + "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b", + "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925", + "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8", + "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4", + "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3" + ], + "index": "pypi", + "version": "==23.3.0" + }, "certifi": { "hashes": [ "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7", @@ -32,6 +63,75 @@ "markers": "python_version >= '3.6'", "version": "==2023.5.7" }, + "cffi": { + "hashes": [ + "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", + "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", + "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", + "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", + "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", + "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", + "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", + "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", + "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", + "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", + "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", + "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", + "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", + "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", + "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", + "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", + "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", + "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", + "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", + "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", + "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", + "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", + "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", + "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", + "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", + "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", + "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", + "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", + "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", + "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", + "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", + "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", + "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", + "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", + "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", + "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", + "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", + "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", + "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", + "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", + "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", + "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", + "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", + "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", + "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", + "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", + "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", + "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", + "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", + "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", + "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", + "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", + "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", + "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", + "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", + "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", + "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", + "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", + "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", + "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", + "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", + "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", + "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", + "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" + ], + "version": "==1.15.1" + }, "charset-normalizer": { "hashes": [ "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", @@ -113,13 +213,100 @@ "markers": "python_full_version >= '3.7.0'", "version": "==3.1.0" }, + "click": { + "hashes": [ + "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", + "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.3" + }, + "colorama": { + "hashes": [ + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + ], + "markers": "platform_system == 'Windows'", + "version": "==0.4.6" + }, + "cryptography": { + "hashes": [ + "sha256:059e348f9a3c1950937e1b5d7ba1f8e968508ab181e75fc32b879452f08356db", + "sha256:1a5472d40c8f8e91ff7a3d8ac6dfa363d8e3138b961529c996f3e2df0c7a411a", + "sha256:1a8e6c2de6fbbcc5e14fd27fb24414507cb3333198ea9ab1258d916f00bc3039", + "sha256:1fee5aacc7367487b4e22484d3c7e547992ed726d14864ee33c0176ae43b0d7c", + "sha256:5d092fdfedaec4cbbffbf98cddc915ba145313a6fdaab83c6e67f4e6c218e6f3", + "sha256:5f0ff6e18d13a3de56f609dd1fd11470918f770c6bd5d00d632076c727d35485", + "sha256:7bfc55a5eae8b86a287747053140ba221afc65eb06207bedf6e019b8934b477c", + "sha256:7fa01527046ca5facdf973eef2535a27fec4cb651e4daec4d043ef63f6ecd4ca", + "sha256:8dde71c4169ec5ccc1087bb7521d54251c016f126f922ab2dfe6649170a3b8c5", + "sha256:8f4ab7021127a9b4323537300a2acfb450124b2def3756f64dc3a3d2160ee4b5", + "sha256:948224d76c4b6457349d47c0c98657557f429b4e93057cf5a2f71d603e2fc3a3", + "sha256:9a6c7a3c87d595608a39980ebaa04d5a37f94024c9f24eb7d10262b92f739ddb", + "sha256:b46e37db3cc267b4dea1f56da7346c9727e1209aa98487179ee8ebed09d21e43", + "sha256:b4ceb5324b998ce2003bc17d519080b4ec8d5b7b70794cbd2836101406a9be31", + "sha256:cb33ccf15e89f7ed89b235cff9d49e2e62c6c981a6061c9c8bb47ed7951190bc", + "sha256:d198820aba55660b4d74f7b5fd1f17db3aa5eb3e6893b0a41b75e84e4f9e0e4b", + "sha256:d34579085401d3f49762d2f7d6634d6b6c2ae1242202e860f4d26b046e3a1006", + "sha256:eb8163f5e549a22888c18b0d53d6bb62a20510060a22fd5a995ec8a05268df8a", + "sha256:f73bff05db2a3e5974a6fd248af2566134d8981fd7ab012e5dd4ddb1d9a70699" + ], + "markers": "python_version >= '3.7'", + "version": "==41.0.1" + }, + "defusedxml": { + "hashes": [ + "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", + "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==0.7.1" + }, "django": { "hashes": [ - "sha256:2a6b6fbff5b59dd07bef10bcb019bee2ea97a30b2a656d51346596724324badf", - "sha256:672b3fa81e1f853bb58be1b51754108ab4ffa12a77c06db86aa8df9ed0c46fe5" + "sha256:45a747e1c5b3d6df1b141b1481e193b033fd1fdbda3ff52677dc81afdaacbaed", + "sha256:f7c7852a5ac5a3da5a8d5b35cc6168f31b605971441798dac845f17ca8028039" + ], + "index": "pypi", + "version": "==4.2.3" + }, + "django-cors-headers": { + "hashes": [ + "sha256:fcd96e2be47c8eef34c650e007a6d546e19e7ee61041b89edbbbbe7619aa3987" + ], + "index": "pypi", + "version": "==1.1.0" + }, + "django-templated-mail": { + "hashes": [ + "sha256:8db807effebb42a532622e2d142dfd453dafcd0d7794c4c3332acb90656315f9", + "sha256:f7127e1e31d7cad4e6c4b4801d25814d4b8782627ead76f4a75b3b7650687556" + ], + "version": "==1.1.1" + }, + "djangorestframework": { + "hashes": [ + "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8", + "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08" + ], + "index": "pypi", + "version": "==3.14.0" + }, + "djangorestframework-simplejwt": { + "hashes": [ + "sha256:4c0d2e2513e12587d93501ac091781684a216c3ee614eb3b5a10586aef5ca845", + "sha256:d27d4bcac2c6394f678dea8b4d0d511c6e18a7f2eb8aaeeb8a7de601aeb77c42" ], "index": "pypi", - "version": "==4.2.2" + "version": "==5.2.2" + }, + "djoser": { + "hashes": [ + "sha256:4aa48502df870c8b5f07109ad4a749cc881c37bb5efa85cf5462ea695a0dca8c", + "sha256:7b24718cdc51b4294b0abcf6bf0ead11aa3ca83652e351dfb04b7b8b15afa3b0" + ], + "index": "pypi", + "version": "==2.2.0" }, "idna": { "hashes": [ @@ -129,6 +316,147 @@ "markers": "python_version >= '3.5'", "version": "==3.4" }, + "install": { + "hashes": [ + "sha256:0d3fadf4aa62c95efe8d34757c8507eb46177f86c016c21c6551eafc6a53d5a9", + "sha256:e67c8a0be5ccf8cb4ffa17d090f3a61b6e820e6a7e21cd1d2c0f7bc59b18e647" + ], + "index": "pypi", + "version": "==1.3.5" + }, + "isort": { + "hashes": [ + "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504", + "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6" + ], + "index": "pypi", + "version": "==5.12.0" + }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" + }, + "numpy": { + "hashes": [ + "sha256:0ac6edfb35d2a99aaf102b509c8e9319c499ebd4978df4971b94419a116d0790", + "sha256:26815c6c8498dc49d81faa76d61078c4f9f0859ce7817919021b9eba72b425e3", + "sha256:4aedd08f15d3045a4e9c648f1e04daca2ab1044256959f1f95aafeeb3d794c16", + "sha256:4c69fe5f05eea336b7a740e114dec995e2f927003c30702d896892403df6dbf0", + "sha256:5177310ac2e63d6603f659fadc1e7bab33dd5a8db4e0596df34214eeab0fee3b", + "sha256:5aa48bebfb41f93043a796128854b84407d4df730d3fb6e5dc36402f5cd594c0", + "sha256:5b1b90860bf7d8a8c313b372d4f27343a54f415b20fb69dd601b7efe1029c91e", + "sha256:6c284907e37f5e04d2412950960894b143a648dea3f79290757eb878b91acbd1", + "sha256:6d183b5c58513f74225c376643234c369468e02947b47942eacbb23c1671f25d", + "sha256:7412125b4f18aeddca2ecd7219ea2d2708f697943e6f624be41aa5f8a9852cc4", + "sha256:7cd981ccc0afe49b9883f14761bb57c964df71124dcd155b0cba2b591f0d64b9", + "sha256:85cdae87d8c136fd4da4dad1e48064d700f63e923d5af6c8c782ac0df8044542", + "sha256:8aa130c3042052d656751df5e81f6d61edff3e289b5994edcf77f54118a8d9f4", + "sha256:95367ccd88c07af21b379be1725b5322362bb83679d36691f124a16357390153", + "sha256:9c7211d7920b97aeca7b3773a6783492b5b93baba39e7c36054f6e749fc7490c", + "sha256:9e3f2b96e3b63c978bc29daaa3700c028fe3f049ea3031b58aa33fe2a5809d24", + "sha256:b76aa836a952059d70a2788a2d98cb2a533ccd46222558b6970348939e55fc24", + "sha256:b792164e539d99d93e4e5e09ae10f8cbe5466de7d759fc155e075237e0c274e4", + "sha256:c0dc071017bc00abb7d7201bac06fa80333c6314477b3d10b52b58fa6a6e38f6", + "sha256:cc3fda2b36482891db1060f00f881c77f9423eead4c3579629940a3e12095fe8", + "sha256:d6b267f349a99d3908b56645eebf340cb58f01bd1e773b4eea1a905b3f0e4208", + "sha256:d76a84998c51b8b68b40448ddd02bd1081bb33abcdc28beee6cd284fe11036c6", + "sha256:e559c6afbca484072a98a51b6fa466aae785cfe89b69e8b856c3191bc8872a82", + "sha256:ecc68f11404930e9c7ecfc937aa423e1e50158317bf67ca91736a9864eae0232", + "sha256:f1accae9a28dc3cda46a91de86acf69de0d1b5f4edd44a9b0c3ceb8036dfff19" + ], + "markers": "python_version >= '3.10'", + "version": "==1.25.0" + }, + "oauthlib": { + "hashes": [ + "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", + "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918" + ], + "markers": "python_version >= '3.6'", + "version": "==3.2.2" + }, + "packaging": { + "hashes": [ + "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", + "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" + ], + "markers": "python_version >= '3.7'", + "version": "==23.1" + }, + "pandas": { + "hashes": [ + "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682", + "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc", + "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b", + "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089", + "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5", + "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26", + "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210", + "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b", + "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641", + "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd", + "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78", + "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b", + "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e", + "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061", + "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0", + "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e", + "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8", + "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d", + "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0", + "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c", + "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183", + "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df", + "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8", + "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f", + "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02" + ], + "index": "pypi", + "version": "==2.0.3" + }, + "pathspec": { + "hashes": [ + "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687", + "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293" + ], + "markers": "python_version >= '3.7'", + "version": "==0.11.1" + }, + "platformdirs": { + "hashes": [ + "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc", + "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e" + ], + "markers": "python_version >= '3.7'", + "version": "==3.8.0" + }, + "pycparser": { + "hashes": [ + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + ], + "version": "==2.21" + }, + "pyjwt": { + "hashes": [ + "sha256:ba2b425b15ad5ef12f200dc67dd56af4e26de2331f965c5439994dad075876e1", + "sha256:bd6ca4a3c4285c1a2d4349e5a035fdf8fb94e04ccd0fcbe6ba289dae9cc3e074" + ], + "index": "pypi", + "version": "==2.7.0" + }, + "python-dateutil": { + "hashes": [ + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.8.2" + }, "python-decouple": { "hashes": [ "sha256:ba6e2657d4f376ecc46f77a3a615e058d93ba5e465c01bbe57289bfb7cce680f", @@ -137,6 +465,20 @@ "index": "pypi", "version": "==3.8" }, + "python3-openid": { + "hashes": [ + "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf", + "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b" + ], + "version": "==3.2.0" + }, + "pytz": { + "hashes": [ + "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588", + "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb" + ], + "version": "==2023.3" + }, "requests": { "hashes": [ "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", @@ -145,6 +487,38 @@ "markers": "python_version >= '3.7'", "version": "==2.31.0" }, + "requests-oauthlib": { + "hashes": [ + "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5", + "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.3.1" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "social-auth-app-django": { + "hashes": [ + "sha256:0347ca4cd23ea9d15a665da9d22950552fb66b95600e6c2ebae38ca883b3a4ed", + "sha256:4a5dae406f3874b4003708ff120c02cb1a4c8eeead56cd163646347309fcd0f8" + ], + "markers": "python_version >= '3.7'", + "version": "==5.2.0" + }, + "social-auth-core": { + "hashes": [ + "sha256:9791d7c7aee2ac8517fe7a2ea2f942a8a5492b3a4ccb44a9b0dacc87d182f2aa", + "sha256:ea7a19c46b791b767e95f467881b53c5fd0d1efb40048d9ed3dbc46daa05c954" + ], + "markers": "python_version >= '3.6'", + "version": "==4.4.2" + }, "sqlparse": { "hashes": [ "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3", From 3c7af87e796ce8f70687e19d4b8cba39ff7dadd8 Mon Sep 17 00:00:00 2001 From: Stephen Nwankwo Date: Thu, 6 Jul 2023 12:17:53 +0100 Subject: [PATCH 6/7] [chore]: updat readme --- README.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 205805a..6a3a0d6 100644 --- a/README.md +++ b/README.md @@ -24,4 +24,34 @@ pipenv shell pipenv install ``` - Create a copy of `.env.example` and rename to `.env` then fill in the values ->- find the treblle keys on the treblle dashboard \ No newline at end of file +>- find the treblle keys on the treblle dashboard + +- Create admin user with: +> whilst in project directory +```shell +python manage.py shell + +>>> from auth.me import CustomUser + +>>> CustomUser.objects.create_user(email="youremail@example.com",username="johnDoe707", password="strongpassword", is_staff=True, is_superuser=True) +``` + +# Testing API + +Postman Docs: [click here📬](https://documenter.getpostman.com/view/16596786/2s93zFXKTM) + +Swagger Docs: + +- use this to generate datetime object to fill into respective fields +```python +from datetime import datetime + +t4 = datetime(year = 2018, month = 7, day = 12, hour = 7, minute = 9, second = 33) +t5 = datetime(year = 2018, month = 7, day = 14, hour = 5, minute = 55, second = 13) +t6 = t5 - t4 + + +print("t4 =", t4) +print("t5 =", t5) +print("t6 =", t6) +``` From 36f37aebd6013e65c56cca1a9d226786e317d0fb Mon Sep 17 00:00:00 2001 From: Valentine Maduagwu Date: Thu, 6 Jul 2023 14:11:47 +0100 Subject: [PATCH 7/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a3a0d6..ff7680a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ pipenv install ```shell python manage.py shell ->>> from auth.me import CustomUser +>>> from authme.models import CustomUser >>> CustomUser.objects.create_user(email="youremail@example.com",username="johnDoe707", password="strongpassword", is_staff=True, is_superuser=True) ```