Initial commit: Django backend with Campaign, ContactUs, and Composition APIs

This commit is contained in:
Shayan Azadi
2025-12-17 15:58:07 +03:30
commit 3e100bae99
23 changed files with 1454 additions and 0 deletions

0
api/__init__.py Normal file
View File

72
api/admin.py Normal file
View File

@@ -0,0 +1,72 @@
from django.contrib import admin
from .models import ContactUs, Composition, Campaign
@admin.register(ContactUs)
class ContactUsAdmin(admin.ModelAdmin):
list_display = ['name', 'email_or_phone', 'category', 'created_at']
list_filter = ['category', 'created_at']
search_fields = ['name', 'email_or_phone', 'description']
readonly_fields = ['created_at', 'updated_at']
date_hierarchy = 'created_at'
fieldsets = (
('اطلاعات تماس', {
'fields': ('name', 'email_or_phone', 'category')
}),
('پیام', {
'fields': ('description',)
}),
('اطلاعات زمانی', {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',)
}),
)
@admin.register(Composition)
class CompositionAdmin(admin.ModelAdmin):
list_display = ['name', 'created_at']
search_fields = ['name', 'description']
readonly_fields = ['created_at', 'updated_at']
date_hierarchy = 'created_at'
fieldsets = (
('اطلاعات ترکیب', {
'fields': ('name', 'description', 'image')
}),
('اطلاعات زمانی', {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',)
}),
)
@admin.register(Campaign)
class CampaignAdmin(admin.ModelAdmin):
list_display = ['name', 'start_time', 'end_time', 'is_active', 'created_at']
list_filter = ['start_time', 'end_time', 'created_at']
search_fields = ['name', 'description']
readonly_fields = ['created_at', 'updated_at', 'is_active']
date_hierarchy = 'start_time'
fieldsets = (
('اطلاعات کمپین', {
'fields': ('name', 'description', 'image')
}),
('زمان‌بندی', {
'fields': ('start_time', 'end_time')
}),
('وضعیت', {
'fields': ('is_active',)
}),
('اطلاعات زمانی', {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',)
}),
)
def is_active(self, obj):
return obj.is_active()
is_active.boolean = True
is_active.short_description = 'فعال'

6
api/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'api'

View File

View File

View File

