voidcli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- void/__init__.py +1 -0
- void/controllers/__init__.py +1 -0
- void/controllers/activity.py +44 -0
- void/controllers/category.py +30 -0
- void/controllers/note.py +47 -0
- void/database/__init__.py +16 -0
- void/database/connection.py +59 -0
- void/database/init_tables.sql +47 -0
- void/main.py +153 -0
- void/models/__init__.py +1 -0
- void/models/activity.py +68 -0
- void/models/category.py +40 -0
- void/models/note.py +64 -0
- void/views/__init__.py +1 -0
- void/views/activity.py +247 -0
- void/views/collection.py +52 -0
- void/views/day_note.py +101 -0
- void/views/note.py +47 -0
- void/views/note_form.py +144 -0
- void/views/welcome.py +72 -0
- voidcli-0.1.0.dist-info/METADATA +77 -0
- voidcli-0.1.0.dist-info/RECORD +25 -0
- voidcli-0.1.0.dist-info/WHEEL +4 -0
- voidcli-0.1.0.dist-info/entry_points.txt +2 -0
- voidcli-0.1.0.dist-info/licenses/LICENSE +21 -0
void/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
""" VOID APP SRC CODE """
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Controllers module"""
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
|
|
3
|
+
from void.models.activity import Activity
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ActivityController:
|
|
7
|
+
|
|
8
|
+
def get_activities(self) -> list[tuple]:
|
|
9
|
+
with Activity() as ac:
|
|
10
|
+
raw_activities = ac.get_active_activities()
|
|
11
|
+
activities = []
|
|
12
|
+
for item in raw_activities:
|
|
13
|
+
activities.append((item['id'], item['activity'], item['category']))
|
|
14
|
+
return activities
|
|
15
|
+
|
|
16
|
+
def get_activities_by_category(self, category_id):
|
|
17
|
+
with Activity() as ac:
|
|
18
|
+
raw_activities = ac.get_activities_by_category(category_id)
|
|
19
|
+
activities = []
|
|
20
|
+
for item in raw_activities:
|
|
21
|
+
activities.append((item["id"], item['name']))
|
|
22
|
+
return activities
|
|
23
|
+
|
|
24
|
+
def create_activity(self, activity, category_id) -> bool:
|
|
25
|
+
"""Return False when the name is already used in that category."""
|
|
26
|
+
with Activity() as ac:
|
|
27
|
+
try:
|
|
28
|
+
ac.create_activity(activity, category_id)
|
|
29
|
+
except sqlite3.IntegrityError:
|
|
30
|
+
return False
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
def update_activity(self, activity_id, activity, category_id) -> bool:
|
|
34
|
+
"""Return False when the name is already used in that category."""
|
|
35
|
+
with Activity() as ac:
|
|
36
|
+
try:
|
|
37
|
+
ac.update_activity(activity_id, activity, category_id)
|
|
38
|
+
except sqlite3.IntegrityError:
|
|
39
|
+
return False
|
|
40
|
+
return True
|
|
41
|
+
|
|
42
|
+
def suspend_activity(self, activity_id):
|
|
43
|
+
with Activity() as ac:
|
|
44
|
+
ac.suspend_activity(activity_id)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
|
|
3
|
+
from void.models.category import Category
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CategoryController:
|
|
7
|
+
def get_categories_for_activities(self) -> list[tuple]:
|
|
8
|
+
with Category() as ca:
|
|
9
|
+
raw_categories = ca.get_active_categories_for_activities()
|
|
10
|
+
categories = []
|
|
11
|
+
for item in raw_categories:
|
|
12
|
+
categories.append((item['category'], item['id']))
|
|
13
|
+
return categories
|
|
14
|
+
|
|
15
|
+
def get_categories(self) -> list[tuple]:
|
|
16
|
+
with Category() as ca:
|
|
17
|
+
raw_categories = ca.get_active_categories()
|
|
18
|
+
categories = []
|
|
19
|
+
for item in raw_categories:
|
|
20
|
+
categories.append((item['category'],))
|
|
21
|
+
return categories
|
|
22
|
+
|
|
23
|
+
def create_category(self, category) -> bool:
|
|
24
|
+
"""Return False when a category with that name already exists."""
|
|
25
|
+
with Category() as ca:
|
|
26
|
+
try:
|
|
27
|
+
ca.create_category(category)
|
|
28
|
+
except sqlite3.IntegrityError:
|
|
29
|
+
return False
|
|
30
|
+
return True
|
void/controllers/note.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from void.controllers.activity import ActivityController
|
|
2
|
+
from void.controllers.category import CategoryController
|
|
3
|
+
from void.models.note import Note
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class NoteController:
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.category_ctrl = CategoryController()
|
|
10
|
+
self.activity_ctrl = ActivityController()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_activities_by_category(self):
|
|
14
|
+
active_categories = self.category_ctrl.get_categories_for_activities()
|
|
15
|
+
categories_and_activities = []
|
|
16
|
+
for cat in active_categories:
|
|
17
|
+
category, id = cat
|
|
18
|
+
activities = self.activity_ctrl.get_activities_by_category(category_id=id)
|
|
19
|
+
data = (id, category, activities)
|
|
20
|
+
categories_and_activities.append(data)
|
|
21
|
+
return categories_and_activities
|
|
22
|
+
|
|
23
|
+
def get_activities(self):
|
|
24
|
+
"""Every active activity as (id, name, category)."""
|
|
25
|
+
return self.activity_ctrl.get_activities()
|
|
26
|
+
|
|
27
|
+
def get_activity_total(self):
|
|
28
|
+
return len(self.get_activities())
|
|
29
|
+
|
|
30
|
+
def save_note(self, date_str, activities):
|
|
31
|
+
with Note() as note:
|
|
32
|
+
note_id = note.save_note(date_str)
|
|
33
|
+
for activity in activities:
|
|
34
|
+
activity_info, activity_note = activity
|
|
35
|
+
self.save_note_details(note_id, activity_info, activity_note)
|
|
36
|
+
|
|
37
|
+
def save_note_details(self, note_id, activity_info, activity_note):
|
|
38
|
+
with Note() as note:
|
|
39
|
+
note.save_note_detail(note_id, activity_info, activity_note)
|
|
40
|
+
|
|
41
|
+
def get_day_note(self, date_str):
|
|
42
|
+
with Note() as note:
|
|
43
|
+
return note.get_day_note(date_str)
|
|
44
|
+
|
|
45
|
+
def get_notes(self):
|
|
46
|
+
with Note() as note:
|
|
47
|
+
return note.get_notes()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Database module """
|
|
2
|
+
|
|
3
|
+
from void.database.connection import DB_PATH, Connection
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def initialize_database():
|
|
7
|
+
"""Create void.db and its schema the first time the app runs.
|
|
8
|
+
|
|
9
|
+
Does nothing once void.db already exists, so it's safe to call on
|
|
10
|
+
every startup.
|
|
11
|
+
"""
|
|
12
|
+
if DB_PATH.exists():
|
|
13
|
+
return
|
|
14
|
+
|
|
15
|
+
with Connection() as db:
|
|
16
|
+
db.create_tables()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
DB_PATH = Path(__file__).parent / "void.db"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Connection:
|
|
8
|
+
def __init__(self):
|
|
9
|
+
"""Connect to void.db and configure results as name-accessible rows."""
|
|
10
|
+
self.db_path = DB_PATH
|
|
11
|
+
self.tables_script = Path(__file__).parent / "init_tables.sql"
|
|
12
|
+
self.test_data_script = Path(__file__).parent / "test_data.sql"
|
|
13
|
+
|
|
14
|
+
self.connection = sqlite3.connect(self.db_path)
|
|
15
|
+
self.connection.row_factory = sqlite3.Row
|
|
16
|
+
self.cursor = self.connection.cursor()
|
|
17
|
+
|
|
18
|
+
def __enter__(self):
|
|
19
|
+
"""Allow `with Connection() as db:`; returns this instance."""
|
|
20
|
+
return self
|
|
21
|
+
|
|
22
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
23
|
+
"""Close the connection when the `with` block ends, even on error."""
|
|
24
|
+
self.close()
|
|
25
|
+
|
|
26
|
+
def create_tables(self):
|
|
27
|
+
"""Run init_tables.sql to create the schema. Safe to call more than
|
|
28
|
+
once, every statement uses IF NOT EXISTS."""
|
|
29
|
+
with open(self.tables_script) as f:
|
|
30
|
+
sql = f.read()
|
|
31
|
+
self.cursor.executescript(sql)
|
|
32
|
+
self.connection.commit()
|
|
33
|
+
|
|
34
|
+
def load_test_data(self):
|
|
35
|
+
"""Run test_data.sql to populate sample rows."""
|
|
36
|
+
with open(self.test_data_script) as f:
|
|
37
|
+
sql = f.read()
|
|
38
|
+
self.cursor.executescript(sql)
|
|
39
|
+
self.connection.commit()
|
|
40
|
+
|
|
41
|
+
def querying(self, sql, params=()):
|
|
42
|
+
"""Run a parameterized SELECT and return the matching rows.
|
|
43
|
+
|
|
44
|
+
params fills any `?` placeholders in sql, keeping values out of the
|
|
45
|
+
SQL string itself (avoids SQL injection).
|
|
46
|
+
"""
|
|
47
|
+
self.cursor.execute(sql, params)
|
|
48
|
+
return self.cursor.fetchall()
|
|
49
|
+
|
|
50
|
+
def execute(self, sql, params=()):
|
|
51
|
+
"""Run a parameterized INSERT/UPDATE/DELETE, commit, and return the
|
|
52
|
+
id of the inserted row (lastrowid; meaningless for UPDATE/DELETE)."""
|
|
53
|
+
self.cursor.execute(sql, params)
|
|
54
|
+
self.connection.commit()
|
|
55
|
+
return self.cursor.lastrowid
|
|
56
|
+
|
|
57
|
+
def close(self):
|
|
58
|
+
"""Close the database connection."""
|
|
59
|
+
self.connection.close()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
PRAGMA foreign_keys = ON;
|
|
2
|
+
|
|
3
|
+
CREATE TABLE IF NOT EXISTS categories(
|
|
4
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
5
|
+
name TEXT NOT NULL,
|
|
6
|
+
is_active INTEGER DEFAULT 1,
|
|
7
|
+
date_created TEXT DEFAULT (datetime('now'))
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ux_categories_name
|
|
11
|
+
ON categories(name) WHERE is_active = 1;
|
|
12
|
+
|
|
13
|
+
CREATE TABLE IF NOT EXISTS void_note(
|
|
14
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
15
|
+
note_date TEXT NOT NULL,
|
|
16
|
+
is_active INTEGER DEFAULT 1,
|
|
17
|
+
date_created TEXT DEFAULT (datetime('now'))
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ux_void_note_note_date
|
|
21
|
+
ON void_note(note_date) WHERE is_active = 1;
|
|
22
|
+
|
|
23
|
+
CREATE TABLE IF NOT EXISTS activities(
|
|
24
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
25
|
+
name TEXT NOT NULL,
|
|
26
|
+
is_active INTEGER DEFAULT 1,
|
|
27
|
+
date_created TEXT DEFAULT (datetime('now')),
|
|
28
|
+
category_id INTEGER NOT NULL,
|
|
29
|
+
FOREIGN KEY (category_id)
|
|
30
|
+
REFERENCES categories (id)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ux_activities_name_category
|
|
34
|
+
ON activities(name, category_id) WHERE is_active = 1;
|
|
35
|
+
|
|
36
|
+
CREATE TABLE IF NOT EXISTS void_note_details (
|
|
37
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
38
|
+
notes TEXT,
|
|
39
|
+
is_active INTEGER DEFAULT 1,
|
|
40
|
+
date_created TEXT DEFAULT (datetime('now')),
|
|
41
|
+
void_note_id INTEGER NOT NULL,
|
|
42
|
+
activity_id INTEGER NOT NULL,
|
|
43
|
+
FOREIGN KEY (void_note_id)
|
|
44
|
+
REFERENCES void_note (id),
|
|
45
|
+
FOREIGN KEY (activity_id)
|
|
46
|
+
REFERENCES activities(id)
|
|
47
|
+
);
|
void/main.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from textual import on
|
|
2
|
+
from textual.app import App, ComposeResult
|
|
3
|
+
from textual.widgets import Footer, Header, TabbedContent, TabPane
|
|
4
|
+
|
|
5
|
+
from void.database import initialize_database
|
|
6
|
+
from void.views.activity import ActivitiesView
|
|
7
|
+
from void.views.collection import CollectionView
|
|
8
|
+
from void.views.day_note import DayNote
|
|
9
|
+
from void.views.note import NoteView
|
|
10
|
+
from void.views.note_form import NoteForm
|
|
11
|
+
from void.views.welcome import WelcomeScreen
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class VoidApp(App):
|
|
15
|
+
|
|
16
|
+
CSS = """
|
|
17
|
+
/* LAYOUT */
|
|
18
|
+
.header{
|
|
19
|
+
dock: top;
|
|
20
|
+
height: auto;
|
|
21
|
+
padding: 1 2 0 2;
|
|
22
|
+
border-bottom: solid $primary;
|
|
23
|
+
}
|
|
24
|
+
.main_container{
|
|
25
|
+
padding: 1 2;
|
|
26
|
+
}
|
|
27
|
+
.card_grid{
|
|
28
|
+
grid-columns: 1fr;
|
|
29
|
+
height: auto;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* TITLES */
|
|
33
|
+
.module_title{
|
|
34
|
+
text-style: bold;
|
|
35
|
+
}
|
|
36
|
+
.section_title{
|
|
37
|
+
text-style: bold;
|
|
38
|
+
color: $text-muted;
|
|
39
|
+
}
|
|
40
|
+
#counter{
|
|
41
|
+
color: $text-muted;
|
|
42
|
+
}
|
|
43
|
+
.empty_state{
|
|
44
|
+
color: $text-muted;
|
|
45
|
+
padding: 1 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* CARDS */
|
|
49
|
+
.note_card{
|
|
50
|
+
height: auto;
|
|
51
|
+
padding: 0 2;
|
|
52
|
+
border: round $primary;
|
|
53
|
+
}
|
|
54
|
+
.note_card Label{
|
|
55
|
+
width: 100%;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* LOGGED ACTIVITY ENTRY */
|
|
59
|
+
.entry_card{
|
|
60
|
+
height: auto;
|
|
61
|
+
margin: 0 0 1 0;
|
|
62
|
+
padding: 0 0 0 2;
|
|
63
|
+
border-left: thick $primary;
|
|
64
|
+
}
|
|
65
|
+
.entry_card Label{
|
|
66
|
+
width: 100%;
|
|
67
|
+
}
|
|
68
|
+
.entry_title{
|
|
69
|
+
text-style: bold;
|
|
70
|
+
}
|
|
71
|
+
.entry_note{
|
|
72
|
+
color: $text-muted;
|
|
73
|
+
}
|
|
74
|
+
.entry_empty{
|
|
75
|
+
color: $text-muted;
|
|
76
|
+
text-style: italic;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* CENTERED STATE SCREENS */
|
|
80
|
+
.state_screen{
|
|
81
|
+
height: 1fr;
|
|
82
|
+
align: center middle;
|
|
83
|
+
}
|
|
84
|
+
.state_card{
|
|
85
|
+
width: 60;
|
|
86
|
+
max-width: 100%;
|
|
87
|
+
height: auto;
|
|
88
|
+
padding: 1 2;
|
|
89
|
+
border: round $panel;
|
|
90
|
+
}
|
|
91
|
+
.state_card Label{
|
|
92
|
+
width: 100%;
|
|
93
|
+
text-align: center;
|
|
94
|
+
}
|
|
95
|
+
.state_card_success{
|
|
96
|
+
border: round $success;
|
|
97
|
+
}
|
|
98
|
+
.state_title{
|
|
99
|
+
text-style: bold;
|
|
100
|
+
}
|
|
101
|
+
.state_success{
|
|
102
|
+
color: $success;
|
|
103
|
+
}
|
|
104
|
+
.state_muted{
|
|
105
|
+
color: $text-muted;
|
|
106
|
+
}
|
|
107
|
+
.state_hint{
|
|
108
|
+
color: $text-muted;
|
|
109
|
+
padding: 0 0 1 0;
|
|
110
|
+
}
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def compose(self) -> ComposeResult:
|
|
114
|
+
yield Header(icon="🧠")
|
|
115
|
+
|
|
116
|
+
with TabbedContent(initial="void_note"):
|
|
117
|
+
|
|
118
|
+
with TabPane("VOID NOTE", id="void_note"):
|
|
119
|
+
yield NoteView()
|
|
120
|
+
|
|
121
|
+
with TabPane("VOID DAY", id="void_day_note"):
|
|
122
|
+
yield DayNote()
|
|
123
|
+
|
|
124
|
+
with TabPane("VOID COLLECTION", id="void_collection"):
|
|
125
|
+
yield CollectionView()
|
|
126
|
+
|
|
127
|
+
with TabPane("VOID ACTIVITIES", id="void_activities"):
|
|
128
|
+
yield ActivitiesView()
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
yield Footer()
|
|
132
|
+
|
|
133
|
+
@on(NoteForm.Saved)
|
|
134
|
+
async def on_note_saved(self) -> None:
|
|
135
|
+
await self.query_one(DayNote).recompose()
|
|
136
|
+
await self.query_one(CollectionView).recompose()
|
|
137
|
+
self.query_one(TabbedContent).active = "void_day_note"
|
|
138
|
+
|
|
139
|
+
@on(ActivitiesView.Changed)
|
|
140
|
+
async def on_activities_changed(self) -> None:
|
|
141
|
+
await self.query_one(NoteView).recompose()
|
|
142
|
+
|
|
143
|
+
def on_mount(self) -> None:
|
|
144
|
+
self.title = "VOID"
|
|
145
|
+
self.sub_title = "Vital Offline Information Diary"
|
|
146
|
+
self.push_screen(WelcomeScreen())
|
|
147
|
+
|
|
148
|
+
def run() -> None:
|
|
149
|
+
initialize_database()
|
|
150
|
+
VoidApp().run()
|
|
151
|
+
|
|
152
|
+
if __name__ == "__main__":
|
|
153
|
+
run()
|
void/models/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Models module"""
|
void/models/activity.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from void.database.connection import Connection
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Activity:
|
|
5
|
+
|
|
6
|
+
def __enter__(self):
|
|
7
|
+
"""Allow `with Activity() as activity:`; returns this instance."""
|
|
8
|
+
return self
|
|
9
|
+
|
|
10
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
11
|
+
"""Close the model when the `with` block ends, even on error."""
|
|
12
|
+
|
|
13
|
+
def get_active_activities(self):
|
|
14
|
+
with Connection() as db:
|
|
15
|
+
sql = """
|
|
16
|
+
SELECT
|
|
17
|
+
activities.id as "id",
|
|
18
|
+
activities.name as "activity",
|
|
19
|
+
categories.name as "category"
|
|
20
|
+
FROM activities
|
|
21
|
+
INNER JOIN
|
|
22
|
+
categories on categories.id = activities.category_id
|
|
23
|
+
WHERE activities.is_active == 1
|
|
24
|
+
AND
|
|
25
|
+
categories.is_active == 1;"""
|
|
26
|
+
result = db.querying(sql)
|
|
27
|
+
return result
|
|
28
|
+
|
|
29
|
+
def get_activities_by_category(self, category_id):
|
|
30
|
+
with Connection() as db:
|
|
31
|
+
sql = """
|
|
32
|
+
SELECT
|
|
33
|
+
activities.id as "id",
|
|
34
|
+
activities.name as "name"
|
|
35
|
+
FROM activities
|
|
36
|
+
WHERE
|
|
37
|
+
activities.category_id = ?
|
|
38
|
+
AND
|
|
39
|
+
activities.is_active = 1
|
|
40
|
+
"""
|
|
41
|
+
result = db.querying(sql, (category_id,))
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
def create_activity(self, activity, category_id):
|
|
45
|
+
with Connection() as db:
|
|
46
|
+
data = (activity, category_id)
|
|
47
|
+
sql = "INSERT INTO activities (name, category_id) VALUES(?, ?)"
|
|
48
|
+
db.execute(sql, data)
|
|
49
|
+
|
|
50
|
+
def update_activity(self, activity_id, activity, category_id):
|
|
51
|
+
with Connection() as db:
|
|
52
|
+
data = (activity, category_id, activity_id)
|
|
53
|
+
sql = """
|
|
54
|
+
UPDATE activities
|
|
55
|
+
SET name = ?, category_id = ?
|
|
56
|
+
WHERE id = ?
|
|
57
|
+
"""
|
|
58
|
+
db.execute(sql, data)
|
|
59
|
+
|
|
60
|
+
def suspend_activity(self, activity_id):
|
|
61
|
+
with Connection() as db:
|
|
62
|
+
data = (activity_id,)
|
|
63
|
+
sql = """
|
|
64
|
+
UPDATE activities
|
|
65
|
+
SET is_active = 0
|
|
66
|
+
WHERE id = ?
|
|
67
|
+
"""
|
|
68
|
+
db.execute(sql, data)
|
void/models/category.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from void.database.connection import Connection
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Category:
|
|
5
|
+
|
|
6
|
+
def __enter__(self):
|
|
7
|
+
"""Allow `with Category() as category:`; returns this instance."""
|
|
8
|
+
return self
|
|
9
|
+
|
|
10
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
11
|
+
"""Close the model when the `with` block ends, even on error."""
|
|
12
|
+
|
|
13
|
+
def get_active_categories_for_activities(self):
|
|
14
|
+
with Connection() as db:
|
|
15
|
+
sql = """
|
|
16
|
+
SELECT
|
|
17
|
+
categories.id as "id",
|
|
18
|
+
categories.name as "category"
|
|
19
|
+
FROM categories
|
|
20
|
+
WHERE categories.is_active = 1
|
|
21
|
+
"""
|
|
22
|
+
result = db.querying(sql)
|
|
23
|
+
return result
|
|
24
|
+
|
|
25
|
+
def get_active_categories(self):
|
|
26
|
+
with Connection() as db:
|
|
27
|
+
sql = """
|
|
28
|
+
SELECT
|
|
29
|
+
categories.name as "category"
|
|
30
|
+
FROM categories
|
|
31
|
+
WHERE categories.is_active = 1
|
|
32
|
+
"""
|
|
33
|
+
result = db.querying(sql)
|
|
34
|
+
return result
|
|
35
|
+
|
|
36
|
+
def create_category(self, category):
|
|
37
|
+
with Connection() as db:
|
|
38
|
+
data = (category,)
|
|
39
|
+
sql = "INSERT INTO categories (name) VALUES(?)"
|
|
40
|
+
db.execute(sql, data)
|
void/models/note.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
|
|
2
|
+
from void.database.connection import Connection
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Note:
|
|
6
|
+
|
|
7
|
+
def __enter__(self):
|
|
8
|
+
"""Allow `with Note() as note:`; returns this instance."""
|
|
9
|
+
return self
|
|
10
|
+
|
|
11
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
12
|
+
"""Close the model when the `with` block ends, even on error."""
|
|
13
|
+
|
|
14
|
+
def save_note(self, date_str):
|
|
15
|
+
with Connection() as db:
|
|
16
|
+
data = (date_str,)
|
|
17
|
+
sql = """
|
|
18
|
+
INSERT INTO
|
|
19
|
+
void_note(note_date)
|
|
20
|
+
VALUES(?)
|
|
21
|
+
"""
|
|
22
|
+
return db.execute(sql, data)
|
|
23
|
+
|
|
24
|
+
def save_note_detail(self, note_id, activity_info, activity_note):
|
|
25
|
+
activity_id, _ = activity_info.split("::")
|
|
26
|
+
with Connection() as db:
|
|
27
|
+
data = (activity_note, note_id, activity_id)
|
|
28
|
+
sql = """
|
|
29
|
+
INSERT INTO
|
|
30
|
+
void_note_details(notes, void_note_id, activity_id)
|
|
31
|
+
VALUES(?, ?, ?)
|
|
32
|
+
"""
|
|
33
|
+
return db.execute(sql, data)
|
|
34
|
+
|
|
35
|
+
NOTE_SQL = """
|
|
36
|
+
SELECT
|
|
37
|
+
void_note.id as "note_id",
|
|
38
|
+
void_note.note_date as "note_date",
|
|
39
|
+
void_note_details.id as "detail_id",
|
|
40
|
+
activities.id as "activity_id",
|
|
41
|
+
activities.name as "activity",
|
|
42
|
+
categories.name as "category",
|
|
43
|
+
void_note_details.notes as "notes"
|
|
44
|
+
FROM void_note
|
|
45
|
+
INNER JOIN
|
|
46
|
+
void_note_details
|
|
47
|
+
ON void_note_details.void_note_id = void_note.id
|
|
48
|
+
INNER JOIN
|
|
49
|
+
activities ON activities.id = void_note_details.activity_id
|
|
50
|
+
INNER JOIN
|
|
51
|
+
categories ON categories.id = activities.category_id
|
|
52
|
+
WHERE void_note.is_active = 1
|
|
53
|
+
AND void_note_details.is_active = 1
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def get_day_note(self, date_str):
|
|
57
|
+
with Connection() as db:
|
|
58
|
+
sql = self.NOTE_SQL + " AND void_note.note_date = ? ORDER BY categories.name, activities.name"
|
|
59
|
+
return db.querying(sql, (date_str,))
|
|
60
|
+
|
|
61
|
+
def get_notes(self):
|
|
62
|
+
"""Every saved note detail, newest day first."""
|
|
63
|
+
with Connection() as db:
|
|
64
|
+
return db.querying(self.NOTE_SQL + " ORDER BY void_note.note_date DESC, void_note_details.id")
|
void/views/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
""" APP VIEWS"""
|