Quellcode durchsuchen

Enormous Update

Peter Oertel vor 1 Jahr
Ursprung
Commit
a7bd96eeca
6 geänderte Dateien mit 283 neuen und 12 gelöschten Zeilen
  1. 14 0
      Dockerfile
  2. 130 9
      main.py
  3. 48 0
      templates/account.html
  4. 27 0
      templates/admin.html
  5. 1 1
      templates/base.html
  6. 63 2
      templates/home.html

+ 14 - 0
Dockerfile

@@ -0,0 +1,14 @@
+FROM python:3.12-slim
+
+WORKDIR /python-docker
+
+RUN apt-get update && apt-get install -y pkg-config python3-dev default-libmysqlclient-dev build-essential default-libmysqlclient-dev && pip install mysqlclient
+
+COPY requirements.txt requirements.txt
+RUN pip3 install -r requirements.txt
+
+COPY . .
+
+EXPOSE 8000
+
+CMD ["gunicorn", "-b", ":8000", "main:app"]

+ 130 - 9
main.py

@@ -15,6 +15,7 @@ from flask import (
 from flask_sqlalchemy import SQLAlchemy
 from sqlalchemy.orm import DeclarativeBase
 from sqlalchemy.sql import func
+from sqlalchemy.exc import IntegrityError
 
 
 dotenv.load_dotenv()
@@ -69,15 +70,88 @@ class Task_Assignment(db.Model):
 
 
 @app.route("/")
-def home():
+def home(completed=False):
     if not session.get("userid"):
         return redirect(url_for("login"))
 
-    tasks = db.session.execute(
-        db.select(Task).where(Task.deleted == False)
-    ).scalars()
+    select = (
+        db.select(Task)
+        .where(Task.deleted == False)
+        .order_by(Task.created_datetime.desc())
+    )
 
-    return render_template("home.html", tasks=tasks)
+    if completed:
+        select = select.where(Task.completed == True)
+    else:
+        select = select.where(Task.completed == False)
+
+    tasks = db.session.execute(select).scalars().all()
+
+    for task in tasks:
+        task.assignees = ""
+
+        assignees = (
+            db.session.execute(
+                db.select(User)
+                .join(Task_Assignment)
+                .where(Task_Assignment.task_id == task.id)
+            )
+            .scalars()
+            .all()
+        )
+
+        for assignee in assignees:
+            task.assignees += assignee.username + ", "
+        task.assignees = task.assignees[:-2]
+
+    users = db.session.execute(db.select(User)).scalars().all()
+
+    return render_template(
+        "home.html", tasks=tasks, completed=completed, users=users
+    )
+
+
[email protected]("/completed")
+def completed():
+    return home(completed=True)
+
+
[email protected]("/account", methods=["GET", "POST"])
+def account():
+    if not session.get("userid"):
+        return redirect(url_for("login"))
+
+    user = db.session.execute(
+        db.select(User).where(User.id == session.get("userid"))
+    ).scalar_one_or_none()
+
+    if not user:
+        return redirect(url_for("login"))
+
+    if request.method == "POST":
+        if newpassword := request.form.get("newpassword"):
+            if not bcrypt.checkpw(
+                request.form.get("oldpassword").encode(), user.passhash.encode()
+            ):
+                flash("Incorrect Password", category="warning")
+                return redirect(url_for("account"))
+
+            salt = bcrypt.gensalt()
+            user.passhash = bcrypt.hashpw(newpassword.encode(), salt)
+            db.session.commit()
+            flash("Updated Password", category="success")
+            return redirect(url_for("account"))
+
+        if username := request.form.get("username"):
+            user.username = username
+            db.session.commit()
+            flash("Updated Username", category="success")
+            return redirect(url_for("account"))
+
+        flash("Failed to update", category="warning")
+        return redirect(url_for("account"))
+
+    return render_template("account.html", user=user)
 
 
 @app.route("/login", methods=["GET", "POST"])
@@ -109,7 +183,7 @@ def login():
                 flash("Failed to login", category="warning")
                 return redirect(url_for("login"))
         except:
-            flash("Critical Error, contact Peter pls", category="error")
+            flash("Critical Error, contact Peter pls", category="danger")
             return redirect(url_for("login"))
 
     return render_template("login.html")
@@ -117,6 +191,8 @@ def login():
 
 @app.route("/addtask", methods=["POST"])
 def addtask():
+    if not session.get("userid"):
+        return redirect(url_for("login"))
     try:
         tasktext = request.form.get("task-text")
         user_id = session.get("userid")
@@ -132,7 +208,7 @@ def addtask():
         db.session.add(newtask)
         db.session.commit()
     except:
-        flash("Critical Error, contact Peter pls", category="error")
+        flash("Critical Error, contact Peter pls", category="danger")
         return redirect(url_for("home"))
 
     return redirect(url_for("home"))
@@ -140,6 +216,8 @@ def addtask():
 
 @app.route("/updatetask", methods=["POST"])
 def updatetask():
+    if not session.get("userid"):
+        return redirect(url_for("login"))
     if "task-id" not in request.form:
         flash("Failed to find task", category="warning")
         return redirect(url_for("home"))
@@ -153,18 +231,61 @@ def updatetask():
         return redirect(url_for("home"))
 
     if "due" in request.form:
-        if not task.due:
+        due = request.form.get("due")
+        if not due:
             flash("Invalid due date", category="warning")
             return redirect(url_for("home"))
 
-        datetime_str = request.form.get("due")
+        datetime_str = due
         datetime_str = datetime_str.replace("T", " ")
         datetime_str += ":00"
+        app.logger.info(datetime_str)
         task.due = datetime_str
 
+    if "assignee" in request.form:
+        assignee = request.form.get("assignee")
+        if not assignee:
+            flash("Invalid assignee", category="warning")
+            return redirect(url_for("home"))
+
+        assignment = Task_Assignment(user_id=assignee, task_id=task.id)
+        db.session.add(assignment)
+
+    if "completed" in request.form:
+        task.completed = not task.completed
+
+    try:
         db.session.commit()
+    except IntegrityError as e:
+        flash("Database error, what did you just try to do?", category="danger")
+
+    return redirect(url_for("home"))
+
+
[email protected]("/admin", methods=["GET", "POST"])
+def admin():
+    if not session.get("userid"):
+        return redirect(url_for("login"))
+    user = db.session.execute(
+        db.select(User).where(User.id == session.get("userid"))
+    ).scalar_one_or_none()
+    if not user or not user.is_admin:
+        flash("You are not an admin!", category="danger")
         return redirect(url_for("home"))
 
+    if request.method == "POST":
+        if userid := request.form.get("userid-reset"):
+            resetuser = db.session.execute(
+                db.select(User).where(User.id == userid)
+            ).scalar_one_or_none()
+            resetuser.passhash = None
+            db.session.commit()
+            flash("Reset user's password succesfully", category="success")
+            return redirect(url_for("admin"))
+
+    users = db.session.execute(db.select(User)).scalars().all()
+    return render_template("admin.html", users=users)
+
 
 if __name__ == "__main__":
     app.run(host="0.0.0.0", debug=True)

+ 48 - 0
templates/account.html

@@ -0,0 +1,48 @@
+{% extends "base.html" %}
+{% block title %}
+Toppers Tasks | Account
+{% endblock %}
+{% block content %}
+{% for category, message in get_flashed_messages(with_categories=True) %}
+<div class="alert alert-{{ category }}" role="alert">
+    {{ message }}
+</div>
+{% endfor %}
+<div class="row">
+    <h1>Hello {{user.username}}!</h1>
+</div>
+<div class="row">
+    <div class="task-card">
+        <form action="/account" method="post">
+            <input
+                type="password"
+                name="oldpassword"
+                id="oldpassword"
+                placeholder="Old Password"
+                class="form-control mb-1 max-wack">
+            <input
+                type="password"
+                name="newpassword"
+                id="newpassword"
+                placeholder="New Password"
+                class="form-control mb-1 max-wack">
+            <button type="submit" class="btn btn-primary max-wack">
+                Change Password
+            </button>
+        </form>
+    </div>
+    <div class="task-card">
+        <form action="/account" method="post">
+            <input
+                type="text"
+                name="username"
+                id="username"
+                placeholder="New Username"
+                class="form-control mb-1 max-wack">
+            <button type="submit" class="btn btn-primary max-wack">
+                Change Username
+            </button>
+        </form>
+    </div>
+</div>
+{% endblock %}

+ 27 - 0
templates/admin.html

@@ -0,0 +1,27 @@
+{% extends "base.html" %}
+{% block title %}
+Toppers Tasks | Account
+{% endblock %}
+{% block content %}
+{% for category, message in get_flashed_messages(with_categories=True) %}
+<div class="alert alert-{{ category }}" role="alert">
+    {{ message }}
+</div>
+{% endfor %}
+<div class="row">
+    <div class="task-card">
+        <h2>Reset Password</h2>
+        <form action="/admin" method="post">
+            <label for="user-select">Username</label>
+            <select class="form-control mb-2" name="userid-reset"
+                id="user-select">
+                {% for user in users %}
+                <option value="{{ user.id }}">{{ user.username }}</option>
+                {% endfor %}
+            </select>
+            <button type="submit" class="btn btn-primary max-wack">Reset
+                Password</button>
+        </form>
+    </div>
+</div>
+{% endblock %}

+ 1 - 1
templates/base.html

@@ -16,7 +16,7 @@
             href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css">
     </head>
     <body>
-        <nav class="navbar navbar-expand-lg bg-body-teritary">
+        <nav class="navbar navbar-expand-lg bg-body-tertiary mb-3">
             <div class="container-fluid">
                 <a class="navbar-brand" href="#">Toppers Tasks</a>
                 <button class="navbar-toggler" type="button"

+ 63 - 2
templates/home.html

@@ -8,6 +8,7 @@ Toppers Tasks
     {{ message }}
 </div>
 {% endfor %}
+{% if not completed %}
 <form action="/addtask" method="post"
     class="d-flex flex-row mb-3">
     <input class="form-control form-control-lg me-3" type="text"
@@ -18,6 +19,7 @@ Toppers Tasks
         <i class="bi-plus-circle"></i>
     </button>
 </form>
+{% endif %}
 <div class="row">
     {% for task in tasks %}
     <div class="task-card" id="task-{{task.id}}">
@@ -43,7 +45,7 @@ Toppers Tasks
                             {% else %}
                             <form action="/updatetask"
                                 method="post">
-                                <input type="text" id="task-id"
+                                <input type="text"
                                     name="task-id"
                                     value="{{task.id}}" hidden>
                                 <input type="datetime-local" name="due"
@@ -54,11 +56,70 @@ Toppers Tasks
                             {% endif %}
                         </td>
                     </tr>
-
+                    <tr>
+                        <td>Assignees</td>
+                        <td>
+                            <div class="d-flex flex-row">
+                                <a href="#assignees-{{task.id}}"
+                                    data-bs-toggle="collapse"
+                                    class="btn btn-primary me-3">
+                                    <i class="bi-plus-circle"></i>
+                                </a>
+                                <p>{{ task.assignees }}</p>
+                            </div>
+                            <div class="collapse mt-1"
+                                id="assignees-{{task.id}}">
+                                <form action="/updatetask"
+                                    method="post"
+                                    class="d-flex flex-row">
+                                    <input type="text" name="task-id"
+                                        value="{{task.id}}"
+                                        hidden>
+                                    <select name="assignee"
+                                        id="assignee-{{task.id}}"
+                                        class="form-control">
+                                        {% for user in users %}
+                                        <option
+                                            value="{{user.id}}">{{user.username}}</option>
+                                        {% endfor %}
+                                    </select>
+                                    <button type="submit"
+                                        class="btn btn-primary">
+                                        Add Assignee
+                                    </button>
+                                </form>
+                            </div>
+                        </td>
+                    </tr>
                 </tbody>
             </table>
+
+        </div>
+        <div class="mt-2">
+            <form action="/updatetask" method="post">
+                <input type="text" name="task-id" value="{{task.id}}" hidden>
+                <input type="text" name="completed" value="bruh" hidden>
+                {% if not task.completed %}
+                <button type="submit" class="btn btn-success max-wack">
+                    <i class="bi-check-circle"></i>
+                    Complete Task
+                </button>
+                {% else %}
+                <button type="submit" class="btn btn-danger max-wack">
+                    <i class="bi-x-circle"></i>
+                    Incomplete Task
+                </button>
+                {% endif %}
+            </form>
         </div>
     </div>
     {% endfor %}
 </div>
+{% if not completed %}
+<div class="row">
+    <a href="/completed" class="btn btn-primary">
+        Completed Tasks
+    </a>
+</div>
+{% endif %}
 {% endblock %}