Primo commit

This commit is contained in:
gbiotti 2019-05-07 23:46:02 +02:00
parent 9667c241c1
commit 67de057e56
15 changed files with 1715 additions and 0 deletions

14
.editorconfig Normal file
View File

@ -0,0 +1,14 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# 2 space indentation
[*.py]
indent_style = space
indent_size = 4

8
.flake8 Normal file
View File

@ -0,0 +1,8 @@
[flake8]
# ignore =
# # indentation is not a multiple of four,
# E111,E114,
# # visually indented line with same indent as next logical line,
# E129
max-line-length=80

193
.gitignore vendored Normal file
View File

@ -0,0 +1,193 @@
# virtual env
.venv/
# Created by https://www.gitignore.io/api/emacs,python,visualstudiocode
# Edit at https://www.gitignore.io/?templates=emacs,python,visualstudiocode
### Emacs ###
# -*- mode: gitignore; -*-
*~
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*
# Org-mode
.org-id-locations
*_archive
# flymake-mode
*_flymake.*
# eshell files
/eshell/history
/eshell/lastdir
# elpa packages
/elpa/
# reftex files
*.rel
# AUCTeX auto folder
/auto/
# cask packages
.cask/
dist/
# Flycheck
flycheck_*.el
# server auth directory
/server/
# projectiles files
.projectile
# directory configuration
.dir-locals.el
# network security
/network-security.data
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that dont work, or not
# install all needed dependencies.
#Pipfile.lock
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
# End of https://www.gitignore.io/api/emacs,python,visualstudiocode

6
.style.yapf Normal file
View File

@ -0,0 +1,6 @@
[style]
# YAPF uses the chromium style
# BASED_ON_STYLE = pep8
BASED_ON_STYLE = google
SPACES_BEFORE_COMMENT = 4
# SPLIT_BEFORE_LOGICAL_OPERATOR = true

3
const.py Normal file
View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
MESSAGE_BOX_TITLE = "GOLEM-Iscritti"

30
create_db.sql Normal file
View File

@ -0,0 +1,30 @@
--
-- File generated with SQLiteStudio v3.2.1 on lun mag 6 20:53:06 2019
--
-- Text encoding used: UTF-8
--
PRAGMA foreign_keys = off;
BEGIN TRANSACTION;
-- Table: iscritti
CREATE TABLE iscritti (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nome VARCHAR (50) NOT NULL,
cognome VARCHAR (50) NOT NULL,
data_nascita DATE NOT NULL,
comune_nascita VARCHAR (255) NOT NULL,
email VARCHAR (255) NOT NULL,
occupazione VARCHAR (50),
fonte VARCHAR (255),
data_firma DATE NOT NULL,
firma BLOB NOT NULL
);
-- Table: sqlite_sequence
CREATE TABLE sqlite_sequence (
name,
seq
);
COMMIT TRANSACTION;
PRAGMA foreign_keys = on;

622
datepicker.py Normal file
View File

@ -0,0 +1,622 @@
# Geraldo 20190506
# File originale nel repository di ActiveState su GitBub
# https://github.com/ActiveState/code/tree/master/recipes/Python/580725_Tkinter_Datepicker_like_jQuery_UI
# Log modifiche rispetto all'originale:
# -------------------------------------
# 20190506
# - Trim trailing space
# - Code style yapf->PEP8
# ----------------------------------------------------------------------------------------------------------
# Author: Miguel Martinez Lopez
#
# Version: 1.0.7
#
# Uncomment the next line to see my email
# print("Author's email: %s"%"61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex")) # noqa
"""
These are the default bindings:
Click button 1 on entry: Show calendar
Click button 1 outsite calendar and entry: Hide calendar
Escape: Hide calendar
CTRL + PAGE UP: Move to the previous month.
CTRL + PAGE DOWN: Move to the next month.
CTRL + SHIFT + PAGE UP: Move to the previous year.
CTRL + SHIFT + PAGE DOWN: Move to the next year.
CTRL + LEFT: Move to the previous day.
CTRL + RIGHT: Move to the next day.
CTRL + UP: Move to the previous week.
CTRL + DOWN: Move to the next week.
CTRL + END: Close the datepicker and erase the date.
CTRL + HOME: Move to the current month.
CTRL + SPACE: Show date on calendar
CTRL + Return: Set current selection to entry
"""
import calendar
import datetime
try:
import Tkinter
import tkFont
import ttk
from Tkconstants import CENTER, LEFT, N, E, W, S
from Tkinter import StringVar
except ImportError: # py3k
import tkinter as Tkinter
import tkinter.font as tkFont
import tkinter.ttk as ttk
from tkinter.constants import CENTER, LEFT, N, E, W, S
from tkinter import StringVar
def get_calendar(locale, fwday):
# instantiate proper calendar class
if locale is None:
return calendar.TextCalendar(fwday)
else:
return calendar.LocaleTextCalendar(fwday, locale)
class Calendar(ttk.Frame):
datetime = calendar.datetime.datetime
timedelta = calendar.datetime.timedelta
def __init__(self,
master=None,
year=None,
month=None,
firstweekday=calendar.MONDAY,
locale=None,
activebackground='#b1dcfb',
activeforeground='black',
selectbackground='#003eff',
selectforeground='white',
command=None,
borderwidth=1,
relief="solid",
on_click_month_button=None):
"""
WIDGET OPTIONS
locale, firstweekday, year, month, selectbackground,
selectforeground, activebackground, activeforeground,
command, borderwidth, relief, on_click_month_button
"""
if year is None:
year = self.datetime.now().year
if month is None:
month = self.datetime.now().month
self._selected_date = None
self._sel_bg = selectbackground
self._sel_fg = selectforeground
self._act_bg = activebackground
self._act_fg = activeforeground
self.on_click_month_button = on_click_month_button
self._selection_is_visible = False
self._command = command
ttk.Frame.__init__(
self, master, borderwidth=borderwidth, relief=relief)
self.bind("<FocusIn>",
lambda event: self.event_generate('<<DatePickerFocusIn>>'))
self.bind("<FocusOut>",
lambda event: self.event_generate('<<DatePickerFocusOut>>'))
self._cal = get_calendar(locale, firstweekday)
# custom ttk styles
style = ttk.Style()
style.layout('L.TButton', ([('Button.focus', {
'children': [('Button.leftarrow', None)]
})]))
style.layout('R.TButton', ([('Button.focus', {
'children': [('Button.rightarrow', None)]
})]))
self._font = tkFont.Font()
self._header_var = StringVar()
# header frame and its widgets
hframe = ttk.Frame(self)
lbtn = ttk.Button(
hframe, style='L.TButton', command=self._on_press_left_button)
lbtn.pack(side=LEFT)
self._header = ttk.Label(
hframe, width=15, anchor=CENTER, textvariable=self._header_var)
self._header.pack(side=LEFT, padx=12)
rbtn = ttk.Button(
hframe, style='R.TButton', command=self._on_press_right_button)
rbtn.pack(side=LEFT)
hframe.grid(columnspan=7, pady=4)
self._day_labels = {}
days_of_the_week = self._cal.formatweekheader(3).split()
for i, day_of_the_week in enumerate(days_of_the_week):
Tkinter.Label(
self, text=day_of_the_week, background='grey90').grid(
row=1, column=i, sticky=N + E + W + S)
for i in range(6):
for j in range(7):
self._day_labels[i, j] = label = Tkinter.Label(
self, background="white")
label.grid(row=i + 2, column=j, sticky=N + E + W + S)
label.bind("<Enter>",
lambda event: event.widget.configure(
background=self._act_bg,
foreground=self._act_fg))
label.bind(
"<Leave>",
lambda event: event.widget.configure(background="white"))
label.bind("<1>", self._pressed)
# adjust its columns width
font = tkFont.Font()
maxwidth = max(font.measure(text) for text in days_of_the_week)
for i in range(7):
self.grid_columnconfigure(i, minsize=maxwidth, weight=1)
self._year = None
self._month = None
# insert dates in the currently empty calendar
self._build_calendar(year, month)
def _build_calendar(self, year, month):
if not (self._year == year and self._month == month):
self._year = year
self._month = month
# update header text (Month, YEAR)
header = self._cal.formatmonthname(year, month, 0)
self._header_var.set(header.title())
# update calendar shown dates
cal = self._cal.monthdayscalendar(year, month)
for i in range(len(cal)):
week = cal[i]
fmt_week = [('%02d' % day) if day else '' for day in week]
for j, day_number in enumerate(fmt_week):
self._day_labels[i, j]["text"] = day_number
if len(cal) < 6:
for j in range(7):
self._day_labels[5, j]["text"] = ""
if (self._selected_date is not None
and self._selected_date.year == self._year
and self._selected_date.month == self._month):
self._show_selection()
def _find_label_coordinates(self, date):
first_weekday_of_the_month = (date.weekday() - date.day) % 7
return divmod(
(first_weekday_of_the_month - self._cal.firstweekday) % 7 +
date.day, 7)
def _show_selection(self):
"""Show a new selection."""
i, j = self._find_label_coordinates(self._selected_date)
label = self._day_labels[i, j]
label.configure(background=self._sel_bg, foreground=self._sel_fg)
label.unbind("<Enter>")
label.unbind("<Leave>")
self._selection_is_visible = True
def _clear_selection(self):
"""Show a new selection."""
i, j = self._find_label_coordinates(self._selected_date)
label = self._day_labels[i, j]
label.configure(background="white", foreground="black")
label.bind("<Enter>",
lambda event: event.widget.configure(
background=self._act_bg, foreground=self._act_fg))
label.bind(
"<Leave>",
lambda event: event.widget.configure(background="white"))
self._selection_is_visible = False
# Callback
def _pressed(self, evt):
"""Clicked somewhere in the calendar."""
text = evt.widget["text"]
if text == "":
return
day_number = int(text)
new_selected_date = datetime.datetime(self._year, self._month,
day_number)
if self._selected_date != new_selected_date:
if self._selected_date is not None:
self._clear_selection()
self._selected_date = new_selected_date
self._show_selection()
if self._command:
self._command(self._selected_date)
def _on_press_left_button(self):
self.prev_month()
if self.on_click_month_button is not None:
self.on_click_month_button()
def _on_press_right_button(self):
self.next_month()
if self.on_click_month_button is not None:
self.on_click_month_button()
def select_prev_day(self):
"""Updated calendar to show the previous day."""
if self._selected_date is None:
self._selected_date = datetime.datetime(self._year, self._month, 1)
else:
self._clear_selection()
self._selected_date = self._selected_date - self.timedelta(days=1)
self._build_calendar(
self._selected_date.year,
self._selected_date.month) # reconstruct calendar
def select_next_day(self):
"""Update calendar to show the next day."""
if self._selected_date is None:
self._selected_date = datetime.datetime(self._year, self._month, 1)
else:
self._clear_selection()
self._selected_date = self._selected_date + self.timedelta(days=1)
self._build_calendar(
self._selected_date.year,
self._selected_date.month) # reconstruct calendar
def select_prev_week_day(self):
"""Updated calendar to show the previous week."""
if self._selected_date is None:
self._selected_date = datetime.datetime(self._year, self._month, 1)
else:
self._clear_selection()
self._selected_date = self._selected_date - self.timedelta(days=7)
self._build_calendar(
self._selected_date.year,
self._selected_date.month) # reconstruct calendar
def select_next_week_day(self):
"""Update calendar to show the next week."""
if self._selected_date is None:
self._selected_date = datetime.datetime(self._year, self._month, 1)
else:
self._clear_selection()
self._selected_date = self._selected_date + self.timedelta(days=7)
self._build_calendar(
self._selected_date.year,
self._selected_date.month) # reconstruct calendar
def select_current_date(self):
"""Update calendar to current date."""
if self._selection_is_visible:
self._clear_selection()
self._selected_date = datetime.datetime.now()
self._build_calendar(self._selected_date.year,
self._selected_date.month)
def prev_month(self):
"""Updated calendar to show the previous week."""
if self._selection_is_visible:
self._clear_selection()
date = self.datetime(self._year, self._month,
1) - self.timedelta(days=1)
self._build_calendar(date.year, date.month) # reconstuct calendar
def next_month(self):
"""Update calendar to show the next month."""
if self._selection_is_visible:
self._clear_selection()
date = self.datetime(self._year, self._month, 1) + self.timedelta(
days=calendar.monthrange(self._year, self._month)[1] + 1)
self._build_calendar(date.year, date.month) # reconstuct calendar
def prev_year(self):
"""Updated calendar to show the previous year."""
if self._selection_is_visible:
self._clear_selection()
self._build_calendar(self._year - 1,
self._month) # reconstruct calendar
def next_year(self):
"""Update calendar to show the next year."""
if self._selection_is_visible:
self._clear_selection()
self._build_calendar(self._year + 1,
self._month) # reconstruct calendar
def get_selection(self):
"""Return a datetime representing the current selected date."""
return self._selected_date
selection = get_selection
def set_selection(self, date):
"""Set the selected date."""
if self._selected_date is not None and self._selected_date != date:
self._clear_selection()
self._selected_date = date
self._build_calendar(date.year, date.month) # reconstruct calendar
# see this URL for date format information:
# https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior
class Datepicker(ttk.Entry):
def __init__(self,
master,
entrywidth=None,
entrystyle=None,
datevar=None,
dateformat="%Y-%m-%d",
onselect=None,
firstweekday=calendar.MONDAY,
locale=None,
activebackground='#b1dcfb',
activeforeground='black',
selectbackground='#003eff',
selectforeground='white',
borderwidth=1,
relief="solid"):
if datevar is not None:
self.date_var = datevar
else:
self.date_var = Tkinter.StringVar()
entry_config = {}
if entrywidth is not None:
entry_config["width"] = entrywidth
if entrystyle is not None:
entry_config["style"] = entrystyle
ttk.Entry.__init__(
self, master, textvariable=self.date_var, **entry_config)
self.date_format = dateformat
self._is_calendar_visible = False
self._on_select_date_command = onselect
self.calendar_frame = Calendar(
self.winfo_toplevel(),
firstweekday=firstweekday,
locale=locale,
activebackground=activebackground,
activeforeground=activeforeground,
selectbackground=selectbackground,
selectforeground=selectforeground,
command=self._on_selected_date,
on_click_month_button=lambda: self.focus())
self.bind_all("<1>", self._on_click, "+")
self.bind("<FocusOut>", lambda event: self._on_entry_focus_out())
self.bind("<Escape>", lambda event: self.hide_calendar())
self.calendar_frame.bind("<<DatePickerFocusOut>>",
lambda event: self._on_calendar_focus_out())
# CTRL + PAGE UP: Move to the previous month.
self.bind("<Control-Prior>",
lambda event: self.calendar_frame.prev_month())
# CTRL + PAGE DOWN: Move to the next month.
self.bind("<Control-Next>",
lambda event: self.calendar_frame.next_month())
# CTRL + SHIFT + PAGE UP: Move to the previous year.
self.bind("<Control-Shift-Prior>",
lambda event: self.calendar_frame.prev_year())
# CTRL + SHIFT + PAGE DOWN: Move to the next year.
self.bind("<Control-Shift-Next>",
lambda event: self.calendar_frame.next_year())
# CTRL + LEFT: Move to the previous day.
self.bind("<Control-Left>",
lambda event: self.calendar_frame.select_prev_day())
# CTRL + RIGHT: Move to the next day.
self.bind("<Control-Right>",
lambda event: self.calendar_frame.select_next_day())
# CTRL + UP: Move to the previous week.
self.bind("<Control-Up>",
lambda event: self.calendar_frame.select_prev_week_day())
# CTRL + DOWN: Move to the next week.
self.bind("<Control-Down>",
lambda event: self.calendar_frame.select_next_week_day())
# CTRL + END: Close the datepicker and erase the date.
self.bind("<Control-End>", lambda event: self.erase())
# CTRL + HOME: Move to the current month.
self.bind("<Control-Home>",
lambda event: self.calendar_frame.select_current_date())
# CTRL + SPACE: Show date on calendar
self.bind("<Control-space>",
lambda event: self.show_date_on_calendar())
# CTRL + Return: Set to entry current selection
self.bind("<Control-Return>",
lambda event: self.set_date_from_calendar())
def set_date_from_calendar(self):
if self.is_calendar_visible:
selected_date = self.calendar_frame.selection()
if selected_date is not None:
self.date_var.set(selected_date.strftime(self.date_format))
if self._on_select_date_command is not None:
self._on_select_date_command(selected_date)
self.hide_calendar()
@property
def current_text(self):
return self.date_var.get()
@current_text.setter
def current_text(self, text):
return self.date_var.set(text)
@property
def current_date(self):
try:
date = datetime.datetime.strptime(self.date_var.get(),
self.date_format)
return date
except ValueError:
return None
@current_date.setter
def current_date(self, date):
self.date_var.set(date.strftime(self.date_format))
@property
def is_valid_date(self):
if self.current_date is None:
return False
else:
return True
def show_date_on_calendar(self):
date = self.current_date
if date is not None:
self.calendar_frame.set_selection(date)
self.show_calendar()
def show_calendar(self):
if not self._is_calendar_visible:
self.calendar_frame.place(in_=self, relx=0, rely=1)
self.calendar_frame.lift()
self._is_calendar_visible = True
def hide_calendar(self):
if self._is_calendar_visible:
self.calendar_frame.place_forget()
self._is_calendar_visible = False
def erase(self):
self.hide_calendar()
self.date_var.set("")
@property
def is_calendar_visible(self):
return self._is_calendar_visible
def _on_entry_focus_out(self):
if not str(self.focus_get()).startswith(str(self.calendar_frame)):
self.hide_calendar()
def _on_calendar_focus_out(self):
if self.focus_get() != self:
self.hide_calendar()
def _on_selected_date(self, date):
self.date_var.set(date.strftime(self.date_format))
self.hide_calendar()
if self._on_select_date_command is not None:
self._on_select_date_command(date)
def _on_click(self, event):
str_widget = str(event.widget)
if str_widget == str(self):
if not self._is_calendar_visible:
self.show_date_on_calendar()
else:
if not str_widget.startswith(str(
self.calendar_frame)) and self._is_calendar_visible:
self.hide_calendar()
if __name__ == "__main__":
import sys
try:
from Tkinter import Tk, Frame, Label
except ImportError:
from tkinter import Tk, Frame, Label
root = Tk()
root.geometry("500x600")
main = Frame(root, pady=15, padx=15)
main.pack(expand=True, fill="both")
Label(main, justify="left", text=__doc__).pack(anchor="w", pady=(0, 15))
Datepicker(main).pack(anchor="w")
if 'win' not in sys.platform:
style = ttk.Style()
style.theme_use('clam')
root.mainloop()

53
db.py Normal file
View File

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
import io
import sqlite3
class Db():
def __init__(self):
self.dbfile = "iscritti.sqlite3"
def insert_new(self, iscritto):
firma_stream = io.BytesIO()
iscritto.img_firma.save(firma_stream, format="PNG")
firma_bytes = firma_stream.getvalue()
conn = sqlite3.connect(self.dbfile)
cur = conn.cursor()
# Come salvo il contenuto della Canvas
# nel db che non ce' una "textvar" associata?
par = (
iscritto.nome.get(),
iscritto.cognome.get(),
iscritto.data_nascita.get(),
iscritto.comune_nascita.get(),
iscritto.email.get(),
iscritto.occupazione.get(),
iscritto.fonte.get(),
iscritto.data_firma.get(),
firma_bytes)
sql = (
"INSERT INTO iscritti ("
"nome"
", cognome"
", data_nascita"
", comune_nascita"
", email"
", occupazione"
", fonte"
", data_firma"
", firma"
") values ("
"?" # nome
", ?" # cognome
", ?" # data_nascita
", ?" # comune_nascita
", ?" # email
", ?" # occupazione
", ?" # fonte
", ?" # data_firma
", ?" # firma
");")
cur.execute(sql, par)
conn.commit()
conn.close()

15
dev_requirements.txt Normal file
View File

@ -0,0 +1,15 @@
appdirs==1.4.3
attrs==19.1.0
autopep8==1.4.4
black==19.3b0
Click==7.0
entrypoints==0.3
flake8==3.7.7
jedi==0.13.3
mccabe==0.6.1
parso==0.4.0
pycodestyle==2.5.0
pyflakes==2.1.1
rope==0.14.0
toml==0.10.0
yapf==0.27.0

502
iscritti.py Normal file
View File

@ -0,0 +1,502 @@
# -*- coding: utf-8 -*-
# Per la generazione dei reports:
# - https://www.reportlab.com/
# - https://pythonhosted.org/PollyReports/tutorial.html
# - https://pbpython.com/pdf-reports.html
# Smart Card
# - https://pyscard.sourceforge.io/
# - https://www.mmxforge.net/index.php/sviluppo/python/item/9-lettura-dei-dati-della-tessera-sanitaria-con-python
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.messagebox as tkMessagebox
from locale import (setlocale, LC_ALL)
from PIL import Image
from PIL import ImageDraw
from PIL import ImageTk
from db import Db
from datepicker import Datepicker
from const import MESSAGE_BOX_TITLE
from iscritto import Iscritto
FRM_PADX = 5
FRM_PADY = 3
FRA_PADX = 5
FRA_PADY = 3
LBL_PADX = 3
LBL_PADY = 3
TXT_PADX = 5
TXT_PADY = 3
IMG_PADX = 5
IMG_PADY = 3
BTN_PADX = 5
BTN_PADY = 3
setlocale(LC_ALL, '')
class Iscritti():
def __init__(self, parent):
self.parent = parent
self.larghezza_firma = 800
self.altezza_firma = 300
self.img_firma_color_fg = "black"
self.img_firma_color_bg = "white"
self.img_firma_penwidth = 5
self.iscritto = Iscritto(self.larghezza_firma,
self.altezza_firma,
self.img_firma_color_fg,
self.img_firma_color_bg)
self.create_widgets()
parent.update()
return
def create_widgets(self):
self.configure_master()
self.crea_master_widgets()
return
def configure_master(self):
# Crea la finestra a tutto schermo
# (non funziona su Mint 19.1)
# self.parent.wm_state('zoomed')
self.parent.focus_set()
self.parent.title("GOLEM - Iscritti")
self.parent.rowconfigure(0, weight=1)
self.parent.columnconfigure(0, weight=1)
self.parent.protocol("WM_DELETE_WINDOW", self.esci)
return
def crea_master_widgets(self):
self.fra_master = ttk.Frame(self.parent)
row_number = 0
s1 = ttk.Separator(self.fra_master, orient=tk.HORIZONTAL)
s1.grid(row=row_number, column=0, sticky=tk.W + tk.E)
row_number += 1
self.crea_frame_pulsanti(self.fra_master, row_number)
self.fra_master.rowconfigure(row_number, weight=0)
row_number += 1
s2 = ttk.Separator(self.fra_master, orient=tk.HORIZONTAL)
s2.grid(row=row_number, column=0, sticky=tk.W + tk.E)
row_number += 1
self.crea_frame_dati(self.fra_master, row_number)
self.fra_master.rowconfigure(row_number, weight=1)
row_number += 1
self.fra_master.columnconfigure(0, weight=1)
self.fra_master.grid(row=0,
column=0,
padx=FRA_PADX,
pady=FRA_PADX,
sticky=tk.N + tk.W + tk.S + tk.E)
return
def crea_frame_pulsanti(self, parent, row_number):
self.fra_pulsanti = ttk.Frame(parent)
self.fra_pulsanti.grid(row=row_number,
column=0,
padx=FRA_PADX,
pady=FRA_PADX,
sticky=tk.N + tk.W + tk.S + tk.E)
child_row = 0
self.crea_widgets_pulsanti(self.fra_pulsanti, child_row)
self.fra_pulsanti.rowconfigure(0, weight=1)
child_row += 1
return
def crea_widgets_pulsanti(self, parent, row_number):
child_column = 0
self.btn_nuovo = ttk.Button(parent, text="Nuovo", command=self.nuovo)
self.btn_nuovo.grid(row=row_number,
column=child_column,
padx=BTN_PADX,
pady=BTN_PADX,
sticky=tk.N + tk.W + tk.S + tk.E)
parent.columnconfigure(child_column, weight=1)
# Funzionalita' ricerca da implementare (datagrid in tkinter???)
# child_column += 1
# self.btn_cerca = ttk.Button(parent, text="Cerca", command=self.cerca)
# self.btn_cerca.grid(row=row_number,
# column=child_column,
# padx=BTN_PADX,
# pady=BTN_PADX,
# sticky=tk.N + tk.W + tk.S + tk.E)
# parent.columnconfigure(child_column, weight=1)
child_column += 1
self.btn_salva = ttk.Button(parent, text="Salva", command=self.salva)
self.btn_salva.grid(row=row_number,
column=child_column,
padx=BTN_PADX,
pady=BTN_PADX,
sticky=tk.N + tk.W + tk.S + tk.E)
parent.columnconfigure(child_column, weight=1)
child_column += 1
self.btn_esci = ttk.Button(parent, text="Esci", command=self.esci)
self.btn_esci.grid(row=row_number,
column=child_column,
padx=BTN_PADX,
pady=BTN_PADX,
sticky=tk.N + tk.W + tk.S + tk.E)
parent.columnconfigure(child_column, weight=1)
return
def crea_frame_dati(self, parent, row_number):
self.fra_dati = ttk.Frame(parent)
self.fra_dati.grid(row=row_number,
column=0,
padx=FRA_PADX,
pady=FRA_PADX,
sticky=tk.N + tk.W + tk.S + tk.E)
child_row = 0
self.crea_widgets_id(self.fra_dati, child_row)
self.fra_dati.rowconfigure(child_row, weight=1)
child_row += 1
self.crea_widgets_cognome(self.fra_dati, child_row)
self.fra_dati.rowconfigure(child_row, weight=1)
child_row += 1
self.crea_widgets_nome(self.fra_dati, child_row)
self.fra_dati.rowconfigure(child_row, weight=1)
child_row += 1
self.crea_widgets_data_nascita(self.fra_dati, child_row)
self.fra_dati.rowconfigure(child_row, weight=1)
child_row += 1
self.crea_widgets_comune_nascita(self.fra_dati, child_row)
self.fra_dati.rowconfigure(child_row, weight=1)
child_row += 1
self.crea_widgets_email(self.fra_dati, child_row)
self.fra_dati.rowconfigure(child_row, weight=1)
child_row += 1
self.crea_widgets_occupazione(self.fra_dati, child_row)
self.fra_dati.rowconfigure(child_row, weight=1)
child_row += 1
self.crea_widgets_fonte(self.fra_dati, child_row)
self.fra_dati.rowconfigure(child_row, weight=1)
child_row += 1
self.crea_widgets_data_firma(self.fra_dati, child_row)
self.fra_dati.rowconfigure(child_row, weight=1)
child_row += 1
self.crea_widgets_firma(self.fra_dati, child_row)
self.fra_dati.rowconfigure(child_row, weight=1)
child_row += 1
self.fra_dati.columnconfigure(0, weight=0)
self.fra_dati.columnconfigure(1, weight=1)
self.fra_dati.columnconfigure(2, weight=1)
return
def crea_widgets_id(self, parent, row_number):
self.lbl_id = ttk.Label(parent, text="Id", anchor=tk.W)
self.lbl_id.grid(row=row_number,
column=0,
padx=LBL_PADX,
pady=LBL_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
self.txt_id = ttk.Entry(parent,
textvariable=self.iscritto.id,
state="readonly")
self.txt_id.grid(row=row_number,
column=1,
columnspan=2,
padx=TXT_PADX,
pady=TXT_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
return
def crea_widgets_cognome(self, parent, row_number):
self.lbl_cognome = ttk.Label(parent, text="Cognome", anchor=tk.W)
self.lbl_cognome.grid(row=row_number,
column=0,
padx=LBL_PADX,
pady=LBL_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
self.txt_cognome = ttk.Entry(parent, textvariable=self.iscritto.cognome)
self.txt_cognome.grid(row=row_number,
column=1,
columnspan=2,
padx=TXT_PADX,
pady=TXT_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
return
def crea_widgets_nome(self, parent, row_number):
self.lbl_nome = ttk.Label(parent, text="Nome", anchor=tk.W)
self.lbl_nome.grid(row=row_number,
column=0,
padx=LBL_PADX,
pady=LBL_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
self.txt_nome = ttk.Entry(parent, textvariable=self.iscritto.nome)
self.txt_nome.grid(row=row_number,
column=1,
columnspan=2,
padx=TXT_PADX,
pady=TXT_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
return
def crea_widgets_data_nascita(self, parent, row_number):
self.lbl_data_nascita = ttk.Label(parent,
text="Data di nascita",
anchor=tk.W)
self.lbl_data_nascita.grid(row=row_number,
column=0,
padx=LBL_PADX,
pady=LBL_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
self.txt_data_nascita = Datepicker(parent,
dateformat="%x",
datevar=self.iscritto.data_nascita)
self.txt_data_nascita.grid(row=row_number,
column=1,
columnspan=2,
padx=TXT_PADX,
pady=TXT_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
return
def crea_widgets_comune_nascita(self, parent, row_number):
self.lbl_comune_nascita = ttk.Label(parent,
text="Comune di nascita",
anchor=tk.W)
self.lbl_comune_nascita.grid(row=row_number,
column=0,
padx=LBL_PADX,
pady=LBL_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
self.txt_comune_nascita = ttk.Entry(
parent, textvariable=self.iscritto.comune_nascita)
self.txt_comune_nascita.grid(row=row_number,
column=1,
columnspan=2,
padx=TXT_PADX,
pady=TXT_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
return
def crea_widgets_email(self, parent, row_number):
self.lbl_email = ttk.Label(parent, text="E-mail", anchor=tk.W)
self.lbl_email.grid(row=row_number,
column=0,
padx=LBL_PADX,
pady=LBL_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
self.txt_email = ttk.Entry(parent, textvariable=self.iscritto.email)
self.txt_email.grid(row=row_number,
column=1,
columnspan=2,
padx=TXT_PADX,
pady=TXT_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
return
def crea_widgets_occupazione(self, parent, row_number):
self.lbl_occupazione = ttk.Label(parent,
text="Occupazione",
anchor=tk.W)
self.lbl_occupazione.grid(row=row_number,
column=0,
padx=LBL_PADX,
pady=LBL_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
self.txt_occupazione = ttk.Entry(parent,
textvariable=self.iscritto.occupazione)
self.txt_occupazione.grid(row=row_number,
column=1,
columnspan=2,
padx=TXT_PADX,
pady=TXT_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
return
def crea_widgets_fonte(self, parent, row_number):
self.lbl_fonte = ttk.Label(parent, text="Fonte", anchor=tk.W)
self.lbl_fonte.grid(row=row_number,
column=0,
padx=LBL_PADX,
pady=LBL_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
self.txt_fonte = ttk.Entry(parent, textvariable=self.iscritto.fonte)
self.txt_fonte.grid(row=row_number,
column=1,
columnspan=2,
padx=TXT_PADX,
pady=TXT_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
return
def crea_widgets_data_firma(self, parent, row_number):
self.lbl_data_firma = ttk.Label(parent, text="Data firma", anchor=tk.W)
self.lbl_data_firma.grid(row=row_number,
column=0,
padx=LBL_PADX,
pady=LBL_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
self.txt_data_firma = Datepicker(parent,
dateformat="%x",
datevar=self.iscritto.data_firma)
self.txt_data_firma.grid(row=row_number,
column=1,
columnspan=2,
padx=TXT_PADX,
pady=TXT_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
return
def crea_widgets_firma(self, parent, row_number):
# https://www.youtube.com/watch?v=rhvJ_M218EQ
self.img_firma_old_x = None
self.img_firma_old_y = None
self.lbl_firma = ttk.Label(parent, text="Firma", anchor=tk.N + tk.W)
self.lbl_firma.grid(row=row_number,
column=0,
padx=LBL_PADX,
pady=LBL_PADY,
sticky=tk.N + tk.S + tk.E + tk.W)
larghezza = self.larghezza_firma
altezza = self.altezza_firma
self.cv_firma = tk.Canvas(
parent,
width=larghezza,
height=altezza,
border=1,
background=self.img_firma_color_bg,
relief=tk.SUNKEN)
self.cv_firma.bind("<B1-Motion>", self.img_firma_paint)
self.cv_firma.bind("<ButtonRelease-1>", self.img_firma_reset)
self.cv_firma.grid(row=row_number,
column=1,
padx=IMG_PADX,
pady=IMG_PADY,
sticky=tk.N + tk.W)
self.draw_frirma = ImageDraw.Draw(self.iscritto.img_firma)
self.fra_pulsanti_firma = ttk.Frame(parent)
self.fra_pulsanti_firma.columnconfigure(0, weight=1)
self.fra_pulsanti_firma.grid(row=row_number,
column=2,
padx=FRA_PADX,
pady=FRA_PADX,
sticky=tk.N + tk.W + tk.S + tk.E)
self.btn_firma_clear = ttk.Button(self.fra_pulsanti_firma,
text="Pulisci")
self.btn_firma_clear.bind("<Button-1>", self.img_firma_clear)
self.btn_firma_clear.grid(row=0,
column=0,
padx=BTN_PADX,
pady=BTN_PADX,
sticky=tk.N + tk.W + tk.S + tk.E)
return
def img_firma_paint(self, e):
if self.img_firma_old_x and self.img_firma_old_y:
self.cv_firma.create_line(self.img_firma_old_x,
self.img_firma_old_y,
e.x,
e.y,
width=self.img_firma_penwidth,
fill=self.img_firma_color_fg,
capstyle=tk.ROUND,
smooth=True)
xy = [self.img_firma_old_x, self.img_firma_old_y, e.x, e.y]
self.draw_frirma.line(xy,
width=self.img_firma_penwidth,
fill=self.img_firma_color_fg)
self.img_firma_old_x = e.x
self.img_firma_old_y = e.y
return
def img_firma_reset(self, e):
self.img_firma_old_x = None
self.img_firma_old_y = None
return
def img_firma_clear(self, e):
self.cv_firma.delete("all")
return
def nuovo(self):
self.iscritto.new()
# # Alla canvas non e' associata nessuna tkinger.Variable()
# # quindi lavoro direttamente sul widget
self.cv_firma.delete("all")
self.draw_frirma = ImageDraw.Draw(self.iscritto.img_firma)
return
def cerca(self):
tkMessagebox.showinfo(MESSAGE_BOX_TITLE, "valido, salvo!")
pass
def salva(self):
if self.iscritto.is_valid():
salva = tkMessagebox.askyesno(MESSAGE_BOX_TITLE,
"Convermi salvataggio?")
if salva:
db = Db()
db.insert_new(self.iscritto)
self.nuovo()
else:
tkMessagebox.showinfo(MESSAGE_BOX_TITLE,
"non valido e quindi non posso salvare!")
self.parent.update()
def esci(self):
if tkMessagebox.askyesno(MESSAGE_BOX_TITLE, "Esco?"):
self.parent.update()
self.parent.destroy()
return

BIN
iscritti.sqlite3 Normal file

Binary file not shown.

146
iscritto.py Normal file
View File

@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
import tkinter as tk
import tkinter.messagebox as tkMessagebox
from datetime import datetime
from PIL import Image
from const import MESSAGE_BOX_TITLE
class Iscritto():
def __init__(self, larghezza_firma, altezza_firma, img_firma_color_fg, img_firma_color_bg):
self.larghezza_firma = larghezza_firma
self.altezza_firma = altezza_firma
self.img_firma_color_fg = img_firma_color_fg
self.img_firma_color_bg = img_firma_color_bg
self.id = tk.StringVar()
self.cognome = tk.StringVar()
self.nome = tk.StringVar()
self.data_nascita = tk.StringVar()
self.comune_nascita = tk.StringVar()
self.email = tk.StringVar()
self.occupazione = tk.StringVar()
self.fonte = tk.StringVar()
self.data_firma = tk.StringVar()
# In Image.new(), se non indicato il "color" e' nero
# "L" = 8 bit - B/N
self.img_firma = Image.new("L", (self.larghezza_firma, self.altezza_firma), self.img_firma_color_bg)
return
def new(self):
self.id.set("")
self.cognome.set("")
self.nome.set("")
self.data_nascita.set("")
self.comune_nascita.set("")
self.email.set("")
self.occupazione.set("")
self.fonte.set("")
self.data_firma.set("")
# In Image.new(), se non indicato il "color" e' nero
# "L" = 8 bit - B/N
self.img_firma = Image.new("L", (self.larghezza_firma, self.altezza_firma), self.img_firma_color_bg)
def is_valid(self):
is_valid = self.cognome_is_valid()
if is_valid:
is_valid = self.nome_is_valid()
if is_valid:
is_valid = self.data_nascita_is_valid()
if is_valid:
is_valid = self.comune_nascita_is_valid()
if is_valid:
is_valid = self.email_is_valid()
if is_valid:
is_valid = self.occupazione_is_valid()
if is_valid:
is_valid = self.fonte_is_valid()
if is_valid:
is_valid = self.data_firma_is_valid()
# Non si puo' convalidare la firma che e'
# una immagine
return is_valid
def cognome_is_valid(self):
is_valid = True
if len(str.rstrip(self.cognome.get())) == 0:
is_valid = False
messaggio = "'Cognome' e' obbligatorio"
tkMessagebox.showerror(MESSAGE_BOX_TITLE, messaggio)
return is_valid
def nome_is_valid(self):
is_valid = True
messaggio = ""
if len(str.rstrip(self.nome.get())) == 0:
is_valid = False
messaggio = "'Nome' e' obbligatorio"
tkMessagebox.showerror(MESSAGE_BOX_TITLE, messaggio)
return is_valid
def data_nascita_is_valid(self):
is_valid = True
messaggio = ""
if self.data_nascita.get() is None:
is_valid = False
messaggio = "'Data di nascita' e' obbligatorio"
elif len(str.rstrip(self.data_nascita.get())) == 0:
is_valid = False
messaggio = "'Data di nascita' e' obbligatorio"
else:
try:
datetime.strptime(self.data_nascita.get(), '%x')
except (ValueError):
is_valid = False
messaggio = "'Data di nascita' non valida"
if not is_valid:
tkMessagebox.showerror(MESSAGE_BOX_TITLE, messaggio)
return is_valid
def comune_nascita_is_valid(self):
is_valid = True
messaggio = ""
if len(str.rstrip(self.comune_nascita.get())) == 0:
is_valid = False
messaggio = "'Comune di nascita' e' obbligatorio"
tkMessagebox.showerror(MESSAGE_BOX_TITLE, messaggio)
return is_valid, messaggio
def email_is_valid(self):
is_valid = True
messaggio = ""
if len(str.rstrip(self.email.get())) == 0:
is_valid = False
messaggio = "'E-mail' e' obbligatorio"
tkMessagebox.showerror(MESSAGE_BOX_TITLE, messaggio)
return is_valid, messaggio
def occupazione_is_valid(self):
# Non obbligatorio
return True
def fonte_is_valid(self):
# Non obbligatorio
return True
def data_firma_is_valid(self):
is_valid = True
messaggio = ""
if self.data_nascita.get() is None:
is_valid = False
messaggio = "'Data firma' e' obbligatorio"
elif len(str.rstrip(self.data_nascita.get())) == 0:
is_valid = False
messaggio = "'Data firma' e' obbligatorio"
else:
try:
datetime.strptime(self.data_nascita.get(), '%x')
except (ValueError):
is_valid = False
messaggio = "'Data firma' non valida"
if not is_valid:
tkMessagebox.showerror(MESSAGE_BOX_TITLE, messaggio)
return is_valid

15
main.py Normal file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import tkinter as tk
from iscritti import Iscritti
def main():
root = tk.Tk()
Iscritti(root)
root.mainloop()
if __name__ == "__main__":
main()

102
pysc.py Normal file
View File

@ -0,0 +1,102 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# https://www.mmxforge.net/index.php/sviluppo/python/item/9-lettura-dei-dati-della-tessera-sanitaria-con-python
from smartcard.System import readers
import array
import string
import datetime
r = readers()
reader = r[0]
print("Sto usando: {}".format(reader.name))
connection = reader.createConnection()
connection.connect()
#Seleziona del MF
#CLS 00, istruzione A4 (seleziona file), P1 = P2 = 0 (seleziona per ID),
#Lc: 2, Data: 3F00 (id del MF)
SELECT_MF = [0x00, 0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00]
data, sw1, sw2 = connection.transmit(SELECT_MF)
#se tutto è andato a buon fine sw1 e sw2 contengono
#rispettivamente i valori 0x90 e 0x00 il corrispettivo del 200 in HTTP
#Seleziona del DF1...vedi sopra
SELECT_DF1 = [0x00, 0xA4, 0x00, 0x00, 0x02, 0x11, 0x00]
data, sw1, sw2 = connection.transmit(SELECT_DF1)
#Seleziona del file EF.Dati_personali... vedi sopra sopra
SELECT_EF_PERS = [0x00, 0xA4, 0x00, 0x00, 0x02, 0x11, 0x02]
data, sw1, sw2 = connection.transmit(SELECT_EF_PERS)
#leggiamo i dati
#CLS 00, istruzione B0 (leggi i dati binari contenuti nel file
READ_BIN = [0x00, 0xB0, 0x00, 0x00, 0x00, 0x00]
data, sw1, sw2 = connection.transmit(READ_BIN)
#data contiene i dati anagrafici in formato binario
#trasformiamo il tutto in una stringa
stringa_dati_personali = array.array('B', data).tostring()
dimensione = int(stringa_dati_personali[0:6],16)
print("Dimensione in byte dei dati: {}".format( dimensione))
numero_field = 0
prox_field_size = int(stringa_dati_personali[6:8], 16)
da = 8
a = da + prox_field_size
if prox_field_size > 0:
codice_emettitore = stringa_dati_personali[da:a]
print("Codice emettitore: {}".format(codice_emettitore.decode("utf-8")))
numero_field += 1
da = a
a +=2
prox_field_size = int(stringa_dati_personali[da:a], 16)
da=a
a += prox_field_size
if prox_field_size > 0:
data_rilascio_tessera = stringa_dati_personali[da:a]
year = int(data_rilascio_tessera[-4:])
month = int(data_rilascio_tessera[2:4])
day = int(data_rilascio_tessera[0:2])
dtm_data_rliascio = datetime.date(year, month, day)
# print(string("Data rilascio tessera: ", data_rilascio_tessera[0:2], "/", data_rilascio_tessera[2:4], "/", data_rilascio_tessera[-4:])
print("Data rilascio tessera: {}".format(dtm_data_rliascio))
numero_field += 1
da = a
a +=2
prox_field_size = int(stringa_dati_personali[da:a], 16)
da=a
a += prox_field_size
if prox_field_size > 0:
data_scadenza_tessera = stringa_dati_personali[da:a]
year = int(data_scadenza_tessera[-4:])
month = int(data_scadenza_tessera[2:4])
day = int(data_scadenza_tessera[0:2])
dtm_data_scadenza = datetime.date(year, month, day)
# print("Data scadenza tessera: ", data_scadenza_tessera[0:2]+"/"+data_scadenza_tessera[2:4]+"/"+data_scadenza_tessera[-4:])
print("Data scadenza tessera: {}".format(dtm_data_scadenza))
numero_field += 1
# da = a
# a +=2
# prox_field_size = int(stringa_dati_personali[da:a], 16)
# da=a
# a += prox_field_size
# if prox_field_size > 0:
# cognome = stringa_dati_personali[da:a]
# print("Cognome: {}".format(cognome))
# numero_field += 1
while a < dimensione:
da = a
a += 2
prox_field_size = int(stringa_dati_personali[da:a], 16)
da=a
a += prox_field_size
if prox_field_size > 0:
field = stringa_dati_personali[da:a]
print("Field#{}: {}".format(numero_field, field))
numero_field += 1

6
requirements.txt Normal file
View File

@ -0,0 +1,6 @@
Pillow==6.0.0
# Su Linux installare
# sudo apt install pcscd
# sudo apt install pcsc-tools
# sudo apt install python3-pyscard
# pyscard==1.9.8