diff --git a/core/admin.py b/core/admin.py index abd125e..b7c13db 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,11 +1,13 @@ from django.contrib import admin from core.models.customer import Customer +from core.models.Subscription import Subscription from core.models.role import Role from core.models.image import Image from core.models.video import Video from core.models.glb import Glb from core.models.pdf import Pdf +from core.models.TeamMember import TeamMember # from .model import user from core.models.AssignedRule import AssignedRule @@ -17,3 +19,5 @@ admin.site.register(Glb) admin.site.register(Role) admin.site.register(AssignedRule) admin.site.register(Customer) +admin.site.register(Subscription) +admin.site.register(TeamMember) diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py index 6984443..84aee35 100644 --- a/core/migrations/0001_initial.py +++ b/core/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0 on 2025-04-12 14:25 +# Generated by Django 5.0 on 2025-05-21 07:58 import django.db.models.deletion from django.conf import settings @@ -25,9 +25,10 @@ class Migration(migrations.Migration): name='Customer', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('profile_img', models.CharField(max_length=255)), - ('profile_glb', models.CharField(max_length=255)), + ('semat', models.CharField(blank=True, max_length=255)), ('mobile_number', models.CharField(max_length=15)), + ('profile_img', models.CharField(blank=True, max_length=255)), + ('profile_glb', models.CharField(blank=True, max_length=255)), ('verification_sms_code', models.CharField(blank=True, max_length=6)), ('verification_email_code', models.CharField(blank=True, max_length=6)), ('is_sms_verified', models.BooleanField(default=False)), @@ -36,6 +37,39 @@ class Migration(migrations.Migration): ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), + migrations.CreateModel( + name='Glb', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('url', models.URLField(blank=True, max_length=250)), + ('glb', models.FileField(blank=True, null=True, upload_to='user_glbs/')), + ('name', models.CharField(max_length=100)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Image', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('url', models.URLField(blank=True, max_length=250)), + ('image', models.ImageField(blank=True, null=True, upload_to='user_images/')), + ('name', models.CharField(max_length=100)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Pdf', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('url', models.URLField(blank=True, max_length=250)), + ('pdf', models.FileField(blank=True, null=True, upload_to='user_pdfs/')), + ('name', models.CharField(max_length=100)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), migrations.CreateModel( name='AssignedRule', fields=[ @@ -44,4 +78,27 @@ class Migration(migrations.Migration): ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.role')), ], ), + migrations.CreateModel( + name='Subscription', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('license_number', models.CharField(max_length=100)), + ('user_count', models.PositiveIntegerField(blank=True, default=0)), + ('startTime', models.DateTimeField()), + ('endTime', models.DateTimeField()), + ('price', models.DecimalField(decimal_places=2, max_digits=30)), + ('user', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Video', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('url', models.URLField(blank=True, max_length=250)), + ('video', models.FileField(blank=True, null=True, upload_to='user_videos/')), + ('name', models.CharField(max_length=100)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), ] diff --git a/core/migrations/0002_alter_customer_profile_glb_and_more.py b/core/migrations/0002_alter_customer_profile_glb_and_more.py deleted file mode 100644 index b8c7b08..0000000 --- a/core/migrations/0002_alter_customer_profile_glb_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.0 on 2025-04-12 14:28 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='customer', - name='profile_glb', - field=models.CharField(blank=True, max_length=255), - ), - migrations.AlterField( - model_name='customer', - name='profile_img', - field=models.CharField(blank=True, max_length=255), - ), - ] diff --git a/core/migrations/0002_teammember.py b/core/migrations/0002_teammember.py new file mode 100644 index 0000000..a6b6841 --- /dev/null +++ b/core/migrations/0002_teammember.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0 on 2025-05-21 12:48 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='TeamMember', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('isAdmin', models.BooleanField(default=False)), + ('adminUser', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='admin_user', to=settings.AUTH_USER_MODEL)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('user', 'adminUser')}, + }, + ), + ] diff --git a/core/migrations/0003_customer_name.py b/core/migrations/0003_customer_name.py deleted file mode 100644 index b0a9dae..0000000 --- a/core/migrations/0003_customer_name.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.0 on 2025-04-13 11:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0002_alter_customer_profile_glb_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='customer', - name='name', - field=models.CharField(blank=True, max_length=255), - ), - ] diff --git a/core/migrations/0004_remove_customer_name_image.py b/core/migrations/0004_remove_customer_name_image.py deleted file mode 100644 index 314f857..0000000 --- a/core/migrations/0004_remove_customer_name_image.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 5.0 on 2025-04-19 10:02 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0003_customer_name'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.RemoveField( - model_name='customer', - name='name', - ), - migrations.CreateModel( - name='Image', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('url', models.URLField(blank=True, max_length=250)), - ('image', models.ImageField(blank=True, null=True, upload_to='user_images/')), - ('name', models.CharField(max_length=100)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - ] diff --git a/core/migrations/0005_glb_pdf_video.py b/core/migrations/0005_glb_pdf_video.py deleted file mode 100644 index 6eccc0b..0000000 --- a/core/migrations/0005_glb_pdf_video.py +++ /dev/null @@ -1,49 +0,0 @@ -# Generated by Django 5.0 on 2025-04-19 11:42 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0004_remove_customer_name_image'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='Glb', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('url', models.URLField(blank=True, max_length=250)), - ('glb', models.ImageField(blank=True, null=True, upload_to='user_glbs/')), - ('name', models.CharField(max_length=100)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='Pdf', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('url', models.URLField(blank=True, max_length=250)), - ('pdf', models.ImageField(blank=True, null=True, upload_to='user_pdfs/')), - ('name', models.CharField(max_length=100)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='Video', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('url', models.URLField(blank=True, max_length=250)), - ('video', models.ImageField(blank=True, null=True, upload_to='user_videos/')), - ('name', models.CharField(max_length=100)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - ] diff --git a/core/migrations/migrations.rar b/core/migrations/migrations.rar new file mode 100644 index 0000000..0cdfc8a Binary files /dev/null and b/core/migrations/migrations.rar differ diff --git a/core/models/Subscription.py b/core/models/Subscription.py new file mode 100644 index 0000000..2a485d8 --- /dev/null +++ b/core/models/Subscription.py @@ -0,0 +1,24 @@ +from django.db import models +from django.conf import settings + + + +class Subscription(models.Model): + # uid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) + user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, on_delete=models.CASCADE) + + license_number = models.CharField(max_length=100) + user_count = models.PositiveIntegerField(default = 0,blank=True) # changed from license_number CharField + + startTime = models.DateTimeField() + endTime = models.DateTimeField() + price = models.DecimalField(max_digits=30, decimal_places=2) + + def __str__(self): + return f"Subscription {self.user_count} - License: {self.license_number}" + + + + + + \ No newline at end of file diff --git a/core/models/TeamMember.py b/core/models/TeamMember.py new file mode 100644 index 0000000..0862fcf --- /dev/null +++ b/core/models/TeamMember.py @@ -0,0 +1,13 @@ +from django.db import models +from django.conf import settings + +class TeamMember(models.Model): + user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='user', on_delete=models.CASCADE) + adminUser = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='admin_user', on_delete=models.CASCADE) + isAdmin = models.BooleanField(default=False) + + def __str__(self): + return f"TeamMember {self.user} - Admin: {self.isAdmin}" + + class Meta: + unique_together = ('user', 'adminUser') # Ensure that one user can be added only once per admin diff --git a/core/serializers/SubscriptionSerializer.py b/core/serializers/SubscriptionSerializer.py new file mode 100644 index 0000000..e9cf8d1 --- /dev/null +++ b/core/serializers/SubscriptionSerializer.py @@ -0,0 +1,21 @@ +from rest_framework import serializers +from core.models.Subscription import Subscription + + + +class SubscriptionSerializer(serializers.ModelSerializer): + class Meta: + model = Subscription + fields = ['user', 'license_number', 'user_count', 'startTime', 'endTime', 'price'] + read_only_fields = ['user'] # user will be set automatically by the view + + def create(self, validated_data): + """ + Override the create method to ensure the user is assigned automatically. + This is important since 'user' is not part of the data coming from the client + but is assigned based on the authenticated user. + """ + # The user is automatically added by the view (request.user) + user = validated_data.pop('user', None) # We do not expect user to be passed from the client + subscription = Subscription.objects.create(**validated_data, user=user) + return subscription diff --git a/core/serializers/TeamMemberSerializer.py b/core/serializers/TeamMemberSerializer.py new file mode 100644 index 0000000..f48cda8 --- /dev/null +++ b/core/serializers/TeamMemberSerializer.py @@ -0,0 +1,16 @@ +from rest_framework import serializers +from core.models.TeamMember import TeamMember + +class TeamMemberSerializer(serializers.ModelSerializer): + class Meta: + model = TeamMember + fields = ['user', 'adminUser', 'isAdmin'] + read_only_fields = ['adminUser'] + + def create(self, validated_data): + """ + Override the create method to automatically assign the admin user (request.user). + """ + admin_user = validated_data.pop('adminUser', None) + team_member = TeamMember.objects.create(adminUser=self.context['request'].user, **validated_data) + return team_member diff --git a/core/urls.py b/core/urls.py index c855aee..1d62dd5 100644 --- a/core/urls.py +++ b/core/urls.py @@ -44,7 +44,11 @@ urlpatterns = [ re_path('uploadGlb/', userView.upload_glb , name='upload_glb'), re_path('uploadVideo/', userView.upload_video , name='upload_video'), re_path('uploadPdf/', userView.upload_pdf , name='upload_pdf'), - + + re_path('add_subscription', userView.addSubscription), + re_path('add_teamMember', userView.addTeamMember), + + ]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/core/views/userView.py b/core/views/userView.py index 3fec2be..6a85300 100644 --- a/core/views/userView.py +++ b/core/views/userView.py @@ -20,6 +20,8 @@ from core.serializers.ImageSerializer import ImageSerializer from core.serializers.GlbSerializer import GlbSerializer from core.serializers.VideoSerializer import VideoSerializer from core.serializers.PdfSerializer import PdfSerializer +from core.serializers.SubscriptionSerializer import SubscriptionSerializer +from core.serializers.TeamMemberSerializer import TeamMemberSerializer # utils.py from core.models.AssignedRule import AssignedRule @@ -27,7 +29,7 @@ from core.models.image import Image from core.models.video import Video from core.models.pdf import Pdf from core.models.glb import Glb - +from core.models.Subscription import Subscription from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string @@ -122,6 +124,9 @@ def signup(request): else: return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + + @api_view(['POST']) def sendForgetPasswordCode(request): try: @@ -315,12 +320,16 @@ def getInfo(request): user_videos = Video.objects.filter(user=user).order_by('-created_at') user_glbs = Glb.objects.filter(user=user).order_by('-created_at') + user_subscriptions = Subscription.objects.filter(user=user) + customer_serializer = CustomerSerializer(customer) image_serializer = ImageSerializer(user_images, many=True) pdf_serializer = PdfSerializer(user_pdfs, many=True) video_serializer = VideoSerializer(user_videos, many=True) glb_serializer = GlbSerializer(user_glbs, many=True) + subscription_serializer = SubscriptionSerializer(user_subscriptions, many=True) + user_data = { 'id': user.id, 'first_name': user.first_name, @@ -336,6 +345,7 @@ def getInfo(request): 'pdfs': pdf_serializer.data, 'videos': video_serializer.data, 'glbs': glb_serializer.data, + 'subscription': subscription_serializer.data, }, 'message': 'موفق' }, status=status.HTTP_200_OK) @@ -947,4 +957,166 @@ def verifyResetCode(request): return Response( {'success': False, 'message': f'Error resetting password: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR - ) \ No newline at end of file + ) + + + + + +@api_view(['POST']) +@authentication_classes([SessionAuthentication, TokenAuthentication]) +@permission_classes([IsAuthenticated]) +def addSubscription(request): + # Make a mutable copy of the request data + data = request.data.copy() + data['user'] = request.user.id # Automatically assign the authenticated user + + # Deserialize and validate the incoming data + serializer = SubscriptionSerializer(data=data) + + if serializer.is_valid(): + # Save the subscription using the validated data + subscription = serializer.save(user=request.user) # This automatically saves the subscription + + # Return the response with the subscription data + return Response({ + "message": "Subscription created successfully.", + "subscription": serializer.data # This gives you the serialized data of the saved subscription + }, status=status.HTTP_201_CREATED) + + else: + # If validation fails, return the errors + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + + + +# @api_view(['POST']) +# @authentication_classes([SessionAuthentication, TokenAuthentication]) +# @permission_classes([IsAuthenticated]) +# def addTeamMember(request): +# # Make a mutable copy of the request data +# data = request.data.copy() +# data['adminUser'] = request.user.id # Automatically assign the authenticated user as admin + +# # Pass the request object to the serializer context +# serializer = TeamMemberSerializer(data=data, context={'request': request}) + +# if serializer.is_valid(): +# # Save the team member using the validated data +# team_member = serializer.save() # This automatically saves the team member + +# # Return the response with the team member data +# return Response({ +# "message": "Team member added successfully.", +# "team_member": serializer.data # This gives you the serialized data of the saved team member +# }, status=status.HTTP_201_CREATED) + +# else: +# # If validation fails, return the errors +# return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + + + + + + + + + + + + + + + + + + + + +@api_view(['POST']) +@authentication_classes([SessionAuthentication, TokenAuthentication]) +@permission_classes([IsAuthenticated]) +def addTeamMember(request): + + # Check if username already exists + if User.objects.filter(username=request.data['mobile_number']).exists(): + return Response({'username': ['A user with that username already exists.']}, status=status.HTTP_400_BAD_REQUEST) + + # Ensure mobile number is provided + if 'mobile_number' not in request.data: + return Response({'mobile_number': ['This field is required.']}, status=status.HTTP_400_BAD_REQUEST) + + + # Proceed with user creation + user_serializer = UserSerializer(data=request.data) + if user_serializer.is_valid(): + user = user_serializer.save() + user.set_password(request.data['password']) + user.username = request.data['mobile_number'] + user.first_name = request.data['first_name'] + user.last_name = request.data['last_name'] + + user.save() + + customer_data = { + 'user': user.id, + 'mobile_number': request.data['mobile_number'], # Ensure mobile number is provided + 'semat': request.data['semat'], + + } + customer_serializer = CustomerSerializer(data=customer_data) + if customer_serializer.is_valid(): + customer_serializer.save() + token = Token.objects.create(user=user) + # return Response({'token': token.key, 'customer': customer_serializer.data, 'user': user_serializer.data}, status=status.HTTP_201_CREATED) + + + + + # Make a mutable copy of the request data + data = request.data.copy() + data['adminUser'] = request.user.id # Automatically assign the authenticated user as admin + data['user'] = user.id # Automatically assign the authenticated user as admin + + # Pass the request object to the serializer context + serializer = TeamMemberSerializer(data=data, context={'request': request}) + + if serializer.is_valid(): + # Save the team member using the validated data + team_member = serializer.save() # This automatically saves the team member + + # Return the response with the team member data + return Response({ + "message": "Team member added successfully.", + "team_member": serializer.data # This gives you the serialized data of the saved team member + }, status=status.HTTP_201_CREATED) + + else: + # If validation fails, return the errors + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + + + + + else: + # If customer data is invalid, delete the created user + user.delete() + return Response(customer_serializer.errors, status=status.HTTP_400_BAD_REQUEST) + else: + return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + + + + + + + + + + + diff --git a/test.rest b/test.rest index cd4d22b..4959240 100644 --- a/test.rest +++ b/test.rest @@ -3,7 +3,7 @@ POST http://127.0.0.1:8000/signup Content-Type: application/json -{ "username": "adam3", "password": "Pass1234!", "email": "adam2@mail.com" , "mobile_number":"09140086509" } +{ "username": "09140086509", "password": "12345678", "email": "adam2@mail.com" , "mobile_number":"09140086509" } ### @@ -85,3 +85,12 @@ Content-Type: application/json Authorization: token d3f1b03996140c8f7561d67221953ff704b482cb { "verification_sms_code": "807806" } + + +### + +POST http://127.0.0.1:8000/add_teamMember +Content-Type: application/json +Authorization: token 8dcae0063521ca707a9d0ab6ce3d4794a90064ca + +{ "mobile_number": "09140086608", "first_name":"mahdi", "last_name":"arabi", "semat":"modir", "password": "12345678", "isAdmin":"true" }