summaryrefslogtreecommitdiff
path: root/jimbrella
diff options
context:
space:
mode:
Diffstat (limited to 'jimbrella')
-rw-r--r--jimbrella/admin.py29
-rw-r--r--jimbrella/umbrellas.py59
-rw-r--r--jimbrella/utils.py17
3 files changed, 75 insertions, 30 deletions
diff --git a/jimbrella/admin.py b/jimbrella/admin.py
index 8a52bb8..799ff66 100644
--- a/jimbrella/admin.py
+++ b/jimbrella/admin.py
@@ -1,10 +1,13 @@
from flask import Blueprint, request, session, render_template, redirect, url_for, abort
from user_agents import parse as user_agent
+from datetime import datetime
+from dateutil.parser import isoparse
from .umbrellas import Umbrellas
from .admin_log import AdminLog
from .users import Users
from .exceptions import *
from .config import *
+from .utils import human_timedelta, CST
bp = Blueprint("admin", __name__, url_prefix="/admin")
db = Umbrellas(DATABASE_PATH)
@@ -45,8 +48,30 @@ def index():
@bp.route("/umbrellas")
def umbrellas():
- umbrellas = db.read()
+ # sqlite3.Row does not support item assignment, but dict does
+ umbrellas = [dict(umb) for umb in db.read()]
edit = request.args.get("edit")
+ if edit is not None:
+ if not edit.isnumeric():
+ abort(400)
+ edit = int(edit)
+
+ for umb in umbrellas:
+ if umb["id"] == edit and not umb["lent_at"]:
+ # autofill current time when admin is editing and lent_at is empty
+ umb["lent_at"] = datetime.now().isoformat(timespec="seconds")
+ elif umb["lent_at"]:
+ try:
+ # web interface provides no timezone so UTC+8 is assumed
+ lent_at = isoparse(umb["lent_at"]).replace(tzinfo=CST)
+ umb["lent_at"] = lent_at.isoformat(timespec="seconds")
+ umb["lent_time_ago"] = human_timedelta(
+ datetime.now().astimezone(CST) - lent_at
+ ) + " ago"
+ except ValueError:
+ umb["lent_at"] = "Invalid date"
+ umb["lent_time_ago"] = ""
+
error = request.args.get("error")
template = (
"admin/umbrellas_mobile.html"
@@ -56,7 +81,7 @@ def umbrellas():
return render_template(
template,
umbrellas=umbrellas,
- edit=int(edit) if edit is not None and edit.isnumeric() else None,
+ edit=edit,
error=error,
)
diff --git a/jimbrella/umbrellas.py b/jimbrella/umbrellas.py
index ff88277..2525a31 100644
--- a/jimbrella/umbrellas.py
+++ b/jimbrella/umbrellas.py
@@ -1,7 +1,8 @@
import sqlite3
-from datetime import datetime, timedelta
+from datetime import datetime, timezone, timedelta
+from dateutil.parser import isoparse
from typing import Union
-from .utils import human_datetime, human_timedelta
+from .utils import human_datetime, human_timedelta, CST
from .config import DUE_HOURS, ADMIN_LOG_PATH
from .exceptions import *
@@ -27,8 +28,8 @@ class Umbrellas:
- tenant_id | string. student or faculty ID.
- tenant_phone | string. phone number via which to contact tenant when the lease is due.
- tenant_email | string. for future compatibility. always None for the time being.
- - lent_at | an ISO 8601 date string "YYYY-MM-DDThh:mm:ss.mmm+08:00" if status is
- | "lent" or "overdue. is None otherwise.
+ - lent_at | an ISO 8601 datetime string if status is "lent" or "overdue", empty string
+ | otherwise.
Schema:
CREATE TABLE Umbrellas(
@@ -54,7 +55,9 @@ class Umbrellas:
if umbid is None:
data = db.execute("SELECT * FROM Umbrellas").fetchall()
else:
- data = db.execute("SELECT * FROM Umbrellas WHERE id = ?", (umbid,)).fetchone()
+ data = db.execute(
+ "SELECT * FROM Umbrellas WHERE id = ?", (umbid,)
+ ).fetchone()
db.close()
return data
@@ -64,13 +67,14 @@ class Umbrellas:
Not all fields in an umbrella dict need to be present in `umb`. Only `id` is required.
If an optional field is not found, its value is left untouched. If an optional field is
- present but its value is an empty string or None, the old datum will become NULL.
+ present but its value is an empty string, it will be erased.
Invalid values are rejected as an UmbrellaValueError.
If `status` is not "lent" or "overdue", `tenant_*` and `lent_at` are automatically erased.
- `lent_at` may be either an ISO 8601 string or a datetime.datetime object. Must be UTC+8.
+ `lent_at` may be either an ISO 8601 string or a datetime.datetime object. If no timezone is
+ supplied, UTC+8 is assumed.
Returns a dict of <field>: (<prior_value>, <updated_value>) for each updated field unless
its erasure can be inferred. For AdminLog.
@@ -82,11 +86,13 @@ class Umbrellas:
raise UmbrellaValueError("id")
db = sqlite3.connect(self.path)
- db.row_factory = sqlite.Row
+ db.row_factory = sqlite3.Row
# check if umbrella #<id> exists in database
umbid = umb["id"]
- umb_in_db = db.execute("SELECT * FROM Umbrellas WHERE id = ?", (umbid,)).fetchone()
+ umb_in_db = db.execute(
+ "SELECT * FROM Umbrellas WHERE id = ?", (umbid,)
+ ).fetchone()
if umb_in_db is None:
raise UmbrellaNotFoundError(umbid)
@@ -99,12 +105,14 @@ class Umbrellas:
if umb_in_db["status"] != umb["status"]:
diff["status"] = (umb_in_db["status"], umb["status"])
- db.execute("UPDATE Umbrellas SET status = ? WHERE id = ?", (status, umbid))
+ db.execute(
+ "UPDATE Umbrellas SET status = ? WHERE id = ?", (status, umbid)
+ )
else:
raise UmbrellaValueError("status")
if status in ("lent", "overdue"):
- for key in (
+ for col in (
"tenant_name",
"tenant_id",
"tenant_phone",
@@ -115,16 +123,17 @@ class Umbrellas:
diff[col] = (umb_in_db[col], umb[col])
db.execute(
- "UPDATE Umbrellas SET ? = ? WHERE id = ?",
- (col,
- umb[col],
- umbid,)
+ f"UPDATE Umbrellas SET {col} = ? WHERE id = ?",
+ (
+ umb[col],
+ umbid,
+ ),
)
if "lent_at" in umb:
try:
# lent_at could be a string, in which case it is parsed
- lent_at = datetime.fromisoformat(umb["lent_at"])
+ lent_at = isoparse(umb["lent_at"])
except TypeError:
# or it could be a datetime.datetime
lent_at = umb["lent_at"]
@@ -132,16 +141,20 @@ class Umbrellas:
# anything else is invalid
raise UmbrellaValueError("lent_at")
- if lent_at.tzinfo.utcoffset(None).seconds != 28800:
- # timezone must be +08:00
- raise UmbrellaValueError("lent_at")
+ if lent_at.tzinfo is None:
+ lent_at = lent_at.replace(tzinfo=CST)
- diff["lent_at"] = (umb_in_db["lent_at"], lent_at.isoformat(timespec="milliseconds"))
+ diff["lent_at"] = (
+ umb_in_db["lent_at"],
+ lent_at.isoformat(timespec="milliseconds"),
+ )
db.execute(
"UPDATE Umbrellas SET lent_at = ? WHERE id = ?",
- ( lent_at.isoformat(timespec="milliseconds"),
- umbid,)
+ (
+ lent_at.isoformat(timespec="milliseconds"),
+ umbid,
+ ),
)
else:
# discard unneeded fields
@@ -152,7 +165,7 @@ class Umbrellas:
"tenant_email",
"lent_at",
):
- db.execute("UPDATE Umbrellas SET ? = NULL WHERE id = ?", (col, umbid))
+ db.execute(f"UPDATE Umbrellas SET {col} = '' WHERE id = ?", (umbid,))
# now that new data are validated, commit the SQL transaction
db.commit()
diff --git a/jimbrella/utils.py b/jimbrella/utils.py
index fd2c86d..6e02d1a 100644
--- a/jimbrella/utils.py
+++ b/jimbrella/utils.py
@@ -1,7 +1,4 @@
-from datetime import datetime, timedelta, tzinfo
-
-# identity function
-identity = lambda x: x
+from datetime import datetime, timedelta, timezone
def human_datetime(time: datetime) -> str:
@@ -18,4 +15,14 @@ def human_timedelta(delta: timedelta) -> str:
hours = delta.seconds // 3600
minutes = (delta.seconds - (hours * 3600)) // 60
- return days + f"{hours:0>2}:{minutes:0>2}" # zero-pad to two digits
+
+ if hours == 0 and minutes == 0:
+ return "<1 minute"
+
+ if hours == 0:
+ return days + f"{minutes}min"
+
+ return days + f"{hours}h {minutes}min"
+
+
+CST = timezone(timedelta(hours=8))