Compare commits

...

2 Commits

Author SHA1 Message Date
Jadowyne Ulve
c7c8383baf test 2025-04-26 10:18:44 -05:00
Jadowyne Ulve
93e8fa3888 test 2025-04-26 10:18:30 -05:00
45 changed files with 5378 additions and 305 deletions

View File

@ -331,6 +331,8 @@ class ShoppingListPayload:
self.type self.type
) )
# DONE
@dataclass @dataclass
class SitePayload: class SitePayload:
site_name: str site_name: str
@ -357,6 +359,7 @@ class SitePayload:
self.default_primary_location self.default_primary_location
) )
#DONE
@dataclass @dataclass
class RolePayload: class RolePayload:
role_name:str role_name:str
@ -408,7 +411,8 @@ class SiteManager:
"shopping_lists", "shopping_lists",
"shopping_list_items", "shopping_list_items",
"item_locations", "item_locations",
"conversions" "conversions",
"sku_prefix"
] ]
self.drop_order = [ self.drop_order = [
"item_info", "item_info",
@ -431,5 +435,6 @@ class SiteManager:
"shopping_list_items", "shopping_list_items",
"shopping_lists", "shopping_lists",
"item_locations", "item_locations",
"conversions" "conversions",
"sku_prefix"
] ]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

122
admin.py
View File

@ -1,122 +0,0 @@
from flask import Blueprint, request, render_template, redirect, session, url_for, send_file, jsonify, Response
import psycopg2, math, json, datetime, main, copy, requests
from config import config, sites_config
from main import unfoldCostLayers, get_sites, get_roles, create_site_secondary, getUser
from manage import create
admin = Blueprint('admin_api', __name__)
@admin.route("/admin/getSites")
def getSites():
sites = get_sites(session.get('user')[13])
return jsonify(sites=sites)
@admin.route("/getRoles")
def getRoles():
sites_roles = {}
sites = get_sites(session.get('user')[13])
for site in sites:
site_roles = get_roles(site_id=site[0])
sites_roles[site[1]] = site_roles
return jsonify(sites=sites_roles)
@admin.route("/admin/getUsers", methods=["POST"])
def getUsers():
if request.method == "POST":
page = request.get_json()['page']
limit = request.get_json()['limit']
offset = (page - 1) * limit
database_config = config()
with psycopg2.connect(**database_config) as conn:
try:
with conn.cursor() as cur:
sql = f"SELECT * FROM logins LIMIT %s OFFSET %s;"
cur.execute(sql, (limit, offset))
users = cur.fetchall()
cur.execute("SELECT COUNT(*) FROM main_items;")
count = cur.fetchone()[0]
return jsonify(users=users, endpage=math.ceil(count/limit))
except (Exception, psycopg2.DatabaseError) as error:
print(error)
conn.rollback()
return jsonify(message="FAILED")
return jsonify(message="FAILED")
@admin.route("/admin/editRole/<id>")
def getRole(id):
database_config = config()
with psycopg2.connect(**database_config) as conn:
try:
with conn.cursor() as cur:
sql = f"SELECT * FROM roles LEFT JOIN sites ON sites.id = roles.site_id WHERE roles.id = %s;"
cur.execute(sql, (id, ))
role = cur.fetchone()
return render_template("admin/role.html", role=role, proto={'referrer': request.referrer})
except (Exception, psycopg2.DatabaseError) as error:
print(error)
conn.rollback()
return jsonify(message="FAILED")
@admin.route("/addRole", methods=["POST"])
def addRole():
if request.method == "POST":
role_name = request.get_json()['role_name']
role_description = request.get_json()['role_description']
site_id = request.get_json()['site_id']
sql = f"INSERT INTO roles (role_name, role_description, site_id) VALUES (%s, %s, %s);"
print(role_name, role_description, site_id)
database_config = config()
with psycopg2.connect(**database_config) as conn:
try:
with conn.cursor() as cur:
data = (role_name, role_description, site_id)
cur.execute(sql, data)
except (Exception, psycopg2.DatabaseError) as error:
print(error)
conn.rollback()
return jsonify(message="FAILED")
return jsonify(message="SUCCESS")
return jsonify(message="FAILED")
@admin.route("/deleteRole", methods=["POST"])
def deleteRole():
if request.method == "POST":
role_id = request.get_json()['role_id']
database_config = config()
with psycopg2.connect(**database_config) as conn:
try:
with conn.cursor() as cur:
sql = f"DELETE FROM roles WHERE roles.id = %s;"
cur.execute(sql, (role_id, ))
return jsonify(message="Role Deleted!")
except (Exception, psycopg2.DatabaseError) as error:
print(error)
conn.rollback()
return jsonify(message=error)
return jsonify(message="FAILED")
@admin.route("/addSite", methods=["POST"])
async def addSite():
if request.method == "POST":
site_name = request.get_json()['site_name']
site_description = request.get_json()['site_description']
default_zone = request.get_json()["default_zone"]
default_location = request.get_json()['default_location']
username = session.get('user')[1]
user_id = session.get('user')[0]
create(site_name, username, default_zone, default_location)
result = await create_site_secondary(site_name, user_id, default_zone, default_location, default_location, site_description)
if result:
return jsonify(message="Success!")
return jsonify(message="Failed!")

263
api_admin.py Normal file
View File

@ -0,0 +1,263 @@
from flask import Blueprint, request, render_template, redirect, session, url_for, send_file, jsonify, Response
import psycopg2, math, json, datetime, main, copy, requests
from config import config, sites_config
from main import unfoldCostLayers, get_sites, get_roles, create_site_secondary, getUser
from manage import create
from user_api import login_required
import postsqldb, process, hashlib, database_admin
admin_api = Blueprint('admin_api', __name__)
@admin_api.route('/admin')
def admin_index():
sites = [site[1] for site in main.get_sites(session['user']['sites'])]
return render_template("admin/index.html",
current_site=session['selected_site'],
sites=sites)
@admin_api.route('/admin/site/<id>')
@login_required
def adminSites(id):
if id == "new":
new_site = postsqldb.SitesTable.Payload(
"",
"",
session['user_id']
)
return render_template("admin/site.html", site=new_site.get_dictionary())
else:
database_config = config()
with psycopg2.connect(**database_config) as conn:
site = postsqldb.SitesTable.select_tuple(conn, (id,))
return render_template('admin/site.html', site=site)
@admin_api.route('/admin/role/<id>')
@login_required
def adminRoles(id):
database_config = config()
with psycopg2.connect(**database_config) as conn:
sites = postsqldb.SitesTable.selectTuples(conn)
if id == "new":
new_role = postsqldb.RolesTable.Payload(
"",
"",
0
)
return render_template("admin/role.html", role=new_role.get_dictionary(), sites=sites)
else:
role = postsqldb.RolesTable.select_tuple(conn, (id,))
return render_template('admin/role.html', role=role, sites=sites)
@admin_api.route('/admin/user/<id>')
@login_required
def adminUser(id):
database_config = config()
with psycopg2.connect(**database_config) as conn:
if id == "new":
new_user = postsqldb.LoginsTable.Payload("", "", "", "")
return render_template("admin/user.html", user=new_user.get_dictionary())
else:
user = database_admin.selectLoginsUser(int(id))
return render_template('admin/user.html', user=user)
@admin_api.route('/admin/getSites', methods=['GET'])
@login_required
def getSites():
if request.method == "GET":
records = []
count = 0
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
offset = (page - 1) * limit
database_config = config()
with psycopg2.connect(**database_config) as conn:
records, count = postsqldb.SitesTable.paginateTuples(conn, (limit, offset))
return jsonify({'sites': records, "end": math.ceil(count/limit), 'error':False, 'message': 'Sites Loaded Successfully!'})
return jsonify({'sites': records, "end": math.ceil(count/limit), 'error':True, 'message': 'There was a problem loading Sites!'})
@admin_api.route('/admin/getRoles', methods=['GET'])
@login_required
def getRoles():
if request.method == "GET":
records = []
count = 0
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
offset = (page - 1) * limit
database_config = config()
with psycopg2.connect(**database_config) as conn:
records, count = postsqldb.RolesTable.paginate_tuples(conn, (limit, offset))
return jsonify({'roles': records, "end": math.ceil(count/limit), 'error':False, 'message': 'Roles Loaded Successfully!'})
return jsonify({'roles': records, "end": math.ceil(count/limit), 'error':True, 'message': 'There was a problem loading Roles!'})
@admin_api.route('/admin/getLogins', methods=['GET'])
@login_required
def getLogins():
if request.method == "GET":
records = []
count = 0
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
offset = (page - 1) * limit
database_config = config()
with psycopg2.connect(**database_config) as conn:
records, count = postsqldb.LoginsTable.paginate_tuples(conn, (limit, offset))
return jsonify({'logins': records, "end": math.ceil(count/limit), 'error':False, 'message': 'logins Loaded Successfully!'})
return jsonify({'logins': records, "end": math.ceil(count/limit), 'error':True, 'message': 'There was a problem loading logins!'})
@admin_api.route('/admin/site/postDeleteSite', methods=["POST"])
def postDeleteSite():
if request.method == "POST":
site_id = request.get_json()['site_id']
database_config = config()
user_id = session['user_id']
try:
with psycopg2.connect(**database_config) as conn:
user = postsqldb.LoginsTable.select_tuple(conn, (user_id,))
admin_user = (user['username'], user['password'], user['email'], user['row_type'])
site = postsqldb.SitesTable.select_tuple(conn, (site_id,))
site = postsqldb.SitesTable.Manager(
site['site_name'],
admin_user,
site['default_zone'],
site['default_primary_location'],
site['site_description']
)
process.deleteSite(site_manager=site)
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f""})
return jsonify({'error': True, 'message': f""})
@admin_api.route('/admin/site/postAddSite', methods=["POST"])
def postAddSite():
if request.method == "POST":
payload = request.get_json()['payload']
database_config = config()
site_name = session['selected_site']
user_id = session['user_id']
print(payload)
try:
with psycopg2.connect(**database_config) as conn:
user = postsqldb.LoginsTable.select_tuple(conn, (user_id,))
admin_user = (user['username'], user['password'], user['email'], user['row_type'])
site = postsqldb.SitesTable.Manager(
payload['site_name'],
admin_user,
payload['default_zone'],
payload['default_primary_location'],
payload['site_description']
)
process.addSite(site_manager=site)
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f"Zone added to {site_name}."})
return jsonify({'error': True, 'message': f"These was an error with adding this Zone to {site_name}."})
@admin_api.route('/admin/site/postEditSite', methods=["POST"])
def postEditSite():
if request.method == "POST":
payload = request.get_json()['payload']
database_config = config()
try:
with psycopg2.connect(**database_config) as conn:
postsqldb.SitesTable.update_tuple(conn, payload)
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f"Site updated."})
return jsonify({'error': True, 'message': f"These was an error with updating Site."})
@admin_api.route('/admin/role/postAddRole', methods=["POST"])
def postAddRole():
if request.method == "POST":
payload = request.get_json()['payload']
database_config = config()
print(payload)
try:
with psycopg2.connect(**database_config) as conn:
role = postsqldb.RolesTable.Payload(
payload['role_name'],
payload['role_description'],
payload['site_id']
)
postsqldb.RolesTable.insert_tuple(conn, role.payload())
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f"Role added."})
return jsonify({'error': True, 'message': f"These was an error with adding this Role."})
@admin_api.route('/admin/role/postEditRole', methods=["POST"])
def postEditRole():
if request.method == "POST":
payload = request.get_json()['payload']
database_config = config()
print(payload)
try:
with psycopg2.connect(**database_config) as conn:
postsqldb.RolesTable.update_tuple(conn, payload)
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f"Role updated."})
return jsonify({'error': True, 'message': f"These was an error with updating this Role."})
@admin_api.route('/admin/user/postAddLogin', methods=["POST"])
def postAddLogin():
if request.method == "POST":
payload = request.get_json()['payload']
database_config = config()
user = []
try:
with psycopg2.connect(**database_config) as conn:
user = postsqldb.LoginsTable.Payload(
payload['username'],
hashlib.sha256(payload['password'].encode()).hexdigest(),
payload['email'],
payload['row_type']
)
user = postsqldb.LoginsTable.insert_tuple(conn, user.payload())
except postsqldb.DatabaseError as error:
conn.rollback()
return jsonify({'user': user, 'error': True, 'message': error})
return jsonify({'user': user, 'error': False, 'message': f"User added."})
return jsonify({'user': user, 'error': True, 'message': f"These was an error with adding this User."})
@admin_api.route('/admin/user/postEditLogin', methods=["POST"])
def postEditLogin():
if request.method == "POST":
payload = request.get_json()['payload']
database_config = config()
try:
with psycopg2.connect(**database_config) as conn:
postsqldb.LoginsTable.update_tuple(conn, payload)
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f"User was Added Successfully."})
return jsonify({'error': True, 'message': f"These was an error with adding this user."})
@admin_api.route('/admin/user/postEditLoginPassword', methods=["POST"])
def postEditLoginPassword():
if request.method == "POST":
payload = request.get_json()['payload']
database_config = config()
try:
with psycopg2.connect(**database_config) as conn:
user = postsqldb.LoginsTable.select_tuple(conn, (payload['id'],))
if hashlib.sha256(payload['current_password'].encode()).hexdigest() != user['password']:
return jsonify({'error': True, 'message': "The provided current password is incorrect"})
payload['update']['password'] = hashlib.sha256(payload['update']['password'].encode()).hexdigest()
postsqldb.LoginsTable.update_tuple(conn, payload)
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f"Password was changed successfully."})
return jsonify({'error': True, 'message': f"These was an error with updating this Users password."})

View File

