Skip to content

Commit 179a481

Browse files
committed
fixed error seen from testing
1 parent b574c92 commit 179a481

21 files changed

+1211
-306
lines changed

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ recursive-include appointment/static/js *.js
88
recursive-include appointment/email_sender *.py
99
recursive-include appointment *.py
1010
recursive-include docs *
11+
include logger_config.py
12+
exclude appointment/__pycache__

appointment/logger_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@
1010
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
1111
datefmt='%m/%d/%Y %I:%M:%S %p',
1212
level=logging.DEBUG,
13-
handlers=[logging.StreamHandler(sys.stdout)])
13+
handlers=[logging.StreamHandler(sys.stdout)])

appointment/migrations/0001_initial.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.2 on 2023-07-27 13:21
1+
# Generated by Django 4.2.3 on 2023-08-02 20:06
22

33
from django.conf import settings
44
import django.core.validators
@@ -126,12 +126,34 @@ class Migration(migrations.Migration):
126126
("name", models.CharField(max_length=100)),
127127
("description", models.TextField(blank=True, null=True)),
128128
("duration", models.DurationField()),
129-
("price", models.DecimalField(decimal_places=2, max_digits=6)),
129+
(
130+
"price",
131+
models.DecimalField(
132+
decimal_places=2,
133+
max_digits=6,
134+
validators=[django.core.validators.MinValueValidator(0)],
135+
),
136+
),
130137
(
131138
"down_payment",
132-
models.DecimalField(decimal_places=2, default=0, max_digits=6),
139+
models.DecimalField(
140+
decimal_places=2,
141+
default=0,
142+
max_digits=6,
143+
validators=[django.core.validators.MinValueValidator(0)],
144+
),
145+
),
146+
(
147+
"currency",
148+
models.CharField(
149+
default="USD",
150+
max_length=3,
151+
validators=[
152+
django.core.validators.MaxLengthValidator(3),
153+
django.core.validators.MinLengthValidator(3),
154+
],
155+
),
133156
),
134-
("currency", models.CharField(default="USD", max_length=3)),
135157
(
136158
"image",
137159
models.ImageField(blank=True, null=True, upload_to="services/"),

appointment/models.py

Lines changed: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import string
44

55
from django.core.exceptions import ValidationError
6-
from django.core.validators import RegexValidator
6+
from django.core.validators import RegexValidator, MinValueValidator, MinLengthValidator, MaxLengthValidator
77
from django.db import models
88
from django.utils.translation import gettext_lazy as _
99

@@ -22,12 +22,19 @@
2222

2323

2424
class Service(models.Model):
25+
"""
26+
Represents a service provided by the appointment system.
27+
28+
Author: Adams Pierre David
29+
Version: 1.1.0
30+
Since: 1.0.0
31+
"""
2532
name = models.CharField(max_length=100)
2633
description = models.TextField(blank=True, null=True)
2734
duration = models.DurationField()
28-
price = models.DecimalField(max_digits=6, decimal_places=2)
29-
down_payment = models.DecimalField(max_digits=6, decimal_places=2, default=0)
30-
currency = models.CharField(max_length=3, default='USD')
35+
price = models.DecimalField(max_digits=6, decimal_places=2, validators=[MinValueValidator(0)])
36+
down_payment = models.DecimalField(max_digits=6, decimal_places=2, default=0, validators=[MinValueValidator(0)])
37+
currency = models.CharField(max_length=3, default='USD', validators=[MaxLengthValidator(3), MinLengthValidator(3)])
3138
image = models.ImageField(upload_to='services/', blank=True, null=True)
3239

3340
# meta data
@@ -43,21 +50,33 @@ def get_name(self):
4350
def get_description(self):
4451
return self.description
4552

53+
def get_duration_parts(self):
54+
total_seconds = int(self.duration.total_seconds())
55+
days = total_seconds // 86400
56+
hours = (total_seconds % 86400) // 3600
57+
minutes = (total_seconds % 3600) // 60
58+
seconds = total_seconds % 60
59+
return days, hours, minutes, seconds
60+
4661
def get_duration(self):
47-
total_seconds = self.duration.total_seconds()
48-
if total_seconds >= 86400: # 1 day = 86400 seconds
49-
days = total_seconds // 86400
50-
return f"{days} day{'s' if days > 1 else ''}"
51-
elif total_seconds >= 3600: # 1 hour = 3600 seconds
52-
hours = total_seconds // 3600
53-
return f"{hours} hour{'s' if hours > 1 else ''}"
54-
elif total_seconds >= 60: # 1 minute = 60 seconds
55-
minutes = total_seconds // 60
56-
return f"{minutes} minute{'s' if minutes > 1 else ''}"
57-
else:
58-
return f"{total_seconds} second{'s' if total_seconds > 1 else ''}"
62+
days, hours, minutes, seconds = self.get_duration_parts()
63+
parts = []
64+
65+
if days:
66+
parts.append(f"{days} day{'s' if days > 1 else ''}")
67+
if hours:
68+
parts.append(f"{hours} hour{'s' if hours > 1 else ''}")
69+
if minutes:
70+
parts.append(f"{minutes} minute{'s' if minutes > 1 else ''}")
71+
if seconds:
72+
parts.append(f"{seconds} second{'s' if seconds > 1 else ''}")
73+
74+
return ' '.join(parts)
5975

6076
def get_price(self):
77+
return self.price
78+
79+
def get_price_text(self):
6180
if self.price == 0:
6281
return "Free"
6382
else:
@@ -89,6 +108,13 @@ def accepts_down_payment(self):
89108

90109

91110
class AppointmentRequest(models.Model):
111+
"""
112+
Represents an appointment request made by a client.
113+
114+
Author: Adams Pierre David
115+
Version: 1.1.0
116+
Since: 1.0.0
117+
"""
92118
date = models.DateField()
93119
start_time = models.TimeField()
94120
end_time = models.TimeField()
@@ -103,6 +129,11 @@ class AppointmentRequest(models.Model):
103129
def __str__(self):
104130
return f"{self.date} - {self.start_time} to {self.end_time} - {self.service.name}"
105131

132+
def clean(self):
133+
if self.start_time is not None and self.end_time is not None:
134+
if self.start_time >= self.end_time:
135+
raise ValueError("Start time must be before end time")
136+
106137
def save(self, *args, **kwargs):
107138
if self.id_request is None:
108139
self.id_request = f"{Utility.get_timestamp()}{self.service.id}{Utility.generate_random_id()}"
@@ -161,6 +192,13 @@ def get_updated_at(self):
161192

162193

163194
class Appointment(models.Model):
195+
"""
196+
Represents an appointment made by a client. It is created when the client confirms the appointment request.
197+
198+
Author: Adams Pierre David
199+
Version: 1.1.0
200+
Since: 1.0.0
201+
"""
164202
client = models.ForeignKey(APPOINTMENT_CLIENT_MODEL, on_delete=models.CASCADE)
165203
appointment_request = models.OneToOneField(AppointmentRequest, on_delete=models.CASCADE)
166204
phone = models.CharField(validators=[phone_regex], max_length=10, blank=True, null=True, default="",
@@ -262,6 +300,14 @@ def set_appointment_paid_status(self, status: bool):
262300

263301

264302
class Config(models.Model):
303+
"""
304+
Represents configuration settings for the appointment system. There can only be one Config object in the database.
305+
If you want to change the settings, you must edit the existing Config object.
306+
307+
Author: Adams Pierre David
308+
Version: 1.1.0
309+
Since: 1.1.0
310+
"""
265311
slot_duration = models.PositiveIntegerField(
266312
null=True,
267313
help_text=_("Minimum time for an appointment in minutes, recommended 30."),
@@ -274,7 +320,7 @@ class Config(models.Model):
274320
null=True,
275321
help_text=_("Time when we stop working."),
276322
)
277-
appointment_buffer_time = models.DurationField(
323+
appointment_buffer_time = models.FloatField(
278324
null=True,
279325
help_text=_("Time between now and the first available slot for the current day (doesn't affect tomorrow)."),
280326
)
@@ -287,6 +333,9 @@ class Config(models.Model):
287333
def clean(self):
288334
if Config.objects.exists() and not self.pk:
289335
raise ValidationError(_("You can only create one Config object"))
336+
if self.lead_time is not None and self.finish_time is not None:
337+
if self.lead_time >= self.finish_time:
338+
raise ValidationError(_("Lead time must be before finish time"))
290339

291340
def save(self, *args, **kwargs):
292341
self.clean()
@@ -298,6 +347,13 @@ def __str__(self):
298347

299348

300349
class PaymentInfo(models.Model):
350+
"""
351+
Represents payment information for an appointment.
352+
353+
Author: Adams Pierre David
354+
Version: 1.1.0
355+
Since: 1.0.0
356+
"""
301357
appointment = models.ForeignKey(Appointment, on_delete=models.CASCADE)
302358

303359
# meta data
@@ -333,6 +389,13 @@ def get_user_email(self):
333389

334390

335391
class EmailVerificationCode(models.Model):
392+
"""
393+
Represents an email verification code for a user when the email already exists in the database.
394+
395+
Author: Adams Pierre David
396+
Version: 1.1.0
397+
Since: 1.1.0
398+
"""
336399
user = models.ForeignKey(APPOINTMENT_CLIENT_MODEL, on_delete=models.CASCADE)
337400
code = models.CharField(max_length=6)
338401

@@ -348,4 +411,4 @@ def generate_code(cls, user):
348411
code = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
349412
verification_code = cls(user=user, code=code)
350413
verification_code.save()
351-
return code
414+
return code

appointment/operations/database_operations.py

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,36 @@
11
# appointment/operations/database_operations.py
22

3-
from django.apps import apps
43
from django.contrib.auth.hashers import make_password
54
from django.urls import reverse
65

6+
from appointment.logger_config import logger
77
from appointment.models import Appointment, PaymentInfo
8-
from logger_config import logger
9-
from appointment.settings import APPOINTMENT_CLIENT_MODEL, APPOINTMENT_PAYMENT_URL
8+
from appointment.settings import APPOINTMENT_PAYMENT_URL
109
from appointment.utils import Utility
1110

12-
CLIENT_MODEL = apps.get_model(APPOINTMENT_CLIENT_MODEL)
11+
CLIENT_MODEL = Utility.get_user_model()
1312

1413

1514
def get_appointments_and_slots(date_, service=None):
15+
"""
16+
Get appointments and available slots for a given date and service.
17+
18+
If a service is provided, the function retrieves appointments for that service on the given date.
19+
Otherwise, it retrieves all appointments for the given date.
20+
21+
Args:
22+
date_ (datetime.date): The date for which to retrieve appointments and available slots.
23+
service (Service, optional): The service for which to retrieve appointments.
24+
25+
Returns:
26+
tuple: A tuple containing two elements:
27+
- A queryset of appointments for the given date and service (if provided).
28+
- A list of available time slots on the given date, excluding booked appointments.
29+
30+
Author: Adams Pierre David
31+
Version: 1.1.0
32+
Since: 1.1.0
33+
"""
1634
if service:
1735
appointments = Appointment.objects.filter(appointment_request__service=service,
1836
appointment_request__date=date_)
@@ -22,11 +40,37 @@ def get_appointments_and_slots(date_, service=None):
2240
return appointments, available_slots
2341

2442

25-
def get_user_by_email(email):
43+
def get_user_by_email(email: str):
44+
"""
45+
Get a user by their email address.
46+
47+
Args:
48+
email (str): The email address of the user.
49+
50+
Returns:
51+
AppointmentClientModel: The user with the specified email address, if found; otherwise, None.
52+
53+
Author: Adams Pierre David
54+
Version: 1.1.0
55+
Since: 1.1.0
56+
"""
2657
return CLIENT_MODEL.objects.filter(email=email).first()
2758

2859

2960
def create_new_user(client_data):
61+
"""
62+
Create a new user and save it to the database.
63+
64+
Args:
65+
client_data (dict): The data of the new user, including name and email.
66+
67+
Returns:
68+
AppointmentClientModel: The newly created user.
69+
70+
Author: Adams Pierre David
71+
Version: 1.1.0
72+
Since: 1.1.0
73+
"""
3074
username = client_data['email'].split('@')[0]
3175
user = CLIENT_MODEL.objects.create_user(first_name=client_data['name'], email=client_data['email'],
3276
username=username)
@@ -37,6 +81,21 @@ def create_new_user(client_data):
3781

3882

3983
def create_and_save_appointment(ar, client_data, appointment_data):
84+
"""
85+
Create and save a new appointment based on the provided appointment request and client data.
86+
87+
Args:
88+
ar (AppointmentRequest): The appointment request associated with the new appointment.
89+
client_data (dict): The data of the client making the appointment.
90+
appointment_data (dict): Additional data for the appointment, including phone number, address, etc.
91+
92+
Returns:
93+
Appointment: The newly created appointment.
94+
95+
Author: Adams Pierre David
96+
Version: 1.1.0
97+
Since: 1.1.0
98+
"""
4099
user = get_user_by_email(client_data['email'])
41100
appointment = Appointment.objects.create(
42101
client=user, appointment_request=ar,
@@ -48,6 +107,19 @@ def create_and_save_appointment(ar, client_data, appointment_data):
48107

49108

50109
def create_payment_info_and_get_url(appointment):
110+
"""
111+
Create a new payment information entry for the appointment and return the payment URL.
112+
113+
Args:
114+
appointment (Appointment): The appointment for which to create payment information.
115+
116+
Returns:
117+
str: The payment URL for the appointment.
118+
119+
Author: Adams Pierre David
120+
Version: 1.1.0
121+
Since: 1.1.0
122+
"""
51123
payment_info = PaymentInfo(appointment=appointment)
52124
payment_info.save()
53125
payment_url = reverse(

0 commit comments

Comments
 (0)