Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
ikarmus2001 committed Jun 3, 2024
2 parents 4a16c03 + 9a20cbd commit f428755
Show file tree
Hide file tree
Showing 14 changed files with 537 additions and 281 deletions.
29 changes: 29 additions & 0 deletions DataSources/dataSourceAbs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from abc import abstractmethod


class IDataSource():
current_instance = None

@classmethod
def __subclasshook__(cls, subclass: type) -> bool:
return (hasattr(subclass, 'GetData') and
callable(subclass.GetData) and
hasattr(subclass, 'checkIntegrity') and
callable(subclass.checkIntegrity) and
hasattr(subclass, 'LoadSavedState') and
callable(subclass.LoadSavedState)
)

@abstractmethod
def GetData(self):
raise RuntimeError

@abstractmethod
def checkIntegrity(self):
raise RuntimeError

@abstractmethod
def LoadSavedState(self):
"""Collect all data saved in data source and instantiate adjacent model objects
"""
raise RuntimeError
83 changes: 38 additions & 45 deletions DataSources/dataSources.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from __future__ import annotations
from abc import ABCMeta, abstractmethod
from collections.abc import Iterable
from enum import Enum
from .dataSourceAbs import IDataSource
from pandas import read_csv, read_excel, DataFrame
from additionalTableSetup import GroupContacts
from models import DataImport, IModel, Contact
from group_controller import GroupController
from models import DataImport, Group, IModel, Contact, Template
import sqlalchemy as alchem
import sqlalchemy.orm as orm
from sqlalchemy.exc import IntegrityError
Expand All @@ -13,32 +14,6 @@
class SupportedDbEngines(Enum):
SQLite=1

class IDataSource():
@classmethod
def __subclasshook__(cls, subclass: type) -> bool:
return (hasattr(subclass, 'GetData') and
callable(subclass.GetData) and
hasattr(subclass, 'checkIntegrity') and
callable(subclass.checkIntegrity) and
hasattr(subclass, 'LoadSavedState') and
callable(subclass.LoadSavedState)
)

@abstractmethod
def GetData(self):
raise RuntimeError

@abstractmethod
def checkIntegrity(self):
raise RuntimeError

@abstractmethod
def LoadSavedState(self):
"""Collect all data saved in data source and instantiate adjacent model objects
"""
raise RuntimeError


