First commit

This commit is contained in:
Cyril Novel 2020-06-11 20:10:49 +02:00
commit 16a203292d
7 changed files with 920 additions and 0 deletions

195
src/email_sender.py Normal file
View file

@ -0,0 +1,195 @@
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from smtplib import SMTP_SSL
from argparse import ArgumentParser
from datetime import date, timedelta
import wikiquote
from src.event import create_events_from_span
week_days = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
months = ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre',
'octobre', 'novembre', 'décembre']
class EmailSender:
def __init__(self, smtp_server, user, password):
self._smtp_server = smtp_server
self._user = user
self._pwd = password
print("Setup OK")
def send_paturlettre(self, recipients):
msg = MIMEMultipart('alternative')
msg['Subject'] = f'[Paturlettre] Semaine {get_week_number_and_span()[0]}'
msg['From'] = 'Paturlettre <paturlettre@kosmon.fr>'
msg['To'] = 'Diffusion Paturlettre <no_reply_paturlettre@kosmon.fr>'
msg.preamble = f'La Paturlettre de la semaine {get_week_number_and_span()[0]} !'
msg.attach(MIMEText(create_plain_email(), 'plain'))
msg.attach(MIMEText(create_email_from_template(), 'html'))
print("Msg OK")
conn = SMTP_SSL(self._smtp_server, port=465)
print("Conn OK")
conn.login(self._user, self._pwd)
print("Login OK")
try:
conn.sendmail(msg['From'], 'diffusion_paturlettre@kosmon.fr', msg.as_string())
print("Send OK")
finally:
conn.quit()
def create_email_from_template():
events_previous, events_current, events_next = get_events()
with open('../html_template/mail.html', 'r', encoding='utf-8') as tmp:
mail = tmp.read()
mail = mail.replace('{LogoAddress}',
'https://tof.cx/images/2020/05/27/448fd7d507778064b39ba95c381cd3e8.png')
wk_and_span = get_week_number_and_span()
mail = mail.replace('{Main_Title}', f'Semaine {wk_and_span[0]}')
mail = mail.replace('{Sub_Title}',
f'du {wk_and_span[1].day} {months[wk_and_span[1].month - 1]}'
f' au {wk_and_span[2].day} {months[wk_and_span[2].month - 1]}')
mail = mail.replace('{EVENTS_PREVIOUS}', create_html_string_for_week(events_previous))
mail = mail.replace('{EVENTS_CURRENT}', create_html_string_for_week(events_current))
mail = mail.replace('{EVENTS_NEXT}', create_html_string_for_week(events_next))
quote = wikiquote.qotd(lang='fr')
mail = mail.replace('{Quote_Text}', quote[0])
mail = mail.replace('{Quote_Author}', quote[1])
return mail
def create_plain_email():
events_previous, events_current, events_next = get_events()
wk_and_span = get_week_number_and_span()
mail_plain = f'Paturlettre - Semaine {wk_and_span[0]}\n'
mail_plain += f'du {wk_and_span[1].day} {months[wk_and_span[1].month - 1]}' \
f' au {wk_and_span[2].day} {months[wk_and_span[2].month - 1]}\n'
mail_plain += '\n\n'
mail_plain += 'Semaine précédente :\n'
mail_plain += create_plain_string_for_week(events_previous)
mail_plain += '\n'
mail_plain += 'Cette semaine :\n'
mail_plain += create_plain_string_for_week(events_current)
mail_plain += '\n'
mail_plain += 'Semaine suivante :\n'
mail_plain += create_plain_string_for_week(events_next)
mail_plain += '\n\n'
quote = wikiquote.qotd(lang='fr')
mail_plain += 'Citation de la semaine :\n'
mail_plain += quote[0] + '\n'
mail_plain += f'{quote[1]}\n\n'
mail_plain += 'Cet email vous est envoyé par Cyril NOVEL parce que vous êtes inscrit sur la ' \
'liste de diffusion de la Paturlettre.\n'
mail_plain += 'Des questions ou des commentaires ? Envoyez un email à paturlettre@kosmon.fr\n'
return mail_plain
def create_html_string_for_day(day_name: str, events: list, last_day: bool) -> str:
html_str = ' <tr style="width: 100%;">\n'
html_str += f' <td class="day_row">{day_name}</td>\n'
html_str += ' </tr>\n'
for i in range(0, len(events)):
e = events[i]
style = "width: 100%;"
if not last_day and i == len(events) - 1:
style = "width: 100%; border-bottom: 1px dashed #9ba5ae;"
html_str += f' <tr style="{style}">\n'
html_str += f' <td class="genea_row"><span class="min_info"><b>{e.type_str()}</b> · {e.event_date_as_str()} </span><br />{e.event_description()}</td>\n'
html_str += ' </tr>'
return html_str
def create_html_string_for_no_event() -> str:
html_str = ' <tr style="width: 100%;">\n'
html_str += f' <td class="day_row">Pas d\'événements</td>\n'
html_str += ' </tr>\n'
return html_str
def create_plain_string_for_no_event() -> str:
return 'Pas d\'événements\n'
def create_html_string_for_week(events: list):
if not events:
return create_html_string_for_no_event()
d = {}
last_day = -1
for i in range(0, 7):
d[i] = [e for e in events if e.get_actual_weekday() == i]
if d[i]:
last_day = i
html_str = ''
for i in range(0, 7):
if d[i]:
html_str += create_html_string_for_day(week_days[i], d[i], i == last_day)
return html_str
def create_plain_string_for_week(events: list):
if not events:
return create_plain_string_for_no_event()
plain_str = ''
d = {}
for i in range(0, 7):
d[i] = [e for e in events if e.get_actual_weekday() == i]
for i in range(0, 7):
if d[i]:
plain_str += f' - {week_days[i]} :\n'
for e in d[i]:
plain_str += f' * {e.type_str()} · {e.event_date_as_str()} · ' \
f'{e.event_description()}\n'
return plain_str
def get_events():
today = date.today()
monday = today - timedelta(days=today.weekday())
previous_monday = monday - timedelta(days=7)
next_monday = monday + timedelta(days=7)
previous_week_events = create_events_from_span(previous_monday,
previous_monday + timedelta(days=6))
this_week_events = create_events_from_span(monday,
monday + timedelta(days=6))
next_week_events = create_events_from_span(next_monday,
next_monday + timedelta(days=6))
return previous_week_events, this_week_events, next_week_events
def get_week_number_and_span():
today = date.today()
monday = today - timedelta(days=today.weekday())
return today.isocalendar()[1], monday, monday + timedelta(days=6)
def main():
parser = ArgumentParser(description='Send mail')
parser.add_argument('-s', '--smtp', dest='smtp_server',
help='Json files to process, multiple values accepted')
parser.add_argument('-u', '--user', dest='user',
help='Json files to process, multiple values accepted')
parser.add_argument('-p', '--password', dest='password',
help='Json files to process, multiple values accepted')
args = parser.parse_args()
email_sender = EmailSender(args.smtp_server, args.user, args.password)
email_sender.send_paturlettre(None)
print("Done")
if __name__ == '__main__':
main()

