update
@@ -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')
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
BIN
gnx-react/backend/media/about/banner/649891.jpg
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
gnx-react/backend/media/about/journey/2149512727.jpg
Normal file
|
After Width: | Height: | Size: 785 KiB |
BIN
gnx-react/backend/media/about/process/124061.jpg
Normal file
|
After Width: | Height: | Size: 333 KiB |
BIN
gnx-react/backend/media/about/services/649891.jpg
Normal file
|
After Width: | Height: | Size: 134 KiB |
@@ -0,0 +1 @@
|
||||
Fake PDF content
|
||||
@@ -0,0 +1 @@
|
||||
Fake PDF content
|
||||
@@ -0,0 +1 @@
|
||||
Test resume content
|
||||
@@ -0,0 +1 @@
|
||||
Test resume content
|
||||
@@ -0,0 +1 @@
|
||||
test resume content
|
||||
@@ -0,0 +1 @@
|
||||
Test resume content
|
||||
@@ -0,0 +1 @@
|
||||
Fake PDF content
|
||||
@@ -0,0 +1 @@
|
||||
Fake PDF content
|
||||
@@ -0,0 +1 @@
|
||||
Test resume content
|
||||
@@ -0,0 +1 @@
|
||||
Fake PDF content
|
||||
@@ -0,0 +1 @@
|
||||
Fake PDF content
|
||||
@@ -0,0 +1 @@
|
||||
test resume
|
||||
|
After Width: | Height: | Size: 409 KiB |
BIN
gnx-react/backend/media/services/images/backend-engineering.jpg
Normal file
|
After Width: | Height: | Size: 226 KiB |
|
After Width: | Height: | Size: 490 KiB |
BIN
gnx-react/backend/media/services/images/data-replication.jpg
Normal file
|
After Width: | Height: | Size: 363 KiB |
|
After Width: | Height: | Size: 274 KiB |
BIN
gnx-react/backend/media/services/images/frontend-engineering.jpg
Normal file
|
After Width: | Height: | Size: 278 KiB |
|
After Width: | Height: | Size: 349 KiB |
@@ -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 = (
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -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',
|
||||
),
|
||||
]
|
||||
@@ -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:
|
||||
|
||||
@@ -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'}")
|
||||