summaryrefslogtreecommitdiff
path: root/jimbrella/database.py
diff options
context:
space:
mode:
Diffstat (limited to 'jimbrella/database.py')
-rw-r--r--jimbrella/database.py94
1 files changed, 86 insertions, 8 deletions
diff --git a/jimbrella/database.py b/jimbrella/database.py
index f069229..d672653 100644
--- a/jimbrella/database.py
+++ b/jimbrella/database.py
@@ -1,10 +1,12 @@
import csv
import os
from datetime import datetime, timedelta
-from .utils import human_datetime, human_timedelta, local_now
+from .utils import human_datetime, human_timedelta
from .config import DUE_HOURS
from .exceptions import *
+STATUSES = ["available", "lent", "overdue", "maintenance", "withheld", "unknown"]
+
class Database:
def __init__(self, path):
@@ -17,7 +19,7 @@ class Database:
- uint | whatever fits the regex /[0-9]+/. despite carrying a numerical value, it is
| internally represented as a string.
- string | any UTF-8 string.
- - date | an ISO 8601 date of format "YYYY-MM-DDThh:mm:ss". default timezone is UTC+8.
+ - date | an ISO 8601 date of format "YYYY-MM-DDThh:mm:ss+08:00".
The status of an umbrella consists of:
- serial | (id) unique identifier for the umbrella.
@@ -128,7 +130,7 @@ class Database:
for umb in umbrellas
]
)
- except:
+ except Exception as e:
# failure occurred on write
# abort write, and write back contents as they were before
# TODO: pass on exception and keep log
@@ -136,6 +138,7 @@ class Database:
f = open(self.path, "w")
f.write(backup)
f.close()
+ raise e
self._unlock()
@@ -169,7 +172,7 @@ class Database:
- lent_time_ago_str: string representation for lent_time_ago.
"""
umbrellas = self._read()
- now = local_now()
+ now = datetime.now()
for idx, umb in enumerate(umbrellas):
if umb["status"] in ("lent", "overdue"):
umbrellas[idx]["lent_at_str"] = human_datetime(umb["lent_at"])
@@ -178,20 +181,95 @@ class Database:
umbrellas[idx]["lent_time_ago_str"] = human_timedelta(lent_time_ago)
return umbrellas
+ def update(self, umb) -> list:
+ """An interface to `_update()` with added convenience and safeguards.
+
+ Convenience: Not all fields in an umbrella dict need to be present in `umb`. If an
+ optional field is not there, its value is left untouched. Every field other
+ than `serial` is optional.
+
+ `serial` may be an int or a str that evaluates to the correct serial when
+ int() is applied to it.
+
+ `tenant_id` and `tenant_phone` may be a str or an int.
+
+ `lent_at` in `umb` may either be a datetime.datetime object, or an ISO 8601
+ formatted string.
+
+ Safeguards: Invalid values are rejected as an Exception, in the default case, an
+ UmbrellaValueError.
+
+ Both: When a value is set, values of unneeded fields are automatically discarded
+ if applicable. For example, when `status` is set to "available", `tenant_*`
+ and `lent_at` are no longer needed; in fact, their existence is dangerous
+ as it implies the status of the umbrella is "lent" or "overdue", which is
+ not the case. Therefore, if `status` is set to "available", the unnecessary
+ fields mentioned above are made blank, and these fields supplied in `umb`
+ are ignored.
+
+ Returns updated database (same as `_update()`).
+ """
+ # `serial` must be specified.
+ if "serial" not in umb:
+ raise UmbrellaValueError
+
+ # check if umbrella #<serial> exists in database
+ umb["serial"] = int(umb["serial"])
+ umb_in_db = self._find_by_serial(umb["serial"])
+ if umb_in_db is None:
+ raise UmbrellaNotFoundError
+
+ status = umb_in_db["status"]
+ if "status" in umb:
+ if status not in STATUSES:
+ raise UmbrellaValueError # invalid
+
+ # admin specifies a (perhaps different) status
+ status = umb["status"]
+
+ if "lent_at" in umb:
+ # check if `umb` has valid date (`lent_at`)
+ if isinstance(umb["lent_at"], datetime):
+ lent_at = umb["lent_at"]
+ elif isinstance(umb["lent_at"], str):
+ try:
+ umb["lent_at"] = datetime.fromisoformat(umb["lent_at"])
+ except ValueError:
+ raise UmbrellaValueError
+ else:
+ raise UmbrellaValueError
+
+ # we will now check the validity of `umb` based on `status`
+ if status == "available":
+ # discard unneeded fields
+ for key in ["tenant_name", "tenant_id", "tenant_phone", "tenant_email"]:
+ umb[key] = ""
+ umb["lent_at"] = None
+ elif status in ("lent", "overdue"):
+ # copy values in database into unfilled or non-existent fields of `umb`
+ for key in [
+ "tenant_name",
+ "tenant_id",
+ "tenant_phone",
+ "tenant_email",
+ "lent_at",
+ ]:
+ umb[key] = umb[key] if (key in umb and umb[key]) else umb_in_db[key]
+
+ return self._update(umb)
+
def group_by_status(umbrellas) -> dict:
"""(static method) Returns umbrellas grouped into a dict by their status."""
keys = set([umb["status"] for umb in umbrellas])
# initiate statuses: each status is []
- statuses = dict.fromkeys(
- ["available", "lent", "overdue", "withheld", "maintenance", "unknown"], []
- )
+ statuses = dict.fromkeys(STATUSES, [])
for key in keys:
statuses[key] = [umb for umb in umbrellas if umb["status"] == key]
return statuses
def find_overdue(umbrellas) -> list:
"""(static method) Returns umbrellas in possession of their tenant for too long."""
- now = local_now()
+ now = datetime.now()
return [
umb
for umb in umbrellas