@@ -0,0 +1,125 @@
from django.core.management.base import BaseCommand
from django.utils import timezone
from datetime import timedelta
from api.models import ContactUs, Composition, Campaign
class Command(BaseCommand):
help = 'Seed the database with Persian sample data'
def handle(self, *args, **options):
self.stdout.write(self.style.SUCCESS('Starting to seed database with Persian data...'))
# Seed ContactUs data
self.stdout.write('Creating ContactUs records...')
contact_data = [
{
'name': 'احمد محمدی',
'email_or_phone': 'ahmad.mohammadi@example.com',
'description': 'سلام، من علاقه‌مند به همکاری با شرکت شما هستم. لطفاً اطلاعات بیشتری در مورد فرصت‌های شغلی ارسال کنید.',
'category': 'همکاری'
},
{
'name': 'فاطمه رضایی',
'email_or_phone': '09123456789',
'description': 'با سلام، می‌خواستم در مورد محصولات شما اطلاعات بیشتری دریافت کنم. آیا امکان بازدید از نمایشگاه وجود دارد؟',
'category': 'فروش'
},
{
'name': 'علی کریمی',
'email_or_phone': 'ali.karimi@example.com',
'description': 'سلام، من مشکلی در استفاده از سرویس شما دارم. لطفاً راهنمایی کنید.',
'category': 'پشتیبانی'
},
{
'name': 'زهرا احمدی',
'email_or_phone': '09187654321',
'description': 'با احترام، می‌خواستم در مورد پروژه‌های سرمایه‌گذاری شما مشاوره دریافت کنم. لطفاً با من تماس بگیرید.',
'category': 'درخواست مشاور'
},
{
'name': 'محمد حسینی',
'email_or_phone': 'mohammad.hosseini@example.com',
'description': 'سلام، سوالی در مورد خدمات شما دارم که در دسته‌بندی‌های موجود نیست. لطفاً راهنمایی کنید.',
'category': 'سایر'
}
]
for i, data in enumerate(contact_data, 1):
ContactUs.objects.create(**data)
self.stdout.write(f' Created contact {i}/5')
# Seed Composition data
self.stdout.write('Creating Composition records...')
composition_data = [
{
'name': 'ترکیب استراتژیک زونکو',
'description': 'این ترکیب شامل بخش‌های مختلف شرکت زونکو می‌باشد که شامل مدیریت، توسعه، بازاریابی و فروش است. این ترکیب به گونه‌ای طراحی شده که بتواند حداکثر کارایی را در تمامی بخش‌ها ایجاد کند.'
},
{
'name': 'ترکیب تکنولوژی و نوآوری',
'description': 'ترکیبی از تکنولوژی‌های پیشرفته و نوآوری‌های روز دنیا که در شرکت زونکو به کار گرفته می‌شود. این ترکیب شامل هوش مصنوعی، بلاک چین و سایر تکنولوژی‌های نوین است.'
},
{
'name': 'ترکیب منابع انسانی',
'description': 'ترکیب بهینه از نیروی انسانی متخصص و با تجربه که در بخش‌های مختلف شرکت فعالیت می‌کنند. این ترکیب شامل مدیران ارشد، متخصصان فنی و کارشناسان بازاریابی است.'
},
{
'name': 'ترکیب مالی و سرمایه‌گذاری',
'description': 'ترکیب استراتژیک از منابع مالی و فرصت‌های سرمایه‌گذاری که شرکت زونکو در اختیار دارد. این ترکیب شامل سرمایه‌گذاری‌های داخلی و خارجی و مدیریت ریسک است.'
},
{
'name': 'ترکیب بازاریابی و فروش',
'description': 'ترکیب موثر از استراتژی‌های بازاریابی و فروش که برای دستیابی به اهداف تجاری شرکت طراحی شده است. این ترکیب شامل بازاریابی دیجیتال، روابط عمومی و فروش مستقیم است.'
}
]
for i, data in enumerate(composition_data, 1):
Composition.objects.create(**data)
self.stdout.write(f' Created composition {i}/5')
# Seed Campaign data
self.stdout.write('Creating Campaign records...')
now = timezone.now()
campaign_data = [
{
'name': 'کمپین تابستانه زونکو ۱۴۰۳',
'description': 'کمپین ویژه تابستان ۱۴۰۳ با تخفیف‌های ویژه و جوایز نقدی. این کمپین شامل تمامی محصولات و خدمات شرکت می‌باشد و فرصت مناسبی برای مشتریان است.',
'start_time': now - timedelta(days=30),
'end_time': now + timedelta(days=30)
},
{
'name': 'کمپین همکاری استراتژیک',
'description': 'کمپین جذب شرکای تجاری جدید برای همکاری در پروژه‌های بزرگ. این کمپین فرصت مناسبی برای شرکت‌هایی است که می‌خواهند با زونکو همکاری کنند.',
'start_time': now - timedelta(days=15),
'end_time': now + timedelta(days=45)
},
{
'name': 'کمپین نوآوری و تکنولوژی',
'description': 'کمپین معرفی آخرین نوآوری‌ها و تکنولوژی‌های شرکت زونکو. این کمپین شامل نمایشگاه‌ها، سمینارها و رویدادهای تخصصی است.',
'start_time': now + timedelta(days=10),
'end_time': now + timedelta(days=70)
},
{
'name': 'کمپین سرمایه‌گذاری زونکو',
'description': 'کمپین جذب سرمایه‌گذاران برای پروژه‌های جدید شرکت. این کمپین شامل ارائه اطلاعات کامل در مورد فرصت‌های سرمایه‌گذاری و بازدهی مورد انتظار است.',
'start_time': now - timedelta(days=60),
'end_time': now - timedelta(days=10)
},
{
'name': 'کمپین آموزش و توسعه',
'description': 'کمپین ارائه دوره‌های آموزشی و کارگاه‌های تخصصی برای توسعه مهارت‌های حرفه‌ای. این کمپین شامل دوره‌های آنلاین و حضوری است.',
'start_time': now + timedelta(days=5),
'end_time': now + timedelta(days=65)
}
]
for i, data in enumerate(campaign_data, 1):
Campaign.objects.create(**data)
self.stdout.write(f' Created campaign {i}/5')
self.stdout.write(self.style.SUCCESS('\nSuccessfully seeded database with Persian data!'))
self.stdout.write(f' - ContactUs: {ContactUs.objects.count()} records')
self.stdout.write(f' - Composition: {Composition.objects.count()} records')
self.stdout.write(f' - Campaign: {Campaign.objects.count()} records')

