Skip to content

Commit 1c8a577

Browse files
committed
start to develop templates. Need simple queue to get calendars in background.
1 parent f383f1d commit 1c8a577

File tree

10 files changed

+246
-15
lines changed

10 files changed

+246
-15
lines changed

src/calendar.py

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,15 @@ def duration(self):
235235

236236
def to_json(self):
237237
return {
238-
start: self.start,
239-
end: self.end,
240-
duration_in_secs: self.duration.total_seconds(),
238+
"start": {
239+
"hour": self.start.hour,
240+
"minute": self.start.minute,
241+
},
242+
"end": {
243+
"hour": self.end.hour,
244+
"minute": self.minute.minute,
245+
},
246+
"duration_in_secs": self.duration.total_seconds(),
241247
}
242248

243249
def __str__(self):
@@ -250,7 +256,9 @@ def __repr__(self):
250256
return f"<TimeSpan (start={self.start}, end={self.end}, [duration={self.duration}])>"
251257

252258

253-
def fetch_calblocks(duration: timedelta, inittime=datetime.now(get_tz())):
259+
def fetch_calblocks(
260+
duration: timedelta, inittime=datetime.now(get_tz())
261+
) -> T.Iterable[TimeSpan]:
254262
inittime = awareness(inittime)
255263
grace = timedelta(**config["grace_period"])
256264
starttime = top_of_hour(inittime) + grace
@@ -265,3 +273,44 @@ def fetch_calblocks(duration: timedelta, inittime=datetime.now(get_tz())):
265273
if not does_conflict(collection, a1, a2):
266274
yield TimeSpan(a1, a2)
267275
a1 = a2
276+
277+
278+
@cached(LRUCache(1024))
279+
def calblock_choices(
280+
duration: timedelta,
281+
year=None,
282+
month=None,
283+
day=None,
284+
inittime=datetime.now(get_tz()),
285+
) -> T.Iterable[TimeSpan]:
286+
"""Automagically returns the next choice in the chain given the input.
287+
288+
TODO: Somhow this seems wrong; might need improvment
289+
290+
Args:
291+
duration (timedelta): The duration of the appointment
292+
year (int, optional): Chosen year. Defaults to None.
293+
month (int, optional): Chosen month. Defaults to None.
294+
day (int, optional): Chosen day. Defaults to None.
295+
inittime (tz-aware datetime, optional): Initial time. Defaults to datetime.now(get_tz()).
296+
297+
Returns:
298+
T.Iterable[T.Any]: depending...
299+
- list of years if nothing is selected
300+
- list of months if year is selected
301+
- list of days if month is selected
302+
- list of timespans (as json) if day is selected
303+
304+
Yields:
305+
Iterator[T.Iterable[TimeSpan]]: _description_
306+
"""
307+
for ts in fetch_calblocks(duration, inittime):
308+
ts: TimeSpan
309+
if year and ts.start.year == year:
310+
yield ts.start.month
311+
elif month and ts.start.month == month:
312+
yield ts.start.day
313+
elif day and ts.start.day == day:
314+
yield ts
315+
elif not year:
316+
yield ts.to_json()

src/config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,10 @@ def get_end_view(when=datetime.now()):
5353
if not when.tzinfo:
5454
when = tz.localize(when)
5555
return when + get_grace_period()
56+
57+
58+
def get_appts(id: T.AnyStr = None):
59+
appts = config["appointments"]
60+
if id:
61+
return appts.get(id, None)
62+
return appts

src/main.py

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,105 @@
1-
from flask import Flask, request
2-
from .config import config
1+
from flask import Flask, request, render_template, abort
2+
from .config import config, get_appts
33
from .checker import check_config
4+
from urllib.parse import parse_qs, parse_qsl
5+
from .calendar import calblock_choices
6+
from datetime import timedelta
7+
import jinja2
48
import logging.config
59
import logging
10+
import pytz
611

712
log = logging.getLogger(__name__)
813
app = Flask(__name__)
914

1015
STEPS = [
1116
"/",
12-
"/<string:block>",
13-
"/<string:block>/<int:year>",
14-
"/<string:block>/<int:year>/<int:month>",
15-
"/<string:block>/<int:year>/<int:month>/<int:day>",
17+
"/<string:block>/",
18+
"/<string:block>/<int:year>/",
19+
"/<string:block>/<int:year>/<int:month>/",
20+
"/<string:block>/<int:year>/<int:month>/<int:day>/",
1621
]
1722
GET = "GET"
23+
POST = "POST"
1824

1925

2026
@app.route(STEPS[0], methods=[GET])
2127
@app.route(STEPS[1], methods=[GET])
2228
@app.route(STEPS[2], methods=[GET])
2329
@app.route(STEPS[3], methods=[GET])
2430
@app.route(STEPS[4], methods=[GET])
25-
def show_form(block, year=None, month=None, day=None):
26-
pass
31+
def show_appointment_scheduler(block=None, year=None, month=None, day=None):
32+
# TODO: sanity checking of timespan; user might
33+
# be able to specify time outside of workday!!!
2734

35+
TEMPLATE = "index/timeblock_selection.html"
2836

