This commit is contained in:
Iliyan Angelov
2025-10-13 01:49:06 +03:00
parent 76c857b4f5
commit 5ad9cbe3a6
97 changed files with 5752 additions and 2376 deletions

View File

@@ -2,7 +2,7 @@ from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import JobPositionViewSet, JobApplicationViewSet
router = DefaultRouter()
router = DefaultRouter(trailing_slash=False)
router.register(r'jobs', JobPositionViewSet, basename='job')
router.register(r'applications', JobApplicationViewSet, basename='application')

View File

@@ -2,6 +2,7 @@ from rest_framework import viewsets, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAdminUser
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
from django_filters.rest_framework import DjangoFilterBackend
from django.shortcuts import get_object_or_404
import logging
@@ -63,6 +64,7 @@ class JobApplicationViewSet(viewsets.ModelViewSet):
queryset = JobApplication.objects.all()
serializer_class = JobApplicationSerializer
parser_classes = [MultiPartParser, FormParser, JSONParser]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['job', 'status']
search_fields = ['first_name', 'last_name', 'email', 'job__title']
@@ -85,20 +87,51 @@ class JobApplicationViewSet(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs):
"""Submit a job application"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
try:
# Build data dict - Django QueryDict returns lists, so we need to get first item
data = {}
# Get POST data (text fields)
for key in request.POST.keys():
value = request.POST.get(key)
data[key] = value
# Get FILES data
for key in request.FILES.keys():
file_obj = request.FILES.get(key)
data[key] = file_obj
except Exception as e:
logger.error(f"Error parsing request: {str(e)}")
return Response(
{'error': 'Error parsing request data'},
status=status.HTTP_400_BAD_REQUEST
)
serializer = self.get_serializer(data=data)
if not serializer.is_valid():
logger.error(f"Validation errors: {serializer.errors}")
return Response(
{'error': 'Validation failed', 'details': serializer.errors},
status=status.HTTP_400_BAD_REQUEST
)
try:
# Save the application
application = serializer.save()
# Send email notifications
email_service = CareerEmailService()
email_service.send_application_confirmation(application)
email_service.send_application_notification_to_admin(application)
logger.info(f"New job application received: {application.full_name} for {application.job.title}")
# Try to send email notifications (non-blocking - don't fail if emails fail)
try:
email_service = CareerEmailService()
email_service.send_application_confirmation(application)
email_service.send_application_notification_to_admin(application)
logger.info(f"Email notifications sent successfully for application {application.id}")
except Exception as email_error:
# Log email error but don't fail the application submission
logger.warning(f"Failed to send email notifications for application {application.id}: {str(email_error)}")
return Response(
{
'message': 'Application submitted successfully',
@@ -106,6 +139,7 @@ class JobApplicationViewSet(viewsets.ModelViewSet):
},
status=status.HTTP_201_CREATED
)
except Exception as e:
logger.error(f"Error submitting job application: {str(e)}", exc_info=True)
return Response(

Binary file not shown.

View File

@@ -245,26 +245,35 @@ CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_ALL_ORIGINS = DEBUG # Only allow all origins in development
# Django URL configuration
APPEND_SLASH = True
# Email Configuration
# Production email settings - use SMTP backend
EMAIL_BACKEND = config('EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')
EMAIL_HOST = config('EMAIL_HOST', default='mail.gnxsoft.com')
EMAIL_PORT = config('EMAIL_PORT', default=587, cast=int)
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=True, cast=bool)
EMAIL_USE_SSL = config('EMAIL_USE_SSL', default=False, cast=bool)
EMAIL_HOST_USER = config('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL')
# Always use .env configuration for consistency across dev and production
# Set USE_SMTP_IN_DEV=True in your .env file to test real email sending in development
USE_SMTP_IN_DEV = config('USE_SMTP_IN_DEV', default=False, cast=bool)
# Email addresses - same for dev and production (from .env)
DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', default='support@gnxsoft.com')
COMPANY_EMAIL = config('COMPANY_EMAIL', default='support@gnxsoft.com')
SUPPORT_EMAIL = config('SUPPORT_EMAIL', default=COMPANY_EMAIL)
if DEBUG and not USE_SMTP_IN_DEV:
# Development: Use console backend to print emails to console (no SMTP required)
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
else:
# Production or Dev with SMTP enabled - use SMTP backend
EMAIL_BACKEND = config('EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')
EMAIL_HOST = config('EMAIL_HOST', default='mail.gnxsoft.com')
EMAIL_PORT = config('EMAIL_PORT', default=587, cast=int)
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=True, cast=bool)
EMAIL_USE_SSL = config('EMAIL_USE_SSL', default=False, cast=bool)
EMAIL_HOST_USER = config('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
# Email timeout settings for production
EMAIL_TIMEOUT = config('EMAIL_TIMEOUT', default=30, cast=int)
# Company email for contact form notifications
COMPANY_EMAIL = config('COMPANY_EMAIL')
# Support email for ticket notifications
SUPPORT_EMAIL = config('SUPPORT_EMAIL', default=config('COMPANY_EMAIL'))
# Site URL for email links
SITE_URL = config('SITE_URL', default='http://localhost:3000')

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

View File

@@ -0,0 +1 @@
Fake PDF content

View File

@@ -0,0 +1 @@
Fake PDF content

View File

@@ -0,0 +1 @@
Test resume content

View File

@@ -0,0 +1 @@
Test resume content

View File

@@ -0,0 +1 @@
test resume content

View File

@@ -0,0 +1 @@
Test resume content

View File

@@ -0,0 +1 @@
Fake PDF content

View File

@@ -0,0 +1 @@
Fake PDF content

View File

@@ -0,0 +1 @@
Test resume content

View File

@@ -0,0 +1 @@
Fake PDF content

View File

@@ -0,0 +1 @@
Fake PDF content

View File

@@ -0,0 +1 @@
test resume

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

View File

@@ -14,6 +14,7 @@ class PolicyAdmin(admin.ModelAdmin):
list_filter = ['type', 'is_active', 'last_updated']
search_fields = ['title', 'type', 'description']
prepopulated_fields = {'slug': ('type',)}
readonly_fields = ('last_updated',)
inlines = [PolicySectionInline]
fieldsets = (

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.7 on 2025-10-11 12:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('services', '0006_service_deliverables_description_and_more'),
]
operations = [
migrations.AddField(
model_name='service',
name='image2',
field=models.ImageField(blank=True, help_text='Secondary service image', null=True, upload_to='services/images/'),
),
migrations.AddField(
model_name='service',
name='image2_url',
field=models.CharField(blank=True, help_text='External secondary image URL (alternative to uploaded image)', max_length=500),
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 4.2.7 on 2025-10-11 12:49
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('services', '0007_service_image2_service_image2_url'),
]
operations = [
migrations.RemoveField(
model_name='service',
name='image2',
),
migrations.RemoveField(
model_name='service',
name='image2_url',
),
]

View File

@@ -89,23 +89,28 @@ class SupportTicketCreateSerializer(serializers.ModelSerializer):
def validate_user_email(self, value):
"""
Validate that the email is registered and active in the RegisteredEmail model
Validate email format and optionally check if registered
"""
from .models import RegisteredEmail
from django.core.validators import validate_email
from django.core.exceptions import ValidationError as DjangoValidationError
# Check if email exists and is active in RegisteredEmail model
# Basic email format validation
try:
validate_email(value)
except DjangoValidationError:
raise serializers.ValidationError("Please enter a valid email address.")
# Optional: Check if email is registered (for analytics/tracking)
# But don't block ticket creation if not registered
try:
registered_email = RegisteredEmail.objects.get(email=value)
if not registered_email.is_active:
raise serializers.ValidationError(
"This email has been deactivated. "
"Please contact us at support@gnxsoft.com for assistance."
)
# Log this but don't block the ticket
logger.warning(f'Ticket submitted by deactivated registered email: {value}')
except RegisteredEmail.DoesNotExist:
raise serializers.ValidationError(
"This email is not registered in our system. "
"Please contact us at support@gnxsoft.com to register your email first."
)
# This is fine - unregistered users can submit tickets
logger.info(f'Ticket submitted by unregistered email: {value}')
return value
@@ -133,12 +138,14 @@ class SupportTicketCreateSerializer(serializers.ModelSerializer):
user_name=ticket.user_name
)
# Update registered email statistics
# Update registered email statistics (only if email is registered)
try:
registered_email = RegisteredEmail.objects.get(email=validated_data['user_email'])
registered_email.increment_ticket_count()
logger.info(f'Updated ticket count for registered email: {validated_data["user_email"]}')
except RegisteredEmail.DoesNotExist:
logger.warning(f'RegisteredEmail not found for {validated_data["user_email"]} after validation')
# This is normal now - unregistered users can submit tickets
logger.info(f'Ticket created by unregistered email: {validated_data["user_email"]}')
# Send email notifications
try:

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env python3
import os
import sys
import django
# Add the backend directory to Python path
sys.path.append('/home/gnx/Desktop/gnx-react/backend')
# Set up Django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gnx.settings')
django.setup()
from about.models import AboutService
# Update the service image
service = AboutService.objects.get(id=1)
service.image = 'about/services/service-image.jpg'
service.save()
print(f"Updated service image: {service.image}")
print(f"Service image URL: {service.image.url if service.image else 'None'}")