Added archive view, label editing
Archives can now be viewed, a cover can optionally be inserted into the covers directory. Labels can now be edited.
This commit is contained in:
parent
c3bd73a068
commit
f0b8b1eb3d
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -7,4 +7,6 @@
|
||||||
!/README.md
|
!/README.md
|
||||||
!/requirements.txt
|
!/requirements.txt
|
||||||
|
|
||||||
|
/flask/static/covers/**
|
||||||
|
!/flask/static/covers/default.webp
|
||||||
**__pycache__**
|
**__pycache__**
|
36
flask/app.py
36
flask/app.py
|
@ -24,6 +24,7 @@ def convsize(s):
|
||||||
s=s/1000
|
s=s/1000
|
||||||
return str("%.2f" % s)+sizes[n]
|
return str("%.2f" % s)+sizes[n]
|
||||||
|
|
||||||
|
|
||||||
## WEB FRONTEND
|
## WEB FRONTEND
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def homepage():
|
def homepage():
|
||||||
|
@ -92,6 +93,39 @@ def loginpage():
|
||||||
else:
|
else:
|
||||||
return render_template("login.html", title="Login")
|
return render_template("login.html", title="Login")
|
||||||
|
|
||||||
|
@app.route('/view/<int:archid>')
|
||||||
|
def viewpage(archid:int):
|
||||||
|
logged_in,userdata=get_login_info(request.cookies.get('session'))
|
||||||
|
archive,category,labels=db.get_archive_info(archid)
|
||||||
|
return render_template("view.html", title="View Archive",userdata=userdata,login=logged_in,archive=archive,category=category,labels=labels)
|
||||||
|
|
||||||
|
@app.route('/labels/<int:archid>', methods=["GET","POST"])
|
||||||
|
def labeleditpage(archid:int):
|
||||||
|
logged_in,userdata=get_login_info(request.cookies.get('session'))
|
||||||
|
archive,category,labels=db.get_archive_info(archid)
|
||||||
|
label_dict=db.get_label_labeltypes(category[3])
|
||||||
|
if not logged_in or userdata[0] != archive[8]:
|
||||||
|
return make_response(redirect(f"/view/{archid}"))
|
||||||
|
|
||||||
|
# POST: parse everything and update labels
|
||||||
|
if request.method == 'POST':
|
||||||
|
on_labels=[]
|
||||||
|
print(request.form)
|
||||||
|
for i in request.form:
|
||||||
|
on_labels.append(i)
|
||||||
|
res, data=db.update_labels(archid, on_labels)
|
||||||
|
if not res:
|
||||||
|
return f"<h2>ERROR: {data}</h2>Go back and try again"
|
||||||
|
return make_response(redirect(f"/view/{archid}"))
|
||||||
|
|
||||||
|
|
||||||
|
# GET: return normal labels page
|
||||||
|
labels_name_list=[]
|
||||||
|
for i in labels:
|
||||||
|
labels_name_list.append(i[0])
|
||||||
|
return render_template("labels.html", title="Edit Labels",userdata=userdata,login=logged_in,archive=archive,res_labels=label_dict,labels_names=labels_name_list)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/search')
|
@app.route('/search')
|
||||||
def searchpage():
|
def searchpage():
|
||||||
# try to get userdata, else logout state
|
# try to get userdata, else logout state
|
||||||
|
@ -144,7 +178,7 @@ def setcookie(name:str,value:str,lifetime:int=10000):
|
||||||
## Gets all categories and returns them (with or without parents)
|
## Gets all categories and returns them (with or without parents)
|
||||||
## OUTPUT: […,(ID:int,NAME:str),…]
|
## OUTPUT: […,(ID:int,NAME:str),…]
|
||||||
def get_category_selection(include_parents:bool=True):
|
def get_category_selection(include_parents:bool=True):
|
||||||
catlist=db.get_all_categories()
|
catlist=db.get_categories()
|
||||||
htmlcatlist=[]
|
htmlcatlist=[]
|
||||||
# parse all categories and sort them into list
|
# parse all categories and sort them into list
|
||||||
for cat in catlist:
|
for cat in catlist:
|
||||||
|
|
|
@ -111,7 +111,7 @@ class db:
|
||||||
archive["hash"]=archive["hash"].upper()
|
archive["hash"]=archive["hash"].upper()
|
||||||
if not re.match('[A-Z0-9]{40}', archive["hash"]):
|
if not re.match('[A-Z0-9]{40}', archive["hash"]):
|
||||||
return False, "Hash needs to be 40 characters in hexadecimal (SHA-1)."
|
return False, "Hash needs to be 40 characters in hexadecimal (SHA-1)."
|
||||||
if re.match('.*[^A-Za-z0-9\. _-].*', archive["name"]):
|
if re.match('.*[^A-Za-z0-9\. +_-].*', archive["name"]):
|
||||||
return False, "The name contains illegal characters. Allowed chars: '[A-Za-z0-9\. _-]'"
|
return False, "The name contains illegal characters. Allowed chars: '[A-Za-z0-9\. _-]'"
|
||||||
print(archive["name"])
|
print(archive["name"])
|
||||||
|
|
||||||
|
@ -126,29 +126,94 @@ class db:
|
||||||
|
|
||||||
|
|
||||||
## Returns all relevant information about one (1) archive
|
## Returns all relevant information about one (1) archive
|
||||||
## OUTPUT: archive:tuple=(NAME:str,HASH:str,IMPORTED[UNIX]:int,CATEGORY,str,CATEGORY.DESCRIPTION:str,UNAME:str,DNAME:str),
|
## OUTPUT: archive:tuple=(ID:int,NAME:str,HASH:str,SIZE:int,IMPORTED[UNIX]:int,CATEGORY.ID:int,CATEGORY,str,CATEGORY.DESCRIPTION:str,USER.ID:int,DNAME:str),
|
||||||
## labels:array=[…,(LABEL:str,CATEGORY:str,CATDESC:str,LABTYPE:str,LABDESC:str),…]
|
## category:tuple=(ID:int,CATEGORY:str,DESCRIPTION:str,PID:int,PCAT:str,PDESC:str)
|
||||||
def get_archive_info(self, hash:str):
|
## labels:array=[…,(LABEL:str,LABTYPE:int,LABDESC:str),…]
|
||||||
self.cur.execute(f"""SELECT Archs.NAME,Archs.HASH,Archs.IMPORTED,Cats.CATEGORY,Cats.DESCRIPTION,Users.UNAME,Users.DNAME FROM Archs
|
def get_archive_info(self, archid:int):
|
||||||
|
# get info about archive itself
|
||||||
|
self.cur.execute(f"""SELECT Archs.ID,Archs.NAME,Archs.HASH,Archs.SIZE,Archs.IMPORTED,Cats.ID,Cats.CATEGORY,Cats.DESCRIPTION,Users.ID,Users.DNAME FROM Archs
|
||||||
JOIN Cats ON Cats.ID=Archs.CATEGORY
|
JOIN Cats ON Cats.ID=Archs.CATEGORY
|
||||||
JOIN Users ON Users.ID=Archs.OWNER
|
JOIN Users ON Users.ID=Archs.OWNER
|
||||||
WHERE hash='{hash}'""")
|
WHERE Archs.ID='{archid}'""")
|
||||||
archive=self.cur.fetchone()
|
archive=self.cur.fetchone()
|
||||||
self.cur.execute(f"""SELECT Labs.LABEL,Cats.CATEGORY,Cats.DESCRIPTION AS CATDESC,LabType.NAME AS LABTYPE,LabType.DESCRIPTION AS LABDESC FROM ArchLab
|
# get info about category and it's parent
|
||||||
|
self.cur.execute(f"""SELECT c.ID,c.CATEGORY,c.DESCRIPTION,p.ID AS PID,p.CATEGORY as PCAT,p.DESCRIPTION AS PDESC FROM Cats c, Cats p
|
||||||
|
WHERE c.ID={archive[5]} AND c.PARENT=p.ID""")
|
||||||
|
category=self.cur.fetchone()
|
||||||
|
# get info about labels of archive
|
||||||
|
self.cur.execute(f"""SELECT Labs.LABEL,LabType.ID AS LABTYPE,LabType.DESCRIPTION AS LABDESC FROM ArchLab
|
||||||
JOIN Archs ON Archs.ID=ArchLab.ARCHID
|
JOIN Archs ON Archs.ID=ArchLab.ARCHID
|
||||||
JOIN Labs ON Labs.ID=ArchLab.LABID
|
JOIN Labs ON Labs.ID=ArchLab.LABID
|
||||||
JOIN Cats ON Labs.CATEGORY=Cats.ID
|
|
||||||
JOIN LabType ON Labs.TYPE=LabType.ID
|
JOIN LabType ON Labs.TYPE=LabType.ID
|
||||||
WHERE ARCHID=1;""")
|
WHERE ARCHID={archid};""")
|
||||||
labels=self.cur.fetchall()
|
labels=self.cur.fetchall()
|
||||||
return archive, labels
|
return archive, category, labels
|
||||||
|
|
||||||
## Returns all categories.
|
## Returns all categories.
|
||||||
## OUTPUT: array=[…,(ID:int,CATEGORY:str,PARENT:int,DESCRIPTION:str),…]
|
## OUTPUT: array=[…,(ID:int,CATEGORY:str,PARENT:int,DESCRIPTION:str),…]
|
||||||
def get_all_categories(self):
|
def get_categories(self):
|
||||||
self.cur.execute("SELECT * FROM Cats;")
|
self.cur.execute("SELECT * FROM Cats;")
|
||||||
return self.cur.fetchall()
|
return self.cur.fetchall()
|
||||||
|
|
||||||
|
## get all labeltypes and their respective labels based on a category parent
|
||||||
|
## OUTPUT: res_dict:dict={…,LabType.NAME:[…,(ID:int,NAME:str),…],…}
|
||||||
|
def get_label_labeltypes(self, catparentid:int):
|
||||||
|
# gets all relevant labtypes: […,(ID.int,NAME:str),…]
|
||||||
|
self.cur.execute(f"""SELECT LabType.ID,LabType.NAME FROM CatLabType
|
||||||
|
JOIN Cats ON Cats.ID=CatLabType.CATID
|
||||||
|
JOIN LabType ON LabType.ID=CatLabType.LABID
|
||||||
|
WHERE Cats.ID={catparentid}
|
||||||
|
ORDER BY LabType.NAME ASC""")
|
||||||
|
labtypes_list=self.cur.fetchall()
|
||||||
|
labtypes_ids,labtypes_names=[],[]
|
||||||
|
for w,e in labtypes_list:
|
||||||
|
labtypes_ids.append(str(w))
|
||||||
|
labtypes_names.append(e)
|
||||||
|
ltid_string="(" + ",".join(labtypes_ids) + ")"
|
||||||
|
# gets all relevant labs: […,(ID:int,NAME:str,LTNAME:str),…]
|
||||||
|
self.cur.execute(f"""SELECT Labs.ID,Labs.LABEL,LabType.NAME AS LTNAME FROM Labs
|
||||||
|
JOIN LabType ON Labs.TYPE=LabType.ID
|
||||||
|
WHERE LabType.ID IN {ltid_string}
|
||||||
|
ORDER BY Labs.LABEL ASC""")
|
||||||
|
labs_list=self.cur.fetchall()
|
||||||
|
res_dict={}
|
||||||
|
# creates all labtype entries in dict
|
||||||
|
for i in labtypes_names:
|
||||||
|
res_dict[i]=[]
|
||||||
|
# puts all labs into their respective labtype
|
||||||
|
for entry in labs_list:
|
||||||
|
res_dict[entry[2]].append(entry[:2])
|
||||||
|
return res_dict
|
||||||
|
|
||||||
|
## get a list of enabled labels and update the DB to reflect that state
|
||||||
|
## OUTPUT: (if on_labels empty) bool=False, str
|
||||||
|
## (else)
|
||||||
|
def update_labels(self, archid:int, on_labels:list): # TODO: CLEAN!!!!
|
||||||
|
# fail if no labels passed
|
||||||
|
if len(on_labels) == 0:
|
||||||
|
return False, "You have to select at least one label!"
|
||||||
|
|
||||||
|
# get all relevant labels
|
||||||
|
self.cur.execute(f"""SELECT ArchLab.LABID FROM ArchLab
|
||||||
|
WHERE ArchLab.ARCHID={archid}""")
|
||||||
|
existing_labs=[]
|
||||||
|
for i in self.cur.fetchall():
|
||||||
|
existing_labs.append(i[0])
|
||||||
|
to_add=[]
|
||||||
|
# get all missing labels to add
|
||||||
|
for lab in on_labels:
|
||||||
|
if int(lab) not in existing_labs:
|
||||||
|
to_add.append(lab)
|
||||||
|
|
||||||
|
# remove all labels which are not on
|
||||||
|
self.cur.execute(f"""DELETE FROM ArchLab WHERE ARCHID={archid} AND LABID NOT IN ({",".join(on_labels)})""")
|
||||||
|
to_add_list=[]
|
||||||
|
for i in to_add:
|
||||||
|
to_add_list.append("(" + str(archid) + "," + str(i) + ")")
|
||||||
|
# add all new labels
|
||||||
|
self.cur.execute(f"""INSERT INTO ArchLab(ARCHID,LABID) VALUES{",".join(to_add_list)}""")
|
||||||
|
return True, ""
|
||||||
|
|
||||||
## Returns n archives, sorted by (imported )time or size
|
## Returns n archives, sorted by (imported )time or size
|
||||||
## OUTPUT: archives:array=[…,(ID:int,NAME:str,SIZE:str,IMPORTED[UNIX]:int),…]
|
## OUTPUT: archives:array=[…,(ID:int,NAME:str,SIZE:str,IMPORTED[UNIX]:int),…]
|
||||||
def get_n_archives(self, sorttype:str="time",category:int=0, keywords:list=[], count:int=20):
|
def get_n_archives(self, sorttype:str="time",category:int=0, keywords:list=[], count:int=20):
|
||||||
|
@ -167,11 +232,10 @@ class db:
|
||||||
for w in keywords:
|
for w in keywords:
|
||||||
keyword_string+=f"AND NAME LIKE '%{w}%' "
|
keyword_string+=f"AND NAME LIKE '%{w}%' "
|
||||||
if len(keywords) == 1:
|
if len(keywords) == 1:
|
||||||
for w in keywords:
|
keyword_string+=f"OR HASH = '{keywords[0]}' "
|
||||||
keyword_string+=f"OR HASH = '{w}' "
|
|
||||||
|
|
||||||
# Get all children of category (if exist) and put into query string
|
# Get all children of category (if exist) and put into query string
|
||||||
categories=self.get_all_categories()
|
categories=self.get_categories()
|
||||||
catlist=[str(category)]
|
catlist=[str(category)]
|
||||||
for i in categories:
|
for i in categories:
|
||||||
if i[2] == int(category):
|
if i[2] == int(category):
|
||||||
|
|
BIN
flask/static/covers/default.webp
Normal file
BIN
flask/static/covers/default.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
8
flask/static/labels.css
Normal file
8
flask/static/labels.css
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
div.flex-container {
|
||||||
|
display: flex;
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.flex-item {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
27
flask/static/view.css
Normal file
27
flask/static/view.css
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
div.grid-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.archive-info {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content auto;
|
||||||
|
grid-template-rows: max-content max-content max-content max-content max-content max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.archive-info > * {
|
||||||
|
padding: 0.3em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.1em;
|
||||||
|
border-radius: 0.4em;
|
||||||
|
background: linear-gradient(160deg, white, pink 65%);
|
||||||
|
}
|
||||||
|
|
||||||
|
img#cover {
|
||||||
|
margin: auto auto;
|
||||||
|
border-radius: 1em;
|
||||||
|
width: 90%;
|
||||||
|
}
|
21
flask/templates/labels.html
Normal file
21
flask/templates/labels.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block meta %}
|
||||||
|
<link rel="stylesheet" href="/static/view.css" />
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>{{archive[1]}}</h2>
|
||||||
|
<form action="/labels/{{archive[0]}}" method="post">
|
||||||
|
<div class="flex-container">
|
||||||
|
{% for ltype in res_labels %}
|
||||||
|
<div class="flex-item"><b>{{ltype}}</b>
|
||||||
|
{% for for_temp in res_labels[ltype] %}
|
||||||
|
<input type="checkbox" id="{{for_temp[0]}}" name="{{for_temp[0]}}" {% if for_temp[1] in labels_names %}checked{% endif %}><label for="{{for_temp[0]}}">{{for_temp[1]}}</label>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
25
flask/templates/view.html
Normal file
25
flask/templates/view.html
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block meta %}
|
||||||
|
<link rel="stylesheet" href="/static/view.css" />
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>{{archive[1]}}</h2>
|
||||||
|
<div class="grid-container">
|
||||||
|
<div class="archive-info">
|
||||||
|
<b>Hash: </b><span>{{archive[2]}}</span>
|
||||||
|
<b>Owner: </b><a href="/user/{{archive[-2]}}">{{archive[-1]}}</a>
|
||||||
|
<b>Category: </b><span>{{category[4]}}/{{category[1]}}</span>
|
||||||
|
<b>Imported: </b><span>{{archive[4]|ctime}}</span>
|
||||||
|
<b>Size: </b><span>{{archive[4]|spacer}}</span>
|
||||||
|
<b>Labels: {% if login and userdata[0] == archive[8] %}<a href="/labels/{{archive[0]}}">Edit</a>{% endif %}</b>
|
||||||
|
<div>
|
||||||
|
{% for lab in labels %}
|
||||||
|
<div class="label">{{lab[0]}}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<img id="cover" src="/static/covers/{{archive[0]}}.webp" onerror="this.onerror=null; this.src='/static/covers/default.webp'" alt="Cover image">
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
Loading…
Reference in a new issue