@ -1809,3 +1809,33 @@
2025-04-20 09:54:33.948670 --- ERROR --- DatabaseError(message=''int' object is not iterable', 2025-04-20 09:54:33.948670 --- ERROR --- DatabaseError(message=''int' object is not iterable',
payload=(), payload=(),
sql='SELECT * FROM sites') sql='SELECT * FROM sites')
2025-04-20 19:52:54.986069 --- ERROR --- DatabaseError(message='table "testsite_zones" does not exist',
payload=DROP TABLE TestSite_zones CASCADE;,
sql='zones')
2025-04-20 20:11:14.230809 --- ERROR --- DatabaseError(message='tuple index out of range',
payload=('main', ''),
sql='INSERT INTO testsite_zones(name, description, site_id) VALUES (%s, %s, %s) RETURNING *;')
2025-04-20 20:32:54.096676 --- ERROR --- DatabaseError(message='relation "testsite_sku_prefix" does not existLINE 1: SELECT * FROM TestSite_sku_prefix LIMIT 25 OFFSET 0; ^',
payload=(25, 0),
sql='SELECT * FROM TestSite_sku_prefix LIMIT %s OFFSET %s;')
2025-04-20 20:33:30.514244 --- ERROR --- DatabaseError(message='relation "testsite_item_locations" does not existLINE 3: FROM TestSite_item_locations mil ^',
payload=['', 50, 0],
sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM TestSite_item_locations mil JOIN TestSite_items mi ON mil.part_id = mi.id GROUP BY mi.id )SELECT TestSite_items.*, row_to_json(TestSite_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=TestSite_item_info.uom) as uomFROM TestSite_itemsLEFT JOIN sum_cte ON TestSite_items.id = sum_cte.idLEFT JOIN TestSite_item_info ON TestSite_items.item_info_id = TestSite_item_info.idWHERE TestSite_items.search_string LIKE '%%' || %s || '%%'ORDER BY TestSite_items.id ASCLIMIT %s OFFSET %s;')
2025-04-20 20:34:46.464320 --- ERROR --- DatabaseError(message='relation "testsite_item_locations" does not existLINE 3: FROM TestSite_item_locations mil ^',
payload=['', 50, 0],
sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM TestSite_item_locations mil JOIN TestSite_items mi ON mil.part_id = mi.id GROUP BY mi.id )SELECT TestSite_items.*, row_to_json(TestSite_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=TestSite_item_info.uom) as uomFROM TestSite_itemsLEFT JOIN sum_cte ON TestSite_items.id = sum_cte.idLEFT JOIN TestSite_item_info ON TestSite_items.item_info_id = TestSite_item_info.idWHERE TestSite_items.search_string LIKE '%%' || %s || '%%'ORDER BY TestSite_items.id ASCLIMIT %s OFFSET %s;')
2025-04-20 21:10:59.263962 --- ERROR --- DatabaseError(message='relation "testsite_item_locations" does not existLINE 3: FROM TestSite_item_locations mil ^',
payload=['', 50, 0],
sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM TestSite_item_locations mil JOIN TestSite_items mi ON mil.part_id = mi.id GROUP BY mi.id )SELECT TestSite_items.*, row_to_json(TestSite_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=TestSite_item_info.uom) as uomFROM TestSite_itemsLEFT JOIN sum_cte ON TestSite_items.id = sum_cte.idLEFT JOIN TestSite_item_info ON TestSite_items.item_info_id = TestSite_item_info.idWHERE TestSite_items.search_string LIKE '%%' || %s || '%%'ORDER BY TestSite_items.id ASCLIMIT %s OFFSET %s;')
2025-04-21 14:42:32.973758 --- ERROR --- DatabaseError(message='new row for relation "logins" violates check constraint "logins_email_check"DETAIL: Failing row contains (29, ScannerDeviceB, 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08, test, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, f, {}, device).',
payload=('ScannerDeviceB', '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', 'test', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', False, '{}', 'device'),
sql='INSERT INTO logins(username, password, email, favorites, unseen_pantry_items, unseen_groups, unseen_shopping_lists, unseen_recipes, seen_pantry_items, seen_groups, seen_shopping_lists, seen_recipes, sites, site_roles, system_admin, flags, row_type) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING *;')
2025-04-21 14:43:23.959085 --- ERROR --- DatabaseError(message='new row for relation "logins" violates check constraint "logins_email_check"DETAIL: Failing row contains (30, ScannerDeviceB, 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08, test, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, f, {}, device).',
payload=('ScannerDeviceB', '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', 'test', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', False, '{}', 'device'),
sql='INSERT INTO logins(username, password, email, favorites, unseen_pantry_items, unseen_groups, unseen_shopping_lists, unseen_recipes, seen_pantry_items, seen_groups, seen_shopping_lists, seen_recipes, sites, site_roles, system_admin, flags, row_type) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING *;')
2025-04-21 14:44:16.605030 --- ERROR --- DatabaseError(message='new row for relation "logins" violates check constraint "logins_email_check"DETAIL: Failing row contains (31, ScannerDeviceB, 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08, test, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, f, {}, device).',
payload=('ScannerDeviceB', '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', 'test', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', False, '{}', 'device'),
sql='INSERT INTO logins(username, password, email, favorites, unseen_pantry_items, unseen_groups, unseen_shopping_lists, unseen_recipes, seen_pantry_items, seen_groups, seen_shopping_lists, seen_recipes, sites, site_roles, system_admin, flags, row_type) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING *;')
2025-04-21 14:45:18.122865 --- ERROR --- DatabaseError(message='new row for relation "logins" violates check constraint "logins_email_check"DETAIL: Failing row contains (32, ScannerDeviceB, 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08, test, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, f, {}, device).',
payload=('ScannerDeviceB', '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', 'test', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', False, '{}', 'device'),
sql='INSERT INTO logins(username, password, email, favorites, unseen_pantry_items, unseen_groups, unseen_shopping_lists, unseen_recipes, seen_pantry_items, seen_groups, seen_shopping_lists, seen_recipes, sites, site_roles, system_admin, flags, row_type) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING *;')

18
database_admin.py Normal file
View File

@ -0,0 +1,18 @@
import postsqldb
import psycopg2
import config
def selectLoginsUser(login_id):
database_config = config.config()
try:
with psycopg2.connect(**database_config) as conn:
with open("sql/SELECT/admin/selectLoginsUser.sql", "r") as file:
sql = file.read()
with conn.cursor() as cur:
cur.execute(sql, (login_id,))
user = cur.fetchone()
if user:
user = postsqldb.tupleDictionaryFactory(cur.description, user)
return user
except Exception as error:
raise postsqldb.DatabaseError(error, login_id, sql)

View File

@ -1137,7 +1137,6 @@ class ZonesTable:
@dataclass @dataclass
class Payload: class Payload:
name: str name: str
site_id: int
description: str = "" description: str = ""
def __post_init__(self): def __post_init__(self):
@ -1148,7 +1147,6 @@ class ZonesTable:
return ( return (
self.name, self.name,
self.description, self.description,
self.site_id
) )
@classmethod @classmethod
@ -1798,6 +1796,163 @@ class CycleCountsTable:
pass pass
class SitesTable: class SitesTable:
@dataclass
class Manager:
site_name: str
admin_user: tuple
default_zone: int
default_location: int
description: str
create_order: list = field(init=False)
drop_order: list = field(init=False)
def __post_init__(self):
self.create_order = [
"logins",
"sites",
"roles",
"units",
"cost_layers",
"linked_items",
"brands",
"food_info",
"item_info",
"zones",
"locations",
"logistics_info",
"transactions",
"item",
"vendors",
"groups",
"group_items",
"receipts",
"receipt_items",
"recipes",
"recipe_items",
"shopping_lists",
"shopping_list_items",
"item_locations",
"conversions"
]
self.drop_order = [
"item_info",
"items",
"cost_layers",
"linked_items",
"transactions",
"brands",
"food_info",
"logistics_info",
"zones",
"locations",
"vendors",
"group_items",
"groups",
"receipt_items",
"receipts",
"recipe_items",
"recipes",
"shopping_list_items",
"shopping_lists",
"item_locations",
"conversions"
]
@dataclass
class Payload:
site_name: str
site_description: str
site_owner_id: int
default_zone: str = None
default_auto_issue_location: str = None
default_primary_location: str = None
creation_date: datetime.datetime = field(init=False)
flags: dict = field(default_factory=dict)
def __post_init__(self):
self.creation_date = datetime.datetime.now()
def payload(self):
return (
self.site_name,
self.site_description,
self.creation_date,
self.site_owner_id,
json.dumps(self.flags),
self.default_zone,
self.default_auto_issue_location,
self.default_primary_location
)
def get_dictionary(self):
return self.__dict__
@classmethod
def insert_tuple(self, conn, payload:tuple, convert=True):
"""inserts payload into sites table
Args:
conn (_T_connector@connect): Postgresql Connector
payload (tuple): (site_name[str], site_description[str], creation_date[timestamp], site_owner_id[int],
flags[dict], default_zone[str], default_auto_issue_location[str], default_primary_location[str])
convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
site_tuple = ()
with open(f"sql/INSERT/insertSitesTuple.sql", "r+") as file:
sql = file.read()
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
site_tuple = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
site_tuple = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return site_tuple
@classmethod
def paginateTuples(self, conn, payload: tuple, convert=True):
"""_summary_
Args:
conn (_type_): _description_
payload (tuple): (limit, offset)
convert (bool, optional): _description_. Defaults to True.
Raises:
DatabaseError: _description_
Returns:
_type_: _description_
"""
recordsets = []
count = 0
sql = f"SELECT * FROM sites LIMIT %s OFFSET %s;"
sql_count = f"SELECT COUNT(*) FROM sites;"
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
recordsets = [tupleDictionaryFactory(cur.description, row) for row in rows]
elif rows and not convert:
recordsets = rows
cur.execute(sql_count)
count = cur.fetchone()[0]
except Exception as error:
raise DatabaseError(error, (), sql)
return recordsets, count
@classmethod @classmethod
def selectTuples(self, conn, convert=True): def selectTuples(self, conn, convert=True):
recordsets = [] recordsets = []
@ -1813,3 +1968,379 @@ class SitesTable:
except Exception as error: except Exception as error:
raise DatabaseError(error, (), sql) raise DatabaseError(error, (), sql)
return recordsets return recordsets
@classmethod
def select_tuple(self, conn, payload:tuple, convert=True):
record = []
sql = f"SELECT * FROM sites WHERE id=%s;"
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
record = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
record = rows
except Exception as error:
raise DatabaseError(error, (), sql)
return record
@classmethod
def update_tuple(self, conn, payload, convert=True):
"""_summary_
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
table (str):
payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}}
convert (bool, optional): determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: updated tuple
"""
updated = ()
set_clause, values = updateStringFactory(payload['update'])
values.append(payload['id'])
sql = f"UPDATE sites SET {set_clause} WHERE id=%s RETURNING *;"
try:
with conn.cursor() as cur:
cur.execute(sql, values)
rows = cur.fetchone()
if rows and convert:
updated = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
updated = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return updated
class RolesTable:
@dataclass
class Payload:
role_name:str
role_description:str
site_id: int
flags: dict = field(default_factory=dict)
def payload(self):
return (
self.role_name,
self.role_description,
self.site_id,
json.dumps(self.flags)
)
def get_dictionary(self):
return self.__dict__
@classmethod
def insert_tuple(self, conn, payload:tuple, convert=True):
"""inserts payload into roles table
Args:
conn (_T_connector@connect): Postgresql Connector
payload (tuple): (role_name[str], role_description[str], site_id[int], flags[jsonb])
convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
role_tuple = ()
with open(f"sql/INSERT/insertRolesTuple.sql", "r+") as file:
sql = file.read()
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
role_tuple = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
role_tuple = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return role_tuple
@classmethod
def select_tuple(self, conn, payload:tuple, convert=True):
record = []
sql = f"SELECT roles.*, row_to_json(sites.*) as site FROM roles LEFT JOIN sites ON sites.id = roles.site_id WHERE roles.id=%s;"
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
record = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
record = rows
except Exception as error:
raise DatabaseError(error, (), sql)
return record
@classmethod
def paginate_tuples(self, conn, payload:tuple, convert=True):
"""
Args:
conn (_T_connector@connect): Postgresql Connector
payload (tuple): (limit, offset)
convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
recordset = []
sql = f"SELECT roles.*, row_to_json(sites.*) as site FROM roles LEFT JOIN sites ON sites.id = roles.site_id LIMIT %s OFFSET %s;"
sql_count = f"SELECT COUNT(*) FROM roles;"
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
recordset = [tupleDictionaryFactory(cur.description, row) for row in rows]
elif rows and not convert:
recordset = rows
cur.execute(sql_count)
count = cur.fetchone()[0]
except Exception as error:
raise DatabaseError(error, payload, sql)
return recordset, count
@classmethod
def update_tuple(self, conn, payload, convert=True):
"""_summary_
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
table (str):
payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}}
convert (bool, optional): determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: updated tuple
"""
updated = ()
set_clause, values = updateStringFactory(payload['update'])
values.append(payload['id'])
sql = f"UPDATE roles SET {set_clause} WHERE id=%s RETURNING *;"
try:
with conn.cursor() as cur:
cur.execute(sql, values)
rows = cur.fetchone()
if rows and convert:
updated = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
updated = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return updated
class LoginsTable:
@dataclass
class Payload:
username:str
password:str
email: str
row_type: str
system_admin: bool = False
flags: dict = field(default_factory=dict)
favorites: dict = field(default_factory=dict)
unseen_pantry_items: list = field(default_factory=list)
unseen_groups: list = field(default_factory=list)
unseen_shopping_lists: list = field(default_factory=list)
unseen_recipes: list = field(default_factory=list)
seen_pantry_items: list = field(default_factory=list)
seen_groups: list = field(default_factory=list)
seen_shopping_lists: list = field(default_factory=list)
seen_recipes: list = field(default_factory=list)
sites: list = field(default_factory=list)
site_roles: list = field(default_factory=list)
def payload(self):
return (
self.username,
self.password,
self.email,
json.dumps(self.favorites),
lst2pgarr(self.unseen_pantry_items),
lst2pgarr(self.unseen_groups),
lst2pgarr(self.unseen_shopping_lists),
lst2pgarr(self.unseen_recipes),
lst2pgarr(self.seen_pantry_items),
lst2pgarr(self.seen_groups),
lst2pgarr(self.seen_shopping_lists),
lst2pgarr(self.seen_recipes),
lst2pgarr(self.sites),
lst2pgarr(self.site_roles),
self.system_admin,
json.dumps(self.flags),
self.row_type
)
def get_dictionary(self):
return self.__dict__
@classmethod
def insert_tuple(self, conn, payload:tuple, convert=True):
"""inserts payload into roles table
Args:
conn (_T_connector@connect): Postgresql Connector
payload (tuple): (username, password, email, favorites, unseen_pantry_items, unseen_groups, unseen_shopping_lists,
unseen_recipes, seen_pantry_items, seen_groups, seen_shopping_lists, seen_recipes,
sites, site_roles, system_admin, flags, row_type)
convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
login = ()
with open(f"sql/INSERT/insertLoginsTupleTwo.sql", "r+") as file:
sql = file.read()
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
login = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
login = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return login
@classmethod
def select_tuple(self, conn, payload:tuple, convert=True):
"""_summary_
Args:
conn (_type_): _description_
payload (tuple): (user_id,)
convert (bool, optional): _description_. Defaults to False.
Raises:
DatabaseError: _description_
Returns:
_type_: _description_
"""
user = ()
try:
with conn.cursor() as cur:
sql = f"SELECT * FROM logins WHERE id=%s;"
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
user = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
user = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return user
@classmethod
def paginate_tuples(self, conn, payload:tuple, convert=True):
"""
Args:
conn (_T_connector@connect): Postgresql Connector
payload (tuple): (limit, offset)
convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
recordset = []
sql = f"SELECT * FROM logins LIMIT %s OFFSET %s;"
sql_count = f"SELECT COUNT(*) FROM logins;"
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
recordset = [tupleDictionaryFactory(cur.description, row) for row in rows]
elif rows and not convert:
recordset = rows
cur.execute(sql_count)
count = cur.fetchone()[0]
except Exception as error:
raise DatabaseError(error, payload, sql)
return recordset, count
@classmethod
def get_washed_tuple(self, conn, payload:tuple, convert=True):
user = ()
try:
with conn.cursor() as cur:
sql = f"SELECT id, username, sites, site_roles, system_admin, flags FROM logins WHERE id=%s;"
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
user = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
user = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return user
@classmethod
def update_tuple(self, conn, payload, convert=True):
"""_summary_
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
table (str):
payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}}
convert (bool, optional): determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: updated tuple
"""
updated = ()
set_clause, values = updateStringFactory(payload['update'])
values.append(payload['id'])
sql = f"UPDATE logins SET {set_clause} WHERE id=%s RETURNING *;"
try:
with conn.cursor() as cur:
cur.execute(sql, values)
rows = cur.fetchone()
if rows and convert:
updated = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
updated = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return updated

View File

@ -418,4 +418,571 @@
2025-04-07 18:13:10.570396 --- CAUTION --- DatabaseError(message='current transaction is aborted, commands ignored until end of transaction block', payload=('loaves', ' loaf', ' Loaf', ' A single loaf of a unit.'), sql='INSERT INTO units(plural, single, fullname, description) VALUES (%s, %s, %s, %s) RETURNING *;') 2025-04-07 18:13:10.570396 --- CAUTION --- DatabaseError(message='current transaction is aborted, commands ignored until end of transaction block', payload=('loaves', ' loaf', ' Loaf', ' A single loaf of a unit.'), sql='INSERT INTO units(plural, single, fullname, description) VALUES (%s, %s, %s, %s) RETURNING *;')
["loaves", " loaf", " Loaf", " A single loaf of a unit."] ["loaves", " loaf", " Loaf", " A single loaf of a unit."]
2025-04-07 18:13:10.579705 --- CAUTION --- DatabaseError(message='current transaction is aborted, commands ignored until end of transaction block', payload=('packs', ' pack', ' Pack', ' A Single Pack of a unit.'), sql='INSERT INTO units(plural, single, fullname, description) VALUES (%s, %s, %s, %s) RETURNING *;') 2025-04-07 18:13:10.579705 --- CAUTION --- DatabaseError(message='current transaction is aborted, commands ignored until end of transaction block', payload=('packs', ' pack', ' Pack', ' A Single Pack of a unit.'), sql='INSERT INTO units(plural, single, fullname, description) VALUES (%s, %s, %s, %s) RETURNING *;')
["packs", " pack", " Pack", " A Single Pack of a unit."] ["packs", " pack", " Pack", " A Single Pack of a unit."]2025-04-20 19:12:42.901904 --- INFO --- logins Created!
2025-04-20 19:12:42.907042 --- INFO --- sites Created!
2025-04-20 19:12:42.916090 --- INFO --- roles Created!
2025-04-20 19:12:42.924299 --- INFO --- units Created!
2025-04-20 19:12:42.936431 --- INFO --- cost_layers Created!
2025-04-20 19:12:42.948063 --- INFO --- linked_items Created!
2025-04-20 19:12:42.956357 --- INFO --- brands Created!
2025-04-20 19:12:42.965966 --- INFO --- food_info Created!
2025-04-20 19:12:42.978005 --- INFO --- item_info Created!
2025-04-20 19:12:42.989756 --- INFO --- zones Created!
2025-04-20 19:12:42.999753 --- INFO --- locations Created!
2025-04-20 19:12:43.010766 --- INFO --- logistics_info Created!
2025-04-20 19:12:43.020793 --- INFO --- transactions Created!
2025-04-20 19:12:43.032889 --- INFO --- item Created!
2025-04-20 19:12:43.042155 --- INFO --- vendors Created!
2025-04-20 19:12:43.052984 --- INFO --- groups Created!
2025-04-20 19:12:43.065257 --- INFO --- group_items Created!
2025-04-20 19:12:43.075250 --- INFO --- receipts Created!
2025-04-20 19:12:43.085279 --- INFO --- receipt_items Created!
2025-04-20 19:12:43.095708 --- INFO --- recipes Created!
2025-04-20 19:12:43.108432 --- INFO --- recipe_items Created!
2025-04-20 19:12:43.118248 --- INFO --- shopping_lists Created!
2025-04-20 19:12:43.130093 --- INFO --- shopping_list_items Created!
2025-04-20 19:12:43.142255 --- INFO --- item_locations Created!
2025-04-20 19:12:43.152766 --- INFO --- conversions Created!
2025-04-20 19:12:43.157756 --- INFO --- Admin User Created!
2025-04-20 19:12:43.173869 --- ERROR --- module 'MyDataclasses' has no attribute 'ZonePayload'
2025-04-20 19:13:09.179803 --- INFO --- logins Created!
2025-04-20 19:13:09.186834 --- INFO --- sites Created!
2025-04-20 19:13:09.191988 --- INFO --- roles Created!
2025-04-20 19:13:09.196031 --- INFO --- units Created!
2025-04-20 19:13:09.203142 --- INFO --- cost_layers Created!
2025-04-20 19:13:09.211122 --- INFO --- linked_items Created!
2025-04-20 19:13:09.217381 --- INFO --- brands Created!
2025-04-20 19:13:09.223367 --- INFO --- food_info Created!
2025-04-20 19:13:09.231980 --- INFO --- item_info Created!
2025-04-20 19:13:09.239962 --- INFO --- zones Created!
2025-04-20 19:13:09.247043 --- INFO --- locations Created!
2025-04-20 19:13:09.254389 --- INFO --- logistics_info Created!
2025-04-20 19:13:09.261528 --- INFO --- transactions Created!
2025-04-20 19:13:09.270171 --- INFO --- item Created!
2025-04-20 19:13:09.276674 --- INFO --- vendors Created!
2025-04-20 19:13:09.283569 --- INFO --- groups Created!
2025-04-20 19:13:09.291142 --- INFO --- group_items Created!
2025-04-20 19:13:09.300047 --- INFO --- receipts Created!
2025-04-20 19:13:09.307082 --- INFO --- receipt_items Created!
2025-04-20 19:13:09.314189 --- INFO --- recipes Created!
2025-04-20 19:13:09.323320 --- INFO --- recipe_items Created!
2025-04-20 19:13:09.331286 --- INFO --- shopping_lists Created!
2025-04-20 19:13:09.338671 --- INFO --- shopping_list_items Created!
2025-04-20 19:13:09.347666 --- INFO --- item_locations Created!
2025-04-20 19:13:09.354290 --- INFO --- conversions Created!
2025-04-20 19:13:09.358352 --- INFO --- Admin User Created!
2025-04-20 19:13:09.373201 --- ERROR --- module 'MyDataclasses' has no attribute 'ZonePayload'
2025-04-20 19:14:30.040093 --- INFO --- logins Created!
2025-04-20 19:14:30.047388 --- INFO --- sites Created!
2025-04-20 19:14:30.053137 --- INFO --- roles Created!
2025-04-20 19:14:30.058339 --- INFO --- units Created!
2025-04-20 19:14:30.066445 --- INFO --- cost_layers Created!
2025-04-20 19:14:30.074589 --- INFO --- linked_items Created!
2025-04-20 19:14:30.080372 --- INFO --- brands Created!
2025-04-20 19:14:30.088112 --- INFO --- food_info Created!
2025-04-20 19:14:30.095435 --- INFO --- item_info Created!
2025-04-20 19:14:30.104268 --- INFO --- zones Created!
2025-04-20 19:14:30.111070 --- INFO --- locations Created!
2025-04-20 19:14:30.118747 --- INFO --- logistics_info Created!
2025-04-20 19:14:30.126464 --- INFO --- transactions Created!
2025-04-20 19:14:30.136123 --- INFO --- item Created!
2025-04-20 19:14:30.142326 --- INFO --- vendors Created!
2025-04-20 19:14:30.150179 --- INFO --- groups Created!
2025-04-20 19:14:30.159002 --- INFO --- group_items Created!
2025-04-20 19:14:30.165728 --- INFO --- receipts Created!
2025-04-20 19:14:30.173195 --- INFO --- receipt_items Created!
2025-04-20 19:14:30.180307 --- INFO --- recipes Created!
2025-04-20 19:14:30.188377 --- INFO --- recipe_items Created!
2025-04-20 19:14:30.195275 --- INFO --- shopping_lists Created!
2025-04-20 19:14:30.204333 --- INFO --- shopping_list_items Created!
2025-04-20 19:14:30.212189 --- INFO --- item_locations Created!
2025-04-20 19:14:30.218980 --- INFO --- conversions Created!
2025-04-20 19:14:30.223144 --- INFO --- Admin User Created!
2025-04-20 19:14:30.237347 --- ERROR --- module 'MyDataclasses' has no attribute 'ZonePayload'
2025-04-20 19:15:37.294592 --- INFO --- logins Created!
2025-04-20 19:15:37.300684 --- INFO --- sites Created!
2025-04-20 19:15:37.306214 --- INFO --- roles Created!
2025-04-20 19:15:37.310743 --- INFO --- units Created!
2025-04-20 19:15:37.320253 --- INFO --- cost_layers Created!
2025-04-20 19:15:37.328204 --- INFO --- linked_items Created!
2025-04-20 19:15:37.334340 --- INFO --- brands Created!
2025-04-20 19:15:37.341236 --- INFO --- food_info Created!
2025-04-20 19:15:37.348720 --- INFO --- item_info Created!
2025-04-20 19:15:37.358766 --- INFO --- zones Created!
2025-04-20 19:15:37.365313 --- INFO --- locations Created!
2025-04-20 19:15:37.373227 --- INFO --- logistics_info Created!
2025-04-20 19:15:37.379296 --- INFO --- transactions Created!
2025-04-20 19:15:37.387641 --- INFO --- item Created!
2025-04-20 19:15:37.395209 --- INFO --- vendors Created!
2025-04-20 19:15:37.402427 --- INFO --- groups Created!
2025-04-20 19:15:37.410391 --- INFO --- group_items Created!
2025-04-20 19:15:37.418421 --- INFO --- receipts Created!
2025-04-20 19:15:37.425524 --- INFO --- receipt_items Created!
2025-04-20 19:15:37.432240 --- INFO --- recipes Created!
2025-04-20 19:15:37.441206 --- INFO --- recipe_items Created!
2025-04-20 19:15:37.448373 --- INFO --- shopping_lists Created!
2025-04-20 19:15:37.456919 --- INFO --- shopping_list_items Created!
2025-04-20 19:15:37.465857 --- INFO --- item_locations Created!
2025-04-20 19:15:37.471974 --- INFO --- conversions Created!
2025-04-20 19:15:37.477332 --- INFO --- Admin User Created!
2025-04-20 19:15:37.488490 --- ERROR --- module 'MyDataclasses' has no attribute 'ZonePayload'
2025-04-20 19:16:04.245426 --- INFO --- logins Created!
2025-04-20 19:16:04.252369 --- INFO --- sites Created!
2025-04-20 19:16:04.257457 --- INFO --- roles Created!
2025-04-20 19:16:04.262528 --- INFO --- units Created!
2025-04-20 19:16:04.268813 --- INFO --- cost_layers Created!
2025-04-20 19:16:04.277403 --- INFO --- linked_items Created!
2025-04-20 19:16:04.282454 --- INFO --- brands Created!
2025-04-20 19:16:04.289349 --- INFO --- food_info Created!
2025-04-20 19:16:04.297588 --- INFO --- item_info Created!
2025-04-20 19:16:04.304872 --- INFO --- zones Created!
2025-04-20 19:16:04.312242 --- INFO --- locations Created!
2025-04-20 19:16:04.320032 --- INFO --- logistics_info Created!
2025-04-20 19:16:04.327526 --- INFO --- transactions Created!
2025-04-20 19:16:04.336107 --- INFO --- item Created!
2025-04-20 19:16:04.342583 --- INFO --- vendors Created!
2025-04-20 19:16:04.350295 --- INFO --- groups Created!
2025-04-20 19:16:04.357531 --- INFO --- group_items Created!
2025-04-20 19:16:04.365626 --- INFO --- receipts Created!
2025-04-20 19:16:04.372230 --- INFO --- receipt_items Created!
2025-04-20 19:16:04.379414 --- INFO --- recipes Created!
2025-04-20 19:16:04.387527 --- INFO --- recipe_items Created!
2025-04-20 19:16:04.394398 --- INFO --- shopping_lists Created!
2025-04-20 19:16:04.403440 --- INFO --- shopping_list_items Created!
2025-04-20 19:16:04.410510 --- INFO --- item_locations Created!
2025-04-20 19:16:04.418073 --- INFO --- conversions Created!
2025-04-20 19:16:04.423218 --- INFO --- Admin User Created!
2025-04-20 19:16:04.436197 --- ERROR --- module 'MyDataclasses' has no attribute 'ZonePayload'
2025-04-20 19:16:58.466588 --- INFO --- logins Created!
2025-04-20 19:16:58.472688 --- INFO --- sites Created!
2025-04-20 19:16:58.477737 --- INFO --- roles Created!
2025-04-20 19:16:58.481251 --- INFO --- units Created!
2025-04-20 19:16:58.489772 --- INFO --- cost_layers Created!
2025-04-20 19:16:58.497672 --- INFO --- linked_items Created!
2025-04-20 19:16:58.502626 --- INFO --- brands Created!
2025-04-20 19:16:58.509339 --- INFO --- food_info Created!
2025-04-20 19:16:58.516796 --- INFO --- item_info Created!
2025-04-20 19:16:58.524536 --- INFO --- zones Created!
2025-04-20 19:16:58.532376 --- INFO --- locations Created!
2025-04-20 19:16:58.540104 --- INFO --- logistics_info Created!
2025-04-20 19:16:58.547122 --- INFO --- transactions Created!
2025-04-20 19:16:58.555955 --- INFO --- item Created!
2025-04-20 19:16:58.562475 --- INFO --- vendors Created!
2025-04-20 19:16:58.570565 --- INFO --- groups Created!
2025-04-20 19:16:58.578991 --- INFO --- group_items Created!
2025-04-20 19:16:58.586669 --- INFO --- receipts Created!
2025-04-20 19:16:58.594838 --- INFO --- receipt_items Created!
2025-04-20 19:16:58.601902 --- INFO --- recipes Created!
2025-04-20 19:16:58.608915 --- INFO --- recipe_items Created!
2025-04-20 19:16:58.617684 --- INFO --- shopping_lists Created!
2025-04-20 19:16:58.625427 --- INFO --- shopping_list_items Created!
2025-04-20 19:16:58.635479 --- INFO --- item_locations Created!
2025-04-20 19:16:58.642372 --- INFO --- conversions Created!
2025-04-20 19:16:58.646415 --- INFO --- Admin User Created!
2025-04-20 19:16:58.668418 --- ERROR --- module 'MyDataclasses' has no attribute 'VendorPayload'
2025-04-20 19:17:15.674844 --- INFO --- logins Created!
2025-04-20 19:17:15.682368 --- INFO --- sites Created!
2025-04-20 19:17:15.687761 --- INFO --- roles Created!
2025-04-20 19:17:15.692281 --- INFO --- units Created!
2025-04-20 19:17:15.700205 --- INFO --- cost_layers Created!
2025-04-20 19:17:15.707736 --- INFO --- linked_items Created!
2025-04-20 19:17:15.714280 --- INFO --- brands Created!
2025-04-20 19:17:15.720500 --- INFO --- food_info Created!
2025-04-20 19:17:15.728902 --- INFO --- item_info Created!
2025-04-20 19:17:15.735575 --- INFO --- zones Created!
2025-04-20 19:17:15.743577 --- INFO --- locations Created!
2025-04-20 19:17:15.750537 --- INFO --- logistics_info Created!
2025-04-20 19:17:15.757739 --- INFO --- transactions Created!
2025-04-20 19:17:15.767727 --- INFO --- item Created!
2025-04-20 19:17:15.773470 --- INFO --- vendors Created!
2025-04-20 19:17:15.781078 --- INFO --- groups Created!
2025-04-20 19:17:15.791354 --- INFO --- group_items Created!
2025-04-20 19:17:15.799134 --- INFO --- receipts Created!
2025-04-20 19:17:15.806841 --- INFO --- receipt_items Created!
2025-04-20 19:17:15.813370 --- INFO --- recipes Created!
2025-04-20 19:17:15.821296 --- INFO --- recipe_items Created!
2025-04-20 19:17:15.828999 --- INFO --- shopping_lists Created!
2025-04-20 19:17:15.837757 --- INFO --- shopping_list_items Created!
2025-04-20 19:17:15.846351 --- INFO --- item_locations Created!
2025-04-20 19:17:15.853372 --- INFO --- conversions Created!
2025-04-20 19:17:15.856625 --- INFO --- Admin User Created!
2025-04-20 19:17:15.871202 --- ERROR --- module 'MyDataclasses' has no attribute 'VendorPayload'
2025-04-20 19:17:59.229086 --- INFO --- logins Created!
2025-04-20 19:17:59.236178 --- INFO --- sites Created!
2025-04-20 19:17:59.241179 --- INFO --- roles Created!
2025-04-20 19:17:59.244639 --- INFO --- units Created!
2025-04-20 19:17:59.252556 --- INFO --- cost_layers Created!
2025-04-20 19:17:59.258931 --- INFO --- linked_items Created!
2025-04-20 19:17:59.265640 --- INFO --- brands Created!
2025-04-20 19:17:59.271635 --- INFO --- food_info Created!
2025-04-20 19:17:59.281402 --- INFO --- item_info Created!
2025-04-20 19:17:59.289153 --- INFO --- zones Created!
2025-04-20 19:17:59.295595 --- INFO --- locations Created!
2025-04-20 19:17:59.303510 --- INFO --- logistics_info Created!
2025-04-20 19:17:59.310620 --- INFO --- transactions Created!
2025-04-20 19:17:59.318745 --- INFO --- item Created!
2025-04-20 19:17:59.325400 --- INFO --- vendors Created!
2025-04-20 19:17:59.333182 --- INFO --- groups Created!
2025-04-20 19:17:59.341367 --- INFO --- group_items Created!
2025-04-20 19:17:59.348914 --- INFO --- receipts Created!
2025-04-20 19:17:59.355674 --- INFO --- receipt_items Created!
2025-04-20 19:17:59.363428 --- INFO --- recipes Created!
2025-04-20 19:17:59.371858 --- INFO --- recipe_items Created!
2025-04-20 19:17:59.378939 --- INFO --- shopping_lists Created!
2025-04-20 19:17:59.388138 --- INFO --- shopping_list_items Created!
2025-04-20 19:17:59.395999 --- INFO --- item_locations Created!
2025-04-20 19:17:59.402528 --- INFO --- conversions Created!
2025-04-20 19:17:59.406967 --- INFO --- Admin User Created!
2025-04-20 19:43:36.914652 --- INFO --- logins Created!
2025-04-20 19:43:36.922436 --- INFO --- sites Created!
2025-04-20 19:43:36.927458 --- INFO --- roles Created!
2025-04-20 19:43:36.931555 --- INFO --- units Created!
2025-04-20 19:43:36.939094 --- INFO --- cost_layers Created!
2025-04-20 19:43:36.947628 --- INFO --- linked_items Created!
2025-04-20 19:43:36.953631 --- INFO --- brands Created!
2025-04-20 19:43:36.959852 --- INFO --- food_info Created!
2025-04-20 19:43:36.968778 --- INFO --- item_info Created!
2025-04-20 19:43:36.976558 --- INFO --- zones Created!
2025-04-20 19:43:36.983310 --- INFO --- locations Created!
2025-04-20 19:43:36.990780 --- INFO --- logistics_info Created!
2025-04-20 19:43:36.998082 --- INFO --- transactions Created!
2025-04-20 19:43:37.005780 --- INFO --- item Created!
2025-04-20 19:43:37.013460 --- INFO --- vendors Created!
2025-04-20 19:43:37.020215 --- INFO --- groups Created!
2025-04-20 19:43:37.028782 --- INFO --- group_items Created!
2025-04-20 19:43:37.036257 --- INFO --- receipts Created!
2025-04-20 19:43:37.043565 --- INFO --- receipt_items Created!
2025-04-20 19:43:37.049814 --- INFO --- recipes Created!
2025-04-20 19:43:37.057702 --- INFO --- recipe_items Created!
2025-04-20 19:43:37.065761 --- INFO --- shopping_lists Created!
2025-04-20 19:43:37.073370 --- INFO --- shopping_list_items Created!
2025-04-20 19:43:37.081053 --- INFO --- item_locations Created!
2025-04-20 19:43:37.088088 --- INFO --- conversions Created!
2025-04-20 19:43:37.093156 --- INFO --- Admin User Created!
2025-04-20 19:45:44.395265 --- INFO --- item_info DROPPED!
2025-04-20 19:45:44.405900 --- INFO --- items DROPPED!
2025-04-20 19:45:44.414804 --- INFO --- cost_layers DROPPED!
2025-04-20 19:45:44.422703 --- INFO --- linked_items DROPPED!
2025-04-20 19:45:44.430346 --- INFO --- transactions DROPPED!
2025-04-20 19:45:44.437350 --- INFO --- brands DROPPED!
2025-04-20 19:45:44.444569 --- INFO --- food_info DROPPED!
2025-04-20 19:45:44.452885 --- INFO --- logistics_info DROPPED!
2025-04-20 19:48:31.583108 --- INFO --- item_info DROPPED!
2025-04-20 19:48:31.591460 --- INFO --- items DROPPED!
2025-04-20 19:48:31.596408 --- INFO --- cost_layers DROPPED!
2025-04-20 19:48:31.601566 --- INFO --- linked_items DROPPED!
2025-04-20 19:48:31.607329 --- INFO --- transactions DROPPED!
2025-04-20 19:48:31.611822 --- INFO --- brands DROPPED!
2025-04-20 19:48:31.615984 --- INFO --- food_info DROPPED!
2025-04-20 19:48:31.621444 --- INFO --- logistics_info DROPPED!
2025-04-20 19:51:08.211394 --- INFO --- item_info DROPPED!
2025-04-20 19:51:08.219628 --- INFO --- items DROPPED!
2025-04-20 19:51:08.225163 --- INFO --- cost_layers DROPPED!
2025-04-20 19:51:08.231236 --- INFO --- linked_items DROPPED!
2025-04-20 19:51:08.236599 --- INFO --- transactions DROPPED!
2025-04-20 19:51:08.241802 --- INFO --- brands DROPPED!
2025-04-20 19:51:08.247341 --- INFO --- food_info DROPPED!
2025-04-20 19:51:08.251883 --- INFO --- logistics_info DROPPED!
2025-04-20 19:52:54.948592 --- INFO --- item_info DROPPED!
2025-04-20 19:52:54.956447 --- INFO --- items DROPPED!
2025-04-20 19:52:54.962023 --- INFO --- cost_layers DROPPED!
2025-04-20 19:52:54.967556 --- INFO --- linked_items DROPPED!
2025-04-20 19:52:54.973165 --- INFO --- transactions DROPPED!
2025-04-20 19:52:54.976632 --- INFO --- brands DROPPED!
2025-04-20 19:52:54.981398 --- INFO --- food_info DROPPED!
2025-04-20 19:52:54.985072 --- INFO --- logistics_info DROPPED!
2025-04-20 19:52:54.989456 --- ERROR --- DatabaseError(message='table "testsite_zones" does not exist', payload=DROP TABLE TestSite_zones CASCADE;, sql='zones')
2025-04-20 19:56:25.272595 --- INFO --- item_info DROPPED!
2025-04-20 19:56:25.282064 --- INFO --- items DROPPED!
2025-04-20 19:56:25.287581 --- INFO --- cost_layers DROPPED!
2025-04-20 19:56:25.293563 --- INFO --- linked_items DROPPED!
2025-04-20 19:56:25.298578 --- INFO --- transactions DROPPED!
2025-04-20 19:56:25.303670 --- INFO --- brands DROPPED!
2025-04-20 19:56:25.307742 --- INFO --- food_info DROPPED!
2025-04-20 19:56:25.313677 --- INFO --- logistics_info DROPPED!
2025-04-20 19:56:25.317716 --- INFO --- zones DROPPED!
2025-04-20 19:56:25.325855 --- INFO --- locations DROPPED!
2025-04-20 19:56:25.330990 --- INFO --- vendors DROPPED!
2025-04-20 19:56:25.337850 --- INFO --- group_items DROPPED!
2025-04-20 19:56:25.346723 --- INFO --- groups DROPPED!
2025-04-20 19:56:25.354480 --- INFO --- receipt_items DROPPED!
2025-04-20 19:56:25.362108 --- INFO --- receipts DROPPED!
2025-04-20 19:56:25.369684 --- INFO --- recipe_items DROPPED!
2025-04-20 19:56:25.377807 --- INFO --- recipes DROPPED!
2025-04-20 19:56:25.385620 --- INFO --- shopping_list_items DROPPED!
2025-04-20 19:56:25.393541 --- INFO --- shopping_lists DROPPED!
2025-04-20 19:56:25.401384 --- INFO --- item_locations DROPPED!
2025-04-20 19:56:25.405523 --- INFO --- conversions DROPPED!
2025-04-20 19:58:10.901757 --- INFO --- item_info DROPPED!
2025-04-20 19:58:10.911845 --- INFO --- items DROPPED!
2025-04-20 19:58:10.917872 --- INFO --- cost_layers DROPPED!
2025-04-20 19:58:10.922065 --- INFO --- linked_items DROPPED!
2025-04-20 19:58:10.928478 --- INFO --- transactions DROPPED!
2025-04-20 19:58:10.932582 --- INFO --- brands DROPPED!
2025-04-20 19:58:10.936689 --- INFO --- food_info DROPPED!
2025-04-20 19:58:10.941754 --- INFO --- logistics_info DROPPED!
2025-04-20 19:58:36.985976 --- INFO --- item_info DROPPED!
2025-04-20 19:58:36.996149 --- INFO --- items DROPPED!
2025-04-20 19:58:37.001166 --- INFO --- cost_layers DROPPED!
2025-04-20 19:58:37.006862 --- INFO --- linked_items DROPPED!
2025-04-20 19:58:37.012179 --- INFO --- transactions DROPPED!
2025-04-20 19:58:37.016694 --- INFO --- brands DROPPED!
2025-04-20 19:58:37.020758 --- INFO --- food_info DROPPED!
2025-04-20 19:58:37.026061 --- INFO --- logistics_info DROPPED!
2025-04-20 20:07:37.732426 --- INFO --- item_info DROPPED!
2025-04-20 20:07:37.740467 --- INFO --- items DROPPED!
2025-04-20 20:07:37.745042 --- INFO --- cost_layers DROPPED!
2025-04-20 20:07:37.750893 --- INFO --- linked_items DROPPED!
2025-04-20 20:07:37.754429 --- INFO --- transactions DROPPED!
2025-04-20 20:07:37.758729 --- INFO --- brands DROPPED!
2025-04-20 20:07:37.763189 --- INFO --- food_info DROPPED!
2025-04-20 20:07:37.767020 --- INFO --- logistics_info DROPPED!
2025-04-20 20:07:37.771055 --- INFO --- zones DROPPED!
2025-04-20 20:07:37.775628 --- INFO --- locations DROPPED!
2025-04-20 20:07:37.779735 --- INFO --- vendors DROPPED!
2025-04-20 20:07:37.784456 --- INFO --- group_items DROPPED!
2025-04-20 20:07:37.788915 --- INFO --- groups DROPPED!
2025-04-20 20:07:37.793395 --- INFO --- receipt_items DROPPED!
2025-04-20 20:07:37.798563 --- INFO --- receipts DROPPED!
2025-04-20 20:07:37.803691 --- INFO --- recipe_items DROPPED!
2025-04-20 20:07:37.808903 --- INFO --- recipes DROPPED!
2025-04-20 20:07:37.813005 --- INFO --- shopping_list_items DROPPED!
2025-04-20 20:07:37.818899 --- INFO --- shopping_lists DROPPED!
2025-04-20 20:07:37.823062 --- INFO --- item_locations DROPPED!
2025-04-20 20:07:37.827577 --- INFO --- conversions DROPPED!
2025-04-20 20:10:51.725827 --- INFO --- item_info DROPPED!
2025-04-20 20:10:51.735709 --- INFO --- items DROPPED!
2025-04-20 20:10:51.741311 --- INFO --- cost_layers DROPPED!
2025-04-20 20:10:51.745890 --- INFO --- linked_items DROPPED!
2025-04-20 20:10:51.750915 --- INFO --- transactions DROPPED!
2025-04-20 20:10:51.755599 --- INFO --- brands DROPPED!
2025-04-20 20:10:51.761188 --- INFO --- food_info DROPPED!
2025-04-20 20:10:51.765063 --- INFO --- logistics_info DROPPED!
2025-04-20 20:10:51.769585 --- INFO --- zones DROPPED!
2025-04-20 20:10:51.774893 --- INFO --- locations DROPPED!
2025-04-20 20:10:51.779100 --- INFO --- vendors DROPPED!
2025-04-20 20:10:51.784704 --- INFO --- group_items DROPPED!
2025-04-20 20:10:51.789211 --- INFO --- groups DROPPED!
2025-04-20 20:10:51.793357 --- INFO --- receipt_items DROPPED!
2025-04-20 20:10:51.799194 --- INFO --- receipts DROPPED!
2025-04-20 20:10:51.803289 --- INFO --- recipe_items DROPPED!
2025-04-20 20:10:51.808076 --- INFO --- recipes DROPPED!
2025-04-20 20:10:51.813010 --- INFO --- shopping_list_items DROPPED!
2025-04-20 20:10:51.817596 --- INFO --- shopping_lists DROPPED!
2025-04-20 20:10:51.821643 --- INFO --- item_locations DROPPED!
2025-04-20 20:10:51.826587 --- INFO --- conversions DROPPED!
2025-04-20 20:11:14.056575 --- INFO --- logins Created!
2025-04-20 20:11:14.063971 --- INFO --- sites Created!
2025-04-20 20:11:14.069365 --- INFO --- roles Created!
2025-04-20 20:11:14.073411 --- INFO --- units Created!
2025-04-20 20:11:14.082962 --- INFO --- cost_layers Created!
2025-04-20 20:11:14.091859 --- INFO --- linked_items Created!
2025-04-20 20:11:14.096907 --- INFO --- brands Created!
2025-04-20 20:11:14.104713 --- INFO --- food_info Created!
2025-04-20 20:11:14.110843 --- INFO --- item_info Created!
2025-04-20 20:11:14.119202 --- INFO --- zones Created!
2025-04-20 20:11:14.125756 --- INFO --- locations Created!
2025-04-20 20:11:14.131825 --- INFO --- logistics_info Created!
2025-04-20 20:11:14.138848 --- INFO --- transactions Created!
2025-04-20 20:11:14.146927 --- INFO --- item Created!
2025-04-20 20:11:14.152932 --- INFO --- vendors Created!
2025-04-20 20:11:14.159374 --- INFO --- groups Created!
2025-04-20 20:11:14.166932 --- INFO --- group_items Created!
2025-04-20 20:11:14.174750 --- INFO --- receipts Created!
2025-04-20 20:11:14.181847 --- INFO --- receipt_items Created!
2025-04-20 20:11:14.186633 --- INFO --- recipes Created!
2025-04-20 20:11:14.194852 --- INFO --- recipe_items Created!
2025-04-20 20:11:14.201475 --- INFO --- shopping_lists Created!
2025-04-20 20:11:14.209506 --- INFO --- shopping_list_items Created!
2025-04-20 20:11:14.217085 --- INFO --- item_locations Created!
2025-04-20 20:11:14.224012 --- INFO --- conversions Created!
2025-04-20 20:11:14.227800 --- INFO --- Admin User Created!
2025-04-20 20:11:14.240274 --- ERROR --- DatabaseError(message='tuple index out of range', payload=('main', ''), sql='INSERT INTO testsite_zones(name, description, site_id) VALUES (%s, %s, %s) RETURNING *;')
2025-04-20 20:12:29.007584 --- INFO --- logins Created!
2025-04-20 20:12:29.015493 --- INFO --- sites Created!
2025-04-20 20:12:29.021142 --- INFO --- roles Created!
2025-04-20 20:12:29.025026 --- INFO --- units Created!
2025-04-20 20:12:29.032140 --- INFO --- cost_layers Created!
2025-04-20 20:12:29.039496 --- INFO --- linked_items Created!
2025-04-20 20:12:29.046115 --- INFO --- brands Created!
2025-04-20 20:12:29.052237 --- INFO --- food_info Created!
2025-04-20 20:12:29.060538 --- INFO --- item_info Created!
2025-04-20 20:12:29.066956 --- INFO --- zones Created!
2025-04-20 20:12:29.073431 --- INFO --- locations Created!
2025-04-20 20:12:29.082564 --- INFO --- logistics_info Created!
2025-04-20 20:12:29.090000 --- INFO --- transactions Created!
2025-04-20 20:12:29.098688 --- INFO --- item Created!
2025-04-20 20:12:29.104873 --- INFO --- vendors Created!
2025-04-20 20:12:29.112717 --- INFO --- groups Created!
2025-04-20 20:12:29.120101 --- INFO --- group_items Created!
2025-04-20 20:12:29.128686 --- INFO --- receipts Created!
2025-04-20 20:12:29.135147 --- INFO --- receipt_items Created!
2025-04-20 20:12:29.141797 --- INFO --- recipes Created!
2025-04-20 20:12:29.151352 --- INFO --- recipe_items Created!
2025-04-20 20:12:29.158714 --- INFO --- shopping_lists Created!
2025-04-20 20:12:29.166933 --- INFO --- shopping_list_items Created!
2025-04-20 20:12:29.175124 --- INFO --- item_locations Created!
2025-04-20 20:12:29.181717 --- INFO --- conversions Created!
2025-04-20 20:12:29.186271 --- INFO --- Admin User Created!
2025-04-20 20:12:38.923616 --- INFO --- item_info DROPPED!
2025-04-20 20:12:38.932843 --- INFO --- items DROPPED!
2025-04-20 20:12:38.937372 --- INFO --- cost_layers DROPPED!
2025-04-20 20:12:38.942503 --- INFO --- linked_items DROPPED!
2025-04-20 20:12:38.946721 --- INFO --- transactions DROPPED!
2025-04-20 20:12:38.951727 --- INFO --- brands DROPPED!
2025-04-20 20:12:38.955969 --- INFO --- food_info DROPPED!
2025-04-20 20:12:38.961360 --- INFO --- logistics_info DROPPED!
2025-04-20 20:12:38.966848 --- INFO --- zones DROPPED!
2025-04-20 20:12:38.970884 --- INFO --- locations DROPPED!
2025-04-20 20:12:38.975885 --- INFO --- vendors DROPPED!
2025-04-20 20:12:38.980966 --- INFO --- group_items DROPPED!
2025-04-20 20:12:38.985746 --- INFO --- groups DROPPED!
2025-04-20 20:12:38.989709 --- INFO --- receipt_items DROPPED!
2025-04-20 20:12:38.994949 --- INFO --- receipts DROPPED!
2025-04-20 20:12:38.998971 --- INFO --- recipe_items DROPPED!
2025-04-20 20:12:39.004057 --- INFO --- recipes DROPPED!
2025-04-20 20:12:39.009013 --- INFO --- shopping_list_items DROPPED!
2025-04-20 20:12:39.013298 --- INFO --- shopping_lists DROPPED!
2025-04-20 20:12:39.017813 --- INFO --- item_locations DROPPED!
2025-04-20 20:12:39.022064 --- INFO --- conversions DROPPED!
2025-04-20 20:32:27.356365 --- INFO --- logins Created!
2025-04-20 20:32:27.364095 --- INFO --- sites Created!
2025-04-20 20:32:27.368637 --- INFO --- roles Created!
2025-04-20 20:32:27.374196 --- INFO --- units Created!
2025-04-20 20:32:27.381877 --- INFO --- cost_layers Created!
2025-04-20 20:32:27.389138 --- INFO --- linked_items Created!
2025-04-20 20:32:27.395362 --- INFO --- brands Created!
2025-04-20 20:32:27.402835 --- INFO --- food_info Created!
2025-04-20 20:32:27.409510 --- INFO --- item_info Created!
2025-04-20 20:32:27.417471 --- INFO --- zones Created!
2025-04-20 20:32:27.424663 --- INFO --- locations Created!
2025-04-20 20:32:27.433133 --- INFO --- logistics_info Created!
2025-04-20 20:32:27.439780 --- INFO --- transactions Created!
2025-04-20 20:32:27.448585 --- INFO --- item Created!
2025-04-20 20:32:27.455303 --- INFO --- vendors Created!
2025-04-20 20:32:27.462869 --- INFO --- groups Created!
2025-04-20 20:32:27.470653 --- INFO --- group_items Created!
2025-04-20 20:32:27.478653 --- INFO --- receipts Created!
2025-04-20 20:32:27.486804 --- INFO --- receipt_items Created!
2025-04-20 20:32:27.493141 --- INFO --- recipes Created!
2025-04-20 20:32:27.500870 --- INFO --- recipe_items Created!
2025-04-20 20:32:27.507862 --- INFO --- shopping_lists Created!
2025-04-20 20:32:27.515340 --- INFO --- shopping_list_items Created!
2025-04-20 20:32:27.524287 --- INFO --- item_locations Created!
2025-04-20 20:32:27.532196 --- INFO --- conversions Created!
2025-04-20 20:32:27.535331 --- INFO --- Admin User Created!
2025-04-20 20:33:25.173233 --- INFO --- item_info DROPPED!
2025-04-20 20:33:25.182788 --- INFO --- items DROPPED!
2025-04-20 20:33:25.187318 --- INFO --- cost_layers DROPPED!
2025-04-20 20:33:25.193746 --- INFO --- linked_items DROPPED!
2025-04-20 20:33:25.197263 --- INFO --- transactions DROPPED!
2025-04-20 20:33:25.202565 --- INFO --- brands DROPPED!
2025-04-20 20:33:25.207124 --- INFO --- food_info DROPPED!
2025-04-20 20:33:25.213012 --- INFO --- logistics_info DROPPED!
2025-04-20 20:33:25.218554 --- INFO --- zones DROPPED!
2025-04-20 20:33:25.222613 --- INFO --- locations DROPPED!
2025-04-20 20:33:25.227136 --- INFO --- vendors DROPPED!
2025-04-20 20:33:25.232695 --- INFO --- group_items DROPPED!
2025-04-20 20:33:25.237264 --- INFO --- groups DROPPED!
2025-04-20 20:33:25.241580 --- INFO --- receipt_items DROPPED!
2025-04-20 20:33:25.246657 --- INFO --- receipts DROPPED!
2025-04-20 20:33:25.251050 --- INFO --- recipe_items DROPPED!
2025-04-20 20:33:25.255068 --- INFO --- recipes DROPPED!
2025-04-20 20:33:25.260690 --- INFO --- shopping_list_items DROPPED!
2025-04-20 20:33:25.265769 --- INFO --- shopping_lists DROPPED!
2025-04-20 20:33:25.269793 --- INFO --- item_locations DROPPED!
2025-04-20 20:33:25.273824 --- INFO --- conversions DROPPED!
2025-04-20 20:34:25.344816 --- INFO --- logins Created!
2025-04-20 20:34:25.352155 --- INFO --- sites Created!
2025-04-20 20:34:25.357696 --- INFO --- roles Created!
2025-04-20 20:34:25.363010 --- INFO --- units Created!
2025-04-20 20:34:25.370030 --- INFO --- cost_layers Created!
2025-04-20 20:34:25.377554 --- INFO --- linked_items Created!
2025-04-20 20:34:25.383668 --- INFO --- brands Created!
2025-04-20 20:34:25.390875 --- INFO --- food_info Created!
2025-04-20 20:34:25.397424 --- INFO --- item_info Created!
2025-04-20 20:34:25.405761 --- INFO --- zones Created!
2025-04-20 20:34:25.412801 --- INFO --- locations Created!
2025-04-20 20:34:25.420564 --- INFO --- logistics_info Created!
2025-04-20 20:34:25.427949 --- INFO --- transactions Created!
2025-04-20 20:34:25.435344 --- INFO --- item Created!
2025-04-20 20:34:25.442389 --- INFO --- vendors Created!
2025-04-20 20:34:25.449534 --- INFO --- groups Created!
2025-04-20 20:34:25.457550 --- INFO --- group_items Created!
2025-04-20 20:34:25.465405 --- INFO --- receipts Created!
2025-04-20 20:34:25.471947 --- INFO --- receipt_items Created!
2025-04-20 20:34:25.478815 --- INFO --- recipes Created!
2025-04-20 20:34:25.486803 --- INFO --- recipe_items Created!
2025-04-20 20:34:25.495032 --- INFO --- shopping_lists Created!
2025-04-20 20:34:25.503567 --- INFO --- shopping_list_items Created!
2025-04-20 20:34:25.511863 --- INFO --- item_locations Created!
2025-04-20 20:34:25.517088 --- INFO --- conversions Created!
2025-04-20 20:34:25.522653 --- INFO --- Admin User Created!
2025-04-20 20:34:40.207702 --- INFO --- item_info DROPPED!
2025-04-20 20:34:40.217297 --- INFO --- items DROPPED!
2025-04-20 20:34:40.223866 --- INFO --- cost_layers DROPPED!
2025-04-20 20:34:40.228928 --- INFO --- linked_items DROPPED!
2025-04-20 20:34:40.233942 --- INFO --- transactions DROPPED!
2025-04-20 20:34:40.238435 --- INFO --- brands DROPPED!
2025-04-20 20:34:40.243588 --- INFO --- food_info DROPPED!
2025-04-20 20:34:40.248615 --- INFO --- logistics_info DROPPED!
2025-04-20 20:34:40.252808 --- INFO --- zones DROPPED!
2025-04-20 20:34:40.258575 --- INFO --- locations DROPPED!
2025-04-20 20:34:40.262926 --- INFO --- vendors DROPPED!
2025-04-20 20:34:40.267004 --- INFO --- group_items DROPPED!
2025-04-20 20:34:40.272103 --- INFO --- groups DROPPED!
2025-04-20 20:34:40.276648 --- INFO --- receipt_items DROPPED!
2025-04-20 20:34:40.281217 --- INFO --- receipts DROPPED!
2025-04-20 20:34:40.287034 --- INFO --- recipe_items DROPPED!
2025-04-20 20:34:40.291009 --- INFO --- recipes DROPPED!
2025-04-20 20:34:40.295365 --- INFO --- shopping_list_items DROPPED!
2025-04-20 20:34:40.301096 --- INFO --- shopping_lists DROPPED!
2025-04-20 20:34:40.305141 --- INFO --- item_locations DROPPED!
2025-04-20 20:34:40.310973 --- INFO --- conversions DROPPED!
2025-04-20 21:03:22.361206 --- INFO --- logins Created!
2025-04-20 21:03:22.368210 --- INFO --- sites Created!
2025-04-20 21:03:22.372737 --- INFO --- roles Created!
2025-04-20 21:03:22.376787 --- INFO --- units Created!
2025-04-20 21:03:22.384390 --- INFO --- cost_layers Created!
2025-04-20 21:03:22.392461 --- INFO --- linked_items Created!
2025-04-20 21:03:22.397727 --- INFO --- brands Created!
2025-04-20 21:03:22.403723 --- INFO --- food_info Created!
2025-04-20 21:03:22.411420 --- INFO --- item_info Created!
2025-04-20 21:03:22.418983 --- INFO --- zones Created!
2025-04-20 21:03:22.426220 --- INFO --- locations Created!
2025-04-20 21:03:22.433434 --- INFO --- logistics_info Created!
2025-04-20 21:03:22.440189 --- INFO --- transactions Created!
2025-04-20 21:03:22.448293 --- INFO --- item Created!
2025-04-20 21:03:22.455434 --- INFO --- vendors Created!
2025-04-20 21:03:22.462439 --- INFO --- groups Created!
2025-04-20 21:03:22.469950 --- INFO --- group_items Created!
2025-04-20 21:03:22.477434 --- INFO --- receipts Created!
2025-04-20 21:03:22.484434 --- INFO --- receipt_items Created!
2025-04-20 21:03:22.491212 --- INFO --- recipes Created!
2025-04-20 21:03:22.499338 --- INFO --- recipe_items Created!
2025-04-20 21:03:22.506939 --- INFO --- shopping_lists Created!
2025-04-20 21:03:22.514674 --- INFO --- shopping_list_items Created!
2025-04-20 21:03:22.522661 --- INFO --- item_locations Created!
2025-04-20 21:03:22.529320 --- INFO --- conversions Created!
2025-04-20 21:03:22.533343 --- INFO --- Admin User Created!
2025-04-20 21:07:46.103441 --- INFO --- item_info DROPPED!
2025-04-20 21:07:46.113647 --- INFO --- items DROPPED!
2025-04-20 21:07:46.119615 --- INFO --- cost_layers DROPPED!
2025-04-20 21:07:46.125899 --- INFO --- linked_items DROPPED!
2025-04-20 21:07:46.130623 --- INFO --- transactions DROPPED!
2025-04-20 21:07:46.134817 --- INFO --- brands DROPPED!
2025-04-20 21:07:46.139474 --- INFO --- food_info DROPPED!
2025-04-20 21:07:46.145048 --- INFO --- logistics_info DROPPED!
2025-04-20 21:07:46.150355 --- INFO --- zones DROPPED!
2025-04-20 21:07:46.155777 --- INFO --- locations DROPPED!
2025-04-20 21:07:46.160313 --- INFO --- vendors DROPPED!
2025-04-20 21:07:46.165662 --- INFO --- group_items DROPPED!
2025-04-20 21:07:46.169670 --- INFO --- groups DROPPED!
2025-04-20 21:07:46.174114 --- INFO --- receipt_items DROPPED!
2025-04-20 21:07:46.179634 --- INFO --- receipts DROPPED!
2025-04-20 21:07:46.184753 --- INFO --- recipe_items DROPPED!
2025-04-20 21:07:46.189291 --- INFO --- recipes DROPPED!
2025-04-20 21:07:46.193588 --- INFO --- shopping_list_items DROPPED!
2025-04-20 21:07:46.197995 --- INFO --- shopping_lists DROPPED!
2025-04-20 21:07:46.203577 --- INFO --- item_locations DROPPED!
2025-04-20 21:07:46.208195 --- INFO --- conversions DROPPED!

View File

@ -5,10 +5,12 @@ import postsqldb
def dropSiteTables(conn, site_manager: MyDataclasses.SiteManager): def dropSiteTables(conn, site_manager: MyDataclasses.SiteManager):
try: try:
for table in site_manager.drop_order: for table in site_manager.drop_order:
print(table)
database.__dropTable(conn, site_manager.site_name, table) database.__dropTable(conn, site_manager.site_name, table)
with open("process.log", "a+") as file: with open("process.log", "a+") as file:
file.write(f"{datetime.datetime.now()} --- INFO --- {table} DROPPED!\n") file.write(f"{datetime.datetime.now()} --- INFO --- {table} DROPPED!\n")
except Exception as error: except Exception as error:
print(error)
raise error raise error
def setupSiteTables(conn, site_manager: MyDataclasses.SiteManager): def setupSiteTables(conn, site_manager: MyDataclasses.SiteManager):
@ -72,6 +74,7 @@ def deleteSite(site_manager: MyDataclasses.SiteManager):
site = database.deleteSitesTuple(conn, site_manager.site_name, (site['id'], ), convert=True) site = database.deleteSitesTuple(conn, site_manager.site_name, (site['id'], ), convert=True)
conn.commit()
except Exception as error: except Exception as error:
with open("process.log", "a+") as file: with open("process.log", "a+") as file:
file.write(f"{datetime.datetime.now()} --- ERROR --- {error}\n") file.write(f"{datetime.datetime.now()} --- ERROR --- {error}\n")
@ -97,19 +100,19 @@ def addSite(site_manager: MyDataclasses.SiteManager):
site_owner_id=admin_user['id'] site_owner_id=admin_user['id']
) )
site = database.insertSitesTuple(conn, site.payload(), convert=True) site = database.insertSitesTuple(conn, site.payload(), convert=True)
print("site", site)
role = MyDataclasses.RolePayload("Admin", f"Admin for {site['site_name']}", site['id']) role = MyDataclasses.RolePayload("Admin", f"Admin for {site['site_name']}", site['id'])
role = database.insertRolesTuple(conn, role.payload(), convert=True) role = database.insertRolesTuple(conn, role.payload(), convert=True)
print("role", role)
admin_user = database.updateAddLoginSitesRoles(conn, (site["id"], role["id"], admin_user["id"]), convert=True) admin_user = database.updateAddLoginSitesRoles(conn, (site["id"], role["id"], admin_user["id"]), convert=True)
print('admin_user', admin_user)
default_zone = MyDataclasses.ZonePayload(site_manager.default_zone, site['id']) default_zone = postsqldb.ZonesTable.Payload(site_manager.default_zone)
default_zone = database.insertZonesTuple(conn, site["site_name"], default_zone.payload(), convert=True) default_zone = database.insertZonesTuple(conn, site["site_name"], default_zone.payload(), convert=True)
print('default_zone', default_zone)
uuid = f"{site_manager.default_zone}@{site_manager.default_location}" uuid = f"{site_manager.default_zone}@{site_manager.default_location}"
default_location = MyDataclasses.LocationPayload(uuid, site_manager.default_location, default_zone['id']) default_location = postsqldb.LocationsTable.Payload(uuid, site_manager.default_location, default_zone['id'])
default_location = database.insertLocationsTuple(conn, site['site_name'], default_location.payload(), convert=True) default_location = database.insertLocationsTuple(conn, site['site_name'], default_location.payload(), convert=True)
print('default_location', default_location)
# need to update the default zones/locations for site. # need to update the default zones/locations for site.
payload = { payload = {
'id': site['id'], 'id': site['id'],
@ -120,8 +123,8 @@ def addSite(site_manager: MyDataclasses.SiteManager):
database.__updateTuple(conn, site_manager.site_name, f"sites", payload) database.__updateTuple(conn, site_manager.site_name, f"sites", payload)
blank_vendor = MyDataclasses.VendorPayload("None", admin_user['id']) blank_vendor = postsqldb.VendorsTable.Payload("None", admin_user['id'])
blank_brand = MyDataclasses.BrandsPayload("None") blank_brand = postsqldb.BrandsTable.Payload("None")
blank_vendor = database.insertVendorsTuple(conn, site['site_name'], blank_vendor.payload(), convert=True) blank_vendor = database.insertVendorsTuple(conn, site['site_name'], blank_vendor.payload(), convert=True)
blank_brand = database.insertBrandsTuple(conn, site['site_name'], blank_brand.payload(), convert=True) blank_brand = database.insertBrandsTuple(conn, site['site_name'], blank_brand.payload(), convert=True)

0
scripts/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

2346
scripts/postsqldb.py Normal file

File diff suppressed because it is too large Load Diff

View File

Binary file not shown.

View File

@ -0,0 +1,25 @@
from scripts import postsqldb
import config
import psycopg2
def getModalSKUs(site, payload, convert=True):
database_config = config.config()
with psycopg2.connect(**database_config) as conn:
with conn.cursor() as cur:
with open("scripts/recipes/sql/itemsModal.sql") as file:
sql = file.read().replace("%%site_name%%", site)
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
rows = [postsqldb.tupleDictionaryFactory(cur.description, row) for row in rows]
with open("scripts/recipes/sql/itemsModalCount.sql") as file:
sql = file.read().replace("%%site_name%%", site)
cur.execute(sql)
count = cur.fetchone()[0]
if rows and count:
return rows, count
return [], 0

View File

@ -5,6 +5,7 @@ from main import unfoldCostLayers
from user_api import login_required from user_api import login_required
import os import os
import postsqldb, webpush import postsqldb, webpush
from scripts.recipes import database_recipes
recipes_api = Blueprint('recipes_api', __name__) recipes_api = Blueprint('recipes_api', __name__)
@ -82,12 +83,10 @@ def getItems():
search_string = request.args.get('search_string', 10) search_string = request.args.get('search_string', 10)
site_name = session['selected_site'] site_name = session['selected_site']
offset = (page - 1) * limit offset = (page - 1) * limit
database_config = config() recordset, count = database_recipes.getModalSKUs(site_name, (limit, offset))
with psycopg2.connect(**database_config) as conn: print(recordset)
payload = (search_string, limit, offset) return jsonify({"items":recordset, "end":math.ceil(count/limit), "error":False, "message":"items fetched succesfully!"})
recordset, count = database.getItemsWithQOH(conn, site_name, payload, convert=True) return jsonify({"items":recordset, "end":math.ceil(count/limit), "error":True, "message":"There was an error with this GET statement"})
return jsonify({"items":recordset, "end":math.ceil(count['count']/limit), "error":False, "message":"items fetched succesfully!"})
return jsonify({"items":recordset, "end":math.ceil(count['count']/limit), "error":True, "message":"There was an error with this GET statement"})
@recipes_api.route('/recipe/postUpdate', methods=["POST"]) @recipes_api.route('/recipe/postUpdate', methods=["POST"])

View File

@ -0,0 +1,2 @@
SELECT item.id, item.barcode, item.item_name FROM %%site_name%%_items item
LIMIT %s OFFSET %s;

View File

@ -0,0 +1 @@
SELECT COUNT(item.*) FROM %%site_name%%_items item;

View File

@ -2,9 +2,5 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_zones(
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR(32) NOT NULL, name VARCHAR(32) NOT NULL,
description TEXT, description TEXT,
site_id INTEGER NOT NULL, UNIQUE(name)
UNIQUE(name),
CONSTRAINT fk_site
FOREIGN KEY(site_id)
REFERENCES sites(id)
); );

View File

@ -0,0 +1,6 @@
INSERT INTO logins
(username, password, email, favorites, unseen_pantry_items, unseen_groups, unseen_shopping_lists,
unseen_recipes, seen_pantry_items, seen_groups, seen_shopping_lists, seen_recipes,
sites, site_roles, system_admin, flags, row_type)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
RETURNING *;

View File

@ -1,4 +1,4 @@
INSERT INTO %%site_name%%_zones INSERT INTO %%site_name%%_zones
(name, description, site_id) (name, description)
VALUES (%s, %s, %s) VALUES (%s, %s)
RETURNING *; RETURNING *;

View File

@ -0,0 +1,16 @@
WITH passed_id AS (SELECT %s AS passed_id),
cte_login AS (
SELECT logins.* FROM logins
WHERE logins.id = (SELECT passed_id FROM passed_id)
),
cte_roles AS (
SELECT roles.*,
row_to_json(sites.*) AS site
FROM roles
LEFT JOIN sites ON sites.id = roles.site_id
WHERE roles.id = ANY(SELECT unnest(site_roles) FROM cte_login)
)
SELECT login.*,
(SELECT COALESCE(array_agg(row_to_json(r)), '{}') FROM cte_roles r) AS site_roles
FROM cte_login login;

View File

@ -1,28 +0,0 @@
async function clickRoleRow(role_id){
const roleurl = new URL(`/admin/editRole/${role_id}`, window.location.origin);
window.location.href = roleurl.toString();
}
async function fetchSites() {
const url = new URL('/admin/getSites', window.location.origin);
const response = await fetch(url);
const data = await response.json();
return data.sites;
}
async function fetchUsers(limit, page) {
const url = new URL('/admin/getUsers', window.location.origin);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
limit: limit,
page: page
}),
});
const data = await response.json();
return data.users;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -0,0 +1,489 @@
var mode = false
async function toggleDarkMode() {
let darkMode = document.getElementById("dark-mode");
darkMode.disabled = !darkMode.disabled;
mode = !mode;
if(mode){
document.getElementById('modeToggle').innerHTML = "light_mode"
document.getElementById('main_html').classList.add('uk-light')
} else {
document.getElementById('modeToggle').innerHTML = "dark_mode"
document.getElementById('main_html').classList.remove('uk-light')
}
}
if(session.user.flags.darkmode){
toggleDarkMode()
}
document.addEventListener('DOMContentLoaded', async function() {
let sites = await fetchSites()
await updateSitesPagination()
await replenishSitesTable(sites)
let roles = await fetchRoles()
await updateRolesPagination()
await replenishRolesTable(roles)
let logins = await fetchLogins()
console.log(logins)
await updateLoginsPagination()
await replenishLoginsTable(logins)
})
async function openDeleteModal(item_name, item_type, site_id) {
document.getElementById('delete_item_name').innerHTML = item_name
if(item_type == "site"){
document.getElementById("deleteSubmitButton").onclick = async function() {
await postDeleteSite(site_id, item_name)
}
}
UIkit.modal(document.getElementById('deleteConfirmation')).show();
}
// Site functions
var sites_current_page = 1
var sites_end_page = 10
var sites_limit = 25
async function fetchSites(){
const url = new URL('/admin/getSites', window.location.origin)
url.searchParams.append('page', sites_current_page)
url.searchParams.append('limit', sites_limit)
const response = await fetch(url)
data = await response.json()
sites_end_page = data.end
return data.sites
}
async function replenishSitesTable(sites){
let sitesTableBody = document.getElementById('sitesTableBody')
sitesTableBody.innerHTML = ""
for(let i=0; i < sites.length; i++){
let tableRow = document.createElement('tr')
let idCell = document.createElement('td')
idCell.innerHTML = `${sites[i].id}`
let nameCell = document.createElement('td')
nameCell.innerHTML = `${sites[i].site_name}`
let descriptionCell = document.createElement('td')
descriptionCell.innerHTML = `${sites[i].site_description}`
let opCell = document.createElement('td')
opCell.innerHTML = ``
let editOp = document.createElement('a')
editOp.setAttribute('class', 'uk-button uk-button-small uk-button-default')
editOp.innerHTML = "edit"
editOp.href = `/admin/site/${sites[i].id}`
let deleteOp = document.createElement('a')
deleteOp.setAttribute('class', 'uk-button uk-button-small uk-button-default')
deleteOp.innerHTML = "delete"
deleteOp.onclick = async function() {
await openDeleteModal(sites[i].site_name, "site", sites[i].id)
}
opCell.append(editOp, deleteOp)
tableRow.append(idCell, nameCell, descriptionCell, opCell)
sitesTableBody.append(tableRow)
}
}
async function updateSitesPagination() {
let paginationElement = document.getElementById("sitesPagination");
paginationElement.innerHTML = "";
// previous
let previousElement = document.createElement('li')
if(sites_current_page<=1){
previousElement.innerHTML = `<a><span uk-pagination-previous></span></a>`;
previousElement.classList.add('uk-disabled');
}else {
previousElement.innerHTML = `<a onclick="setSitesPage(${sites_current_page-1})"><span uk-pagination-previous></span></a>`;
}
paginationElement.append(previousElement)
//first
let firstElement = document.createElement('li')
if(sites_current_page<=1){
firstElement.innerHTML = `<a>1</a>`;
firstElement.classList.add('uk-disabled');
}else {
firstElement.innerHTML = `<a onclick="setSitesPage(1)">1</a>`;
}
paginationElement.append(firstElement)
// ...
if(sites_current_page-2>1){
let firstDotElement = document.createElement('li')
firstDotElement.classList.add('uk-disabled')
firstDotElement.innerHTML = `<span>…</span>`;
paginationElement.append(firstDotElement)
}
// last
if(sites_current_page-2>0){
let lastElement = document.createElement('li')
lastElement.innerHTML = `<a onclick=setSitesPage(${sites_current_page-1})>${sites_current_page-1}</a>`
paginationElement.append(lastElement)
}
// current
if(sites_current_page!=1 && sites_current_page != sites_end_page){
let currentElement = document.createElement('li')
currentElement.innerHTML = `<li class="uk-active"><span aria-current="page"><strong>${sites_current_page}</strong></span></li>`
paginationElement.append(currentElement)
}
// next
if(sites_current_page+2<sites_end_page+1){
let nextElement = document.createElement('li')
nextElement.innerHTML = `<a onclick=setSitesPage(${sites_current_page+1})>${sites_current_page+1}</a>`
paginationElement.append(nextElement)
}
// ...
if(sites_current_page+2<=sites_end_page){
let secondDotElement = document.createElement('li')
secondDotElement.classList.add('uk-disabled')
secondDotElement.innerHTML = `<span>…</span>`;
paginationElement.append(secondDotElement)
}
//end
let endElement = document.createElement('li')
if(sites_current_page>=sites_end_page){
endElement.innerHTML = `<a>${sites_end_page}</a>`;
endElement.classList.add('uk-disabled');
}else {
endElement.innerHTML = `<a onclick="setSitesPage(${sites_end_page})">${sites_end_page}</a>`;
}
paginationElement.append(endElement)
//next button
let nextElement = document.createElement('li')
if(sites_current_page>=sites_end_page){
nextElement.innerHTML = `<a><span uk-pagination-next></span></a>`;
nextElement.classList.add('uk-disabled');
}else {
nextElement.innerHTML = `<a onclick="setSitesPage(${sites_current_page+1})"><span uk-pagination-next></span></a>`;
}
paginationElement.append(nextElement)
}
async function setSitesPage(pageNumber){
sites_current_page = pageNumber;
let sites = await fetchSites()
await updateSitesPagination()
await replenishSitesTable(sites)
}
async function postDeleteSite(site_id, item_name){
let valid = document.getElementById('delete_input')
if(valid.value==item_name){
valid.classList.remove('uk-form-danger')
const response = await fetch(`/admin/site/postDeleteSite`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
site_id: site_id
}),
});
data = await response.json();
transaction_status = "success"
if (data.error){
transaction_status = "danger"
}
} else {
valid.classList.add('uk-form-danger')
data = {'message': 'You did not confirm the item correctly!!'}
transaction_status = "danger"
}
UIkit.notification({
message: data.message,
status: transaction_status,
pos: 'top-right',
timeout: 5000
});
let sites = await fetchSites()
await updateSitesPagination()
await replenishSitesTable(sites)
UIkit.modal(document.getElementById('deleteConfirmation')).hide();
}
// Roles functions
var roles_current_page = 1
var roles_end_page = 10
var roles_limit = 25
async function fetchRoles(){
const url = new URL('/admin/getRoles', window.location.origin)
url.searchParams.append('page', roles_current_page)
url.searchParams.append('limit', roles_limit)
const response = await fetch(url)
data = await response.json()
roles_end_page = data.end
return data.roles
}
async function replenishRolesTable(roles){
let rolesTableBody = document.getElementById('rolesTableBody')
rolesTableBody.innerHTML = ""
for(let i=0; i < roles.length; i++){
let tableRow = document.createElement('tr')
let idCell = document.createElement('td')
idCell.innerHTML = `${roles[i].id}`
let nameCell = document.createElement('td')
nameCell.innerHTML = `${roles[i].role_name}`
let descriptionCell = document.createElement('td')
descriptionCell.innerHTML = `${roles[i].role_description}`
let siteCell = document.createElement('td')
siteCell.innerHTML = `${roles[i].site.site_name}`
let opCell = document.createElement('td')
opCell.innerHTML = ``
let editOp = document.createElement('a')
editOp.setAttribute('class', 'uk-button uk-button-small uk-button-default')
editOp.innerHTML = "edit"
editOp.href = `/admin/role/${roles[i].id}`
let deleteOp = document.createElement('a')
deleteOp.setAttribute('class', 'uk-button uk-button-small uk-button-default')
deleteOp.innerHTML = "delete"
deleteOp.onclick = async function() {
await openDeleteModal(roles[i].role_name, "role", roles[i].id)
}
opCell.append(editOp, deleteOp)
tableRow.append(idCell, nameCell, descriptionCell, siteCell, opCell)
rolesTableBody.append(tableRow)
}
}
async function updateRolesPagination() {
let paginationElement = document.getElementById("rolesPagination");
paginationElement.innerHTML = "";
// previous
let previousElement = document.createElement('li')
if(roles_current_page<=1){
previousElement.innerHTML = `<a><span uk-pagination-previous></span></a>`;
previousElement.classList.add('uk-disabled');
}else {
previousElement.innerHTML = `<a onclick="setRolesPage(${roles_current_page-1})"><span uk-pagination-previous></span></a>`;
}
paginationElement.append(previousElement)
//first
let firstElement = document.createElement('li')
if(roles_current_page<=1){
firstElement.innerHTML = `<a>1</a>`;
firstElement.classList.add('uk-disabled');
}else {
firstElement.innerHTML = `<a onclick="setRolesPage(1)">1</a>`;
}
paginationElement.append(firstElement)
// ...
if(roles_current_page-2>1){
let firstDotElement = document.createElement('li')
firstDotElement.classList.add('uk-disabled')
firstDotElement.innerHTML = `<span>…</span>`;
paginationElement.append(firstDotElement)
}
// last
if(roles_current_page-2>0){
let lastElement = document.createElement('li')
lastElement.innerHTML = `<a onclick=setRolesPage(${roles_current_page-1})>${roles_current_page-1}</a>`
paginationElement.append(lastElement)
}
// current
if(roles_current_page!=1 && roles_current_page != roles_end_page){
let currentElement = document.createElement('li')
currentElement.innerHTML = `<li class="uk-active"><span aria-current="page"><strong>${roles_current_page}</strong></span></li>`
paginationElement.append(currentElement)
}
// next
if(roles_current_page+2<roles_end_page+1){
let nextElement = document.createElement('li')
nextElement.innerHTML = `<a onclick=setRolesPage(${roles_current_page+1})>${roles_current_page+1}</a>`
paginationElement.append(nextElement)
}
// ...
if(roles_current_page+2<=roles_end_page){
let secondDotElement = document.createElement('li')
secondDotElement.classList.add('uk-disabled')
secondDotElement.innerHTML = `<span>…</span>`;
paginationElement.append(secondDotElement)
}
//end
let endElement = document.createElement('li')
if(roles_current_page>=roles_end_page){
endElement.innerHTML = `<a>${roles_end_page}</a>`;
endElement.classList.add('uk-disabled');
}else {
endElement.innerHTML = `<a onclick="setRolesPage(${roles_end_page})">${roles_end_page}</a>`;
}
paginationElement.append(endElement)
//next button
let nextElement = document.createElement('li')
if(roles_current_page>=roles_end_page){
nextElement.innerHTML = `<a><span uk-pagination-next></span></a>`;
nextElement.classList.add('uk-disabled');
}else {
nextElement.innerHTML = `<a onclick="setRolesPage(${roles_current_page+1})"><span uk-pagination-next></span></a>`;
}
paginationElement.append(nextElement)
}
async function setRolesPage(pageNumber){
roles_current_page = pageNumber;
let roles = await fetchRoles()
await updateRolesPagination()
await replenishRolesTable(roles)
}
// users/devices functions
var logins_current_page = 1
var logins_end_page = 10
var logins_limit = 25
async function fetchLogins(){
const url = new URL('/admin/getLogins', window.location.origin)
url.searchParams.append('page', logins_current_page)
url.searchParams.append('limit', logins_limit)
const response = await fetch(url)
data = await response.json()
logins_end_page = data.end
return data.logins
}
async function replenishLoginsTable(logins){
let usersTableBody = document.getElementById('usersTableBody')
usersTableBody.innerHTML = ""
for(let i=0; i < logins.length; i++){
let tableRow = document.createElement('tr')
let idCell = document.createElement('td')
idCell.innerHTML = `${logins[i].id}`
let nameCell = document.createElement('td')
nameCell.innerHTML = `${logins[i].username}`
let emailCell = document.createElement('td')
emailCell.innerHTML = `${logins[i].email}`
let typeCell = document.createElement('td')
typeCell.innerHTML = `${logins[i].row_type}`
let opCell = document.createElement('td')
opCell.innerHTML = ``
let editOp = document.createElement('a')
editOp.setAttribute('class', 'uk-button uk-button-small uk-button-default')
editOp.innerHTML = "edit"
editOp.href = `/admin/user/${logins[i].id}`
let deleteOp = document.createElement('a')
deleteOp.setAttribute('class', 'uk-button uk-button-small uk-button-default')
deleteOp.innerHTML = "delete"
deleteOp.onclick = async function() {
await openDeleteModal(logins[i].username, "login", logins[i].id)
}
opCell.append(editOp, deleteOp)
tableRow.append(idCell, nameCell, emailCell, typeCell, opCell)
usersTableBody.append(tableRow)
}
}
async function updateLoginsPagination() {
let paginationElement = document.getElementById("usersPagination");
paginationElement.innerHTML = "";
// previous
let previousElement = document.createElement('li')
if(logins_current_page<=1){
previousElement.innerHTML = `<a><span uk-pagination-previous></span></a>`;
previousElement.classList.add('uk-disabled');
}else {
previousElement.innerHTML = `<a onclick="setLoginsPage(${logins_current_page-1})"><span uk-pagination-previous></span></a>`;
}
paginationElement.append(previousElement)
//first
let firstElement = document.createElement('li')
if(logins_current_page<=1){
firstElement.innerHTML = `<a>1</a>`;
firstElement.classList.add('uk-disabled');
}else {
firstElement.innerHTML = `<a onclick="setLoginsPage(1)">1</a>`;
}
paginationElement.append(firstElement)
// ...
if(logins_current_page-2>1){
let firstDotElement = document.createElement('li')
firstDotElement.classList.add('uk-disabled')
firstDotElement.innerHTML = `<span>…</span>`;
paginationElement.append(firstDotElement)
}
// last
if(logins_current_page-2>0){
let lastElement = document.createElement('li')
lastElement.innerHTML = `<a onclick=setLoginsPage(${logins_current_page-1})>${logins_current_page-1}</a>`
paginationElement.append(lastElement)
}
// current
if(logins_current_page!=1 && logins_current_page != logins_end_page){
let currentElement = document.createElement('li')
currentElement.innerHTML = `<li class="uk-active"><span aria-current="page"><strong>${logins_current_page}</strong></span></li>`
paginationElement.append(currentElement)
}
// next
if(logins_current_page+2<logins_end_page+1){
let nextElement = document.createElement('li')
nextElement.innerHTML = `<a onclick=setLoginsPage(${logins_current_page+1})>${logins_current_page+1}</a>`
paginationElement.append(nextElement)
}
// ...
if(logins_current_page+2<=logins_end_page){
let secondDotElement = document.createElement('li')
secondDotElement.classList.add('uk-disabled')
secondDotElement.innerHTML = `<span>…</span>`;
paginationElement.append(secondDotElement)
}
//end
let endElement = document.createElement('li')
if(logins_current_page>=logins_end_page){
endElement.innerHTML = `<a>${logins_end_page}</a>`;
endElement.classList.add('uk-disabled');
}else {
endElement.innerHTML = `<a onclick="setLoginsPage(${logins_end_page})">${logins_end_page}</a>`;
}
paginationElement.append(endElement)
//next button
let nextElement = document.createElement('li')
if(logins_current_page>=logins_end_page){
nextElement.innerHTML = `<a><span uk-pagination-next></span></a>`;
nextElement.classList.add('uk-disabled');
}else {
nextElement.innerHTML = `<a onclick="setLoginsPage(${logins_current_page+1})"><span uk-pagination-next></span></a>`;
}
paginationElement.append(nextElement)
}
async function setLoginsPage(pageNumber){
logins_current_page = pageNumber;
let logins = await fetchLogins()
await updateLoginsPagination()
await replenishLoginsTable(logins)
}
// uom functions
async function test() {
console.log('test')
}

281
templates/admin/index.html Normal file
View File

@ -0,0 +1,281 @@
<!DOCTYPE html>
<html lang="en" dir="ltr" id="main_html">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Material Symbols - Outlined Set -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
<!-- Material Symbols - Rounded Set -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded" rel="stylesheet" />
<!-- Material Symbols - Sharp Set -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/uikit.min.css') }}"/>
<link rel="stylesheet" href="{{ url_for('static', filename='css/pantry.css') }}"/>
<link id="dark-mode" rel="stylesheet" href="{{ url_for('static', filename='css/dark-mode.css') }}" disabled/>
</head>
<body>
<div uk-sticky="sel-target: .uk-navbar-container; cls-active: uk-navbar-sticky">
<!-- to color the navbar i have to stlye this element the nav element -->
<nav id="navbar" class="uk-navbar-container">
<div class="uk-container uk-container-expand">
<div uk-navbar="dropbar: true">
<div id="offcanvas-slide" uk-offcanvas="mode: slide; overlay: true">
<div class="uk-offcanvas-bar uk-flex uk-flex-column">
<ul class="uk-nav uk-nav-secondary">
<img class="uk-align-center uk-border-circle" data-src="{{ url_for('static', filename='pictures/logo.jpg') }}" style="width: 150px; height: auto;" uk-img />
<li class="uk-nav-header">Apps</li>
<li><a href="/shopping-lists">Shopping Lists</a></li>
<li><a href="/recipes">Recipes</a></li>
<li class="uk-nav-header">Logistics</li>
<li><a href="/items">Items</a></li>
<li><a href="/transaction">Add Transaction</a></li>
<li>
<a href="/workshop">
<div class="uk-active">Workshop<div class="uk-nav-subtitle" disabled>Building in the workshop...</div>
</div></a>
</li>
<li><a href="/receipts">Receipts</a></li>
<li class="uk-nav-header">System Management</li>
<li class="uk-disabled" hidden><a><div>{{current_site}}<div class="uk-nav-subtitle">This is the current site you are viewing...</div></div></a>
<div uk-dropdown="mode: click">
<ul class="uk-nav uk-dropdown-nav">
{% for site in sites %}
{% if site == current_site %}
<li><a class="uk-disabled" href="#">{{site}}</a></li>
{% else %}
<li><a onclick="changeSite('{{site}}')">{{site}}</a></li>
{% endif %}
{% endfor %}
</ul>
</div>
</li>
{% if system_admin %}
<li><a href="/admin">Administration</a></li>
{% endif %}
<li><a href="" class="">{{username}}</a></li>
</ul>
<button class="uk-button uk-margin-small uk-position-top-right" uk-icon="icon: close" href=""></button>
</div>
</div>
<div class="uk-navbar-left uk-margin-small">
<a href="#offcanvas-slide" class="uk-button uk-button-default uk-button-small" uk-icon="icon: menu" uk-toggle> Menu</a>
</div>
<div class="uk-navbar-center uk-margin-small uk-visible@s">
<ul class="uk-breadcrumb">
<li style="cursor: default;"><span><strong>Administration</strong></span></li>
</ul>
</div>
<div class="uk-navbar-right">
<div>
<a onclick="toggleDarkMode()" class="uk-button uk-button-small"><span id="modeToggle" class="uk-flex material-symbols-outlined">dark_mode</span></a>
</div>
<div>
<a href="" class="" uk-icon="icon: user" uk-toggle>{{username}}</a>
</div>
</div>
</div>
</div>
</nav>
</div>
<div class="uk-section uk-margin-left">
<div class="uk-child-width-1-1" uk-grid>
<div>
<div uk-grid>
<div class="uk-width-auto uk-margin-left">
<ul class="uk-tab-right" uk-tab="connect: #component-tab-left; animation: uk-animation-fade">
<li><a href="#">Site</a></li>
<li><a href="#">Roles</a></li>
<li><a href="#">Users/Devices</a></li>
<li><a href="#">Units of Measure</a></li>
</ul>
</div>
<div class="uk-width-expand">
<div id="component-tab-left" class="uk-switcher">
<!-- sites -->
<div class="uk-container">
<div uk-grid>
<div class="uk-width-1-1 uk-flex uk-flex-center">
<nav aria-label="Pagination">
<ul id="sitesPagination" class="uk-pagination" uk-margin>
<li><a href="#"><span uk-pagination-previous></span></a></li>
<li><a href="#">1</a></li>
<li class="uk-disabled"><span></span></li>
<li><a href="#">5</a></li>
<li><a href="#">6</a></li>
<li class="uk-active"><span aria-current="page">7</span></li>
<li><a href="#">8</a></li>
<li><a href="#"><span uk-pagination-next></span></a></li>
</ul>
</nav>
</div>
<div class="uk-width-1-1">
<caption class="uk-text-meta"></caption>
<table style="margin-bottom: 0px;" class="uk-table uk-table-striped">
<thead>
<tr>
<th>ID</th>
<th>Site Name</th>
<th>Site Description</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="sitesTableBody">
</tbody>
</table>
<a href="/admin/site/new" class="uk-button add-button"><i class="uk-flex-middle material-symbols-outlined" style="">add_circle</i></a>
</div>
</div>
</div>
<!-- Roles -->
<div class="uk-container">
<div uk-grid>
<div class="uk-width-1-1 uk-flex uk-flex-center">
<nav aria-label="Pagination">
<ul id="rolesPagination" class="uk-pagination" uk-margin>
<li><a href="#"><span uk-pagination-previous></span></a></li>
<li><a href="#">1</a></li>
<li class="uk-disabled"><span></span></li>
<li><a href="#">5</a></li>
<li><a href="#">6</a></li>
<li class="uk-active"><span aria-current="page">7</span></li>
<li><a href="#">8</a></li>
<li><a href="#"><span uk-pagination-next></span></a></li>
</ul>
</nav>
</div>
<div class="uk-width-1-1">
<caption class="uk-text-meta"></caption>
<table style="margin-bottom: 0px;" class="uk-table uk-table-striped">
<thead>
<tr>
<th>ID</th>
<th>Roles Name</th>
<th>Role Description</th>
<th>Site</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="rolesTableBody">
</tbody>
</table>
<a href="/admin/role/new" class="uk-button add-button"><i class="uk-flex-middle material-symbols-outlined" style="">add_circle</i></a>
</div>
</div>
</div>
<!-- Users/Devices -->
<div class="uk-container">
<div uk-grid>
<div class="uk-width-1-1 uk-flex uk-flex-center">
<nav aria-label="Pagination">
<ul id="usersPagination" class="uk-pagination" uk-margin>
<li><a href="#"><span uk-pagination-previous></span></a></li>
<li><a href="#">1</a></li>
<li class="uk-disabled"><span></span></li>
<li><a href="#">5</a></li>
<li><a href="#">6</a></li>
<li class="uk-active"><span aria-current="page">7</span></li>
<li><a href="#">8</a></li>
<li><a href="#"><span uk-pagination-next></span></a></li>
</ul>
</nav>
</div>
<div class="uk-width-1-1">
<caption class="uk-text-meta"></caption>
<table style="margin-bottom: 0px;" class="uk-table uk-table-striped">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Email</th>
<th>Type</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="usersTableBody">
</tbody>
</table>
<a href="/admin/user/new" class="uk-button add-button"><i class="uk-flex-middle material-symbols-outlined" style="">add_circle</i></a>
</div>
</div>
</div>
<!-- Units of Measure -->
<div class="uk-container">
<div uk-grid>
<div class="uk-width-1-1 uk-flex uk-flex-center">
<nav aria-label="Pagination">
<ul id="uomPagination" class="uk-pagination" uk-margin>
<li><a href="#"><span uk-pagination-previous></span></a></li>
<li><a href="#">1</a></li>
<li class="uk-disabled"><span></span></li>
<li><a href="#">5</a></li>
<li><a href="#">6</a></li>
<li class="uk-active"><span aria-current="page">7</span></li>
<li><a href="#">8</a></li>
<li><a href="#"><span uk-pagination-next></span></a></li>
</ul>
</nav>
</div>
<div class="uk-width-1-1">
<caption class="uk-text-meta"></caption>
<table style="margin-bottom: 0px;" class="uk-table uk-table-striped">
<thead>
<tr>
<th>ID</th>
<th>Fullname</th>
<th>Description</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="uomTableBody">
</tbody>
</table>
<button onclick="" class="uk-button add-button"><i class="uk-flex-middle material-symbols-outlined" style="">add_circle</i></button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modals -->
<!-- Sites Modal -->
<div id="deleteConfirmation" class="uk-modal-container" uk-modal>
<div class="uk-modal-dialog">
<button class="uk-modal-close-default" type="button" uk-close></button>
<div class="uk-modal-header">
<h2 class="uk-modal-title">DELETE</h2>
</div>
<div class="uk-modal-body">
<div uk-grid>
<div class="uk-width-1-1">
<p>You are attempting to delete something important, in order to ensure this is your intent, please type in the name of the
item you were going to delete</p>
</div>
<div class="uk-width-1-1">
<h3 id="delete_item_name"></h3>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<input id="delete_input" class="uk-input" type="text">
</div>
</div>
</div>
</div>
<div class="uk-modal-footer uk-text-right">
<button class="uk-button uk-button-default uk-modal-close" type="button">Cancel</button>
<button id="deleteSubmitButton" class="uk-button uk-button-danger" type="button">Delete</button>
</div>
</div>
</div>
</body>
{% assets "js_all" %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %}
<script>const session = {{session|tojson}}</script>
<script src="{{ url_for('static', filename='handlers/adminHandler.js') }}"></script>
</html>

View File

@ -1,130 +1,164 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" dir="ltr"> <html lang="en" dir="ltr" id="main_html">
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title>Edit Role</title> <title id="title"></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" /> <!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" /> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" /> <!-- Material Symbols - Outlined Set -->
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script> <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
<!-- Material Symbols - Rounded Set -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded" rel="stylesheet" />
<!-- Material Symbols - Sharp Set -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/uikit.min.css') }}"/>
<link rel="stylesheet" href="{{ url_for('static', filename='css/pantry.css') }}"/>
<link id="dark-mode" rel="stylesheet" href="{{ url_for('static', filename='css/dark-mode.css') }}" disabled/>
</head> </head>
<style>
.dropdown-disabled {
pointer-events: none;
}
.item :hover{
cursor: pointer;
background-color: whitesmoke;
}
.custom_row:hover{
background-color: rgb(230, 230, 230) !important;
cursor: pointer;
}
</style>
<body> <body>
<div class="container"> <div class="uk-container uk-section">
<div class="section"> <div uk-grid>
<div class="row" style="gap: 1em;"> <div class="uk-width-1-1" >
<div class="col s12" style="padding-bottom: 10px;"> <a href="/admin" class="uk-button uk-button-small"><span class="uk-flex material-symbols-outlined">arrow_back</span></a>
<a href='{{ proto['referrer'] }}' class="left btn green lighten-4 black-text btn-flat"><i class="material-icons">arrow_back</i></a> <a onclick="toggleDarkMode()" class="uk-button uk-button-small uk-align-right"><span id="modeToggle" class="uk-flex material-symbols-outlined">dark_mode</span></a>
<a href="/items" class="btn btn-flat right">Home</a>
<a href="/profile" class="btn btn-flat right">Profile</a>
</div> </div>
<div class="s12 m6 input-field"> <div class="uk-width-1-1">
<input id="role_name" type="text" placeholder=" " maxlength="20"> <h3 class="uk-text-center">Role Form</h3>
<label for="role_name">Name</label>
</div> </div>
<div class="input-field col s12"> <div class="uk-width-1-1">
<select id="role_site"> <p>Roles exist to harness a users/devices access to a sites specific apps and inputs. You use roles to better define a groups permissions
<option value="" disabled selected>Choose your option</option> within the app in order to control who has access to what. Specific User/Device permissions will overwrite any of their roles, permissions.</p>
</div>
<div class="uk-width-1-1">
<hr class="uk-divider-icon">
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="role_name">Role Name</label>
<div class="uk-form-controls">
<input id="role_name" class="uk-input" type="text">
</div>
</div>
</div>
<div class="uk-width-1-1">
<label class="uk-form-label" for="role_description">Role Description</label>
<div class="uk-margin">
<textarea id="role_description" class="uk-textarea" rows="5" aria-label="Textarea"></textarea>
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="site_id">Select</label>
<div class="uk-form-controls">
<select id="site_id" class="uk-select">
{% for site in sites %}
<option value="{{site['id']}}">{{site['site_name']}}</option>
{% endfor %}
</select> </select>
<label for="role_site">Site</label>
</div> </div>
<div class="input-field col s12">
<textarea id="role_description" class="materialize-textarea" placeholder=" "></textarea>
<label for="role_description">Description</label>
</div> </div>
<div class="col s12">
<h4>Permissions</h4>
</div> </div>
<div class="col s12"> <div class="uk-width-1-1">
<h5>All</h5> <hr class="uk-divider-icon">
</div>
<div class="col s12 m6">
<p>
<label>
<input type="checkbox" />
<span>Edit</span>
</label>
</p>
<p>
<label>
<input type="checkbox" />
<span>Delete</span>
</label>
</p>
</div>
<div class="col s12 m6">
<p>
<label>
<input type="checkbox" />
<span>Create</span>
</label>
</p>
<p>
<label>
<input type="checkbox" />
<span>View</span>
</label>
</p>
</div>
<div class="col s12">
<button class="btn btn-flat green lighten-4 right" onclick="updateRole()">Update</button>
<button class="btn btn-flat red lighten-4 right" style="margin-right: 10px;" onclick="deleteRole()">Delete</button>
</div> </div>
<div class="uk-width-1-1">
<button id="SubmitButton" class="uk-button uk-button-primary uk-align-right">Save</button>
</div> </div>
</div> </div>
</div> </div>
</body> </body>
<script src="{{ url_for('static', filename='adminHandler.js') }}"></script> {% assets "js_all" %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %}
<script> <script>
let role = {{role|tojson}} let role = {{role|tojson}}
const session = {{session|tojson}}
const path = window.location.pathname
console.log(role) console.log(role)
document.addEventListener('DOMContentLoaded', async function() { document.addEventListener('DOMContentLoaded', async function() {
var elems = document.querySelectorAll('select'); let mode = "edit"
var instances = M.FormSelect.init(elems, {}) if(path == "/admin/role/new"){
await populateRoleForm() mode = "new"
}); }
await replenishForm(role, mode)
})
async function populateRoleForm() { var mode = false
document.getElementById("role_name").value = role[1]; async function toggleDarkMode() {
document.getElementById("role_description").value = role[2]; let darkMode = document.getElementById("dark-mode");
const selectElement = document.getElementById("role_site"); darkMode.disabled = !darkMode.disabled;
selectElement.innerHTML = ""; mode = !mode;
if(mode){
var sites = await fetchSites() document.getElementById('modeToggle').innerHTML = "light_mode"
for (let i = 0; i < sites.length; i++){ document.getElementById('main_html').classList.add('uk-light')
let newOption = document.createElement("option"); } else {
newOption.value = sites[i][0]; document.getElementById('modeToggle').innerHTML = "dark_mode"
newOption.text = sites[i][1]; document.getElementById('main_html').classList.remove('uk-light')
selectElement.appendChild(newOption); }
};
selectElement.value = role[3];
M.FormSelect.init(selectElement);
} }
async function deleteRole(){ if(session.user.flags.darkmode){
const url = new URL('/deleteRole', window.location.origin); toggleDarkMode()
await fetch(url, { }
async function replenishForm(role, mode){
document.getElementById('role_name').value = role.role_name
document.getElementById('role_description').value = role.role_description
document.getElementById('site_id').value = role.site_id
if(mode=="new"){
document.getElementById('site_id').classList.remove('uk-disabled')
document.getElementById('SubmitButton').innerHTML = "Add Role"
document.getElementById('SubmitButton').onclick = async function(){
await postAddRole()
}
} else {
document.getElementById('site_id').classList.add('uk-disabled')
document.getElementById('SubmitButton').innerHTML = "Update Role"
document.getElementById('SubmitButton').onclick = async function(){
await postEditRole()
}
}
}
async function postAddRole(){
let payload = {
role_name: document.getElementById('role_name').value,
role_description: document.getElementById('role_description').value,
site_id: document.getElementById('site_id').value,
}
const response = await fetch(`/admin/role/postAddRole`, {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/json',}, headers: {
body: JSON.stringify({role_id: role[0],}), 'Content-Type': 'application/json',
},
body: JSON.stringify({
payload: payload
}),
});
}
async function postEditRole(){
let payload = {
id: role.id,
update: {role_name: document.getElementById('role_name').value, role_description: document.getElementById('role_description').value}
}
const response = await fetch(`/admin/role/postEditRole`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
payload: payload
}),
}); });
const adminurl = new URL(`/admin`, window.location.origin);
window.location.href = adminurl.toString();
} }
</script> </script>

240
templates/admin/site.html Normal file
View File

@ -0,0 +1,240 @@
<!DOCTYPE html>
<html lang="en" dir="ltr" id="main_html">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Material Symbols - Outlined Set -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
<!-- Material Symbols - Rounded Set -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded" rel="stylesheet" />
<!-- Material Symbols - Sharp Set -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/uikit.min.css') }}"/>
<link rel="stylesheet" href="{{ url_for('static', filename='css/pantry.css') }}"/>
<link id="dark-mode" rel="stylesheet" href="{{ url_for('static', filename='css/dark-mode.css') }}" disabled/>
</head>
<body id="test">
<div class="uk-container uk-section uk-margin-remove-top">
<div uk-grid>
<div class="uk-width-1-1" >
<a href="/admin" class="uk-button uk-button-small"><span class="uk-flex material-symbols-outlined">arrow_back</span></a>
<a onclick="toggleDarkMode()" class="uk-button uk-button-small uk-align-right"><span id="modeToggle" class="uk-flex material-symbols-outlined">dark_mode</span></a>
</div>
<div class="uk-width-1-1">
<h3 class="uk-text-center">Site Form</h3>
</div>
<div class="uk-width-1-1">
<p>Sites are the main driving force of the system. They are essentially larger pools of items and apps that are segregated and only meet in cross-sites applications. Think of
sites as your house, your shed, or your car. When designing a site think about who has access and who will be using it the most often, what permissions might be needed, and
do you need all the apps active on the site. The more sites you have the more bloated the system can become so beware making tons of sites. Thats why Zones and Locations
exist.</p>
</div>
<div class="uk-width-1-1">
<hr class="uk-divider-icon">
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="site_name">Site Name</label>
<div class="uk-form-controls">
<input class="uk-input" id="site_name" type="text" placeholder="Some text...">
</div>
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="site_description">Site Description</label>
<div class="uk-form-controls">
<textarea id="site_description" class="uk-textarea" rows="5" placeholder="Textarea" aria-label="Textarea"></textarea>
</div>
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="creation_date">Creation Date</label>
<div class="uk-form-controls">
<input class="uk-input uk-disabled" id="creation_date" type="text" placeholder="Some text...">
</div>
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="site_owner">Site Owner</label>
<div class="uk-form-controls">
<input class="uk-input uk-disabled" id="site_owner" type="text" placeholder="Some text...">
</div>
</div>
</div>
<div id="edit_locations" class="uk-width-1-1">
<div class="uk-width-large uk-grid-small" uk-grid>
<div class="uk-width-3-4 uk-margin-auto-top">
<label for="default_zone">Default Zone</label>
<input class="uk-input uk-disabled" id="default_zone" type="text">
</div>
<div class="uk-width-1-4 uk-margin-auto-top">
<button onclick="" class="uk-button uk-button-default">Select</button>
</div>
</div>
<div class="uk-width-large uk-grid-small" uk-grid>
<div class="uk-width-3-4 uk-margin-auto-top">
<label for="default_auto_issue_location">Default Auto Issue Location</label>
<input class="uk-input uk-disabled" id="default_auto_issue_location" type="text">
</div>
<div class="uk-width-1-4 uk-margin-auto-top">
<button onclick="" class="uk-button uk-button-default">Select</button>
</div>
</div>
<div class="uk-width-large uk-grid-small" uk-grid>
<div class="uk-width-3-4 uk-margin-auto-top">
<label for="default_primary_location">Default Primary Location</label>
<input class="uk-input uk-disabled" id="default_primary_location" type="text">
</div>
<div class="uk-width-1-4 uk-margin-auto-top">
<button onclick="" class="uk-button uk-button-default">Select</button>
</div>
</div>
</div>
<div id="new_locations" class="uk-width-1-1" uk-grid>
<div class="uk-width-1-1">
<caption> These are how the system will create the default locations and zones that get assigned to all new items by default.</caption>
</div>
<div class="uk-width-1-1">
<div class="">
<label for="new_default_zone">Default Zone</label>
<div class="uk-form-controls">
<input class="uk-input" id="new_default_zone" type="text" placeholder="Some text...">
</div>
</div>
</div>
<div class="uk-width-1-1">
<div class="">
<label for="new_default_auto_issue_location">Default Auto Issue Location</label>
<div class="uk-form-controls">
<input class="uk-input" id="new_default_auto_issue_location" type="text" placeholder="Some text...">
</div>
</div>
</div>
<div class="uk-width-1-1">
<div class="">
<label for="new_default_primary_location">Default Primary Location</label>
<div class="uk-form-controls">
<input class="uk-input" id="new_default_primary_location" type="text" placeholder="Some text...">
</div>
</div>
</div>
</div>
<div class="uk-width-1-1">
<hr class="uk-divider-icon">
</div>
<div class="uk-width-1-1 uk-margin-remove">
<button id="SubmitButton" class="uk-button uk-button-primary uk-align-right">Save</button>
</div>
</div>
</div>
</body>
{% assets "js_all" %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %}
<script>
const site = {{site|tojson}}
const session = {{session|tojson}}
const path = window.location.pathname
</script>
<script>
document.addEventListener('DOMContentLoaded', async function() {
let mode = "edit"
if(path == "/admin/site/new"){
mode = "new"
}
replenishForm(site, mode)
console.log(mode)
})
var mode = false
async function toggleDarkMode() {
let darkMode = document.getElementById("dark-mode");
darkMode.disabled = !darkMode.disabled;
mode = !mode;
if(mode){
document.getElementById('modeToggle').innerHTML = "light_mode"
document.getElementById('main_html').classList.add('uk-light')
} else {
document.getElementById('modeToggle').innerHTML = "dark_mode"
document.getElementById('main_html').classList.remove('uk-light')
}
}
if(session.user.flags.darkmode){
toggleDarkMode()
}
async function replenishForm(site, mode){
document.getElementById('site_name').value = site.site_name
document.getElementById('site_description').value = site.site_description
document.getElementById('creation_date').value = site.creation_date
document.getElementById('site_owner').value = site.site_owner_id
if(mode=="new"){
document.getElementById('edit_locations').hidden = true
document.getElementById('new_locations').hidden = false
document.getElementById('SubmitButton').innerHTML = "Add Site"
document.getElementById('SubmitButton').onclick = async function(){
await postAddSite()
}
} else {
document.getElementById('edit_locations').hidden = false
document.getElementById('new_locations').hidden = true
document.getElementById('default_zone').value = site.default_zone
document.getElementById('default_auto_issue_location').value = site.default_auto_issue_location
document.getElementById('default_primary_location').value = site.default_primary_location
document.getElementById('SubmitButton').innerHTML = "Update Site"
document.getElementById('SubmitButton').onclick = async function(){
await postEditSite()
}
}
}
async function postAddSite(){
let payload = {
site_name: document.getElementById('site_name').value,
site_description: document.getElementById('site_description').value,
default_zone: document.getElementById('new_default_zone').value,
default_auto_issue_location: document.getElementById('new_default_auto_issue_location').value,
default_primary_location: document.getElementById('new_default_primary_location').value
}
const response = await fetch(`/admin/site/postAddSite`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
payload: payload
}),
});
}
async function postEditSite(){
let payload = {
id: site.id,
update: {site_name: document.getElementById('site_name').value,
site_description: document.getElementById('site_description').value}
}
const response = await fetch(`/admin/site/postEditSite`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
payload: payload
}),
});
}
</script>
</html>

367
templates/admin/user.html Normal file
View File

@ -0,0 +1,367 @@
<!DOCTYPE html>
<html lang="en" dir="ltr" id="main_html">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title">User</title>
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Material Symbols - Outlined Set -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
<!-- Material Symbols - Rounded Set -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded" rel="stylesheet" />
<!-- Material Symbols - Sharp Set -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/uikit.min.css') }}"/>
<link rel="stylesheet" href="{{ url_for('static', filename='css/pantry.css') }}"/>
<link id="dark-mode" rel="stylesheet" href="{{ url_for('static', filename='css/dark-mode.css') }}" disabled/>
</head>
<body>
<div class="uk-section">
<div class="uk-container uk-container-xsmall">
<div class="uk-grid-small" uk-grid>
<div class="uk-width-1-1" >
<a href="/admin" class="uk-button uk-button-small"><span class="uk-flex material-symbols-outlined">arrow_back</span></a>
<a onclick="toggleDarkMode()" class="uk-button uk-button-small uk-align-right"><span id="modeToggle" class="uk-flex material-symbols-outlined">dark_mode</span></a>
</div>
<div class="uk-width-1-1">
<h3 class="uk-text-center">User/Device Form</h3>
</div>
<div class="uk-width-1-1">
<p>Users and Devices, better known as access points, are credentials to acccess the system from specific areas. Users are intended to be
you basic login for actual people. Devices are created when you know a the credentials are to be used in one place and no where else.
Differentiating between the two is helpful to see who by/where in your setup transactions are happening.</p>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="login_name">Access Name</label>
<div class="uk-form-controls">
<input class="uk-input" id="login_name" type="text">
</div>
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="login_email">Access Email</label>
<div class="uk-form-controls">
<input class="uk-input" id="login_email" type="text">
</div>
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="login_type">Access Type</label>
<div class="uk-form-controls">
<select class="uk-select" id="login_type">
<option value="user">User</option>
<option value="device">Device</option>
</select>
</div>
</div>
</div>
<div class="uk-width-1-1">
<hr class="uk-divider-icon">
</div>
<div id="new_password" class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="new_login_password">Access Password</label>
<div class="uk-form-controls">
<input class="uk-input" id="new_login_password" type="password">
</div>
</div>
</div>
<div id="new_password" class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="new_login_password_retype">Access Retype Password</label>
<div class="uk-form-controls">
<input class="uk-input" id="new_login_password_retype" type="password">
</div>
</div>
</div>
<div id="old_password" class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="old_login_password">Current Access Password</label>
<div class="uk-form-controls">
<input class="uk-input" id="old_login_password" type="password">
</div>
</div>
</div>
<div id="old_password" class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="old_login_password_new">New Access Password</label>
<div class="uk-form-controls">
<input class="uk-input" id="old_login_password_new" type="password">
</div>
</div>
</div>
<div id="old_password" class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="old_login_password_new_retype">Retype New Access Password</label>
<div class="uk-form-controls">
<input class="uk-input" id="old_login_password_new_retype" type="password">
</div>
</div>
</div>
<div id="old_password" class="uk-width-1-1">
<button onclick="postEditLoginPassword()" class="uk-button uk-button-primary uk-align-right">Update Password</button>
</div>
<div class="uk-width-1-1">
<hr class="uk-divider-icon">
</div>
<div class="uk-width-1-1">
<caption>These are the Sites this access point has permission to see</caption>
<table class="uk-table uk-table-striped uk-table-small">
<thead>
<tr>
<th class="uk-text-center">Site Name</th>
<th class="uk-text-center">Operations</th>
</tr>
</thead>
<tbody id="sitesTableBody">
<tr>
<td class="uk-text-center">Test</td>
<td class="uk-text-center"><button class="uk-button uk-button-small uk-button-default">Remove</button></td>
</tr>
<tr>
<td class="uk-text-center">Test</td>
<td class="uk-text-center"><button class="uk-button uk-button-small uk-button-default">Remove</button></td>
</tr>
</tbody>
</table>
</div>
<div class="uk-width-1-1">
<caption>These are the roles that the access point has in each permissable site</caption>
<table class="uk-table uk-table-striped uk-table-small">
<thead>
<tr>
<th class="uk-text-center">Site Role</th>
<th class="uk-text-center">Operations</th>
</tr>
</thead>
<tbody id="siteRolesTableBody">
<tr>
<td class="uk-text-center">Test</td>
<td class="uk-text-center"><button class="uk-button uk-button-small uk-button-default">Remove</button></td>
</tr>
<tr>
<td class="uk-text-center">Test</td>
<td class="uk-text-center"><button class="uk-button uk-button-small uk-button-default">Remove</button></td>
</tr>
</tbody>
</table>
</div>
<div class="uk-width-1-1">
<hr class="uk-divider-icon">
</div>
<div class="uk-width-1-1">
<button id="SubmitButton" class="uk-button uk-button-primary uk-align-right">Save</button>
</div>
</div>
</div>
</div>
</body>
{% assets "js_all" %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %}
<script>
const user = {{user|tojson}}
const session = {{session|tojson}}
const path = window.location.pathname
</script>
<script>
var update = {
username: "",
password: "",
row_type: "",
sites: [],
site_roles: [],
system_admin: false,
flags: {
permissions: {
sitename: {
items: {'nav': true, 'edit': false, 'view': true, 'delete': false},
item:{'nav': true, 'edit': false, 'view': true, 'delete': false},
recipes: {'nav': true, 'edit': false, 'view': true, 'delete': false},
receipts: {'nav': true, 'edit': false, 'view': true, 'delete': false},
shopping_lists: {'nav': true, 'edit': false, 'view': true, 'delete': false}
}
},
darkmode: true, // prefers dark mode
solarized: false // preferse solarized
}
}
document.addEventListener('DOMContentLoaded', async function() {
let mode = "edit"
if(path == "/admin/user/new"){
mode = "new"
}
console.log(user)
await replenishForm(user, mode)
})
var mode = false
async function toggleDarkMode() {
let darkMode = document.getElementById("dark-mode");
darkMode.disabled = !darkMode.disabled;
mode = !mode;
if(mode){
document.getElementById('modeToggle').innerHTML = "light_mode"
document.getElementById('main_html').classList.add('uk-light')
} else {
document.getElementById('modeToggle').innerHTML = "dark_mode"
document.getElementById('main_html').classList.remove('uk-light')
}
}
if(session.user.flags.darkmode){
toggleDarkMode()
}
async function replenishForm(user, mode){
document.getElementById('login_name').value = user.username
document.getElementById('login_email').value = user.email
document.getElementById('login_type').value = user.row_type
if(mode=="new"){
document.querySelectorAll('#new_password').forEach(function(element) {
element.hidden = false;
});
document.querySelectorAll('#old_password').forEach(function(element) {
element.hidden = true;
});
document.getElementById('SubmitButton').innerHTML = "Add User"
document.getElementById('SubmitButton').onclick = async function(){
await postAddLogin()
}
} else {
document.querySelectorAll('#new_password').forEach(function(element) {
element.hidden = true;
});
document.querySelectorAll('#old_password').forEach(function(element) {
element.hidden = false;
});
document.getElementById('SubmitButton').innerHTML = "Update User"
document.getElementById('SubmitButton').onclick = async function(){
await postEditLogin()
}
}
}
async function postAddLogin(){
let payload = {
username: document.getElementById('login_name').value,
email: document.getElementById('login_email').value,
password: document.getElementById('new_login_password').value,
row_type: document.getElementById('login_type').value,
}
const response = await fetch(`/admin/user/postAddLogin`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
payload: payload
}),
});
data = await response.json();
let transaction_status = "success"
if (data.error){
transaction_status = "danger"
UIkit.notification({
message: data.message,
status: transaction_status,
pos: 'bottom-left',
timeout: 5000
});
} else {
window.location.href = `/admin/user/${data.user.id}`
}
}
async function postEditLoginPassword(){
let payload = {
id: user.id,
current_password: document.getElementById('old_login_password').value,
update: {password: document.getElementById('old_login_password_new').value}
}
const response = await fetch(`/admin/user/postEditLoginPassword`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
payload: payload
}),
});
data = await response.json();
transaction_status = "success"
if (data.error){
transaction_status = "danger"
UIkit.notification({
message: data.message,
status: transaction_status,
pos: 'bottom-left',
timeout: 5000
});
document.getElementById('old_login_password').classList.add('uk-form-danger')
document.getElementById('old_login_password_new').classList.add('uk-form-danger')
document.getElementById('old_login_password_new_retype').classList.add('uk-form-danger')
} else {
UIkit.notification({
message: data.message,
status: transaction_status,
pos: 'bottom-left',
timeout: 5000
});
document.getElementById('old_login_password').classList.remove('uk-form-danger')
document.getElementById('old_login_password_new').classList.remove('uk-form-danger')
document.getElementById('old_login_password_new_retype').classList.remove('uk-form-danger')
document.getElementById('old_login_password').value = ""
document.getElementById('old_login_password_new').value = ""
document.getElementById('old_login_password_new_retype').value = ""
}
}
async function postEditLogin(){
let payload = {
id: user.id,
update: {
row_type: document.getElementById('login_type').value,
username: document.getElementById('login_name').value,
email: document.getElementById('login_email').value
}
}
const response = await fetch(`/admin/user/postEditLogin`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
payload: payload
}),
});
data = await response.json();
if (data.error){
transaction_status = "danger"
UIkit.notification({
message: data.message,
status: transaction_status,
pos: 'bottom-left',
timeout: 5000
});
} else {
UIkit.notification({
message: data.message,
status: transaction_status,
pos: 'bottom-left',
timeout: 5000
});
}
}
</script>
</html>

View File

@ -1,17 +0,0 @@
<div class="row" id="sites">
<div class="col s12">
<h1>Your Users</h1>
</div>
<div class="col s12">
<p class="flow-text">This is where all the users who have access to this instance!</p>
</div>
<div class="col s12">
%%userstable%%
</div>
<div class="col s12 center-align">
<span class="center-align"><button data-target="add_site"class="btn btn-flat center-align modal-trigger" style="width: 100%; border-radius: 10px;"><i class="large material-symbols-outlined" style="font-size: 2rem;">add_circle</i></button></span>
</div>
<div class="col s12">
%%pagination%%
</div>
</div>

11
test.py
View File

@ -5,3 +5,14 @@ import random, uuid, csv, postsqldb
import pdf2image, os, pymupdf, PIL import pdf2image, os, pymupdf, PIL
from pywebpush import webpush, WebPushException from pywebpush import webpush, WebPushException
site = MyDataclasses.SitePayload(
"testA",
"Test site A",
1
)
print("payload", site)
x = site.__dict__
print("dict", x)

View File

@ -4,9 +4,16 @@ from config import config, sites_config, setFirstSetupDone
from functools import wraps from functools import wraps
from manage import create from manage import create
from main import create_site, getUser, setSystemAdmin from main import create_site, getUser, setSystemAdmin
import postsqldb
login_app = Blueprint('login', __name__) login_app = Blueprint('login', __name__)
def update_session_user():
database_config = config()
with psycopg2.connect(**database_config) as conn:
user = postsqldb.LoginsTable.get_washed_tuple(conn, (session['user_id'],))
session['user'] = user
def login_required(func): def login_required(func):
@wraps(func) @wraps(func)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):

View File

@ -1,13 +1,14 @@
import celery.schedules import celery.schedules
from flask import Flask, render_template, session, request, redirect, jsonify from flask import Flask, render_template, session, request, redirect, jsonify
from flask_assets import Environment, Bundle from flask_assets import Environment, Bundle
import api, config, user_api, psycopg2, main, admin, item_API, receipts_API, shopping_list_API, group_api, recipes_api import api, config, user_api, psycopg2, main, api_admin, item_API, receipts_API, shopping_list_API, group_api
from user_api import login_required from user_api import login_required, update_session_user
from external_API import external_api from external_API import external_api
from workshop_api import workshop_api from workshop_api import workshop_api
import database import database
import postsqldb import postsqldb
from webpush import trigger_push_notifications_for_subscriptions from webpush import trigger_push_notifications_for_subscriptions
from scripts.recipes import recipes_api
app = Flask(__name__, instance_relative_config=True) app = Flask(__name__, instance_relative_config=True)
UPLOAD_FOLDER = 'static/pictures' UPLOAD_FOLDER = 'static/pictures'
@ -21,7 +22,7 @@ assets = Environment(app)
app.secret_key = '11gs22h2h1a4h6ah8e413a45' app.secret_key = '11gs22h2h1a4h6ah8e413a45'
app.register_blueprint(api.database_api) app.register_blueprint(api.database_api)
app.register_blueprint(user_api.login_app) app.register_blueprint(user_api.login_app)
app.register_blueprint(admin.admin) app.register_blueprint(api_admin.admin_api)
app.register_blueprint(item_API.items_api) app.register_blueprint(item_API.items_api)
app.register_blueprint(external_api) app.register_blueprint(external_api)
app.register_blueprint(workshop_api) app.register_blueprint(workshop_api)
@ -90,6 +91,7 @@ def transaction():
@app.route("/items") @app.route("/items")
@login_required @login_required
def items(): def items():
update_session_user()
sites = [site[1] for site in main.get_sites(session['user']['sites'])] sites = [site[1] for site in main.get_sites(session['user']['sites'])]
return render_template("items/index.html", return render_template("items/index.html",
current_site=session['selected_site'], current_site=session['selected_site'],
@ -116,6 +118,7 @@ def subscribe():
@app.route("/") @app.route("/")
@login_required @login_required
def home(): def home():
update_session_user()
sites = [site[1] for site in main.get_sites(session['user']['sites'])] sites = [site[1] for site in main.get_sites(session['user']['sites'])]
session['selected_site'] = sites[0] session['selected_site'] = sites[0]
return redirect("/items") return redirect("/items")

View File

@ -10,6 +10,7 @@ workshop_api = Blueprint('workshop_api', __name__)
@workshop_api.route("/workshop") @workshop_api.route("/workshop")
@login_required @login_required
def workshop(): def workshop():
print(session['user'])
sites = [site[1] for site in main.get_sites(session['user']['sites'])] sites = [site[1] for site in main.get_sites(session['user']['sites'])]
print(session.get('user')['system_admin']) print(session.get('user')['system_admin'])
if not session.get('user')['system_admin']: if not session.get('user')['system_admin']:
@ -117,7 +118,6 @@ def postAddZone():
site_id = cur.fetchone()[0] site_id = cur.fetchone()[0]
zone = postsqldb.ZonesTable.Payload( zone = postsqldb.ZonesTable.Payload(
request.get_json()['name'], request.get_json()['name'],
site_id,
request.get_json()['description'] request.get_json()['description']
) )
postsqldb.ZonesTable.insert_tuple(conn, site_name, zone.payload()) postsqldb.ZonesTable.insert_tuple(conn, site_name, zone.payload())