Skip to content

Commit

Permalink
Merge pull request #14 from kuczynskimaciej1/Sending
Browse files Browse the repository at this point in the history
Sending
  • Loading branch information
ikarmus2001 authored May 31, 2024
2 parents 9f45b9a + 89e3da1 commit 4a16c03
Show file tree
Hide file tree
Showing 20 changed files with 748 additions and 174 deletions.
44 changes: 38 additions & 6 deletions DataSources/dataSources.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
from enum import Enum
from pandas import read_csv, read_excel, DataFrame
from additionalTableSetup import GroupContacts
from models import IModel, Contact
from models import DataImport, IModel, Contact
import sqlalchemy as alchem
import sqlalchemy.orm as orm
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.hybrid import hybrid_property

class SupportedDbEngines(Enum):
Expand Down Expand Up @@ -116,15 +117,33 @@ def LoadSavedState(self) -> None:
continue
IModel.run_loading = False

def Update(self, obj: IModel):
Session = orm.sessionmaker(bind=self.dbEngineInstance)
try:
with Session() as session:
session.merge(obj)
session.commit()
except IntegrityError as ie:
print(ie)
except Exception as e:
print(e)
finally:
self.dbEngineInstance.dispose()

def Save(self, obj: IModel | GroupContacts):
Session = orm.sessionmaker(bind=self.dbEngineInstance)
with Session() as session:
session.add(obj)
session.commit()
session.refresh(obj)
try:
with Session() as session:
session.add(obj)
session.commit()
session.refresh(obj)
except IntegrityError as ie:
print(ie)
except Exception as e:
print(e)
self.dbEngineInstance.dispose()

def DeleteEntry(self, obj: IModel | GroupContacts):
def DeleteEntry(self, obj: IModel | GroupContacts):
Session = orm.sessionmaker(bind=self.dbEngineInstance)
with Session() as session:
session.delete(obj)
Expand Down Expand Up @@ -168,6 +187,8 @@ class GapFillSource():
def __init__(self, source: IDataSource | IModel = Contact) -> None:
if isinstance(source, IDataSource):
self.iData: IDataSource = source
elif isinstance(source, DataImport) or isinstance(source, list):
self.model_source: IModel = source
elif issubclass(source, IModel):
self.model_source: IModel = source
else:
Expand All @@ -192,5 +213,16 @@ def get_possible_values(self):
elif hasattr(self, "model_source"):
if self.model_source == Contact:
self.possible_values = { name: attr for name, attr in Contact.__dict__.items() if isinstance(attr, hybrid_property) and attr != "all_instances" }
elif isinstance(self.model_source, DataImport):
self.possible_values = self.model_source.getColumnPreview()
else:
raise AttributeError(f"{type(self.model_source)} isn't supported")

@staticmethod
def getPreviewText(searched: str) -> str | None:
for g in GapFillSource.all_instances:
candidate = g.possible_values.get(searched, None)
if candidate == None:
continue
return candidate
return None
2 changes: 1 addition & 1 deletion Interface/AddContactWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@


class AddContactWindow(Toplevel):
def __init__(self, parent: Toplevel | ContactList) -> None:
def __init__(self, parent: Toplevel) -> None:
super().__init__(parent)
self.parent = parent

Expand Down
78 changes: 55 additions & 23 deletions Interface/AppUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
from typing import NoReturn
from tkinter import Menu, simpledialog, Listbox, Tk, Frame, Label, Entry, Scrollbar
from tkinter.constants import BOTH, RIDGE, END, LEFT, RIGHT, TOP, X, Y, INSERT
from models import IModel, Template, Group
from models import IModel, Message, Template, Group, User
from tkhtmlview import HTMLLabel
from .GroupEditor import GroupEditor
from .Settings import Settings
from .TemplateEditor import TemplateEditor
from MessagingService.senders import ISender
import MessagingService.smtp_data
from MessagingService.ethereal_demo import send_email


def errorHandler(xd, exctype: type, excvalue: Exception, tb: TracebackType):
Expand All @@ -35,6 +38,20 @@ def prepareInterface(self) -> None:
self.__create_mailing_group_pane()
self.__create_template_pane()
self.__create_mail_input_pane()
self.populateInterface()


def populateInterface(self) -> None:
modelType_func_mapper = {
Template: self.add_template,
Group: self.add_group
}

for (modelType, ui_func) in modelType_func_mapper.items():
ui_func(modelType.all_instances)

def setSender(self, new_sender: ISender):
self.sender = new_sender

def add_periodic_task(self, period: int, func: Callable):
# TODO można poprawić żeby się odpalało tylko przy dodaniu obiektu,
Expand Down Expand Up @@ -69,22 +86,41 @@ def add_group(self, g: Group | Iterable[Group]):
[self.grupy.append(i) for i in g if i not in self.grupy]
self.__update_listbox(self.grupy_listbox, self.grupy)

def clearData(self):
self.grupy = []
self.szablony = []

def update(self):
self.clearData()
self.populateInterface()

def __add_group_clicked(self):
self.show_group_window()