182
src/event.py Normal file
View file

@ -0,0 +1,182 @@
from datetime import date
from src.person import Person
from src.mariage import Mariage
from os.path import abspath, join, dirname
event_types = ['N', 'M', 'D']
members_file = abspath(join(dirname(__file__), 'data', 'members.csv'))
mariages_file = abspath(join(dirname(__file__), 'data', 'mariages.csv'))
class Event:
def __init__(self, event_type: str, event_date: date, current_date: date,
person_a: Person, person_b: Person = None):
if event_type not in event_types:
raise ValueError('{} is not in {}'.format(event_type, event_types))
self._type = event_type
self._date = event_date
self._current_date = current_date
self._p_a = person_a
self._p_b = person_b
def get_year_span(self) -> int:
return self._current_date.year - self._date.year
def event_description(self) -> str:
y = self.get_year_span()
if self._type == 'N':
return '{}{}{} il y a {} an{}'.format(
self._p_a.formatted_name(),
' (a épousé {})'.format(self._p_b.formatted_name()) if self._p_b else '',
'' if self._p_a.is_male() else 'e',
y,
's' if y > 1 else ''
)
if self._type == 'M':
return '{} a épousé {} il y a {} an{}'.format(
self._p_a.formatted_name(),
self._p_b.formatted_name(),
y,
's' if y > 1 else ''
)
return '{}{} décédé{} il y a {} ans{}'.format(
self._p_a.formatted_name(),
' (a épousé {})'.format(self._p_b.formatted_name()) if self._p_b else '',
'' if self._p_a.is_male() else 'e',
y,
's' if y > 1 else ''
)
def event_date_as_str(self) -> str:
day = str(self._date.day) if self._date.day > 9 else '0' + str(self._date.day)
month = str(self._date.month) if self._date.month > 9 else '0' + str(self._date.month)
return '{}/{}/{}'.format(day, month, self._date.year)
def get_actual_weekday(self) -> int:
return self._current_date.weekday()
def type_str(self):
return self._type
def load_persons_to_dict() -> dict:
id_to_person = {}
first_line = True
with open(members_file, 'r', encoding='utf-8') as members:
for line in members:
if first_line:
first_line = False
continue
line.strip()
infos = line.split(';')
birth = infos[5].split('-')
death = None if infos[6] == 'NULL' else infos[6].split('-')
p = Person(int(infos[0]),
'M' if infos[3] == '1' else 'F',
infos[2],
infos[1],
date(int(birth[0]), int(birth[1]), int(birth[2])),
None if not death else date(int(death[0]), int(death[1]), int(death[2])),
infos[4] == '1',
infos[7] == '1')
id_to_person[int(infos[0])] = p
return id_to_person
def load_mariage_to_dict() -> dict:
id_to_mariage = {}
first_line = True
with open(mariages_file, 'r', encoding='utf-8') as mariages:
for line in mariages:
if first_line:
first_line = False
continue
line.strip()
infos = line.split(';')
if infos[3] == "NULL": # Union libre, not an event
continue
m_type = 'M'
if infos[6] == '0':
m_type = 'D'
if infos[6] == '2':
m_type = 'L'
m_date = infos[3].split('-')
m = Mariage(
int(infos[0]),
int(infos[1]),
int(infos[2]),
date(int(m_date[0]), int(m_date[1]), int(m_date[2])),
infos[4] == '1',
infos[5] == '1',
m_type
)
if m.show_mariage():
id_to_mariage[int(infos[0])] = m
return id_to_mariage
def find_significant_other(id_to_mariage: dict, uid: int) -> int:
for m in id_to_mariage.values():
if m.in_this_union(uid) and m.show_mariage():
return m.get_so(uid)
return -1
def create_events_from_span(start: date, end: date) -> list:
id_to_person = load_persons_to_dict()
id_to_mariage = load_mariage_to_dict()
cur_year = start.year
events = []
for p in id_to_person.values():
so = find_significant_other(id_to_mariage, p.uid())
if so == p.uid():
print(f"What ??? {so}")
if not p.is_dead():
birth = p.birth()
birth_in_year = date(cur_year, birth.month, birth.day)
if start <= birth_in_year <= end:
e = Event('N', birth, birth_in_year,
p, None if so == -1 else id_to_person[so])
events.append(e)
birth_in_year = date(cur_year + 1, birth.month, birth.day)
if start <= birth_in_year <= end:
e = Event('N', birth, birth_in_year,
p, None if so == -1 else id_to_person[so])
events.append(e)
else:
death = p.death()
death_in_year = date(cur_year, death.month, death.day)
if start <= death_in_year <= end:
e = Event('D', death, death_in_year,
p, None if so == -1 else id_to_person[so])
events.append(e)
death_in_year = date(cur_year + 1, death.month, death.day)
if start <= death_in_year <= end:
e = Event('D', death, death_in_year,
p, None if so == -1 else id_to_person[so])
events.append(e)
for m in id_to_mariage.values():
if not m.show_mariage():
continue
m_date = m.event_date()
date_in_year = date(cur_year, m_date.month, m_date.day)
if start <= date_in_year <= end:
e = Event('M', m_date, date_in_year,
id_to_person[m.id_a()], id_to_person[m.id_b()])
events.append(e)
date_in_year = date(cur_year + 1, m_date.month, m_date.day)
if start <= date_in_year <= end:
e = Event('M', m_date, date_in_year,
id_to_person[m.id_a()], id_to_person[m.id_b()])
events.append(e)
return events