View File

@@ -0,0 +1,68 @@
# Generated by Django 4.2.7 on 2025-12-17 12:13
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='ContactUs',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='نام و نام خانوادگی')),
('email_or_phone', models.CharField(help_text='Can be either email or phone number', max_length=200, verbose_name='ایمیل / شماره تماس')),
('description', models.TextField(verbose_name='توضیحات')),
('category', models.CharField(choices=[('همکاری', 'همکاری'), ('فروش', 'فروش'), ('پشتیبانی', 'پشتیبانی'), ('درخواست مشاور', 'درخواست مشاور'), ('سایر', 'سایر')], max_length=50, verbose_name='دسته\u200cبندی')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='تاریخ ایجاد')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='تاریخ بروزرسانی')),
],
options={
'verbose_name': 'تماس با ما',
'verbose_name_plural': 'تماس\u200cهای با ما',
'ordering': ['-created_at'],
'indexes': [models.Index(fields=['-created_at'], name='api_contact_created_d03dd7_idx'), models.Index(fields=['category'], name='api_contact_categor_76ad37_idx')],
},
),
migrations.CreateModel(
name='Composition',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='نام')),
('description', models.TextField(verbose_name='توضیحات')),
('image', models.ImageField(blank=True, null=True, upload_to='compositions/', verbose_name='تصویر')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='تاریخ ایجاد')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='تاریخ بروزرسانی')),
],
options={
'verbose_name': 'ترکیب',
'verbose_name_plural': 'ترکیبات',
'ordering': ['-created_at'],
'indexes': [models.Index(fields=['-created_at'], name='api_composi_created_ac63fb_idx')],
},
),
migrations.CreateModel(
name='Campaign',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='نام')),
('description', models.TextField(verbose_name='توضیحات')),
('start_time', models.DateTimeField(verbose_name='زمان شروع')),
('end_time', models.DateTimeField(verbose_name='زمان پایان')),
('image', models.ImageField(blank=True, null=True, upload_to='campaigns/', verbose_name='تصویر')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='تاریخ ایجاد')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='تاریخ بروزرسانی')),
],
options={
'verbose_name': 'کمپین',
'verbose_name_plural': 'کمپین\u200cها',
'ordering': ['-start_time'],
'indexes': [models.Index(fields=['-start_time'], name='api_campaig_start_t_c79399_idx'), models.Index(fields=['end_time'], name='api_campaig_end_tim_d3f700_idx')],
},
),
]

View File

113
api/models.py Normal file
View File

@@ -0,0 +1,113 @@
from django.db import models
from django.core.validators import EmailValidator
from django.utils import timezone
class ContactUs(models.Model):
"""
Contact Us model for handling contact form submissions.
"""
CATEGORY_CHOICES = [
('همکاری', 'همکاری'),
('فروش', 'فروش'),
('پشتیبانی', 'پشتیبانی'),
('درخواست مشاور', 'درخواست مشاور'),
('سایر', 'سایر'),
]
name = models.CharField(max_length=200, verbose_name='نام و نام خانوادگی')
email_or_phone = models.CharField(
max_length=200,
verbose_name='ایمیل / شماره تماس',
help_text='Can be either email or phone number'
)
description = models.TextField(verbose_name='توضیحات')
category = models.CharField(
max_length=50,
choices=CATEGORY_CHOICES,
verbose_name='دسته‌بندی'
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name='تاریخ ایجاد')
updated_at = models.DateTimeField(auto_now=True, verbose_name='تاریخ بروزرسانی')
class Meta:
verbose_name = 'تماس با ما'
verbose_name_plural = 'تماس‌های با ما'
ordering = ['-created_at']
indexes = [
models.Index(fields=['-created_at']),
models.Index(fields=['category']),
]
def __str__(self):
return f"{self.name} - {self.category}"
class Composition(models.Model):
"""
Composition model for storing composition information.
"""
name = models.CharField(max_length=200, verbose_name='نام')
description = models.TextField(verbose_name='توضیحات')
image = models.ImageField(
upload_to='compositions/',
verbose_name='تصویر',
null=True,
blank=True
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name='تاریخ ایجاد')
updated_at = models.DateTimeField(auto_now=True, verbose_name='تاریخ بروزرسانی')
class Meta:
verbose_name = 'ترکیب'
verbose_name_plural = 'ترکیبات'
ordering = ['-created_at']
indexes = [
models.Index(fields=['-created_at']),
]
def __str__(self):
return self.name
class Campaign(models.Model):
"""
Campaign model for storing campaign information.
"""
name = models.CharField(max_length=200, verbose_name='نام')
description = models.TextField(verbose_name='توضیحات')
start_time = models.DateTimeField(verbose_name='زمان شروع')
end_time = models.DateTimeField(verbose_name='زمان پایان')
image = models.ImageField(
upload_to='campaigns/',
verbose_name='تصویر',
null=True,
blank=True
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name='تاریخ ایجاد')
updated_at = models.DateTimeField(auto_now=True, verbose_name='تاریخ بروزرسانی')
class Meta:
verbose_name = 'کمپین'
verbose_name_plural = 'کمپین‌ها'
ordering = ['-start_time']
indexes = [
models.Index(fields=['-start_time']),
models.Index(fields=['end_time']),
]
def __str__(self):
return self.name
def is_active(self):
"""Check if campaign is currently active."""
now = timezone.now()
return self.start_time <= now <= self.end_time
def is_upcoming(self):
"""Check if campaign is upcoming."""
return timezone.now() < self.start_time
def is_ended(self):
"""Check if campaign has ended."""
return timezone.now() > self.end_time

