Skip to content

Commit 09949f0

Browse files
committed
rebasing
1 parent 0c19fa7 commit 09949f0

File tree

7 files changed

+155
-44
lines changed

7 files changed

+155
-44
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*.pyc
55
__pycache__/
66
local_settings.py
7-
db.sqlite3
7+
db.sqlite3*
88
db.sqlite3-journal
99
db.sqlite3.init
1010
media

appointment/email_sender/email_sender.py

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
# Path: appointment/email_sender/email_sender.py
33
import os
44

5-
from django.core.mail import mail_admins, send_mail
5+
from django.conf import settings
6+
from django.core.mail import send_mail
67
from django.template import loader
78

89
from appointment.logger_config import get_logger
@@ -53,49 +54,70 @@ def render_email_template(template_url, context):
5354

5455

5556
def send_email(recipient_list, subject: str, template_url: str = None, context: dict = None, from_email=None,
56-
message: str = None):
57+
message: str = None, attachments=None):
5758
if not has_required_email_settings():
5859
return
5960

6061
from_email = from_email or APP_DEFAULT_FROM_EMAIL
6162
html_message = render_email_template(template_url, context)
6263

6364
if get_use_django_q_for_emails() and check_q_cluster() and DJANGO_Q_AVAILABLE:
64-
# Asynchronously send the email using Django-Q
65+
# Pass only the necessary data to construct the email
6566
async_task(
66-
"appointment.tasks.send_email_task", recipient_list=recipient_list, subject=subject,
67-
message=message, html_message=html_message if template_url else None, from_email=from_email
67+
'appointment.tasks.send_email_task',
68+
recipient_list=recipient_list,
69+
subject=subject,
70+
message=message,
71+
html_message=html_message,
72+
from_email=from_email,
73+
attachments=attachments
6874
)
6975
else:
7076
# Synchronously send the email
7177
try:
7278
send_mail(
73-
subject=subject, message=message if not template_url else "",
74-
html_message=html_message if template_url else None, from_email=from_email,
75-
recipient_list=recipient_list, fail_silently=False,
79+
subject=subject,
80+
message=message if not template_url else "",
81+
html_message=html_message if template_url else None,
82+
from_email=from_email,
83+
recipient_list=recipient_list,
84+
fail_silently=False,
7685
)
7786
except Exception as e:
7887
logger.error(f"Error sending email: {e}")
7988

8089

81-
def notify_admin(subject: str, template_url: str = None, context: dict = None, message: str = None):
90+
def notify_admin(subject: str, template_url: str = None, context: dict = None, message: str = None,
91+
recipient_email: str = None, attachments=None):
8292
if not has_required_email_settings():
8393
return
8494

8595
html_message = render_email_template(template_url, context)
8696

