diff --git a/.gitignore b/.gitignore index 8f9852c..358ecfc 100644 --- a/.gitignore +++ b/.gitignore @@ -172,3 +172,4 @@ local.properties .scala_dependencies .worksheet +.project diff --git a/app.py b/app.py index e243f93..96751e3 100644 --- a/app.py +++ b/app.py @@ -1,21 +1,39 @@ import gi import os gi.require_version('Gtk', '3.0') -from gi.repository import Gtk +from gi.repository import Gtk, GObject +from mods.db import Patient, store_patient_index, search_patients, db +from datetime import date +import peewee resource_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'res') ui_dir = os.path.join(resource_dir, 'ui') main_win_file = os.path.join(ui_dir, 'main_win.glade') new_patient_win_file = os.path.join(ui_dir, 'new_patient_win.glade') -def create_new_patient_win(): - class NewPatienWinHandler: - pass - b = Gtk.Builder() - b.add_from_file(new_patient_win_file) - b.connect_signals(NewPatienWinHandler()) - w = b.get_object('new_patient_window') - return w +builder = Gtk.Builder() + +class PatientFilter: + def __init__(self): + self.reset() + def filter(self, query): + if query != self.fstr: + self.fstr = query + self.ids = list(map(lambda x: x.id, search_patients(query))) + return self.ids + def reset(self): + self.fstr = '' + self.ids = [] +patient_filter = PatientFilter() + +class PatientRow(Gtk.ListBoxRow): + @GObject.Property + def db_id(self): + return self._db_id + + @db_id.setter + def my_custom_property(self, value): + self._db_id = value class MainWinHandler: def main_win_close(self, *args): @@ -23,10 +41,107 @@ class MainWinHandler: def show_new_patient_win(self, button): new_patient_win = create_new_patient_win() new_patient_win.show_all() + def patient_filter_changed(self, filter_widget): + pl = builder.get_object('patient_list') + pl.unselect_all() + pl.invalidate_filter() + '''pl = builder.get_object('patient_list') + for r in pl.get_children(): + pl.remove(r) + fstr = builder.get_object('patient_filter').get_text().strip() + if not fstr: + for p in Patient.select(): + pl.add(build_patient_row(p)) + else: + patients = search_patients(fstr) + for p in patients: + pl.add(build_patient_row(p)) + pl.show_all()''' + +def build_patient_row(patient): + row = PatientRow() + row.props.db_id = patient.id + label = Gtk.Label(f'{" ".join([patient.last_name, patient.first_name, patient.middle_name])}, {patient.birth_date}', xalign=0) + row.add(label) + return row +def patient_sort_func(row1, row2, *a): + text1 = row1.get_children()[0].get_text() + text2 = row2.get_children()[0].get_text() + return (text1 > text2) - (text1 < text2) +def patient_filter_func(row): + fstr = builder.get_object('patient_filter').get_text().strip() + if not fstr: + patient_filter.reset() + return True + return row.props.db_id in patient_filter.filter(fstr) -builder = Gtk.Builder() builder.add_from_file(main_win_file) builder.connect_signals(MainWinHandler()) +patient_list = builder.get_object('patient_list') +patient_list.set_sort_func(patient_sort_func) +patient_list.set_filter_func(patient_filter_func) +with db.atomic(): + for p in Patient.select(): + patient_list.add(build_patient_row(p)) + +def show_msg(text, sec_text='', level='info'): + msg_win_file = os.path.join(ui_dir, f'{level}_win.glade') + b = Gtk.Builder() + b.add_from_file(msg_win_file) + w = b.get_object(level) + w.set_property('text',text) + w.set_property('secondary_text',sec_text) + w.run() + w.close() + +def create_new_patient_win(): + b = Gtk.Builder() + class NewPatienWinHandler: + def only_digits(self, entry): + text = entry.get_text() + text = ''.join(filter(lambda x: x.isdigit(), text)) + entry.set_text(text) + def save_patient(self, *args): + l_name = b.get_object('last_name').get_text().replace(' ', '') + f_name = b.get_object('first_name').get_text().replace(' ', '') + m_name = b.get_object('middle_name').get_text().replace(' ', '') + if '' in (l_name, f_name): + return show_msg('Не указаны имя или фамилия', 'Данные поля должны быть заполнены', level='warn') + try: + birth_d = int(b.get_object('birth_day').get_text()) + if birth_d < 1 or birth_d > 31: + return show_msg('Неверный день месяца', 'Укажите число в диапазоне 1-31', level='warn') + birth_m = int(b.get_object('birth_month').get_text()) + if birth_m < 1 or birth_m > 12: + return show_msg('Неверный номер месяца', 'Укажите число в диапазоне 1-12', level='warn') + birth_y = int(b.get_object('birth_year').get_text()) + except ValueError: + return show_msg('Неверно указана дата рождения', 'Все поля даты должны быть заполнены', level='warn') + gender = b.get_object('gender').get_active_id() + if not gender: + return show_msg('Не выбран пол', level='warn') + birth_date = date(birth_y, birth_m, birth_d) + with db.atomic(): + try: + patient = Patient.create( + last_name=l_name, + first_name=f_name, + middle_name=m_name, + birth_date=birth_date, + gender=gender + ) + except peewee.IntegrityError: + return show_msg('Данный пациент уже существует', 'Пациент с указанными фамилией, именем\nи датой рождения уже есть с базе данных', level='warn') + store_patient_index(patient) + row = build_patient_row(patient) + patient_list.add(row) + patient_list.select_row(row) + patient_list.show_all() + b.get_object('new_patient_window').close() + b.add_from_file(new_patient_win_file) + b.connect_signals(NewPatienWinHandler()) + w = b.get_object('new_patient_window') + return w main_win = builder.get_object('main_window') main_win.show_all() diff --git a/mods/db.py b/mods/db.py index e69de29..c85a51c 100644 --- a/mods/db.py +++ b/mods/db.py @@ -0,0 +1,66 @@ +import os +from peewee import Model, CharField, BigAutoField, DateTimeField, IntegerField, fn, Field, Expression, TextField, ForeignKeyField, DateField +from playhouse.sqlite_ext import SqliteExtDatabase, FTS5Model, AutoIncrementField, SearchField, RowIDField + +var_dir = os.path.join(os.path.abspath(os.environ['HOME']), '.eldoc') +db_dir = os.path.join(var_dir, 'db') +db_file = os.path.join(db_dir, 'eldoc.sqlite3') + +if not os.path.exists(var_dir): + os.mkdir(var_dir) +if not os.path.exists(db_dir): + os.mkdir(db_dir) + +db = SqliteExtDatabase(db_file, pragmas={ + 'journal_mode': 'wal', + 'cache_size': -1 * 64000, # 64MB + 'foreign_keys': 'on', + 'ignore_check_constraints': 'off', + 'synchronous': 'off'}) + +class BaseModel(Model): + 'Базовая таблица' + class Meta: + database = db +class BaseFTSModel(FTS5Model): + class Meta: + database = db + options = {'tokenize': 'porter'} + +class Patient(BaseModel): + id = AutoIncrementField() + last_name = TextField() + first_name = TextField() + middle_name = TextField(null=True) + birth_date = DateField() + gender = CharField(6) +Patient.add_index( + Patient.index(Patient.last_name, Patient.first_name, Patient.birth_date, unique=True) + ) +class PatientIndex(BaseFTSModel): + rowid = RowIDField() + fio = SearchField() +def search_patients(q): + with db.atomic(): + return (Patient.select() + .join( + PatientIndex, + on=(Patient.id == PatientIndex.rowid)) + .where(PatientIndex.match(q)) + .order_by(PatientIndex.bm25())) +def store_patient_index(pat): + fio_list = [pat.last_name, pat.first_name] + if pat.middle_name: + fio_list.append(pat.middle_name) + PatientIndex.insert( + { + PatientIndex.rowid: pat.id, + PatientIndex.fio: ' '.join(fio_list) + } + ).execute() + +db.connect() +db.create_tables([ + Patient, + PatientIndex + ]) diff --git a/res/ui/main_win.glade b/res/ui/main_win.glade index 03c0c31..41ad06c 100644 --- a/res/ui/main_win.glade +++ b/res/ui/main_win.glade @@ -138,7 +138,7 @@ - + True False @@ -154,12 +154,13 @@ False True - + True True edit-find-symbolic False False + diff --git a/res/ui/new_patient_win.glade b/res/ui/new_patient_win.glade index e7b4d22..b3fe46e 100644 --- a/res/ui/new_patient_win.glade +++ b/res/ui/new_patient_win.glade @@ -7,6 +7,9 @@ True 600 500 + dialog + True + True True @@ -22,6 +25,7 @@ True top True + @@ -50,7 +54,7 @@ vertical 5 - + True True Фамилия @@ -62,7 +66,7 @@ - + True True Имя @@ -74,7 +78,7 @@ - + True True Отчество @@ -120,12 +124,13 @@ True False - + True True 2 3 digits + False @@ -146,12 +151,13 @@ - + True True 2 3 digits + False @@ -172,12 +178,13 @@ - + True True 4 5 digits + False @@ -229,12 +236,12 @@ 12 5 - + True False - Мужской - Женский + Мужской + Женский