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
|
||||
!/requirements.txt
|
||||
|
||||
/flask/static/covers/**
|
||||
!/flask/static/covers/default.webp
|
||||
**__pycache__**
|
36
flask/app.py
36
flask/app.py
|
@ -24,6 +24,7 @@ def convsize(s):
|
|||
s=s/1000
|
||||
return str("%.2f" % s)+sizes[n]
|
||||
|
||||
|
||||
## WEB FRONTEND
|
||||
@app.route('/')
|
||||
def homepage():
|
||||
|
@ -92,6 +93,39 @@ def loginpage():
|
|||
else:
|
||||
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')
|
||||
def searchpage():
|
||||
# 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)
|
||||
## OUTPUT: […,(ID:int,NAME:str),…]
|
||||
def get_category_selection(include_parents:bool=True):
|
||||
catlist=db.get_all_categories()
|
||||
catlist=db.get_categories()
|
||||
htmlcatlist=[]
|
||||
# parse all categories and sort them into list
|
||||
for cat in catlist:
|
||||
|
|
|
@ -111,7 +111,7 @@ class db:
|
|||
archive["hash"]=archive["hash"].upper()
|
||||
if not re.match('[A-Z0-9]{40}', archive["hash"]):
|
||||
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\. _-]'"
|
||||
print(archive["name"])
|
||||
|
||||
|
@ -126,28 +126,93 @@ class db:
|
|||
|
||||
|
||||
## 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),
|
||||
## labels:array=[…,(LABEL:str,CATEGORY:str,CATDESC:str,LABTYPE:str,LABDESC:str),…]
|
||||
def get_archive_info(self, hash:str):
|
||||
self.cur.execute(f"""SELECT Archs.NAME,Archs.HASH,Archs.IMPORTED,Cats.CATEGORY,Cats.DESCRIPTION,Users.UNAME,Users.DNAME FROM Archs
|
||||
## 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),
|
||||
## category:tuple=(ID:int,CATEGORY:str,DESCRIPTION:str,PID:int,PCAT:str,PDESC:str)
|
||||
## labels:array=[…,(LABEL:str,LABTYPE:int,LABDESC:str),…]
|
||||
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 Users ON Users.ID=Archs.OWNER
|
||||
WHERE hash='{hash}'""")
|
||||
WHERE Archs.ID='{archid}'""")
|
||||
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 Labs ON Labs.ID=ArchLab.LABID
|
||||
JOIN Cats ON Labs.CATEGORY=Cats.ID
|
||||
JOIN LabType ON Labs.TYPE=LabType.ID
|
||||
WHERE ARCHID=1;""")
|
||||
WHERE ARCHID={archid};""")
|
||||
labels=self.cur.fetchall()
|
||||
return archive, labels
|
||||
return archive, category, labels
|
||||
|
||||
## Returns all categories.
|
||||
## 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;")
|
||||
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
|
||||
## OUTPUT: archives:array=[…,(ID:int,NAME:str,SIZE:str,IMPORTED[UNIX]:int),…]
|
||||
|
@ -167,11 +232,10 @@ class db:
|
|||
for w in keywords:
|
||||
keyword_string+=f"AND NAME LIKE '%{w}%' "
|
||||
if len(keywords) == 1:
|
||||
for w in keywords:
|
||||
keyword_string+=f"OR HASH = '{w}' "
|
||||
keyword_string+=f"OR HASH = '{keywords[0]}' "
|
||||
|
||||
# Get all children of category (if exist) and put into query string
|
||||
categories=self.get_all_categories()
|
||||
categories=self.get_categories()
|
||||
catlist=[str(category)]
|
||||
for i in categories:
|
||||
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