class DatabaseHandler(IDataSource):
def __init__(self, connectionString: str, tableCreators: Iterable[IModel],
engine: SupportedDbEngines = SupportedDbEngines.SQLite) -> None:
Expand All @@ -51,6 +26,7 @@ def __init__(self, connectionString: str, tableCreators: Iterable[IModel],
case _:
raise NotImplementedError
self.tableCreators = tableCreators
IDataSource.current_instance = self


def checkIntegrity(self) -> bool:
Expand Down Expand Up @@ -116,6 +92,20 @@ def LoadSavedState(self) -> None:
print(e)
continue
IModel.run_loading = False
self.runAdditionalBindings()


def runAdditionalBindings(self):
for g in Group.all_instances:
g.contacts = GroupController.get_contacts(g)

for t in Template.all_instances:
if t.dataimport_id != None:
for di in DataImport.all_instances:
if t.dataimport_id == di.id:
t.dataimport = di
break # TODO w razie dodanie większej ilości di - templatek


def Update(self, obj: IModel):
Session = orm.sessionmaker(bind=self.dbEngineInstance)
Expand Down Expand Up @@ -162,7 +152,6 @@ def GetData(self) -> DataFrame:
print("Błąd podczas wczytywania pliku XLSX:", e)
return None


class CSVHandler(IDataSource):
def __init__(self, path: str) -> None:
self.file_path = path
Expand All @@ -185,9 +174,10 @@ class GapFillSource():
all_instances: list[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):
# if isinstance(source, IDataSource):
# self.iData: IDataSource = source
# el
if isinstance(source, DataImport) or isinstance(source, list):
self.model_source: IModel = source
elif issubclass(source, IModel):
self.model_source: IModel = source
Expand All @@ -198,25 +188,28 @@ def __init__(self, source: IDataSource | IModel = Contact) -> None:
self.get_possible_values()

def get_possible_values(self):
if hasattr(self, "iData"):
idata_type = type(self.iData)
match(idata_type):
case type(DatabaseHandler):
# openTablePicker()
pass
case type(XLSXHandler):
# openSheetPicker()
pass
case type(CSVHandler):
# openCsvPicker()
pass
elif hasattr(self, "model_source"):
# if hasattr(self, "iData"):
# idata_type = type(self.iData)
# match(idata_type):
# case type(DatabaseHandler):
# # openTablePicker()
# pass
# case type(XLSXHandler):
# # openSheetPicker()
# pass
# case type(CSVHandler):
# # openCsvPicker()
# pass
# el
if 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")
else:
raise AttributeError(f"Incorrectly created GapFillSource, expected 'model_source'={self.model_source} to be present.")

@staticmethod
def getPreviewText(searched: str) -> str | None:
Expand Down
15 changes: 2 additions & 13 deletions Interface/AddContactWindow.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
from collections.abc import Callable, Iterable
from enum import Enum
from sqlalchemy.exc import IntegrityError
from types import TracebackType
from traceback import print_tb
from typing import Literal, Any, NoReturn
from tkinter import Event, Menu, simpledialog, ttk, Listbox, Tk, Text, Button, Frame, Label, Entry, Scrollbar, Toplevel, Misc, messagebox, Menubutton, Canvas,Checkbutton,BooleanVar, VERTICAL, RAISED
from tkinter.ttk import Combobox
from tkinter.constants import NORMAL, DISABLED, BOTH, RIDGE, END, LEFT, RIGHT, TOP, X, Y, INSERT, SEL, WORD
from group_controller import GroupController
from models import Contact, IModel, Template, Group
from tkhtmlview import HTMLLabel, HTMLText
from DataSources.dataSources import GapFillSource
from tkinter import Button, Label, Entry, Toplevel, messagebox
from models import Contact


class AddContactWindow(Toplevel):
Expand Down
107 changes: 64 additions & 43 deletions Interface/AppUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
from types import TracebackType
from traceback import print_tb
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 tkinter import Menu, simpledialog, Listbox, Tk, Frame, Label, Entry, Scrollbar, Button, messagebox
from tkinter.constants import BOTH, RIDGE, END, LEFT, RIGHT, TOP, BOTTOM, X, Y, INSERT
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
#import MessagingService.smtp_data
#from MessagingService.ethereal_demo import send_email


def errorHandler(xd, exctype: type, excvalue: Exception, tb: TracebackType):
Expand Down Expand Up @@ -42,17 +42,25 @@ def prepareInterface(self) -> None:


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

for (modelType, ui_func) in modelType_func_mapper.items():
ui_func(modelType.all_instances)
# 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 setUser(self, current_user: User):
self.user = current_user

def setDb(self, new_db):
#AppUI.db = new_db
IModel.db = new_db

def add_periodic_task(self, period: int, func: Callable):
# TODO można poprawić żeby się odpalało tylko przy dodaniu obiektu,
# przemyśleć
Expand All @@ -69,21 +77,12 @@ def __exit_clicked(self) -> NoReturn | None:
print("Exiting")
exit()

def add_template(self, content: Template | Iterable[Template]):
if isinstance(content, Template):
if content not in self.szablony:
self.szablony.append(content)
else:
[self.szablony.append(i)
for i in content if i not in self.szablony]
def update_templates(self):
self.szablony = Template.all_instances
self.__update_listbox(self.template_listbox, self.szablony)

def add_group(self, g: Group | Iterable[Group]):
if isinstance(g, Group):
if g not in self.grupy:
self.grupy.append(g)
else:
[self.grupy.append(i) for i in g if i not in self.grupy]
def update_groups(self):
self.grupy = Group.all_instances
self.__update_listbox(self.grupy_listbox, self.grupy)

def clearData(self):
Expand All @@ -102,25 +101,18 @@ def show_group_window(self, g: Group | None = None):
group_editor.prepareInterface()

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]
if self.selected_mailing_group == None:
messagebox.showerror("Error", "Wybierz grupę!")
return

if self.selected_template_group == None:
messagebox.showerror("Error", "Wybierz szablon!")
return

#tmp = self.template_listbox.curselection()
#if len(tmp) == 0:
# raise ValueError("Wybierz templatkę!")
#else:
# selectedTemplate: Template = tmp[0]
u = User.GetCurrentUser()
self.sender.Send(self.selected_mailing_group, self.selected_template_group, u)
messagebox.showinfo("Zakończono wysyłanie", "Ukończono wysyłanie maili")

