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:
Michael Rodin 2023-10-19 18:24:36 +02:00
parent c3bd73a068
commit f0b8b1eb3d
8 changed files with 196 additions and 15 deletions

2
.gitignore vendored
View file

@ -7,4 +7,6 @@
!/README.md
!/requirements.txt
/flask/static/covers/**
!/flask/static/covers/default.webp
**__pycache__**

View file

@ -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:

View file

@ -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):

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

8
flask/static/labels.css Normal file
View 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
View 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%;
}

View 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
View 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 %}