126
api/serializers.py Normal file
View File

@@ -0,0 +1,126 @@
from rest_framework import serializers
from .models import ContactUs, Composition, Campaign
class ContactUsSerializer(serializers.ModelSerializer):
"""
Serializer for ContactUs model.
"""
class Meta:
model = ContactUs
fields = ['id', 'name', 'email_or_phone', 'description', 'category',
'created_at', 'updated_at']
read_only_fields = ['id', 'created_at', 'updated_at']
def validate_email_or_phone(self, value):
"""Validate that the field contains either email or phone."""
if not value or len(value.strip()) == 0:
raise serializers.ValidationError("Email or phone number is required.")
return value.strip()
def validate_name(self, value):
"""Validate name field."""
if not value or len(value.strip()) == 0:
raise serializers.ValidationError("Name is required.")
return value.strip()
def validate_description(self, value):
"""Validate description field."""
if not value or len(value.strip()) == 0:
raise serializers.ValidationError("Description is required.")
return value.strip()
class CompositionSerializer(serializers.ModelSerializer):
"""
Serializer for Composition model.
"""
image_url = serializers.SerializerMethodField()
class Meta:
model = Composition
fields = ['id', 'name', 'description', 'image', 'image_url',
'created_at', 'updated_at']
read_only_fields = ['id', 'created_at', 'updated_at', 'image_url']
def get_image_url(self, obj):
"""Get the full URL of the image."""
if obj.image:
request = self.context.get('request')
if request:
return request.build_absolute_uri(obj.image.url)
return obj.image.url
return None
def validate_name(self, value):
"""Validate name field."""
if not value or len(value.strip()) == 0:
raise serializers.ValidationError("Name is required.")
return value.strip()
def validate_description(self, value):
"""Validate description field."""
if not value or len(value.strip()) == 0:
raise serializers.ValidationError("Description is required.")
return value.strip()
class CampaignSerializer(serializers.ModelSerializer):
"""
Serializer for Campaign model.
"""
image_url = serializers.SerializerMethodField()
is_active = serializers.SerializerMethodField()
is_upcoming = serializers.SerializerMethodField()
is_ended = serializers.SerializerMethodField()
class Meta:
model = Campaign
fields = ['id', 'name', 'description', 'start_time', 'end_time',
'image', 'image_url', 'is_active', 'is_upcoming', 'is_ended',
'created_at', 'updated_at']
read_only_fields = ['id', 'created_at', 'updated_at', 'image_url',
'is_active', 'is_upcoming', 'is_ended']
def get_image_url(self, obj):
"""Get the full URL of the image."""
if obj.image:
request = self.context.get('request')
if request:
return request.build_absolute_uri(obj.image.url)
return obj.image.url
return None
def get_is_active(self, obj):
"""Get campaign active status."""
return obj.is_active()
def get_is_upcoming(self, obj):
"""Get campaign upcoming status."""
return obj.is_upcoming()
def get_is_ended(self, obj):
"""Get campaign ended status."""
return obj.is_ended()
def validate(self, data):
"""Validate that end_time is after start_time."""
if 'start_time' in data and 'end_time' in data:
if data['end_time'] <= data['start_time']:
raise serializers.ValidationError({
'end_time': 'End time must be after start time.'
})
return data
def validate_name(self, value):
"""Validate name field."""
if not value or len(value.strip()) == 0:
raise serializers.ValidationError("Name is required.")
return value.strip()
def validate_description(self, value):
"""Validate description field."""
if not value or len(value.strip()) == 0:
raise serializers.ValidationError("Description is required.")
return value.strip()

