Compare commits

...

7 Commits

Author SHA1 Message Date
jeanGaston 23c4276afb Update Readme
- Correction on file tree
2024-10-25 14:14:41 +02:00
jeanGaston 95f0dea5b3 Update Readme 2024-10-25 11:05:11 +02:00
jeanGaston 0f2df17584 Update Gitignore
- Add srv/env.py
2024-10-25 10:49:59 +02:00
jeanGaston 8934f58292 Update env.py
- Add CSV_PATH
- Add CSV_Prefix
_ Add History_years
2024-10-25 10:49:37 +02:00
jeanGaston 2f3f1d9a13 minor Updates 2024-10-25 10:45:38 +02:00
jeanGaston 541b947609 Update function to load n years of history
Add a function to load participants data from the current year file
2024-10-25 10:45:22 +02:00
jeanGaston 598bae34ba Update draw function to take n years of history in account 2024-10-25 10:44:39 +02:00
6 changed files with 136 additions and 66 deletions
+1 -1
View File
@@ -160,4 +160,4 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
config/env.py
src/env.py
+28 -15
View File
@@ -1,12 +1,12 @@
# Random Christmas Bot
This Python project automates the process of organizing a Secret Santa event. It randomly assigns each participant one or two recipients and ensures participants do not receive the same recipients from the previous year. The results are sent via email using an SMTP relay, and participant data (names, emails, and previous draw results) are managed in a CSV file.
This Python project automates the process of organizing a Secret Santa event. It randomly assigns each participant one or two recipients and ensures participants do not receive the same recipients from the previous years. The results are sent via email using an SMTP relay, and participant data (names, emails, and previous draw results) are managed in a CSV file.
## Features
- Randomly assign one or two recipients for each participant.
- Ensure participants do not receive the same recipients as last year.
- Ensure participants do not receive the same recipients as the last `n` years.
- Sends personalized emails with draw results to participants.
- Stores participant data (names, emails, and draw results) in a CSV file.
- Modular structure for better code maintenance.
@@ -15,7 +15,7 @@ This Python project automates the process of organizing a Secret Santa event. It
## Project Structure
```bash
secret-santa/
Random-Christmas-Bot/
├── src/
│ ├── draw.py # Logic for drawing names
@@ -35,11 +35,14 @@ The configuration file contains SMTP settings, file paths, and customizable para
Example `env.py`:
```python
# Configuration file with SMTP settings and CSV path settings
SMTP_SERVER = "smtp.example.com"
SMTP_PORT = 25
SENDER_EMAIL = "santa@example.com"
CSV_FILE_PATH = r"secret_santa_DB.csv"
DRAW_PER_PERSON = 2 # Choose 1 or 2 recipients per person
CSV_PATH = r"path\to\your\csv\files" # Path to the CSV files
CSV_PREFIX = r"secret_santa_DB" # Prefix for CSV files
HISTORY_YEARS = 2 # Number of past years to consider in the draw
DRAW_PER_PERSON = 2 # Number of recipients per person
# Email content
EMAIL_SUBJECT = "Secret Santa {year} Draw"
@@ -68,8 +71,7 @@ This email was sent automatically, please do not reply.
2. Ensure you have Python installed on your system. If not, download and install Python from [here](https://www.python.org/downloads/).
3. Set up the `env.py` file in the `src/` directory, adjusting the SMTP settings, CSV file path, and draw parameters as needed.
Example structure of the CSV file
with the following colunms
Example structure of the CSV file with the following columns:
```csv
Name,Email,Last_Year_Recipient_1,Last_Year_Recipient_2
```
@@ -80,33 +82,44 @@ Bob,bob@example.com,Alice,David
Charlie,charlie@example.com,David,Alice
```
4. Ensure the CSV file is in the correct location as specified in `env.py`.
4. Name your CSVs using a consistent naming convention `[prefix]_20xx.csv` as the program will retrieve those using the prefix set in `env.py`.
5. Ensure the CSV file is in the correct location as specified in `env.py`.
## Usage
1. Run the main script by executing:
1. Create a CSV file for the current year draw with the participants' information (Name and email).
Like this:
```csv
Alice,alice@example.com
Bob,bob@example.com
Charlie,charlie@example.com
```
2. Run the main script by executing:
```bash
python src/main.py
```
2. The script will:
3. The script will:
- Load participant data from the CSV file.
- Perform the Secret Santa draw based on the configuration (1 or 2 recipients).
- Send an email to each participant with the names of their gift recipients.
- Save the updated draw results back to the CSV file.
3. If any errors occur, they will be displayed in the console, and you can retry or debug as needed.
4. If any errors occur, they will be displayed in the console, and you can retry or debug as needed.
## Customization
- **Number of recipients**: Modify `DRAW_PER_PERSON` in `env.py` to choose whether participants receive one or two recipients.
- **Email content**: Customize the email subject and body in `env.py` using placeholders like `{name}` for the participant's name and `{draws}` for their recipients.
- **CSV file location**: Adjust the `CSV_FILE_PATH` in `env.py` if you prefer a different directory for the participant data.
- **CSV file location**: Adjust the `CSV_PATH` in `env.py` if you prefer a different directory for the participant data.
- **Number of historical years**: Change `HISTORY_YEARS` in `env.py` to set how many previous years of draws should be considered.
## File Descriptions
- **`draw.py`**: Contains the logic for performing the Secret Santa draw, ensuring no repeat recipients from last year.
- **`draw.py`**: Contains the logic for performing the Secret Santa draw, ensuring no repeat recipients from the last years.
- **`emailer.py`**: Handles email sending via the SMTP server.
- **`file_io.py`**: Responsible for reading and writing the participant data from/to the CSV file.
- **`main.py`**: The main program that ties everything together and coordinates the draw and email sending.
@@ -124,4 +137,4 @@ This project is licensed under the MIT License. You are free to modify and distr
## Contributions
Contributions are welcome! Feel free to open issues or submit pull requests to improve this project.
Contributions are welcome! Feel free to open issues or submit pull requests to improve this project.
+38 -28
View File
@@ -1,41 +1,51 @@
from random import choice
def draw_names(previous_draw, draws_per_person):
def draw_names(current_participants, history_data, draws_per_person, max_attempts=5):
"""
Perform the Secret Santa draw considering past draws.
:param previous_draw: Last year's draw data (list of participants and recipients).
Perform the Secret Santa draw considering past years' draws.
:param current_participants: This year's participant list.
:param history_data: Historical draw data to avoid repeats.
:param draws_per_person: Number of people each participant should give gifts to.
:param max_attempts: Maximum number of retry attempts before loosening exclusions. by default : 5.
:return: The new draw results.
"""
participants = [a[0] for a in previous_draw] # Get participant names
participants = [p[0] for p in current_participants] # Get participant names
already_drawn = [] # Track who has been drawn
new_draw = [] # Store new draw results
for i in range(len(participants)):
giver = previous_draw[i][0]
email = previous_draw[i][1]
print(email)
last_year_r1 = previous_draw[i][2]
last_year_r2 = previous_draw[i][3]
for attempt in range(max_attempts):
new_draw.clear()
already_drawn.clear()
success = True # Track if draw is successful in this attempt
available_participants = participants.copy()
try:
available_participants.remove(giver)
available_participants.remove(last_year_r1)
available_participants.remove(last_year_r2)
except ValueError:
pass
for i, giver in enumerate(participants):
email = current_participants[i][1]
# Collect previous recipients to avoid drawing the same person again
previous_recipients = {recipient for record in history_data if record[0] == giver for recipient in record[2:]}
new_recipients = []
while len(new_recipients) < draws_per_person:
selected = choice(available_participants)
if already_drawn.count(selected) >= draws_per_person:
available_participants.remove(selected)
else:
new_recipients.append(selected)
already_drawn.append(selected)
available_participants.remove(selected)
# Create a set of available participants excluding the giver and previous recipients
available_participants = set(participants) - {giver} - previous_recipients
new_recipients = []
new_draw.append([giver, email] + new_recipients)
# Ensure there are enough available participants for the draw
if len(available_participants) < draws_per_person:
success = False
break
return new_draw
# Select recipients for the current giver
while len(new_recipients) < draws_per_person:
selected = choice(list(available_participants))
if already_drawn.count(selected) < draws_per_person:
new_recipients.append(selected)
already_drawn.append(selected)
available_participants.discard(selected)
new_draw.append([giver, email] + new_recipients)
# If the draw was successful, break out of attempts
if success:
return new_draw
print(f"Attempt {attempt + 1} failed, retrying...")
# If all attempts fail, raise an error
raise ValueError("Unable to complete a valid draw after maximum attempts.")
+6 -3
View File
@@ -1,8 +1,11 @@
# Configuration file with SMTP settings and CSV path settings
SMTP_SERVER = "smtp.example.com"
SMTP_PORT = 25
SENDER_EMAIL = "santa@example.com"
CSV_FILE_PATH = r"secret_santa_DB.csv"
DRAW_PER_PERSON = 2 # Choose 1 or 2 recipients per person
CSV_PATH = r"path\to\your\csv\files" # Path to the CSV files
CSV_PREFIX = r"secret_santa_DB" # Prefix for CSV files
HISTORY_YEARS = 2 # Number of past years to consider in the draw
DRAW_PER_PERSON = 2 # Number of recipients per person
# Email content
EMAIL_SUBJECT = "Secret Santa {year} Draw"
@@ -15,4 +18,4 @@ Feel free to use your imagination and make their Christmas magical!
Merry Christmas!
This email was sent automatically, please do not reply.
"""
"""
+52 -8
View File
@@ -1,13 +1,57 @@
import csv
from datetime import date
from env import CSV_PREFIX, CSV_PATH
def open_csv(file_name):
"""Open the CSV file and return its contents as a list of rows"""
with open(file_name, "r", encoding='utf-8') as file:
reader = csv.reader(file)
return list(reader)
def load_history(years):
"""
Load participant data from multiple CSV files based on the specified history years.
:param years: Number of years of history to load.
:return: A list of all historical draw data.
"""
current_year = date.today().year
history_data = []
def save_csv(data, file_name):
"""Save the updated draw results to the CSV file"""
# Load data for each of the past specified years
for i in range(1, years + 1):
try:
year = current_year - i
file_name = f"{CSV_PATH}/{CSV_PREFIX}_{year}.csv"
with open(file_name, "r", encoding='utf-8') as file:
reader = csv.reader(file)
history_data.extend(list(reader)) # Add each year's data
except FileNotFoundError:
print(f"No historical file found for year {year}, skipping.")
continue
return history_data
def load_participants():
"""
Load participant data from the current year CSV file.
:return: A list of all historical draw data.
"""
current_year = date.today().year
participants_data = []
try:
file_name = f"{CSV_PATH}/{CSV_PREFIX}_{current_year}.csv"
print(file_name)
with open(file_name, "r", encoding='utf-8') as file:
reader = csv.reader(file)
participants_data.extend(list(reader)) # Add each year's data
except FileNotFoundError:
print(f"No file found for year {current_year}, skipping.")
return participants_data
def save_csv(data, year):
"""
Save the new draw results to a CSV file named with the current year.
:param data: New draw data.
:param year: The current year for naming the file.
"""
file_name = f"{CSV_PATH}/{CSV_PREFIX}_{year}.csv"
with open(file_name, 'w', newline='', encoding='utf-8') as file:
writer = csv.writer(file)
writer.writerows(data)
writer.writerows(data)
+11 -11
View File
@@ -1,5 +1,5 @@
from env import CSV_FILE_PATH, DRAW_PER_PERSON, EMAIL_SUBJECT, EMAIL_BODY
from file_io import open_csv, save_csv
from env import DRAW_PER_PERSON, HISTORY_YEARS, EMAIL_SUBJECT, EMAIL_BODY
from file_io import load_history, save_csv, load_participants
from draw import draw_names
from emailer import send_email
from utils import get_current_time
@@ -11,29 +11,29 @@ def send_all_emails(new_draw):
for participant in new_draw:
name = participant[0]
receiver_email = participant[1]
draws = ", ".join(participant[2:]) # List of recipients
draws = ", ".join(participant[2:])
message = EMAIL_BODY.format(name=name, draws=draws)
subject = EMAIL_SUBJECT.format(year=current_year)
send_email(receiver_email, subject, message) # Send the email
send_email(receiver_email, subject, message)
print(f"Email sent to {name} ({receiver_email})")
if __name__ == "__main__":
try:
# Load previous draw data
old_draw = open_csv(CSV_FILE_PATH)
# Load data from historical CSVs and this year's participants
history_data = load_history(HISTORY_YEARS)
# Perform new draw
new_draw = draw_names(old_draw, DRAW_PER_PERSON)
current_year = date.today().year
current_participants = load_participants() # Load participants data
# Perform the draw
new_draw = draw_names(current_participants, history_data, DRAW_PER_PERSON)
# Send emails to participants
send_all_emails(new_draw)
# Save new draw results
save_csv(new_draw, CSV_FILE_PATH)
save_csv(new_draw, current_year)
# Output completion time
print(f"Process completed at {get_current_time()[1]} on {get_current_time()[0]}")
except Exception as e:
print(f"Error occurred: {e}")
# Retry or handle errors