Browse Source

contact us api added

master
Mohsen Taba 1 month ago
parent
commit
b73f33fd8c
  1. 17
      apps/api/admin.py
  2. 78
      apps/api/migrations/0002_supportmessage.py
  3. 42
      apps/api/models.py
  4. 142
      apps/api/permissions.py
  5. 35
      apps/api/serializers.py
  6. 3
      apps/api/urls.py
  7. 60
      apps/api/views/api_views.py

17
apps/api/admin.py

@ -9,7 +9,7 @@ from dj_language.models import Language
from filer.models.thumbnailoptionmodels import ThumbnailOption
# from filer.admin.thumbnailoptionmodels import ThumbnailOptionAdmin as OriginalThumbnailOptionAdmin
from .models import Comment, AppVersion
from .models import Comment, AppVersion, SupportMessage
admin.site.unregister(ThumbnailOption)
@ -116,8 +116,23 @@ class AppVersionAdmin(ModelAdmin):
ordering = ['-created_at']
class SupportMessageAdmin(ModelAdmin):
list_display = [
'sender_name',
'sender_email',
'subject',
'sender_phone',
'created_at',
]
search_fields = ['sender_name', 'sender_email', 'subject', 'sender_phone', 'message']
list_filter = ['created_at']
ordering = ['-created_at']
# Register models with both admin sites
# project_admin_site.register(Comment, CommentAdmin)
project_admin_site.register(AppVersion, AppVersionAdmin)
project_admin_site.register(SupportMessage, SupportMessageAdmin)
# dovoodi_admin_site.register(Comment, CommentAdmin)
dovoodi_admin_site.register(AppVersion, AppVersionAdmin)
dovoodi_admin_site.register(SupportMessage, SupportMessageAdmin)

78
apps/api/migrations/0002_supportmessage.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"],
},
),
]

42
apps/api/models.py

@ -2,6 +2,8 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from dj_language.field import LanguageField
from django.core.validators import RegexValidator
from phonenumber_field.modelfields import PhoneNumberField
from utils.validators import validate_possible_number
class Comment(models.Model):
@ -136,3 +138,43 @@ class AppVersion(models.Model):
Get the latest active version
"""
return cls.objects.filter(is_active=True).order_by('-created_at').first()
class SupportMessage(models.Model):
sender_name = models.CharField(
max_length=255,
verbose_name=_('Sender Name'),
help_text=_('Full name of the sender'),
)
sender_email = models.EmailField(
verbose_name=_('Sender Email'),
help_text=_('Email address of the sender'),
)
subject = models.CharField(
max_length=255,
verbose_name=_('Subject'),
help_text=_('Message subject'),
)
sender_phone = PhoneNumberField(
validators=[validate_possible_number],
blank=True,
null=True,
verbose_name=_('Sender Phone'),
help_text=_("e.g., +49 151 12345678"),
)
message = models.TextField(
verbose_name=_('Message'),
help_text=_('Support message body'),
)
created_at = models.DateTimeField(
auto_now_add=True,
verbose_name=_('Created At'),
)
class Meta:
verbose_name = _('Support Message')
verbose_name_plural = _('Support Messages')
ordering = ['-created_at']
def __str__(self):
return f'{self.sender_name} - {self.subject}'

142
apps/api/permissions.py

@ -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

35
apps/api/serializers.py

@ -1,6 +1,6 @@
from rest_framework import serializers
from utils import FileFieldSerializer
from .models import Comment, AppVersion
from .models import Comment, AppVersion, SupportMessage
class CommentSerializer(serializers.ModelSerializer):
@ -52,3 +52,36 @@ class AppVersionSerializer(serializers.ModelSerializer):
'updated_at',
]
read_only_fields = ['id', 'created_at', 'updated_at']
class SupportMessageCreateSerializer(serializers.ModelSerializer):
class Meta:
model = SupportMessage
fields = [
'id',
'sender_name',
'sender_email',
'subject',
'sender_phone',
'message',
'created_at',
]
read_only_fields = ['id', 'created_at']
def validate_sender_name(self, value):
value = value.strip()
if not value:
raise serializers.ValidationError("Sender name is required.")
return value
def validate_subject(self, value):
value = value.strip()
if not value:
raise serializers.ValidationError("Subject is required.")
return value
def validate_message(self, value):
value = value.strip()
if not value:
raise serializers.ValidationError("Message is required.")
return value

3
apps/api/urls.py

@ -1,7 +1,7 @@
from django.urls import path,include
from .views import HomeView, CountryView, CommentListAPIView, CityListView
from .views.api_views import AppVersionListAPIView
from .views.api_views import AppVersionListAPIView, SupportMessageCreateAPIView
@ -11,4 +11,5 @@ urlpatterns = [
path('cities/', CityListView.as_view(), name='city-list'),
path('comments/', CommentListAPIView.as_view(), name='comment-list'),
path('app-versions/', AppVersionListAPIView.as_view(), name='appversion-list'),
path('contact-us/', SupportMessageCreateAPIView.as_view(), name='contact-us-create-message'),
]

60
apps/api/views/api_views.py

@ -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": "ali.ahmadi@example.com",
"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)
Loading…
Cancel
Save