diff --git a/backend/account/admin.py b/backend/account/admin.py index 8c38f3f..029f197 100644 --- a/backend/account/admin.py +++ b/backend/account/admin.py @@ -1,3 +1,9 @@ from django.contrib import admin +from .models import * +from unfold.admin import ModelAdmin -# Register your models here. + +@admin.register(User) +class UserAdmin(ModelAdmin): + list_display = ['phone', 'email', 'is_superuser'] + readonly_fields = ['password', 'last_login', 'otp_expiry'] \ No newline at end of file diff --git a/backend/account/migrations/0001_initial.py b/backend/account/migrations/0001_initial.py new file mode 100644 index 0000000..b0233a5 --- /dev/null +++ b/backend/account/migrations/0001_initial.py @@ -0,0 +1,39 @@ +# Generated by Django 5.1.2 on 2024-12-13 17:49 + +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='User', + 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=255, unique=True, verbose_name='ایمیل')), + ('phone_number', models.CharField(max_length=12, unique=True, verbose_name='شماره تماس')), + ('first_name', models.CharField(blank=True, max_length=50, verbose_name='نام')), + ('last_name', models.CharField(blank=True, max_length=50, verbose_name='نام خانوادگی')), + ('profile_photo', models.ImageField(blank=True, null=True, upload_to='profile_photos/', verbose_name='عکس پروفایل')), + ('is_active', models.BooleanField(default=True)), + ('is_staff', models.BooleanField(default=False)), + ('date_joined', models.DateTimeField(auto_now_add=True, verbose_name='تاریخ ثبتنام')), + ('otp', models.CharField(blank=True, max_length=6, null=True, verbose_name='تاریخ ثبتنام')), + ('otp_expiry', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ تمام شدن otp')), + ('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={ + 'abstract': False, + }, + ), + ] diff --git a/backend/account/migrations/0002_rename_phone_number_user_phone.py b/backend/account/migrations/0002_rename_phone_number_user_phone.py new file mode 100644 index 0000000..255cde3 --- /dev/null +++ b/backend/account/migrations/0002_rename_phone_number_user_phone.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.2 on 2024-12-13 17:51 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='user', + old_name='phone_number', + new_name='phone', + ), + ] diff --git a/backend/account/migrations/0003_alter_user_email.py b/backend/account/migrations/0003_alter_user_email.py new file mode 100644 index 0000000..5d87dc6 --- /dev/null +++ b/backend/account/migrations/0003_alter_user_email.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.2 on 2024-12-13 17:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0002_rename_phone_number_user_phone'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='email', + field=models.EmailField(max_length=255, verbose_name='ایمیل'), + ), + ] diff --git a/backend/account/migrations/0004_alter_user_email.py b/backend/account/migrations/0004_alter_user_email.py new file mode 100644 index 0000000..1e9c8f3 --- /dev/null +++ b/backend/account/migrations/0004_alter_user_email.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.2 on 2024-12-13 19:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0003_alter_user_email'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='email', + field=models.EmailField(blank=True, max_length=255, null=True, verbose_name='ایمیل'), + ), + ] diff --git a/backend/account/migrations/0005_remove_user_groups_remove_user_user_permissions.py b/backend/account/migrations/0005_remove_user_groups_remove_user_user_permissions.py new file mode 100644 index 0000000..324cd14 --- /dev/null +++ b/backend/account/migrations/0005_remove_user_groups_remove_user_user_permissions.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1.2 on 2024-12-13 19:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0004_alter_user_email'), + ] + + operations = [ + migrations.RemoveField( + model_name='user', + name='groups', + ), + migrations.RemoveField( + model_name='user', + name='user_permissions', + ), + ] diff --git a/backend/account/models.py b/backend/account/models.py index 71a8362..cc94052 100644 --- a/backend/account/models.py +++ b/backend/account/models.py @@ -1,3 +1,100 @@ +from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin from django.db import models +from django.utils.translation import gettext_lazy as _ +import random +from datetime import datetime, timedelta +from django.utils import timezone +from rest_framework_simplejwt.token_blacklist.models import BlacklistedToken, OutstandingToken + +class UserManager(BaseUserManager): + def create_user(self, phone, password=None): + if not phone: + raise ValueError('Users must have a phone number') + + user = self.model( + phone=phone, + ) + + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, phone, password=None): + user = self.create_user( + phone=phone, + ) + user.is_staff = True + user.is_superuser = True + + user.set_password(password) + user.save(using=self._db) + return user + + +class User(AbstractBaseUser, PermissionsMixin): + email = models.EmailField(max_length=255, verbose_name='ایمیل', blank=True, null=True) + phone = models.CharField(max_length=12, verbose_name='شماره تماس', unique=True) + first_name = models.CharField(max_length=50, blank=True, verbose_name='نام') + last_name = models.CharField(max_length=50, blank=True, verbose_name='نام خانوادگی') + profile_photo = models.ImageField(upload_to='profile_photos/', null=True, blank=True, verbose_name='عکس پروفایل') + is_active = models.BooleanField(default=True) + is_staff = models.BooleanField(default=False) + date_joined = models.DateTimeField(auto_now_add=True, verbose_name='تاریخ ثبتنام') + otp = models.CharField(max_length=6, blank=True, null=True, verbose_name='تاریخ ثبتنام') + otp_expiry = models.DateTimeField(blank=True, null=True, verbose_name='تاریخ تمام شدن otp') + objects = UserManager() + + USERNAME_FIELD = 'phone' + REQUIRED_FIELDS = [] + + @property + def groups(self): + return None + + @property + def user_permissions(self): + return None + + + + def set_otp(self): + self.otp = str(random.randint(100000, 999999)) + self.otp_expiry = timezone.now() + timedelta(minutes=5) + self.save() + + def clear_otp(self): + self.otp = None + self.otp_expiry = None + self.save() + + def verify_otp(self, otp_code): + if self.otp == otp_code and self.otp_expiry > timezone.now(): + return True + return False + + + + + def blacklist_user_tokens(self): + try: + tokens = OutstandingToken.objects.filter(user=self) + for token in tokens: + BlacklistedToken.objects.get_or_create(token=token) + print('done') + except Exception as e: + print(f"ridi dadash") + + + def __str__(self): + if self.first_name and self.last_name: + return f'{self.first_name} {self.last_name}' + else: + return self.email + + def get_name(self): + if self.first_name and self.last_name: + return f'{self.first_name} {self.last_name}' + else: + return self.email + -# Create your models here. diff --git a/backend/account/serializers.py b/backend/account/serializers.py new file mode 100644 index 0000000..b342d1e --- /dev/null +++ b/backend/account/serializers.py @@ -0,0 +1,11 @@ +from .models import * +from rest_framework import serializers + + + + +class ProfileSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ['first_name', 'last_name', 'email', 'profile_photo', 'phone'] + read_only_fields = ("phone",) diff --git a/backend/account/urls.py b/backend/account/urls.py new file mode 100644 index 0000000..a8cab77 --- /dev/null +++ b/backend/account/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('profile', views.ProfileView.as_view()), +] \ No newline at end of file diff --git a/backend/account/views.py b/backend/account/views.py index 91ea44a..d131306 100644 --- a/backend/account/views.py +++ b/backend/account/views.py @@ -1,3 +1,22 @@ from django.shortcuts import render +from rest_framework.views import APIView +from .serializers import ProfileSerializer +from rest_framework.permissions import IsAuthenticated +from rest_framework import status +from rest_framework.response import Response +class ProfileView(APIView): + serializer_class = ProfileSerializer + permission_classes = [IsAuthenticated] + + def get(self, request): + user_ser = self.serializer_class(instance=request.user) + return Response(user_ser.data, status=status.HTTP_200_OK) -# Create your views here. + + def patch(self, request): + user = request.user + user_ser = self.serializer_class(user, data=request.data, partial=True) + if user_ser.is_valid(): + user_ser.save() + return Response(user_ser.data) + return Response(user_ser.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file diff --git a/backend/core/settings.py b/backend/core/settings.py index c518f31..89d5fe3 100644 --- a/backend/core/settings.py +++ b/backend/core/settings.py @@ -94,7 +94,9 @@ INSTALLED_APPS = [ 'rest_framework.authtoken', 'djoser', # custom apps - 'product' + 'product', + 'account', + 'entertainment', ] MIDDLEWARE = [ @@ -278,7 +280,7 @@ UNFOLD = { { "title": _("محصولات"), "icon": "redeem", - "link": reverse_lazy("admin:product_product_changelist"), + "link": reverse_lazy("admin:product_productmodel_changelist"), }, # esm category model ro lower case bezar inja amir @@ -296,12 +298,32 @@ UNFOLD = { ], }, + + { + "title": _("بخش کاربران و مشتریان"), + "separator": True, + "collapsible": True, + "items": [ + + { + "title": _("users"), + "icon": "person", + "link": reverse_lazy("admin:account_user_changelist"), + }, + + ], + }, + + + + + ], }, } - +AUTH_USER_MODEL = 'account.User' def environment_callback(request): return ["Development", "warning"] diff --git a/backend/core/urls.py b/backend/core/urls.py index 931c2b0..8c977b5 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -5,6 +5,7 @@ from drf_spectacular.views import SpectacularSwaggerView, SpectacularAPIView from django.conf import settings from rest_framework_simplejwt.views import TokenObtainPairView,TokenRefreshView from product import views + urlpatterns = [ # djoser @@ -18,6 +19,7 @@ urlpatterns = [ path('schema/', SpectacularAPIView.as_view(), name='schema'), # path('comment/', views.CommentView.as_view(), name='comment-list'), path('products/', include('product.urls')), + path('accounts/', include('account.urls')), path('', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), ] diff --git a/backend/entertainment/migrations/0001_initial.py b/backend/entertainment/migrations/0001_initial.py new file mode 100644 index 0000000..3c86881 --- /dev/null +++ b/backend/entertainment/migrations/0001_initial.py @@ -0,0 +1,141 @@ +# Generated by Django 5.1.2 on 2024-12-13 17:49 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='abjad', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('word', models.TextField(verbose_name='صورت ی سوال')), + ('image', models.ImageField(blank=True, null=True, upload_to='media/', verbose_name='عکس بازی افتابه')), + ('difficulty_type', models.CharField(choices=[('hard', 'سخت'), ('normal', 'متوسط'), ('easy', 'اسون')], max_length=13, verbose_name='سختی')), + ('answer', models.CharField(max_length=30, verbose_name='جواب')), + ('option2', models.CharField(blank=True, max_length=30, null=True, verbose_name='گزینه ی اشتباه')), + ('option3', models.CharField(blank=True, max_length=30, null=True, verbose_name='گزینه ی اشتباه')), + ('option4', models.CharField(blank=True, max_length=30, null=True, verbose_name='گزینه ی اشتباه')), + ], + options={ + 'verbose_name': 'سوال ابجد', + 'verbose_name_plural': 'سوالات ابجد', + }, + ), + migrations.CreateModel( + name='challenge', + fields=[ + ('type', models.CharField(choices=[('map', 'نقشه ی گنج'), ('prize', 'جوایز')], max_length=30, primary_key=True, serialize=False, verbose_name='نوع چالش')), + ('image', models.ImageField(upload_to='media/', verbose_name='عکس')), + ('link', models.URLField(verbose_name='لینک')), + ('text', models.TextField(verbose_name='متن توضیحات')), + ('button_text', models.CharField(max_length=40, verbose_name='متن دکمه')), + ], + options={ + 'verbose_name': 'چالش', + 'verbose_name_plural': 'چالش ها', + }, + ), + migrations.CreateModel( + name='Dare', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('lang1', models.CharField(max_length=200, verbose_name='فارسی')), + ('is_for_adults', models.BooleanField(verbose_name='+18 سوال')), + ], + options={ + 'verbose_name': 'شجاعت', + 'verbose_name_plural': 'شجاعت ها', + }, + ), + migrations.CreateModel( + name='MovieCategory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=40, verbose_name='نام دسته بندی')), + ], + options={ + 'verbose_name': 'دسته بندی قیلم', + 'verbose_name_plural': 'دسته بندی فیلم ها', + }, + ), + migrations.CreateModel( + name='UploadParent', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=300, verbose_name='نام')), + ('message_id', models.CharField(max_length=40, verbose_name='ای دی پیام تلگرام')), + ], + ), + migrations.CreateModel( + name='MusicCategory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=40, verbose_name='نام دسته بندی')), + ], + options={ + 'verbose_name': 'دسته بندی موزیک', + 'verbose_name_plural': 'دسته بندی موزیک ها', + }, + ), + migrations.CreateModel( + name='Truth', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('lang1', models.CharField(max_length=200, verbose_name='فارسی')), + ('is_for_adults', models.BooleanField(verbose_name='+18 سوال')), + ], + options={ + 'verbose_name': 'حقیقت', + 'verbose_name_plural': 'حقیقت ها', + }, + ), + migrations.CreateModel( + name='Would_you_rather', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('lang1', models.CharField(max_length=200, verbose_name='فارسی')), + ('is_for_adults', models.BooleanField(verbose_name='+18 سوال')), + ], + options={ + 'verbose_name': 'ترجیح میدی', + 'verbose_name_plural': 'ترجیح میدی ها', + }, + ), + migrations.CreateModel( + name='MovieModel', + fields=[ + ('uploadparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='entertainment.uploadparent')), + ('description', models.CharField(blank=True, max_length=4000, null=True, verbose_name='توضیحات فیلم')), + ('receommended', models.BooleanField(default=False, verbose_name='پیشنهادی')), + ('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='entertainment.moviecategory', verbose_name='دسته بندی')), + ], + options={ + 'verbose_name': 'مدل فیلم', + 'verbose_name_plural': 'مدل فیلم ها', + }, + bases=('entertainment.uploadparent',), + ), + migrations.CreateModel( + name='MusicModel', + fields=[ + ('uploadparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='entertainment.uploadparent')), + ('lyric', models.CharField(blank=True, max_length=4000, null=True, verbose_name='متن اهنگ')), + ('singer', models.CharField(blank=True, max_length=300, null=True, verbose_name='خواننده')), + ('trand', models.BooleanField(default=False, verbose_name='ترند')), + ('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='entertainment.musiccategory', verbose_name='دسته بندی')), + ], + options={ + 'verbose_name': 'مدل اهنگ', + 'verbose_name_plural': 'مدل اهنگ ها', + }, + bases=('entertainment.uploadparent',), + ), + ] diff --git a/backend/media/profile_photos/PlaygroundImage32.png b/backend/media/profile_photos/PlaygroundImage32.png new file mode 100644 index 0000000..0e8c477 Binary files /dev/null and b/backend/media/profile_photos/PlaygroundImage32.png differ diff --git a/backend/media/profile_photos/PlaygroundImage32_foW1ctY.png b/backend/media/profile_photos/PlaygroundImage32_foW1ctY.png new file mode 100644 index 0000000..0e8c477 Binary files /dev/null and b/backend/media/profile_photos/PlaygroundImage32_foW1ctY.png differ diff --git a/backend/product/models.py b/backend/product/models.py index 6ee1562..68da356 100644 --- a/backend/product/models.py +++ b/backend/product/models.py @@ -1,6 +1,6 @@ from django.db import models from django.utils.text import slugify -from django.contrib.auth.models import User +from account.models import User from django.urls import reverse class CategoryModel(models.Model): diff --git a/frontend/components/Button.vue b/frontend/components/Button.vue index 2b559d0..46ea375 100644 --- a/frontend/components/Button.vue +++ b/frontend/components/Button.vue @@ -1,45 +1,42 @@ - diff --git a/frontend/components/Input.vue b/frontend/components/Input.vue index eb55311..756718d 100644 --- a/frontend/components/Input.vue +++ b/frontend/components/Input.vue @@ -1,21 +1,20 @@ \ No newline at end of file +
+ + + +
+ + diff --git a/frontend/components/global/Footer.vue b/frontend/components/global/Footer.vue new file mode 100644 index 0000000..a0d73b3 --- /dev/null +++ b/frontend/components/global/Footer.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/frontend/components/global/Header.vue b/frontend/components/global/Header.vue index a72124d..c5c0bd9 100644 --- a/frontend/components/global/Header.vue +++ b/frontend/components/global/Header.vue @@ -1,11 +1,71 @@ - + diff --git a/frontend/layouts/Default.vue b/frontend/layouts/Default.vue index 83d4745..84ac262 100644 --- a/frontend/layouts/Default.vue +++ b/frontend/layouts/Default.vue @@ -11,10 +11,11 @@