def show_group_window(self, g: Group | None = None):
group_editor = GroupEditor(self, g)
group_editor.prepareInterface()

def __send_clicked(event) -> None:
print("send mail")
pass

def __importuj_clicked(self):
pass

def __eksportuj_clicked(self):
pass
def __send_clicked(self) -> None:
# TODO: Jakoś trzeba ogarnąć multiple selection na template + group (albo zrobić jakiś hackment)
#tmp = self.grupy_listbox.curselection()
#if len(tmp) == 0:
# raise ValueError("Wybierz grupę!")
#else:
# selectedGroup: Group = tmp[0]

#tmp = self.template_listbox.curselection()
#if len(tmp) == 0:
# raise ValueError("Wybierz templatkę!")
#else:
# selectedTemplate: Template = tmp[0]

#self.sender.SendEmails(selectedGroup, selectedTemplate, User.GetCurrentUser())
message = "Hello"
print(message)
#recipient = '[email protected]'
#self.sender.Send(self, MessagingService.smtp_data.smtp_host, MessagingService.smtp_data.smtp_port, MessagingService.smtp_data.email, MessagingService.smtp_data.password, message, recipient)
send_email()

def __template_selection_changed(self, _event):
selected = self.template_listbox.curselection()
Expand All @@ -101,7 +137,9 @@ def __group_selection_changed(self, _event):
selected: int = self.grupy_listbox.curselection()
if len(selected) > 0:
g: Group = self.grupy[selected[0]]
mails = [", ".join(x.email) for x in g.contacts]
mails = ""
for c in g.contacts:
mails += c.email + ", "
self.entry_adres.delete(0, END)
self.entry_adres.insert(INSERT, mails)

Expand All @@ -124,21 +162,14 @@ def __update_listbox(lb: Listbox, content: Iterable[IModel] | dict[IModel]):
lb.delete(0, END)
[lb.insert(END, k) for k in content.keys()]
else:
raise AttributeError(
f"Wrong type of 'content', expected dict or Iterable, got {
type(content)}")
raise AttributeError(f"Wrong type of 'content', expected dict or Iterable, got {type(content)}")

def __add_template_clicked(self):
self.show_template_window()

def __create_menu(self):
menubar = Menu(self.root)

file_menu = Menu(menubar, tearoff=0)
file_menu.add_command(label="Import", command=self.__importuj_clicked)
file_menu.add_command(label="Export", command=self.__eksportuj_clicked)
menubar.add_cascade(label="File", menu=file_menu)