#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 Down Expand Up @@ -196,11 +188,27 @@ def __create_mailing_group_pane(self):
'<<ListboxSelect>>',
self.__group_selection_changed)
self.grupy_listbox.bind('<Double-1>', self.__group_doubleclicked)
assign_button = Button(
groups_frame, text="Wybierz grupę", command=self.__assign_group)

groups_frame.pack(side=LEFT, padx=10, pady=10,
fill=BOTH, expand=True, ipadx=5, ipady=5)
grupy_label.pack()
self.grupy_listbox.pack(fill=BOTH, expand=True)
assign_button.pack(side=BOTTOM)


def __assign_group(self):
selected_index = self.grupy_listbox.curselection()
if selected_index:
selected_group = self.grupy_listbox.get(selected_index)
nameidx = selected_group.find(": ") + 1 # do ujęcia w tym spacji
selected_group = selected_group[nameidx + 1::] # dodawanie do pominięcia spacji
for g in Group.all_instances:
if g.name == selected_group:
self.selected_mailing_group = g
return
raise LookupError(f"Nie znaleziono grupy {selected_group}")


def __create_template_pane(self):
Expand All @@ -214,11 +222,25 @@ def __create_template_pane(self):
'<<ListboxSelect>>',
self.__template_selection_changed)
self.template_listbox.bind('<Double-1>', self.__template_doubleclicked)
assign_button = Button(
templates_frame, text="Wybierz szablon", command=self.__assign_template)

templates_frame.pack(side=LEFT, padx=10, pady=10,
fill=BOTH, expand=True, ipadx=5, ipady=5)
szablony_label.pack()
self.template_listbox.pack(fill=BOTH, expand=True)
assign_button.pack(side=BOTTOM)


def __assign_template(self):
selected_index = self.template_listbox.curselection()
if selected_index:
selected_template = self.template_listbox.get(selected_index)
for t in Template.all_instances:
if t.name == selected_template:
self.selected_template_group = t
return
raise LookupError(f"Nie znaleziono szablonu {selected_template}")


def __create_mail_input_pane(self):
Expand Down Expand Up @@ -246,10 +268,9 @@ def show_template_window(self, obj: Template | None = None):
self.template_window.prepareInterface()

def __openSettings_clicked(self):
root = Tk() # Otwórz ponownie okno logowania
settings = Settings(root)
settings = Settings(self)
settings.prepareInterface()
root.mainloop()
# root.mainloop()



Expand Down
14 changes: 3 additions & 11 deletions Interface/ContactList.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
from collections.abc import Callable, Iterable
from enum import Enum
from sqlalchemy.exc import IntegrityError
from types import TracebackType
from traceback import print_tb
from typing import Literal, Any, NoReturn
from tkinter import Event, Menu, simpledialog, ttk, Listbox, Tk, Text, Button, Frame, Label, Entry, Scrollbar, Toplevel, Misc, messagebox, Menubutton, Canvas,Checkbutton,BooleanVar, VERTICAL, RAISED
from tkinter.ttk import Combobox
from tkinter.constants import NORMAL, DISABLED, BOTH, RIDGE, END, LEFT, RIGHT, TOP, X, Y, INSERT, SEL, WORD
from tkinter import Button, Frame, Label, Entry, Scrollbar, Toplevel, Canvas, Checkbutton, BooleanVar, VERTICAL
from tkinter.constants import BOTH, LEFT, RIGHT, X, Y
from group_controller import GroupController
from models import Contact, IModel, Template, Group
from tkhtmlview import HTMLLabel, HTMLText
from DataSources.dataSources import GapFillSource
from models import Contact, Group
from .AddContactWindow import AddContactWindow

class ContactList(Toplevel):
Expand Down
1 change: 1 addition & 0 deletions Interface/ExternalSourceImportWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,6 @@ def update_preview(self, event=None):
def add_data(self):
di = DataImport(_name=basename(self.file_path), _localPath=self.file_path)
self.template.dataimport = di
self.template.dataimport_id = di.id
self.parent.update()
self.destroy()
Loading

0 comments on commit f428755

Please sign in to comment.