diff --git a/apps/account/manager.py b/apps/account/manager.py index db93a79..b037fa5 100644 --- a/apps/account/manager.py +++ b/apps/account/manager.py @@ -1,5 +1,6 @@ from django.contrib.auth.models import BaseUserManager +from django.contrib.auth.models import Group from django.db.models import Manager @@ -40,24 +41,17 @@ class UserManager(BaseUserManager): return user - def change_user_type(self, new_user_type): - # حذف گروه‌های فعلی - old_group_name = f"{self.user_type.capitalize()} Group" - old_group = Group.objects.filter(name=old_group_name).first() - if old_group: - self.groups.remove(old_group) + def change_user_type(self, user, new_user_type): + group_name = f"{new_user_type.capitalize()} Group" + if user.user_type != new_user_type and not user.groups.filter(name=group_name).exists(): - # تغییر نوع کاربر - self.user_type = new_user_type + user.user_type = new_user_type + new_group, _ = Group.objects.get_or_create(name=group_name) + user.groups.add(new_group) + user.save() + return user + return None - # افزودن گروه جدید - new_group_name = f"{new_user_type.capitalize()} Group" - new_group, _ = Group.objects.get_or_create(name=new_group_name) - self.groups.add(new_group) - - # ذخیره تغییرات - self.save() - class ProfessorUserManager(UserManager): diff --git a/apps/account/migrations/0005_user_city.py b/apps/account/migrations/0005_user_city.py new file mode 100644 index 0000000..9de7e74 --- /dev/null +++ b/apps/account/migrations/0005_user_city.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.4 on 2024-11-30 23:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0004_user_skill'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='city', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='City'), + ), + ] diff --git a/apps/account/models/user.py b/apps/account/models/user.py index cbed67d..c387375 100644 --- a/apps/account/models/user.py +++ b/apps/account/models/user.py @@ -42,6 +42,7 @@ class User(AbstractUser): verbose_name="User Type", help_text="Type of the user." ) + city = models.CharField(verbose_name=_('City'), max_length=255, null=True, blank=True) device_id = models.CharField(verbose_name=_('device id'), max_length=255, null=True, blank=True) fcm = models.CharField(max_length=512, null=True, blank=True) date_joined = models.DateTimeField(auto_now_add=True, verbose_name="Date Joined", help_text="The date and time the user registered.") diff --git a/apps/account/serializers/user.py b/apps/account/serializers/user.py index ff2d04d..8244ed0 100644 --- a/apps/account/serializers/user.py +++ b/apps/account/serializers/user.py @@ -15,7 +15,7 @@ class UserProfileSerializer(serializers.ModelSerializer): fullname = serializers.CharField(required=False) class Meta: model = User - fields = ['id', 'fullname', 'avatar', 'email', 'phone_number', 'password', 'info', 'skill'] + fields = ['id', 'fullname', 'avatar', 'email', 'phone_number', 'password', 'info', 'skill', 'city'] read_only_fields = ['email', 'info'] # def validate_email(self, value): diff --git a/apps/chat/admin.py b/apps/chat/admin.py index 3090931..4f88fbe 100644 --- a/apps/chat/admin.py +++ b/apps/chat/admin.py @@ -15,7 +15,7 @@ class MessageReadStatusAdmin(admin.ModelAdmin): @admin.register(RoomMessage) class RoomMessageAdmin(admin.ModelAdmin): list_display = ( - 'name', 'room_type', 'course', 'initiator', 'recipient', 'created_at', + 'name', 'room_type', 'course', 'initiator', 'recipient', 'created_at', 'unread_messages_count' ) list_filter = ('room_type', 'created_at', 'updated_at', 'course') search_fields = ('name', 'description', 'course__title', 'initiator__username', 'recipient__username') diff --git a/apps/course/admin/participant.py b/apps/course/admin/participant.py index 95be293..641d335 100644 --- a/apps/course/admin/participant.py +++ b/apps/course/admin/participant.py @@ -7,7 +7,7 @@ from apps.account.models import StudentUser, User @admin.register(Participant) class ParticipantAdmin(admin.ModelAdmin): - list_display = ('student', 'course', 'joined_date') + list_display = ('student', 'course', 'joined_date', 'unread_messages_count') search_fields = ('student__fullname', 'student__email', 'course__title') list_filter = ('course', 'joined_date') ordering = ('-joined_date',) diff --git a/apps/course/models/participant.py b/apps/course/models/participant.py index 2e99a4e..eee680a 100644 --- a/apps/course/models/participant.py +++ b/apps/course/models/participant.py @@ -2,7 +2,7 @@ from django.db import models -from apps.account.models import StudentUser +from apps.account.models import StudentUser, User from apps.course.models import Course @@ -21,5 +21,4 @@ class Participant(models.Model): unread_messages_count = models.IntegerField(default=0) class Meta: - unique_together = ('student', 'course') - \ No newline at end of file + unique_together = ('student', 'course') \ No newline at end of file diff --git a/apps/course/serializers/__init__.py b/apps/course/serializers/__init__.py index 6e8ef48..e86b7ee 100644 --- a/apps/course/serializers/__init__.py +++ b/apps/course/serializers/__init__.py @@ -1,2 +1,3 @@ from .course import * -from .lesson import * \ No newline at end of file +from .lesson import * +from .participant import * \ No newline at end of file diff --git a/apps/course/serializers/participant.py b/apps/course/serializers/participant.py new file mode 100644 index 0000000..8a00bc2 --- /dev/null +++ b/apps/course/serializers/participant.py @@ -0,0 +1,17 @@ +from rest_framework import serializers + + +from apps.course.models import Lesson, Participant, LessonCompletion +from apps.account.models import StudentUser, User + + + + +class ParticipantSerializer(serializers.ModelSerializer): + email = serializers.EmailField(required=True) + gender = serializers.ChoiceField(choices=User.GenderChoices.choices, required=True) + + + class Meta: + model = StudentUser + fields = ['fullname' , 'phone_number', 'gender', 'email', 'birthdate'] diff --git a/apps/course/urls.py b/apps/course/urls.py index de80cde..3af3994 100644 --- a/apps/course/urls.py +++ b/apps/course/urls.py @@ -15,5 +15,6 @@ urlpatterns = [ path('/lessons/', views.LessonListView.as_view(), name='course-lesson-list'), path('/participants/', views.CourseParticipantsView.as_view(), name='course-participant-list'), + # path('/participant/join/', views.ParticipantCreateView.as_view(), name='course-participant-join'), ] diff --git a/apps/course/views/participant.py b/apps/course/views/participant.py index 2f39fd8..a89e9ae 100644 --- a/apps/course/views/participant.py +++ b/apps/course/views/participant.py @@ -1,17 +1,20 @@ -from rest_framework.generics import ListAPIView +from rest_framework import generics from rest_framework.exceptions import NotFound from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi +from rest_framework.permissions import IsAuthenticated from apps.account.models import StudentUser from apps.course.models import Participant, Course -from apps.account.serializers import UserProfileSerializer +from apps.course.serializers import ParticipantSerializer +from apps.account.serializers import UserProfileSerializer from apps.course.doc import * +from utils.exceptions import AppAPIException -class CourseParticipantsView(ListAPIView): +class CourseParticipantsView(generics.ListAPIView): serializer_class = UserProfileSerializer @swagger_auto_schema( @@ -22,6 +25,37 @@ class CourseParticipantsView(ListAPIView): try: course = Course.objects.get(slug=course_slug) except Course.DoesNotExist: - raise NotFound("Course not found") + raise AppAPIException({'message': "Course not found"}) # Handle course not found return StudentUser.objects.filter(participated_courses__course=course) + + + + +# class ParticipantCreateView(generics.CreateAPIView): +# queryset = StudentUser.objects.all() +# serializer_class = ParticipantSerializer +# permission_classes = [IsAuthenticated] + + +# def create(self, request, *args, **kwargs): +# user = request.user +# course_slug = self.kwargs.get('slug') # Get the slug from the URL +# try: +# course = Course.objects.get(slug=slug) # Retrieve the Course object +# except Course.DoesNotExist: +# raise AppAPIException({'message': "Course not found"}) # Handle course not found + +# if request.data.get('email') != request.user: +# raise AppAPIException({'message': "The email must be for the requesting user"}) + +# if user.user_type != User.UserType.STUDENT: +# user.change_user_type(User.UserType.STUDENT) + +# participant, created = Participant.objects.get_or_create( +# student=user, +# course=course +# ) + +# serializer = self.get_serializer(participant) +# return Response(serializer.data, status=status.HTTP_201_CREATED) \ No newline at end of file diff --git a/apps/transaction/__init__.py b/apps/transaction/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/transaction/admin.py b/apps/transaction/admin.py new file mode 100644 index 0000000..7be51bc --- /dev/null +++ b/apps/transaction/admin.py @@ -0,0 +1,37 @@ +from django.contrib import admin + +from apps.transaction.models import TransactionParticipant, ParticipantInfo +from django.utils.translation import gettext_lazy as _ + + + +class ParticipantInfoInline(admin.StackedInline): + model = ParticipantInfo + extra = 1 + fields = ['fullname', 'email', 'phone_number', 'gender', 'birthdate'] + # readonly_fields = ['email', 'phone_number'] + classes = ['collapse'] + + + + +@admin.register(TransactionParticipant) +class TransactionParticipantAdmin(admin.ModelAdmin): + list_display = ('user', 'course', 'is_paid', 'price', 'created_at', 'updated_at') + list_filter = ('is_paid', 'course', 'created_at') + search_fields = ('user__email', 'course__title') + readonly_fields = ['user', 'course', 'price', 'created_at', 'updated_at'] + inlines = [ParticipantInfoInline] + ordering = ('-created_at',) + list_filter = ('is_paid', 'course', 'created_at') + + fieldsets = ( + (None, { + 'fields': ('user', 'course', 'is_paid', 'price') + }), + (_('Timestamps'), { + 'fields': ('created_at', 'updated_at'), + 'classes': ('collapse',) + }), + ) + diff --git a/apps/transaction/apps.py b/apps/transaction/apps.py new file mode 100644 index 0000000..8cf8565 --- /dev/null +++ b/apps/transaction/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TransactionConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.transaction' diff --git a/apps/transaction/migrations/0001_initial.py b/apps/transaction/migrations/0001_initial.py new file mode 100644 index 0000000..4e010b3 --- /dev/null +++ b/apps/transaction/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 3.2.4 on 2024-11-30 22:25 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import phonenumber_field.modelfields +import utils.validators + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('course', '0005_participant_unread_messages_count'), + ] + + operations = [ + migrations.CreateModel( + name='TransactionParticipant', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_paid', models.BooleanField(default=False, help_text='Indicates whether the payment has been completed or not', verbose_name='Payment Status')), + ('price', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='Transaction Price')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='course_transactions', to='course.course')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transactions', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='ParticipantInfo', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('fullname', models.CharField(help_text='Enter the full name of the user.', max_length=255, verbose_name='Full Name')), + ('email', models.EmailField(help_text="Enter the user's email address.", max_length=254, unique=True, verbose_name='Email Address')), + ('phone_number', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, null=True, region=None, unique=True, validators=[utils.validators.validate_possible_number], verbose_name='phone')), + ('gender', models.CharField(blank=True, choices=[('male', 'Male'), ('female', 'Female')], help_text="Select the user's gender.", max_length=20, null=True, verbose_name='Gender')), + ('birthdate', models.DateField(blank=True, null=True, verbose_name='birthdate')), + ('transaction_participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='participant_infos', to='transaction.transactionparticipant', verbose_name='Transaction Participant')), + ], + ), + ] diff --git a/apps/transaction/migrations/__init__.py b/apps/transaction/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/transaction/models.py b/apps/transaction/models.py new file mode 100644 index 0000000..4e1d373 --- /dev/null +++ b/apps/transaction/models.py @@ -0,0 +1,50 @@ +from django.db import models + +from django.utils.translation import gettext_lazy as _ + +from apps.account.models import StudentUser, User +from apps.course.models import Course +from phonenumber_field.modelfields import PhoneNumberField +from utils.validators import validate_possible_number + + + + + +class TransactionParticipant(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='transactions') + course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='course_transactions') + is_paid = models.BooleanField(default=False, verbose_name='Payment Status', help_text='Indicates whether the payment has been completed or not') + price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='Transaction Price') + + created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at")) + updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated at")) + + + + +class ParticipantInfo(models.Model): + class GenderChoices(models.TextChoices): + MALE = 'male', 'Male' + FEMALE = 'female', 'Female' + + transaction_participant = models.ForeignKey( + TransactionParticipant, + on_delete=models.CASCADE, + related_name='participant_infos', + verbose_name="Transaction Participant" + ) + fullname = models.CharField(max_length=255, verbose_name="Full Name", help_text="Enter the full name of the user.") + email = models.EmailField(unique=True, verbose_name="Email Address", help_text="Enter the user's email address.") + phone_number = PhoneNumberField(unique=True, validators=[validate_possible_number], null=True, blank=True, verbose_name=_('phone')) + gender = models.CharField( + max_length=20, choices=GenderChoices.choices, null=True, blank=True, verbose_name=_('Gender'), help_text="Select the user's gender." + ) + birthdate = models.DateField(verbose_name=_('birthdate'), null=True, blank=True) + + + + + + + diff --git a/apps/transaction/serializers.py b/apps/transaction/serializers.py new file mode 100644 index 0000000..137a653 --- /dev/null +++ b/apps/transaction/serializers.py @@ -0,0 +1,44 @@ + +from rest_framework import serializers + +from apps.transaction.models import TransactionParticipant, ParticipantInfo +from apps.course.serializers import CourseDetailSerializer + + + + +class ParticipantInfoSerializer(serializers.ModelSerializer): + class Meta: + model = ParticipantInfo + fields = ['fullname', 'email', 'phone_number', 'gender', 'birthdate'] + + +class TransactionParticipantSerializer(serializers.ModelSerializer): + participant_infos = ParticipantInfoSerializer(many=True) + + class Meta: + model = TransactionParticipant + fields = ['participant_infos'] + + + def create(self, validated_data): + participant_infos_data = validated_data.pop('participant_infos', []) + transaction_participant = TransactionParticipant.objects.create(**validated_data) + + for participant_info_data in participant_infos_data: + ParticipantInfo.objects.create(transaction_participant=transaction_participant, **participant_info_data) + + return transaction_participant + + + +class TransactionListSerializer(serializers.ModelSerializer): + course = serializers.SerializerMethodField() + + class Meta: + model = TransactionParticipant + fields = ['course', 'is_paid', 'price', 'created_at', 'updated_at'] + + def get_course(self, obj): + return CourseDetailSerializer(obj.course, context=self.context).data + \ No newline at end of file diff --git a/apps/transaction/tests.py b/apps/transaction/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/transaction/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/transaction/urls.py b/apps/transaction/urls.py new file mode 100644 index 0000000..01f8804 --- /dev/null +++ b/apps/transaction/urls.py @@ -0,0 +1,13 @@ + +from django.urls import path + +from . import views + + + +urlpatterns = [ + path('/join/', views.TransactionParticipantCreateView.as_view(), name='transaction-participant-create'), + path('list/', views.TransactiontListView.as_view(), name='transaction-list'), + + +] diff --git a/apps/transaction/views.py b/apps/transaction/views.py new file mode 100644 index 0000000..4801f09 --- /dev/null +++ b/apps/transaction/views.py @@ -0,0 +1,77 @@ +from rest_framework import generics, status +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response + +from apps.course.models import Participant, Course +from apps.transaction.models import TransactionParticipant +from apps.transaction.serializers import TransactionParticipantSerializer, TransactionListSerializer +from utils.exceptions import AppAPIException +from apps.account.models import User + + + +class TransactionParticipantCreateView(generics.CreateAPIView): + queryset = TransactionParticipant.objects.all() + serializer_class = TransactionParticipantSerializer + permission_classes = [IsAuthenticated] + + + def create(self, request, *args, **kwargs): + user = request.user + course_slug = self.kwargs.get('slug') # Get the slug from the URL + try: + course = Course.objects.get(slug=course_slug) # Retrieve the Course object + except Course.DoesNotExist: + raise AppAPIException({'message': "Course not found"}) # Handle course not found + + participant_infos = request.data.get('participant_infos', []) + print(f'1---> {participant_infos}') + print(f'2---> {len(participant_infos)}') + + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + + if len(participant_infos) == 1 and (course.final_price == 0 or course.is_free): + participant = participant_infos[0] + if participant.get('email') != user.email: + raise AppAPIException({'message': "The email must be for the requesting user"}) + + if user.user_type != User.UserType.STUDENT: + user = User.objects.change_user_type(user, User.UserType.STUDENT) + + participant, created = Participant.objects.get_or_create( + student=user, + course=course + ) + return Response({ + 'message': 'Transaction Participant created successfully.', + 'participant_id': participant.id, + 'participant_infos': serializer.data['participant_infos'] + }, status=status.HTTP_201_CREATED) + + + + transaction_participant = serializer.save(user=user, course=course, price=course.final_price) + print(f'---> {type(transaction_participant)}/ {transaction_participant}') + return Response({ + 'message': 'Transaction Participant created successfully.', + 'transaction_id': transaction_participant.id, + 'participant_infos': serializer.data['participant_infos'] + }, status=status.HTTP_201_CREATED) + + + + + + + +class TransactiontListView(generics.ListAPIView): + queryset = TransactionParticipant.objects.all() # یا هر فیلتر که بخواهید اضافه کنید + serializer_class = TransactionListSerializer + permission_classes = [IsAuthenticated] # برای دسترسی کاربران احراز هویت شده + + + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset.filter(user=self.request.user) + return queryset \ No newline at end of file diff --git a/config/settings/base.py b/config/settings/base.py index 8df4188..3df04fa 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -45,6 +45,9 @@ LOCAL_APPS = [ 'apps.course.apps.CourseConfig', 'apps.chat.apps.ChatConfig', 'apps.quiz.apps.QuizConfig', + 'apps.transaction.apps.TransactionConfig', + 'dynamic_preferences', + ] THIRD_PARTY_APPS = [ diff --git a/config/urls.py b/config/urls.py index 34860d6..3a10fba 100644 --- a/config/urls.py +++ b/config/urls.py @@ -37,6 +37,9 @@ api_patterns = [ path('account/', include('apps.account.urls')), path('courses/', include('apps.course.urls')), path('quiz/', include('apps.quiz.urls')), + path('transaction/', include('apps.transaction.urls')), + path('settings/', include('dynamic_preferences.urls')), + path('upload-tmp-media/', UploadTmpMedia.as_view()), ] diff --git a/dynamic_preferences/dynamic_preferences_registry.py b/dynamic_preferences/dynamic_preferences_registry.py index 9885de1..f39ab06 100644 --- a/dynamic_preferences/dynamic_preferences_registry.py +++ b/dynamic_preferences/dynamic_preferences_registry.py @@ -3,6 +3,7 @@ import json from django import forms from limitless_dashboard.fields.tinyeditor import TinyWidget +# from limitless_dashboard.fields.summernote import from dynamic_preferences.preferences import Section from dynamic_preferences.registries import global_preferences_registry @@ -13,3 +14,168 @@ from utils.json_editor_field import JsonEditorWidget class EditorPreferences(LongStringPreference): widget = TinyWidget(attrs={'class': 'editor-field'}) + + +@global_preferences_registry.register +class AboutUsConfig(EditorPreferences): + section = Section('aboutus', verbose_name='AboutUsConfig') + name = 'aboutus' + required = False + verbose_name = 'About Us' + default = '' + + +class JsonSerializer(BaseSerializer): + + @classmethod + def serialize(cls, value, **kwargs): + return json.dumps(value, ensure_ascii=False) + + @classmethod + def to_python(cls, value, **kwargs): + if isinstance(value, str) and len(value.strip()) > 0: + try: + return json.loads(value) + except json.JSONDecodeError as e: + try: + value_replaced = value.replace("'", '"') + return json.loads(value_replaced) + except json.JSONDecodeError as e2: + return {} + return value + + + +get_fqa_courses_schema = { + 'type': "array", + 'format': 'table', + 'title': ' ', + 'items': { + 'type': 'object', + 'title': str('Questions about courses'), + 'properties': { + 'question': {'type': 'string', "format": "textarea", 'title': str('Question')}, + 'answer': { + 'type': "string", + "format": "textarea", + 'title': str('Answer') + } + } + } + } + + +class JsonFieldFAQCourse(BasePreferenceType): + field_class = forms.JSONField + serializer = JsonSerializer + widget = JsonEditorWidget(attrs={'schema': get_fqa_courses_schema}) + + +@global_preferences_registry.register +class FAQCourseConfig(JsonFieldFAQCourse): + widget = JsonEditorWidget(attrs={'schema': get_fqa_courses_schema}) + section = Section('FAQ_Course', verbose_name='Questions about courses') + name = 'FAQ_Course' + required = False + verbose_name = 'FAQ Course' + default = {} + + + +get_fqa_general_schema = { + 'type': "array", + 'format': 'table', + 'title': ' ', + 'items': { + 'type': 'object', + 'title': str('Questions General'), + 'properties': { + 'question': {'type': 'string', "format": "textarea", 'title': str('Question')}, + 'answer': { + 'type': "string", + "format": "textarea", + 'title': str('Answer') + } + } + } + } + +class JsonFieldFAQGeneral(BasePreferenceType): + field_class = forms.JSONField + serializer = JsonSerializer + widget = JsonEditorWidget(attrs={'schema': get_fqa_general_schema}) + + + +@global_preferences_registry.register +class FAQGeneralConfig(JsonFieldFAQGeneral): + widget = JsonEditorWidget(attrs={'schema': get_fqa_general_schema}) + section = Section('FAQ_General', verbose_name='Questions General') + name = 'FAQ_General' + required = False + verbose_name = 'FAQ General' + default = {} + + + + + +support_fields = { + "type": "object", + "format": "table", + "title": "", + "required_by_default": 1, + "required": ['telegram_number', "whatsapp_number"], + "properties": { + "telegram_number": {"type": "string", "title": "Telegram Number"}, + "whatsapp_number": {"type": "string", "title": "Whatsapp Number"}, + } +} + + + +class JsonFieldSupport(BasePreferenceType): + field_class = forms.JSONField + serializer = JsonSerializer + widget = JsonEditorWidget(attrs={'schema': support_fields}) + +@global_preferences_registry.register +class SupportConfig(JsonFieldSupport): + section = Section('support', verbose_name='Support Detail') + name = 'support' + required = False + verbose_name = 'Support Detail' + default = {} + + + + + + +card_fields = { + "type": "object", + "format": "table", + "title": "", + "required_by_default": 1, + "required": ['card_number', "whatsapp_number"], + "properties": { + "card_number": {"type": "string", "title": "Card Number"}, + "whatsapp_number": {"type": "string", "title": "Whatsapp Number"}, + } +} + + + +class JsonFieldCard(BasePreferenceType): + field_class = forms.JSONField + serializer = JsonSerializer + widget = JsonEditorWidget(attrs={'schema': card_fields}) + +@global_preferences_registry.register +class SupportConfig(JsonFieldCard): + section = Section('card', verbose_name='Card Detail') + name = 'card' + required = False + verbose_name = 'Card Detail' + default = {} + diff --git a/dynamic_preferences/serializers.py b/dynamic_preferences/serializers.py index 9846ce6..83912a5 100644 --- a/dynamic_preferences/serializers.py +++ b/dynamic_preferences/serializers.py @@ -24,7 +24,24 @@ from django.utils.timezone import ( from six import string_types, text_type from django.db.models.fields.files import FieldFile +from rest_framework import serializers +class AboutUsSerializer(serializers.Serializer): + content = serializers.CharField(allow_blank=True) + +class FAQItemSerializer(serializers.Serializer): + question = serializers.CharField() + answer = serializers.CharField() + +class SupportSerializer(serializers.Serializer): + telegram_number = serializers.CharField() + whatsapp_number = serializers.CharField() + +class CardSerializer(serializers.Serializer): + card_number = serializers.CharField() + whatsapp_number = serializers.CharField() + + class UnsetValue(object): pass diff --git a/dynamic_preferences/urls.py b/dynamic_preferences/urls.py index e98f93b..dab3388 100644 --- a/dynamic_preferences/urls.py +++ b/dynamic_preferences/urls.py @@ -7,10 +7,30 @@ from django.contrib.admin.views.decorators import staff_member_required from . import views from .registries import global_preferences_registry from .forms import GlobalPreferenceForm +from django.urls import path + +from .views import ( + AboutUsAPIView, + FAQCourseAPIView, + FAQGeneralAPIView, + SupportAPIView, + CardAPIView +) app_name = "dynamic_preferences" + + + + urlpatterns = [ + path('about-us/', AboutUsAPIView.as_view(), name='about-us-api'), + path('faq-course/', FAQCourseAPIView.as_view(), name='faq-course-api'), + path('faq-general/', FAQGeneralAPIView.as_view(), name='faq-general-api'), + path('support/', SupportAPIView.as_view(), name='support-api'), + path('card/', CardAPIView.as_view(), name='card-api'), + + re_path( r"^global/$", staff_member_required( diff --git a/dynamic_preferences/views.py b/dynamic_preferences/views.py index 04d1acf..f12572b 100644 --- a/dynamic_preferences/views.py +++ b/dynamic_preferences/views.py @@ -1,7 +1,66 @@ from django.views.generic import TemplateView, FormView from django.http import Http404 from .forms import preference_form_builder - +from rest_framework.generics import GenericAPIView +from rest_framework.response import Response +from dynamic_preferences.registries import global_preferences_registry +from .serializers import ( + AboutUsSerializer, + FAQItemSerializer, + SupportSerializer, + CardSerializer +) + +class AboutUsAPIView(GenericAPIView): + serializer_class = AboutUsSerializer + + def get(self, request, *args, **kwargs): + preferences = global_preferences_registry.manager() + about_us = preferences.get('aboutus__aboutus', '') + serializer = self.get_serializer(data={'content': about_us}) + serializer.is_valid(raise_exception=True) + return Response(serializer.data) + +class FAQCourseAPIView(GenericAPIView): + serializer_class = FAQItemSerializer + + def get(self, request, *args, **kwargs): + preferences = global_preferences_registry.manager() + faq_course = preferences.get('FAQ_Course__FAQ_Course', []) + serializer = self.get_serializer(data=faq_course, many=True) + serializer.is_valid(raise_exception=True) + return Response(serializer.data) + +class FAQGeneralAPIView(GenericAPIView): + serializer_class = FAQItemSerializer + + def get(self, request, *args, **kwargs): + preferences = global_preferences_registry.manager() + faq_general = preferences.get('FAQ_General__FAQ_General', []) + serializer = self.get_serializer(data=faq_general, many=True) + serializer.is_valid(raise_exception=True) + return Response(serializer.data) + +class SupportAPIView(GenericAPIView): + serializer_class = SupportSerializer + + def get(self, request, *args, **kwargs): + preferences = global_preferences_registry.manager() + support = preferences.get('support__support', {}) + serializer = self.get_serializer(data=support) + serializer.is_valid(raise_exception=True) + return Response(serializer.data) + +class CardAPIView(GenericAPIView): + serializer_class = CardSerializer + + def get(self, request, *args, **kwargs): + preferences = global_preferences_registry.manager() + card = preferences.get('card__card', {}) + serializer = self.get_serializer(data=card) + serializer.is_valid(raise_exception=True) + return Response(serializer.data) + """Todo : remove these views and use only context processors"""