7 changed files with 301 additions and 76 deletions
-
17apps/api/admin.py
-
78apps/api/migrations/0002_supportmessage.py
-
42apps/api/models.py
-
142apps/api/permissions.py
-
35apps/api/serializers.py
-
3apps/api/urls.py
-
60apps/api/views/api_views.py
@ -0,0 +1,78 @@ |
|||
from django.db import migrations, models |
|||
import phonenumber_field.modelfields |
|||
import utils.validators |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
dependencies = [ |
|||
("api", "0001_initial"), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.CreateModel( |
|||
name="SupportMessage", |
|||
fields=[ |
|||
( |
|||
"id", |
|||
models.BigAutoField( |
|||
auto_created=True, |
|||
primary_key=True, |
|||
serialize=False, |
|||
verbose_name="ID", |
|||
), |
|||
), |
|||
( |
|||
"sender_name", |
|||
models.CharField( |
|||
help_text="Full name of the sender", |
|||
max_length=255, |
|||
verbose_name="Sender Name", |
|||
), |
|||
), |
|||
( |
|||
"sender_email", |
|||
models.EmailField( |
|||
help_text="Email address of the sender", |
|||
max_length=254, |
|||
verbose_name="Sender Email", |
|||
), |
|||
), |
|||
( |
|||
"subject", |
|||
models.CharField( |
|||
help_text="Message subject", |
|||
max_length=255, |
|||
verbose_name="Subject", |
|||
), |
|||
), |
|||
( |
|||
"sender_phone", |
|||
phonenumber_field.modelfields.PhoneNumberField( |
|||
blank=True, |
|||
help_text="e.g., +49 151 12345678", |
|||
max_length=128, |
|||
null=True, |
|||
region=None, |
|||
validators=[utils.validators.validate_possible_number], |
|||
verbose_name="Sender Phone", |
|||
), |
|||
), |
|||
( |
|||
"message", |
|||
models.TextField( |
|||
help_text="Support message body", |
|||
verbose_name="Message", |
|||
), |
|||
), |
|||
( |
|||
"created_at", |
|||
models.DateTimeField(auto_now_add=True, verbose_name="Created At"), |
|||
), |
|||
], |
|||
options={ |
|||
"verbose_name": "Support Message", |
|||
"verbose_name_plural": "Support Messages", |
|||
"ordering": ["-created_at"], |
|||
}, |
|||
), |
|||
] |
|||
@ -1,71 +1,71 @@ |
|||
from rest_framework import permissions |
|||
from rest_framework.authtoken.models import Token |
|||
from django.contrib.auth.models import AnonymousUser |
|||
|
|||
|
|||
class SwaggerTokenPermission(permissions.BasePermission): |
|||
""" |
|||
Custom permission for Swagger that allows access to authenticated users via token |
|||
or admin users via session authentication |
|||
""" |
|||
|
|||
def has_permission(self, request, view): |
|||
# Check if user is admin (for session-based access) |
|||
if request.user and request.user.is_authenticated and request.user.is_staff: |
|||
return True |
|||
|
|||
# Check for token in session (from our custom auth system) |
|||
swagger_token = request.session.get('swagger_token') |
|||
if swagger_token: |
|||
try: |
|||
token_obj = Token.objects.get(key=swagger_token) |
|||
if token_obj.user.is_active: |
|||
return True |
|||
except Token.DoesNotExist: |
|||
pass |
|||
|
|||
# Check for Authorization header |
|||
auth_header = request.META.get('HTTP_AUTHORIZATION', '') |
|||
if auth_header.startswith('Token '): |
|||
token = auth_header.split(' ')[1] |
|||
try: |
|||
token_obj = Token.objects.get(key=token) |
|||
if token_obj.user.is_active: |
|||
return True |
|||
except Token.DoesNotExist: |
|||
pass |
|||
|
|||
return False |
|||
|
|||
|
|||
class IsAdminOrSwaggerToken(permissions.BasePermission): |
|||
""" |
|||
Permission that allows access to admin users or users with valid swagger token |
|||
""" |
|||
|
|||
def has_permission(self, request, view): |
|||
# Allow admin users |
|||
if request.user and request.user.is_authenticated and request.user.is_staff: |
|||
return True |
|||
|
|||
# Check swagger token in session |
|||
swagger_token = request.session.get('swagger_token') |
|||
if swagger_token: |
|||
try: |
|||
token_obj = Token.objects.get(key=swagger_token) |
|||
return token_obj.user.is_active |
|||
except Token.DoesNotExist: |
|||
pass |
|||
|
|||
return False |
|||
|
|||
|
|||
class IsProfessorUser(permissions.BasePermission): |
|||
""" |
|||
Permission that allows access only to professors or admin users. |
|||
""" |
|||
|
|||
def has_permission(self, request, view): |
|||
if not (request.user and request.user.is_authenticated): |
|||
return False |
|||
return request.user.user_type == 'professor' or request.user.is_staff |
|||
from rest_framework import permissions |
|||
from rest_framework.authtoken.models import Token |
|||
from django.contrib.auth.models import AnonymousUser |
|||
|
|||
|
|||
class SwaggerTokenPermission(permissions.BasePermission): |
|||
""" |
|||
Custom permission for Swagger that allows access to authenticated users via token |
|||
or admin users via session authentication |
|||
""" |
|||
|
|||
def has_permission(self, request, view): |
|||
# Check if user is admin (for session-based access) |
|||
if request.user and request.user.is_authenticated and request.user.is_staff: |
|||
return True |
|||
|
|||
# Check for token in session (from our custom auth system) |
|||
swagger_token = request.session.get('swagger_token') |
|||
if swagger_token: |
|||
try: |
|||
token_obj = Token.objects.get(key=swagger_token) |
|||
if token_obj.user.is_active: |
|||
return True |
|||
except Token.DoesNotExist: |
|||
pass |
|||
|
|||
# Check for Authorization header |
|||
auth_header = request.META.get('HTTP_AUTHORIZATION', '') |
|||
if auth_header.startswith('Token '): |
|||
token = auth_header.split(' ')[1] |
|||
try: |
|||
token_obj = Token.objects.get(key=token) |
|||
if token_obj.user.is_active: |
|||
return True |
|||
except Token.DoesNotExist: |
|||
pass |
|||
|
|||
return False |
|||
|
|||
|
|||
class IsAdminOrSwaggerToken(permissions.BasePermission): |
|||
""" |
|||
Permission that allows access to admin users or users with valid swagger token |
|||
""" |
|||
|
|||
def has_permission(self, request, view): |
|||
# Allow admin users |
|||
if request.user and request.user.is_authenticated and request.user.is_staff: |
|||
return True |
|||
|
|||
# Check swagger token in session |
|||
swagger_token = request.session.get('swagger_token') |
|||
if swagger_token: |
|||
try: |
|||
token_obj = Token.objects.get(key=swagger_token) |
|||
return token_obj.user.is_active |
|||
except Token.DoesNotExist: |
|||
pass |
|||
|
|||
return False |
|||
|
|||
|
|||
class IsProfessorUser(permissions.BasePermission): |
|||
""" |
|||
Permission that allows access only to professors or admin users. |
|||
""" |
|||
|
|||
def has_permission(self, request, view): |
|||
if not (request.user and request.user.is_authenticated): |
|||
return False |
|||
return request.user.user_type == 'professor' or request.user.is_staff |
|||
@ -1,5 +1,6 @@ |
|||
import random |
|||
from rest_framework.generics import GenericAPIView, ListAPIView |
|||
from rest_framework import status |
|||
from rest_framework.generics import CreateAPIView, GenericAPIView, ListAPIView |
|||
from rest_framework.views import APIView |
|||
from rest_framework.response import Response |
|||
from rest_framework import serializers |
|||
@ -10,7 +11,11 @@ from drf_yasg import openapi |
|||
from rest_framework.authtoken.models import Token |
|||
from apps.account.models import User |
|||
from apps.api.models import Comment, AppVersion |
|||
from apps.api.serializers import CommentSerializer, AppVersionSerializer |
|||
from apps.api.serializers import ( |
|||
AppVersionSerializer, |
|||
CommentSerializer, |
|||
SupportMessageCreateSerializer, |
|||
) |
|||
from utils.pagination import StandardResultsSetPagination |
|||
|
|||
class HomeSerializer(serializers.Serializer): |
|||
@ -239,3 +244,54 @@ class AppVersionListAPIView(ListAPIView): |
|||
) |
|||
def get(self, request, *args, **kwargs): |
|||
return super().get(request, *args, **kwargs) |
|||
|
|||
|
|||
class SupportMessageCreateAPIView(CreateAPIView): |
|||
serializer_class = SupportMessageCreateSerializer |
|||
permission_classes = [AllowAny] |
|||
|
|||
@swagger_auto_schema( |
|||
tags=["Support"], |
|||
operation_summary="Create support message", |
|||
operation_description=( |
|||
"Create a new support message from the website or mobile contact form.\n\n" |
|||
"This endpoint is public and does not require authentication.\n\n" |
|||
"Required fields:\n" |
|||
"- `sender_name`: full name of the sender\n" |
|||
"- `sender_email`: valid email address\n" |
|||
"- `subject`: message subject\n" |
|||
"- `message`: message body\n\n" |
|||
"Optional fields:\n" |
|||
"- `sender_phone`: sender phone number in international format\n\n" |
|||
"Phone number validation follows the same rules used in the account module." |
|||
), |
|||
request_body=SupportMessageCreateSerializer, |
|||
responses={ |
|||
status.HTTP_201_CREATED: openapi.Response( |
|||
description="Support message created successfully.", |
|||
schema=SupportMessageCreateSerializer, |
|||
examples={ |
|||
"application/json": { |
|||
"id": 12, |
|||
"sender_name": "Ali Ahmadi", |
|||
"sender_email": "[email protected]", |
|||
"subject": "Problem accessing my purchased course", |
|||
"sender_phone": "+989121234567", |
|||
"message": "Hello, I purchased a course today but the lessons are not available in my account. Please check my access.", |
|||
"created_at": "2026-06-17T09:30:12.421000Z", |
|||
} |
|||
}, |
|||
), |
|||
status.HTTP_400_BAD_REQUEST: openapi.Response( |
|||
description="Validation error.", |
|||
examples={ |
|||
"application/json": { |
|||
"sender_email": ["Enter a valid email address."], |
|||
"sender_phone": ["The phone number entered is not valid."], |
|||
} |
|||
}, |
|||
), |
|||
}, |
|||
) |
|||
def post(self, request, *args, **kwargs): |
|||
return super().post(request, *args, **kwargs) |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue