Добавление пациента с проверкой заполнения полей и фильтр списка
This commit is contained in:
parent
5a4b23cf2d
commit
0bf880cab7
|
@ -172,3 +172,4 @@ local.properties
|
||||||
.scala_dependencies
|
.scala_dependencies
|
||||||
.worksheet
|
.worksheet
|
||||||
|
|
||||||
|
.project
|
||||||
|
|
135
app.py
135
app.py
|
@ -1,21 +1,39 @@
|
||||||
import gi
|
import gi
|
||||||
import os
|
import os
|
||||||
gi.require_version('Gtk', '3.0')
|
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')
|
resource_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'res')
|
||||||
ui_dir = os.path.join(resource_dir, 'ui')
|
ui_dir = os.path.join(resource_dir, 'ui')
|
||||||
main_win_file = os.path.join(ui_dir, 'main_win.glade')
|
main_win_file = os.path.join(ui_dir, 'main_win.glade')
|
||||||
new_patient_win_file = os.path.join(ui_dir, 'new_patient_win.glade')
|
new_patient_win_file = os.path.join(ui_dir, 'new_patient_win.glade')
|
||||||
|
|
||||||
def create_new_patient_win():
|
builder = Gtk.Builder()
|
||||||
class NewPatienWinHandler:
|
|
||||||
pass
|
class PatientFilter:
|
||||||
b = Gtk.Builder()
|
def __init__(self):
|
||||||
b.add_from_file(new_patient_win_file)
|
self.reset()
|
||||||
b.connect_signals(NewPatienWinHandler())
|
def filter(self, query):
|
||||||
w = b.get_object('new_patient_window')
|
if query != self.fstr:
|
||||||
return w
|
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:
|
class MainWinHandler:
|
||||||
def main_win_close(self, *args):
|
def main_win_close(self, *args):
|
||||||
|
@ -23,10 +41,107 @@ class MainWinHandler:
|
||||||
def show_new_patient_win(self, button):
|
def show_new_patient_win(self, button):
|
||||||
new_patient_win = create_new_patient_win()
|
new_patient_win = create_new_patient_win()
|
||||||
new_patient_win.show_all()
|
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.add_from_file(main_win_file)
|
||||||
builder.connect_signals(MainWinHandler())
|
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 = builder.get_object('main_window')
|
||||||
main_win.show_all()
|
main_win.show_all()
|
||||||
|
|
66
mods/db.py
66
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
|
||||||
|
])
|
|
@ -138,7 +138,7 @@
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkListBox">
|
<object class="GtkListBox" id="patient_list">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
</object>
|
</object>
|
||||||
|
@ -154,12 +154,13 @@
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="search_mode_enabled">True</property>
|
<property name="search_mode_enabled">True</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkSearchEntry">
|
<object class="GtkSearchEntry" id="patient_filter">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="primary_icon_name">edit-find-symbolic</property>
|
<property name="primary_icon_name">edit-find-symbolic</property>
|
||||||
<property name="primary_icon_activatable">False</property>
|
<property name="primary_icon_activatable">False</property>
|
||||||
<property name="primary_icon_sensitive">False</property>
|
<property name="primary_icon_sensitive">False</property>
|
||||||
|
<signal name="changed" handler="patient_filter_changed" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
<property name="modal">True</property>
|
<property name="modal">True</property>
|
||||||
<property name="default_width">600</property>
|
<property name="default_width">600</property>
|
||||||
<property name="default_height">500</property>
|
<property name="default_height">500</property>
|
||||||
|
<property name="type_hint">dialog</property>
|
||||||
|
<property name="skip_taskbar_hint">True</property>
|
||||||
|
<property name="skip_pager_hint">True</property>
|
||||||
<child type="titlebar">
|
<child type="titlebar">
|
||||||
<object class="GtkHeaderBar">
|
<object class="GtkHeaderBar">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
@ -22,6 +25,7 @@
|
||||||
<property name="use_stock">True</property>
|
<property name="use_stock">True</property>
|
||||||
<property name="image_position">top</property>
|
<property name="image_position">top</property>
|
||||||
<property name="always_show_image">True</property>
|
<property name="always_show_image">True</property>
|
||||||
|
<signal name="clicked" handler="save_patient" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
@ -50,7 +54,7 @@
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
<property name="spacing">5</property>
|
<property name="spacing">5</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkEntry" id="patient_sn">
|
<object class="GtkEntry" id="last_name">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="placeholder_text" translatable="yes">Фамилия</property>
|
<property name="placeholder_text" translatable="yes">Фамилия</property>
|
||||||
|
@ -62,7 +66,7 @@
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkEntry" id="patient_given_name">
|
<object class="GtkEntry" id="first_name">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="placeholder_text" translatable="yes">Имя</property>
|
<property name="placeholder_text" translatable="yes">Имя</property>
|
||||||
|
@ -74,7 +78,7 @@
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkEntry" id="patient_middle_name">
|
<object class="GtkEntry" id="middle_name">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="placeholder_text" translatable="yes">Отчество</property>
|
<property name="placeholder_text" translatable="yes">Отчество</property>
|
||||||
|
@ -120,12 +124,13 @@
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkEntry">
|
<object class="GtkEntry" id="birth_day">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="max_length">2</property>
|
<property name="max_length">2</property>
|
||||||
<property name="width_chars">3</property>
|
<property name="width_chars">3</property>
|
||||||
<property name="input_purpose">digits</property>
|
<property name="input_purpose">digits</property>
|
||||||
|
<signal name="changed" handler="only_digits" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
|
@ -146,12 +151,13 @@
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkEntry">
|
<object class="GtkEntry" id="birth_month">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="max_length">2</property>
|
<property name="max_length">2</property>
|
||||||
<property name="width_chars">3</property>
|
<property name="width_chars">3</property>
|
||||||
<property name="input_purpose">digits</property>
|
<property name="input_purpose">digits</property>
|
||||||
|
<signal name="changed" handler="only_digits" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
|
@ -172,12 +178,13 @@
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkEntry">
|
<object class="GtkEntry" id="birth_year">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="max_length">4</property>
|
<property name="max_length">4</property>
|
||||||
<property name="width_chars">5</property>
|
<property name="width_chars">5</property>
|
||||||
<property name="input_purpose">digits</property>
|
<property name="input_purpose">digits</property>
|
||||||
|
<signal name="changed" handler="only_digits" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
|
@ -229,12 +236,12 @@
|
||||||
<property name="left_padding">12</property>
|
<property name="left_padding">12</property>
|
||||||
<property name="right_padding">5</property>
|
<property name="right_padding">5</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkComboBoxText">
|
<object class="GtkComboBoxText" id="gender">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<items>
|
<items>
|
||||||
<item id="gender_male" translatable="yes">Мужской</item>
|
<item id="male" translatable="yes">Мужской</item>
|
||||||
<item id="gender_female" translatable="yes">Женский</item>
|
<item id="female" translatable="yes">Женский</item>
|
||||||
</items>
|
</items>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
|
Loading…
Reference in New Issue