edit_menu = Menu(menubar, tearoff=0)
add_menu = Menu(edit_menu, tearoff=0)
add_menu.add_command(
Expand All @@ -147,8 +178,8 @@ def __create_menu(self):
add_menu.add_command(label="Group", command=self.__add_group_clicked)
edit_menu.add_cascade(label="Add...", menu=add_menu)
menubar.add_cascade(label="Edit", menu=edit_menu)
menubar.add_command(label="Open Settings", command=self.logout)
menubar.add_command(label="Send", command=lambda: self.__send_clicked())
menubar.add_command(label="Open Settings", command=self.__openSettings_clicked)
menubar.add_command(label="Send", command=self.__send_clicked)


self.root.config(menu=menubar)
Expand All @@ -171,6 +202,7 @@ def __create_mailing_group_pane(self):
grupy_label.pack()
self.grupy_listbox.pack(fill=BOTH, expand=True)


def __create_template_pane(self):
templates_frame = Frame(
self.root, bg="lightblue", width=200, height=100, relief=RIDGE, borderwidth=2)
Expand All @@ -188,6 +220,7 @@ def __create_template_pane(self):
szablony_label.pack()
self.template_listbox.pack(fill=BOTH, expand=True)


def __create_mail_input_pane(self):
entry_frame = Frame(self.root, bg="lightblue",
relief=RIDGE, borderwidth=2)
Expand All @@ -212,8 +245,7 @@ def show_template_window(self, obj: Template | None = None):
self.template_window = TemplateEditor(self, self.root, obj)
self.template_window.prepareInterface()

def logout(self):

def __openSettings_clicked(self):
root = Tk() # Otwórz ponownie okno logowania
settings = Settings(root)
settings.prepareInterface()
Expand Down
24 changes: 10 additions & 14 deletions Interface/ContactList.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@
from models import Contact, IModel, Template, Group
from tkhtmlview import HTMLLabel, HTMLText
from DataSources.dataSources import GapFillSource
from GroupEditor import GroupEditor
from AddContactWindow import AddContactWindow
from .AddContactWindow import AddContactWindow

class ContactList(Toplevel):
def __init__(self, parent: Toplevel | GroupEditor, group: Group | None = None) -> None:
def __init__(self, parent: Toplevel, group: Group | None = None) -> None:
super().__init__(parent)
self.group = group
self.parent = parent
Expand Down Expand Up @@ -60,16 +59,15 @@ def update(self):
self.populateWindow()

def populateWindow(self):
shouldAddButton = self.parent != None and isinstance(self.parent, GroupEditor)
for idx, c in enumerate(Contact.all_instances):
self.create_contact_widget(c, idx, addBtn=shouldAddButton)

shouldAddButton = self.parent != None
if self.group:
group_contacts = GroupController.get_contacts(self.group)
group_emails = {contact.email for contact in group_contacts}
for idx, c in enumerate(Contact.all_instances):
added_to_group = c.email in group_emails
self.create_contact_widget(c, idx, added_to_group, addBtn=shouldAddButton)

for idx, c in enumerate(Contact.all_instances):
shouldToggle = c.email in group_emails
self.create_contact_widget(c, idx, added_to_group=shouldToggle, addBtn=shouldAddButton)


def create_contact_widget(self, c: Contact, idx: int, added_to_group: bool = False, addBtn: bool = True):
def toggle_checkbox():
Expand All @@ -91,15 +89,13 @@ def add_contact_to_group(self, c: Contact):

try:
GroupController.add_contact(self.group, c)
if isinstance(self.parent, GroupEditor):
self.parent.update()
self.parent.update()
except IntegrityError:
pass

def remove_contact_from_group(self, c: Contact):
GroupController.delete_connection(self.group, c)
if isinstance(self.parent, GroupEditor):
self.parent.update()
self.parent.update()

def search_contact(self):
search_criteria = self.search_entry.get().strip()
Expand Down
92 changes: 92 additions & 0 deletions Interface/ExternalSourceImportWindow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from os.path import basename
from tkinter import END, Misc, Tk, Toplevel
import tkinter.messagebox as msg
import tkinter.filedialog as fd
from tkinter.ttk import Button, Label, Combobox, Treeview, Scrollbar
from openpyxl import load_workbook
from models import DataImport, Template

class ExternalSourceImportWindow(Toplevel):
def __init__(self, parent: Toplevel | Tk, master: Misc, template: Template) -> None:
super().__init__(master)
self.parent = parent
self.template = template
self.prepareInterface()
self.file_path = None

def prepareInterface(self):
self.title("Importuj dane")

self.grid_rowconfigure(2, weight=1)
self.grid_columnconfigure(1, weight=1)
self.grid_columnconfigure(3, weight=1)

self.label = Label(self, text="Select an Excel file:")
self.select_button = Button(self, text="Browse", command=self.browse_file)
self.combobox_label = Label(self, text="Worksheet names:")
self.combobox = Combobox(self)
self.combobox.bind("<<ComboboxSelected>>", self.update_preview)

self.treeview = Treeview(self, show="headings")
self.treeview_scroll = Scrollbar(self, orient="vertical", command=self.treeview.yview)
self.treeview.configure(yscrollcommand=self.treeview_scroll.set)

self.add_button = Button(self, text="Add", command=self.add_data)

self.label.grid(row=0, column=0, padx=10, pady=10)
self.select_button.grid(row=0, column=1, padx=10, pady=10)
self.combobox_label.grid(row=1, column=0, padx=10, pady=10)
self.combobox.grid(row=1, column=1, padx=10, pady=10, sticky="ew")
self.treeview.grid(row=2, column=0, columnspan=4, padx=10, pady=10, sticky="nsew")
self.treeview_scroll.grid(row=2, column=4, sticky="ns")
self.add_button.grid(row=3, column=0, columnspan=5, padx=10, pady=10)

def browse_file(self):
self.file_path = fd.askopenfilename(
filetypes=[("Excel files", "*.xlsx;*.xlsm"), ("All files", "*.*")]
)

if self.file_path:
self.load_worksheets()

def load_worksheets(self):
try:
workbook = load_workbook(self.file_path, read_only=True)
sheet_names = workbook.sheetnames
self.combobox['values'] = sheet_names
if sheet_names:
self.combobox.current(0)
self.update_preview()
except Exception as e:
msg.showerror("Error", f"Failed to read the Excel file: {e}")

def update_preview(self, event=None):
selected_sheet = self.combobox.get()
if not selected_sheet or not self.file_path:
return

try:
workbook = load_workbook(self.file_path, read_only=True)
sheet = workbook[selected_sheet]

self.treeview.delete(*self.treeview.get_children())
first_row = next(sheet.iter_rows(values_only=True))
if "Email" not in first_row:
# TODO: Można zrobić jakiś label zamiast treeview i errora
raise ValueError("Arkusz musi mieć kolumnę 'Email', aby dało się go połączyć z danymi")

self.treeview["columns"] = first_row
for col in first_row:
self.treeview.heading(col, text=col)
self.treeview.column(col, width=100)

for row in sheet.iter_rows(min_row=2, values_only=True):
self.treeview.insert("", END, values=row)
except Exception as e:
msg.showerror("Error", f"Failed to read the selected worksheet: {e}")

def add_data(self):
di = DataImport(_name=basename(self.file_path), _localPath=self.file_path)
self.template.dataimport = di
self.parent.update()
self.destroy()
Loading

0 comments on commit 4a16c03

Please sign in to comment.