97+
recipients = [recipient_email] if recipient_email else [email for name, email in settings.ADMINS]
98+
8799
if get_use_django_q_for_emails() and check_q_cluster() and DJANGO_Q_AVAILABLE:
88-
# Enqueue the task to send admin email asynchronously
89-
async_task('appointment.tasks.notify_admin_task', subject=subject, message=message, html_message=html_message)
100+
# Asynchronously send the email using Django-Q
101+
async_task("appointment.tasks.send_email_task",
102+
subject=subject,
103+
message=message,
104+
html_message=html_message,
105+
from_email=settings.DEFAULT_FROM_EMAIL,
106+
recipient_list=recipients,
107+
attachments=attachments)
90108
else:
91-
# Synchronously send admin email
109+
# Synchronously send the email
92110
try:
93-
mail_admins(
94-
subject=subject, message=message if not template_url else "",
95-
html_message=html_message if template_url else None
111+
send_mail(
112+
subject=subject,
113+
message=message if not template_url else "",
114+
html_message=html_message if template_url else None,
115+
from_email=settings.DEFAULT_FROM_EMAIL,
116+
recipient_list=recipients,
117+
fail_silently=False,
96118
)
97119
except Exception as e:
98-
logger.error(f"Error sending email to admin: {e}")
120+
logger.error(f"Error sending email: {e}")
99121

100122

101123
def get_use_django_q_for_emails():

appointment/tasks.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
Author: Adams Pierre David
66
Since: 3.1.0
77
"""
8+
from django.core.mail import EmailMessage
89
from django.utils.translation import gettext as _
910

1011
from appointment.email_sender import notify_admin, send_email
@@ -42,18 +43,23 @@ def send_email_reminder(to_email, first_name, reschedule_link, appointment_id):
4243
)
4344

4445

45-
def send_email_task(recipient_list, subject, message, html_message, from_email):
46-
"""
47-
Task function to send an email asynchronously using Django's send_mail function.
48-
This function tries to send an email and logs an error if it fails.
49-
"""
46+
def send_email_task(recipient_list, subject, message, html_message, from_email, attachments=None):
5047
try:
51-
from django.core.mail import send_mail
52-
logger.info(f"Sending email to {recipient_list} with subject: {subject}")
53-
send_mail(
54-
subject=subject, message=message, html_message=html_message, from_email=from_email,
55-
recipient_list=recipient_list, fail_silently=False,
48+
email = EmailMessage(
49+
subject=subject,
50+
body=message if not html_message else html_message,
51+
from_email=from_email,
52+
to=recipient_list
5653
)
54+
55+
if html_message:
56+
email.content_subtype = "html"
57+
58+
if attachments:
59+
for attachment in attachments:
60+
email.attach(*attachment)
61+
62+
email.send(fail_silently=False)
5763
except Exception as e:
5864
logger.error(f"Error sending email from task: {e}")
5965

appointment/utils/email_ops.py

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from appointment.settings import APPOINTMENT_PAYMENT_URL
2222
from appointment.utils.date_time import convert_24_hour_time_to_12_hour_time
2323
from appointment.utils.db_helpers import get_absolute_url_, get_website_name
24+
from appointment.utils.ics_utils import generate_ics_file
2425

2526
logger = get_logger(__name__)
2627

@@ -46,14 +47,15 @@ def get_thank_you_message(ar: AppointmentRequest) -> str:
4647

4748

4849
def send_thank_you_email(ar: AppointmentRequest, user, request, email: str, appointment_details=None,
49-
account_details=None):
50+
account_details=None, ics_file=None):
5051
"""Send a thank-you email to the client for booking an appointment.
5152
5253
:param ar: The appointment request associated with the booking.
5354
:param user: The user who booked the appointment.
5455
:param email: The email address of the client.
5556
:param appointment_details: Additional details about the appointment (default None).
5657
:param account_details: Additional details about the account (default None).
58+
:param ics_file: The ICS file to attach to the email (default None).
5759
:param request: The request object.
5860
:return: None
5961
"""
@@ -87,7 +89,8 @@ def send_thank_you_email(ar: AppointmentRequest, user, request, email: str, appo
8789
}
8890
send_email(
8991
recipient_list=[email], subject=_("Thank you for booking us."),
90-
template_url='email_sender/thank_you_email.html', context=email_context
92+
template_url='email_sender/thank_you_email.html', context=email_context,
93+
attachments=[('appointment.ics', ics_file, 'text/calendar')]
9194
)
9295

9396

@@ -142,20 +145,64 @@ def send_reset_link_to_staff_member(user, request, email: str, account_details=N
142145

143146
def notify_admin_about_appointment(appointment, client_name: str):
144147
"""Notify the admin and the staff member about a new appointment request."""
145-
logger.info(f"Sending admin notification for new appointment {appointment.id}")
146-
email_context = {
148+
logger.info(f"Sending notifications for new appointment {appointment.id}")
149+
150+
staff_member = appointment.get_staff_member()
151+
ics_file = generate_ics_file(appointment)
152+
153+
# Create a set to keep track of notified email addresses
154+
notified_emails = set()
155+
156+
# Prepare the staff member notification
157+
staff_email = staff_member.user.email
158+
staff_name = staff_member.user.get_full_name() or staff_member.user.username
159+
staff_context = {
160+
'recipient_name': staff_name,
147161
'client_name': client_name,
148-
'appointment': appointment
162+
'appointment': appointment,
163+
'is_staff_member': True,
164+
'staff_member_name': staff_name
149165
}
150166

151-
subject = _("New Appointment Request for ") + client_name
152-
staff_member = appointment.get_staff_member()
153-
notify_admin(subject=subject, template_url='email_sender/admin_new_appointment_email.html', context=email_context)
154-
if staff_member.user.email not in settings.ADMINS:
155-
logger.info(
156-
f"Let's notify the staff member as well for new appointment {appointment.id} since they are not an admin.")
157-
send_email(recipient_list=[staff_member.user.email], subject=subject,
158-
template_url='email_sender/admin_new_appointment_email.html', context=email_context)
167+
# Notify admins
168+
for admin_name, admin_email in settings.ADMINS:
169+
if admin_email in notified_emails:
170+
continue # Skip if this email has already been notified
171+
172+
is_staff_admin = admin_email == staff_email
173+
email_context = staff_context if is_staff_admin else {
174+
'recipient_name': admin_name,
175+
'client_name': client_name,
176+
'appointment': appointment,
177+
'is_staff_member': False,
178+
'staff_member_name': staff_name
179+
}
180+
181+
subject = _("New Appointment Request for ") + client_name
182+
attachments = [('appointment.ics', ics_file, 'text/calendar')] if is_staff_admin else None
183+
184+
notify_admin(
185+
subject=subject,
186+
template_url='email_sender/admin_new_appointment_email.html',
187+
context=email_context,
188+
recipient_email=admin_email,
189+
attachments=attachments
190+
)
191+
192+
notified_emails.add(admin_email)
193+
194+
# Notify staff member if they haven't been notified as an admin
195+
if staff_email not in notified_emails:
196+
logger.info(f"Notifying the staff member for new appointment {appointment.id}")
197+
send_email(
198+
recipient_list=[staff_email],
199+
subject=_("New Appointment Request for ") + client_name,
200+
template_url='email_sender/admin_new_appointment_email.html',
201+
context=staff_context,
202+
attachments=[('appointment.ics', ics_file, 'text/calendar')]
203+
)
204+
205+
logger.info(f"Notifications sent for appointment {appointment.id}")
159206

160207

161208
def send_verification_email(user, email: str):

appointment/utils/ics_utils.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# appointment/utils/ics_utils.py
2+
3+
from datetime import datetime
4+
5+
from icalendar import Calendar, Event
6+
7+
from appointment.utils.db_helpers import Appointment, get_website_name
8+
9+
10+
def generate_ics_file(appointment: Appointment):
11+
company_name = get_website_name()
12+
13+
cal = Calendar()
14+
cal.add('prodid', f"-//{company_name}//DjangoAppointmentSystem//EN")
15+
cal.add('version', '2.0')
16+
17+
event = Event()
18+
event.add('summary', appointment.get_service_name())
19+
event.add('dtstart', appointment.get_start_time())
20+
event.add('dtend', appointment.get_end_time())
21+
event.add('dtstamp', datetime.now())
22+
event.add('location', appointment.address)
23+
event.add('description', appointment.additional_info)
24+
25+
organizer = f"MAILTO:{appointment.appointment_request.staff_member.user.email}"
26+
event.add('organizer', organizer)
27+
28+
attendee = f"MAILTO:{appointment.client.email}"
29+
event.add('attendee', attendee)
30+
31+
cal.add_component(event)
32+
return cal.to_ical()

appointment/views.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
from datetime import date, timedelta
1010

11-
from appointment.settings import check_q_cluster
1211
from django.contrib import messages
1312
from django.contrib.auth import login
1413
from django.contrib.auth.forms import SetPasswordForm
@@ -22,13 +21,14 @@
2221
from django.utils.timezone import get_current_timezone_name
2322
from django.utils.translation import gettext as _
2423

25-
from appointment.forms import AppointmentForm, AppointmentRequestForm, SlotForm, ClientDataForm
24+
from appointment.forms import AppointmentForm, AppointmentRequestForm, ClientDataForm, SlotForm
2625
from appointment.logger_config import get_logger
2726
from appointment.models import (
2827
Appointment, AppointmentRequest, AppointmentRescheduleHistory, Config, DayOff, EmailVerificationCode,
2928
PasswordResetToken, Service,
3029
StaffMember
3130
)
31+
from appointment.settings import check_q_cluster
3232
from appointment.utils.db_helpers import (
3333
can_appointment_be_rescheduled, check_day_off_for_staff, create_and_save_appointment, create_new_user,
3434
create_payment_info_and_get_url, get_non_working_days_for_staff, get_user_by_email, get_user_model,
@@ -47,6 +47,7 @@
4747
from .settings import (APPOINTMENT_PAYMENT_URL, APPOINTMENT_THANK_YOU_URL)
4848
from .utils.date_time import convert_str_to_date
4949
from .utils.error_codes import ErrorCode
50+
from .utils.ics_utils import generate_ics_file
5051
from .utils.json_context import get_generic_context_with_extra, json_response
5152

5253
CLIENT_MODEL = get_user_model()
@@ -395,8 +396,9 @@ def enter_verification_code(request, appointment_request_id, id_request):
395396
'Appointment Time': appointment.appointment_request.start_time,
396397
'Duration': appointment.get_service_duration()
397398
}
399+
ics_file = generate_ics_file(appointment)
398400
send_thank_you_email(ar=appointment_request_object, user=user, email=email,
399-
appointment_details=appointment_details, request=request)
401+
appointment_details=appointment_details, request=request, ics_file=ics_file)
400402
return response
401403
else:
402404
messages.error(request, _("Invalid verification code."))
@@ -433,8 +435,9 @@ def default_thank_you(request, appointment_id):
433435
}
434436
if username_in_user_model():
435437
account_details[_('Username')] = appointment.client.username
438+
ics_file = generate_ics_file(appointment)
436439
send_thank_you_email(ar=ar, user=appointment.client, email=email, appointment_details=appointment_details,
437-
account_details=account_details, request=request)
440+
account_details=account_details, request=request, ics_file=ics_file)
438441
extra_context = {
439442
'appointment': appointment,
440443
}

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ requests~=2.32.3
88
python-dotenv==1.0.1
99
colorama~=0.4.6
1010
django-q2==1.7.3
11+
icalendar~=5.0.13

0 commit comments

Comments
 (0)