From 58f913d3ed3b70900a31755bfe270cdaace2e3a0 Mon Sep 17 00:00:00 2001 From: HamodiGit Date: Sat, 9 Nov 2024 16:00:06 -0700 Subject: [PATCH 1/6] feat(Send_Email): Added feature to send emails --- ConversionToExcel.py | 44 ++++++++ README.md | 1 + WriteDB.py | 134 +++++++++++++++++++++++++ __pycache__/send_email.cpython-312.pyc | Bin 0 -> 2065 bytes interview_database.xlsx | Bin 0 -> 5586 bytes interview_database.yaml | 64 ++++++++++++ send_email.py | 41 ++++++++ 7 files changed, 284 insertions(+) create mode 100644 ConversionToExcel.py create mode 100644 README.md create mode 100644 WriteDB.py create mode 100644 __pycache__/send_email.cpython-312.pyc create mode 100644 interview_database.xlsx create mode 100644 interview_database.yaml create mode 100644 send_email.py diff --git a/ConversionToExcel.py b/ConversionToExcel.py new file mode 100644 index 0000000..2b6e9e9 --- /dev/null +++ b/ConversionToExcel.py @@ -0,0 +1,44 @@ +import pandas as pd +import yaml + +# Load the YAML file +with open("./interview_database.yaml", 'r') as file: + interview_data = yaml.safe_load(file) + +# Access the nested structure within 'Data' +flattened_data = [] +for date, date_info in interview_data['Data'].items(): + meeting_duration = date_info.get('Meeting Duration') + + for start_time_info in date_info.get('Meeting Start Times', []): + for start_time, meeting_info in start_time_info.items(): + interviewer_name = meeting_info['Interviewer'][0].get('Name') + interviewer_email = meeting_info['Interviewer'][1].get('Email') + interviewee_name = meeting_info['Interviewee'][0].get('Name') + interviewee_email = meeting_info['Interviewee'][1].get('Email') + category = meeting_info.get('Category') + status = meeting_info.get('Status') + slot = meeting_info.get('Slot') + + # Add flattened row to list + flattened_data.append({ + 'Date': date, + 'Meeting Duration': meeting_duration, + 'Start Time': start_time, + 'Interviewer Name': interviewer_name, + 'Interviewer Email': interviewer_email, + 'Interviewee Name': interviewee_name, + 'Interviewee Email': interviewee_email, + 'Category': category, + 'Status': status, + 'Slot': slot + }) + +# Convert to DataFrame if flattened data is not empty +if flattened_data: + df = pd.DataFrame(flattened_data) + # Write the DataFrame to an Excel file + df.to_excel("interview_database.xlsx", index=False) + print("Data has been written to interview_database.xlsx") +else: + print("No data found to write.") diff --git a/README.md b/README.md new file mode 100644 index 0000000..c40a406 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Interview-Backend \ No newline at end of file diff --git a/WriteDB.py b/WriteDB.py new file mode 100644 index 0000000..6cff7e0 --- /dev/null +++ b/WriteDB.py @@ -0,0 +1,134 @@ +import pandas as pd +import json +from openpyxl import load_workbook +from send_email import send_email + +# Define the path to the Excel file +file_path = "./interview_database.xlsx" + +def ReadDatabase(): + """ + Reads the Database to retrieve available interview slots. + + ``REQUIRES``: None + + ``PROMISES``: JSON (Available interview slots) + + ``Developed by``: Ahmad + + ``Contact``: ahmad.ahmad1@ucalgary.ca + """ + # Load the Excel file into a pandas DataFrame with specific columns + df = pd.read_excel(file_path, usecols=['Date', 'Start Time', 'Slot', 'Interviewee Name', 'Interviewee Email', 'Meeting Duration']) + + # Initialize the dictionary to store structured data for available slots + interview_data = {} + + # Process each row in the DataFrame to structure data by date and time + for _, row in df.iterrows(): + # Convert Date and Start Time to string format for easier comparison + date = str(row['Date']).split(" ")[0] # Format date to YYYY-MM-DD + start_time = str(row['Start Time']) + + # Calculate the slot capacity and current number of interviewees + slot_capacity = int(row['Slot']) if not pd.isna(row['Slot']) else 0 + interviewee_names = [name.strip() for name in str(row['Interviewee Name']).split(',') if name.strip()] + interviewee_count = len(interviewee_names) if interviewee_names != ["nan"] else 0 + + # Check if there are available slots for more interviewees + if interviewee_count < slot_capacity: + # Organize data by date and time, keeping track of available slots and meeting duration + if date not in interview_data: + interview_data[date] = {} + interview_data[date][start_time] = { + 'Meeting Duration': row['Meeting Duration'], + 'Available Slots': slot_capacity - interviewee_count + } + + return interview_data + +def AppendAppointment(date, start_time, interviewee_name, interviewee_email): + """ + Appends a new appointment with the interviewee's name and email if the slot is available. + + ``REQUIRES``: date (str), start_time (str), interviewee_name (str), interviewee_email (str) + + ``PROMISES``: Updates the Excel file with the new interviewee's name and email if there is an available slot. Returns Bool. + + ``Developed by``: Ahmad + + ``Contact``: ahmad.ahmad1@ucalgary.ca + """ + available_slots = ReadDatabase() + + # Check if the requested slot is available in the `available_slots` structure + if date in available_slots and start_time in available_slots[date]: + # Load workbook and select "Sheet1" for updating appointments + workbook = load_workbook(file_path) + sheet = workbook["Sheet1"] + df = pd.read_excel(file_path) + + # Find and update the row that matches the provided date and start time + for index, row in df.iterrows(): + row_date = str(row['Date']).split(" ")[0] + row_start_time = str(row['Start Time']) + + if row_date == date and row_start_time == start_time: + # Current entries for names and emails, and append new data with comma and space + current_names = str(row['Interviewee Name']).strip() + current_emails = str(row['Interviewee Email']).strip() + + updated_names = f"{current_names}, {interviewee_name}" if current_names != "nan" else interviewee_name + updated_emails = f"{current_emails}, {interviewee_email}" if current_emails != "nan" else interviewee_email + + # Update the cells with new names and emails + name_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Interviewee Name') + 1) + email_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Interviewee Email') + 1) + name_cell.value = updated_names + email_cell.value = updated_emails + + workbook.save(file_path) + send_email(interviewee_email, interviewee_name, date, start_time) + return True + + # If no slots available, return that the slot is unavailable + return False + +def run_tests(): + """ + Executes test cases to verify appointment scheduling and slot availability. + + ``REQUIRES``: None + + ``PROMISES``: Prints test outcomes to validate successful booking or slot unavailability. + """ + print("Available Slots:") + available_slots = ReadDatabase() + print(json.dumps(available_slots, indent=4)) + + # Test Case 1: Append to an available slot on 2024-09-16 at 10:30:00 + date_1 = "2024-09-16" + start_time_1 = "10:30:00" + interviewee_name_1 = "Alice Johnson" + interviewee_email_1 = "alice.johnson@example.com" + print(f"\nTest Case 1: Trying to book {date_1} at {start_time_1} for {interviewee_name_1} ({interviewee_email_1})") + AppendAppointment(date_1, start_time_1, interviewee_name_1, interviewee_email_1) + + # Test Case 2: Append to an available slot on 2024-09-17 at 13:30:00 + date_2 = "2024-09-17" + start_time_2 = "13:30:00" + interviewee_name_2 = "Bob Smith" + interviewee_email_2 = "bob.smith@example.com" + print(f"\nTest Case 2: Trying to book {date_2} at {start_time_2} for {interviewee_name_2} ({interviewee_email_2})") + AppendAppointment(date_2, start_time_2, interviewee_name_2, interviewee_email_2) + + # Test Case 3: Attempting to book at 10:30:00 on 2024-09-16 for a different interviewee + date_3 = "2024-09-16" + start_time_3 = "10:30:00" + interviewee_name_3 = "Charlie Brown" + interviewee_email_3 = "charlie.brown@example.com" + print(f"\nTest Case 3: Trying to book {date_3} at {start_time_3} for {interviewee_name_3} ({interviewee_email_3})") + AppendAppointment(date_3, start_time_3, interviewee_name_3, interviewee_email_3) + +# Run tests +run_tests() diff --git a/__pycache__/send_email.cpython-312.pyc b/__pycache__/send_email.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cab579e27d0da1d8f4af79c05bf197ee0b4b311 GIT binary patch literal 2065 zcmZ8iU2NM#96u*^;y6juF4^eVG~Gt`5t?Qjj0vi3Vrx5Sw~nnXLaQpZj(tttI(BAX z+9payD}mU=B;aY2rb&GS^<|HJLE6*@9(XZY5tZ3^0SWOIC=aN-@INPR%1-j#@BSb6 z|Ge|}@wkLw-2VM!{@V~jzp{-#0!^^h3*bIdk;>_4oUH+!D+I;^9K%69Sm4Kbz=Fzu z!i@_7M1<9*Du>EkI`jwIT}yLre0XqpV7R1HtteX*2qGhlkwvQTO%1k$;S0jI^$X`2 z=4!mk)dV$A3sImnhHs)4t_sz#8axN&Dtd@izREvDkAgrG7)=cU3VTSba=?#N129X~ zAk5K6;hI}^AX6PDxVKP6dgXTwfYuTn-O}aBwKtR`EnehdwrUk@bcNt z@~L(_5?ZmBldmDV&zI{$R%cDxrMIUlK?MRW`I|^h)}-4~IoVhjxCOKryonaLbV@!1 zDa8_oF-i>8#*uO2t_fSLSuqBl{ULTi`kvXwJUHZD+dO=!rLrqtz7D5X1^kMJ+jSZEBh{kAIzqSK~!yY zSTDYoj#=zxxFMNRS(&leGvtPuAzjP4{OIu5byxHXPj%ZBbTh9RK!78dSvMlvlh8m= zDVM)h(kQqJ8|p>|78O!tjhl|T9YFD$w$XKN`)(5JH<4n zSZ+rHL%$Sd+g>m&)eRPGa3N=^Wj6#O&=M|3Z1&`^i`5+4J$=0sm!OSszopHSiJdg} zT#}VJh-rca)-aeYmcg5yt#ufVVlSedgzLJ5>g_0XYU%1`N6*sGW@nGX@BKT3qMe_g zT)6U|r~NDt`Q^dQz3%{sEMKdKP$K1|x}4!M8yn)AO~wPsIeK<2=5!4_vA?T5 zt*w8&A%4Vo(Yw)m@#kXPY43J2=bi2gYvjq*Cl{O>BTn1skK))r&!efo5GQ0jnPV%r zRurf0^`FGp@^_+xsCoPvaNQ_eIfqG}4y|uBY1ijtAF|3xLyn^rT%(-0IDEv9Smi5Z>4cY=- zWMP=L#d8Id-O&qH8n*1OWgmbEoaMNmQQHfYeSx~R_#<5A&-XdRML&&MCxHJS=@>f} literal 0 HcmV?d00001 diff --git a/interview_database.xlsx b/interview_database.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..97233833ab09eebf643dd0669e9b1acd5f50fbfd GIT binary patch literal 5586 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 e^iLcp!+&ioJxyYgUw Date: Sat, 23 Nov 2024 12:01:28 -0700 Subject: [PATCH 2/6] Docker testing --- ConversionToExcel.py | 71 ++++++++++++++------------------- composetest/Dockerfile | 8 ++++ composetest/app.py | 33 +++++++++++++++ composetest/docker-compose.yaml | 8 ++++ composetest/requirements.txt | 0 5 files changed, 79 insertions(+), 41 deletions(-) create mode 100644 composetest/Dockerfile create mode 100644 composetest/app.py create mode 100644 composetest/docker-compose.yaml create mode 100644 composetest/requirements.txt diff --git a/ConversionToExcel.py b/ConversionToExcel.py index 2b6e9e9..69e5c83 100644 --- a/ConversionToExcel.py +++ b/ConversionToExcel.py @@ -1,44 +1,33 @@ -import pandas as pd -import yaml +import time +from http.server import BaseHTTPRequestHandler, HTTPServer -# Load the YAML file -with open("./interview_database.yaml", 'r') as file: - interview_data = yaml.safe_load(file) +hit_count = 0 # In-memory counter -# Access the nested structure within 'Data' -flattened_data = [] -for date, date_info in interview_data['Data'].items(): - meeting_duration = date_info.get('Meeting Duration') - - for start_time_info in date_info.get('Meeting Start Times', []): - for start_time, meeting_info in start_time_info.items(): - interviewer_name = meeting_info['Interviewer'][0].get('Name') - interviewer_email = meeting_info['Interviewer'][1].get('Email') - interviewee_name = meeting_info['Interviewee'][0].get('Name') - interviewee_email = meeting_info['Interviewee'][1].get('Email') - category = meeting_info.get('Category') - status = meeting_info.get('Status') - slot = meeting_info.get('Slot') - - # Add flattened row to list - flattened_data.append({ - 'Date': date, - 'Meeting Duration': meeting_duration, - 'Start Time': start_time, - 'Interviewer Name': interviewer_name, - 'Interviewer Email': interviewer_email, - 'Interviewee Name': interviewee_name, - 'Interviewee Email': interviewee_email, - 'Category': category, - 'Status': status, - 'Slot': slot - }) +def get_hit_count(): + global hit_count + hit_count += 1 + return hit_count -# Convert to DataFrame if flattened data is not empty -if flattened_data: - df = pd.DataFrame(flattened_data) - # Write the DataFrame to an Excel file - df.to_excel("interview_database.xlsx", index=False) - print("Data has been written to interview_database.xlsx") -else: - print("No data found to write.") +class RequestHandler(BaseHTTPRequestHandler): + def do_GET(self): + if self.path == '/': + count = get_hit_count() + response = f'Hello World! I have been seen {count} times.\n' + self.send_response(200) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write(response.encode('utf-8')) + else: + self.send_response(404) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write(b'Not Found\n') + +def run(server_class=HTTPServer, handler_class=RequestHandler, port=8000): + server_address = ('', port) + httpd = server_class(server_address, handler_class) + print(f'Starting server on port {port}...') + httpd.serve_forever() + +if __name__ == '__main__': + run() diff --git a/composetest/Dockerfile b/composetest/Dockerfile new file mode 100644 index 0000000..8505363 --- /dev/null +++ b/composetest/Dockerfile @@ -0,0 +1,8 @@ +# syntax=docker/dockerfile:1 +FROM python:3.10-slim +WORKDIR /code +COPY requirements.txt requirements.txt +RUN pip install -r requirements.txt +EXPOSE 5000 +COPY . . +CMD ["python", "app.py"] \ No newline at end of file diff --git a/composetest/app.py b/composetest/app.py new file mode 100644 index 0000000..69e5c83 --- /dev/null +++ b/composetest/app.py @@ -0,0 +1,33 @@ +import time +from http.server import BaseHTTPRequestHandler, HTTPServer + +hit_count = 0 # In-memory counter + +def get_hit_count(): + global hit_count + hit_count += 1 + return hit_count + +class RequestHandler(BaseHTTPRequestHandler): + def do_GET(self): + if self.path == '/': + count = get_hit_count() + response = f'Hello World! I have been seen {count} times.\n' + self.send_response(200) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write(response.encode('utf-8')) + else: + self.send_response(404) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write(b'Not Found\n') + +def run(server_class=HTTPServer, handler_class=RequestHandler, port=8000): + server_address = ('', port) + httpd = server_class(server_address, handler_class) + print(f'Starting server on port {port}...') + httpd.serve_forever() + +if __name__ == '__main__': + run() diff --git a/composetest/docker-compose.yaml b/composetest/docker-compose.yaml new file mode 100644 index 0000000..c24417b --- /dev/null +++ b/composetest/docker-compose.yaml @@ -0,0 +1,8 @@ +services: + web: + container_name: test + build: . + ports: + - "8000:8000" + volumes: + - ./:/code diff --git a/composetest/requirements.txt b/composetest/requirements.txt new file mode 100644 index 0000000..e69de29 From 52af294c815912311286237182366045567edcd1 Mon Sep 17 00:00:00 2001 From: HamodiGit Date: Sat, 23 Nov 2024 14:42:30 -0700 Subject: [PATCH 3/6] feat(SendMail): We added calender buttons and html text, and changed it so that it sends in html --- send_email.py | 52 +++++++++++++++++++++++++++++++++++++++++++++------ web1.html | 25 +++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 web1.html diff --git a/send_email.py b/send_email.py index 1c4b0df..3a82b6d 100644 --- a/send_email.py +++ b/send_email.py @@ -1,8 +1,10 @@ import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText +from datetime import datetime, timedelta +import pytz # For timezone handling -def send_email(interviewee_email="darkicewolf50@gmail.com", interviewee_name="brock", date="y", start_time="x"): +def send_email(interviewee_email="darkicewolf50@gmail.com", interviewee_name="brock", date="10-1-2024", start_time="10:00 AM", location ="ENC25"): """ Sends an email notification to the interviewee and a static Gmail account. @@ -14,6 +16,19 @@ def send_email(interviewee_email="darkicewolf50@gmail.com", interviewee_name="br gmail_user = "uofcbaja.noreply@gmail.com" gmail_apppassword = "pver lpnt upjd zvld" + # Define Mountain Standard Time + mst = pytz.timezone("America/Edmonton") # MST with Daylight Savings considered + + # Parse the input date and time, localize to MST + start_datetime = mst.localize(datetime.strptime(f"{date} {start_time}", "%d-%m-%Y %I:%M %p")) + end_datetime = start_datetime + timedelta(minutes=30) + + # Format date and time for the calendar URLs + start_time_google = start_datetime.strftime("%Y%m%dT%H%M%S") # Google Calendar (Local time without Z) + end_time_google = end_datetime.strftime("%Y%m%dT%H%M%S") # Google Calendar (Local time without Z) + outlook_start = start_datetime.isoformat() # Outlook Calendar (ISO local time) + outlook_end = end_datetime.isoformat() # Outlook Calendar (ISO local time) + # Create message object msg = MIMEMultipart() msg['From'] = gmail_user @@ -21,11 +36,36 @@ def send_email(interviewee_email="darkicewolf50@gmail.com", interviewee_name="br msg['Subject'] = "Interview Appointment Confirmation" # Message body - body = (f"Dear {interviewee_name},\n\n" - f"Your interview has been scheduled on {date} at {start_time}.\n" - "Please ensure to be available at the designated time.\n\n" - "Best regards,\nYour Interview Team") - msg.attach(MIMEText(body, 'plain')) + body = f''' + + + Interview Invitation + + +

