diff --git a/backend/apps/authentication/__init__.py b/backend/apps/authentication/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/apps/authentication/admin.py b/backend/apps/authentication/admin.py deleted file mode 100644 index 8c38f3f..0000000 --- a/backend/apps/authentication/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/backend/apps/authentication/apps.py b/backend/apps/authentication/apps.py deleted file mode 100644 index 975015d..0000000 --- a/backend/apps/authentication/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class AuthenticationConfig(AppConfig): - default_auto_field = "django.db.models.BigAutoField" - name = "apps.authentication" diff --git a/backend/apps/authentication/migrations/__init__.py b/backend/apps/authentication/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/apps/authentication/models.py b/backend/apps/authentication/models.py deleted file mode 100644 index 71a8362..0000000 --- a/backend/apps/authentication/models.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/backend/apps/authentication/serializers.py b/backend/apps/authentication/serializers.py deleted file mode 100644 index 2fc587f..0000000 --- a/backend/apps/authentication/serializers.py +++ /dev/null @@ -1,24 +0,0 @@ -from dj_rest_auth.registration.serializers import RegisterSerializer -from rest_framework import serializers - -from apps.users.models import CustomUser - - -class CustomRegisterSerializer(serializers.ModelSerializer): - username = serializers.CharField(required=True) - email = serializers.EmailField(required=True) - - class Meta: - model = CustomUser - fields = "__all__" - - def validate(self, attrs): - username = attrs.get("username") - email = attrs.get("email") - if not username: - raise serializers.ValidationError(("Username is required.")) - - if not email: - raise serializers.ValidationError(("Email is required.")) - - return attrs diff --git a/backend/apps/authentication/tests.py b/backend/apps/authentication/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/backend/apps/authentication/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/backend/apps/authentication/urls.py b/backend/apps/authentication/urls.py deleted file mode 100644 index e9414a9..0000000 --- a/backend/apps/authentication/urls.py +++ /dev/null @@ -1,56 +0,0 @@ -from dj_rest_auth.registration.views import ( - RegisterView, - ResendEmailVerificationView, - VerifyEmailView, -) -from dj_rest_auth.views import ( - LoginView, - LogoutView, - PasswordResetConfirmView, - PasswordResetView, - UserDetailsView, -) -from django.urls import path - -# from .views import ( -# email_confirm_redirect, -# password_reset_confirm_redirect, -# CustomRegisterView, -# ) - - -app_name = "auth" -urlpatterns = [ - path("register/", RegisterView.as_view(), name="rest_register"), - # path("register/", CustomRegisterView.as_view(), name="rest_register"), - path("register/verify-email/", VerifyEmailView.as_view(), name="rest_verify_email"), - path( - "register/resend-email/", - ResendEmailVerificationView.as_view(), - name="rest_resend_email", - ), - # path( - # "account-confirm-email//", - # email_confirm_redirect, - # name="account_confirm_email", - # ), - path( - "account-confirm-email/", - VerifyEmailView.as_view(), - name="account_email_verification_sent", - ), - path("password/reset/", PasswordResetView.as_view(), name="rest_password_reset"), - # path( - # "password/reset/confirm///", - # password_reset_confirm_redirect, - # name="password_reset_confirm", - # ), - path( - "password/reset/confirm/", - PasswordResetConfirmView.as_view(), - name="password_reset_confirm", - ), - path("login/", LoginView.as_view(), name="rest_login"), - path("logout/", LogoutView.as_view(), name="rest_logout"), - path("user/", UserDetailsView.as_view(), name="rest_user_details"), -] diff --git a/backend/apps/authentication/views.py b/backend/apps/authentication/views.py deleted file mode 100644 index 3460f9b..0000000 --- a/backend/apps/authentication/views.py +++ /dev/null @@ -1,23 +0,0 @@ -from django.http import HttpResponseRedirect - -# from config.settings import ( -# EMAIL_CONFIRM_REDIRECT_BASE_URL, -# PASSWORD_RESET_CONFIRM_REDIRECT_BASE_URL, -# ) -# from dj_rest_auth.registration.views import RegisterView - -# from serializers import CustomRegisterSerializer - - -# def email_confirm_redirect(request, key): -# return HttpResponseRedirect(f"{EMAIL_CONFIRM_REDIRECT_BASE_URL}{key}/") - - -# def password_reset_confirm_redirect(request, uidb64, token): -# return HttpResponseRedirect( -# f"{PASSWORD_RESET_CONFIRM_REDIRECT_BASE_URL}{uidb64}/{token}/" -# ) - - -# class CustomRegisterView(RegisterView): -# serializer_class = CustomRegisterSerializer diff --git a/backend/apps/user_profile/admin.py b/backend/apps/user_profile/admin.py index 6ff8e73..272dd32 100644 --- a/backend/apps/user_profile/admin.py +++ b/backend/apps/user_profile/admin.py @@ -10,12 +10,12 @@ class ProfileAdmin(admin.ModelAdmin): list_display = [ "user", + "full_name", "country", "city", "adress", "slug", "created", "modified", - "wallet_address", ] - list_display_links = ["user", "wallet_address"] + list_display_links = ["user", "full_name"] diff --git a/backend/apps/user_profile/migrations/0003_remove_profile_wallet_address_profile_full_name.py b/backend/apps/user_profile/migrations/0003_remove_profile_wallet_address_profile_full_name.py new file mode 100644 index 0000000..fd4d844 --- /dev/null +++ b/backend/apps/user_profile/migrations/0003_remove_profile_wallet_address_profile_full_name.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.1 on 2024-03-24 15:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("user_profile", "0002_remove_profile_full_name_profile_wallet_address"), + ] + + operations = [ + migrations.RemoveField( + model_name="profile", + name="wallet_address", + ), + migrations.AddField( + model_name="profile", + name="full_name", + field=models.CharField( + blank=True, max_length=150, verbose_name="Full name" + ), + ), + ] diff --git a/backend/apps/user_profile/models.py b/backend/apps/user_profile/models.py index 7117b9a..535686b 100644 --- a/backend/apps/user_profile/models.py +++ b/backend/apps/user_profile/models.py @@ -8,7 +8,7 @@ from abstract.constants import Genders, Status, Types from apps.users.models import CustomUser -from config.helpers import is_valid_wallet_address, setFullName +from config.helpers import setFullName # Create your models here. @@ -52,7 +52,7 @@ class Profile(TimeStampedModel): _("First name"), max_length=150, blank=True, null=True ) last_name = models.CharField(_("Last name"), max_length=150, blank=True, null=True) - # full_name = models.CharField(_("Full name"), max_length=150, blank=True) + full_name = models.CharField(_("Full name"), max_length=150, blank=True) profile_key = RandomCharField( _("Profile key"), length=12, unique=True, blank=True, include_alpha=True ) @@ -60,15 +60,6 @@ class Profile(TimeStampedModel): city = models.CharField(_("City"), max_length=50, null=True, blank=True) adress = models.CharField(_("Adress"), max_length=50, null=True, blank=True) additional_information = models.TextField(_("Additional Information"), blank=True) - wallet_address = models.CharField( - _("Wallet address"), - unique=True, - db_index=True, - max_length=100, - blank=True, - null=True, - validators=[is_valid_wallet_address], - ) is_active = models.BooleanField(_("Is active"), default=False) objects = ProfileManager() @@ -106,22 +97,24 @@ class Meta: verbose_name_plural = _("Profiles") def save(self, *args, **kwargs): - if not self.user.username: - is_exists = __class__.objects.filter(user=self.user.username).exists() + if not self.full_name: + full_name = setFullName( + self.first_name if self.first_name is not None else "", + self.last_name if self.last_name is not None else "", + ) + is_exists = __class__.objects.filter(full_name=full_name).exists() if is_exists: - self.user.username = f"{self.user.username}_{self.profile_key}" + self.full_name = f"{full_name}_{self.profile_key}" else: - self.user.username = f"{self.user.username}" - self.slug = slugify(self.user.username) - else: - self.slug = slugify(self.user.username) + self.full_name = f"{full_name}" + self.slug = slugify(self.full_name) super(Profile, self).save(*args, **kwargs) - def get_user_name(self): - return self.user.username + def get_full_name(self): + return self.full_name def __str__(self): - return self.user.username + return self.full_name # create_user_profile diff --git a/backend/apps/users/admin.py b/backend/apps/users/admin.py index 4b9df0a..fb9af2b 100644 --- a/backend/apps/users/admin.py +++ b/backend/apps/users/admin.py @@ -1,12 +1,21 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin +from apps.user_profile.models import Profile + from .forms import CustomUserChangeForm, CustomUserCreationForm from .models import CustomUser # Register your models here. +# !ProfileInline +class ProfileInline(admin.StackedInline): + model = Profile + can_delete = False + verbose_name_plural = "Profiles" + + # !CustomUserAdmin class CustomUserAdmin(UserAdmin): add_form = CustomUserCreationForm @@ -52,6 +61,7 @@ class CustomUserAdmin(UserAdmin): ) search_fields = ("email",) ordering = ("email",) + inlines = (ProfileInline,) admin.site.register(CustomUser, CustomUserAdmin) diff --git a/backend/apps/users/forms.py b/backend/apps/users/forms.py index 85de6bd..2d0e5b7 100644 --- a/backend/apps/users/forms.py +++ b/backend/apps/users/forms.py @@ -7,11 +7,11 @@ class CustomUserCreationForm(UserCreationForm): class Meta: model = CustomUser - exclude = ("email",) + fields = ("email",) # !CustomUserChangeForm class CustomUserChangeForm(UserChangeForm): class Meta: model = CustomUser - exclude = ("email",) + fields = ("email",) diff --git a/backend/apps/users/migrations/0006_delete_customuser.py b/backend/apps/users/migrations/0006_delete_customuser.py new file mode 100644 index 0000000..80f4fd3 --- /dev/null +++ b/backend/apps/users/migrations/0006_delete_customuser.py @@ -0,0 +1,16 @@ +# Generated by Django 5.0.1 on 2024-03-24 14:30 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("users", "0005_customuser_email"), + ] + + operations = [ + migrations.DeleteModel( + name="CustomUser", + ), + ] diff --git a/backend/apps/users/migrations/0007_initial.py b/backend/apps/users/migrations/0007_initial.py new file mode 100644 index 0000000..c8b8052 --- /dev/null +++ b/backend/apps/users/migrations/0007_initial.py @@ -0,0 +1,116 @@ +# Generated by Django 5.0.1 on 2024-03-24 15:48 + +import django.utils.timezone +from django.db import migrations, models + +import config.helpers + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ("users", "0006_delete_customuser"), + ] + + operations = [ + migrations.CreateModel( + name="CustomUser", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "email", + models.EmailField( + max_length=254, + null=True, + unique=True, + verbose_name="Email address", + ), + ), + ( + "wallet_address", + models.CharField( + blank=True, + db_index=True, + max_length=100, + null=True, + unique=True, + validators=[config.helpers.is_valid_wallet_address], + verbose_name="Wallet address", + ), + ), + ( + "is_staff", + models.BooleanField(default=False, verbose_name="Is staff"), + ), + ( + "is_active", + models.BooleanField(default=True, verbose_name="Is active"), + ), + ( + "date_joined", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="Date joined" + ), + ), + ( + "username", + models.CharField( + max_length=100, null=True, 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": "Custom user", + "verbose_name_plural": "Custom users", + "ordering": ["-date_joined"], + }, + ), + ] diff --git a/backend/apps/users/models.py b/backend/apps/users/models.py index f6d9969..e3f0d35 100644 --- a/backend/apps/users/models.py +++ b/backend/apps/users/models.py @@ -13,7 +13,7 @@ # !CustomUser class CustomUser(AbstractBaseUser, PermissionsMixin): - # username = None + username = None email = models.EmailField(_("Email address"), unique=True, null=True) wallet_address = models.CharField( _("Wallet address"), @@ -29,7 +29,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): date_joined = models.DateTimeField(_("Date joined"), default=timezone.now) username = models.CharField(_("Username"), max_length=100, unique=True, null=True) - USERNAME_FIELD = "username" + USERNAME_FIELD = "email" REQUIRED_FIELDS = [] objects = CustomUserManager() diff --git a/backend/config/base.py b/backend/config/base.py index 5f1cfd1..17b6302 100644 --- a/backend/config/base.py +++ b/backend/config/base.py @@ -1,5 +1,6 @@ import mimetypes import os +from datetime import timedelta from pathlib import Path from decouple import config @@ -70,6 +71,7 @@ "allauth.socialaccount", "dj_rest_auth", "dj_rest_auth.registration", + "rest_framework_simplejwt.token_blacklist", ] # AUTHENTICATION_BACKENDS = [ @@ -85,7 +87,7 @@ "apps.upload", "apps.order", "apps.transaction", - "apps.authentication", + # "apps.authentication", ] # !Installed Apps @@ -106,14 +108,14 @@ # ) # !Dj Rest Auth Configuration -ACCOUNT_EMAIL_VERIFICATION = None -ACCOUNT_EMAIL_REQUIRED = True -ACCOUNT_USERNAME_REQUIRED = True -ACCOUNT_UNIQUE_EMAIL = True +# ACCOUNT_EMAIL_VERIFICATION = None +# ACCOUNT_EMAIL_REQUIRED = True +# ACCOUNT_USERNAME_REQUIRED = True +# ACCOUNT_UNIQUE_EMAIL = True -REST_AUTH_REGISTER_SERIALIZERS = { - "REGISTER_SERIALIZER": "apps.authentication.serializers.CustomRegisterSerializer", -} +# REST_AUTH_REGISTER_SERIALIZERS = { +# "REGISTER_SERIALIZER": "apps.authentication.serializers.CustomRegisterSerializer", +# } # !Middleware @@ -288,9 +290,12 @@ # !Rest Framework REST_FRAMEWORK = { "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", - "DEFAULT_AUTHENTICATION_CLASSES": [ - "rest_framework.authentication.TokenAuthentication", - ], + # "DEFAULT_AUTHENTICATION_CLASSES": [ + # "rest_framework.authentication.TokenAuthentication", + # ], + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework_simplejwt.authentication.JWTAuthentication", + ), } # !SPECTACULAR_SETTINGS @@ -352,4 +357,31 @@ # } # } +# !SIMPLE_JWT +SIMPLE_JWT = { + "ACCESS_TOKEN_LIFETIME": timedelta(minutes=15), + "REFRESH_TOKEN_LIFETIME": timedelta(days=14), + "ROTATE_REFRESH_TOKENS": True, + "BLACKLIST_AFTER_ROTATION": True, + "UPDATE_LAST_LOGIN": False, + "ALGORITHM": "HS256", + "SIGNING_KEY": SECRET_KEY, + "VERIFYING_KEY": None, + "AUDIENCE": None, + "ISSUER": None, + "JWK_URL": None, + "LEEWAY": 0, + "AUTH_HEADER_TYPES": ("Bearer",), + "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", + "USER_ID_FIELD": "id", + "USER_ID_CLAIM": "user_id", + "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule", + "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",), + "TOKEN_TYPE_CLAIM": "token_type", + "JTI_CLAIM": "jti", + "SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp", + "SLIDING_TOKEN_LIFETIME": timedelta(minutes=5), + "SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1), +} + print("Configgg ", config("REDIS_HOST"), config("REDIS_PORT"), config("REDIS_DB")) diff --git a/backend/config/urls.py b/backend/config/urls.py index c975bc8..84b745b 100644 --- a/backend/config/urls.py +++ b/backend/config/urls.py @@ -58,9 +58,9 @@ urlpatterns += [ path("transactions/", include("apps.transaction.urls", namespace="transaction")) ] - urlpatterns += [ - path("api/auth/", include("apps.authentication.urls", namespace="auth")) - ] + # urlpatterns += [ + # path("api/auth/", include("apps.authentication.urls", namespace="auth")) + # ] # *Settings Debug diff --git a/backend/poetry.lock b/backend/poetry.lock index 1e6f521..7f8a067 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -889,6 +889,30 @@ files = [ django = ">=3.0" pytz = "*" +[[package]] +name = "djangorestframework-simplejwt" +version = "5.2.2" +description = "A minimal JSON Web Token authentication plugin for Django REST Framework" +optional = false +python-versions = ">=3.7" +files = [ + {file = "djangorestframework_simplejwt-5.2.2-py3-none-any.whl", hash = "sha256:4c0d2e2513e12587d93501ac091781684a216c3ee614eb3b5a10586aef5ca845"}, + {file = "djangorestframework_simplejwt-5.2.2.tar.gz", hash = "sha256:d27d4bcac2c6394f678dea8b4d0d511c6e18a7f2eb8aaeeb8a7de601aeb77c42"}, +] + +[package.dependencies] +django = "*" +djangorestframework = "*" +pyjwt = ">=1.7.1,<3" + +[package.extras] +crypto = ["cryptography (>=3.3.1)"] +dev = ["Sphinx (>=1.6.5,<2)", "cryptography", "flake8", "ipython", "isort", "pep8", "pytest", "pytest-cov", "pytest-django", "pytest-watch", "pytest-xdist", "python-jose (==3.3.0)", "sphinx-rtd-theme (>=0.1.9)", "tox", "twine", "wheel"] +doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)"] +lint = ["flake8", "isort", "pep8"] +python-jose = ["python-jose (==3.3.0)"] +test = ["cryptography", "pytest", "pytest-cov", "pytest-django", "pytest-xdist", "tox"] + [[package]] name = "drf-spectacular" version = "0.27.1" @@ -2365,4 +2389,4 @@ testing = ["coverage (>=5.0)", "pytest", "pytest-cov"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "48c18fe2d85172c29335fe4a20ea4721d9bb2d577d836f6ab943c5b0616db8ba" +content-hash = "e807297302f62d530e292e41d4348fa4fd19755d1947bbe6a2844fd604ff5405" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index e728459..5378510 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -43,6 +43,7 @@ eth-hash = {extras = ["pycryptodome"], version = "^0.7.0"} pytest-xdist = "^3.5.0" django-allauth = "0.52.0" dj-rest-auth = {version = "4.0.0", extras = ["with-social"]} +djangorestframework-simplejwt = "5.2.2" [tool.poetry.group.dev.dependencies]