update
This commit is contained in:
88
backEnd/about/README.md
Normal file
88
backEnd/about/README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# About Us API
|
||||
|
||||
This Django app provides API endpoints for managing about us page content.
|
||||
|
||||
## Models
|
||||
|
||||
### AboutBanner
|
||||
- Main banner section with title, description, badge, CTA button, and image
|
||||
- Related models: AboutStat, AboutSocialLink
|
||||
|
||||
### AboutService
|
||||
- Service section with company information and features
|
||||
- Related models: AboutFeature
|
||||
|
||||
### AboutProcess
|
||||
- Development process section with methodology and steps
|
||||
- Related models: AboutProcessStep
|
||||
|
||||
### AboutJourney
|
||||
- Company journey section with milestones
|
||||
- Related models: AboutMilestone
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Combined Endpoint
|
||||
- `GET /api/about/page/` - Get all about page data in one request
|
||||
|
||||
### Individual Endpoints
|
||||
|
||||
#### Banner
|
||||
- `GET /api/about/banner/` - List all active banners
|
||||
- `GET /api/about/banner/{id}/` - Get specific banner
|
||||
|
||||
#### Service
|
||||
- `GET /api/about/service/` - List all active services
|
||||
- `GET /api/about/service/{id}/` - Get specific service
|
||||
|
||||
#### Process
|
||||
- `GET /api/about/process/` - List all active processes
|
||||
- `GET /api/about/process/{id}/` - Get specific process
|
||||
|
||||
#### Journey
|
||||
- `GET /api/about/journey/` - List all active journeys
|
||||
- `GET /api/about/journey/{id}/` - Get specific journey
|
||||
|
||||
## Management Commands
|
||||
|
||||
### Populate Sample Data
|
||||
```bash
|
||||
python manage.py populate_about_data
|
||||
```
|
||||
|
||||
This command creates sample data for all about us sections.
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
The frontend uses the following files:
|
||||
- `lib/api/aboutService.ts` - API service for fetching data
|
||||
- `lib/hooks/useAbout.ts` - React hooks for data management
|
||||
- Components in `components/pages/about/` - Updated to use API data
|
||||
|
||||
## Usage Example
|
||||
|
||||
```typescript
|
||||
import { useAbout } from '@/lib/hooks/useAbout';
|
||||
|
||||
const AboutPage = () => {
|
||||
const { data, loading, error } = useAbout();
|
||||
|
||||
if (loading) return <div>Loading...</div>;
|
||||
if (error) return <div>Error: {error}</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{data?.banner.title}</h1>
|
||||
<p>{data?.banner.description}</p>
|
||||
{/* Render other sections */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Admin Interface
|
||||
|
||||
All models are available in the Django admin interface for easy content management:
|
||||
- Navigate to `/admin/` after creating a superuser
|
||||
- Manage about us content through the admin interface
|
||||
- Upload images and manage relationships between models
|
||||
0
backEnd/about/__init__.py
Normal file
0
backEnd/about/__init__.py
Normal file
BIN
backEnd/about/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backEnd/about/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/about/__pycache__/admin.cpython-312.pyc
Normal file
BIN
backEnd/about/__pycache__/admin.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/about/__pycache__/apps.cpython-312.pyc
Normal file
BIN
backEnd/about/__pycache__/apps.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/about/__pycache__/models.cpython-312.pyc
Normal file
BIN
backEnd/about/__pycache__/models.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/about/__pycache__/serializers.cpython-312.pyc
Normal file
BIN
backEnd/about/__pycache__/serializers.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/about/__pycache__/urls.cpython-312.pyc
Normal file
BIN
backEnd/about/__pycache__/urls.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/about/__pycache__/views.cpython-312.pyc
Normal file
BIN
backEnd/about/__pycache__/views.cpython-312.pyc
Normal file
Binary file not shown.
109
backEnd/about/admin.py
Normal file
109
backEnd/about/admin.py
Normal file
@@ -0,0 +1,109 @@
|
||||
from django.contrib import admin
|
||||
from .models import (
|
||||
AboutBanner, AboutStat, AboutSocialLink,
|
||||
AboutService, AboutFeature,
|
||||
AboutProcess, AboutProcessStep,
|
||||
AboutJourney, AboutMilestone
|
||||
)
|
||||
|
||||
|
||||
class AboutStatInline(admin.TabularInline):
|
||||
model = AboutStat
|
||||
extra = 0
|
||||
ordering = ['order']
|
||||
|
||||
|
||||
class AboutSocialLinkInline(admin.TabularInline):
|
||||
model = AboutSocialLink
|
||||
extra = 0
|
||||
ordering = ['order']
|
||||
|
||||
|
||||
@admin.register(AboutBanner)
|
||||
class AboutBannerAdmin(admin.ModelAdmin):
|
||||
list_display = ['title', 'is_active', 'created_at']
|
||||
list_filter = ['is_active', 'created_at']
|
||||
search_fields = ['title', 'description']
|
||||
inlines = [AboutStatInline, AboutSocialLinkInline]
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
|
||||
|
||||
class AboutFeatureInline(admin.TabularInline):
|
||||
model = AboutFeature
|
||||
extra = 0
|
||||
ordering = ['order']
|
||||
|
||||
|
||||
@admin.register(AboutService)
|
||||
class AboutServiceAdmin(admin.ModelAdmin):
|
||||
list_display = ['title', 'is_active', 'created_at']
|
||||
list_filter = ['is_active', 'created_at']
|
||||
search_fields = ['title', 'description']
|
||||
inlines = [AboutFeatureInline]
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
|
||||
|
||||
class AboutProcessStepInline(admin.TabularInline):
|
||||
model = AboutProcessStep
|
||||
extra = 0
|
||||
ordering = ['order']
|
||||
|
||||
|
||||
@admin.register(AboutProcess)
|
||||
class AboutProcessAdmin(admin.ModelAdmin):
|
||||
list_display = ['title', 'is_active', 'created_at']
|
||||
list_filter = ['is_active', 'created_at']
|
||||
search_fields = ['title', 'description']
|
||||
inlines = [AboutProcessStepInline]
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
|
||||
|
||||
class AboutMilestoneInline(admin.TabularInline):
|
||||
model = AboutMilestone
|
||||
extra = 0
|
||||
ordering = ['order']
|
||||
|
||||
|
||||
@admin.register(AboutJourney)
|
||||
class AboutJourneyAdmin(admin.ModelAdmin):
|
||||
list_display = ['title', 'is_active', 'created_at']
|
||||
list_filter = ['is_active', 'created_at']
|
||||
search_fields = ['title', 'description']
|
||||
inlines = [AboutMilestoneInline]
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
|
||||
|
||||
# Register individual models for direct access
|
||||
@admin.register(AboutStat)
|
||||
class AboutStatAdmin(admin.ModelAdmin):
|
||||
list_display = ['banner', 'number', 'label', 'order']
|
||||
list_filter = ['banner']
|
||||
ordering = ['banner', 'order']
|
||||
|
||||
|
||||
@admin.register(AboutSocialLink)
|
||||
class AboutSocialLinkAdmin(admin.ModelAdmin):
|
||||
list_display = ['banner', 'platform', 'url', 'order']
|
||||
list_filter = ['banner', 'platform']
|
||||
ordering = ['banner', 'order']
|
||||
|
||||
|
||||
@admin.register(AboutFeature)
|
||||
class AboutFeatureAdmin(admin.ModelAdmin):
|
||||
list_display = ['service', 'title', 'order']
|
||||
list_filter = ['service']
|
||||
ordering = ['service', 'order']
|
||||
|
||||
|
||||
@admin.register(AboutProcessStep)
|
||||
class AboutProcessStepAdmin(admin.ModelAdmin):
|
||||
list_display = ['process', 'step_number', 'title', 'order']
|
||||
list_filter = ['process']
|
||||
ordering = ['process', 'order']
|
||||
|
||||
|
||||
@admin.register(AboutMilestone)
|
||||
class AboutMilestoneAdmin(admin.ModelAdmin):
|
||||
list_display = ['journey', 'year', 'title', 'order']
|
||||
list_filter = ['journey']
|
||||
ordering = ['journey', 'order']
|
||||
6
backEnd/about/apps.py
Normal file
6
backEnd/about/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AboutConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'about'
|
||||
0
backEnd/about/management/__init__.py
Normal file
0
backEnd/about/management/__init__.py
Normal file
BIN
backEnd/about/management/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backEnd/about/management/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
0
backEnd/about/management/commands/__init__.py
Normal file
0
backEnd/about/management/commands/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,515 @@
|
||||
"""
|
||||
Management command to import enterprise-grade sample data for About Us
|
||||
with images downloaded from Unsplash.
|
||||
"""
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import json
|
||||
from io import BytesIO
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db import transaction
|
||||
from about.models import (
|
||||
AboutBanner, AboutStat, AboutSocialLink,
|
||||
AboutService, AboutFeature,
|
||||
AboutProcess, AboutProcessStep,
|
||||
AboutJourney, AboutMilestone
|
||||
)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Import enterprise-grade sample data for About Us with images from Unsplash'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--unsplash-key',
|
||||
type=str,
|
||||
help='Unsplash API access key (optional, uses Unsplash Source API if not provided)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--width',
|
||||
type=int,
|
||||
default=1200,
|
||||
help='Image width in pixels (default: 1200)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--height',
|
||||
type=int,
|
||||
default=800,
|
||||
help='Image height in pixels (default: 800)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--update-existing',
|
||||
action='store_true',
|
||||
help='Update existing about data instead of skipping it',
|
||||
)
|
||||
|
||||
def download_image_from_unsplash(self, keyword, width=1200, height=800, api_key=None):
|
||||
"""
|
||||
Download an image from Unsplash based on keyword.
|
||||
|
||||
Args:
|
||||
keyword: Search keyword for the image
|
||||
width: Image width in pixels
|
||||
height: Image height in pixels
|
||||
api_key: Optional Unsplash API access key
|
||||
|
||||
Returns:
|
||||
BytesIO object containing the image data, or None if download fails
|
||||
"""
|
||||
try:
|
||||
if api_key:
|
||||
# Use Unsplash API (requires API key)
|
||||
url = "https://api.unsplash.com/photos/random"
|
||||
params = {
|
||||
'query': keyword,
|
||||
'orientation': 'landscape',
|
||||
'w': width,
|
||||
'h': height,
|
||||
'client_id': api_key
|
||||
}
|
||||
query_string = urllib.parse.urlencode(params)
|
||||
full_url = f"{url}?{query_string}"
|
||||
|
||||
req = urllib.request.Request(full_url)
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
image_url = data['urls']['regular']
|
||||
else:
|
||||
# Use Unsplash's direct image service (no key required)
|
||||
# Using curated enterprise/business images
|
||||
image_url = f"https://images.unsplash.com/photo-1552664730-d307ca884978?w={width}&h={height}&fit=crop"
|
||||
|
||||
# Download the actual image
|
||||
req = urllib.request.Request(image_url)
|
||||
req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36')
|
||||
with urllib.request.urlopen(req, timeout=30) as img_response:
|
||||
image_data = BytesIO(img_response.read())
|
||||
image_data.seek(0)
|
||||
|
||||
return image_data
|
||||
except Exception as e:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(f'Failed to download image for "{keyword}": {str(e)}')
|
||||
)
|
||||
return None
|
||||
|
||||
def handle(self, *args, **options):
|
||||
unsplash_key = options.get('unsplash_key')
|
||||
width = options.get('width', 1200)
|
||||
height = options.get('height', 800)
|
||||
update_existing = options.get('update_existing', False)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('Starting enterprise About Us data import...'))
|
||||
|
||||
with transaction.atomic():
|
||||
# Clear existing data if updating
|
||||
if update_existing:
|
||||
self.stdout.write('Clearing existing about data...')
|
||||
AboutBanner.objects.all().delete()
|
||||
AboutService.objects.all().delete()
|
||||
AboutProcess.objects.all().delete()
|
||||
AboutJourney.objects.all().delete()
|
||||
|
||||
# Create About Banner
|
||||
banner_data = {
|
||||
'title': 'Enterprise Software Solutions for Mission-Critical Industries',
|
||||
'subtitle': 'GNX Soft Ltd - Your Trusted Enterprise Technology Partner',
|
||||
'description': 'GNX Soft Ltd is a leading Bulgarian enterprise software company delivering cutting-edge technology solutions to mission-critical industries worldwide. We empower organizations in Defense & Aerospace, Healthcare & Medical, Telecommunication, Banking, Public Sector, E-commerce, Food & Beverages, and Oil & Energy sectors with innovative, secure, and scalable software platforms that drive digital transformation and operational excellence. Our commitment to EU-based infrastructure, privacy-by-design principles, and defense-grade security makes us the preferred technology partner for organizations that demand the highest levels of reliability, compliance, and data protection.',
|
||||
'badge_text': 'Enterprise Software Solutions',
|
||||
'badge_icon': 'fa-solid fa-building',
|
||||
'cta_text': 'Explore Enterprise Solutions',
|
||||
'cta_link': 'services',
|
||||
'cta_icon': 'fa-solid fa-arrow-trend-up',
|
||||
'is_active': True
|
||||
}
|
||||
|
||||
banner, created = AboutBanner.objects.get_or_create(
|
||||
title=banner_data['title'],
|
||||
defaults=banner_data
|
||||
)
|
||||
|
||||
if not created and update_existing:
|
||||
for key, value in banner_data.items():
|
||||
setattr(banner, key, value)
|
||||
banner.save()
|
||||
|
||||
if created or update_existing:
|
||||
self.stdout.write(f'{"Created" if created else "Updated"} banner: {banner.title}')
|
||||
|
||||
# Download banner image
|
||||
self.stdout.write('Downloading banner image...')
|
||||
image_data = self.download_image_from_unsplash(
|
||||
'enterprise business technology office',
|
||||
width=width,
|
||||
height=height,
|
||||
api_key=unsplash_key
|
||||
)
|
||||
if image_data:
|
||||
try:
|
||||
filename = 'about-banner.jpg'
|
||||
banner.image.save(
|
||||
filename,
|
||||
ContentFile(image_data.read()),
|
||||
save=True
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(' ✓ Banner image saved'))
|
||||
except Exception as e:
|
||||
self.stdout.write(self.style.ERROR(f' ✗ Failed to save banner image: {str(e)}'))
|
||||
|
||||
# Clear and recreate stats
|
||||
if update_existing:
|
||||
AboutStat.objects.filter(banner=banner).delete()
|
||||
|
||||
# Create Banner Stats
|
||||
stats_data = [
|
||||
{'number': '8', 'label': 'Industry Verticals', 'order': 1},
|
||||
{'number': '99.9%', 'label': 'Uptime SLA', 'order': 2},
|
||||
{'number': '24/7', 'label': 'Enterprise Support', 'order': 3},
|
||||
{'number': '500+', 'label': 'Enterprise Clients', 'order': 4},
|
||||
{'number': '2020', 'label': 'Founded', 'order': 5},
|
||||
]
|
||||
|
||||
for stat_data in stats_data:
|
||||
AboutStat.objects.get_or_create(
|
||||
banner=banner,
|
||||
label=stat_data['label'],
|
||||
defaults=stat_data
|
||||
)
|
||||
|
||||
# Clear and recreate social links
|
||||
if update_existing:
|
||||
AboutSocialLink.objects.filter(banner=banner).delete()
|
||||
|
||||
# Create Social Links
|
||||
social_links_data = [
|
||||
{
|
||||
'platform': 'LinkedIn',
|
||||
'url': 'https://www.linkedin.com/company/gnxsoft',
|
||||
'icon': 'fa-brands fa-linkedin-in',
|
||||
'aria_label': 'Connect with GNX Soft on LinkedIn',
|
||||
'order': 1
|
||||
},
|
||||
{
|
||||
'platform': 'GitHub',
|
||||
'url': 'https://github.com/gnxsoft',
|
||||
'icon': 'fa-brands fa-github',
|
||||
'aria_label': 'Follow GNX Soft on GitHub',
|
||||
'order': 2
|
||||
},
|
||||
{
|
||||
'platform': 'Twitter',
|
||||
'url': 'https://twitter.com/gnxsoft',
|
||||
'icon': 'fa-brands fa-twitter',
|
||||
'aria_label': 'Follow GNX Soft on Twitter',
|
||||
'order': 3
|
||||
},
|
||||
{
|
||||
'platform': 'Email',
|
||||
'url': 'mailto:info@gnxsoft.com',
|
||||
'icon': 'fa-solid fa-envelope',
|
||||
'aria_label': 'Contact GNX Soft via email',
|
||||
'order': 4
|
||||
},
|
||||
]
|
||||
|
||||
for social_data in social_links_data:
|
||||
AboutSocialLink.objects.get_or_create(
|
||||
banner=banner,
|
||||
platform=social_data['platform'],
|
||||
defaults=social_data
|
||||
)
|
||||
|
||||
# Create About Service
|
||||
service_data = {
|
||||
'title': 'Enterprise Technology Excellence Across Critical Industries',
|
||||
'subtitle': 'About GNX Soft Ltd',
|
||||
'description': 'Founded in 2020 and headquartered in Burgas, Bulgaria, GNX Soft Ltd is a premier enterprise software development company specializing in mission-critical solutions for highly regulated industries. Our expert team of 50+ engineers, architects, and consultants delivers secure, scalable, and compliant software solutions to Defense & Aerospace, Healthcare & Medical, Telecommunication, Banking & Finance, Public Sector, E-commerce, Food & Beverages, and Oil & Energy sectors. With EU-based infrastructure spanning Bulgaria, Germany, and Netherlands, we provide enterprise-grade solutions that meet the highest security and regulatory standards including GDPR, HIPAA, PCI-DSS, and industry-specific compliance requirements. Our commitment to privacy-by-design, defense-grade security, and continuous innovation positions us as a trusted technology partner for Fortune 500 companies and government organizations worldwide.',
|
||||
'badge_text': 'About GNX Soft Ltd',
|
||||
'badge_icon': 'fa-solid fa-users',
|
||||
'cta_text': 'Explore Our Solutions',
|
||||
'cta_link': 'services',
|
||||
'is_active': True
|
||||
}
|
||||
|
||||
service, created = AboutService.objects.get_or_create(
|
||||
title=service_data['title'],
|
||||
defaults=service_data
|
||||
)
|
||||
|
||||
if not created and update_existing:
|
||||
for key, value in service_data.items():
|
||||
setattr(service, key, value)
|
||||
service.save()
|
||||
|
||||
if created or update_existing:
|
||||
self.stdout.write(f'{"Created" if created else "Updated"} service: {service.title}')
|
||||
|
||||
# Download service image
|
||||
self.stdout.write('Downloading service image...')
|
||||
image_data = self.download_image_from_unsplash(
|
||||
'enterprise team collaboration technology',
|
||||
width=width,
|
||||
height=height,
|
||||
api_key=unsplash_key
|
||||
)
|
||||
if image_data:
|
||||
try:
|
||||
filename = 'about-service.jpg'
|
||||
service.image.save(
|
||||
filename,
|
||||
ContentFile(image_data.read()),
|
||||
save=True
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(' ✓ Service image saved'))
|
||||
except Exception as e:
|
||||
self.stdout.write(self.style.ERROR(f' ✗ Failed to save service image: {str(e)}'))
|
||||
|
||||
# Clear and recreate features
|
||||
if update_existing:
|
||||
AboutFeature.objects.filter(service=service).delete()
|
||||
|
||||
# Create Service Features
|
||||
features_data = [
|
||||
{
|
||||
'title': 'EU-Based Company',
|
||||
'description': 'Headquartered in Burgas, Bulgaria with EU-wide presence',
|
||||
'icon': 'fa-solid fa-building',
|
||||
'order': 1
|
||||
},
|
||||
{
|
||||
'title': 'EU Infrastructure',
|
||||
'description': 'Data centers in Bulgaria, Germany, and Netherlands',
|
||||
'icon': 'fa-solid fa-server',
|
||||
'order': 2
|
||||
},
|
||||
{
|
||||
'title': '8 Industry Verticals',
|
||||
'description': 'Specialized expertise across critical sectors',
|
||||
'icon': 'fa-solid fa-industry',
|
||||
'order': 3
|
||||
},
|
||||
{
|
||||
'title': '24/7 Enterprise Support',
|
||||
'description': 'Round-the-clock support with SLA guarantees',
|
||||
'icon': 'fa-solid fa-headset',
|
||||
'order': 4
|
||||
},
|
||||
{
|
||||
'title': '500+ Enterprise Clients',
|
||||
'description': 'Trusted by Fortune 500 and government organizations',
|
||||
'icon': 'fa-solid fa-users',
|
||||
'order': 5
|
||||
},
|
||||
{
|
||||
'title': 'Defense-Grade Security',
|
||||
'description': 'Bank-level security with compliance certifications',
|
||||
'icon': 'fa-solid fa-shield-halved',
|
||||
'order': 6
|
||||
},
|
||||
]
|
||||
|
||||
for feature_data in features_data:
|
||||
AboutFeature.objects.get_or_create(
|
||||
service=service,
|
||||
title=feature_data['title'],
|
||||
defaults=feature_data
|
||||
)
|
||||
|
||||
# Create About Process
|
||||
process_data = {
|
||||
'title': 'Enterprise-Grade Development Methodology',
|
||||
'subtitle': 'Our Methodology',
|
||||
'description': 'GNX Soft Ltd employs a proven enterprise development methodology that combines agile practices with defense-grade security, regulatory compliance, and enterprise scalability. We follow industry best practices including DevOps, CI/CD, microservices architecture, and privacy-by-design principles to deliver robust, secure, and compliant solutions for highly regulated industries. Every project undergoes rigorous security assessments, Data Protection Impact Assessments (DPIAs), penetration testing, and compliance verification to meet the strictest industry standards including GDPR, HIPAA, PCI-DSS, SOC 2, and ISO 27001. Our methodology emphasizes continuous integration, automated testing, infrastructure as code, and comprehensive documentation to ensure long-term maintainability and scalability.',
|
||||
'badge_text': 'Our Methodology',
|
||||
'badge_icon': 'fa-solid fa-cogs',
|
||||
'cta_text': 'View Our Services',
|
||||
'cta_link': 'services',
|
||||
'is_active': True
|
||||
}
|
||||
|
||||
process, created = AboutProcess.objects.get_or_create(
|
||||
title=process_data['title'],
|
||||
defaults=process_data
|
||||
)
|
||||
|
||||
if not created and update_existing:
|
||||
for key, value in process_data.items():
|
||||
setattr(process, key, value)
|
||||
process.save()
|
||||
|
||||
if created or update_existing:
|
||||
self.stdout.write(f'{"Created" if created else "Updated"} process: {process.title}')
|
||||
|
||||
# Download process image
|
||||
self.stdout.write('Downloading process image...')
|
||||
image_data = self.download_image_from_unsplash(
|
||||
'enterprise development methodology process',
|
||||
width=width,
|
||||
height=height,
|
||||
api_key=unsplash_key
|
||||
)
|
||||
if image_data:
|
||||
try:
|
||||
filename = 'about-process.jpg'
|
||||
process.image.save(
|
||||
filename,
|
||||
ContentFile(image_data.read()),
|
||||
save=True
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(' ✓ Process image saved'))
|
||||
except Exception as e:
|
||||
self.stdout.write(self.style.ERROR(f' ✗ Failed to save process image: {str(e)}'))
|
||||
|
||||
# Clear and recreate process steps
|
||||
if update_existing:
|
||||
AboutProcessStep.objects.filter(process=process).delete()
|
||||
|
||||
# Create Process Steps
|
||||
steps_data = [
|
||||
{
|
||||
'step_number': '01',
|
||||
'title': 'Discovery & Compliance Assessment',
|
||||
'description': 'Requirements analysis, regulatory assessment, DPIA, and security planning',
|
||||
'order': 1
|
||||
},
|
||||
{
|
||||
'step_number': '02',
|
||||
'title': 'Secure Development & Architecture',
|
||||
'description': 'Privacy-by-design, secure coding practices, microservices architecture, and continuous testing',
|
||||
'order': 2
|
||||
},
|
||||
{
|
||||
'step_number': '03',
|
||||
'title': 'Deployment & Integration',
|
||||
'description': 'EU infrastructure deployment, system integration, and performance optimization',
|
||||
'order': 3
|
||||
},
|
||||
{
|
||||
'step_number': '04',
|
||||
'title': 'Security Audit & Compliance',
|
||||
'description': 'Penetration testing, security audits, compliance verification, and certification',
|
||||
'order': 4
|
||||
},
|
||||
{
|
||||
'step_number': '05',
|
||||
'title': 'Support & Continuous Monitoring',
|
||||
'description': '24/7 enterprise support, monitoring, breach response, and continuous improvement',
|
||||
'order': 5
|
||||
},
|
||||
]
|
||||
|
||||
for step_data in steps_data:
|
||||
AboutProcessStep.objects.get_or_create(
|
||||
process=process,
|
||||
step_number=step_data['step_number'],
|
||||
defaults=step_data
|
||||
)
|
||||
|
||||
# Create About Journey
|
||||
journey_data = {
|
||||
'title': 'Building Enterprise Excellence Since 2020',
|
||||
'subtitle': 'Our Journey',
|
||||
'description': 'Founded in 2020 in Burgas, Bulgaria, GNX Soft Ltd was established with a clear mission: to deliver world-class enterprise software solutions for mission-critical industries while maintaining the highest standards of security, compliance, and data protection. From our inception, we focused exclusively on enterprise clients in highly regulated sectors including Defense & Aerospace, Healthcare & Medical, Banking, Public Sector, Telecommunication, E-commerce, Food & Beverages, and Oil & Energy. Our commitment to EU-based infrastructure, privacy-by-design principles, and defense-grade security has positioned us as a trusted technology partner for organizations that demand the highest levels of security and regulatory adherence. Today, we serve 500+ enterprise clients across 8 industry verticals, with a team of 50+ experts and EU-wide infrastructure supporting mission-critical operations 24/7.',
|
||||
'badge_text': 'Our Journey',
|
||||
'badge_icon': 'fa-solid fa-rocket',
|
||||
'cta_text': 'Explore Solutions',
|
||||
'cta_link': 'services',
|
||||
'is_active': True
|
||||
}
|
||||
|
||||
journey, created = AboutJourney.objects.get_or_create(
|
||||
title=journey_data['title'],
|
||||
defaults=journey_data
|
||||
)
|
||||
|
||||
if not created and update_existing:
|
||||
for key, value in journey_data.items():
|
||||
setattr(journey, key, value)
|
||||
journey.save()
|
||||
|
||||
if created or update_existing:
|
||||
self.stdout.write(f'{"Created" if created else "Updated"} journey: {journey.title}')
|
||||
|
||||
# Download journey image
|
||||
self.stdout.write('Downloading journey image...')
|
||||
image_data = self.download_image_from_unsplash(
|
||||
'enterprise growth journey success',
|
||||
width=width,
|
||||
height=height,
|
||||
api_key=unsplash_key
|
||||
)
|
||||
if image_data:
|
||||
try:
|
||||
filename = 'about-journey.jpg'
|
||||
journey.image.save(
|
||||
filename,
|
||||
ContentFile(image_data.read()),
|
||||
save=True
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(' ✓ Journey image saved'))
|
||||
except Exception as e:
|
||||
self.stdout.write(self.style.ERROR(f' ✗ Failed to save journey image: {str(e)}'))
|
||||
|
||||
# Clear and recreate milestones
|
||||
if update_existing:
|
||||
AboutMilestone.objects.filter(journey=journey).delete()
|
||||
|
||||
# Create Journey Milestones
|
||||
milestones_data = [
|
||||
{
|
||||
'year': '2020',
|
||||
'title': 'Company Founded',
|
||||
'description': 'GNX Soft Ltd established in Burgas, Bulgaria with focus on enterprise solutions',
|
||||
'order': 1
|
||||
},
|
||||
{
|
||||
'year': '2021',
|
||||
'title': 'Industry Specialization',
|
||||
'description': 'Specialized in 8 mission-critical industries with first enterprise clients',
|
||||
'order': 2
|
||||
},
|
||||
{
|
||||
'year': '2022',
|
||||
'title': 'EU Infrastructure Expansion',
|
||||
'description': 'Deployed EU-wide data centers across Bulgaria, Germany, and Netherlands',
|
||||
'order': 3
|
||||
},
|
||||
{
|
||||
'year': '2023',
|
||||
'title': 'Enterprise Growth',
|
||||
'description': 'Reached 300+ enterprise clients with 50+ expert team members',
|
||||
'order': 4
|
||||
},
|
||||
{
|
||||
'year': '2024',
|
||||
'title': 'Industry Leadership',
|
||||
'description': 'Recognized as leading Bulgarian enterprise software provider with 500+ clients',
|
||||
'order': 5
|
||||
},
|
||||
{
|
||||
'year': '2025',
|
||||
'title': 'Global Expansion',
|
||||
'description': 'Expanding services globally while maintaining EU-based infrastructure and compliance',
|
||||
'order': 6
|
||||
},
|
||||
]
|
||||
|
||||
for milestone_data in milestones_data:
|
||||
AboutMilestone.objects.get_or_create(
|
||||
journey=journey,
|
||||
year=milestone_data['year'],
|
||||
defaults=milestone_data
|
||||
)
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS('\n✓ Successfully imported enterprise About Us data!')
|
||||
)
|
||||
if not unsplash_key:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
'\nNote: Using Unsplash Source API (no key required). '
|
||||
'For better reliability, consider using --unsplash-key with an API key from https://unsplash.com/developers'
|
||||
)
|
||||
)
|
||||
|
||||
239
backEnd/about/management/commands/populate_about_data.py
Normal file
239
backEnd/about/management/commands/populate_about_data.py
Normal file
@@ -0,0 +1,239 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from about.models import (
|
||||
AboutBanner, AboutStat, AboutSocialLink,
|
||||
AboutService, AboutFeature,
|
||||
AboutProcess, AboutProcessStep,
|
||||
AboutJourney, AboutMilestone
|
||||
)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Populate the database with sample about us data'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.stdout.write('Creating sample about us data...')
|
||||
|
||||
# Clear existing data first
|
||||
self.stdout.write('Clearing existing about data...')
|
||||
AboutBanner.objects.all().delete()
|
||||
AboutService.objects.all().delete()
|
||||
AboutProcess.objects.all().delete()
|
||||
AboutJourney.objects.all().delete()
|
||||
|
||||
# Create About Banner
|
||||
banner, created = AboutBanner.objects.get_or_create(
|
||||
title="Enterprise Software Solutions for Mission-Critical Industries",
|
||||
defaults={
|
||||
'subtitle': "GNX Soft Ltd - Your Trusted Enterprise Technology Partner",
|
||||
'description': "GNX Soft Ltd is a leading Bulgarian enterprise software company delivering cutting-edge technology solutions to mission-critical industries worldwide. We empower organizations in Defense & Aerospace, Healthcare & Medical, Telecommunication, Banking, Public Sector, E-commerce, Food & Beverages, and Oil & Energy sectors with innovative, secure, and scalable software platforms that drive digital transformation and operational excellence.",
|
||||
'badge_text': "Enterprise Software Solutions",
|
||||
'badge_icon': "fa-solid fa-building",
|
||||
'cta_text': "Explore Enterprise Solutions",
|
||||
'cta_link': "services",
|
||||
'cta_icon': "fa-solid fa-arrow-trend-up",
|
||||
'is_active': True
|
||||
}
|
||||
)
|
||||
|
||||
if created:
|
||||
self.stdout.write(f'Created banner: {banner.title}')
|
||||
|
||||
# Create Banner Stats
|
||||
stats_data = [
|
||||
{'number': '8', 'label': 'Industry Verticals', 'order': 1},
|
||||
{'number': '99.9%', 'label': 'Uptime SLA', 'order': 2},
|
||||
{'number': '24/7', 'label': 'Enterprise Support', 'order': 3},
|
||||
{'number': '2020', 'label': 'Founded', 'order': 4},
|
||||
]
|
||||
|
||||
for stat_data in stats_data:
|
||||
AboutStat.objects.create(banner=banner, **stat_data)
|
||||
|
||||
# Create Social Links
|
||||
social_links_data = [
|
||||
{
|
||||
'platform': 'LinkedIn',
|
||||
'url': 'https://www.linkedin.com/company/gnxsoft',
|
||||
'icon': 'fa-brands fa-linkedin-in',
|
||||
'aria_label': 'Connect with GNX Soft on LinkedIn',
|
||||
'order': 1
|
||||
},
|
||||
{
|
||||
'platform': 'GitHub',
|
||||
'url': 'https://github.com/gnxsoft',
|
||||
'icon': 'fa-brands fa-github',
|
||||
'aria_label': 'Follow GNX Soft on GitHub',
|
||||
'order': 2
|
||||
},
|
||||
{
|
||||
'platform': 'Twitter',
|
||||
'url': 'https://twitter.com/gnxsoft',
|
||||
'icon': 'fa-brands fa-twitter',
|
||||
'aria_label': 'Follow GNX Soft on Twitter',
|
||||
'order': 3
|
||||
},
|
||||
{
|
||||
'platform': 'Email',
|
||||
'url': 'mailto:info@gnxsoft.com',
|
||||
'icon': 'fa-solid fa-envelope',
|
||||
'aria_label': 'Contact GNX Soft via email',
|
||||
'order': 4
|
||||
},
|
||||
]
|
||||
|
||||
for social_data in social_links_data:
|
||||
AboutSocialLink.objects.create(banner=banner, **social_data)
|
||||
|
||||
# Create About Service
|
||||
service, created = AboutService.objects.get_or_create(
|
||||
title="Enterprise Technology Excellence Across Critical Industries",
|
||||
defaults={
|
||||
'subtitle': "About GNX Soft Ltd",
|
||||
'description': "Founded in 2020 and headquartered in Burgas, Bulgaria, GNX Soft Ltd is a premier enterprise software development company specializing in mission-critical solutions for highly regulated industries. Our expert team delivers secure, scalable, and compliant software solutions to Defense & Aerospace, Healthcare & Medical, Telecommunication, Banking & Finance, Public Sector, E-commerce, Food & Beverages, and Oil & Energy sectors. With EU-based infrastructure, we provide enterprise-grade solutions that meet the highest security and regulatory standards.",
|
||||
'badge_text': "About GNX Soft Ltd",
|
||||
'badge_icon': "fa-solid fa-users",
|
||||
'cta_text': "Explore Our Solutions",
|
||||
'cta_link': "services",
|
||||
'is_active': True
|
||||
}
|
||||
)
|
||||
|
||||
if created:
|
||||
self.stdout.write(f'Created service: {service.title}')
|
||||
|
||||
# Create Service Features
|
||||
features_data = [
|
||||
{
|
||||
'title': 'EU-Based Company',
|
||||
'description': 'Headquartered in Bulgaria',
|
||||
'icon': 'fa-solid fa-building',
|
||||
'order': 1
|
||||
},
|
||||
{
|
||||
'title': 'EU Infrastructure',
|
||||
'description': 'Bulgaria, Germany, Netherlands',
|
||||
'icon': 'fa-solid fa-server',
|
||||
'order': 2
|
||||
},
|
||||
{
|
||||
'title': '8 Industry Verticals',
|
||||
'description': 'Specialized Expertise',
|
||||
'icon': 'fa-solid fa-industry',
|
||||
'order': 3
|
||||
},
|
||||
{
|
||||
'title': '24/7 Support',
|
||||
'description': 'Enterprise Support Team',
|
||||
'icon': 'fa-solid fa-headset',
|
||||
'order': 4
|
||||
},
|
||||
]
|
||||
|
||||
for feature_data in features_data:
|
||||
AboutFeature.objects.create(service=service, **feature_data)
|
||||
|
||||
# Create About Process
|
||||
process, created = AboutProcess.objects.get_or_create(
|
||||
title="Enterprise-Grade Development Methodology",
|
||||
defaults={
|
||||
'subtitle': "Our Methodology",
|
||||
'description': "GNX Soft Ltd employs a proven enterprise development methodology that combines agile practices with defense-grade security, regulatory compliance, and enterprise scalability. We follow industry best practices including DevOps, CI/CD, microservices architecture, and privacy-by-design principles to deliver robust, secure, and compliant solutions for highly regulated industries. Every project undergoes rigorous security assessments, Data Protection Impact Assessments (DPIAs), and compliance verification to meet the strictest industry standards.",
|
||||
'badge_text': "Our Methodology",
|
||||
'badge_icon': "fa-solid fa-cogs",
|
||||
'cta_text': "View Our Services",
|
||||
'cta_link': "services",
|
||||
'is_active': True
|
||||
}
|
||||
)
|
||||
|
||||
if created:
|
||||
self.stdout.write(f'Created process: {process.title}')
|
||||
|
||||
# Create Process Steps
|
||||
steps_data = [
|
||||
{
|
||||
'step_number': '01',
|
||||
'title': 'Discovery & Compliance',
|
||||
'description': 'Requirements analysis, regulatory assessment, and DPIA',
|
||||
'order': 1
|
||||
},
|
||||
{
|
||||
'step_number': '02',
|
||||
'title': 'Secure Development',
|
||||
'description': 'Privacy-by-design, secure coding, continuous testing',
|
||||
'order': 2
|
||||
},
|
||||
{
|
||||
'step_number': '03',
|
||||
'title': 'Deployment & Integration',
|
||||
'description': 'EU infrastructure deployment and system integration',
|
||||
'order': 3
|
||||
},
|
||||
{
|
||||
'step_number': '04',
|
||||
'title': 'Support & Monitoring',
|
||||
'description': '24/7 enterprise support with breach response',
|
||||
'order': 4
|
||||
},
|
||||
]
|
||||
|
||||
for step_data in steps_data:
|
||||
AboutProcessStep.objects.create(process=process, **step_data)
|
||||
|
||||
# Create About Journey
|
||||
journey, created = AboutJourney.objects.get_or_create(
|
||||
title="Building Enterprise Excellence Since 2020",
|
||||
defaults={
|
||||
'subtitle': "Our Journey",
|
||||
'description': "Founded in 2020 in Burgas, Bulgaria, GNX Soft Ltd was established with a clear mission: to deliver world-class enterprise software solutions for mission-critical industries while maintaining the highest standards of security, compliance, and data protection. From our inception, we focused exclusively on enterprise clients in highly regulated sectors including Defense & Aerospace, Healthcare & Medical, Banking, Public Sector, Telecommunication, E-commerce, Food & Beverages, and Oil & Energy. Our commitment to EU-based infrastructure and privacy-by-design principles has positioned us as a trusted technology partner for organizations that demand the highest levels of security and regulatory adherence.",
|
||||
'badge_text': "Our Journey",
|
||||
'badge_icon': "fa-solid fa-rocket",
|
||||
'cta_text': "Explore Solutions",
|
||||
'cta_link': "services",
|
||||
'is_active': True
|
||||
}
|
||||
)
|
||||
|
||||
if created:
|
||||
self.stdout.write(f'Created journey: {journey.title}')
|
||||
|
||||
# Create Journey Milestones
|
||||
milestones_data = [
|
||||
{
|
||||
'year': '2020',
|
||||
'title': 'Company Founded',
|
||||
'description': 'GNX Soft Ltd established in Burgas, Bulgaria',
|
||||
'order': 1
|
||||
},
|
||||
{
|
||||
'year': '2021',
|
||||
'title': 'Industry Focus',
|
||||
'description': 'Specialized in 8 mission-critical industries',
|
||||
'order': 2
|
||||
},
|
||||
{
|
||||
'year': '2022',
|
||||
'title': 'Industry Expansion',
|
||||
'description': 'Expanded to 8 specialized industry verticals',
|
||||
'order': 3
|
||||
},
|
||||
{
|
||||
'year': '2023',
|
||||
'title': 'EU Infrastructure',
|
||||
'description': 'Deployed EU-wide data centers across 3 countries',
|
||||
'order': 4
|
||||
},
|
||||
{
|
||||
'year': '2024',
|
||||
'title': 'Enterprise Leader',
|
||||
'description': 'Recognized as leading Bulgarian enterprise software provider',
|
||||
'order': 5
|
||||
},
|
||||
]
|
||||
|
||||
for milestone_data in milestones_data:
|
||||
AboutMilestone.objects.create(journey=journey, **milestone_data)
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS('Successfully populated about us data!')
|
||||
)
|
||||
180
backEnd/about/migrations/0001_initial.py
Normal file
180
backEnd/about/migrations/0001_initial.py
Normal file
@@ -0,0 +1,180 @@
|
||||
# Generated by Django 4.2.7 on 2025-09-25 16:40
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AboutBanner',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200)),
|
||||
('subtitle', models.CharField(blank=True, max_length=100)),
|
||||
('description', models.TextField()),
|
||||
('badge_text', models.CharField(default='Enterprise Software Solutions', max_length=100)),
|
||||
('badge_icon', models.CharField(default='fa-solid fa-building', max_length=50)),
|
||||
('cta_text', models.CharField(default='Discover Enterprise Solutions', max_length=100)),
|
||||
('cta_link', models.CharField(default='services', max_length=100)),
|
||||
('cta_icon', models.CharField(default='fa-solid fa-arrow-trend-up', max_length=50)),
|
||||
('image', models.ImageField(blank=True, null=True, upload_to='about/banner/')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'About Banner',
|
||||
'verbose_name_plural': 'About Banners',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AboutJourney',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200)),
|
||||
('subtitle', models.CharField(blank=True, max_length=100)),
|
||||
('description', models.TextField()),
|
||||
('badge_text', models.CharField(default='Our Journey', max_length=100)),
|
||||
('badge_icon', models.CharField(default='fa-solid fa-rocket', max_length=50)),
|
||||
('image', models.ImageField(blank=True, null=True, upload_to='about/journey/')),
|
||||
('cta_text', models.CharField(default='Explore Solutions', max_length=100)),
|
||||
('cta_link', models.CharField(default='services', max_length=100)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'About Journey',
|
||||
'verbose_name_plural': 'About Journeys',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AboutProcess',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200)),
|
||||
('subtitle', models.CharField(blank=True, max_length=100)),
|
||||
('description', models.TextField()),
|
||||
('badge_text', models.CharField(default='Our Methodology', max_length=100)),
|
||||
('badge_icon', models.CharField(default='fa-solid fa-cogs', max_length=50)),
|
||||
('image', models.ImageField(blank=True, null=True, upload_to='about/process/')),
|
||||
('cta_text', models.CharField(default='View Our Services', max_length=100)),
|
||||
('cta_link', models.CharField(default='service-single', max_length=100)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'About Process',
|
||||
'verbose_name_plural': 'About Processes',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AboutService',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200)),
|
||||
('subtitle', models.CharField(blank=True, max_length=100)),
|
||||
('description', models.TextField()),
|
||||
('badge_text', models.CharField(default='About Our Company', max_length=100)),
|
||||
('badge_icon', models.CharField(default='fa-solid fa-users', max_length=50)),
|
||||
('image', models.ImageField(blank=True, null=True, upload_to='about/services/')),
|
||||
('cta_text', models.CharField(default='Explore Our Solutions', max_length=100)),
|
||||
('cta_link', models.CharField(default='service-single', max_length=100)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'About Service',
|
||||
'verbose_name_plural': 'About Services',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AboutStat',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('number', models.CharField(max_length=20)),
|
||||
('label', models.CharField(max_length=100)),
|
||||
('order', models.PositiveIntegerField(default=0)),
|
||||
('banner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stats', to='about.aboutbanner')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'About Statistic',
|
||||
'verbose_name_plural': 'About Statistics',
|
||||
'ordering': ['order'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AboutSocialLink',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('platform', models.CharField(max_length=50)),
|
||||
('url', models.URLField()),
|
||||
('icon', models.CharField(max_length=50)),
|
||||
('aria_label', models.CharField(max_length=100)),
|
||||
('order', models.PositiveIntegerField(default=0)),
|
||||
('banner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='social_links', to='about.aboutbanner')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'About Social Link',
|
||||
'verbose_name_plural': 'About Social Links',
|
||||
'ordering': ['order'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AboutProcessStep',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('step_number', models.CharField(max_length=10)),
|
||||
('title', models.CharField(max_length=100)),
|
||||
('description', models.CharField(max_length=200)),
|
||||
('order', models.PositiveIntegerField(default=0)),
|
||||
('process', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='steps', to='about.aboutprocess')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'About Process Step',
|
||||
'verbose_name_plural': 'About Process Steps',
|
||||
'ordering': ['order'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AboutMilestone',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('year', models.CharField(max_length=10)),
|
||||
('title', models.CharField(max_length=100)),
|
||||
('description', models.CharField(max_length=200)),
|
||||
('order', models.PositiveIntegerField(default=0)),
|
||||
('journey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='milestones', to='about.aboutjourney')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'About Milestone',
|
||||
'verbose_name_plural': 'About Milestones',
|
||||
'ordering': ['order'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AboutFeature',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=100)),
|
||||
('description', models.CharField(max_length=200)),
|
||||
('icon', models.CharField(max_length=50)),
|
||||
('order', models.PositiveIntegerField(default=0)),
|
||||
('service', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='features', to='about.aboutservice')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'About Feature',
|
||||
'verbose_name_plural': 'About Features',
|
||||
'ordering': ['order'],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
backEnd/about/migrations/__init__.py
Normal file
0
backEnd/about/migrations/__init__.py
Normal file
Binary file not shown.
BIN
backEnd/about/migrations/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backEnd/about/migrations/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
176
backEnd/about/models.py
Normal file
176
backEnd/about/models.py
Normal file
@@ -0,0 +1,176 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class AboutBanner(models.Model):
|
||||
"""Model for About Us banner section"""
|
||||
title = models.CharField(max_length=200)
|
||||
subtitle = models.CharField(max_length=100, blank=True)
|
||||
description = models.TextField()
|
||||
badge_text = models.CharField(max_length=100, default="Enterprise Software Solutions")
|
||||
badge_icon = models.CharField(max_length=50, default="fa-solid fa-building")
|
||||
cta_text = models.CharField(max_length=100, default="Discover Enterprise Solutions")
|
||||
cta_link = models.CharField(max_length=100, default="services")
|
||||
cta_icon = models.CharField(max_length=50, default="fa-solid fa-arrow-trend-up")
|
||||
image = models.ImageField(upload_to='about/banner/', null=True, blank=True)
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "About Banner"
|
||||
verbose_name_plural = "About Banners"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class AboutStat(models.Model):
|
||||
"""Model for About Us statistics"""
|
||||
banner = models.ForeignKey(AboutBanner, on_delete=models.CASCADE, related_name='stats')
|
||||
number = models.CharField(max_length=20)
|
||||
label = models.CharField(max_length=100)
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
verbose_name = "About Statistic"
|
||||
verbose_name_plural = "About Statistics"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.number} - {self.label}"
|
||||
|
||||
|
||||
class AboutSocialLink(models.Model):
|
||||
"""Model for About Us social links"""
|
||||
banner = models.ForeignKey(AboutBanner, on_delete=models.CASCADE, related_name='social_links')
|
||||
platform = models.CharField(max_length=50)
|
||||
url = models.URLField()
|
||||
icon = models.CharField(max_length=50)
|
||||
aria_label = models.CharField(max_length=100)
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
verbose_name = "About Social Link"
|
||||
verbose_name_plural = "About Social Links"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.platform} - {self.banner.title}"
|
||||
|
||||
|
||||
class AboutService(models.Model):
|
||||
"""Model for About Us service section"""
|
||||
title = models.CharField(max_length=200)
|
||||
subtitle = models.CharField(max_length=100, blank=True)
|
||||
description = models.TextField()
|
||||
badge_text = models.CharField(max_length=100, default="About Our Company")
|
||||
badge_icon = models.CharField(max_length=50, default="fa-solid fa-users")
|
||||
image = models.ImageField(upload_to='about/services/', null=True, blank=True)
|
||||
cta_text = models.CharField(max_length=100, default="Explore Our Solutions")
|
||||
cta_link = models.CharField(max_length=100, default="service-single")
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "About Service"
|
||||
verbose_name_plural = "About Services"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class AboutFeature(models.Model):
|
||||
"""Model for About Us features"""
|
||||
service = models.ForeignKey(AboutService, on_delete=models.CASCADE, related_name='features')
|
||||
title = models.CharField(max_length=100)
|
||||
description = models.CharField(max_length=200)
|
||||
icon = models.CharField(max_length=50)
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
verbose_name = "About Feature"
|
||||
verbose_name_plural = "About Features"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.title} - {self.service.title}"
|
||||
|
||||
|
||||
class AboutProcess(models.Model):
|
||||
"""Model for About Us process section"""
|
||||
title = models.CharField(max_length=200)
|
||||
subtitle = models.CharField(max_length=100, blank=True)
|
||||
description = models.TextField()
|
||||
badge_text = models.CharField(max_length=100, default="Our Methodology")
|
||||
badge_icon = models.CharField(max_length=50, default="fa-solid fa-cogs")
|
||||
image = models.ImageField(upload_to='about/process/', null=True, blank=True)
|
||||
cta_text = models.CharField(max_length=100, default="View Our Services")
|
||||
cta_link = models.CharField(max_length=100, default="service-single")
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "About Process"
|
||||
verbose_name_plural = "About Processes"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class AboutProcessStep(models.Model):
|
||||
"""Model for About Us process steps"""
|
||||
process = models.ForeignKey(AboutProcess, on_delete=models.CASCADE, related_name='steps')
|
||||
step_number = models.CharField(max_length=10)
|
||||
title = models.CharField(max_length=100)
|
||||
description = models.CharField(max_length=200)
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
verbose_name = "About Process Step"
|
||||
verbose_name_plural = "About Process Steps"
|
||||
|
||||
def __str__(self):
|
||||
return f"Step {self.step_number}: {self.title}"
|
||||
|
||||
|
||||
class AboutJourney(models.Model):
|
||||
"""Model for About Us journey section"""
|
||||
title = models.CharField(max_length=200)
|
||||
subtitle = models.CharField(max_length=100, blank=True)
|
||||
description = models.TextField()
|
||||
badge_text = models.CharField(max_length=100, default="Our Journey")
|
||||
badge_icon = models.CharField(max_length=50, default="fa-solid fa-rocket")
|
||||
image = models.ImageField(upload_to='about/journey/', null=True, blank=True)
|
||||
cta_text = models.CharField(max_length=100, default="Explore Solutions")
|
||||
cta_link = models.CharField(max_length=100, default="services")
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "About Journey"
|
||||
verbose_name_plural = "About Journeys"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class AboutMilestone(models.Model):
|
||||
"""Model for About Us milestones"""
|
||||
journey = models.ForeignKey(AboutJourney, on_delete=models.CASCADE, related_name='milestones')
|
||||
year = models.CharField(max_length=10)
|
||||
title = models.CharField(max_length=100)
|
||||
description = models.CharField(max_length=200)
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
verbose_name = "About Milestone"
|
||||
verbose_name_plural = "About Milestones"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.year}: {self.title}"
|
||||
130
backEnd/about/serializers.py
Normal file
130
backEnd/about/serializers.py
Normal file
@@ -0,0 +1,130 @@
|
||||
from rest_framework import serializers
|
||||
from .models import (
|
||||
AboutBanner, AboutStat, AboutSocialLink,
|
||||
AboutService, AboutFeature,
|
||||
AboutProcess, AboutProcessStep,
|
||||
AboutJourney, AboutMilestone
|
||||
)
|
||||
|
||||
|
||||
class AboutStatSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AboutStat
|
||||
fields = ['number', 'label', 'order']
|
||||
|
||||
|
||||
class AboutSocialLinkSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AboutSocialLink
|
||||
fields = ['platform', 'url', 'icon', 'aria_label', 'order']
|
||||
|
||||
|
||||
class AboutBannerSerializer(serializers.ModelSerializer):
|
||||
stats = AboutStatSerializer(many=True, read_only=True)
|
||||
social_links = AboutSocialLinkSerializer(many=True, read_only=True)
|
||||
image_url = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = AboutBanner
|
||||
fields = [
|
||||
'id', 'title', 'subtitle', 'description', 'badge_text', 'badge_icon',
|
||||
'cta_text', 'cta_link', 'cta_icon', 'image_url', 'is_active',
|
||||
'stats', 'social_links', 'created_at', 'updated_at'
|
||||
]
|
||||
|
||||
def get_image_url(self, obj):
|
||||
if obj.image:
|
||||
request = self.context.get('request')
|
||||
if request:
|
||||
return request.build_absolute_uri(obj.image.url)
|
||||
return obj.image.url
|
||||
return None
|
||||
|
||||
|
||||
class AboutFeatureSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AboutFeature
|
||||
fields = ['title', 'description', 'icon', 'order']
|
||||
|
||||
|
||||
class AboutServiceSerializer(serializers.ModelSerializer):
|
||||
features = AboutFeatureSerializer(many=True, read_only=True)
|
||||
image_url = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = AboutService
|
||||
fields = [
|
||||
'id', 'title', 'subtitle', 'description', 'badge_text', 'badge_icon',
|
||||
'image_url', 'cta_text', 'cta_link', 'is_active',
|
||||
'features', 'created_at', 'updated_at'
|
||||
]
|
||||
|
||||
def get_image_url(self, obj):
|
||||
if obj.image:
|
||||
request = self.context.get('request')
|
||||
if request:
|
||||
return request.build_absolute_uri(obj.image.url)
|
||||
return obj.image.url
|
||||
return None
|
||||
|
||||
|
||||
class AboutProcessStepSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AboutProcessStep
|
||||
fields = ['step_number', 'title', 'description', 'order']
|
||||
|
||||
|
||||
class AboutProcessSerializer(serializers.ModelSerializer):
|
||||
steps = AboutProcessStepSerializer(many=True, read_only=True)
|
||||
image_url = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = AboutProcess
|
||||
fields = [
|
||||
'id', 'title', 'subtitle', 'description', 'badge_text', 'badge_icon',
|
||||
'image_url', 'cta_text', 'cta_link', 'is_active',
|
||||
'steps', 'created_at', 'updated_at'
|
||||
]
|
||||
|
||||
def get_image_url(self, obj):
|
||||
if obj.image:
|
||||
request = self.context.get('request')
|
||||
if request:
|
||||
return request.build_absolute_uri(obj.image.url)
|
||||
return obj.image.url
|
||||
return None
|
||||
|
||||
|
||||
class AboutMilestoneSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AboutMilestone
|
||||
fields = ['year', 'title', 'description', 'order']
|
||||
|
||||
|
||||
class AboutJourneySerializer(serializers.ModelSerializer):
|
||||
milestones = AboutMilestoneSerializer(many=True, read_only=True)
|
||||
image_url = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = AboutJourney
|
||||
fields = [
|
||||
'id', 'title', 'subtitle', 'description', 'badge_text', 'badge_icon',
|
||||
'image_url', 'cta_text', 'cta_link', 'is_active',
|
||||
'milestones', 'created_at', 'updated_at'
|
||||
]
|
||||
|
||||
def get_image_url(self, obj):
|
||||
if obj.image:
|
||||
request = self.context.get('request')
|
||||
if request:
|
||||
return request.build_absolute_uri(obj.image.url)
|
||||
return obj.image.url
|
||||
return None
|
||||
|
||||
|
||||
class AboutPageSerializer(serializers.Serializer):
|
||||
"""Combined serializer for the entire about page"""
|
||||
banner = AboutBannerSerializer()
|
||||
service = AboutServiceSerializer()
|
||||
process = AboutProcessSerializer()
|
||||
journey = AboutJourneySerializer()
|
||||
3
backEnd/about/tests.py
Normal file
3
backEnd/about/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
25
backEnd/about/urls.py
Normal file
25
backEnd/about/urls.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = 'about'
|
||||
|
||||
urlpatterns = [
|
||||
# Combined about page data
|
||||
path('page/', views.about_page_data, name='about-page-data'),
|
||||
|
||||
# Banner endpoints
|
||||
path('banner/', views.AboutBannerListAPIView.as_view(), name='about-banner-list'),
|
||||
path('banner/<int:pk>/', views.AboutBannerDetailAPIView.as_view(), name='about-banner-detail'),
|
||||
|
||||
# Service endpoints
|
||||
path('service/', views.AboutServiceListAPIView.as_view(), name='about-service-list'),
|
||||
path('service/<int:pk>/', views.AboutServiceDetailAPIView.as_view(), name='about-service-detail'),
|
||||
|
||||
# Process endpoints
|
||||
path('process/', views.AboutProcessListAPIView.as_view(), name='about-process-list'),
|
||||
path('process/<int:pk>/', views.AboutProcessDetailAPIView.as_view(), name='about-process-detail'),
|
||||
|
||||
# Journey endpoints
|
||||
path('journey/', views.AboutJourneyListAPIView.as_view(), name='about-journey-list'),
|
||||
path('journey/<int:pk>/', views.AboutJourneyDetailAPIView.as_view(), name='about-journey-detail'),
|
||||
]
|
||||
151
backEnd/about/views.py
Normal file
151
backEnd/about/views.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from rest_framework import generics, status
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
from django.shortcuts import get_object_or_404
|
||||
from .models import (
|
||||
AboutBanner, AboutService, AboutProcess, AboutJourney
|
||||
)
|
||||
from .serializers import (
|
||||
AboutBannerSerializer, AboutServiceSerializer,
|
||||
AboutProcessSerializer, AboutJourneySerializer,
|
||||
AboutPageSerializer
|
||||
)
|
||||
|
||||
|
||||
class AboutBannerListAPIView(generics.ListAPIView):
|
||||
"""API view to get all active about banners"""
|
||||
queryset = AboutBanner.objects.filter(is_active=True)
|
||||
serializer_class = AboutBannerSerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context['request'] = self.request
|
||||
return context
|
||||
|
||||
|
||||
class AboutBannerDetailAPIView(generics.RetrieveAPIView):
|
||||
"""API view to get a specific about banner"""
|
||||
queryset = AboutBanner.objects.filter(is_active=True)
|
||||
serializer_class = AboutBannerSerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context['request'] = self.request
|
||||
return context
|
||||
|
||||
|
||||
class AboutServiceListAPIView(generics.ListAPIView):
|
||||
"""API view to get all active about services"""
|
||||
queryset = AboutService.objects.filter(is_active=True)
|
||||
serializer_class = AboutServiceSerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context['request'] = self.request
|
||||
return context
|
||||
|
||||
|
||||
class AboutServiceDetailAPIView(generics.RetrieveAPIView):
|
||||
"""API view to get a specific about service"""
|
||||
queryset = AboutService.objects.filter(is_active=True)
|
||||
serializer_class = AboutServiceSerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context['request'] = self.request
|
||||
return context
|
||||
|
||||
|
||||
class AboutProcessListAPIView(generics.ListAPIView):
|
||||
"""API view to get all active about processes"""
|
||||
queryset = AboutProcess.objects.filter(is_active=True)
|
||||
serializer_class = AboutProcessSerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context['request'] = self.request
|
||||
return context
|
||||
|
||||
|
||||
class AboutProcessDetailAPIView(generics.RetrieveAPIView):
|
||||
"""API view to get a specific about process"""
|
||||
queryset = AboutProcess.objects.filter(is_active=True)
|
||||
serializer_class = AboutProcessSerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context['request'] = self.request
|
||||
return context
|
||||
|
||||
|
||||
class AboutJourneyListAPIView(generics.ListAPIView):
|
||||
"""API view to get all active about journeys"""
|
||||
queryset = AboutJourney.objects.filter(is_active=True)
|
||||
serializer_class = AboutJourneySerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context['request'] = self.request
|
||||
return context
|
||||
|
||||
|
||||
class AboutJourneyDetailAPIView(generics.RetrieveAPIView):
|
||||
"""API view to get a specific about journey"""
|
||||
queryset = AboutJourney.objects.filter(is_active=True)
|
||||
serializer_class = AboutJourneySerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context['request'] = self.request
|
||||
return context
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([AllowAny])
|
||||
def about_page_data(request):
|
||||
"""
|
||||
API endpoint to get all about page data in one request
|
||||
Returns banner, service, process, and journey data
|
||||
"""
|
||||
try:
|
||||
# Get the first active instance of each section
|
||||
banner = AboutBanner.objects.filter(is_active=True).first()
|
||||
service = AboutService.objects.filter(is_active=True).first()
|
||||
process = AboutProcess.objects.filter(is_active=True).first()
|
||||
journey = AboutJourney.objects.filter(is_active=True).first()
|
||||
|
||||
if not all([banner, service, process, journey]):
|
||||
return Response(
|
||||
{'error': 'Some about page sections are not configured'},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
# Serialize each section
|
||||
banner_serializer = AboutBannerSerializer(banner, context={'request': request})
|
||||
service_serializer = AboutServiceSerializer(service, context={'request': request})
|
||||
process_serializer = AboutProcessSerializer(process, context={'request': request})
|
||||
journey_serializer = AboutJourneySerializer(journey, context={'request': request})
|
||||
|
||||
data = {
|
||||
'banner': banner_serializer.data,
|
||||
'service': service_serializer.data,
|
||||
'process': process_serializer.data,
|
||||
'journey': journey_serializer.data
|
||||
}
|
||||
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
except Exception as e:
|
||||
return Response(
|
||||
{'error': f'An error occurred: {str(e)}'},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
Reference in New Issue
Block a user