mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-25 00:51:12 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
0
django-backend/apps/contact/__init__.py
Normal file
0
django-backend/apps/contact/__init__.py
Normal file
115
django-backend/apps/contact/admin.py
Normal file
115
django-backend/apps/contact/admin.py
Normal file
@@ -0,0 +1,115 @@
|
||||
"""
|
||||
Django admin interface for Contact submissions.
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from django.utils import timezone
|
||||
from .models import ContactSubmission
|
||||
|
||||
|
||||
@admin.register(ContactSubmission)
|
||||
class ContactSubmissionAdmin(admin.ModelAdmin):
|
||||
"""Admin interface for managing contact submissions."""
|
||||
|
||||
list_display = [
|
||||
'ticket_number',
|
||||
'name',
|
||||
'email',
|
||||
'category',
|
||||
'status_badge',
|
||||
'assigned_to',
|
||||
'created_at',
|
||||
]
|
||||
|
||||
list_filter = [
|
||||
'status',
|
||||
'category',
|
||||
'created_at',
|
||||
'assigned_to',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
'ticket_number',
|
||||
'name',
|
||||
'email',
|
||||
'subject',
|
||||
'message',
|
||||
]
|
||||
|
||||
readonly_fields = [
|
||||
'id',
|
||||
'ticket_number',
|
||||
'user',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'resolved_at',
|
||||
]
|
||||
|
||||
fieldsets = (
|
||||
('Contact Information', {
|
||||
'fields': ('ticket_number', 'name', 'email', 'user', 'category')
|
||||
}),
|
||||
('Message', {
|
||||
'fields': ('subject', 'message')
|
||||
}),
|
||||
('Status & Assignment', {
|
||||
'fields': ('status', 'assigned_to', 'admin_notes')
|
||||
}),
|
||||
('Resolution', {
|
||||
'fields': ('resolved_at', 'resolved_by'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
('Metadata', {
|
||||
'fields': ('id', 'created_at', 'updated_at'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
def status_badge(self, obj):
|
||||
"""Display status with colored badge."""
|
||||
colors = {
|
||||
'pending': '#ff9800',
|
||||
'in_progress': '#2196f3',
|
||||
'resolved': '#4caf50',
|
||||
'archived': '#9e9e9e',
|
||||
}
|
||||
color = colors.get(obj.status, '#9e9e9e')
|
||||
return format_html(
|
||||
'<span style="background-color: {}; color: white; padding: 3px 10px; '
|
||||
'border-radius: 3px; font-weight: bold;">{}</span>',
|
||||
color,
|
||||
obj.get_status_display()
|
||||
)
|
||||
status_badge.short_description = 'Status'
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
"""Auto-set resolved_by when status changes to resolved."""
|
||||
if change and 'status' in form.changed_data:
|
||||
if obj.status == 'resolved' and not obj.resolved_by:
|
||||
obj.resolved_by = request.user
|
||||
obj.resolved_at = timezone.now()
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
actions = ['mark_as_in_progress', 'mark_as_resolved', 'assign_to_me']
|
||||
|
||||
def mark_as_in_progress(self, request, queryset):
|
||||
"""Mark selected submissions as in progress."""
|
||||
updated = queryset.update(status='in_progress')
|
||||
self.message_user(request, f'{updated} submission(s) marked as in progress.')
|
||||
mark_as_in_progress.short_description = "Mark as In Progress"
|
||||
|
||||
def mark_as_resolved(self, request, queryset):
|
||||
"""Mark selected submissions as resolved."""
|
||||
updated = queryset.filter(status__in=['pending', 'in_progress']).update(
|
||||
status='resolved',
|
||||
resolved_at=timezone.now(),
|
||||
resolved_by=request.user
|
||||
)
|
||||
self.message_user(request, f'{updated} submission(s) marked as resolved.')
|
||||
mark_as_resolved.short_description = "Mark as Resolved"
|
||||
|
||||
def assign_to_me(self, request, queryset):
|
||||
"""Assign selected submissions to current user."""
|
||||
updated = queryset.update(assigned_to=request.user)
|
||||
self.message_user(request, f'{updated} submission(s) assigned to you.')
|
||||
assign_to_me.short_description = "Assign to Me"
|
||||
7
django-backend/apps/contact/apps.py
Normal file
7
django-backend/apps/contact/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ContactConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.contact'
|
||||
verbose_name = 'Contact Management'
|
||||
300
django-backend/apps/contact/migrations/0001_initial.py
Normal file
300
django-backend/apps/contact/migrations/0001_initial.py
Normal file
@@ -0,0 +1,300 @@
|
||||
# Generated by Django 4.2.8 on 2025-11-09 17:45
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import pgtrigger.compiler
|
||||
import pgtrigger.migrations
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("pghistory", "0006_delete_aggregateevent"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="ContactSubmission",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255)),
|
||||
("email", models.EmailField(max_length=254)),
|
||||
("subject", models.CharField(max_length=255)),
|
||||
("message", models.TextField()),
|
||||
(
|
||||
"category",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("general", "General Inquiry"),
|
||||
("bug", "Bug Report"),
|
||||
("feature", "Feature Request"),
|
||||
("abuse", "Report Abuse"),
|
||||
("data", "Data Correction"),
|
||||
("account", "Account Issue"),
|
||||
("other", "Other"),
|
||||
],
|
||||
default="general",
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
(
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("pending", "Pending Review"),
|
||||
("in_progress", "In Progress"),
|
||||
("resolved", "Resolved"),
|
||||
("archived", "Archived"),
|
||||
],
|
||||
db_index=True,
|
||||
default="pending",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
(
|
||||
"ticket_number",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
help_text="Auto-generated ticket number for tracking",
|
||||
max_length=20,
|
||||
null=True,
|
||||
unique=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"admin_notes",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
help_text="Internal notes for admin use only",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
("resolved_at", models.DateTimeField(blank=True, null=True)),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"assigned_to",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="assigned_contacts",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
"resolved_by",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="resolved_contacts",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="contact_submissions",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Contact Submission",
|
||||
"verbose_name_plural": "Contact Submissions",
|
||||
"ordering": ["-created_at"],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ContactSubmissionEvent",
|
||||
fields=[
|
||||
("pgh_id", models.AutoField(primary_key=True, serialize=False)),
|
||||
("pgh_created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("pgh_label", models.TextField(help_text="The event label.")),
|
||||
(
|
||||
"id",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4, editable=False, serialize=False
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255)),
|
||||
("email", models.EmailField(max_length=254)),
|
||||
("subject", models.CharField(max_length=255)),
|
||||
("message", models.TextField()),
|
||||
(
|
||||
"category",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("general", "General Inquiry"),
|
||||
("bug", "Bug Report"),
|
||||
("feature", "Feature Request"),
|
||||
("abuse", "Report Abuse"),
|
||||
("data", "Data Correction"),
|
||||
("account", "Account Issue"),
|
||||
("other", "Other"),
|
||||
],
|
||||
default="general",
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
(
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("pending", "Pending Review"),
|
||||
("in_progress", "In Progress"),
|
||||
("resolved", "Resolved"),
|
||||
("archived", "Archived"),
|
||||
],
|
||||
default="pending",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
(
|
||||
"ticket_number",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
help_text="Auto-generated ticket number for tracking",
|
||||
max_length=20,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"admin_notes",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
help_text="Internal notes for admin use only",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
("resolved_at", models.DateTimeField(blank=True, null=True)),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"assigned_to",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name="+",
|
||||
related_query_name="+",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
"pgh_context",
|
||||
models.ForeignKey(
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name="+",
|
||||
related_query_name="+",
|
||||
to="pghistory.context",
|
||||
),
|
||||
),
|
||||
(
|
||||
"pgh_obj",
|
||||
models.ForeignKey(
|
||||
db_constraint=False,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name="events",
|
||||
related_query_name="+",
|
||||
to="contact.contactsubmission",
|
||||
),
|
||||
),
|
||||
(
|
||||
"resolved_by",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name="+",
|
||||
related_query_name="+",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name="+",
|
||||
related_query_name="+",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="contactsubmission",
|
||||
index=models.Index(
|
||||
fields=["status", "-created_at"], name="contact_con_status_0384dd_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="contactsubmission",
|
||||
index=models.Index(
|
||||
fields=["category", "-created_at"],
|
||||
name="contact_con_categor_72d10a_idx",
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="contactsubmission",
|
||||
index=models.Index(
|
||||
fields=["ticket_number"], name="contact_con_ticket__fac4eb_idx"
|
||||
),
|
||||
),
|
||||
pgtrigger.migrations.AddTrigger(
|
||||
model_name="contactsubmission",
|
||||
trigger=pgtrigger.compiler.Trigger(
|
||||
name="insert_insert",
|
||||
sql=pgtrigger.compiler.UpsertTriggerSql(
|
||||
func='INSERT INTO "contact_contactsubmissionevent" ("admin_notes", "assigned_to_id", "category", "created_at", "email", "id", "message", "name", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "resolved_at", "resolved_by_id", "status", "subject", "ticket_number", "updated_at", "user_id") VALUES (NEW."admin_notes", NEW."assigned_to_id", NEW."category", NEW."created_at", NEW."email", NEW."id", NEW."message", NEW."name", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."resolved_at", NEW."resolved_by_id", NEW."status", NEW."subject", NEW."ticket_number", NEW."updated_at", NEW."user_id"); RETURN NULL;',
|
||||
hash="cbbb92ce277f4fa1d4fe3dccd8e111b39c9bc9a6",
|
||||
operation="INSERT",
|
||||
pgid="pgtrigger_insert_insert_32905",
|
||||
table="contact_contactsubmission",
|
||||
when="AFTER",
|
||||
),
|
||||
),
|
||||
),
|
||||
pgtrigger.migrations.AddTrigger(
|
||||
model_name="contactsubmission",
|
||||
trigger=pgtrigger.compiler.Trigger(
|
||||
name="update_update",
|
||||
sql=pgtrigger.compiler.UpsertTriggerSql(
|
||||
condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)",
|
||||
func='INSERT INTO "contact_contactsubmissionevent" ("admin_notes", "assigned_to_id", "category", "created_at", "email", "id", "message", "name", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "resolved_at", "resolved_by_id", "status", "subject", "ticket_number", "updated_at", "user_id") VALUES (NEW."admin_notes", NEW."assigned_to_id", NEW."category", NEW."created_at", NEW."email", NEW."id", NEW."message", NEW."name", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."resolved_at", NEW."resolved_by_id", NEW."status", NEW."subject", NEW."ticket_number", NEW."updated_at", NEW."user_id"); RETURN NULL;',
|
||||
hash="ff38205a830f0b09c39d88d8bcce780f7c2fd2ab",
|
||||
operation="UPDATE",
|
||||
pgid="pgtrigger_update_update_a7348",
|
||||
table="contact_contactsubmission",
|
||||
when="AFTER",
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
0
django-backend/apps/contact/migrations/__init__.py
Normal file
0
django-backend/apps/contact/migrations/__init__.py
Normal file
135
django-backend/apps/contact/models.py
Normal file
135
django-backend/apps/contact/models.py
Normal file
@@ -0,0 +1,135 @@
|
||||
"""
|
||||
Contact submission models for user inquiries and support tickets.
|
||||
"""
|
||||
import uuid
|
||||
import pghistory
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
@pghistory.track()
|
||||
class ContactSubmission(models.Model):
|
||||
"""
|
||||
User-submitted contact form messages and support tickets.
|
||||
Tracks all communication from users for admin follow-up.
|
||||
"""
|
||||
STATUS_CHOICES = [
|
||||
('pending', 'Pending Review'),
|
||||
('in_progress', 'In Progress'),
|
||||
('resolved', 'Resolved'),
|
||||
('archived', 'Archived'),
|
||||
]
|
||||
|
||||
CATEGORY_CHOICES = [
|
||||
('general', 'General Inquiry'),
|
||||
('bug', 'Bug Report'),
|
||||
('feature', 'Feature Request'),
|
||||
('abuse', 'Report Abuse'),
|
||||
('data', 'Data Correction'),
|
||||
('account', 'Account Issue'),
|
||||
('other', 'Other'),
|
||||
]
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
|
||||
# Contact Information
|
||||
name = models.CharField(max_length=255)
|
||||
email = models.EmailField()
|
||||
subject = models.CharField(max_length=255)
|
||||
message = models.TextField()
|
||||
category = models.CharField(
|
||||
max_length=50,
|
||||
choices=CATEGORY_CHOICES,
|
||||
default='general'
|
||||
)
|
||||
|
||||
# Status & Assignment
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=STATUS_CHOICES,
|
||||
default='pending',
|
||||
db_index=True
|
||||
)
|
||||
ticket_number = models.CharField(
|
||||
max_length=20,
|
||||
unique=True,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Auto-generated ticket number for tracking"
|
||||
)
|
||||
|
||||
# User Association (if logged in when submitting)
|
||||
user = models.ForeignKey(
|
||||
'users.User',
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='contact_submissions'
|
||||
)
|
||||
|
||||
# Assignment & Resolution
|
||||
assigned_to = models.ForeignKey(
|
||||
'users.User',
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='assigned_contacts'
|
||||
)
|
||||
admin_notes = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Internal notes for admin use only"
|
||||
)
|
||||
resolved_at = models.DateTimeField(null=True, blank=True)
|
||||
resolved_by = models.ForeignKey(
|
||||
'users.User',
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='resolved_contacts'
|
||||
)
|
||||
|
||||
# Timestamps
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Contact Submission'
|
||||
verbose_name_plural = 'Contact Submissions'
|
||||
ordering = ['-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['status', '-created_at']),
|
||||
models.Index(fields=['category', '-created_at']),
|
||||
models.Index(fields=['ticket_number']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
ticket = f" ({self.ticket_number})" if self.ticket_number else ""
|
||||
return f"{self.name} - {self.get_category_display()}{ticket}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Auto-generate ticket number if not set
|
||||
if not self.ticket_number:
|
||||
# Format: CONT-YYYYMMDD-XXXX
|
||||
from django.db.models import Max
|
||||
today = timezone.now().strftime('%Y%m%d')
|
||||
prefix = f"CONT-{today}"
|
||||
|
||||
# Get the highest ticket number for today
|
||||
last_ticket = ContactSubmission.objects.filter(
|
||||
ticket_number__startswith=prefix
|
||||
).aggregate(Max('ticket_number'))['ticket_number__max']
|
||||
|
||||
if last_ticket:
|
||||
# Extract the sequence number and increment
|
||||
seq = int(last_ticket.split('-')[-1]) + 1
|
||||
else:
|
||||
seq = 1
|
||||
|
||||
self.ticket_number = f"{prefix}-{seq:04d}"
|
||||
|
||||
# Set resolved_at when status changes to resolved
|
||||
if self.status == 'resolved' and not self.resolved_at:
|
||||
self.resolved_at = timezone.now()
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
150
django-backend/apps/contact/tasks.py
Normal file
150
django-backend/apps/contact/tasks.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
Celery tasks for contact submission notifications.
|
||||
"""
|
||||
from celery import shared_task
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
|
||||
@shared_task
|
||||
def send_contact_confirmation_email(contact_id):
|
||||
"""
|
||||
Send confirmation email to user who submitted contact form.
|
||||
|
||||
Args:
|
||||
contact_id: UUID of the ContactSubmission
|
||||
"""
|
||||
from .models import ContactSubmission
|
||||
|
||||
try:
|
||||
contact = ContactSubmission.objects.get(id=contact_id)
|
||||
|
||||
# Render email template
|
||||
html_message = render_to_string('emails/contact_confirmation.html', {
|
||||
'name': contact.name,
|
||||
'ticket_number': contact.ticket_number,
|
||||
'subject': contact.subject,
|
||||
'category': contact.get_category_display(),
|
||||
'message': contact.message,
|
||||
})
|
||||
plain_message = strip_tags(html_message)
|
||||
|
||||
# Send email
|
||||
send_mail(
|
||||
subject=f'Contact Form Received - Ticket #{contact.ticket_number}',
|
||||
message=plain_message,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
recipient_list=[contact.email],
|
||||
html_message=html_message,
|
||||
fail_silently=False,
|
||||
)
|
||||
|
||||
return f"Confirmation email sent to {contact.email}"
|
||||
|
||||
except ContactSubmission.DoesNotExist:
|
||||
return f"Contact submission {contact_id} not found"
|
||||
except Exception as e:
|
||||
# Log error but don't fail the task
|
||||
print(f"Error sending contact confirmation: {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
@shared_task
|
||||
def notify_admins_new_contact(contact_id):
|
||||
"""
|
||||
Notify admin team of new contact submission.
|
||||
|
||||
Args:
|
||||
contact_id: UUID of the ContactSubmission
|
||||
"""
|
||||
from .models import ContactSubmission
|
||||
from apps.users.models import User
|
||||
|
||||
try:
|
||||
contact = ContactSubmission.objects.get(id=contact_id)
|
||||
|
||||
# Get all admin and moderator emails
|
||||
admin_emails = User.objects.filter(
|
||||
role__in=['admin', 'moderator']
|
||||
).values_list('email', flat=True)
|
||||
|
||||
if not admin_emails:
|
||||
return "No admin emails found"
|
||||
|
||||
# Render email template
|
||||
html_message = render_to_string('emails/contact_admin_notification.html', {
|
||||
'ticket_number': contact.ticket_number,
|
||||
'name': contact.name,
|
||||
'email': contact.email,
|
||||
'subject': contact.subject,
|
||||
'category': contact.get_category_display(),
|
||||
'message': contact.message,
|
||||
'admin_url': f"{settings.SITE_URL}/admin/contact/contactsubmission/{contact.id}/change/",
|
||||
})
|
||||
plain_message = strip_tags(html_message)
|
||||
|
||||
# Send email
|
||||
send_mail(
|
||||
subject=f'New Contact Submission - Ticket #{contact.ticket_number}',
|
||||
message=plain_message,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
recipient_list=list(admin_emails),
|
||||
html_message=html_message,
|
||||
fail_silently=False,
|
||||
)
|
||||
|
||||
return f"Admin notification sent to {len(admin_emails)} admin(s)"
|
||||
|
||||
except ContactSubmission.DoesNotExist:
|
||||
return f"Contact submission {contact_id} not found"
|
||||
except Exception as e:
|
||||
# Log error but don't fail the task
|
||||
print(f"Error sending admin notification: {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
@shared_task
|
||||
def send_contact_resolution_email(contact_id):
|
||||
"""
|
||||
Send email to user when their contact submission is resolved.
|
||||
|
||||
Args:
|
||||
contact_id: UUID of the ContactSubmission
|
||||
"""
|
||||
from .models import ContactSubmission
|
||||
|
||||
try:
|
||||
contact = ContactSubmission.objects.get(id=contact_id)
|
||||
|
||||
if contact.status != 'resolved':
|
||||
return f"Contact {contact_id} is not resolved yet"
|
||||
|
||||
# Render email template
|
||||
html_message = render_to_string('emails/contact_resolved.html', {
|
||||
'name': contact.name,
|
||||
'ticket_number': contact.ticket_number,
|
||||
'subject': contact.subject,
|
||||
'resolved_by': contact.resolved_by.username if contact.resolved_by else 'Support Team',
|
||||
})
|
||||
plain_message = strip_tags(html_message)
|
||||
|
||||
# Send email
|
||||
send_mail(
|
||||
subject=f'Your Support Ticket Has Been Resolved - #{contact.ticket_number}',
|
||||
message=plain_message,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
recipient_list=[contact.email],
|
||||
html_message=html_message,
|
||||
fail_silently=False,
|
||||
)
|
||||
|
||||
return f"Resolution email sent to {contact.email}"
|
||||
|
||||
except ContactSubmission.DoesNotExist:
|
||||
return f"Contact submission {contact_id} not found"
|
||||
except Exception as e:
|
||||
# Log error but don't fail the task
|
||||
print(f"Error sending resolution email: {str(e)}")
|
||||
raise
|
||||
Reference in New Issue
Block a user