29-
@app.route(STEPS[4], methods=[GET])
37+
appts = get_appts()
38+
39+
# if we don't have a block, render that
40+
if not block:
41+
return render_template(TEMPLATE, **{
42+
"choices": [
43+
{"value": k, "label": a["label"], "time": a["time"]}
44+
for (k, a) in appts.items()
45+
],
46+
})
47+
else:
48+
# or check it
49+
if block not in appts:
50+
abort(Response(f"Block {block} not found"))
51+
52+
53+
appt = appts[block]
54+
duration = timedelta(minutes=appt["time"])
55+
choices = calblock_choices(duration, year, month, day)
56+
57+
# construct meta info
58+
59+
# I know there's a shorter way to construct this,
60+
# but it can lead to getting confusing, so we'll do it the
61+
# long way for now.
62+
if block:
63+
back_url = "/"
64+
back_label = "Choose Time Block"
65+
if year:
66+
back_url = f"/{block}"
67+
back_label = f"Choose Time Block"
68+
if month:
69+
back_url = f"/{block}/{year}"
70+
back_label = f"Choose Year"
71+
if day:
72+
back_url = f"/{block}/{year}/{month}/"
73+
back_label = f"Choose Month"
74+
75+
# Next, the year.
76+
# if we don't have a block, render that
77+
return render_template(TEMPLATE, **{
78+
"timezones": [(t, t) for t in pytz.all_timezones],
79+
"choices": choices,
80+
"year": year,
81+
"month": month,
82+
"day": day,
83+
"meta": {
84+
"back": {
85+
"url": back_url,
86+
"label": back_label,
87+
}
88+
},
89+
})
90+
91+
92+
@app.route(STEPS[4], methods=[POST])
3093
def submit_complete(block, year=None, month=None, day=None):
31-
request.form
94+
# Once the form has been submitted
95+
# construct the ics as an attachment
96+
# email both the user and the "owners"
97+
time = request.form.get("time")
98+
email = request.form.get("email")
99+
details = request.form.get("details")
32100

33101

34102
if __name__ == "__main__":
35103
check_config()
36104
logging.config.dictConfig(config["logging"])
37-
# app.run(debug=True)
105+
app.run(debug=True)

src/templates/inc/selection/day.html

Whitespace-only changes.

src/templates/inc/selection/month.html

Whitespace-only changes.

src/templates/inc/selection/time.html

Whitespace-only changes.

src/templates/inc/selection/timezone.html

Whitespace-only changes.

src/templates/inc/selection/year.html

Whitespace-only changes.

src/templates/index/base.html

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<!-- Required meta tags -->
5+
<meta charset="utf-8" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
8+
<!-- Bootstrap CSS -->
9+
<link
10+
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
11+
rel="stylesheet"
12+
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
13+
crossorigin="anonymous"
14+
/>
15+
16+
<title>{% block site_title %}{% endblock %}</title>
17+
</head>
18+
<body>
19+
<h1 class="title">{% block title %}{% endblock %}</h1>
20+
21+
<section id="header">{% block header %}{% endblock %}</section>
22+
23+
<main>{% block main %}{% endblock %}</main>
24+
25+
<!-- Optional JavaScript; choose one of the two! -->
26+
27+
<!-- Option 1: Bootstrap Bundle with Popper -->
28+
<script
29+
src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
30+
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
31+
crossorigin="anonymous"
32+
></script>
33+
34+
<!-- Option 2: Separate Popper and Bootstrap JS -->
35+
<!--
36+
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
37+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script>
38+
-->
39+
</body>
40+
</html>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{% extends "index/base.html" %}
2+
{% block site_title %}Ink-In Time{% endblock %}
3+
{% block title %}Choose A Time to Meet{% endblock %}
4+
5+
{% block main %}
6+
7+
<div class="container">
8+
{% if meta and meta.back %}
9+
<a href="{{ meta.back.url }}">{{ meta.back.label }}</a>
10+
{% endif %}
11+
{% include "inc/selection/timezone.html" %}
12+
13+
<div class="section-container">
14+
<code>{{ choices }}</code>
15+
{% if block %}
16+
{{ block.label }}
17+
{% else %}
18+
{% for block in choices %}
19+
<a href="/{{ block.value }}/" class="btn btn-lg btn-outline-primary">
20+
<p class="lg">{{ block.label }}</p>
21+
<p class="text-lg">{{ block.time }} min</p>
22+
</a>
23+
{% endfor %}
24+
{% endif %}
25+
</div>
26+
27+
<div class="selection-container">
28+
{% if year %}
29+
<p>{{ year }}</p>
30+
{% else %}
31+
{% include "inc/selection/year.html" %}
32+
{% endif %}
33+
</div>
34+
35+
<div class="selection-container">
36+
{% if month %}
37+
<div class="">{{ month }}</div>
38+
{% else %}
39+
{% include "inc/selection/day.html" %}
40+
{% endif %}
41+
</div>
42+
43+
<div class="selection-container">
44+
{% if day %}
45+
<div class="">{{ day }}</div>
46+
{% elif year %}
47+
{% include "inc/selection/day.html" %}
48+
{% endif %}
49+
</div>
50+
51+
<div class="selection-container">
52+
{% if day %}
53+
<div class="">{{ day }}</div>
54+
{% elif month %}
55+
{% include "inc/selection/day.html" %}
56+
{% endif %}
57+
</div>
58+
<div class="selection-container">
59+
{% if day %}
60+
<div class="">{{ day }}</div>
61+
{% elif month %}
62+
{% include "inc/selection/day.html" %}
63+
{% endif %}
64+
</div>
65+
</div>
66+
67+
{% endblock %}

0 commit comments

Comments
 (0)