Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 35 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,22 @@
⚠️ **IMPORTANT**: If upgrading from a version before 2.x.x, please note significant database changes were introduced in
Version 2.0.0 introduces significant database changes. Please read
the [migration guide](https://github.com/adamspd/django-appointment/tree/main/docs/migration_guides/v2_1_0.md) before
updating. No database changes were introduced in version 3.0.1.
updating. Version 3.1.0 introduces the ability to send email reminders for appointments using Django Q for efficient
task scheduling.

Django-Appointment is a Django app engineered for managing appointment scheduling with ease and flexibility. It enables
users to define custom configurations for time slots, lead time, and finish time, or utilize the default values
provided. This app proficiently manages conflicts and availability for appointments, ensuring a seamless user
experience.

For a detailed walkthrough and live example of the system, please refer to
For a detailed walkthrough and live example of the system, please refer to
[this tutorial](https://github.com/adamspd/django-appointment/tree/main/docs/explanation.md).

Detailed documentation can be found in
the [docs' directory](https://github.com/adamspd/django-appointment/tree/main/docs/README.md).
For changes and migration information, please refer to the [release
notes](https://github.com/adamspd/django-appointment/tree/main/docs/release_notes/latest.md).
For changes and migration information, please refer to the release
notes [here](https://github.com/adamspd/django-appointment/releases)
and [here](https://github.com/adamspd/django-appointment/tree/main/docs/release_notes).

## Features ✨

Expand All @@ -36,50 +38,19 @@ notes](https://github.com/adamspd/django-appointment/tree/main/docs/release_note
3. Seamless integration with the Django admin interface for appointment management.
4. Custom admin interface for managing appointment/staff member editing, creation, availability, and conflicts.
5. User-friendly interface for viewing available time slots and scheduling appointments.
6. Capability to send email notifications to clients upon scheduling an appointment.
6. Capability to send email notifications to clients upon scheduling an appointment and email reminders for
appointments, leveraging Django Q for task scheduling and efficiency.

## Key features introduced in previous versions.

- For more information, please refer to
this [documentation](https://github.com/adamspd/django-appointment/tree/main/docs/history/readme_v2_1_1.md).
this [documentation](https://github.com/adamspd/django-appointment/tree/main/docs/history).

## Added Features in version 3.0.1
## Added Features and Bug Fixes in version 3.1.0

This release of Django Appointment brings a series of improvements and updates aimed at enhancing the overall
functionality and user experience:

1. **Dynamic Appointment Management (#49, #55)**

2. **User Interface Enhancements and JavaScript Refactoring (#55)**

3. **Dynamic Label Customization in Appointment Pages (#19)**

4. **Updated Documentation and Workflow Enhancements (#25, #26, #27)**

5. **Community Engagement and Standards (#21, #22, #23, #24)**

6. **Library Updates and Security Patches (#14, #15, #18)**

7. **Enhanced Project Visibility (#16)**

8. **Translation Refinements (#31)**

9. **Bug Fixes (#48)**

See more at the [release notes](https://github.com/adamspd/django-appointment/tree/main/docs/release_notes/latest.md).

These updates collectively contribute to the robustness and versatility of the Django Appointment package, aligning with
our commitment to providing a high-quality and user-friendly appointment management solution.

### Bug Fixes 🆕

See the [release notes](https://github.com/adamspd/django-appointment/tree/main/docs/release_notes/latest.md)
for more information.

### Breaking Changes in version 3.0.1:

See the [release notes](https://github.com/adamspd/django-appointment/tree/main/docs/release_notes/latest.md) for more
information.
See the [release notes](https://github.com/adamspd/django-appointment/releases/tag/v3.1.0).
For older version,
see their [release notes](https://github.com/adamspd/django-appointment/tree/main/docs/release_notes).

## Quick Start 🚀

Expand All @@ -89,6 +60,7 @@ See the [release notes](https://github.com/adamspd/django-appointment/tree/main/
INSTALLED_APPS = [
# other apps
'appointment',
'django_q',
]
```

Expand All @@ -102,7 +74,6 @@ See the [release notes](https://github.com/adamspd/django-appointment/tree/main/
path('appointment/', include('appointment.urls')),
]
```

3. In your Django's `settings.py`, append the following:

```python
Expand Down Expand Up @@ -146,13 +117,29 @@ See the [release notes](https://github.com/adamspd/django-appointment/tree/main/
<p>® 2023 {{ APPOINTMENT_WEBSITE_NAME }}. All Rights Reserved.</p>
```

4. Execute `python manage.py migrate` to create the appointment models.
5. Launch the development server and navigate to http://127.0.0.1:8000/admin/ to create appointments, manage
Configure `Q_CLUSTER` in your Django's `settings.py` to enable Django Q task scheduling:
```python
Q_CLUSTER = {
'name': 'DjangORM',
'workers': 4,
'timeout': 90,
'retry': 120,
'queue_limit': 50,
'bulk': 10,
'orm': 'default',
}
```

4. Run `python manage.py migrate` to create the appointment models.

5. Start the Django Q cluster with `python manage.py qcluster`.

6. Launch the development server and navigate to http://127.0.0.1:8000/admin/ to create appointments, manage
configurations, and handle appointment conflicts (the Admin app must be enabled).
6. You must create at least one service before using the application on the admin page. If your service is free, input 0
7. You must create at least one service before using the application on the admin page. If your service is free, input 0
as the price. If your service is paid, input the price in the price field. You may also provide a description for
your service.
7. Visit http://127.0.0.1:8000/appointment/request/<service_id>/ to view the available time slots and schedule an
8. Visit http://127.0.0.1:8000/appointment/request/<service_id>/ to view the available time slots and schedule an
appointment.

## Customization 🔧
Expand Down Expand Up @@ -186,7 +173,7 @@ information.

## Notes 📝⚠️

Currently, the application does not send email reminders yet. I'm also working on a testing website for the application
I'm working on a testing website for the application
that is not fully functional yet, no hard feelings. But you can check it out
at [https://django-appt.adamspierredavid.com/](https://django-appt.adamspierredavid.com/). Ideas are welcome here since
I'm blocked on a few points.
Expand Down
1 change: 1 addition & 0 deletions appointment/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
__description__ = "Managing appointment scheduling with customizable slots, staff features, and conflict handling."
__package_name__ = "django-appointment"
__url__ = "https://github.com/adamspd/django-appointment"
__package_website__ = "https://django-appt.adamspierredavid.com/"
__version__ = "3.0.1"
__test_version__ = False
10 changes: 9 additions & 1 deletion appointment/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
day_off_exists_for_date_range, exclude_booked_slots, get_all_appointments, get_all_staff_members,
get_appointment_by_id, get_appointments_for_date_and_time, get_staff_member_appointment_list,
get_staff_member_from_user_id_or_logged_in, get_times_from_config, get_user_by_email,
get_working_hours_for_staff_and_day, parse_name, working_hours_exist)
get_working_hours_for_staff_and_day, parse_name, update_appointment_reminder, working_hours_exist)
from appointment.utils.error_codes import ErrorCode
from appointment.utils.json_context import convert_appointment_to_json, get_generic_context, json_response
from appointment.utils.permissions import check_entity_ownership
Expand Down Expand Up @@ -326,6 +326,11 @@ def save_appointment(appt, client_name, client_email, start_time, phone_number,

# Modify and save appointment request details
appt_request = appt.appointment_request

# Update reminder here
update_appointment_reminder(appointment=appt, new_date=appt_request.date, new_start_time=start_time,
want_reminder=want_reminder)

appt_request.service = service
appt_request.start_time = start_time
appt_request.end_time = end_time
Expand Down Expand Up @@ -367,6 +372,9 @@ def save_appt_date_time(appt_start_time, appt_date, appt_id):
else:
appt_date_obj = appt_date

# Update reminder here
update_appointment_reminder(appointment=appt, new_date=appt_date_obj, new_start_time=appt_start_time_obj)

# Modify and save appointment request details
appt_request = appt.appointment_request
appt_request.date = appt_date_obj
Expand Down
48 changes: 48 additions & 0 deletions appointment/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from django.conf import settings
from django.conf.global_settings import DEFAULT_FROM_EMAIL

from appointment.logger_config import logger

APPOINTMENT_BASE_TEMPLATE = getattr(settings, 'APPOINTMENT_BASE_TEMPLATE', 'base_templates/base.html')
APPOINTMENT_ADMIN_BASE_TEMPLATE = getattr(settings, 'APPOINTMENT_ADMIN_BASE_TEMPLATE', 'base_templates/base.html')
APPOINTMENT_WEBSITE_NAME = getattr(settings, 'APPOINTMENT_WEBSITE_NAME', 'Website')
Expand All @@ -20,3 +22,49 @@
APPOINTMENT_FINISH_TIME = getattr(settings, 'APPOINTMENT_FINISH_TIME', (18, 30))
APP_DEFAULT_FROM_EMAIL = getattr(settings, 'DEFAULT_FROM_EMAIL', DEFAULT_FROM_EMAIL)
APP_TIME_ZONE = getattr(settings, 'TIME_ZONE', 'America/New_York')


def check_q_cluster():
"""
Checks if Django Q is properly installed and configured in the Django settings.
If 'django_q' is not in INSTALLED_APPS, it warns about both 'django_q' not being installed
and 'Q_CLUSTER' likely not being configured.
If 'django_q' is installed but 'Q_CLUSTER' is not configured, it only warns about 'Q_CLUSTER'.
Returns True if configurations are correct, otherwise False.
"""
missing_conf = []

# Check if Django Q is installed
if 'django_q' not in settings.INSTALLED_APPS:
missing_conf.append("Django Q is not in settings.INSTALLED_APPS. Please add it to the list.\n"
"Example: \n\n"
"INSTALLED_APPS = [\n"
" ...\n"
" 'appointment',\n"
" 'django_q',\n"
"]\n")

# Check if Q_CLUSTER configuration is defined
if not hasattr(settings, 'Q_CLUSTER'):
missing_conf.append("Q_CLUSTER is not defined in settings. Please define it.\n"
"Example: \n\n"
"Q_CLUSTER = {\n"
" 'name': 'DjangORM',\n"
" 'workers': 4,\n"
" 'timeout': 90,\n"
" 'retry': 120,\n"
" 'queue_limit': 50,\n"
" 'bulk': 10,\n"
" 'orm': 'default',\n"
"}\n"
"Then run 'python manage.py qcluster' to start the worker.\n"
"See https://django-q.readthedocs.io/en/latest/configure.html for more information.")

# Log warnings if any configurations are missing
if missing_conf:
for warning in missing_conf:
logger.warning(warning)
return False
print(f"Mising conf: {missing_conf}")
# Both 'django_q' is installed and 'Q_CLUSTER' is configured
return True
40 changes: 40 additions & 0 deletions appointment/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# tasks.py
# Path: appointment/tasks.py

"""
Author: Adams Pierre David
Since: 3.1.0
"""
from django.utils.translation import gettext as _

from appointment.email_sender import notify_admin, send_email
from appointment.models import Appointment
from appointment.logger_config import logger


def send_email_reminder(to_email, first_name, appointment_id):
"""
Send a reminder email to the client about the upcoming appointment.

:param to_email: The email address of the client.
:param first_name: The first name of the client.
:param appointment_id: The appointment ID.
:return: None
"""

# Fetch the appointment using appointment_id
logger.info(f"Sending reminder to {to_email} for appointment {appointment_id}")
appointment = Appointment.objects.get(id=appointment_id)
email_context = {
'first_name': first_name,
'appointment': appointment,
}
send_email(
recipient_list=[to_email], subject=_("Reminder: Upcoming Appointment"),
template_url='email_sender/reminder_email.html', context=email_context
)
# Notify the admin
notify_admin(
subject=_("Admin Reminder: Upcoming Appointment"),
template_url='email_sender/reminder_email.html', context=email_context
)
19 changes: 19 additions & 0 deletions appointment/templates/email_sender/reminder_email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% load i18n %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% translate 'Appointment Reminder' %}</title>
</head>
<body>
<h1>{% translate 'Appointment Reminder' %}</h1>
<p>{% translate 'Dear' %} {{ first_name }},</p>
<p>{% translate 'This is a reminder for your upcoming appointment' %}.</p>
<p>{% translate 'Service' %}: {{ appointment.get_service_name }}</p>
<p>{% translate 'Date' %}: {{ appointment.appointment_request.date }}</p>
<p>{% translate 'Time' %}: {{ appointment.appointment_request.start_time }}</p>
<p>{% translate 'Location' %}: {{ appointment.address }}</p>
<p>{% translate 'If you have any questions or need to reschedule, please contact us' %}.</p>
<p>{% translate 'Thank you for choosing us' %}!</p>
</body>
</html>
3 changes: 3 additions & 0 deletions appointment/tests/test_availability_slot.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# test_availability_slot.py
# Path: appointment/tests/test_availability_slot.py

from datetime import date, time, timedelta

from django.test import TestCase
Expand Down
3 changes: 3 additions & 0 deletions appointment/tests/test_services.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# test_services.py
# Path: appointment/tests/test_services.py

import datetime
import json
from _decimal import Decimal
Expand Down
45 changes: 45 additions & 0 deletions appointment/tests/test_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# test_settings.py
# Path: appointment/tests/test_settings.py

from unittest.mock import patch

from django.test import TestCase

from appointment.settings import check_q_cluster


class CheckQClusterTest(TestCase):
@patch('appointment.settings.settings')
@patch('appointment.settings.logger')
def test_check_q_cluster_with_django_q_missing(self, mock_logger, mock_settings):
# Simulate 'django_q' not being in INSTALLED_APPS
mock_settings.INSTALLED_APPS = []

# Call the function under test
result = check_q_cluster()

# Check the result
self.assertFalse(result)
# Verify logger was called with the expected warning about 'django_q' not being installed
mock_logger.warning.assert_called_with(
"Django Q is not in settings.INSTALLED_APPS. Please add it to the list.\n"
"Example: \n\n"
"INSTALLED_APPS = [\n"
" ...\n"
" 'appointment',\n"
" 'django_q',\n"
"]\n")

@patch('appointment.settings.settings')
@patch('appointment.settings.logger')
def test_check_q_cluster_with_all_configurations_present(self, mock_logger, mock_settings):
# Simulate both 'django_q' being in INSTALLED_APPS and 'Q_CLUSTER' configuration present
mock_settings.INSTALLED_APPS = ['django_q']
mock_settings.Q_CLUSTER = {'name': 'DjangORM'}

# Call the function under test
result = check_q_cluster()

# Check the result and ensure no warnings are logged
self.assertTrue(result)
mock_logger.warning.assert_not_called()
Loading