37
src/mariage.py Normal file
View file

@ -0,0 +1,37 @@
from datetime import date
unions = ['D', 'M', 'L'] # Divorce (0), Mariage ou Pacs (1), Union Libre (2)
class Mariage:
def __init__(self, uid: int, id_a: int, id_b: int, event_date: date, last_mariage: bool,
hide: bool, union: str):
self._uid = uid
self._id_a = id_a
self._id_b = id_b
self._date = event_date
self._last_mariage = last_mariage
self._hide = hide
if union not in unions:
raise ValueError('{} is not in {}'.format(union, unions))
self._union = union
def show_mariage(self):
return not self._hide and self._union != 'D' and self._last_mariage
def event_date(self):
return self._date
def in_this_union(self, uid: int) -> bool:
return self._id_a == uid or self._id_b == uid
def get_so(self, uid: int) -> int:
if uid == self._id_a:
return self._id_b
return self._id_a
def id_a(self):
return self._id_a
def id_b(self):
return self._id_b

45
src/person.py Normal file
View file

@ -0,0 +1,45 @@
from datetime import date
genders = ['M', 'F']
class Person:
def __init__(self, uid: int, gender: str, first_name: str, last_name: str,
birth: date, death: date, in_law: bool, hide: bool):
if gender not in genders:
raise ValueError('{} is not in {}'.format(gender, genders))
self._gender = gender
self._first = first_name
self._last = last_name
self._birth = birth
self._death = death
self._in_law = in_law
self._uid = uid
self._hide = hide
def uid(self):
return self._uid
def is_dead(self):
return self._death is not None
def hide(self):
return self._hide
def death(self, death: date = None) -> date:
if death is not None:
self._death = death
return self._death
def birth(self, birth: date = None) -> date:
if birth is not None:
self._birth = birth
return self._birth
def formatted_name(self):
return '{}{} {}'.format('' if self.is_dead() else '',
self._first.title(),
self._last.upper())
def is_male(self):
return self._gender == 'M'