3
api/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

13
api/urls.py Normal file
View File

@@ -0,0 +1,13 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ContactUsViewSet, CompositionViewSet, CampaignViewSet
router = DefaultRouter()
router.register(r'contact-us', ContactUsViewSet, basename='contact-us')
router.register(r'compositions', CompositionViewSet, basename='composition')
router.register(r'campaigns', CampaignViewSet, basename='campaign')
urlpatterns = [
path('', include(router.urls)),
]

114
api/views.py Normal file
View File

@@ -0,0 +1,114 @@
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAuthenticated
from django.db.models import Q
from django.utils import timezone
from .models import ContactUs, Composition, Campaign
from .serializers import ContactUsSerializer, CompositionSerializer, CampaignSerializer
class ContactUsViewSet(viewsets.ModelViewSet):
"""
ViewSet for ContactUs model.
Provides CRUD operations for contact form submissions.
"""
queryset = ContactUs.objects.all()
serializer_class = ContactUsSerializer
permission_classes = [AllowAny] # Allow anyone to submit contact forms
def get_permissions(self):
"""
Override to allow GET (list, retrieve) and POST (create) for anyone.
"""
if self.action in ['list', 'retrieve', 'create']:
return [AllowAny()]
return [IsAuthenticated()]
@action(detail=False, methods=['get'])
def by_category(self, request):
"""Get contacts filtered by category."""
category = request.query_params.get('category', None)
if category:
contacts = self.queryset.filter(category=category)
serializer = self.get_serializer(contacts, many=True)
return Response(serializer.data)
return Response(
{'error': 'Category parameter is required.'},
status=status.HTTP_400_BAD_REQUEST
)
class CompositionViewSet(viewsets.ModelViewSet):
"""
ViewSet for Composition model.
Provides CRUD operations for compositions.
"""
queryset = Composition.objects.all()
serializer_class = CompositionSerializer
permission_classes = [AllowAny] # Public read access
def get_permissions(self):
"""
Override to allow GET (list, retrieve) and POST (create) for anyone.
"""
if self.action in ['list', 'retrieve', 'create']:
return [AllowAny()]
return [IsAuthenticated()]
def get_serializer_context(self):
"""Add request to serializer context for image URL generation."""
context = super().get_serializer_context()
context['request'] = self.request
return context
class CampaignViewSet(viewsets.ModelViewSet):
"""
ViewSet for Campaign model.
Provides CRUD operations for campaigns.
"""
queryset = Campaign.objects.all()
serializer_class = CampaignSerializer
permission_classes = [AllowAny] # Public read access
def get_permissions(self):
"""
Override to allow GET (list, retrieve) and POST (create) for anyone.
"""
if self.action in ['list', 'retrieve', 'create', 'active', 'upcoming', 'ended']:
return [AllowAny()]
return [IsAuthenticated()]
def get_serializer_context(self):
"""Add request to serializer context for image URL generation."""
context = super().get_serializer_context()
context['request'] = self.request
return context
@action(detail=False, methods=['get'])
def active(self, request):
"""Get all currently active campaigns."""
now = timezone.now()
active_campaigns = self.queryset.filter(
start_time__lte=now,
end_time__gte=now
)
serializer = self.get_serializer(active_campaigns, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def upcoming(self, request):
"""Get all upcoming campaigns."""
now = timezone.now()
upcoming_campaigns = self.queryset.filter(start_time__gt=now)
serializer = self.get_serializer(upcoming_campaigns, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def ended(self, request):
"""Get all ended campaigns."""
now = timezone.now()
ended_campaigns = self.queryset.filter(end_time__lt=now)
serializer = self.get_serializer(ended_campaigns, many=True)
return Response(serializer.data)