Compare commits
2 Commits
38c67b4a27
...
11ac82d6f9
| Author | SHA1 | Date | |
|---|---|---|---|
| 11ac82d6f9 | |||
| cd5ad1d753 |
@ -139,9 +139,9 @@ EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
|||||||
EMAIL_HOST = os.getenv('SMTP_HOST', 'smtp.hostinger.com')
|
EMAIL_HOST = os.getenv('SMTP_HOST', 'smtp.hostinger.com')
|
||||||
EMAIL_PORT = int(os.getenv('SMTP_PORT', 465))
|
EMAIL_PORT = int(os.getenv('SMTP_PORT', 465))
|
||||||
EMAIL_USE_SSL = True
|
EMAIL_USE_SSL = True
|
||||||
EMAIL_HOST_USER = os.getenv('SMTP_USERNAME', 'hello@attunehearttherapy.com')
|
EMAIL_HOST_USER = os.getenv('SMTP_USERNAME', 'admin@attunehearttherapy.com')
|
||||||
EMAIL_HOST_PASSWORD = os.getenv('SMTP_PASSWORD')
|
EMAIL_HOST_PASSWORD = os.getenv('SMTP_PASSWORD')
|
||||||
DEFAULT_FROM_EMAIL = os.getenv('SMTP_FROM', 'hello@attunehearttherapy.com')
|
DEFAULT_FROM_EMAIL = os.getenv('SMTP_FROM', 'admin@attunehearttherapy.com')
|
||||||
|
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
|
|||||||
@ -8,6 +8,24 @@ def api_root(request, format=None):
|
|||||||
base_url = request.build_absolute_uri('/api/')
|
base_url = request.build_absolute_uri('/api/')
|
||||||
|
|
||||||
endpoints = {
|
endpoints = {
|
||||||
|
'contact': {
|
||||||
|
'description': 'Contact form submission endpoint',
|
||||||
|
'base_path': '/api/auth/',
|
||||||
|
'endpoints': {
|
||||||
|
'contact': {
|
||||||
|
'description': 'Submit a contact form',
|
||||||
|
'url': request.build_absolute_uri('/api/auth/contact/'),
|
||||||
|
'methods': ['POST'],
|
||||||
|
'required_fields': ['name', 'email', 'phone', 'message'],
|
||||||
|
'example_request': {
|
||||||
|
'name': 'John Doe',
|
||||||
|
'email': 'n8E5I@example.com',
|
||||||
|
'phone': '+1234567890',
|
||||||
|
'message': 'Hello, how can I help you?'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
'authentication': {
|
'authentication': {
|
||||||
'description': 'User authentication and management endpoints',
|
'description': 'User authentication and management endpoints',
|
||||||
'base_path': '/api/auth/',
|
'base_path': '/api/auth/',
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-28 15:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('meetings', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='appointmentrequest',
|
||||||
|
name='jitsi_room_id',
|
||||||
|
field=models.CharField(blank=True, default=None, help_text='Jitsi room ID', max_length=100, null=True, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
105
templates/emails/admin_contact_notification.html
Normal file
105
templates/emails/admin_contact_notification.html
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>New Contact Form Submission</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f3f4f6;">
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f3f4f6; padding: 40px 0;">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 12px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);">
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="background: linear-gradient(135deg, #f43f5e 0%, #ec4899 100%); padding: 40px; border-radius: 12px 12px 0 0; text-align: center;">
|
||||||
|
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: 700;">
|
||||||
|
🔔 New Contact Message
|
||||||
|
</h1>
|
||||||
|
<p style="margin: 10px 0 0 0; color: #fce7f3; font-size: 16px;">
|
||||||
|
Someone just reached out to you
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 40px;">
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" style="margin-bottom: 30px;">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" style="padding-bottom: 20px;">
|
||||||
|
<h2 style="margin: 0; color: #111827; font-size: 20px; font-weight: 600;">
|
||||||
|
Contact Information
|
||||||
|
</h2>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 12px 0; width: 120px; color: #6b7280; font-size: 14px; font-weight: 600;">
|
||||||
|
Name:
|
||||||
|
</td>
|
||||||
|
<td style="padding: 12px 0; color: #111827; font-size: 14px;">
|
||||||
|
{{ contact_message.name }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="border-top: 1px solid #e5e7eb;">
|
||||||
|
<td style="padding: 12px 0; width: 120px; color: #6b7280; font-size: 14px; font-weight: 600;">
|
||||||
|
Email:
|
||||||
|
</td>
|
||||||
|
<td style="padding: 12px 0;">
|
||||||
|
<a href="mailto:{{ contact_message.email }}" style="color: #f43f5e; text-decoration: none; font-size: 14px;">
|
||||||
|
{{ contact_message.email }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% if contact_message.phone %}
|
||||||
|
<tr style="border-top: 1px solid #e5e7eb;">
|
||||||
|
<td style="padding: 12px 0; width: 120px; color: #6b7280; font-size: 14px; font-weight: 600;">
|
||||||
|
Phone:
|
||||||
|
</td>
|
||||||
|
<td style="padding: 12px 0;">
|
||||||
|
<a href="tel:{{ contact_message.phone }}" style="color: #f43f5e; text-decoration: none; font-size: 14px;">
|
||||||
|
{{ contact_message.phone }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
<tr style="border-top: 1px solid #e5e7eb;">
|
||||||
|
<td style="padding: 12px 0; width: 120px; color: #6b7280; font-size: 14px; font-weight: 600;">
|
||||||
|
Received:
|
||||||
|
</td>
|
||||||
|
<td style="padding: 12px 0; color: #111827; font-size: 14px;">
|
||||||
|
{{ contact_message.created_at|date:"F d, Y" }} at {{ contact_message.created_at|time:"h:i A" }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 30px;">
|
||||||
|
<h2 style="margin: 0 0 15px 0; color: #111827; font-size: 20px; font-weight: 600;">
|
||||||
|
Message
|
||||||
|
</h2>
|
||||||
|
<div style="background-color: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; padding: 20px;">
|
||||||
|
<p style="margin: 0; color: #374151; font-size: 14px; line-height: 1.6; white-space: pre-wrap;">{{ contact_message.message }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align: center; margin-top: 30px;">
|
||||||
|
<a href="mailto:{{ contact_message.email }}" style="display: inline-block; background: linear-gradient(135deg, #f43f5e 0%, #ec4899 100%); color: #ffffff; text-decoration: none; padding: 14px 32px; border-radius: 8px; font-weight: 600; font-size: 15px; box-shadow: 0 4px 6px rgba(244, 63, 94, 0.3);">
|
||||||
|
Reply to {{ contact_message.name }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="background-color: #f9fafb; padding: 30px; border-radius: 0 0 12px 12px; text-align: center; border-top: 1px solid #e5e7eb;">
|
||||||
|
<p style="margin: 0; color: #6b7280; font-size: 13px; line-height: 1.6;">
|
||||||
|
This is an automated notification from your contact form.<br>
|
||||||
|
Please respond to the sender as soon as possible.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
80
templates/emails/user_contact_confirmation.html
Normal file
80
templates/emails/user_contact_confirmation.html
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Thank You for Contacting Us</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f3f4f6;">
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f3f4f6; padding: 40px 0;">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 12px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);">
|
||||||
|
<tr>
|
||||||
|
<td style="background: linear-gradient(135deg, #f43f5e 0%, #ec4899 100%); padding: 40px; border-radius: 12px 12px 0 0; text-align: center;">
|
||||||
|
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: 700;">
|
||||||
|
✨ Thank You!
|
||||||
|
</h1>
|
||||||
|
<p style="margin: 10px 0 0 0; color: #fce7f3; font-size: 16px;">
|
||||||
|
We've received your message
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 40px;">
|
||||||
|
<div style="text-align: center; margin-bottom: 30px;">
|
||||||
|
<div style="display: inline-block; background-color: #fef2f2; border-radius: 50%; padding: 20px; margin-bottom: 20px;">
|
||||||
|
<span style="font-size: 48px;">✅</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 style="margin: 0 0 20px 0; color: #111827; font-size: 22px; font-weight: 600; text-align: center;">
|
||||||
|
Hi {{ contact_message.name }},
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p style="margin: 0 0 20px 0; color: #374151; font-size: 16px; line-height: 1.6; text-align: center;">
|
||||||
|
Thank you for reaching out to us. We've received your message and our team will review it shortly. We typically respond within 24-48 hours.
|
||||||
|
</p>
|
||||||
|
<div style="background-color: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; padding: 25px; margin: 30px 0;">
|
||||||
|
<h3 style="margin: 0 0 15px 0; color: #111827; font-size: 16px; font-weight: 600;">
|
||||||
|
Your Message:
|
||||||
|
</h3>
|
||||||
|
<p style="margin: 0; color: #6b7280; font-size: 14px; line-height: 1.6; white-space: pre-wrap;">{{ contact_message.message }}</p>
|
||||||
|
</div>
|
||||||
|
<div style="background-color: #fef2f2; border-left: 4px solid #f43f5e; padding: 20px; border-radius: 6px; margin: 30px 0;">
|
||||||
|
<p style="margin: 0 0 10px 0; color: #991b1b; font-weight: 600; font-size: 14px;">
|
||||||
|
📧 We'll reply to:
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0; color: #b91c1c; font-size: 14px;">
|
||||||
|
{{ contact_message.email }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p style="margin: 30px 0 0 0; color: #6b7280; font-size: 14px; line-height: 1.6; text-align: center;">
|
||||||
|
If you have any urgent questions in the meantime, please don't hesitate to reach out to us directly.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="background-color: #f9fafb; padding: 30px; border-radius: 0 0 12px 12px; text-align: center; border-top: 1px solid #e5e7eb;">
|
||||||
|
<p style="margin: 0 0 15px 0; color: #111827; font-size: 14px; font-weight: 600;">
|
||||||
|
Need immediate assistance?
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0; color: #6b7280; font-size: 13px; line-height: 1.6;">
|
||||||
|
Email us at <a href="mailto:{{support_email}}" style="color: #f43f5e; text-decoration: none;">{{support_email}}</a><br>
|
||||||
|
or call us at <a href="tel:+1754816-2311" style="color: #f43f5e; text-decoration: none;">+1 (754) 816-2311</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #e5e7eb;">
|
||||||
|
<p style="margin: 0 0 10px 0; color: #9ca3af; font-size: 12px;">
|
||||||
|
© {{ current_year }} {{ company_name }}. All rights reserved.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -1,7 +1,5 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import CustomUser, UserProfile
|
from .models import CustomUser, UserProfile, ContactMessage
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
|
|
||||||
@admin.register(CustomUser)
|
@admin.register(CustomUser)
|
||||||
class UserAdmin(admin.ModelAdmin):
|
class UserAdmin(admin.ModelAdmin):
|
||||||
@ -17,3 +15,41 @@ class UserProfileAdmin(admin.ModelAdmin):
|
|||||||
ordering = ('user__email',)
|
ordering = ('user__email',)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ContactMessage)
|
||||||
|
class ContactMessageAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['name', 'email', 'phone', 'created_at', 'is_read', 'is_responded']
|
||||||
|
list_filter = ['is_read', 'is_responded', 'created_at']
|
||||||
|
search_fields = ['name', 'email', 'phone', 'message']
|
||||||
|
readonly_fields = ['created_at']
|
||||||
|
date_hierarchy = 'created_at'
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
('Contact Information', {
|
||||||
|
'fields': ('name', 'email', 'phone')
|
||||||
|
}),
|
||||||
|
('Message', {
|
||||||
|
'fields': ('message',)
|
||||||
|
}),
|
||||||
|
('Status', {
|
||||||
|
'fields': ('is_read', 'is_responded', 'created_at')
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
qs = super().get_queryset(request)
|
||||||
|
return qs.select_related()
|
||||||
|
|
||||||
|
actions = ['mark_as_read', 'mark_as_responded']
|
||||||
|
|
||||||
|
def mark_as_read(self, request, queryset):
|
||||||
|
updated = queryset.update(is_read=True)
|
||||||
|
self.message_user(request, f'{updated} message(s) marked as read.')
|
||||||
|
mark_as_read.short_description = "Mark selected as read"
|
||||||
|
|
||||||
|
def mark_as_responded(self, request, queryset):
|
||||||
|
updated = queryset.update(is_responded=True)
|
||||||
|
self.message_user(request, f'{updated} message(s) marked as responded.')
|
||||||
|
mark_as_responded.short_description = "Mark selected as responded"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
31
users/migrations/0002_contactmessage.py
Normal file
31
users/migrations/0002_contactmessage.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-28 15:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ContactMessage',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('email', models.EmailField(max_length=254)),
|
||||||
|
('phone', models.CharField(blank=True, max_length=20)),
|
||||||
|
('message', models.TextField()),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('is_read', models.BooleanField(default=False)),
|
||||||
|
('is_responded', models.BooleanField(default=False)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Contact Message',
|
||||||
|
'verbose_name_plural': 'Contact Messages',
|
||||||
|
'ordering': ['-created_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -37,4 +37,22 @@ class UserProfile(models.Model):
|
|||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.user.email} Profile"
|
return f"{self.user.email} Profile"
|
||||||
|
|
||||||
|
|
||||||
|
class ContactMessage(models.Model):
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
email = models.EmailField()
|
||||||
|
phone = models.CharField(max_length=20, blank=True)
|
||||||
|
message = models.TextField()
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
is_read = models.BooleanField(default=False)
|
||||||
|
is_responded = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['-created_at']
|
||||||
|
verbose_name = 'Contact Message'
|
||||||
|
verbose_name_plural = 'Contact Messages'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name} - {self.email} - {self.created_at.strftime('%Y-%m-%d')}"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.contrib.auth.password_validation import validate_password
|
from django.contrib.auth.password_validation import validate_password
|
||||||
from .models import CustomUser, UserProfile
|
from .models import CustomUser, UserProfile, ContactMessage
|
||||||
|
|
||||||
class UserProfileSerializer(serializers.ModelSerializer):
|
class UserProfileSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -53,4 +53,23 @@ class ResetPasswordSerializer(serializers.Serializer):
|
|||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CustomUser
|
model = CustomUser
|
||||||
fields = ('id', 'email', 'first_name', 'last_name', 'phone_number', 'isVerified', 'date_joined', 'last_login', 'is_staff', 'is_superuser', 'is_active')
|
fields = ('id', 'email', 'first_name', 'last_name', 'phone_number', 'isVerified', 'date_joined', 'last_login', 'is_staff', 'is_superuser', 'is_active')
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from .models import ContactMessage
|
||||||
|
|
||||||
|
class ContactMessageSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ContactMessage
|
||||||
|
fields = ['id', 'name', 'email', 'phone', 'message', 'created_at']
|
||||||
|
read_only_fields = ['id', 'created_at']
|
||||||
|
|
||||||
|
def validate_name(self, value):
|
||||||
|
if len(value.strip()) < 2:
|
||||||
|
raise serializers.ValidationError("Name must be at least 2 characters long.")
|
||||||
|
return value.strip()
|
||||||
|
|
||||||
|
def validate_message(self, value):
|
||||||
|
if len(value.strip()) < 10:
|
||||||
|
raise serializers.ValidationError("Message must be at least 10 characters long.")
|
||||||
|
return value.strip()
|
||||||
|
|||||||
@ -3,6 +3,8 @@ from rest_framework_simplejwt.views import TokenRefreshView
|
|||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path('contact/', views.ContactMessageView.as_view(), name='contact-message'),
|
||||||
|
|
||||||
|
|
||||||
path('register/', views.register_user, name='register'),
|
path('register/', views.register_user, name='register'),
|
||||||
path('login/', views.login_user, name='login'),
|
path('login/', views.login_user, name='login'),
|
||||||
|
|||||||
@ -6,6 +6,9 @@ from django.conf import settings
|
|||||||
from django.core.mail import EmailMultiAlternatives
|
from django.core.mail import EmailMultiAlternatives
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.html import strip_tags
|
from django.utils.html import strip_tags
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def generate_otp():
|
def generate_otp():
|
||||||
return str(random.randint(100000, 999999))
|
return str(random.randint(100000, 999999))
|
||||||
@ -54,4 +57,49 @@ def send_otp_via_email(email, otp, user_name=None, context='registration'):
|
|||||||
def is_otp_expired(otp_expiry):
|
def is_otp_expired(otp_expiry):
|
||||||
if otp_expiry and timezone.now() < otp_expiry:
|
if otp_expiry and timezone.now() < otp_expiry:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def send_email_notifications(contact_message):
|
||||||
|
try:
|
||||||
|
send_admin_notification(contact_message)
|
||||||
|
|
||||||
|
|
||||||
|
send_user_confirmation(contact_message)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error sending email notifications: {str(e)}")
|
||||||
|
|
||||||
|
def send_admin_notification(contact_message):
|
||||||
|
subject = f"New Contact Form Submission from {contact_message.name}"
|
||||||
|
|
||||||
|
html_content = render_to_string('emails/admin_contact_notification.html', {
|
||||||
|
'contact_message': contact_message
|
||||||
|
})
|
||||||
|
|
||||||
|
email = EmailMultiAlternatives(
|
||||||
|
subject=subject,
|
||||||
|
body=html_content,
|
||||||
|
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||||
|
to=[settings.DEFAULT_FROM_EMAIL]
|
||||||
|
)
|
||||||
|
email.content_subtype = 'html'
|
||||||
|
email.send()
|
||||||
|
|
||||||
|
def send_user_confirmation(self, contact_message):
|
||||||
|
subject = "Thank you for contacting us"
|
||||||
|
|
||||||
|
html_content = render_to_string('emails/user_contact_confirmation.html', {
|
||||||
|
'contact_message': contact_message,
|
||||||
|
'company_name': 'Attune Heart Therapy',
|
||||||
|
'support_email': 'admin@attunehearttherapy.com',
|
||||||
|
'current_year': timezone.now().year,
|
||||||
|
})
|
||||||
|
|
||||||
|
email = EmailMultiAlternatives(
|
||||||
|
subject=subject,
|
||||||
|
body=html_content,
|
||||||
|
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||||
|
to=[contact_message.email]
|
||||||
|
)
|
||||||
|
email.content_subtype = 'html'
|
||||||
|
email.send()
|
||||||
|
|||||||
@ -1,16 +1,51 @@
|
|||||||
from rest_framework import status, generics
|
from rest_framework import status, generics
|
||||||
from rest_framework.decorators import api_view, permission_classes
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser
|
from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser
|
||||||
from rest_framework_simplejwt.tokens import RefreshToken
|
from rest_framework_simplejwt.tokens import RefreshToken
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
from .models import CustomUser, UserProfile
|
from .models import CustomUser, UserProfile, ContactMessage
|
||||||
from .serializers import UserRegistrationSerializer, UserSerializer, ResetPasswordSerializer, ForgotPasswordSerializer, VerifyPasswordResetOTPSerializer
|
from .serializers import UserRegistrationSerializer, UserSerializer, ResetPasswordSerializer, ForgotPasswordSerializer, VerifyPasswordResetOTPSerializer, ContactMessageSerializer
|
||||||
from .utils import send_otp_via_email, is_otp_expired, generate_otp
|
from .utils import send_otp_via_email, is_otp_expired, generate_otp,send_email_notifications
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse
|
||||||
from meetings.models import AppointmentRequest
|
from meetings.models import AppointmentRequest
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class ContactMessageView(APIView):
|
||||||
|
def post(self, request):
|
||||||
|
serializer = ContactMessageSerializer(data=request.data)
|
||||||
|
|
||||||
|
if serializer.is_valid():
|
||||||
|
try:
|
||||||
|
contact_message = serializer.save()
|
||||||
|
|
||||||
|
send_email_notifications(contact_message)
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
'success': True,
|
||||||
|
'message': 'Thank you for your message. We will get back to you soon!',
|
||||||
|
'data': serializer.data
|
||||||
|
}, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing contact form: {str(e)}")
|
||||||
|
return Response({
|
||||||
|
'success': False,
|
||||||
|
'message': 'There was an error processing your request. Please try again later.'
|
||||||
|
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
'success': False,
|
||||||
|
'message': 'Please check your input and try again.',
|
||||||
|
'errors': serializer.errors
|
||||||
|
}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user