Skip to content

Commit 735ce02

Browse files
committed
added ability to edit events
1 parent 1607f07 commit 735ce02

File tree

5 files changed

+195
-82
lines changed

5 files changed

+195
-82
lines changed

config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@
3030
f.stem for f in Path("static/icons").iterdir() if f.is_file() and f.suffix == ".svg"
3131
]
3232

33-
# phosphor icons from static/icons/phosphor-bold.css
33+
# load all phosphor icons from CSS file
3434
phosphor_icons = sorted(
3535
set(
3636
re.findall(
37-
r"\.ph-([a-z0-9-]+)::before",
37+
r"\.ph-bold\.ph-([a-z0-9-]+):before",
3838
Path("static/icons/phosphor-bold.css").read_text(),
3939
)
4040
)

events/ui.py

Lines changed: 139 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
from flask import Blueprint, flash, redirect, render_template, request, url_for
1+
from flask import Blueprint, abort, flash, redirect, render_template, request, url_for
2+
from werkzeug.datastructures import ImmutableMultiDict
23
from werkzeug.wrappers import Response
34

45
from auth.auth import is_exec_wrapper
5-
from config import colours, icons
6+
from config import colours, custom_icons
67
from events.utils import (
78
create_event,
9+
edit_event,
810
get_all_tags,
911
get_datetime_from_string,
1012
get_event_by_slug,
@@ -15,9 +17,64 @@
1517
events_ui_bp = Blueprint("events_ui", __name__, url_prefix="/events")
1618

1719

20+
def parse_form_data(form_data: ImmutableMultiDict) -> dict | str:
21+
"""Parse event from form data"""
22+
# parse colour
23+
text_colour = form_data.get("text_colour", None)
24+
color_colour = form_data.get("color_colour", None)
25+
26+
text_colour = text_colour.strip().lower() if text_colour else None
27+
color_colour = color_colour.strip().lower() if color_colour else None
28+
29+
if (error := validate_colour(text_colour, color_colour)) is not None:
30+
return error
31+
32+
# prefer text_colour if both are provided (in case these colours change)
33+
colour = text_colour if text_colour else color_colour
34+
35+
# parse dates and duration
36+
start_time = get_datetime_from_string(form_data["start_time"])
37+
if isinstance(start_time, str):
38+
return start_time
39+
40+
duration = (
41+
get_timedelta_from_string(form_data["duration"])
42+
if form_data["duration"]
43+
else None
44+
)
45+
if isinstance(duration, str):
46+
return duration
47+
48+
end_time = (
49+
get_datetime_from_string(form_data["end_time"])
50+
if form_data["end_time"]
51+
else None
52+
)
53+
if isinstance(end_time, str):
54+
return end_time
55+
56+
# parse tags
57+
tags = form_data.getlist("tags[]")
58+
tags = [tag.strip().lower() for tag in tags if tag.strip()]
59+
60+
return {
61+
"name": form_data["name"],
62+
"description": form_data["description"],
63+
"draft": "draft" in form_data,
64+
"location": form_data["location"],
65+
"location_url": form_data.get("location_url", None),
66+
"icon": form_data.get("icon", None),
67+
"colour": colour,
68+
"start_time": start_time,
69+
"duration": duration,
70+
"end_time": end_time,
71+
"tags": tags,
72+
}
73+
74+
1875
@events_ui_bp.route("/create", methods=["GET", "POST"])
1976
@is_exec_wrapper
20-
def create(error: str | None = None) -> str | Response: # noqa: PLR0911
77+
def create() -> str | Response:
2178
"""Create a new event."""
2279

2380
tags = [tag.name for tag in get_all_tags()]
@@ -26,11 +83,9 @@ def create(error: str | None = None) -> str | Response: # noqa: PLR0911
2683
if request.method == "GET":
2784
return render_template(
2885
"events/form.html",
29-
error=error,
3086
action="events_ui.create",
3187
method="POST",
32-
event=None,
33-
icons=icons,
88+
icons=custom_icons,
3489
colours=colours,
3590
tags=tags,
3691
)
@@ -39,67 +94,92 @@ def create(error: str | None = None) -> str | Response: # noqa: PLR0911
3994

4095
print("Creating event with data:", request.form)
4196

42-
# parse colour
43-
text_colour = request.form.get("text_colour", None)
44-
color_colour = request.form.get("color_colour", None)
45-
46-
text_colour = text_colour.strip().lower() if text_colour else None
47-
color_colour = color_colour.strip().lower() if color_colour else None
48-
49-
if (colour := validate_colour(text_colour, color_colour)) is not None:
50-
flash(colour, "error")
97+
# parse form data
98+
data = parse_form_data(request.form)
99+
if isinstance(data, str):
100+
flash(data, "error")
51101
return redirect(url_for("events_ui.create"))
52102

53-
# prefer text_colour if both are provided (in case these colours change)
54-
colour = text_colour if text_colour else color_colour
103+
# attempt to create the event
104+
event = create_event(**data)
55105

56-
# parse dates and duration
57-
start_time = get_datetime_from_string(request.form["start_time"])
58-
if isinstance(start_time, str):
59-
flash(start_time, "error")
106+
# if failed, redirect to the create page with an error
107+
if isinstance(event, str):
108+
flash(event, "error")
60109
return redirect(url_for("events_ui.create"))
61110

62-
duration = (
63-
get_timedelta_from_string(request.form["duration"])
64-
if request.form["duration"]
65-
else None
111+
# if successful, redirect to the event page
112+
return redirect(
113+
url_for(
114+
"events_ui.view",
115+
year=event.date.academic_year,
116+
term=event.date.term,
117+
week=event.date.week,
118+
slug=event.slug,
119+
)
66120
)
67-
if isinstance(duration, str):
68-
flash(duration, "error")
69-
return redirect(url_for("events_ui.create"))
70121

71-
end_time = (
72-
get_datetime_from_string(request.form["end_time"])
73-
if request.form["end_time"]
74-
else None
75-
)
76-
if isinstance(end_time, str):
77-
flash(end_time, "error")
78-
return redirect(url_for("events_ui.create"))
79122

80-
# parse tags
81-
tags = request.form.getlist("tags[]")
82-
tags = [tag.strip().lower() for tag in tags if tag.strip()] if tags else []
123+
@events_ui_bp.route(
124+
"/<int:year>/<int:term>/<int:week>/<string:slug>/edit", methods=["GET", "POST"]
125+
)
126+
@is_exec_wrapper
127+
def edit(
128+
year: int, term: int, week: int, slug: str, error: str | None = None
129+
) -> str | Response:
130+
"""Edit an existing event by its year, term, week, and slug."""
83131

84-
# attempt to create the event
85-
event = create_event(
86-
request.form["name"],
87-
request.form["description"],
88-
"draft" in request.form,
89-
request.form["location"],
90-
request.form.get("location_url", None),
91-
request.form.get("icon", None),
92-
colour,
93-
start_time,
94-
duration,
95-
end_time,
96-
tags,
97-
)
132+
event = get_event_by_slug(year, term, week, slug)
98133

99-
# if failed, redirect to the create page with an error
134+
if event is None:
135+
return abort(404, description="Event not found")
136+
137+
tags = [tag.name for tag in get_all_tags()]
138+
139+
# if getting, return the ui for editing the event
140+
if request.method == "GET":
141+
return render_template(
142+
"events/form.html",
143+
error=error,
144+
action="events_ui.edit",
145+
method="POST",
146+
event=event,
147+
icons=custom_icons,
148+
colours=colours,
149+
tags=tags,
150+
)
151+
152+
# if posting, update the event
153+
154+
# parse form data
155+
data = parse_form_data(request.form)
156+
if isinstance(data, str):
157+
flash(data, "error")
158+
return redirect(
159+
url_for(
160+
"events_ui.edit",
161+
year=year,
162+
term=term,
163+
week=week,
164+
slug=slug,
165+
)
166+
)
167+
168+
# attempt to edit the event
169+
event = edit_event(event.id, **data)
170+
171+
# if failed, redirect to the edit page with an error
100172
if isinstance(event, str):
101173
flash(event, "error")
102-
return redirect(url_for("events_ui.create", error=event))
174+
return redirect(
175+
url_for(
176+
"events_ui.edit",
177+
year=year,
178+
term=term,
179+
week=week,
180+
slug=slug,
181+
)
182+
)
103183

104184
# if successful, redirect to the event page
105185
return redirect(
@@ -113,16 +193,16 @@ def create(error: str | None = None) -> str | Response: # noqa: PLR0911
113193
)
114194

115195

116-
# TODO: other event management UI
117-
118-
119196
@events_ui_bp.route("/<int:year>/<int:term>/<int:week>/<string:slug>")
120197
def view(year: int, term: int, week: int, slug: str) -> str:
121198
"""View an event by its year, term, week, and slug."""
122199

123200
event = get_event_by_slug(year, term, week, slug)
124201

125202
if event is None:
126-
return "Event not found"
203+
return abort(404, description="Event not found")
127204

128205
return str(event.to_dict())
206+
207+
208+
# TODO: combos of events

events/utils.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import pytz
66
import requests
7+
from sqlalchemy import and_, exists
78

89
from config import colours
910
from schema import Event, Tag, Week, db
@@ -118,7 +119,7 @@ def get_week_from_date(date: datetime) -> Week | None: # noqa: PLR0912
118119
"""Get the week from a given date"""
119120

120121
week = Week.query.filter(
121-
(date.date >= Week.start_date) & (date.date <= Week.end_date) # type: ignore
122+
(date >= Week.start_date) & (date <= Week.end_date) # type: ignore
122123
).first()
123124

124125
if week is None:
@@ -247,8 +248,18 @@ def clean_weeks() -> None:
247248
"""Clean weeks that are not associated with any events"""
248249
weeks = Week.query.all()
249250
for week in weeks:
250-
if not week.events: # type: ignore
251+
has_events = db.session.query(
252+
exists().where(
253+
and_(
254+
Event.start_time >= week.start_date,
255+
Event.start_time <= week.end_date,
256+
)
257+
)
258+
).scalar()
259+
260+
if not has_events: # type: ignore
251261
db.session.delete(week)
262+
252263
db.session.commit()
253264

254265

static/js/event-management.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ document.addEventListener("DOMContentLoaded", () => {
118118
hours.toString().padStart(2, '0'),
119119
minutes.toString().padStart(2, '0')
120120
].join(':');
121-
console.log(`Duration: ${formattedDuration}`);
122121
durationInput.value = formattedDuration;
123122
}
124123
}
@@ -194,4 +193,11 @@ document.addEventListener("DOMContentLoaded", () => {
194193
colourText.value = "#" + colourText.value;
195194
}
196195
}, false);
196+
197+
// trigger events on load
198+
[iconInput, colourText, startTimeInput, endTimeInput].forEach(input => {
199+
if (input && input.value) {
200+
input.dispatchEvent(new Event('input', { bubbles: true }));
201+
}
202+
});
197203
});

0 commit comments

Comments
 (0)