updated contact us with emails sending functioanlity

This commit is contained in:
rizwanisready
2024-04-10 12:57:44 +05:30
parent acc0eeb846
commit 1b59354f11
11 changed files with 227 additions and 225 deletions

View File

@@ -190,20 +190,27 @@ class ProfileSerializer(serializers.ModelSerializer):
return ""
def get_has_active_subscription(self, obj):
subscription_status = {
"has_active_subscription": False,
"in_grace_period": False,
"grace_period_end_date": None,
}
today = timezone.now().date()
try:
last_active_subscription = PrincipalSubscription.objects.filter(
principal=obj,
is_paid=True,
cancelled=False,
deleted=False,
active=True,
status=SubscriptionStatus.ACTIVE,
end_date__gte=today,
).latest("end_date")
return True # If the query does not raise DoesNotExist, return True
except PrincipalSubscription.DoesNotExist:
return False # If no matching subscription is found, return False
active_subscriptions = PrincipalSubscription.objects.filter(
principal=obj,
is_paid=True,
cancelled=False,
deleted=False,
active=True,
status=SubscriptionStatus.ACTIVE,
end_date__gte=today,
)
if active_subscriptions.exists():
latest_subscription = active_subscriptions.last()
subscription_status["has_active_subscription"] = latest_subscription.is_paid and latest_subscription.status == SubscriptionStatus.ACTIVE and latest_subscription.end_date >= today
subscription_status["in_grace_period"] = not subscription_status["has_active_subscription"] and latest_subscription.grace_period_end_date >= today
subscription_status["grace_period_end_date"] = latest_subscription.grace_period_end_date if subscription_status["in_grace_period"] else None
return active_subscriptions.exists()
def to_representation(self, instance):
data = super().to_representation(instance)

View File

@@ -113,8 +113,10 @@ class IAmPrincipalForm(forms.ModelForm):
# Set is_superuser and is_staff based on principal_type
if principal_type == models.IAmPrincipalType.objects.get(name=PRINCIPAL_TYPE_ADMIN):
instance.is_superuser = True
instance.is_staff = True
elif principal_type == models.IAmPrincipalType.objects.get(name=PRINCIPAL_TYPE_SUBADMIN):
instance.is_staff = True
instance.is_superuser = False
if commit:
instance.save()
return instance
@@ -327,8 +329,8 @@ class IAmPrincipalResourceLinkForm(IAmPrincipalForm):
model = models.IAmPrincipal
fields = [
"principal_type",
# "first_name",
# "last_name",
"first_name",
"last_name",
"email",
# "password",
# "confirm_password",

View File

@@ -17,7 +17,7 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse_lazy
from django.views import generic
from django.db import models, transaction, IntegrityError
from django.utils import timezone
from accounts import permission
from goodtimes import constants
from . import resource_action
@@ -215,7 +215,7 @@ class PrincipalCreateOrUpdateView(LoginRequiredMixin, generic.View):
if not principal.pk: # pk is None for new objects
principal.created_by = request.user
principal.modified_by = request.user
principal.modified_on = datetime.datetime.now()
principal.modified_on = timezone.now()
# Save the object
principal.save()

View File

@@ -295,7 +295,7 @@ class PaymentProcessingService:
payment_method="",
transaction_status=TransactionStatus.SUCCESS,
amount=0,
coin=1,
coins=1,
comment="Referral reward",
# Populate other fields as necessary, such as `order_id`, `product_id`, or `reference_id` if applicable
)

View File

@@ -0,0 +1,16 @@
import logging
from threading import Thread
# Configure logging at the beginning of your application
logging.basicConfig(level=logging.INFO, filename='app.log', filemode='a', format='%(name)s - %(levelname)s - %(message)s')
def send_email_async(email_service):
try:
email_service.send()
print("Email sent from utils successfully!")
except Exception as e:
# Log the exception
print(f"Failed to send email: {e}")
logging.error(f"Failed to send email: {e}")
# Optionally, you could use other means to notify you of the failure,
# such as sending an alert to an admin email, or using a monitoring service.