Dear {interviewee_name},

+

Your interview has been scheduled on {date} at {start_time} MST.

+

Your interview location is at {location} or will be emailed to you.

+

Please ensure to be available at the designated time.

+ + + + + + +

Best regards,

+

UCalgary Baja Interview Team

+ UCalgary Baja Team + + + ''' + + msg.attach(MIMEText(body, 'html')) try: # Setup the server and send the email diff --git a/web1.html b/web1.html new file mode 100644 index 0000000..31da8f9 --- /dev/null +++ b/web1.html @@ -0,0 +1,25 @@ + + + Interview Invitation + + +

Dear {interviewee_name},

+

Your interview has been scheduled on {date} at {start_time}

+

Please ensure to be available at the designated time.

+ + + + + + +

Best regards,

+

UCalgary Baja Interview Team

+ UCalgary Baja Team + + From b150eee58bbddeedbcc5a011fdcebbf4872e76a3 Mon Sep 17 00:00:00 2001 From: HamodiGit Date: Sat, 26 Oct 2024 16:07:00 -0600 Subject: [PATCH 4/6] init(WriteDB): Created file to write data to database --- ConversionToExcel.py | 71 +++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/ConversionToExcel.py b/ConversionToExcel.py index 69e5c83..2b6e9e9 100644 --- a/ConversionToExcel.py +++ b/ConversionToExcel.py @@ -1,33 +1,44 @@ -import time -from http.server import BaseHTTPRequestHandler, HTTPServer +import pandas as pd +import yaml -hit_count = 0 # In-memory counter +# Load the YAML file +with open("./interview_database.yaml", 'r') as file: + interview_data = yaml.safe_load(file) -def get_hit_count(): - global hit_count - hit_count += 1 - return hit_count +# Access the nested structure within 'Data' +flattened_data = [] +for date, date_info in interview_data['Data'].items(): + meeting_duration = date_info.get('Meeting Duration') + + for start_time_info in date_info.get('Meeting Start Times', []): + for start_time, meeting_info in start_time_info.items(): + interviewer_name = meeting_info['Interviewer'][0].get('Name') + interviewer_email = meeting_info['Interviewer'][1].get('Email') + interviewee_name = meeting_info['Interviewee'][0].get('Name') + interviewee_email = meeting_info['Interviewee'][1].get('Email') + category = meeting_info.get('Category') + status = meeting_info.get('Status') + slot = meeting_info.get('Slot') + + # Add flattened row to list + flattened_data.append({ + 'Date': date, + 'Meeting Duration': meeting_duration, + 'Start Time': start_time, + 'Interviewer Name': interviewer_name, + 'Interviewer Email': interviewer_email, + 'Interviewee Name': interviewee_name, + 'Interviewee Email': interviewee_email, + 'Category': category, + 'Status': status, + 'Slot': slot + }) -class RequestHandler(BaseHTTPRequestHandler): - def do_GET(self): - if self.path == '/': - count = get_hit_count() - response = f'Hello World! I have been seen {count} times.\n' - self.send_response(200) - self.send_header('Content-type', 'text/plain') - self.end_headers() - self.wfile.write(response.encode('utf-8')) - else: - self.send_response(404) - self.send_header('Content-type', 'text/plain') - self.end_headers() - self.wfile.write(b'Not Found\n') - -def run(server_class=HTTPServer, handler_class=RequestHandler, port=8000): - server_address = ('', port) - httpd = server_class(server_address, handler_class) - print(f'Starting server on port {port}...') - httpd.serve_forever() - -if __name__ == '__main__': - run() +# Convert to DataFrame if flattened data is not empty +if flattened_data: + df = pd.DataFrame(flattened_data) + # Write the DataFrame to an Excel file + df.to_excel("interview_database.xlsx", index=False) + print("Data has been written to interview_database.xlsx") +else: + print("No data found to write.") From bcdaa37d97b6c3fd13f0d299d9c0210a269f4521 Mon Sep 17 00:00:00 2001 From: HamodiGit Date: Sat, 23 Nov 2024 15:37:20 -0700 Subject: [PATCH 5/6] feat(WriteDB): Added file lock, prevented race condition --- WriteDB.py | 60 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/WriteDB.py b/WriteDB.py index 6cff7e0..9073367 100644 --- a/WriteDB.py +++ b/WriteDB.py @@ -1,10 +1,12 @@ import pandas as pd import json from openpyxl import load_workbook -from send_email import send_email +#from send_email import send_email +from filelock import FileLock -# Define the path to the Excel file +# Define the path to the Excel file and the lock file file_path = "./interview_database.xlsx" +lock_path = "./interview_database.xlsx.lock" # Lock file for synchronization def ReadDatabase(): """ @@ -18,8 +20,10 @@ def ReadDatabase(): ``Contact``: ahmad.ahmad1@ucalgary.ca """ - # Load the Excel file into a pandas DataFrame with specific columns - df = pd.read_excel(file_path, usecols=['Date', 'Start Time', 'Slot', 'Interviewee Name', 'Interviewee Email', 'Meeting Duration']) + # Use a file-based lock for thread-safe and process-safe access + with FileLock(lock_path): + # Load the Excel file into a pandas DataFrame with specific columns + df = pd.read_excel(file_path, usecols=['Date', 'Start Time', 'Slot', 'Interviewee Name', 'Interviewee Email', 'Meeting Duration']) # Initialize the dictionary to store structured data for available slots interview_data = {} @@ -63,37 +67,39 @@ def AppendAppointment(date, start_time, interviewee_name, interviewee_email): # Check if the requested slot is available in the `available_slots` structure if date in available_slots and start_time in available_slots[date]: - # Load workbook and select "Sheet1" for updating appointments - workbook = load_workbook(file_path) - sheet = workbook["Sheet1"] - df = pd.read_excel(file_path) + with FileLock(lock_path): # Ensure process-safe access to the file + # Load workbook and select "Sheet1" for updating appointments + workbook = load_workbook(file_path) + sheet = workbook["Sheet1"] + df = pd.read_excel(file_path) - # Find and update the row that matches the provided date and start time - for index, row in df.iterrows(): - row_date = str(row['Date']).split(" ")[0] - row_start_time = str(row['Start Time']) + # Find and update the row that matches the provided date and start time + for index, row in df.iterrows(): + row_date = str(row['Date']).split(" ")[0] + row_start_time = str(row['Start Time']) - if row_date == date and row_start_time == start_time: - # Current entries for names and emails, and append new data with comma and space - current_names = str(row['Interviewee Name']).strip() - current_emails = str(row['Interviewee Email']).strip() - - updated_names = f"{current_names}, {interviewee_name}" if current_names != "nan" else interviewee_name - updated_emails = f"{current_emails}, {interviewee_email}" if current_emails != "nan" else interviewee_email + if row_date == date and row_start_time == start_time: + # Current entries for names and emails, and append new data with comma and space + current_names = str(row['Interviewee Name']).strip() + current_emails = str(row['Interviewee Email']).strip() + + updated_names = f"{current_names}, {interviewee_name}" if current_names != "nan" else interviewee_name + updated_emails = f"{current_emails}, {interviewee_email}" if current_emails != "nan" else interviewee_email - # Update the cells with new names and emails - name_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Interviewee Name') + 1) - email_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Interviewee Email') + 1) - name_cell.value = updated_names - email_cell.value = updated_emails + # Update the cells with new names and emails + name_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Interviewee Name') + 1) + email_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Interviewee Email') + 1) + name_cell.value = updated_names + email_cell.value = updated_emails - workbook.save(file_path) - send_email(interviewee_email, interviewee_name, date, start_time) - return True + workbook.save(file_path) + #send_email(interviewee_email, interviewee_name, date, start_time) + return True # If no slots available, return that the slot is unavailable return False + def run_tests(): """ Executes test cases to verify appointment scheduling and slot availability. From 6ff802f6046f2e8a5b80e55e530179593ea29e7e Mon Sep 17 00:00:00 2001 From: HamodiGit Date: Sat, 30 Nov 2024 13:37:30 -0700 Subject: [PATCH 6/6] Fixed the code for date which caused issues, create a new file to ensure WriteDB and SendEmail work from any script --- TestBookAppointment.py | 11 +++++++++++ WriteDB.py | 9 +++++---- __pycache__/WriteDB.cpython-312.pyc | Bin 0 -> 5852 bytes __pycache__/send_email.cpython-312.pyc | Bin 2065 -> 4134 bytes interview_database.xlsx | Bin 5586 -> 5561 bytes send_email.py | 5 +++-- 6 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 TestBookAppointment.py create mode 100644 __pycache__/WriteDB.cpython-312.pyc diff --git a/TestBookAppointment.py b/TestBookAppointment.py new file mode 100644 index 0000000..f076d51 --- /dev/null +++ b/TestBookAppointment.py @@ -0,0 +1,11 @@ +from WriteDB import AppendAppointment + +def TestBookAppointment(): + date_1 = "2024-09-16" + start_time_1 = "10:30:00" + interviewee_name_1 = "Alice Johnson" + interviewee_email_1 = "ahmadmuhammadofficial@gmail.com" + print(f"\nTest Case 1: Trying to book {date_1} at {start_time_1} for {interviewee_name_1} ({interviewee_email_1})") + AppendAppointment(date_1, start_time_1, interviewee_name_1, interviewee_email_1) + +TestBookAppointment() \ No newline at end of file diff --git a/WriteDB.py b/WriteDB.py index 9073367..cf9ad46 100644 --- a/WriteDB.py +++ b/WriteDB.py @@ -1,7 +1,7 @@ import pandas as pd import json from openpyxl import load_workbook -#from send_email import send_email +from send_email import send_email from filelock import FileLock # Define the path to the Excel file and the lock file @@ -93,7 +93,7 @@ def AppendAppointment(date, start_time, interviewee_name, interviewee_email): email_cell.value = updated_emails workbook.save(file_path) - #send_email(interviewee_email, interviewee_name, date, start_time) + send_email(interviewee_email, interviewee_name, date, start_time) return True # If no slots available, return that the slot is unavailable @@ -116,7 +116,7 @@ def run_tests(): date_1 = "2024-09-16" start_time_1 = "10:30:00" interviewee_name_1 = "Alice Johnson" - interviewee_email_1 = "alice.johnson@example.com" + interviewee_email_1 = "ahmadmuhammadofficial@gmail.com" print(f"\nTest Case 1: Trying to book {date_1} at {start_time_1} for {interviewee_name_1} ({interviewee_email_1})") AppendAppointment(date_1, start_time_1, interviewee_name_1, interviewee_email_1) @@ -137,4 +137,5 @@ def run_tests(): AppendAppointment(date_3, start_time_3, interviewee_name_3, interviewee_email_3) # Run tests -run_tests() +if __name__ == "__main__": + run_tests() diff --git a/__pycache__/WriteDB.cpython-312.pyc b/__pycache__/WriteDB.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..401e4ac5dc3fe01ea4c805117385b742890ecc71 GIT binary patch literal 5852 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 literal 0 HcmV?d00001 diff --git a/__pycache__/send_email.cpython-312.pyc b/__pycache__/send_email.cpython-312.pyc index 4cab579e27d0da1d8f4af79c05bf197ee0b4b311..1b561ac4a65d1d012b1201b20a480d9c42bded85 100644 GIT binary patch literal 4134 zcmb^!T}&J4ao1~`H5iNyA&}&98z-g^gAEBm4Z`FIAqh!HxImPKqjlEa53^=}e7m3d zS=m=dYSlzN>3UCzbcz&J_mC=m;E~5vr)wYjVoEC9y*ss1`*Lqq(}$M6biQ3LCY;)9 zrCRbg^UcgR-^_e7^L_i<=H^BUeyjL;=I1RG^=C41ey$qw`UikKrUXi$C2F2Pmqg31 zc^6G^Mq*_5yxYRvl1KK;dt~pt*P7E(!+e9#Ab5U6&$GxUc$a%9N@HCVVqesh)6_b> zPWQ53lljeF+H9Vlnx34_OIXbD8U_MO2yagJYgc95Z z<`e2EBY0lacG%PDEBJ7eBQ1D;+2C*tAQl@7zHGC@twVuzq>4?2#&41EgN{Ibt%Esn zE;fUP|HN;>!EdYy6ugCi;2Q--fdc;;3m)Ph(3%KM@B`LtVJ!t3_yH>ltt1LUu;7L< zWYOCSO@Ox-{4gG}=p7dQu!VOPnt|R`Xo2y_)2?D`A&_lzY^g(h$iWM(!qI~frYRqV z4?7asP6w+);h6B2v#J;@1hYr$JO^{4MN`0k%c0gG#G}E{VadE*=Q$Y2Pz6uM1zwzR zXm#l9@PcG1{46aeZG8Xe$k7yzICSB-bqa5jQxLeP9w$4XhQcQ)-0jGnqO{IpXy3XM zg%F&Zt1f?!62isM2ch+_4Oi(^YK^%^tR$$15C?c~RmN6^n~v zn#ZE5a9HK=65>P!BW*=Qs|bOh!10`pK_Jatv6k@Zw3=6N%u;6!_wV1HeD}`O?aA5u z_eb_GPuiRG=veFRdxN3yGU94<(f}MBcG7T?GyRj{h;(wq^D-0P3X{SkqSTpfu_k3?p_J`QIH zJ+DC)l{`ke(HyxKkt4!fk276!su7-Yg3pcbGj&2$7DY|Aj>&A8 z&8M;`jg6ueozW#MOPs_jnM4>V{dZ==V-}s`MwbvTI1`v+5ld*SzQdHVB9fC;mC?A( za~8(;uN+OO!umcnxv@)#*EnOiZ#15JgMLrVYxUFMmUx{@Ap{4IUP3}%LIMY;VYoRS zbH)%iJv$dWsDV46LQ-qFDMHf1sy}h6#;uBy1d8nx2BnY!>uNp*9v-k~Mnb#}a*CeU z5D|k#{0f{ZpOOH>R=NvF7c&avASi;oJ75xD3kj*6B@Hbm!b=$E^pSWx%}Y>AycWx- zYDPjNQTAwY4OmFKz^6%c6LXW(GdITPCc81*d5Jp{yp-X!_2@;&w&>n*L{~+;6g3*U zNj>O^T{>M4V8BAiS?hAh`P^>c%;rScf^ zm&5BhlnC3CFqf8iT~CC^1p%vIad-?c62mK&yBv@#_ebOQg1W-dIKOZ5?*pjju>>U; zlf;z+(Hn>h2ue^?mg5lBoT{S>x}Edi61>4~lB@qyw0zxG&k=9}LJuAR!Vg2ctcQx@ z{{KO|W&8eLM=>{c5$TxI5G0PEf1~i6eBek*aeK+ak<56rC`38K=~}v;#yK&q=jB*# zNyVxjKQk~84|BW(8S|!2qAAS9or8r_-)jn^angZoj_D@2(W1*Z7u#!~;&pgD8Am6r z`$H$ax89Pab&^zu$GU2E>6p=R88Y6kY7iCdDj(r)_O_eu+&VT)mXxWXD#&C>(o3R& zH1fDJS-59%gsFL*=^~k$K2cW}Rnk8-az~q99>XJZNpq7)1F=;SQ>J@%dTxd&fh!10 zy6KVBjHmzstRR+~K3-447I0^n?)UQ|Ha$6*VAD6bmPW9#s`R#*L7+I-r+xpKt@}yj z<}=--#?8k1ZD+eeRG5DI`l_vMw1Sw->qs+$HV$`K4oY)X)dZ7~b!@iSdiEaDelR$> zHw)HxN!qQ-3=jbl8HdnZt3_}RoHv`CDaes6z+ZCQrWdS$o6w|@PF{1QCm#6i_NPY{ ztR%doL0K>CrDFd@KD`X;7DzO0-(tCS2*KV|I|nMsgIp)sQlhG@RQo{bMkUx^x>o7z zFS`%@%}X@}KI(teU+z4;-54!RRJ~M3*W-nc7e4LT37_2xpWVP4i<{${+0BLW(6#c7 zv(k3r)2WTrt5fH<+Rm45z`CyQ>~zMrI^!FG?ap^Vccmk|(-Gb3h(5oy z-7#9aRcR0JwD)bb_ieYwO4F6l@tsi5R;Xt?bh>n%*wOjZcxj>%I#!x^=zVa#(srsG ziafjh-2d!@a_cY<-4CWJht31ydvLSr^|l79ekv3$w}z_}eR8lecy7bHA#9Fp_QCuL z9j!#qK0jThm~$!(#^vaDTvduv>2U(aT~`U1g3mJmS6%NB@SbadfbY8`0v22{0kVr{ z0Hl~K0X(w|z`M*W)3CWj7wB@f0Q4fQ5MkA|N`T^8BVf%nPS#vxZV@of%n&fc+$G?9 zOqzhZi~yifU_8~6NBwN+Qgwv#us^x>;M#|4Pux!yw*!$^?(VAF=jr=Pu=DY;kB^m) z$2OSl;Lt;Fg>CsL@F?(#?IM`}k^g7&6yGaapqiaqei|^ z%y#S98Iz&9VzTqiYqy=iJ{EZiSpCOpT~@==$k&=x1;i%OCz8ouP_KbVX+tpVvZJy} zo>61k7);5xtsaK~yrk(bsFts&*jLo4KTmVh;F4jsO4v delta 822 zcmZ8fO-vI(6n?Wm-F9}j1-2NJ1Q+=!)q-HapjHztiP(6Pm_R(xu$zX4AG0l%npr3b z#>9AO<^T!dYGOEO;)RO`!qH0;J&+o_dadEc$+s<=8ecNsy!qa@Z@#y?AIwTy_?xa% zz}WbGGy64A33rjZmDu2A*^;)&oGgGXA9!U90wod;RNnP%E=WzLfeNCC>+BZ+*a3^U z5-!;i+&}@DX81i^g~?2@#3a%T_P~;uvzbME)!Ge}{AT&=07X}D~_ziNk!_uv=Uvon&lv8@U62W5PelNmZV*GmiOsC_yP_ zfrL0WLb;|)T&MJroiCVf{Uht$PrOR-^q@W1tRXaQtL^^7HM(=wa^gMID4R8LNw;K31Np2SEd>&ukT8` z+3%6TA5x5$!#mwa`osGv6^_+VDj^<6!>O2f9FsAROFS7dX^oU>WIY~mG|nOu)1Il1 zaE1tuiEtiFF0Wc^OZEdNjz@F~`EG=|NC^1};Zqnnh5j??8cF=YKZK}nG-n9we*uIy BqUrzu diff --git a/interview_database.xlsx b/interview_database.xlsx index 97233833ab09eebf643dd0669e9b1acd5f50fbfd..e89113f5c38b9a9b4902fede8d683f4b19794584 100644 GIT binary patch delta 2738 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

#;A}ExnZZMpjVA z>hE0;<_&0UJ06F%^7pFW5B6wJqQ>d2QNBiO`Zp7^SDE~FTJME%Stsk0%oWak=yB0* zl!dE%294}4Zn%Wkmfiq)47NLuxo;D=_w>~2sA6lzhi<{{y$$@+6nk~2q!6O7A}KM- ze60@C9y%AT8Id2^Ep*VbH;|v*JA5_8aK~s;pT!m z!0aKFvE*y}h=%dmo)u7$fz7jRPbtDr{Qd67r1}0|dkPiLbsZ@wQ&5z3@QnRL2|S*qOT*46KCdoQP~b`5Tb)T* zY05J4mMP!|`?!9~etW(r_niTmvUyW09{5x7wMUjRnzn>4xg~l*03fYwAN(C6-0dPS z*^qifWbrSLwf>F~t8%%{c4kg%Cfle4z}~Fq*pmR~FKF+uA_n^MbhblV%B$EYYFNj# zYtT5dHi|4;gNk3&t4Lc6C|WP$Li~J4Z6>$|6iZ6nd~=(jMN3FQ$OvETKa+3kz-J}u zHm!O8;8MV*$M@01>;^6cYHXgQ|M261Q1f)U zMY0T9;lK-*Da|)XYAb1<@@rC7cHJyuWVr!s2eK0`?^xy2=XpM*c4K2lpgnu_^AY7(!PSw+8)skRl)k|4VSBN09x$ zt854y#32bw?3Dli%(At){~ku-B&M=`PKa&qde99 diff --git a/send_email.py b/send_email.py index 3a82b6d..736b75d 100644 --- a/send_email.py +++ b/send_email.py @@ -20,7 +20,7 @@ def send_email(interviewee_email="darkicewolf50@gmail.com", interviewee_name="br mst = pytz.timezone("America/Edmonton") # MST with Daylight Savings considered # Parse the input date and time, localize to MST - start_datetime = mst.localize(datetime.strptime(f"{date} {start_time}", "%d-%m-%Y %I:%M %p")) + start_datetime = mst.localize(datetime.strptime(f"{date} {start_time}", "%Y-%m-%d %H:%M:%S")) end_datetime = start_datetime + timedelta(minutes=30) # Format date and time for the calendar URLs @@ -78,4 +78,5 @@ def send_email(interviewee_email="darkicewolf50@gmail.com", interviewee_name="br except Exception as e: print(f"Failed to send email: {e}") -send_email() +if __name__ == "__main__": + send_email()