Compare commits
21 Commits
a1c41349bd
...
master
Author | SHA1 | Date | |
---|---|---|---|
cda12028c1
|
|||
7ea0a7ddb7
|
|||
a2ec1aeebd
|
|||
004b1a1c37 | |||
48e98cbfc3
|
|||
e632ca23a5 | |||
|
6739e997bc | ||
33cd9c202e | |||
efcab93460 | |||
b4344d31a8 | |||
2d4b88bc7a | |||
2721d2a524 | |||
13d1e1674d
|
|||
44b7228982
|
|||
673c04af19
|
|||
4911842d3f
|
|||
d71cde1171
|
|||
e615f774ad
|
|||
ed38156e77
|
|||
4b82097fd9
|
|||
d0d9529e41
|
19
Makefile
Normal file
19
Makefile
Normal file
@@ -0,0 +1,19 @@
|
||||
VENV = venv
|
||||
PYTHON = $(VENV)/bin/python3
|
||||
PIP = $(VENV)/bin/pip
|
||||
|
||||
run: $(VENV)/bin/activate
|
||||
$(PYTHON) init_db.py
|
||||
$(PYTHON) run.py
|
||||
|
||||
|
||||
$(VENV)/bin/activate: requirements.txt
|
||||
python3 -m venv $(VENV)
|
||||
$(PIP) install -r requirements.txt
|
||||
|
||||
|
||||
clean:
|
||||
rm -rf __pycache__
|
||||
rm -rf $(VENV)
|
||||
rm -r taskmanager/__pycache__/
|
||||
rm instance/taskmanager.db
|
11
README.md
11
README.md
@@ -10,12 +10,21 @@ Install python and pip on local machine
|
||||
pip install virtualenv
|
||||
python -m venv venv #/path/to/new/virtual/environment
|
||||
source venv/bin/activate #activate virtual env
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirments.txt
|
||||
python3 ./init_db.py #initialize database
|
||||
|
||||
python3 ./run.py #run project
|
||||
```
|
||||
|
||||
# On database changes
|
||||
|
||||
Delete file `/instance/taskmanager.db`
|
||||
And reinit the db
|
||||
|
||||
```shell
|
||||
python3 ./init_db.py
|
||||
```
|
||||
|
||||
# Build app
|
||||
|
||||
```bash
|
||||
|
@@ -7,5 +7,5 @@ Installed-Size: 2000
|
||||
Depends: gunicorn, python3-flask-sqlalchemy
|
||||
Homepage: https://gitea.dmz.rs/Decentrala/taskmanager
|
||||
Maintainer: Decentrala <dmz@dmz.rs>
|
||||
Description: Web app
|
||||
Version: 1.0.2
|
||||
Description: Interactive TODO list Web app
|
||||
Version: 1.0.10
|
||||
|
@@ -2,3 +2,11 @@
|
||||
/usr/bin/systemctl enable taskmanager.service
|
||||
/var/taskmanager/init_db.py
|
||||
/sbin/service taskmanager start
|
||||
if [ -f /tmp/oldtaskmanagerconfig.ini ] ; then
|
||||
cp /tmp/oldtaskmanagerconfig.ini /var/taskmanager/taskmanager/config.ini
|
||||
rm /tmp/oldtaskmanagerconfig.ini
|
||||
fi
|
||||
if [ -f /tmp/oldtaskmanager.db ] ; then
|
||||
cp /tmp/oldtaskmanager.db /var/taskmanager/instance/taskmanager.db
|
||||
rm /tmp/oldtaskmanager.db
|
||||
fi
|
||||
|
6
build-deb/taskmanager/DEBIAN/preinst
Executable file
6
build-deb/taskmanager/DEBIAN/preinst
Executable file
@@ -0,0 +1,6 @@
|
||||
if [ -f /var/taskmanager/taskmanager/config.ini ] ; then
|
||||
cp /var/taskmanager/taskmanager/config.ini /tmp/oldtaskmanagerconfig.ini
|
||||
fi
|
||||
if [ -f /var/taskmanager/instance/taskmanager.db ] ; then
|
||||
cp /var/taskmanager/instance/taskmanager.db /tmp/oldtaskmanager.db
|
||||
fi
|
@@ -1,3 +1,9 @@
|
||||
#!/bin/sh
|
||||
/sbin/service taskmanager stop
|
||||
/usr/bin/systemctl disable taskmanager.service
|
||||
if [ -f /var/taskmanager/taskmanager/config.ini ] ; then
|
||||
cp /var/taskmanager/taskmanager/config.ini /tmp/oldtaskmanagerconfig.ini
|
||||
fi
|
||||
if [ -f /var/taskmanager/instance/taskmanager.db ] ; then
|
||||
cp /var/taskmanager/instance/taskmanager.db /tmp/oldtaskmanager.db
|
||||
fi
|
||||
|
2
taskmanager/config.ini
Normal file
2
taskmanager/config.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
[credentials]
|
||||
ADMINPASS = defaultpassword
|
@@ -4,6 +4,7 @@ class Task(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String, nullable=False)
|
||||
desc = db.Column(db.String, nullable=True)
|
||||
creatorid = db.Column(db.Integer, nullable=True)
|
||||
|
||||
class User(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
@@ -1,70 +1,184 @@
|
||||
import configparser
|
||||
import os
|
||||
|
||||
from flask import render_template, request, redirect
|
||||
|
||||
from taskmanager import app, db
|
||||
from taskmanager.functions import *
|
||||
from taskmanager.models import *
|
||||
|
||||
PROJECT_PATH = os.path.abspath(os.path.dirname(__file__))
|
||||
CONFIG_PATH = os.path.join(PROJECT_PATH, "config.ini")
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read(CONFIG_PATH)
|
||||
ADMINPASS = config.get('credentials', 'ADMINPASS')
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
def index():
|
||||
tasks = Task.query.all()
|
||||
return render_template('index.html', tasks = tasks)
|
||||
return render_template('pages/index.html', tasks = tasks)
|
||||
|
||||
@app.route('/addtask', methods=['GET','POST'])
|
||||
def addtask():
|
||||
if request.method == 'GET':
|
||||
return render_template('addtask.html')
|
||||
return render_template('pages/addtask.html')
|
||||
elif request.method == 'POST':
|
||||
taskname = request.form['taskname']
|
||||
taskdesc = request.form['taskdesc']
|
||||
sqladdtask = Task(name = taskname, desc = taskdesc)
|
||||
username = request.form['username']
|
||||
# Input sanitation
|
||||
# Task name
|
||||
if not taskname.isprintable() or ("<" in taskname and ">" in taskname):
|
||||
return render_template('pages/response.html', response = "Task name has to be made only of letters or numbers.")
|
||||
if len(taskname) < 1 or len(taskname) > 40:
|
||||
return render_template('pages/response.html', response = "Task name lenght invalid, only smaller then 40 charachters allowed")
|
||||
|
||||
# Username
|
||||
if username == "":
|
||||
creatorid = None
|
||||
else:
|
||||
try:
|
||||
creatorid = User.query.filter_by(username = username).first().id
|
||||
except:
|
||||
return render_template('pages/response.html', response = 'No user with this username. Please register')
|
||||
if creatorid is None:
|
||||
return render_template('pages/response.html', response = 'No user with this username. Please register.')
|
||||
|
||||
# Task descripton
|
||||
if taskdesc != '':
|
||||
if not taskdesc.isprintable() or ("<" in taskdesc and ">" in taskdesc):
|
||||
return render_template('pages/response.html', response = "Task description has to be made of printable characters.")
|
||||
if len(taskdesc) > 2000:
|
||||
return render_template('pages/response.html', response = "Task description lenght invalid, only smaller then 2000 charachters allowed")
|
||||
sqladdtask = Task(name = taskname, desc = taskdesc, creatorid = creatorid)
|
||||
try:
|
||||
db.session.add(sqladdtask)
|
||||
db.session.commit()
|
||||
return 'Task added'
|
||||
return render_template('pages/response.html', response = 'Task added')
|
||||
except:
|
||||
return 'Adding task failed'
|
||||
return render_template('pages/response.html', response = 'Adding task failed')
|
||||
|
||||
@app.route('/register', methods=['POST', 'GET'])
|
||||
def register():
|
||||
if request.method == 'GET':
|
||||
return render_template('register.html')
|
||||
return render_template('pages/register.html')
|
||||
elif request.method == 'POST':
|
||||
username = request.form['username']
|
||||
contact = request.form['contact']
|
||||
password = request.form['password']
|
||||
|
||||
# Username
|
||||
if not username.isalnum():
|
||||
return render_template('pages/response.html', response = "Username has to be made only of letters or numbers.")
|
||||
if len(username) < 1 or len(username) > 40:
|
||||
return render_template('pages/response.html', response = "Username lenght invalid, only smaller then 40 charachters allowed")
|
||||
|
||||
# Contact
|
||||
if contact != '':
|
||||
if not contact.isprintable() or ("<" in contact and ">" in contact):
|
||||
return render_template('pages/response.html', response = "Contact information has to be made of printable characters.")
|
||||
if len(contact) > 100:
|
||||
return render_template('pages/response.html', response = "Contact lenght invalid, only smaller then 100 charachters allowed")
|
||||
|
||||
# Password
|
||||
if password != '':
|
||||
if not password.isprintable():
|
||||
return render_template('pages/response.html', response = "Password has to be made of printable characters.")
|
||||
if len(password) > 500:
|
||||
return render_template('pages/response.html', response = "Password lenght invalid, only smaller then 500 charachters allowed")
|
||||
|
||||
sqladduser = User(username = username, contact = contact, password = password)
|
||||
try:
|
||||
db.session.add(sqladduser)
|
||||
db.session.commit()
|
||||
return 'User added'
|
||||
return render_template('pages/response.html', response = 'User added')
|
||||
except:
|
||||
return 'Adding user failed'
|
||||
return render_template('pages/response.html', response = 'Adding user failed')
|
||||
|
||||
else:
|
||||
return 'HTTP request method not recogniezed'
|
||||
return render_template('pages/response.html', response = 'HTTP request method not recogniezed')
|
||||
|
||||
|
||||
@app.route('/projects/<int:task_id>', methods=['GET','POST'])
|
||||
def project(task_id:int):
|
||||
try:
|
||||
task = Task.query.get(task_id)
|
||||
except:
|
||||
return render_template('pages/response.html', response = 'Task not found, bad URL')
|
||||
if task is None:
|
||||
return render_template('pages/response.html', response = 'Task not found, bad URL')
|
||||
users = gettaskusers(task_id)
|
||||
if request.method == 'GET':
|
||||
try:
|
||||
task = Task.query.get(task_id)
|
||||
except:
|
||||
return 'Task not found, bad URL'
|
||||
return render_template("project.html", task = task, users = users)
|
||||
return render_template("pages/project.html", task = task, users = users)
|
||||
elif request.method == 'POST':
|
||||
# Assigning user to task
|
||||
username = request.form['username']
|
||||
if username in users:
|
||||
return 'User already added to task'
|
||||
for user in users:
|
||||
if username == user.username:
|
||||
return render_template('pages/response.html', response = 'User already added to task')
|
||||
try:
|
||||
userid = User.query.filter_by(username = username).first().id
|
||||
except:
|
||||
return 'User not found, please <a href="/register">register</a>.'
|
||||
return render_template('pages/response.html', response = 'User not found, please register.')
|
||||
if userid is None:
|
||||
return render_template('pages/response.html', response = 'User not found, please register.')
|
||||
sqladduser = TaskUser(userid = userid, taskid = task_id)
|
||||
try:
|
||||
db.session.add(sqladduser)
|
||||
db.session.commit()
|
||||
return 'User added'
|
||||
return render_template('pages/response.html', response = 'User added')
|
||||
except:
|
||||
return 'Adding user failed'
|
||||
return render_template('pages/response.html', response = 'Adding user failed')
|
||||
|
||||
@app.route('/projects/<int:task_id>/del', methods=['GET','POST'])
|
||||
def deltask(task_id:int):
|
||||
try:
|
||||
task = Task.query.get(task_id)
|
||||
except:
|
||||
return render_template('pages/response.html', response = 'Task not found, bad URL')
|
||||
if task is None:
|
||||
return render_template('pages/response.html', response = 'Task not found, bad URL')
|
||||
try:
|
||||
taskusers = TaskUser.query.filter_by(taskid = task_id).all()
|
||||
except:
|
||||
taskusers = None
|
||||
creatorid = task.creatorid
|
||||
if request.method == 'GET':
|
||||
if creatorid is None:
|
||||
try:
|
||||
db.session.delete(task)
|
||||
db.session.commit()
|
||||
except:
|
||||
return render_template('pages/response.html', response = 'Deleting task failed')
|
||||
try:
|
||||
if taskusers != None:
|
||||
for taskuser in taskusers:
|
||||
db.session.delete(taskuser)
|
||||
db.session.commit()
|
||||
except:
|
||||
return render_template('pages/response.html', response = 'Deleting user assignment to task failed')
|
||||
return render_template('pages/response.html', response = 'Task deleted')
|
||||
else:
|
||||
return render_template('pages/deltask.html', task = task)
|
||||
if request.method == 'POST':
|
||||
password = request.form['password']
|
||||
if len(password) < 1 or len(password) > 500:
|
||||
return render_template('pages/response.html', response = "Password lenght invalid, only smaller then 500 charachters allowed")
|
||||
# Check password
|
||||
if password != ADMINPASS and password != User.query.get(creatorid).password:
|
||||
return render_template('pages/response.html', response = 'Wrong password')
|
||||
# Delete task
|
||||
try:
|
||||
db.session.delete(task)
|
||||
db.session.commit()
|
||||
except:
|
||||
return render_template('pages/response.html', response = 'Deleting task failed')
|
||||
try:
|
||||
if taskusers != None:
|
||||
for taskuser in taskusers:
|
||||
db.session.delete(taskuser)
|
||||
db.session.commit()
|
||||
except:
|
||||
return render_template('pages/response.html', response = 'Deleting user assignment to task failed')
|
||||
return render_template('pages/response.html', response = 'Task deleted')
|
||||
|
2
taskmanager/templates/includes/footer.html
Normal file
2
taskmanager/templates/includes/footer.html
Normal file
@@ -0,0 +1,2 @@
|
||||
<footer>
|
||||
</footer>
|
8
taskmanager/templates/includes/header.html
Normal file
8
taskmanager/templates/includes/header.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<header>
|
||||
<nav class="container">
|
||||
<ul>
|
||||
<li class="current"><a href="/">Home</a></li>
|
||||
<li><a href="/register">Register</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
@@ -1,12 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<title>Task manager</title>
|
||||
</head>
|
||||
<body>
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block content %}
|
||||
<header>
|
||||
<nav class="container">
|
||||
<ul>
|
||||
@@ -35,5 +28,4 @@
|
||||
</main>
|
||||
<footer>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock content %}
|
15
taskmanager/templates/layouts/base.html
Normal file
15
taskmanager/templates/layouts/base.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<title>Task manager</title>
|
||||
</head>
|
||||
<body>
|
||||
{% include "includes/header.html" %}
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
{% include "includes/footer.html" %}
|
||||
</body>
|
||||
</html>
|
@@ -1,20 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<title>Add new task</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="container">
|
||||
<ul>
|
||||
<li class="current"><a href="/">Home</a></li>
|
||||
<li><a href="/register">Register</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block content %}
|
||||
<main class="container page page-addtask">
|
||||
<h1>Create new task</h1>
|
||||
<div class="form-wrap">
|
||||
@@ -25,7 +10,11 @@
|
||||
</div>
|
||||
<div class="form-input">
|
||||
<label for="taskdesc" class="label">Description:</label>
|
||||
<input type="text" name="taskdesc" id="taskdesc" required />
|
||||
<input type="text" name="taskdesc" id="taskdesc" placeholder="optional"/>
|
||||
</div>
|
||||
<div class="form-input">
|
||||
<label for="username" class="label">Username:</label>
|
||||
<input type="text" name="username" id="username" placeholder="optional"/>
|
||||
</div>
|
||||
<div class="btn-wrap">
|
||||
<button class="btn">Submit</button>
|
||||
@@ -33,5 +22,4 @@
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock content %}
|
19
taskmanager/templates/pages/deltask.html
Normal file
19
taskmanager/templates/pages/deltask.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block content %}
|
||||
<body>
|
||||
<main class="container page page-addtask">
|
||||
<h1>Create new task</h1>
|
||||
<div class="form-wrap">
|
||||
<form action="/projects/{{task.id}}/del" method="POST">
|
||||
<p> Task creator's password <p>
|
||||
<div class="form-input">
|
||||
<label for="password" class="label">password:</label>
|
||||
<input type="password" name="password" id="password" required />
|
||||
</div>
|
||||
<div class="btn-wrap">
|
||||
<button class="btn">DELETE</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
{% endblock content %}
|
21
taskmanager/templates/pages/index.html
Normal file
21
taskmanager/templates/pages/index.html
Normal file
@@ -0,0 +1,21 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block content %}
|
||||
<main class="container page page-index">
|
||||
<section>
|
||||
<div class="btn">
|
||||
<a href="/addtask">Add new task</a>
|
||||
</div>
|
||||
<div class="tasks-wrap">
|
||||
<h1>Tasks</h1>
|
||||
{% for task in tasks %}
|
||||
<div class="task">
|
||||
<a href="/projects/{{task.id}}">
|
||||
<div>{{task.id}}.</div>
|
||||
<div>{{task.name}}</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
{% endblock content %}
|
@@ -1,20 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<title>{{task.name}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="container">
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/register">Register</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block content %}
|
||||
<main class="container page page-project">
|
||||
<section >
|
||||
<h1>{{task.name}}</h1>
|
||||
@@ -43,11 +28,9 @@
|
||||
<button class="btn">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
<p><a href="/projects/{{task.id}}/del">DELETE TASK</a><p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<footer>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock content %}
|
@@ -1,20 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<title>Register</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="container">
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li class="current"><a href="/register">Register</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block content %}
|
||||
<main class="container page page-register">
|
||||
<div class="form-wrap">
|
||||
<form action="/register" method="POST">
|
||||
@@ -36,5 +21,4 @@
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock content %}
|
8
taskmanager/templates/pages/response.html
Normal file
8
taskmanager/templates/pages/response.html
Normal file
@@ -0,0 +1,8 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block content %}
|
||||
<main class="container page page-project">
|
||||
<section >
|
||||
<p>{{response}}<p>
|
||||
</section>
|
||||
</main>
|
||||
{% endblock content %}
|
Reference in New Issue
Block a user