View File

@@ -1,6 +1,11 @@
import threading
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from accounts import resource_action
from django.views import generic
from goodtimes.services import EmailService
from manage_communications.utils import send_email_async
from .models import Feedback, ContactUs, Tickets
from goodtimes import constants
from django.shortcuts import get_object_or_404, redirect, render
@@ -42,6 +47,33 @@ class ContactUsReplyView(LoginRequiredMixin, generic.View):
instance.reply = message
instance.save()
messages.success(request, self.success_message)
name = instance.name
email = instance.email_address
subject = instance.subject
# Create an instance of the EmailService
email_service = EmailService(
subject=f"GoodTimes - {subject}",
to=[
email,
],
from_email=settings.EMAIL_HOST_USER,
)
print("email_service: ", email_service)
email_service.load_template(
"manage_communications/contact_us_email.html",
context={
"email": email,
"message": message,
"subject": subject,
"name": name,
},
)
# Send the email using threading
email_thread = threading.Thread(
target=send_email_async, args=(email_service,)
)
email_thread.start()
print("Email sent from views successfully!")
except self.model.DoesNotExist:
messages.error(request, "Contact Us entry not found")
except Exception as e:
@@ -94,6 +126,7 @@ class TicketListView(LoginRequiredMixin, generic.ListView):
context["page_name"] = self.page_name
return context
class FeedbackListView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_MANAGE_FEEDBACK
resource = resource_action.RESOURCE_MANAGE_FEEDBACK

View File

