Initial commit: Django backend with Campaign, ContactUs, and Composition APIs
This commit is contained in:
51
.gitignore
vendored
Normal file
51
.gitignore
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Django
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
/media
|
||||
/staticfiles
|
||||
|
||||
# Virtual Environment
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
.venv
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
|
||||
135
README.md
Normal file
135
README.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# Zoneco ORG Backend API
|
||||
|
||||
A professional Django REST Framework backend for Zoneco ORG with PostgreSQL database.
|
||||
|
||||
## Features
|
||||
|
||||
- **Contact Us API**: Handle contact form submissions with name, email/phone, description, and category
|
||||
- **Composition API**: Manage compositions with name, description, and image
|
||||
- **Campaign API**: Manage campaigns with name, description, start/end times, and image
|
||||
- **RESTful API**: Full CRUD operations for all models
|
||||
- **CORS Enabled**: Configured for frontend integration
|
||||
- **Image Upload**: Support for image uploads with proper media handling
|
||||
- **Admin Panel**: Django admin interface for managing data
|
||||
|
||||
## Database Configuration
|
||||
|
||||
- **Database Name**: Zoneco_ORG
|
||||
- **User**: postgres
|
||||
- **Password**: postgres
|
||||
- **Host**: localhost
|
||||
- **Port**: 5432
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Install Python dependencies:**
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. **Create PostgreSQL database:**
|
||||
```sql
|
||||
CREATE DATABASE "Zoneco_ORG";
|
||||
```
|
||||
|
||||
3. **Run migrations:**
|
||||
```bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
```
|
||||
|
||||
4. **Create superuser (optional):**
|
||||
```bash
|
||||
python manage.py createsuperuser
|
||||
```
|
||||
|
||||
5. **Run development server:**
|
||||
```bash
|
||||
python manage.py runserver
|
||||
```
|
||||
|
||||
The API will be available at `http://localhost:8000/api/`
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Contact Us
|
||||
- `GET /api/contact-us/` - List all contacts
|
||||
- `POST /api/contact-us/` - Create new contact (public)
|
||||
- `GET /api/contact-us/{id}/` - Get specific contact
|
||||
- `PUT /api/contact-us/{id}/` - Update contact (authenticated)
|
||||
- `PATCH /api/contact-us/{id}/` - Partial update (authenticated)
|
||||
- `DELETE /api/contact-us/{id}/` - Delete contact (authenticated)
|
||||
- `GET /api/contact-us/by_category/?category={category}` - Filter by category
|
||||
|
||||
### Compositions
|
||||
- `GET /api/compositions/` - List all compositions
|
||||
- `POST /api/compositions/` - Create new composition (authenticated)
|
||||
- `GET /api/compositions/{id}/` - Get specific composition
|
||||
- `PUT /api/compositions/{id}/` - Update composition (authenticated)
|
||||
- `PATCH /api/compositions/{id}/` - Partial update (authenticated)
|
||||
- `DELETE /api/compositions/{id}/` - Delete composition (authenticated)
|
||||
|
||||
### Campaigns
|
||||
- `GET /api/campaigns/` - List all campaigns
|
||||
- `POST /api/campaigns/` - Create new campaign (authenticated)
|
||||
- `GET /api/campaigns/{id}/` - Get specific campaign
|
||||
- `PUT /api/campaigns/{id}/` - Update campaign (authenticated)
|
||||
- `PATCH /api/campaigns/{id}/` - Partial update (authenticated)
|
||||
- `DELETE /api/campaigns/{id}/` - Delete campaign (authenticated)
|
||||
- `GET /api/campaigns/active/` - Get active campaigns
|
||||
- `GET /api/campaigns/upcoming/` - Get upcoming campaigns
|
||||
- `GET /api/campaigns/ended/` - Get ended campaigns
|
||||
|
||||
## Models
|
||||
|
||||
### ContactUs
|
||||
- `name` (CharField): Name of the contact
|
||||
- `email_or_phone` (CharField): Email or phone number
|
||||
- `description` (TextField): Message description
|
||||
- `category` (CharField): Category (همکاری, فروش, پشتیبانی, درخواست مشاور, سایر)
|
||||
- `created_at` (DateTimeField): Creation timestamp
|
||||
- `updated_at` (DateTimeField): Last update timestamp
|
||||
|
||||
### Composition
|
||||
- `name` (CharField): Name of the composition
|
||||
- `description` (TextField): Description
|
||||
- `image` (ImageField): Composition image
|
||||
- `created_at` (DateTimeField): Creation timestamp
|
||||
- `updated_at` (DateTimeField): Last update timestamp
|
||||
|
||||
### Campaign
|
||||
- `name` (CharField): Campaign name
|
||||
- `description` (TextField): Campaign description
|
||||
- `start_time` (DateTimeField): Campaign start time
|
||||
- `end_time` (DateTimeField): Campaign end time
|
||||
- `image` (ImageField): Campaign image
|
||||
- `created_at` (DateTimeField): Creation timestamp
|
||||
- `updated_at` (DateTimeField): Last update timestamp
|
||||
|
||||
## Admin Panel
|
||||
|
||||
Access the Django admin panel at `http://localhost:8000/admin/` after creating a superuser.
|
||||
|
||||
## CORS Configuration
|
||||
|
||||
CORS is configured to allow requests from:
|
||||
- `http://localhost:5173` (Vite default)
|
||||
- `http://localhost:3000`
|
||||
- `http://127.0.0.1:5173`
|
||||
- `http://127.0.0.1:3000`
|
||||
|
||||
## Media Files
|
||||
|
||||
Uploaded images are stored in the `media/` directory:
|
||||
- Composition images: `media/compositions/`
|
||||
- Campaign images: `media/campaigns/`
|
||||
|
||||
## Development Notes
|
||||
|
||||
- The backend uses Django REST Framework for API endpoints
|
||||
- PostgreSQL is used as the database
|
||||
- Image uploads are handled via multipart/form-data
|
||||
- All models include proper indexing for performance
|
||||
- Serializers include validation and proper error handling
|
||||
- Viewsets provide both public read access and authenticated write access
|
||||
|
||||
352
Zoneco_ORG_API.postman_collection.json
Normal file
352
Zoneco_ORG_API.postman_collection.json
Normal file
@@ -0,0 +1,352 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "zoneco-org-api-collection",
|
||||
"name": "Zoneco ORG API",
|
||||
"description": "Complete API collection for Zoneco ORG backend - Campaign, Contact Us, and Composition endpoints",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Campaigns",
|
||||
"item": [
|
||||
{
|
||||
"name": "GET All Campaigns",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/campaigns/",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"campaigns",
|
||||
""
|
||||
]
|
||||
},
|
||||
"description": "Get all campaigns"
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "GET Campaign by ID",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/campaigns/1/",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"campaigns",
|
||||
"1",
|
||||
""
|
||||
]
|
||||
},
|
||||
"description": "Get a specific campaign by ID"
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "POST Create Campaign",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"name\": \"کمپین تست\",\n \"description\": \"این یک کمپین تستی است\",\n \"start_time\": \"2024-01-01T00:00:00Z\",\n \"end_time\": \"2024-12-31T23:59:59Z\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/campaigns/",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"campaigns",
|
||||
""
|
||||
]
|
||||
},
|
||||
"description": "Create a new campaign"
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "GET Active Campaigns",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/campaigns/active/",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"campaigns",
|
||||
"active",
|
||||
""
|
||||
]
|
||||
},
|
||||
"description": "Get all currently active campaigns"
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "GET Upcoming Campaigns",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/campaigns/upcoming/",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"campaigns",
|
||||
"upcoming",
|
||||
""
|
||||
]
|
||||
},
|
||||
"description": "Get all upcoming campaigns"
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "GET Ended Campaigns",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/campaigns/ended/",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"campaigns",
|
||||
"ended",
|
||||
""
|
||||
]
|
||||
},
|
||||
"description": "Get all ended campaigns"
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
],
|
||||
"description": "Campaign management endpoints"
|
||||
},
|
||||
{
|
||||
"name": "Contact Us",
|
||||
"item": [
|
||||
{
|
||||
"name": "GET All Contact Us",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/contact-us/",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"contact-us",
|
||||
""
|
||||
]
|
||||
},
|
||||
"description": "Get all contact form submissions"
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "GET Contact Us by ID",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/contact-us/1/",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"contact-us",
|
||||
"1",
|
||||
""
|
||||
]
|
||||
},
|
||||
"description": "Get a specific contact submission by ID"
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "POST Create Contact Us",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"name\": \"احمد محمدی\",\n \"email_or_phone\": \"ahmad@example.com\",\n \"description\": \"سلام، میخواستم در مورد خدمات شما اطلاعات بیشتری دریافت کنم.\",\n \"category\": \"پشتیبانی\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/contact-us/",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"contact-us",
|
||||
""
|
||||
]
|
||||
},
|
||||
"description": "Submit a new contact form"
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "GET Contact Us by Category",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/contact-us/by_category/?category=پشتیبانی",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"contact-us",
|
||||
"by_category",
|
||||
""
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "category",
|
||||
"value": "پشتیبانی",
|
||||
"description": "Category: همکاری, فروش, پشتیبانی, درخواست مشاور, سایر"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Get contacts filtered by category"
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
],
|
||||
"description": "Contact form submission endpoints"
|
||||
},
|
||||
{
|
||||
"name": "Compositions",
|
||||
"item": [
|
||||
{
|
||||
"name": "GET All Compositions",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/compositions/",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"compositions",
|
||||
""
|
||||
]
|
||||
},
|
||||
"description": "Get all compositions"
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "GET Composition by ID",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/compositions/1/",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"compositions",
|
||||
"1",
|
||||
""
|
||||
]
|
||||
},
|
||||
"description": "Get a specific composition by ID"
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "POST Create Composition",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"name\": \"ترکیب تست\",\n \"description\": \"این یک ترکیب تستی است\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/compositions/",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"compositions",
|
||||
""
|
||||
]
|
||||
},
|
||||
"description": "Create a new composition"
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
],
|
||||
"description": "Composition management endpoints"
|
||||
}
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "base_url",
|
||||
"value": "http://localhost:8000",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
0
api/__init__.py
Normal file
0
api/__init__.py
Normal file
72
api/admin.py
Normal file
72
api/admin.py
Normal 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
6
api/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ApiConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'api'
|
||||
0
api/management/__init__.py
Normal file
0
api/management/__init__.py
Normal file
0
api/management/commands/__init__.py
Normal file
0
api/management/commands/__init__.py
Normal file
125
api/management/commands/seed_data.py
Normal file
125
api/management/commands/seed_data.py
Normal 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')
|
||||
68
api/migrations/0001_initial.py
Normal file
68
api/migrations/0001_initial.py
Normal 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')],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
api/migrations/__init__.py
Normal file
0
api/migrations/__init__.py
Normal file
113
api/models.py
Normal file
113
api/models.py
Normal 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
126
api/serializers.py
Normal 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
3
api/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
13
api/urls.py
Normal file
13
api/urls.py
Normal 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
114
api/views.py
Normal 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)
|
||||
22
manage.py
Normal file
22
manage.py
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'zonco_backend.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
Django==4.2.7
|
||||
djangorestframework==3.14.0
|
||||
psycopg2-binary==2.9.11
|
||||
django-cors-headers==4.3.1
|
||||
Pillow==12.0.0
|
||||
|
||||
0
zonco_backend/__init__.py
Normal file
0
zonco_backend/__init__.py
Normal file
16
zonco_backend/asgi.py
Normal file
16
zonco_backend/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for zonco_backend project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'zonco_backend.settings')
|
||||
|
||||
application = get_asgi_application()
|
||||
186
zonco_backend/settings.py
Normal file
186
zonco_backend/settings.py
Normal file
@@ -0,0 +1,186 @@
|
||||
"""
|
||||
Django settings for zonco_backend project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 4.2.7.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-ro-sqgr@$*(qao)@d&ezk*z9&%+vbeurgi$b+6y650j*$1b+n5'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
# Third party apps
|
||||
'rest_framework',
|
||||
'corsheaders',
|
||||
# Local apps
|
||||
'api',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'zonco_backend.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'zonco_backend.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': 'Zoneco_ORG',
|
||||
'USER': 'postgres',
|
||||
'PASSWORD': 'postgres',
|
||||
'HOST': 'localhost',
|
||||
'PORT': '5432',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||
|
||||
# Media files
|
||||
MEDIA_URL = 'media/'
|
||||
MEDIA_ROOT = BASE_DIR / 'media'
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
# REST Framework configuration
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.AllowAny',
|
||||
],
|
||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||
'PAGE_SIZE': 20,
|
||||
'DEFAULT_RENDERER_CLASSES': [
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
],
|
||||
'DEFAULT_PARSER_CLASSES': [
|
||||
'rest_framework.parsers.JSONParser',
|
||||
'rest_framework.parsers.MultiPartParser',
|
||||
'rest_framework.parsers.FormParser',
|
||||
],
|
||||
}
|
||||
|
||||
# CORS configuration
|
||||
CORS_ALLOWED_ORIGINS = [
|
||||
"http://localhost:5173", # Vite default port
|
||||
"http://localhost:3000",
|
||||
"http://127.0.0.1:5173",
|
||||
"http://127.0.0.1:3000",
|
||||
]
|
||||
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
|
||||
CORS_ALLOW_METHODS = [
|
||||
'DELETE',
|
||||
'GET',
|
||||
'OPTIONS',
|
||||
'PATCH',
|
||||
'POST',
|
||||
'PUT',
|
||||
]
|
||||
|
||||
CORS_ALLOW_HEADERS = [
|
||||
'accept',
|
||||
'accept-encoding',
|
||||
'authorization',
|
||||
'content-type',
|
||||
'dnt',
|
||||
'origin',
|
||||
'user-agent',
|
||||
'x-csrftoken',
|
||||
'x-requested-with',
|
||||
]
|
||||
30
zonco_backend/urls.py
Normal file
30
zonco_backend/urls.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
URL configuration for zonco_backend project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/4.2/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('api/', include('api.urls')),
|
||||
]
|
||||
|
||||
# Serve media files in development
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
16
zonco_backend/wsgi.py
Normal file
16
zonco_backend/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for zonco_backend project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'zonco_backend.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
Reference in New Issue
Block a user