test
This commit is contained in:
parent
f1cc51f378
commit
c18c6cec16
BIN
__pycache__/admin.cpython-312.pyc
Normal file
BIN
__pycache__/admin.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
__pycache__/html_factory.cpython-312.pyc
Normal file
BIN
__pycache__/html_factory.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
__pycache__/manage.cpython-312.pyc
Normal file
BIN
__pycache__/manage.cpython-312.pyc
Normal file
Binary file not shown.
BIN
__pycache__/user_api.cpython-312.pyc
Normal file
BIN
__pycache__/user_api.cpython-312.pyc
Normal file
Binary file not shown.
122
admin.py
Normal file
122
admin.py
Normal file
@ -0,0 +1,122 @@
|
||||
from flask import Blueprint, request, render_template, redirect, session, url_for, send_file, jsonify, Response
|
||||
import psycopg2, math, json, datetime, main, copy, requests, html_factory
|
||||
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!")
|
||||
30
api.py
30
api.py
@ -116,8 +116,11 @@ def paginate_groups():
|
||||
print(group[3])
|
||||
for item_id in group[3]:
|
||||
cur.execute(sql_item, (item_id,))
|
||||
item_row = cur.fetchone()
|
||||
qty += float(item_row[2])
|
||||
item_row = list(cur.fetchone())
|
||||
cur.execute(f"SELECT quantity_on_hand FROM {site_name}_item_locations WHERE part_id=%s;", (item_id, ))
|
||||
item_locations = cur.fetchall()[0]
|
||||
qty += float(sum(item_locations))
|
||||
item_row[2] = sum(item_locations)
|
||||
items.append(item_row)
|
||||
group[3] = items
|
||||
group.append(qty)
|
||||
@ -1060,10 +1063,15 @@ def paginate_lists():
|
||||
custom_items = shopping_list[4]
|
||||
list_length = len(custom_items)
|
||||
|
||||
sqlfile = open(f"sites/{site_name}/sql/unique/shopping_lists_safetystock_count.sql", "r+")
|
||||
sql = "\n".join(sqlfile.readlines())
|
||||
sqlfile.close()
|
||||
print(sql)
|
||||
if shopping_list[10] == 'calculated':
|
||||
item_sql = f"SELECT COUNT(*) FROM {site_name}_items LEFT JOIN {site_name}_logistics_info ON {site_name}_items.logistics_info_id = {site_name}_logistics_info.id LEFT JOIN {site_name}_item_info ON {site_name}_items.item_info_id = {site_name}_item_info.id LEFT JOIN {site_name}_food_info ON {site_name}_items.food_info_id = {site_name}_food_info.id WHERE {site_name}_logistics_info.quantity_on_hand < {site_name}_item_info.safety_stock AND shopping_lists @> ARRAY[%s];"
|
||||
cur.execute(item_sql, (shopping_list[0], ))
|
||||
print(shopping_list[0])
|
||||
cur.execute(sql, (shopping_list[0], ))
|
||||
list_length += cur.fetchone()[0]
|
||||
|
||||
else:
|
||||
list_length += len(pantry_items)
|
||||
|
||||
@ -1089,13 +1097,17 @@ def get_list_view():
|
||||
shopping_list = list(cur.fetchone())
|
||||
|
||||
if shopping_list[10] == "calculated":
|
||||
itemSQL = f"SELECT {site_name}_items.id, {site_name}_items.barcode, {site_name}_items.item_name, {site_name}_items.links, {site_name}_logistics_info.quantity_on_hand, {site_name}_item_info.safety_stock, {site_name}_item_info.uom FROM {site_name}_items LEFT JOIN {site_name}_logistics_info ON {site_name}_items.logistics_info_id = {site_name}_logistics_info.id LEFT JOIN {site_name}_item_info ON {site_name}_items.item_info_id = {site_name}_item_info.id LEFT JOIN {site_name}_food_info ON {site_name}_items.food_info_id = {site_name}_food_info.id WHERE {site_name}_logistics_info.quantity_on_hand < {site_name}_item_info.safety_stock AND shopping_lists @> ARRAY[%s];"
|
||||
sqlfile = open(f"sites/{site_name}/sql/unique/shopping_lists_safetystock.sql", "r+")
|
||||
sql = "\n".join(sqlfile.readlines())
|
||||
sqlfile.close()
|
||||
else:
|
||||
itemSQL = f"SELECT {site_name}_items.id, {site_name}_items.barcode, {site_name}_items.item_name, {site_name}_items.links, {site_name}_logistics_info.quantity_on_hand, {site_name}_item_info.safety_stock, {site_name}_item_info.uom FROM {site_name}_items LEFT JOIN {site_name}_logistics_info ON {site_name}_items.logistics_info_id = {site_name}_logistics_info.id LEFT JOIN {site_name}_item_info ON {site_name}_items.item_info_id = {site_name}_item_info.id LEFT JOIN {site_name}_food_info ON {site_name}_items.food_info_id = {site_name}_food_info.id WHERE shopping_lists @> ARRAY[%s];"
|
||||
|
||||
cur.execute(itemSQL, (id, ))
|
||||
sqlfile = open(f"sites/{site_name}/sql/unique/shopping_lists_safetystock_uncalculated.sql", "r+")
|
||||
sql = "\n".join(sqlfile.readlines())
|
||||
sqlfile.close()
|
||||
|
||||
cur.execute(sql, (id, ))
|
||||
shopping_list[3] = list(cur.fetchall())
|
||||
print(shopping_list)
|
||||
print(shopping_list[4])
|
||||
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
|
||||
23
config.py
23
config.py
@ -27,20 +27,33 @@ def sites_config(filename='database.ini', section='manage'):
|
||||
parser.read(filename)
|
||||
|
||||
# get section, default to postgresql
|
||||
sites = {}
|
||||
instance_config = {}
|
||||
first_setup = False
|
||||
if parser.has_section(section):
|
||||
params = parser.items(section)
|
||||
params = parser.items(section)
|
||||
print(params)
|
||||
for param in params:
|
||||
sites[param[0]] = param[1].split(',')
|
||||
instance_config[param[0]] = param[1].split(',')
|
||||
else:
|
||||
raise Exception('Section {0} not found in the {1} file'.format(section, filename))
|
||||
|
||||
return sites
|
||||
instance_config['first_setup'] = parser.getboolean('manage', 'first_setup')
|
||||
instance_config['signup_enabled'] = parser.getboolean('manage', 'signup_enabled')
|
||||
|
||||
|
||||
print(instance_config)
|
||||
return instance_config
|
||||
|
||||
def setFirstSetupDone():
|
||||
config = ConfigParser()
|
||||
config.read('database.ini')
|
||||
config.set('manage', 'first_setup', 'False')
|
||||
with open('database.ini', 'w') as configFile:
|
||||
config.write(configFile)
|
||||
|
||||
def write_new_site(site_name):
|
||||
|
||||
old_value = sites_config()['sites']
|
||||
old_value = [site for site in sites_config()['sites'] if site != ""]
|
||||
print(old_value)
|
||||
|
||||
old_value.append(site_name)
|
||||
|
||||
@ -6,5 +6,7 @@ password = test
|
||||
port = 5432
|
||||
|
||||
[manage]
|
||||
sites = ,test,main,Backpack
|
||||
sites = Backpack,main
|
||||
first_setup = False
|
||||
signup_enabled = False
|
||||
|
||||
|
||||
56
html_factory.py
Normal file
56
html_factory.py
Normal file
@ -0,0 +1,56 @@
|
||||
import math
|
||||
|
||||
|
||||
def manufactureUsersTable(rows):
|
||||
table = """<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
%%rows%%
|
||||
</tbody>
|
||||
</table>
|
||||
"""
|
||||
|
||||
string_rows = []
|
||||
for row in rows:
|
||||
string_row = f"""<tr>
|
||||
<td>{row[1]}</td>
|
||||
</tr>"""
|
||||
string_rows.append(string_row)
|
||||
|
||||
table = table.replace("%%rows%%", "".join(string_rows))
|
||||
|
||||
return table
|
||||
|
||||
|
||||
def manufacturePagination(current_page:int , count:int, limit:int):
|
||||
total_pages = math.ceil(count/limit)
|
||||
pag = ""
|
||||
limits = "hx-vals='{" + f'"limit": "{str(limit)}"' + "}'"
|
||||
if count >= limit:
|
||||
pag += '<ul class="pagination">'
|
||||
|
||||
if current_page > 1:
|
||||
pag += f'<li class="waves-effect my_btn"><a hx-post="/admin/users/{current_page - 1}" hx-target="#main_body" {limits}><i class="material-icons">chevron_left</i></a></li>'
|
||||
|
||||
p = [_ for _ in [current_page-2, current_page-1, current_page] if _ >= 1]
|
||||
y = [_ for _ in [current_page+1, current_page+2] if _ <= total_pages]
|
||||
_elems = p + y
|
||||
print(_elems)
|
||||
|
||||
for _element in _elems:
|
||||
if _element == current_page:
|
||||
pag += f'<li class="active"><a hx-post="/admin/users/{_element}" hx-target="#main_body" {limits}>{_element}</a></li>'
|
||||
else:
|
||||
pag += f'<li class="my_btn waves-effect"><a hx-post="/admin/users/{_element}" hx-target="#main_body" {limits}>{_element}</a></li>'
|
||||
|
||||
if current_page != total_pages:
|
||||
pag += f'<li class="waves-effect my_btn"><a hx-post="/admin/users/{current_page + 1}" hx-target="#main_body" {limits}><i class="material-icons">chevron_right</i></a></li>'
|
||||
|
||||
pag += "</ul>"
|
||||
|
||||
return pag
|
||||
|
||||
231
main.py
231
main.py
@ -189,7 +189,6 @@ def setLogisticsDataTransaction(conn, site_name, location, logistics_info_id, qt
|
||||
return error
|
||||
return "success"
|
||||
|
||||
|
||||
def handleNegativeQuantityOnHand(qty, cost_layers):
|
||||
cost_layers = [ast.literal_eval(item) for item in ast.literal_eval(cost_layers.replace('{', '[').replace('}', ']'))]
|
||||
dummy_quantity = qty
|
||||
@ -415,11 +414,12 @@ def delete_site(site_name):
|
||||
drop_table(f'sites/{site_name}/sql/drop/shopping_lists.sql')
|
||||
drop_table(f'sites/{site_name}/sql/drop/item_locations.sql')
|
||||
|
||||
def create_site(site_name):
|
||||
|
||||
site_config = config(f"sites/{site_name}/site.ini", 'defaults')
|
||||
def create_site(site_name, admin_user: tuple, default_zone, default_primary, default_auto, description):
|
||||
|
||||
create_table(f'sites/{site_name}/sql/create/logins.sql')
|
||||
create_table(f"sites/{site_name}/sql/create/sites.sql")
|
||||
create_table(f"sites/{site_name}/sql/create/roles.sql")
|
||||
|
||||
create_table(f'sites/{site_name}/sql/create/groups.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/linked_items.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/brands.sql')
|
||||
@ -437,16 +437,69 @@ def create_site(site_name):
|
||||
create_table(f'sites/{site_name}/sql/create/shopping_lists.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/item_locations.sql')
|
||||
|
||||
|
||||
add_admin_sql = f"INSERT INTO logins(username, password, email) VALUES(%s, %s, %s) RETURNING id;"
|
||||
add_site_sql = f"INSERT INTO sites(site_name, creation_date, site_owner_id, flags, default_zone, default_auto_issue_location, default_primary_location, site_description) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;"
|
||||
add_admin_role = f"INSERT INTO roles(role_name, site_id) VALUES(%s, %s) RETURNING id;"
|
||||
|
||||
sql = f"INSERT INTO {site_name}_zones(name) VALUES (%s) RETURNING id;"
|
||||
sqltwo = f"INSERT INTO {site_name}_locations(uuid, name, zone_id, items) VALUES (%s, %s, %s, %s);"
|
||||
sqlthree = f"INSERT INTO {site_name}_vendors(vendor_name, creation_date, created_by) VALUES (%s, %s, %s);"
|
||||
|
||||
database_config = config()
|
||||
with psycopg2.connect(**database_config) as conn:
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(add_admin_sql, admin_user)
|
||||
rows = cur.fetchone()
|
||||
if rows:
|
||||
user_id = rows[0]
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
print(user_id)
|
||||
|
||||
# set up site in database
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
data = (site_name, str(datetime.datetime.now()), user_id, json.dumps({}), default_zone, default_auto, default_primary, description)
|
||||
cur.execute(add_site_sql, data)
|
||||
rows = cur.fetchone()
|
||||
if rows:
|
||||
site_id = rows[0]
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
|
||||
# add admin role for site
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
data = ('Admin', site_id)
|
||||
cur.execute(add_admin_role, data)
|
||||
rows = cur.fetchone()
|
||||
if rows:
|
||||
role_id = rows[0]
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
|
||||
# update user with site_id and admin role.
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
data = (site_id, role_id, user_id)
|
||||
cur.execute(f"UPDATE logins SET sites = sites || %s, site_roles = site_roles || %s WHERE id=%s;", data)
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
|
||||
# setup the default zone.
|
||||
zone_id = None
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sql, (site_config["default_zone"], ))
|
||||
cur.execute(sql, (default_zone, ))
|
||||
rows = cur.fetchone()
|
||||
if rows:
|
||||
zone_id = rows[0]
|
||||
@ -455,11 +508,11 @@ def create_site(site_name):
|
||||
conn.rollback()
|
||||
return False
|
||||
|
||||
uuid = f"{site_config["default_zone"]}@{site_config["default_primary_location"]}"
|
||||
uuid = f"{default_zone}@{default_primary}"
|
||||
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sqltwo, (uuid, site_config["default_primary_location"], zone_id, json.dumps({})))
|
||||
cur.execute(sqltwo, (uuid, default_primary, zone_id, json.dumps({})))
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
@ -473,9 +526,171 @@ def create_site(site_name):
|
||||
conn.rollback()
|
||||
return False
|
||||
|
||||
conn.commit()
|
||||
|
||||
async def create_site_secondary(site_name, user_id, default_zone, default_primary, default_auto, description):
|
||||
|
||||
create_table(f'sites/{site_name}/sql/create/logins.sql')
|
||||
create_table(f"sites/{site_name}/sql/create/sites.sql")
|
||||
create_table(f"sites/{site_name}/sql/create/roles.sql")
|
||||
|
||||
create_table(f'sites/{site_name}/sql/create/groups.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/linked_items.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/brands.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/food_info.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/item_info.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/logistics_info.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/transactions.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/item.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/zones.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/locations.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/vendors.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/receipts.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/receipt_items.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/recipes.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/shopping_lists.sql')
|
||||
create_table(f'sites/{site_name}/sql/create/item_locations.sql')
|
||||
|
||||
add_site_sql = f"INSERT INTO sites(site_name, creation_date, site_owner_id, flags, default_zone, default_auto_issue_location, default_primary_location, site_description) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;"
|
||||
add_admin_role = f"INSERT INTO roles(role_name, site_id, role_description) VALUES(%s, %s, %s) RETURNING id;"
|
||||
|
||||
sql = f"INSERT INTO {site_name}_zones(name) VALUES (%s) RETURNING id;"
|
||||
sqltwo = f"INSERT INTO {site_name}_locations(uuid, name, zone_id, items) VALUES (%s, %s, %s, %s);"
|
||||
sqlthree = f"INSERT INTO {site_name}_vendors(vendor_name, creation_date, created_by) VALUES (%s, %s, %s);"
|
||||
|
||||
database_config = config()
|
||||
with psycopg2.connect(**database_config) as conn:
|
||||
# set up site in database
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
data = (site_name, str(datetime.datetime.now()), user_id, json.dumps({}), default_zone, default_auto, default_primary, description)
|
||||
cur.execute(add_site_sql, data)
|
||||
rows = cur.fetchone()
|
||||
if rows:
|
||||
site_id = rows[0]
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
|
||||
# add admin role for site
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
data = ('Admin', site_id, f"This is the admin role for {site_name}.")
|
||||
cur.execute(add_admin_role, data)
|
||||
rows = cur.fetchone()
|
||||
if rows:
|
||||
role_id = rows[0]
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
|
||||
# update user with site_id and admin role.
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
data = (site_id, role_id, user_id)
|
||||
cur.execute(f"UPDATE logins SET sites = sites || %s, site_roles = site_roles || %s WHERE id=%s;", data)
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
|
||||
# setup the default zone.
|
||||
zone_id = None
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sql, (default_zone, ))
|
||||
rows = cur.fetchone()
|
||||
if rows:
|
||||
zone_id = rows[0]
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
|
||||
uuid = f"{default_zone}@{default_primary}"
|
||||
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sqltwo, (uuid, default_primary, zone_id, json.dumps({})))
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sqlthree, ("None", str(datetime.datetime.now()), 1))
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
|
||||
conn.commit()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def getUser(username, password):
|
||||
database_config = config()
|
||||
with psycopg2.connect(**database_config) as conn:
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
sql = f"SELECT * FROM logins WHERE username=%s;"
|
||||
cur.execute(sql, (username,))
|
||||
user = cur.fetchone()
|
||||
if user and user[2] == password:
|
||||
return list(user)
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return []
|
||||
|
||||
def setSystemAdmin(user_id: int):
|
||||
database_config = config()
|
||||
with psycopg2.connect(**database_config) as conn:
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(f"UPDATE logins SET system_admin = TRUE WHERE id=%s;", (user_id, ))
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
|
||||
def get_roles(site_id):
|
||||
database_config = config()
|
||||
with psycopg2.connect(**database_config) as conn:
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(f"SELECT * FROM roles WHERE site_id=%s;", (site_id, ))
|
||||
roles = cur.fetchall()
|
||||
return roles
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
|
||||
|
||||
def get_sites(sites=[]):
|
||||
database_config = config()
|
||||
with psycopg2.connect(**database_config) as conn:
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
site_rows = []
|
||||
for each in sites:
|
||||
cur.execute(f"SELECT * FROM sites WHERE id=%s;", (each, ))
|
||||
site_rows.append(cur.fetchone())
|
||||
print(site_rows)
|
||||
return site_rows
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
transaction_payload = {
|
||||
"timestamp": None,
|
||||
"logistics_info_id": 0,
|
||||
|
||||
16
manage.py
16
manage.py
@ -55,19 +55,7 @@ def rename_create_sql(site_name):
|
||||
with open(f"sites/{site_name}/sql/create/{file_name}", "w") as file:
|
||||
file.write(words)
|
||||
|
||||
def create():
|
||||
site_name = input("Site Name: ")
|
||||
site_owner = input("Site Owner: ")
|
||||
email = input("Contact Email: ")
|
||||
|
||||
default_zone_name = input("Set Default Zone Name (default): ").strip()
|
||||
if default_zone_name == "":
|
||||
default_zone_name = "default"
|
||||
|
||||
print(f"Now you will set the default location that you wish for things to be received into (primary location) and used from (auto-issue).")
|
||||
default_location_name = input("Set Default Location (all): ").strip()
|
||||
if default_location_name == "":
|
||||
default_location_name = "all"
|
||||
def create(site_name, owner_name, default_zone_name, default_location_name, email=""):
|
||||
|
||||
if not os.path.exists(f"sites/{site_name}"):
|
||||
print(f"Creating {site_name} site...")
|
||||
@ -82,7 +70,7 @@ def create():
|
||||
with open(f"sites/{site_name}/site.ini", "w+") as config:
|
||||
config.write(f"[site]\n")
|
||||
config.write(f"site_name={site_name}\n")
|
||||
config.write(f"site_owner={site_owner}\n")
|
||||
config.write(f"site_owner={owner_name}\n")
|
||||
config.write(f"email={email}\n")
|
||||
config.write(f"\n")
|
||||
|
||||
|
||||
47
scratch.py
47
scratch.py
@ -1,38 +1,25 @@
|
||||
sql = "SELECT items FROM main_locations WHERE id=1;"
|
||||
|
||||
from config import config
|
||||
import psycopg2, requests
|
||||
import main, datetime
|
||||
import main, datetime, json
|
||||
|
||||
|
||||
"""database_config = config()
|
||||
|
||||
database_config = config()
|
||||
with psycopg2.connect(**database_config) as conn:
|
||||
result = main.setLocationData(conn, "main", "default@all", 1, 4.0, 0.0)
|
||||
print(result)"""
|
||||
|
||||
url = "http://192.168.1.45:5810/resolveReceiptItem"
|
||||
"""payload_receipt = {
|
||||
"receipt_id": 123456,
|
||||
"receipt_status": "Unresolved",
|
||||
"date_submitted": str(datetime.datetime.now()),
|
||||
"submitted_by": 1,
|
||||
"vendor_id": 0,
|
||||
"files": {},
|
||||
"items": [
|
||||
("FOOD", 0, "%1234%", "test_item", 1.0, {"cost": 1.99, "EXPIRES": False}, "Unresolved"),
|
||||
("FOOD", 0, "%1235%", "test_item", 1.0, {"cost": 1.99, "EXPIRES": False}, "Unresolved"),
|
||||
("FOOD", 0, "%1236%", "test_item", 1.0, {"cost": 1.99, "EXPIRES": False}, "Unresolved"),
|
||||
],
|
||||
"site_name": "main"
|
||||
}"""
|
||||
# update user with site_id and admin role.
|
||||
sqlfile = open('sites/main/sql/unique/shopping_lists_safetystock_uncalculated.sql', "r+")
|
||||
sql = sqlfile.readlines()
|
||||
sql = "\n".join(sql)
|
||||
sqlfile.close()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sql, (1, ))
|
||||
x = cur.fetchall()
|
||||
for _ in x:
|
||||
print(_)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
response = requests.post(url)
|
||||
|
||||
receipt_id = response.json()["receipt_id"]
|
||||
|
||||
|
||||
print(receipt_id)
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
[site]
|
||||
site_name=Backpack
|
||||
site_owner=Jadowyne
|
||||
site_owner=jadowyne
|
||||
email=
|
||||
|
||||
[defaults]
|
||||
default_zone=default
|
||||
default_primary_location=all
|
||||
default_auto_issue_location=all
|
||||
default_zone=MAIN
|
||||
default_primary_location=POUCH A
|
||||
default_auto_issue_location=POUCH A
|
||||
|
||||
@ -2,15 +2,21 @@ CREATE TABLE IF NOT EXISTS logins(
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(255),
|
||||
password VARCHAR(255),
|
||||
favorites JSONB,
|
||||
unseen_pantry_items INTEGER [],
|
||||
unseen_groups INTEGER [],
|
||||
unseen_shopping_lists INTEGER [],
|
||||
unseen_recipes INTEGER [],
|
||||
seen_pantry_items INTEGER [],
|
||||
seen_groups INTEGER[],
|
||||
seen_shopping_lists INTEGER [],
|
||||
seen_recipes INTEGER [],
|
||||
flags JSONB
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
favorites JSONB DEFAULT '{}',
|
||||
unseen_pantry_items INTEGER [] DEFAULT '{}',
|
||||
unseen_groups INTEGER [] DEFAULT '{}',
|
||||
unseen_shopping_lists INTEGER [] DEFAULT '{}',
|
||||
unseen_recipes INTEGER [] DEFAULT '{}',
|
||||
seen_pantry_items INTEGER [] DEFAULT '{}',
|
||||
seen_groups INTEGER[] DEFAULT '{}',
|
||||
seen_shopping_lists INTEGER [] DEFAULT '{}',
|
||||
seen_recipes INTEGER [] DEFAULT '{}',
|
||||
sites INTEGER [] DEFAULT '{}',
|
||||
site_roles INTEGER [] DEFAULT '{}',
|
||||
system_admin BOOLEAN DEFAULT FALSE,
|
||||
flags JSONB DEFAULT '{}',
|
||||
UNIQUE(username),
|
||||
CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')
|
||||
);
|
||||
|
||||
|
||||
11
sites/Backpack/sql/create/roles.sql
Normal file
11
sites/Backpack/sql/create/roles.sql
Normal file
@ -0,0 +1,11 @@
|
||||
CREATE TABLE IF NOT EXISTS roles(
|
||||
id SERIAL PRIMARY KEY,
|
||||
role_name VARCHAR(255) NOT NULL,
|
||||
role_description TEXT,
|
||||
site_id INTEGER NOT NULL,
|
||||
flags JSONB DEFAULT '{}',
|
||||
UNIQUE(role_name, site_id),
|
||||
CONSTRAINT fk_site
|
||||
FOREIGN KEY(site_id)
|
||||
REFERENCES sites(id)
|
||||
);
|
||||
15
sites/Backpack/sql/create/sites.sql
Normal file
15
sites/Backpack/sql/create/sites.sql
Normal file
@ -0,0 +1,15 @@
|
||||
CREATE TABLE IF NOT EXISTS sites (
|
||||
id SERIAL PRIMARY KEY,
|
||||
site_name VARCHAR(120),
|
||||
site_description TEXT,
|
||||
creation_date TIMESTAMP,
|
||||
site_owner_id INTEGER NOT NULL,
|
||||
flags JSONB,
|
||||
default_zone VARCHAR(32),
|
||||
default_auto_issue_location VARCHAR(32),
|
||||
default_primary_location VARCHAR(32),
|
||||
UNIQUE(site_name),
|
||||
CONSTRAINT fk_site_owner
|
||||
FOREIGN KEY(site_owner_id)
|
||||
REFERENCES logins(id)
|
||||
);
|
||||
43
sites/Backpack/sql/unique/shopping_lists_safetystock.sql
Normal file
43
sites/Backpack/sql/unique/shopping_lists_safetystock.sql
Normal file
@ -0,0 +1,43 @@
|
||||
WITH sum_cte AS (
|
||||
SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum
|
||||
FROM Backpack_item_locations mil
|
||||
JOIN Backpack_items mi ON mil.part_id = mi.id
|
||||
GROUP BY mi.id
|
||||
)
|
||||
SELECT *
|
||||
FROM Backpack_items
|
||||
LEFT JOIN Backpack_item_info ON Backpack_items.item_info_id = Backpack_item_info.id
|
||||
LEFT JOIN sum_cte ON Backpack_items.id = sum_cte.id
|
||||
WHERE Backpack_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0)
|
||||
AND Backpack_item_info.shopping_lists @> ARRAY[%s];
|
||||
|
||||
/*
|
||||
00 - item_id
|
||||
01 - barcode
|
||||
02 - item_name
|
||||
03 - brand (id)
|
||||
04 - description
|
||||
05 - tags
|
||||
06 - links
|
||||
07 - item_info_id
|
||||
08 - logistics_info_id
|
||||
09 - food_info_id
|
||||
10 - row_type
|
||||
11 - item_type
|
||||
12 - search_string
|
||||
13 - item_info_id
|
||||
14 - barcode
|
||||
15 - linked_items
|
||||
16 - shopping_lists
|
||||
17 - recipes
|
||||
18 - groups
|
||||
19 - packaging
|
||||
20 - uom
|
||||
21 - cost
|
||||
22 - safety_stock
|
||||
23 - lead_time_days
|
||||
24 - ai_pick
|
||||
25 - sum_cte_id
|
||||
26 - total_sum/QOH
|
||||
*/
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
WITH sum_cte AS (
|
||||
SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum
|
||||
FROM Backpack_item_locations mil
|
||||
JOIN Backpack_items mi ON mil.part_id = mi.id
|
||||
GROUP BY mi.id
|
||||
)
|
||||
SELECT COUNT(*)
|
||||
FROM Backpack_items
|
||||
LEFT JOIN Backpack_item_info ON Backpack_items.item_info_id = Backpack_item_info.id
|
||||
LEFT JOIN sum_cte ON Backpack_items.id = sum_cte.id
|
||||
WHERE Backpack_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0)
|
||||
AND Backpack_item_info.shopping_lists @> ARRAY[%s];
|
||||
@ -0,0 +1,11 @@
|
||||
WITH sum_cte AS (
|
||||
SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum
|
||||
FROM Backpack_item_locations mil
|
||||
JOIN Backpack_items mi ON mil.part_id = mi.id
|
||||
GROUP BY mi.id
|
||||
)
|
||||
SELECT *
|
||||
FROM Backpack_items
|
||||
LEFT JOIN Backpack_item_info ON Backpack_items.item_info_id = Backpack_item_info.id
|
||||
LEFT JOIN sum_cte ON Backpack_items.id = sum_cte.id
|
||||
WHERE Backpack_item_info.shopping_lists @> ARRAY[%s];
|
||||
@ -2,15 +2,21 @@ CREATE TABLE IF NOT EXISTS logins(
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(255),
|
||||
password VARCHAR(255),
|
||||
favorites JSONB,
|
||||
unseen_pantry_items INTEGER [],
|
||||
unseen_groups INTEGER [],
|
||||
unseen_shopping_lists INTEGER [],
|
||||
unseen_recipes INTEGER [],
|
||||
seen_pantry_items INTEGER [],
|
||||
seen_groups INTEGER[],
|
||||
seen_shopping_lists INTEGER [],
|
||||
seen_recipes INTEGER [],
|
||||
flags JSONB
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
favorites JSONB DEFAULT '{}',
|
||||
unseen_pantry_items INTEGER [] DEFAULT '{}',
|
||||
unseen_groups INTEGER [] DEFAULT '{}',
|
||||
unseen_shopping_lists INTEGER [] DEFAULT '{}',
|
||||
unseen_recipes INTEGER [] DEFAULT '{}',
|
||||
seen_pantry_items INTEGER [] DEFAULT '{}',
|
||||
seen_groups INTEGER[] DEFAULT '{}',
|
||||
seen_shopping_lists INTEGER [] DEFAULT '{}',
|
||||
seen_recipes INTEGER [] DEFAULT '{}',
|
||||
sites INTEGER [] DEFAULT '{}',
|
||||
site_roles INTEGER [] DEFAULT '{}',
|
||||
system_admin BOOLEAN DEFAULT FALSE,
|
||||
flags JSONB DEFAULT '{}',
|
||||
UNIQUE(username),
|
||||
CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')
|
||||
);
|
||||
|
||||
|
||||
11
sites/default/sql/create/roles.sql
Normal file
11
sites/default/sql/create/roles.sql
Normal file
@ -0,0 +1,11 @@
|
||||
CREATE TABLE IF NOT EXISTS roles(
|
||||
id SERIAL PRIMARY KEY,
|
||||
role_name VARCHAR(255) NOT NULL,
|
||||
role_description TEXT,
|
||||
site_id INTEGER NOT NULL,
|
||||
flags JSONB DEFAULT '{}',
|
||||
UNIQUE(role_name, site_id),
|
||||
CONSTRAINT fk_site
|
||||
FOREIGN KEY(site_id)
|
||||
REFERENCES sites(id)
|
||||
);
|
||||
15
sites/default/sql/create/sites.sql
Normal file
15
sites/default/sql/create/sites.sql
Normal file
@ -0,0 +1,15 @@
|
||||
CREATE TABLE IF NOT EXISTS sites (
|
||||
id SERIAL PRIMARY KEY,
|
||||
site_name VARCHAR(120),
|
||||
site_description TEXT,
|
||||
creation_date TIMESTAMP,
|
||||
site_owner_id INTEGER NOT NULL,
|
||||
flags JSONB,
|
||||
default_zone VARCHAR(32),
|
||||
default_auto_issue_location VARCHAR(32),
|
||||
default_primary_location VARCHAR(32),
|
||||
UNIQUE(site_name),
|
||||
CONSTRAINT fk_site_owner
|
||||
FOREIGN KEY(site_owner_id)
|
||||
REFERENCES logins(id)
|
||||
);
|
||||
43
sites/default/sql/unique/shopping_lists_safetystock.sql
Normal file
43
sites/default/sql/unique/shopping_lists_safetystock.sql
Normal file
@ -0,0 +1,43 @@
|
||||
WITH sum_cte AS (
|
||||
SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum
|
||||
FROM %sitename%_item_locations mil
|
||||
JOIN %sitename%_items mi ON mil.part_id = mi.id
|
||||
GROUP BY mi.id
|
||||
)
|
||||
SELECT *
|
||||
FROM %sitename%_items
|
||||
LEFT JOIN %sitename%_item_info ON %sitename%_items.item_info_id = %sitename%_item_info.id
|
||||
LEFT JOIN sum_cte ON %sitename%_items.id = sum_cte.id
|
||||
WHERE %sitename%_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0)
|
||||
AND %sitename%_item_info.shopping_lists @> ARRAY[%s];
|
||||
|
||||
/*
|
||||
00 - item_id
|
||||
01 - barcode
|
||||
02 - item_name
|
||||
03 - brand (id)
|
||||
04 - description
|
||||
05 - tags
|
||||
06 - links
|
||||
07 - item_info_id
|
||||
08 - logistics_info_id
|
||||
09 - food_info_id
|
||||
10 - row_type
|
||||
11 - item_type
|
||||
12 - search_string
|
||||
13 - item_info_id
|
||||
14 - barcode
|
||||
15 - linked_items
|
||||
16 - shopping_lists
|
||||
17 - recipes
|
||||
18 - groups
|
||||
19 - packaging
|
||||
20 - uom
|
||||
21 - cost
|
||||
22 - safety_stock
|
||||
23 - lead_time_days
|
||||
24 - ai_pick
|
||||
25 - sum_cte_id
|
||||
26 - total_sum/QOH
|
||||
*/
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
WITH sum_cte AS (
|
||||
SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum
|
||||
FROM %sitename%_item_locations mil
|
||||
JOIN %sitename%_items mi ON mil.part_id = mi.id
|
||||
GROUP BY mi.id
|
||||
)
|
||||
SELECT COUNT(*)
|
||||
FROM %sitename%_items
|
||||
LEFT JOIN %sitename%_item_info ON %sitename%_items.item_info_id = %sitename%_item_info.id
|
||||
LEFT JOIN sum_cte ON %sitename%_items.id = sum_cte.id
|
||||
WHERE %sitename%_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0)
|
||||
AND %sitename%_item_info.shopping_lists @> ARRAY[%s];
|
||||
@ -0,0 +1,11 @@
|
||||
WITH sum_cte AS (
|
||||
SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum
|
||||
FROM %sitename%_item_locations mil
|
||||
JOIN %sitename%_items mi ON mil.part_id = mi.id
|
||||
GROUP BY mi.id
|
||||
)
|
||||
SELECT *
|
||||
FROM %sitename%_items
|
||||
LEFT JOIN %sitename%_item_info ON %sitename%_items.item_info_id = %sitename%_item_info.id
|
||||
LEFT JOIN sum_cte ON %sitename%_items.id = sum_cte.id
|
||||
WHERE %sitename%_item_info.shopping_lists @> ARRAY[%s];
|
||||
@ -1,9 +1,9 @@
|
||||
[site]
|
||||
site_name=main
|
||||
site_owner=
|
||||
email=
|
||||
site_owner=jadowyne
|
||||
email=jadowyne.ulve@outlook.com
|
||||
|
||||
[defaults]
|
||||
default_zone=default
|
||||
default_primary_location=all
|
||||
default_auto_issue_location=all
|
||||
default_zone=DEFAULT
|
||||
default_primary_location=ALL
|
||||
default_auto_issue_location=ALL
|
||||
|
||||
@ -2,15 +2,21 @@ CREATE TABLE IF NOT EXISTS logins(
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(255),
|
||||
password VARCHAR(255),
|
||||
favorites JSONB,
|
||||
unseen_pantry_items INTEGER [],
|
||||
unseen_groups INTEGER [],
|
||||
unseen_shopping_lists INTEGER [],
|
||||
unseen_recipes INTEGER [],
|
||||
seen_pantry_items INTEGER [],
|
||||
seen_groups INTEGER[],
|
||||
seen_shopping_lists INTEGER [],
|
||||
seen_recipes INTEGER [],
|
||||
flags JSONB
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
favorites JSONB DEFAULT '{}',
|
||||
unseen_pantry_items INTEGER [] DEFAULT '{}',
|
||||
unseen_groups INTEGER [] DEFAULT '{}',
|
||||
unseen_shopping_lists INTEGER [] DEFAULT '{}',
|
||||
unseen_recipes INTEGER [] DEFAULT '{}',
|
||||
seen_pantry_items INTEGER [] DEFAULT '{}',
|
||||
seen_groups INTEGER[] DEFAULT '{}',
|
||||
seen_shopping_lists INTEGER [] DEFAULT '{}',
|
||||
seen_recipes INTEGER [] DEFAULT '{}',
|
||||
sites INTEGER [] DEFAULT '{}',
|
||||
site_roles INTEGER [] DEFAULT '{}',
|
||||
system_admin BOOLEAN DEFAULT FALSE,
|
||||
flags JSONB DEFAULT '{}',
|
||||
UNIQUE(username),
|
||||
CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')
|
||||
);
|
||||
|
||||
|
||||
11
sites/main/sql/create/roles.sql
Normal file
11
sites/main/sql/create/roles.sql
Normal file
@ -0,0 +1,11 @@
|
||||
CREATE TABLE IF NOT EXISTS roles(
|
||||
id SERIAL PRIMARY KEY,
|
||||
role_name VARCHAR(255) NOT NULL,
|
||||
role_description TEXT,
|
||||
site_id INTEGER NOT NULL,
|
||||
flags JSONB DEFAULT '{}',
|
||||
UNIQUE(role_name, site_id),
|
||||
CONSTRAINT fk_site
|
||||
FOREIGN KEY(site_id)
|
||||
REFERENCES sites(id)
|
||||
);
|
||||
15
sites/main/sql/create/sites.sql
Normal file
15
sites/main/sql/create/sites.sql
Normal file
@ -0,0 +1,15 @@
|
||||
CREATE TABLE IF NOT EXISTS sites (
|
||||
id SERIAL PRIMARY KEY,
|
||||
site_name VARCHAR(120),
|
||||
site_description TEXT,
|
||||
creation_date TIMESTAMP,
|
||||
site_owner_id INTEGER NOT NULL,
|
||||
flags JSONB,
|
||||
default_zone VARCHAR(32),
|
||||
default_auto_issue_location VARCHAR(32),
|
||||
default_primary_location VARCHAR(32),
|
||||
UNIQUE(site_name),
|
||||
CONSTRAINT fk_site_owner
|
||||
FOREIGN KEY(site_owner_id)
|
||||
REFERENCES logins(id)
|
||||
);
|
||||
12
sites/main/sql/unique/shopping_lists_safetystock.sql
Normal file
12
sites/main/sql/unique/shopping_lists_safetystock.sql
Normal file
@ -0,0 +1,12 @@
|
||||
WITH sum_cte AS (
|
||||
SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum
|
||||
FROM main_item_locations mil
|
||||
JOIN main_items mi ON mil.part_id = mi.id
|
||||
GROUP BY mi.id
|
||||
)
|
||||
SELECT *
|
||||
FROM main_items
|
||||
LEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.id
|
||||
LEFT JOIN sum_cte ON main_items.id = sum_cte.id
|
||||
WHERE main_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0)
|
||||
AND main_item_info.shopping_lists @> ARRAY[%s];
|
||||
12
sites/main/sql/unique/shopping_lists_safetystock_count.sql
Normal file
12
sites/main/sql/unique/shopping_lists_safetystock_count.sql
Normal file
@ -0,0 +1,12 @@
|
||||
WITH sum_cte AS (
|
||||
SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum
|
||||
FROM main_item_locations mil
|
||||
JOIN main_items mi ON mil.part_id = mi.id
|
||||
GROUP BY mi.id
|
||||
)
|
||||
SELECT COUNT(*)
|
||||
FROM main_items
|
||||
LEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.id
|
||||
LEFT JOIN sum_cte ON main_items.id = sum_cte.id
|
||||
WHERE main_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0)
|
||||
AND main_item_info.shopping_lists @> ARRAY[%s];
|
||||
@ -0,0 +1,11 @@
|
||||
WITH sum_cte AS (
|
||||
SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum
|
||||
FROM main_item_locations mil
|
||||
JOIN main_items mi ON mil.part_id = mi.id
|
||||
GROUP BY mi.id
|
||||
)
|
||||
SELECT *
|
||||
FROM main_items
|
||||
LEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.id
|
||||
LEFT JOIN sum_cte ON main_items.id = sum_cte.id
|
||||
WHERE main_item_info.shopping_lists @> ARRAY[%s];
|
||||
@ -1,9 +0,0 @@
|
||||
[site]
|
||||
site_name=test
|
||||
site_owner=
|
||||
email=
|
||||
|
||||
[defaults]
|
||||
default_zone=default
|
||||
default_primary_location=all
|
||||
default_auto_issue_location=all
|
||||
@ -1,4 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS test_brands (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255)
|
||||
);
|
||||
@ -1,7 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS test_food_info (
|
||||
id SERIAL PRIMARY KEY,
|
||||
food_groups TEXT [],
|
||||
ingrediants TEXT [],
|
||||
nutrients JSONB,
|
||||
expires BOOLEAN
|
||||
);
|
||||
@ -1,8 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS test_groups(
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
included_items INTEGER [],
|
||||
group_type VARCHAR(255),
|
||||
UNIQUE (name)
|
||||
);
|
||||
@ -1,32 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS test_items(
|
||||
id SERIAL PRIMARY KEY,
|
||||
barcode VARCHAR(255) NOT NULL,
|
||||
item_name VARCHAR(255) NOT NULL,
|
||||
brand INTEGER,
|
||||
description TEXT,
|
||||
tags TEXT [],
|
||||
links JSONB,
|
||||
item_info_id INTEGER NOT NULL,
|
||||
logistics_info_id INTEGER NOT NULL,
|
||||
food_info_id INTEGER,
|
||||
row_type VARCHAR(255) NOT NULL,
|
||||
item_type VARCHAR(255) NOT NULL,
|
||||
search_string TEXT NOT NULL,
|
||||
UNIQUE(barcode, item_info_id),
|
||||
CONSTRAINT fk_item_info
|
||||
FOREIGN KEY(item_info_id)
|
||||
REFERENCES test_item_info(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT fk_food_info
|
||||
FOREIGN KEY(food_info_id)
|
||||
REFERENCES test_food_info(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT fk_brand
|
||||
FOREIGN KEY(brand)
|
||||
REFERENCES test_brands(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT fk_logistics_info
|
||||
FOREIGN KEY(logistics_info_id)
|
||||
REFERENCES test_logistics_info(id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
@ -1,15 +0,0 @@
|
||||
CREATE TABLE IF NOt EXISTS test_item_info (
|
||||
id SERIAL PRIMARY KEY,
|
||||
barcode VARCHAR(255) NOT NULL,
|
||||
linked_items INTEGER [],
|
||||
shopping_lists INTEGER [],
|
||||
recipes INTEGER [],
|
||||
groups INTEGER [],
|
||||
packaging VARCHAR(255),
|
||||
uom VARCHAR(255),
|
||||
cost FLOAT8,
|
||||
safety_stock FLOAT8,
|
||||
lead_time_days FLOAT8,
|
||||
ai_pick BOOLEAN,
|
||||
UNIQUE(barcode)
|
||||
);
|
||||
@ -1,23 +0,0 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'cost_layer') THEN
|
||||
CREATE TYPE cost_layer AS (qty FLOAT8, cost FLOAT8);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS test_item_locations(
|
||||
id SERIAL PRIMARY KEY,
|
||||
part_id INTEGER NOT NULL,
|
||||
location_id INTEGER NOT NULL,
|
||||
quantity_on_hand FLOAT8 NOT NULL,
|
||||
cost_layers cost_layer[],
|
||||
UNIQUE(part_id, location_id),
|
||||
CONSTRAINT fk_part_id
|
||||
FOREIGN KEY(part_id)
|
||||
REFERENCES test_items(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT fk_location_id
|
||||
FOREIGN KEY(location_id)
|
||||
REFERENCES test_locations(id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
@ -1,8 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS test_itemlinks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
barcode VARCHAR(255) NOt NULL,
|
||||
link INTEGER NOT NULL,
|
||||
data JSONB NOT NULL,
|
||||
conv_factor FLOAT8 NOt NULL,
|
||||
UNIQUE(barcode)
|
||||
);
|
||||
@ -1,11 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS test_locations(
|
||||
id SERIAL PRIMARY KEY,
|
||||
uuid VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(32) NOT NULL,
|
||||
zone_id INTEGER NOT NULL,
|
||||
items JSONB,
|
||||
UNIQUE(uuid),
|
||||
CONSTRAINT fk_zone
|
||||
FOREIGN KEY(zone_id)
|
||||
REFERENCES test_zones(id)
|
||||
);
|
||||
@ -1,16 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS logins(
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(255),
|
||||
password VARCHAR(255),
|
||||
favorites JSONB,
|
||||
unseen_pantry_items INTEGER [],
|
||||
unseen_groups INTEGER [],
|
||||
unseen_shopping_lists INTEGER [],
|
||||
unseen_recipes INTEGER [],
|
||||
seen_pantry_items INTEGER [],
|
||||
seen_groups INTEGER[],
|
||||
seen_shopping_lists INTEGER [],
|
||||
seen_recipes INTEGER [],
|
||||
flags JSONB
|
||||
);
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS test_logistics_info(
|
||||
id SERIAL PRIMARY KEY,
|
||||
barcode VARCHAR(255) NOT NULL,
|
||||
primary_location VARCHAR(64),
|
||||
auto_issue_location VARCHAR(64),
|
||||
dynamic_locations JSONB,
|
||||
location_data JSONB,
|
||||
quantity_on_hand FLOAT8 NOT NULL,
|
||||
UNIQUE(barcode)
|
||||
);
|
||||
@ -1,13 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS test_receipt_items (
|
||||
id SERIAL PRIMARY KEY,
|
||||
type VARCHAR(255) NOT NULL,
|
||||
receipt_id INTEGER NOT NULL,
|
||||
barcode VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
qty FLOAT8 NOT NULL,
|
||||
data JSONB,
|
||||
status VARCHAR (64),
|
||||
CONSTRAINT fk_receipt
|
||||
FOREIGN KEY(receipt_id)
|
||||
REFERENCES test_receipts(id)
|
||||
);
|
||||
@ -1,13 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS test_receipts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
receipt_id VARCHAR (32) NOT NULL,
|
||||
receipt_status VARCHAR (64) NOT NULL,
|
||||
date_submitted TIMESTAMP NOT NULL,
|
||||
submitted_by INTEGER NOT NULL,
|
||||
vendor_id INTEGER,
|
||||
files JSONB,
|
||||
UNIQUE(receipt_id),
|
||||
CONSTRAINT fk_vendor
|
||||
FOREIGN KEY(vendor_id)
|
||||
REFERENCES test_vendors(id)
|
||||
);
|
||||
@ -1,12 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS test_recipes (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR,
|
||||
author INTEGER,
|
||||
description TEXT,
|
||||
creation_date TIMESTAMP,
|
||||
custom_items JSONB,
|
||||
pantry_items JSONB,
|
||||
group_items JSONB,
|
||||
instructions TEXT [],
|
||||
picture_path TEXT
|
||||
);
|
||||
@ -1,14 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS test_shopping_lists (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
pantry_items INTEGER [],
|
||||
custom_items JSONB,
|
||||
recipes INTEGER [],
|
||||
groups INTEGER [],
|
||||
quantities JSONB,
|
||||
author INTEGER,
|
||||
creation_date TIMESTAMP,
|
||||
type VARCHAR(64),
|
||||
UNIQUE(name)
|
||||
);
|
||||
@ -1,15 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS test_Transactions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
timestamp TIMESTAMP,
|
||||
logistics_info_id INTEGER NOT NULL,
|
||||
barcode VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(255),
|
||||
transaction_type VARCHAR(255) NOT NULL,
|
||||
quantity FLOAT8 NOT NULL,
|
||||
description TEXT,
|
||||
user_id INTEGER NOT NULL,
|
||||
data JSONB,
|
||||
CONSTRAINT fk_logistics_info
|
||||
FOREIGN KEY(logistics_info_id)
|
||||
REFERENCES test_logistics_info(id)
|
||||
);
|
||||
@ -1,8 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS test_vendors (
|
||||
id SERIAL PRIMARY KEY,
|
||||
vendor_name VARCHAR(255) NOT NULL,
|
||||
vendor_address VARCHAR(255),
|
||||
creation_date TIMESTAMP NOT NULL,
|
||||
created_by INTEGER NOT NULL,
|
||||
phone_number VARCHAR(32)
|
||||
);
|
||||
@ -1,5 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS test_zones(
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(32) NOT NULL,
|
||||
UNIQUE(name)
|
||||
);
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_brands CASCADE;
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_food_info CASCADE;
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_groups CASCADE;
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_item_info CASCADE;
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_item_locations CASCADE;
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_items CASCADE;
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_itemlinks CASCADE;
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_locations CASCADE;
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_logistics_info CASCADE;
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_receipt_items CASCADE;
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_receipts CASCADE;
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_recipes CASCADE;
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_shopping_lists CASCADE;
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_transactions CASCADE;
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_vendors CASCADE;
|
||||
@ -1 +0,0 @@
|
||||
DROP TABLE test_zones CASCADE;
|
||||
@ -1,3 +0,0 @@
|
||||
INSERT INTO test_transactions
|
||||
(timestamp, logistics_info_id, barcode, name, transaction_type, quantity, description, user_id, data)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s);
|
||||
@ -1,3 +0,0 @@
|
||||
UPDATE test_logistics_info
|
||||
SET quantity_on_hand = %s, location_data = %s
|
||||
WHERE id = %s;
|
||||
@ -1,45 +0,0 @@
|
||||
SELECT * FROM test_items
|
||||
LEFT JOIN test_logistics_info ON test_items.logistics_info_id = test_logistics_info.id
|
||||
LEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.id
|
||||
LEFT JOIN test_food_info ON test_items.food_info_id = test_food_info.id
|
||||
WHERE test_items.id=%s;
|
||||
|
||||
/*
|
||||
00 - item_id
|
||||
01 - barcode
|
||||
02 - item_name
|
||||
03 - brand (id)
|
||||
04 - description
|
||||
05 - tags
|
||||
06 - links
|
||||
07 - item_info_id
|
||||
08 - logistics_info_id
|
||||
09 - food_info_id
|
||||
10 - row_type
|
||||
11 - item_type
|
||||
12 - search_string
|
||||
13 - logistics_info_id
|
||||
14 - barcode
|
||||
15 - primary_location
|
||||
16 - auto_issue_location
|
||||
17 - dynamic_locations
|
||||
18 - location_data
|
||||
19 - quantity_on_hand
|
||||
20 - item_info_id
|
||||
21 - barcode
|
||||
22 - linked_items
|
||||
23 - shopping_lists
|
||||
24 - recipes
|
||||
25 - groups
|
||||
26 - packaging
|
||||
27 - uom
|
||||
28 - cost
|
||||
29 - safety_stock
|
||||
30 - lead_time_days
|
||||
31 - ai_pick
|
||||
32 - food_info_id
|
||||
33 - food_groups
|
||||
34 - ingrediants
|
||||
35 - nutrients
|
||||
36 - expires
|
||||
*/
|
||||
@ -1,45 +0,0 @@
|
||||
SELECT * FROM test_items
|
||||
LEFT JOIN test_logistics_info ON test_items.logistics_info_id = test_logistics_info.id
|
||||
LEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.id
|
||||
LEFT JOIN test_food_info ON test_items.food_info_id = test_food_info.id
|
||||
WHERE test_items.barcode=%s;
|
||||
|
||||
/*
|
||||
00 - item_id
|
||||
01 - barcode
|
||||
02 - item_name
|
||||
03 - brand (id)
|
||||
04 - description
|
||||
05 - tags
|
||||
06 - links
|
||||
07 - item_info_id
|
||||
08 - logistics_info_id
|
||||
09 - food_info_id
|
||||
10 - row_type
|
||||
11 - item_type
|
||||
12 - search_string
|
||||
13 - logistics_info_id
|
||||
14 - barcode
|
||||
15 - primary_location
|
||||
16 - auto_issue_location
|
||||
17 - dynamic_locations
|
||||
18 - location_data
|
||||
19 - quantity_on_hand
|
||||
20 - item_info_id
|
||||
21 - barcode
|
||||
22 - linked_items
|
||||
23 - shopping_lists
|
||||
24 - recipes
|
||||
25 - groups
|
||||
26 - packaging
|
||||
27 - uom
|
||||
28 - cost
|
||||
29 - safety_stock
|
||||
30 - lead_time_days
|
||||
31 - ai_pick
|
||||
32 - food_info_id
|
||||
33 - food_groups
|
||||
34 - ingrediants
|
||||
35 - nutrients
|
||||
36 - expires
|
||||
*/
|
||||
@ -1,3 +0,0 @@
|
||||
UPDATE test_locations
|
||||
SET items = %s
|
||||
WHERE id = %s;
|
||||
28
static/adminHandler.js
Normal file
28
static/adminHandler.js
Normal file
@ -0,0 +1,28 @@
|
||||
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;
|
||||
}
|
||||
|
||||
BIN
static/pictures/logo.jpg
Normal file
BIN
static/pictures/logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
449
templates/admin.html
Normal file
449
templates/admin.html
Normal file
@ -0,0 +1,449 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
|
||||
<title>Admin</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
|
||||
<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" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<style>
|
||||
header, main, footer, body {
|
||||
padding-left: 300px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width : 992px) {
|
||||
header, main, footer, body {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
.dropdown-disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
.item :hover{
|
||||
cursor: pointer;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
.custom_row:hover{
|
||||
background-color: rgb(230, 230, 230) !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
.my_btn:hover{
|
||||
background-color: rgb(230, 230, 230) !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<ul id="slide-out" class="sidenav sidenav-fixed z-depth-0" style="width: 250px; border-right: 2px;">
|
||||
<div class="center-align" style="padding-top: 10px; padding-bottom: 10px;">
|
||||
<img src="{{ url_for('static', filename='pictures/logo.jpg') }}" alt="Description" class="responsive-img circle center-align" style="width: 30%; height: auto;">
|
||||
</div>
|
||||
<li><a onclick="openSection('sites')">Sites</a></li>
|
||||
<li><a onclick="openSection('roles')">Roles</a></li>
|
||||
<li><a onclick="openSection('users')">Users</a></li>
|
||||
<li><a href="#!">Instance Settings</a></li>
|
||||
</ul>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="section">
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<button class="btn btn-flat sidenav-trigger hide-on-large-only" data-target="slide-out"><i class="material-symbols-outlined">side_navigation</i></button>
|
||||
<a href="/items" class="btn btn-flat right">home</a>
|
||||
<a href="/profile" class="btn btn-flat right">Profile</a>
|
||||
</div>
|
||||
<div class="col s12" id="main_body">
|
||||
<div id="sites" class="row hide">
|
||||
<div class="col s12">
|
||||
<h1>Your Sites</h1>
|
||||
</div>
|
||||
<div class="col s12">
|
||||
<p class="flow-text">Listed below are all the sites within your instance of MyPantry. Clicking on one will allow you
|
||||
edit most of the attributes inherited by the site.</p>
|
||||
</div>
|
||||
<div class="col s12" id="sites_div">
|
||||
<table id="sites_table">
|
||||
<tr>
|
||||
<th>Site</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col s12 center-align" style="padding-top: 10px;">
|
||||
<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>
|
||||
<div id="roles" class="row hide">
|
||||
<div class="col s12">
|
||||
<h1>Your Roles</h1>
|
||||
</div>
|
||||
<div class="col s12">
|
||||
<p class="flow-text">Listed below are all the roles within your instance of MyPantry. Clicking on one will allow you
|
||||
edit most of the attributes inherited by the role.</p>
|
||||
</div>
|
||||
<div class="col s12" id="roles_div">
|
||||
<table id="roles_table">
|
||||
<tr>
|
||||
<th>Site</th>
|
||||
<th>Role</th>
|
||||
<th>Role Description</th>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col s12 center-align" style="padding-top: 10px;">
|
||||
<span class="center-align"><button data-target="add_role"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>
|
||||
<div id="users" class="row hide">
|
||||
<div class="col s12">
|
||||
<h1>Your Users</h1>
|
||||
</div>
|
||||
<div class="col s12">
|
||||
<p class="flow-text">Listed below is all the users that have access to your instance.</p>
|
||||
</div>
|
||||
<div class="col s12" id="users_div">
|
||||
<table id="users_table">
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col s12 center-align" style="padding-top: 10px;">
|
||||
<span class="center-align"><button data-target="add_role"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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="add_site" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Create Site</h4>
|
||||
<div class="card-panel green lighten-4 z-depth-0">
|
||||
<span class="black-text">A site is a main component to your instance. Each site is a boudry property meaning that all parts
|
||||
made within the site is not accessible within other sites beyond cross-site features. Think of them like <b>House A</b>,
|
||||
<b>Garage</b>, or <b>Warehouse</b>.
|
||||
</span>
|
||||
</div>
|
||||
<div class="row" style="gap: 10px;">
|
||||
<div class="s12 m6 input-field">
|
||||
<input id="site_name" type="text" placeholder="main" maxlength="20">
|
||||
<label for="site_name">Site Name</label>
|
||||
<span class="supporting-text">Supporting Text</span>
|
||||
</div>
|
||||
<div class="s12 m6 input-field">
|
||||
<i class="material-icons prefix">account_circle</i>
|
||||
<input id="site_owner" type="text" placeholder=" " value="{{username}}" disabled>
|
||||
<label for="site_owner">Site Ownser</label>
|
||||
</div>
|
||||
<div class="input-field col s12">
|
||||
<textarea id="site_description" class="materialize-textarea" placeholder=" "></textarea>
|
||||
<label for="site_description">Site Description</label>
|
||||
</div>
|
||||
<div class="s12 m6 input-field">
|
||||
<input id="default_zone" type="text" placeholder="DEFAULT" value="DEFAULT" maxlength="20">
|
||||
<label for="default_zone">Default Zone</label>
|
||||
</div>
|
||||
<div class="s12 m6 input-field">
|
||||
<input id="default_location" type="text" placeholder="ALL" value="ALL" maxlength="20">
|
||||
<label for="default_location">Default Location</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button onclick="addSite()" class="modal-close waves-effect btn-flat green lighten-4">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="add_role" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Create Role</h4>
|
||||
<div class="card-panel green lighten-4 z-depth-0">
|
||||
<span class="black-text">A Site Role is used to define and assign general permissions to users for that specific site. This could be important
|
||||
as a user's permission to access certain sites is determined by their roles.
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<div class="row" style="gap: 10px;">
|
||||
<div class="s12 m6 input-field">
|
||||
<input id="role_name" type="text" placeholder="main" maxlength="20">
|
||||
<label for="role_name">Role Name</label>
|
||||
<span class="supporting-text">Supporting Text</span>
|
||||
</div>
|
||||
<div class="s12 m6 input-field">
|
||||
<select id="selected_site">
|
||||
<option value="" disabled selected>Choose your option</option>
|
||||
<option value="1">Option 1</option>
|
||||
<option value="2">Option 2</option>
|
||||
<option value="3">Option 3</option>
|
||||
</select>
|
||||
<label for="selected_site">Role's Site</label>
|
||||
</div>
|
||||
<div class="input-field col s12">
|
||||
<textarea id="role_description" class="materialize-textarea" placeholder=" "></textarea>
|
||||
<label for="role_description">Role Description</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button onclick="addRole()" class="modal-close waves-effect btn-flat green lighten-4">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="edit_role" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Edit Role</h4>
|
||||
<div class="card-panel green lighten-4 z-depth-0">
|
||||
<span class="black-text">A Site Role is used to define and assign general permissions to users for that specific site. This could be important
|
||||
as a user's permission to access certain sites is determined by their roles.
|
||||
</span>
|
||||
</div>
|
||||
<div class="row" style="gap: 10px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button onclick="" class="modal-close waves-effect btn-flat red lighten-4">Delete</button>
|
||||
<button onclick="" class="modal-close waves-effect btn-flat green lighten-4">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
<script src="{{ url_for('static', filename='adminHandler.js') }}"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
var elems = document.querySelectorAll('.sidenav');
|
||||
var instances = M.Sidenav.init(elems);
|
||||
var elems = document.querySelectorAll('.dropdown-trigger');
|
||||
var instances = M.Dropdown.init(elems, {});
|
||||
var elems = document.querySelectorAll('.collapsible');
|
||||
var instances = M.Collapsible.init(elems, {});
|
||||
var elems = document.querySelectorAll('.modal');
|
||||
var instances = M.Modal.init(elems, {});
|
||||
var elems = document.querySelectorAll('select');
|
||||
var instances = M.FormSelect.init(elems, {})
|
||||
M.AutoInit();
|
||||
await openSection('sites')
|
||||
});
|
||||
|
||||
async function openSection(section){
|
||||
let sections = ['sites', 'roles', 'users']
|
||||
for (i=0; i < sections.length; i++){
|
||||
document.getElementById(sections[i]).classList.add("hide");
|
||||
}
|
||||
document.getElementById(section).classList.remove("hide");
|
||||
if (section == "sites"){
|
||||
var sites = await fetchSites()
|
||||
await populateSites(sites)
|
||||
} else if (section == "roles") {
|
||||
await fetchPopulateRoles()
|
||||
} else if (section == "users"){
|
||||
var users = await fetchUsers(50, 1)
|
||||
await populateUsers(users)
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPopulateSites(){
|
||||
const url = new URL('/admin/getSites', window.location.origin);
|
||||
await fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
|
||||
const collection = document.getElementById('sites_collection')
|
||||
collection.innerHTML = ""
|
||||
data.sites.forEach(site => {
|
||||
console.log(site)
|
||||
|
||||
let list_item = document.createElement('li')
|
||||
list_item.classList.add('collection-item')
|
||||
list_item.classList.add('avatar')
|
||||
list_item.onclick = function(){
|
||||
selectSite(site[0])
|
||||
};
|
||||
list_item.id = site[0]
|
||||
list_item.style = "border-radius: 10px;"
|
||||
list_item.innerHTML = `
|
||||
<i class="material-icons circle green">insert_chart</i>
|
||||
<span class="title" style="font-size:16pt;">${site[1]}</span>
|
||||
<p>${site[3]}<br>${site[2]}</p>`
|
||||
collection.append(list_item)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function populateSites(sites){
|
||||
const table = document.getElementById("sites_table")
|
||||
while (table.rows.length > 1) {
|
||||
table.deleteRow(1);
|
||||
};
|
||||
|
||||
let reference_state = 1
|
||||
for(let i = 0; i < sites.length; i++){
|
||||
var row = table.insertRow();
|
||||
|
||||
var row_name = row.insertCell();
|
||||
var row_desc = row.insertCell();
|
||||
row_name.style = "display: inline-flex; align-items: center;"
|
||||
|
||||
|
||||
row_name.innerHTML = `<i class="material-symbols-outlined" style="padding-right: 5px;">wysiwyg</i>${sites[i][1]}`
|
||||
row_desc.innerHTML = `${sites[i][2]}`
|
||||
|
||||
|
||||
if ((reference_state % 2) == 0){
|
||||
row.classList.add('green')
|
||||
row.classList.add('lighten-5')
|
||||
}
|
||||
row.classList.add("custom_row")
|
||||
row.addEventListener('click', function(){
|
||||
clickRoleRow(sites[i][0])
|
||||
})
|
||||
reference_state++
|
||||
}
|
||||
}
|
||||
|
||||
async function populateUsers(users){
|
||||
const table = document.getElementById("users_table")
|
||||
while (table.rows.length > 1) {
|
||||
table.deleteRow(1);
|
||||
};
|
||||
let reference_state = 1
|
||||
for(let i = 0; i < users.length; i++){
|
||||
var row = table.insertRow();
|
||||
var row_username = row.insertCell();
|
||||
var row_email = row.insertCell();
|
||||
|
||||
|
||||
row_username.style = "display: inline-flex; align-items: center;"
|
||||
row_username.innerHTML = `<i class="material-symbols-outlined" style="padding-right: 5px;">person</i>${users[i][1]}`
|
||||
row_email.innerHTML = `${users[i][3]}`
|
||||
|
||||
|
||||
if ((reference_state % 2) == 0){
|
||||
row.classList.add('green')
|
||||
row.classList.add('lighten-5')
|
||||
}
|
||||
row.classList.add("custom_row")
|
||||
row.addEventListener('click', function(){
|
||||
clickRoleRow(users[i][0])
|
||||
})
|
||||
reference_state++
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPopulateRoles(){
|
||||
const Rolesurl = new URL('/getRoles', window.location.origin);
|
||||
await fetch(Rolesurl)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data.sites)
|
||||
|
||||
const table = document.getElementById("roles_table")
|
||||
|
||||
while (table.rows.length > 1) {
|
||||
table.deleteRow(1);
|
||||
};
|
||||
|
||||
let reference_state = 1
|
||||
|
||||
for (let key in data.sites){
|
||||
for (let i = 0; i < data.sites[key].length; i++){
|
||||
var row = table.insertRow();
|
||||
var row_site = row.insertCell();
|
||||
var row_name = row.insertCell();
|
||||
var row_description = row.insertCell();
|
||||
|
||||
row_site.style = "display: inline-flex; align-items: center;"
|
||||
|
||||
row_site.innerHTML = `<i class="material-symbols-outlined" style="padding-right: 5px;">group</i>${key}`
|
||||
row_name.innerHTML = `${data.sites[key][i][1]}`
|
||||
row_description.innerHTML = `${data.sites[key][i][2]}`
|
||||
|
||||
|
||||
if ((reference_state % 2) == 0){
|
||||
row.classList.add('green')
|
||||
row.classList.add('lighten-5')
|
||||
}
|
||||
row.classList.add("custom_row")
|
||||
row.addEventListener('click', function(){
|
||||
clickRoleRow(data.sites[key][i][0])
|
||||
})
|
||||
reference_state++
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var selectElement = document.getElementById('selected_site');
|
||||
selectElement.innerHTML = '';
|
||||
|
||||
const SitesURL = new URL('/admin/getSites', window.location.origin);
|
||||
await fetch(SitesURL)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data)
|
||||
data.sites.forEach(site => {
|
||||
var newOption = document.createElement('option')
|
||||
newOption.value = site[0];
|
||||
newOption.text = site[1];
|
||||
selectElement.appendChild(newOption);
|
||||
})
|
||||
})
|
||||
M.FormSelect.init(selectElement);
|
||||
|
||||
}
|
||||
|
||||
function selectSite(id){
|
||||
console.log(id)
|
||||
}
|
||||
|
||||
|
||||
async function addSite(){
|
||||
var site_name = document.getElementById('site_name').value
|
||||
var site_description = document.getElementById('site_description').value
|
||||
var default_zone = document.getElementById('default_zone').value
|
||||
var default_location = document.getElementById('default_location').value
|
||||
|
||||
await fetch(`/addSite`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
site_name: site_name,
|
||||
site_description: site_description,
|
||||
default_zone: default_zone,
|
||||
default_location: default_location,
|
||||
}),
|
||||
});
|
||||
|
||||
// var sites = await fetchSites()
|
||||
// await populateSites(sites)
|
||||
location.reload()
|
||||
M.toast({text: "Site has been added Successfully!", classes: "rounded green lighten-4 black-text"});
|
||||
}
|
||||
|
||||
async function addRole(){
|
||||
var role_name = document.getElementById('role_name').value
|
||||
var role_description = document.getElementById('role_description').value
|
||||
var selected_site_id = Number(document.getElementById('selected_site').value)
|
||||
|
||||
await fetch(`/addRole`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
role_name: role_name,
|
||||
role_description: role_description,
|
||||
site_id: selected_site_id
|
||||
}),
|
||||
});
|
||||
await fetchPopulateRoles()
|
||||
M.toast({text: "Role has been added Successfully!", classes: "rounded green lighten-4 black-text"});
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
131
templates/admin/role.html
Normal file
131
templates/admin/role.html
Normal file
@ -0,0 +1,131 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
|
||||
<title>Edit Role</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
|
||||
<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" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
|
||||
</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>
|
||||
<div class="container">
|
||||
<div class="section">
|
||||
<div class="row" style="gap: 1em;">
|
||||
<div class="col s12" style="padding-bottom: 10px;">
|
||||
<a href='{{ proto['referrer'] }}' class="left btn green lighten-4 black-text btn-flat"><i class="material-icons">arrow_back</i></a>
|
||||
<a href="/items" class="btn btn-flat right">Home</a>
|
||||
<a href="/profile" class="btn btn-flat right">Profile</a>
|
||||
</div>
|
||||
<div class="s12 m6 input-field">
|
||||
<input id="role_name" type="text" placeholder=" " maxlength="20">
|
||||
<label for="role_name">Name</label>
|
||||
</div>
|
||||
<div class="input-field col s12">
|
||||
<select id="role_site">
|
||||
<option value="" disabled selected>Choose your option</option>
|
||||
</select>
|
||||
<label for="role_site">Site</label>
|
||||
</div>
|
||||
<div class="input-field col s12">
|
||||
<textarea id="role_description" class="materialize-textarea" placeholder=" "></textarea>
|
||||
<label for="role_description">Description</label>
|
||||
</div>
|
||||
<div class="col s12">
|
||||
<h4>Permissions</h4>
|
||||
</div>
|
||||
<div class="col s12">
|
||||
<h5>All</h5>
|
||||
</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>
|
||||
</div>
|
||||
</body>
|
||||
<script src="{{ url_for('static', filename='adminHandler.js') }}"></script>
|
||||
<script>
|
||||
let role = {{role|tojson}}
|
||||
console.log(role)
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
var elems = document.querySelectorAll('select');
|
||||
var instances = M.FormSelect.init(elems, {})
|
||||
await populateRoleForm()
|
||||
});
|
||||
|
||||
async function populateRoleForm() {
|
||||
document.getElementById("role_name").value = role[1];
|
||||
document.getElementById("role_description").value = role[2];
|
||||
const selectElement = document.getElementById("role_site");
|
||||
selectElement.innerHTML = "";
|
||||
|
||||
var sites = await fetchSites()
|
||||
for (let i = 0; i < sites.length; i++){
|
||||
let newOption = document.createElement("option");
|
||||
newOption.value = sites[i][0];
|
||||
newOption.text = sites[i][1];
|
||||
selectElement.appendChild(newOption);
|
||||
};
|
||||
selectElement.value = role[3];
|
||||
M.FormSelect.init(selectElement);
|
||||
}
|
||||
|
||||
async function deleteRole(){
|
||||
const url = new URL('/deleteRole', window.location.origin);
|
||||
await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json',},
|
||||
body: JSON.stringify({role_id: role[0],}),
|
||||
});
|
||||
const adminurl = new URL(`/admin`, window.location.origin);
|
||||
window.location.href = adminurl.toString();
|
||||
}
|
||||
|
||||
</script>
|
||||
</html>
|
||||
17
templates/admin/users.html
Normal file
17
templates/admin/users.html
Normal file
@ -0,0 +1,17 @@
|
||||
<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>
|
||||
@ -19,45 +19,42 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
[type="radio"]:checked + span:after {
|
||||
border: 2px solid rgb(0 128 0 / 30%); /* Outline color */
|
||||
background-color: rgb(0 128 0 / 30%); /* Fill color */
|
||||
border: 2px solid rgb(0 128 0 / 30%);
|
||||
background-color: rgb(0 128 0 / 30%);
|
||||
}
|
||||
header, main, footer, body {
|
||||
padding-left: 300px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width : 992px) {
|
||||
header, main, footer, body {
|
||||
padding-left: 0;
|
||||
.dropdown-disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.dropdown-disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5; /* or your desired degree of transparency */
|
||||
}
|
||||
</style>
|
||||
<div class="navbar-fixed">
|
||||
<nav class="green lighten-4 text-black z-depth-0">
|
||||
<div class="nav-wrapper">
|
||||
<ul id="nav-mobile" class="left hide-on-med-and-down black-text">
|
||||
<li><a class="dropdown-trigger black-text" data-target="dropdown1">Current Site > {{current_site}}<i class="material-icons right">arrow_drop_down</i></a></li>
|
||||
<li class="active"><a href="/items" class="black-text">Site Items</a></li>
|
||||
<li><a href="/groups" class="black-text">Site Groups</a></li>
|
||||
<li><a href="/shopping-lists" class="black-text">Site Shopping Lists</a></li>
|
||||
<li><a href="/receipts" class="black-text">Site Receipts</a></li>
|
||||
</ul>
|
||||
<ul class="right">
|
||||
<li><a class="dropdown-trigger hide-on-med-and-down black-text" data-target="username_dropdown">{{username}}<i class="material-icons right">arrow_drop_down</i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<ul id='dropdown1' class='dropdown-content'>
|
||||
{% for site in sites %}
|
||||
<li><button class="btn transparent black-text z-depth-0" onclick="changeSite('{{ site }}')">{{site}}</button></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<ul id="slide-out" class="sidenav sidenav-fixed green lighten-4" style="width: 250px;">
|
||||
<li>
|
||||
<div class="user-view">
|
||||
<!-- <div class="background">
|
||||
<img src="images/office.jpg">
|
||||
</div> -->
|
||||
<!-- <a href="#user"><img class="circle" src="images/yuna.jpg"></a> -->
|
||||
<a href="#name"><span class="black-text name">John Doe</span></a>
|
||||
<a href="#email"><span class="black-text email">jdoe@example.com</span></a>
|
||||
</div>
|
||||
</li>
|
||||
<li><a class="dropdown-trigger" data-target="dropdown1">Current Site > {{current_site}}<i class="material-icons right">arrow_drop_down</i></a></li>
|
||||
<li><div class="divider grey darken-1" style="margin-left: 5px; margin-right: 10px;"></div></li>
|
||||
<li class="active"><a href="/items">Site Items</a></li>
|
||||
<li><a href="/groups">Site Groups</a></li>
|
||||
<li><a href="/shopping-lists">Site Shopping Lists</a></li>
|
||||
<li><a href="/receipts">Site Receipts</a></li>
|
||||
<ul id='username_dropdown' class='dropdown-content'>
|
||||
<li><a href="/profile" class="hide-on-med-and-down black-text">Profile</a></li>
|
||||
{% if system_admin == True %}
|
||||
<li><a href="/admin" class="hide-on-med-and-down black-text">Administration</a></li>
|
||||
{% endif %}
|
||||
<li><a href="/logout" class="hide-on-med-and-down black-text">Logout</a></li>
|
||||
</ul>
|
||||
<body>
|
||||
<div class="container section" style="padding-bottom: 72px;">
|
||||
|
||||
37
templates/login.html
Normal file
37
templates/login.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
|
||||
<title>My Pantry</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="valign-wrapper" style="height: 90vh;">
|
||||
<div class="container center-align">
|
||||
<div class="section">
|
||||
<div class="row" style="gap: 1em; margin-bottom: 10px;">
|
||||
<div class="s12 m6 offset-m3">
|
||||
<img src="{{ url_for('static', filename='pictures/logo.jpg') }}" alt="Description" class="responsive-img circle" style="width: 30%; height: auto;">
|
||||
</div>
|
||||
</div>
|
||||
<form class="row" style="gap: 1em;" action="/login" method="post" enctype="multipart/form-data">
|
||||
<div class="s12 m6 input-field offset-m3" style="margin: 10px;">
|
||||
<input id="username" type="text" name="username" class="validate" placeholder=" ">
|
||||
<label for="username">Username</label>
|
||||
</div>
|
||||
<div class="s12 m6 input-field offset-m3" style="margin: 10px;">
|
||||
<input id="password" type="password" name="password" class="validate" placeholder=" " maxlength="20">
|
||||
<label for="password">Password</label>
|
||||
</div>
|
||||
<div class="s12 m6 offset-m3" style="margin: 10px;">
|
||||
<button class="btn icon-right waves-effect waves-light right" type="submit" name="action">
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
91
templates/setup.html
Normal file
91
templates/setup.html
Normal file
@ -0,0 +1,91 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
|
||||
<title>My Pantry - Setup</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container section">
|
||||
<div class="s12 row">
|
||||
<form class="s12 row" style="gap: 1em;" action="/setup" method="post" enctype="multipart/form-data">
|
||||
<div class="s12">
|
||||
<h5>Database Setup</h5>
|
||||
<p> Setup your Postgresql database settings here! </p>
|
||||
</div>
|
||||
<div class="s12 input-field">
|
||||
<input id="database_address" name="database_address" type="text" placeholder=" ">
|
||||
<label for="database_address">Database Host</label>
|
||||
</div>
|
||||
<div class="s12 input-field ">
|
||||
<input id="database_port" name="database_port" type="text" placeholder=" ">
|
||||
<label for="database_port">Database Port</label>
|
||||
</div>
|
||||
<div class="s12 input-field">
|
||||
<input id="database_name" name="database_name" type="text" placeholder=" ">
|
||||
<label for="database_name">Database Name</label>
|
||||
</div>
|
||||
<div class="s12 input-field">
|
||||
<input id="database_user" name="database_user" type="text" placeholder=" ">
|
||||
<label for="database_user">Database User</label>
|
||||
</div>
|
||||
<div class="s12 input-field">
|
||||
<input id="database_password" type="password" name="database_password" placeholder=" ">
|
||||
<label for="database_password">Database Password</label>
|
||||
</div>
|
||||
<div class="s12">
|
||||
<h5>Admin Account</h5>
|
||||
<p> Setup your first admin account! This will be your main account to edit sites
|
||||
and other instance wide settings! Other users will later be able to be made admins
|
||||
as well </p>
|
||||
</div>
|
||||
<div class="s12 input-field">
|
||||
<input id="email" name="email" type="email" placeholder=" ">
|
||||
<label for="email">Email</label>
|
||||
</div>
|
||||
<div class="s12 input-field">
|
||||
<input id="username" name="username" type="text" placeholder=" ">
|
||||
<label for="username">Username</label>
|
||||
</div>
|
||||
<div class="s12 input-field">
|
||||
<input id="password" type="password" name="password" placeholder=" ">
|
||||
<label for="password">Password</label>
|
||||
</div>
|
||||
<div class="s12 input-field">
|
||||
<input id="password_verification" type="password" name="password_verification" placeholder=" ">
|
||||
<label for="password_verification">Retype Password</label>
|
||||
</div>
|
||||
<div class="s12">
|
||||
<h5>First Site</h5>
|
||||
<p> Setup your first Site! This will be your main site in this instance to start out.
|
||||
later you will be able to add more sites beyond this one or even delete this site! NOTE:
|
||||
you must have atleast one site.
|
||||
</p>
|
||||
</div>
|
||||
<div class="s12 input-field">
|
||||
<input id="site_name" name="site_name" type="text" placeholder=" ">
|
||||
<label for="site_name">Site Name</label>
|
||||
</div>
|
||||
<div class="input-field col s12">
|
||||
<textarea id="site_description" name="site_description" class="materialize-textarea" placeholder=" " value="This is your first site!"></textarea>
|
||||
<label for="site_description">Site Description</label>
|
||||
</div>
|
||||
<div class="s12 input-field">
|
||||
<input id="site_default_zone" name="site_default_zone" type="text" placeholder=" ">
|
||||
<label for="site_default_zone">Default Zone</label>
|
||||
</div>
|
||||
<div class="s12 input-field">
|
||||
<input id="site_default_location" name="site_default_location" type="text" placeholder=" ">
|
||||
<label for="site_default_location">Default Location</label>
|
||||
</div>
|
||||
<div class="s12 input-field">
|
||||
<button class="btn btn-flat green lighten-4 icon-right waves-effect waves-light right" type="submit" name="action">
|
||||
Submit<i class="material-icons right">send</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
@ -92,7 +92,6 @@
|
||||
|
||||
await populateInfo()
|
||||
await populateReferences()
|
||||
console.log(quantities)
|
||||
})
|
||||
|
||||
async function fetchList(){
|
||||
@ -129,7 +128,6 @@
|
||||
var tbl_body = document.createElement('tbody')
|
||||
|
||||
let inventory_items = shoppingList[3];
|
||||
console.log(inventory_items)
|
||||
|
||||
// if it is plain then we want to grab the qty from the quantities dictionary
|
||||
|
||||
@ -138,17 +136,16 @@
|
||||
var row = document.createElement('tr')
|
||||
|
||||
let name_cell = document.createElement('td')
|
||||
if (inventory_items[i][3]){
|
||||
name_cell.innerHTML = `<a href=${inventory_items[i][3].main} target="_blank">${inventory_items[i][2]}</a>`
|
||||
if (inventory_items[i][6]){
|
||||
name_cell.innerHTML = `<a href=${inventory_items[i][6].main} target="_blank">${inventory_items[i][2]}</a>`
|
||||
} else {
|
||||
name_cell.innerHTML = `${inventory_items[i][2]}`
|
||||
}
|
||||
|
||||
console.log(inventory_items[i])
|
||||
let qty_uom_cell = document.createElement('td')
|
||||
if(shoppingList[10] == 'calculated'){
|
||||
qty = Number(inventory_items[i][5]) - Number(inventory_items[i][4])
|
||||
uom = inventory_items[i][6]
|
||||
qty = Number(inventory_items[i][22]) - Number(inventory_items[i][26])
|
||||
uom = inventory_items[i][20]
|
||||
} else {
|
||||
qty = quantities[`${inventory_items[i][0]}@item`]['qty']
|
||||
uom = quantities[`${inventory_items[i][0]}@item`]['uom']
|
||||
@ -162,6 +159,26 @@
|
||||
};
|
||||
|
||||
// all custom items will pull quantities form the quantities dictionary.
|
||||
console.log(quantities)
|
||||
|
||||
for(let key in shoppingList[4]){
|
||||
var row = document.createElement('tr')
|
||||
|
||||
let name = key
|
||||
let tag = `${key}@custom`
|
||||
qty = quantities[tag]['qty']
|
||||
uom = quantities[tag]['uom']
|
||||
|
||||
let name_cell = document.createElement('td')
|
||||
name_cell.innerHTML = `<a href=${shoppingList[4][key][3].main} target="_blank">${shoppingList[4][key][2]}</a>`
|
||||
let qty_uom_cell = document.createElement('td')
|
||||
qty_uom_cell.innerHTML = `${qty} ${uom}`
|
||||
|
||||
row.appendChild(name_cell)
|
||||
row.appendChild(qty_uom_cell)
|
||||
|
||||
tbl_body.appendChild(row)
|
||||
}
|
||||
|
||||
references_table.appendChild(tbl_header)
|
||||
references_table.appendChild(tbl_body)
|
||||
|
||||
27
templates/signup.html
Normal file
27
templates/signup.html
Normal file
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
|
||||
<title>My Pantry</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<form class="row" style="gap: 1em;" action="/signup" method="post" enctype="multipart/form-data">
|
||||
<div class="s12 m6 input-field">
|
||||
<input id="username" name="username" type="text" class="validate" placeholder=" ">
|
||||
<label for="username">Username</label>
|
||||
<span class="supporting-text" data-error="wrong" data-success="right">Supporting text for additional information</span>
|
||||
</div>
|
||||
<div class="s12 m6 input-field">
|
||||
<input id="password" type="password" name="password" class="validate" placeholder=" " maxlength="20">
|
||||
<label for="password">Password</label>
|
||||
</div>
|
||||
<button class="btn icon-right waves-effect waves-light" type="submit" name="action">
|
||||
Submit<i class="material-icons right">send</i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
115
user_api.py
Normal file
115
user_api.py
Normal file
@ -0,0 +1,115 @@
|
||||
from flask import Blueprint, request, render_template, redirect, session, url_for
|
||||
import hashlib, psycopg2
|
||||
from config import config, sites_config, setFirstSetupDone
|
||||
from functools import wraps
|
||||
from manage import create
|
||||
from main import create_site, getUser, setSystemAdmin
|
||||
|
||||
login_app = Blueprint('login', __name__)
|
||||
|
||||
def login_required(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
if 'user' not in session or session['user'] == None:
|
||||
return redirect(url_for('login.login'))
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
@login_app.route('/setup', methods=['GET', 'POST'])
|
||||
def first_time_setup():
|
||||
if request.method == "POST":
|
||||
database_address = request.form['database_address']
|
||||
database_port = request.form['database_port']
|
||||
database_name = request.form['database_name']
|
||||
database_user = request.form['database_user']
|
||||
database_password = request.form['database_address']
|
||||
|
||||
username = request.form['username']
|
||||
email = request.form['email']
|
||||
password = hashlib.sha256(request.form['password'].encode()).hexdigest()
|
||||
|
||||
site_name = request.form['site_name']
|
||||
site_description = request.form['site_description']
|
||||
site_default_zone = request.form['site_default_zone']
|
||||
site_default_location = request.form['site_default_location']
|
||||
|
||||
print(email, site_description)
|
||||
|
||||
create(site_name, username, site_default_zone, site_default_location, email=email)
|
||||
create_site(site_name, (username, password, email), site_default_zone, site_default_location, site_default_location, site_description)
|
||||
setFirstSetupDone()
|
||||
user = getUser(username, password)
|
||||
setSystemAdmin(user_id=user[0])
|
||||
|
||||
|
||||
return redirect("/login")
|
||||
|
||||
return render_template("setup.html")
|
||||
|
||||
|
||||
|
||||
@login_app.route('/logout', methods=['GET'])
|
||||
def logout():
|
||||
if 'user' in session.keys():
|
||||
session['user'] = None
|
||||
return redirect('/login')
|
||||
|
||||
@login_app.route('/login', methods=['POST', 'GET'])
|
||||
def login():
|
||||
session.clear()
|
||||
instance_config = sites_config()
|
||||
print(instance_config["first_setup"])
|
||||
|
||||
if instance_config['first_setup']:
|
||||
return redirect('/setup')
|
||||
|
||||
if request.method == "POST":
|
||||
username = request.form['username']
|
||||
password = hashlib.sha256(request.form['password'].encode()).hexdigest()
|
||||
database_config = config()
|
||||
with psycopg2.connect(**database_config) as conn:
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
sql = f"SELECT * FROM logins WHERE username=%s;"
|
||||
cur.execute(sql, (username,))
|
||||
user = cur.fetchone()
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
|
||||
if user and user[2] == password:
|
||||
session['user_id'] = user[0]
|
||||
session['user'] = user
|
||||
return redirect('/')
|
||||
|
||||
if 'user' not in session.keys():
|
||||
session['user'] = None
|
||||
|
||||
return render_template("login.html")
|
||||
|
||||
@login_app.route('/signup', methods=['POST', 'GET'])
|
||||
def signup():
|
||||
|
||||
instance_config = sites_config()
|
||||
print(instance_config["signup_enabled"])
|
||||
|
||||
if not instance_config['signup_enabled']:
|
||||
return redirect('/login')
|
||||
|
||||
if request.method == "POST":
|
||||
username = request.form['username']
|
||||
password = hashlib.sha256(request.form['password'].encode()).hexdigest()
|
||||
database_config = config()
|
||||
with psycopg2.connect(**database_config) as conn:
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
sql = f"INSERT INTO logins(username, password) VALUES(%s, %s);"
|
||||
cur.execute(sql, (username, password))
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
|
||||
return redirect("/login")
|
||||
|
||||
return render_template("signup.html")
|
||||
116
webserver.py
116
webserver.py
@ -1,84 +1,134 @@
|
||||
from flask import Flask, render_template, session, request
|
||||
import api, config, external_devices
|
||||
from flask import Flask, render_template, session, request, redirect
|
||||
import api, config, external_devices, user_api, psycopg2, main, admin
|
||||
from user_api import login_required
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = '11gs22h2h1a4h6ah8e413a45'
|
||||
app.register_blueprint(api.database_api)
|
||||
app.register_blueprint(external_devices.external_api)
|
||||
app.register_blueprint(user_api.login_app)
|
||||
app.register_blueprint(admin.admin)
|
||||
|
||||
|
||||
@app.context_processor
|
||||
def inject_user():
|
||||
if 'user_id' in session.keys() and session['user_id'] is not None:
|
||||
database_config = config.config()
|
||||
with psycopg2.connect(**database_config) as conn:
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
sql = f"SELECT * FROM logins WHERE id=%s;"
|
||||
cur.execute(sql, (session['user_id'],))
|
||||
user = cur.fetchone()
|
||||
session['user'] = user
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return dict(username="")
|
||||
|
||||
return dict(
|
||||
user_id=session.get('user')[0],
|
||||
username=session.get('user')[1],
|
||||
system_admin=session.get('user')[15]
|
||||
)
|
||||
|
||||
return dict(username="")
|
||||
|
||||
|
||||
@app.route("/group/<id>")
|
||||
@login_required
|
||||
def group(id):
|
||||
sites = config.sites_config()
|
||||
return render_template("groups/group.html", id=id, current_site=session['selected_site'], sites=sites['sites'])
|
||||
sites = [site[1] for site in main.get_sites(session['user'][13])]
|
||||
return render_template("groups/group.html", id=id, current_site=session['selected_site'], sites=sites)
|
||||
|
||||
@app.route("/transactions/<id>")
|
||||
@login_required
|
||||
def transactions(id):
|
||||
sites = config.sites_config()
|
||||
return render_template("items/transactions.html", id=id, current_site=session['selected_site'], sites=sites['sites'])
|
||||
sites = [site[1] for site in main.get_sites(session['user'][13])]
|
||||
return render_template("items/transactions.html", id=id, current_site=session['selected_site'], sites=sites)
|
||||
|
||||
|
||||
@app.route("/item/<id>")
|
||||
@login_required
|
||||
def item(id):
|
||||
sites = config.sites_config()
|
||||
return render_template("items/item.html", id=id, current_site=session['selected_site'], sites=sites['sites'])
|
||||
sites = [site[1] for site in main.get_sites(session['user'][13])]
|
||||
return render_template("items/item.html", id=id, current_site=session['selected_site'], sites=sites)
|
||||
|
||||
@app.route("/itemlink/<id>")
|
||||
@login_required
|
||||
def itemLink(id):
|
||||
sites = config.sites_config()
|
||||
return render_template("items/itemlink.html", current_site=session['selected_site'], sites=sites['sites'], proto={'referrer': request.referrer}, id=id)
|
||||
sites = [site[1] for site in main.get_sites(session['user'][13])]
|
||||
return render_template("items/itemlink.html", current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer}, id=id)
|
||||
|
||||
@app.route("/transaction")
|
||||
@login_required
|
||||
def transaction():
|
||||
print(request.referrer)
|
||||
sites = config.sites_config()
|
||||
return render_template("transaction.html", current_site=session['selected_site'], sites=sites['sites'], proto={'referrer': request.referrer})
|
||||
sites = [site[1] for site in main.get_sites(session['user'][13])]
|
||||
return render_template("transaction.html", current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer})
|
||||
|
||||
@app.route("/workshop")
|
||||
@app.route("/admin")
|
||||
@login_required
|
||||
def workshop():
|
||||
sites = config.sites_config()
|
||||
return render_template("workshop.html", current_site=session['selected_site'], sites=sites['sites'])
|
||||
sites = [site[1] for site in main.get_sites(session['user'][13])]
|
||||
if not session.get('user')[15]:
|
||||
return redirect('/logout')
|
||||
return render_template("admin.html", current_site=session['selected_site'], sites=sites)
|
||||
|
||||
@app.route("/shopping-list/view/<id>")
|
||||
@login_required
|
||||
def shopping_lists_view(id):
|
||||
sites = config.sites_config()
|
||||
return render_template("shopping-lists/view.html", id=id, current_site=session['selected_site'], sites=sites['sites'])
|
||||
sites = [site[1] for site in main.get_sites(session['user'][13])]
|
||||
return render_template("shopping-lists/view.html", id=id, current_site=session['selected_site'], sites=sites)
|
||||
|
||||
@app.route("/shopping-list/edit/<id>")
|
||||
@login_required
|
||||
def shopping_lists_edit(id):
|
||||
sites = config.sites_config()
|
||||
return render_template("shopping-lists/edit.html", id=id, current_site=session['selected_site'], sites=sites['sites'])
|
||||
sites = [site[1] for site in main.get_sites(session['user'][13])]
|
||||
return render_template("shopping-lists/edit.html", id=id, current_site=session['selected_site'], sites=sites)
|
||||
|
||||
@app.route("/shopping-lists")
|
||||
@login_required
|
||||
def shopping_lists():
|
||||
sites = config.sites_config()
|
||||
return render_template("shopping-lists/index.html", current_site=session['selected_site'], sites=sites['sites'])
|
||||
sites = [site[1] for site in main.get_sites(session['user'][13])]
|
||||
return render_template("shopping-lists/index.html", current_site=session['selected_site'], sites=sites)
|
||||
|
||||
@app.route("/receipt/<id>")
|
||||
@login_required
|
||||
def receipt(id):
|
||||
sites = config.sites_config()
|
||||
return render_template("receipts/receipt.html", id=id, current_site=session['selected_site'], sites=sites['sites'])
|
||||
sites = [site[1] for site in main.get_sites(session['user'][13])]
|
||||
return render_template("receipts/receipt.html", id=id, current_site=session['selected_site'], sites=sites)
|
||||
|
||||
|
||||
@app.route("/receipts")
|
||||
@login_required
|
||||
def receipts():
|
||||
sites = config.sites_config()
|
||||
return render_template("receipts/index.html", current_site=session['selected_site'], sites=sites['sites'])
|
||||
sites = [site[1] for site in main.get_sites(session['user'][13])]
|
||||
return render_template("receipts/index.html", current_site=session['selected_site'], sites=sites)
|
||||
|
||||
|
||||
@app.route("/groups")
|
||||
@login_required
|
||||
def groups():
|
||||
sites = config.sites_config()
|
||||
return render_template("groups/index.html", current_site=session['selected_site'], sites=sites['sites'])
|
||||
sites = [site[1] for site in main.get_sites(session['user'][13])]
|
||||
return render_template("groups/index.html",
|
||||
current_site=session['selected_site'],
|
||||
sites=sites)
|
||||
|
||||
@app.route("/items")
|
||||
@login_required
|
||||
def items():
|
||||
sites = config.sites_config()
|
||||
return render_template("items/index.html", current_site=session['selected_site'], sites=sites['sites'])
|
||||
sites = [site[1] for site in main.get_sites(session['user'][13])]
|
||||
return render_template("items/index.html",
|
||||
current_site=session['selected_site'],
|
||||
sites=sites)
|
||||
|
||||
@app.route("/")
|
||||
@login_required
|
||||
def home():
|
||||
session['selected_site'] = 'main'
|
||||
sites = config.sites_config()
|
||||
return render_template("items/index.html", current_site=session['selected_site'], sites=sites['sites'])
|
||||
print(session['user'][12])
|
||||
sites = [site[1] for site in main.get_sites(session['user'][13])]
|
||||
session['selected_site'] = sites[0]
|
||||
return redirect("/items")
|
||||
return render_template("items/index.html", current_site=session['selected_site'], sites=sites)
|
||||
|
||||
app.run(host="0.0.0.0", port=5810, debug=True)
|
||||
Loading…
x
Reference in New Issue
Block a user