@@ -390,7 +390,7 @@ class WithdrawalRequestCreateAPI(APIView):
return ApiResponse.success(
data=serializer.data,
status=status.HTTP_200_OK,
message=constants.SUCCESS,
message="Request Sent Successfully",
)
else:
return ApiResponse.error(

View File

@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
.container {
max-width: 600px;
margin: 20px auto;
padding: 20px;
background-color: #ffffff;
border: 1px solid #dddddd;
}
.header {
background-color: #4CAF50;
color: #ffffff;
padding: 10px 20px;
text-align: center;
}
.content {
padding: 20px;
}
.footer {
background-color: #333;
/* Dark background for contrast */
color: #f1f1f1;
/* Light text color for readability */
text-align: center;
/* Center-align text */
padding: 20px 10px;
/* Padding for space around the content */
font-size: 14px;
/* Adjust font size as needed */
line-height: 1.6;
/* Improve line spacing for better readability */
border-top: 2px solid #4CAF50;
/* A green top border for a pop of color */
}
.footer p {
margin: 0;
/* Remove default margin for the paragraph */
padding: 0;
/* Remove default padding for a cleaner look */
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
/* A more modern font stack */
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h2>Your Inquiry Has Been Received</h2>
</div>
<div class="content">
<p>Hello {{ name|default:"Customer" }},</p>
<p>Thank you for reaching out to us. We have received your inquiry regarding:</p>
<blockquote>{{ subject }}</blockquote>
<p>Here is our response:</p>
<p><i>{{ message }}</i></p>
<p>We hope this response addresses your concerns. Should you have any further questions, please do not hesitate to
contact us again.</p>
<p>Warm regards,</p>
<p>The GoodTimes Team</p>
</div>
<div class="footer">
<p>
GoodTimes Ltd, UK
</p>
</div>
</div>
</body>
</html>

View File

@@ -149,10 +149,6 @@
{% csrf_token %}
<div class="modal-body">
<input type="hidden" name="id" value="" id="contactUs">
<div class="col-12">
<label for="status" class="form-label">Status</label>
<input type="text" class="form-control" name="status" id="status">
</div>
<div class="col-12">
<label for="message" class="form-label">Message</label>
<textarea id="message" class="form-control" name="message" rows="4" cols="50"></textarea>
@@ -183,6 +179,7 @@
"sSearchPlaceholder": "Search...",
"sLengthMenu": "Results : _MENU_",
},
"order": [[ 0, "desc" ]],
"stripeClasses": [],
"lengthMenu": [5, 10, 20, 50],
"pageLength": 10

View File

@@ -2,10 +2,7 @@
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% include "cdn_through_html/animate_cdn_css.html" %}
{% include "cdn_through_html/modal_cdn_css.html" %}
{% endblock %}
@@ -13,142 +10,65 @@
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<div class="row">
<div class="col-sm-6">
<h3>Manage Feedback</h3>
</div>
<div class="col text-end">
{% comment %} <button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<div class="col-sm-6 text-md-end">
<!--
<button class="btn btn-dark mb-2 me-md-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'manage_cms:faq_category_add' %}">Add Category</a> {% endcomment %}
{% comment %} <a class="btn btn-primary mb-2 me-4" href="{% url 'manage_cms:faq_add' %}">Add FAQ</a> {% endcomment %}
</button>
-->
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="faqs_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="dt--top-section">
<div class="row">
<div class="col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center">
<div class="dataTables_length" id="style-3_length"><label>Results : <select
name="style-3_length" aria-controls="style-3" class="form-control">
<option value="5">5</option>
<option value="10">10</option>
<option value="20">20</option>
<option value="50">50</option>
</select></label></div>
</div>
<div
class="col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3">
<div id="style-3_filter" class="dataTables_filter"><label><svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="feather feather-search">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg><input type="search" class="form-control" placeholder="Search..."
aria-controls="style-3"></label></div>
</div>
</div>
</div>
<div id="style-3_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="faqs" class="table style-3 dt-table-hover dataTable no-footer" role="grid"
<table id="style-3" class="table style-3 dt-table-hover dataTable no-footer" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="checkbox-column sorting_asc" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 69.2656px;"> Record Id </th>
<th class="sorting" tabindex="1" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Customer</th>
<th class="sorting" tabindex="3" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Email</th>
<th class="sorting" tabindex="4" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Comment</th>
<th class="sorting" tabindex="5" aria-controls="style-3"
style="width: 79.7969px;">Rating</th>
<th class="dt-no-sorting sorting" tabindex="6"
aria-controls="style-3"
style="width: 51.625px;">Action</th>
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 69.2656px;"> Record Id </th>
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 69.2656px;"> Customer </th>
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 69.2656px;"> Email </th>
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 69.2656px;"> Comment </th>
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 69.2656px;"> Rating </th>
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 69.2656px;"> Created At </th>
</tr>
</thead>
<tbody>
{% for data_obj in feedback_obj%}
{% for data_obj in feedback_obj %}
<tr role="row">
<td class="text-center sorting_1"> {{data_obj.id}}</td>
<td> {{data_obj.principal.id}}</td>
<td>{% if data_obj.email %}{{ data_obj.email }}{% else %}{{data_obj.principal.email}}{% endif %}</td>
<td>{{data_obj.id}}</td>
<td>{{data_obj.principal}}</td>
<td>{{data_obj.email}}</td>
<td>{{data_obj.comment}}</td>
<td>
{% for _ in "x"|ljust:data_obj.rating %}
<td>{% for _ in "x"|ljust:data_obj.rating %}
<span class="material-symbols-outlined">
star
</span>
{% endfor %}
</td>
<td class="text-center">
<ul class="table-controls">
<li><a href="{% url 'manage_communications:feedback_delete' data_obj.id %}" class="bs-tooltip"
data-bs-toggle="tooltip" data-bs-placement="top" title=""
data-original-title="Delete" data-bs-original-title="Delete"
aria-label="Delete"><svg xmlns="http://www.w3.org/2000/svg"
width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="feather feather-trash p-1 br-8 mb-1">
<polyline points="3 6 5 6 21 6"></polyline>
<path
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2">
</path>
</svg></a></li>
</ul>
</td>
{% endfor %}</td>
<td>{{data_obj.created_on}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="dt--bottom-section d-sm-flex justify-content-sm-between text-center">
<div class="dt--pages-count mb-sm-0 mb-3">
<div class="dataTables_info" id="style-3_info" role="status" aria-live="polite">Showing
page 1 of 1</div>
</div>
<div class="dt--pagination">
<div class="dataTables_paginate paging_simple_numbers" id="style-3_paginate">
<ul class="pagination">
<li class="paginate_button page-item previous disabled" id="style-3_previous"><a
href="#" aria-controls="style-3" data-dt-idx="0" tabindex="0"
class="page-link"><svg xmlns="http://www.w3.org/2000/svg" width="24"
height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="feather feather-arrow-left">
<line x1="19" y1="12" x2="5" y2="12"></line>
<polyline points="12 19 5 12 12 5"></polyline>
</svg></a></li>
<li class="paginate_button page-item active"><a href="#" aria-controls="style-3"
data-dt-idx="1" tabindex="0" class="page-link">1</a></li>
<li class="paginate_button page-item next disabled" id="style-3_next"><a
href="#" aria-controls="style-3" data-dt-idx="2" tabindex="0"
class="page-link"><svg xmlns="http://www.w3.org/2000/svg" width="24"
height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="feather feather-arrow-right">
<line x1="5" y1="12" x2="19" y2="12"></line>
<polyline points="12 5 19 12 12 19"></polyline>
</svg></a></li>
</ul>
</div>
</div>
</div>
</div>
@@ -156,92 +76,32 @@
</div>
</div>
</div>
</div>
</div>
</div>
<!-- modal for View FAQ Question Answer -->
<div class="modal fade" id="faqmodal" tabindex="-1" role="dialog" aria-labelledby="faqModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content" >
<div class="modal-header">
<h5 class="modal-title" id="faqModalLabel">Faq</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
</button>
</div>
<div class="modal-body">
<div class="card mb-2">
<div class="card-body">
<h6 class="card-title">Question</h6>
<p class="mb-0" id="questionData"></p>
</div>
</div>
<div class="card">
<div class="card-body">
<h6 class="card-title">Answer</h6>
<p class="mb-0" id="answerData"></p>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-dark" data-bs-dismiss="modal">Discard</button>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
<script>
faq_category = $('#faq-category').DataTable({
"dom": "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
"oLanguage": {
"oPaginate": { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
"sInfo": "Showing page _PAGE_ of _PAGES_",
"sSearch": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
"sSearchPlaceholder": "Search...",
"sLengthMenu": "Results : _MENU_",
},
"stripeClasses": [],
"lengthMenu": [5, 10, 20, 50],
"pageLength": 10
});
faqs = $('#faqs').DataTable({
"dom": "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
"oLanguage": {
"oPaginate": { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
"sInfo": "Showing page _PAGE_ of _PAGES_",
"sSearch": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
"sSearchPlaceholder": "Search...",
"sLengthMenu": "Results : _MENU_",
},
"stripeClasses": [],
"lengthMenu": [5, 10, 20, 50],
"pageLength": 10
});
<!-- Faq modal show and set data-->
function faqModal(question, answer) {
// Set the data in the modal content
$("#questionData").text(question);
$("#answerData").text(answer);
// Show the modal
$('#faqmodal').modal('show');
}
</script>
{% endblock %}
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
<script>
c3 = $('#style-3').DataTable({
"dom": "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
"oLanguage": {
"oPaginate": { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
"sInfo": "Showing page _PAGE_ of _PAGES_",
"sSearch": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
"sSearchPlaceholder": "Search...",
"sLengthMenu": "Results : _MENU_",
},
"order": [[ 0, "desc" ]],
"stripeClasses": [],
"lengthMenu": [5, 10, 20, 50],
"pageLength": 10
});
multiCheck(c3);
</script>
{% endblock %}

View File

@@ -178,6 +178,7 @@
"sSearchPlaceholder": "Search...",
"sLengthMenu": "Results : _MENU_",
},
"order": [[ 0, "desc" ]],
"stripeClasses": [],
"lengthMenu": [5, 10, 20, 50],
"pageLength": 10