Skip to content

Commit c225585

Browse files
authored
Merge pull request #32 from filips123/feed-and-calendar-improvements
2 parents 13b7fd0 + 213fe30 commit c225585

File tree

5 files changed

+124
-15
lines changed

5 files changed

+124
-15
lines changed

API/gimvicurnik/__init__.py

+66-9
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from .database import Class, Classroom, Document, Entity, LunchMenu, LunchSchedule, Session, SnackMenu, Teacher
2020
from .errors import ConfigError, ConfigParseError, ConfigReadError, ConfigValidationError
2121
from .utils.flask import DateConverter, ListConverter
22-
from .utils.ical import create_calendar
22+
from .utils.ical import create_schedule_calendar, create_school_calendar
2323
from .utils.url import tokenize_url
2424

2525

@@ -115,6 +115,7 @@ def __init__(self, configfile):
115115
self.convert_date_objects()
116116

117117
self.register_route_converters()
118+
self.register_jinja_filters()
118119
self.register_commands()
119120
self.register_routes()
120121

@@ -256,6 +257,16 @@ def register_route_converters(self):
256257
self.app.url_map.converters["date"] = DateConverter
257258
self.app.url_map.converters["list"] = ListConverter
258259

260+
def register_jinja_filters(self):
261+
"""Register all custom Jinja filters."""
262+
263+
def format_date(date):
264+
return date.strftime("%d. %m. %Y")
265+
266+
filters = self.app.jinja_env.filters
267+
filters["date_format_daily"] = format_date
268+
filters["date_format_weekly"] = lambda date: f"{format_date(date)}{format_date((date + timedelta(days=4)))}"
269+
259270
def register_commands(self):
260271
"""Register all application commands."""
261272

@@ -271,7 +282,7 @@ def register_commands(self):
271282
def register_routes(self):
272283
"""Register all application routes."""
273284

274-
def create_feed(filter, name, type, format):
285+
def create_feed(filter, name, type, format, display_date=None, display_date_type="daily"):
275286
query = (
276287
self.session.query(Document.date, Document.type, Document.url, Document.description)
277288
.filter(filter)
@@ -285,6 +296,8 @@ def create_feed(filter, name, type, format):
285296
type=type,
286297
entries=query,
287298
last_updated=max(model.date for model in query),
299+
display_date=display_date,
300+
display_date_type=display_date_type,
288301
)
289302

290303
return (
@@ -433,6 +446,7 @@ def _circulars_get_atom():
433446
name="Okrožnice",
434447
type="circulars",
435448
format="atom",
449+
display_date=False,
436450
)
437451

438452
@self.app.route("/feeds/circulars.rss")
@@ -442,23 +456,52 @@ def _circulars_get_rss():
442456
name="Okrožnice",
443457
type="circulars",
444458
format="rss",
459+
display_date=False,
445460
)
446461

447462
@self.app.route("/feeds/substitutions.atom")
448463
def _substitutions_get_atom():
449-
return create_feed(filter=Document.type == "substitutions", name="Nadomeščanja", type="substitutions", format="atom")
464+
return create_feed(
465+
filter=Document.type == "substitutions",
466+
name="Nadomeščanja",
467+
type="substitutions",
468+
format="atom",
469+
display_date=True,
470+
display_date_type="daily",
471+
)
450472

451473
@self.app.route("/feeds/substitutions.rss")
452474
def _substitutions_get_rss():
453-
return create_feed(filter=Document.type == "substitutions", name="Nadomeščanja", type="substitutions", format="rss")
475+
return create_feed(
476+
filter=Document.type == "substitutions",
477+
name="Nadomeščanja",
478+
type="substitutions",
479+
format="rss",
480+
display_date=True,
481+
display_date_type="daily",
482+
)
454483

455484
@self.app.route("/feeds/schedules.atom")
456485
def _schedules_get_atom():
457-
return create_feed(filter=Document.type == "lunch-schedule", name="Razporedi kosil", type="schedules", format="atom")
486+
return create_feed(
487+
filter=Document.type == "lunch-schedule",
488+
name="Razporedi delitve kosila",
489+
type="schedules",
490+
format="atom",
491+
display_date=True,
492+
display_date_type="daily",
493+
)
458494

459495
@self.app.route("/feeds/schedules.rss")
460496
def _schedules_get_rss():
461-
return create_feed(filter=Document.type == "lunch-schedule", name="Razporedi kosil", type="schedules", format="rss")
497+
return create_feed(
498+
filter=Document.type == "lunch-schedule",
499+
name="Razporedi delitve kosila",
500+
type="schedules",
501+
format="rss",
502+
display_date=True,
503+
display_date_type="daily",
504+
)
462505

463506
@self.app.route("/feeds/menus.atom")
464507
def _menu_get_atom():
@@ -467,6 +510,8 @@ def _menu_get_atom():
467510
name="Jedilniki",
468511
type="menus",
469512
format="atom",
513+
display_date=True,
514+
display_date_type="weekly",
470515
)
471516

472517
@self.app.route("/feeds/menus.rss")
@@ -476,11 +521,13 @@ def _menu_get_rss():
476521
name="Jedilniki",
477522
type="menus",
478523
format="rss",
524+
display_date=True,
525+
display_date_type="weekly",
479526
)
480527

481528
@self.app.route("/calendar/combined/<list:classes>")
482529
def _get_calendar_for_classes(classes):
483-
return create_calendar(
530+
return create_school_calendar(
484531
Class.get_substitutions(self.session, None, classes),
485532
Class.get_lessons(self.session, classes),
486533
self.config["hourtimes"],
@@ -489,7 +536,7 @@ def _get_calendar_for_classes(classes):
489536

490537
@self.app.route("/calendar/timetable/<list:classes>")
491538
def _get_calendar_timetable_for_classes(classes):
492-
return create_calendar(
539+
return create_school_calendar(
493540
Class.get_substitutions(self.session, None, classes),
494541
Class.get_lessons(self.session, classes),
495542
self.config["hourtimes"],
@@ -499,14 +546,24 @@ def _get_calendar_timetable_for_classes(classes):
499546

500547
@self.app.route("/calendar/substitutions/<list:classes>")
501548
def _get_calendar_substitutions_for_classes(classes):
502-
return create_calendar(
549+
return create_school_calendar(
503550
Class.get_substitutions(self.session, None, classes),
504551
Class.get_lessons(self.session, classes),
505552
self.config["hourtimes"],
506553
f"Nadomeščanja - {', '.join(classes)} - Gimnazija Vič",
507554
timetable=False,
508555
)
509556

557+
@self.app.route("/calendar/schedules/<list:classes>")
558+
def _get_calendar_schedules_for_classes(classes):
559+
return create_schedule_calendar(
560+
self.session.query(LunchSchedule)
561+
.join(Class)
562+
.filter(Class.name.in_(classes))
563+
.order_by(LunchSchedule.time, LunchSchedule.class_),
564+
f"Razporedi delitve kosila - {', '.join(classes)} - Gimnazija Vič",
565+
)
566+
510567

511568
def create_app():
512569
"""Application factory that accepts a configuration file from environment variable."""

API/gimvicurnik/templates/atom.xml

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919

2020
{% for model in entries %}
2121
<entry>
22-
<title>{{ model.description }}</title>
22+
{% if display_date and display_date_type == "daily" %}<title>{{ model.description }}, {{ model.date | date_format_daily }}</title>
23+
{% elif display_date and display_date_type == "weekly" %}<title>{{ model.description }}, {{ model.date | date_format_weekly }}</title>
24+
{% else %}<title>{{ model.description }}</title>
25+
{% endif %}
2326
<link href="{{ model.url if not config["shareToken"] else tokenize_url(model.url, config, token) }}" />
2427
<id>{{ model.url if not config["shareToken"] else tokenize_url(model.url, config, token) }}</id>
2528
<updated>{{ model.date.strftime("%Y-%m-%dT%H:%M:%S%z") }}</updated>

API/gimvicurnik/templates/rss.xml

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020

2121
{% for model in entries %}
2222
<item>
23-
<title>{{ model.description }}</title>
23+
{% if display_date and display_date_type == "daily" %}<title>{{ model.description }}, {{ model.date | date_format_daily }}</title>
24+
{% elif display_date and display_date_type == "weekly" %}<title>{{ model.description }}, {{ model.date | date_format_weekly }}</title>
25+
{% else %}<title>{{ model.description }}</title>
26+
{% endif %}
2427
<link>{{ model.url if not config["shareToken"] else tokenize_url(model.url, config, token) }}</link>
2528
<guid>{{ model.url if not config["shareToken"] else tokenize_url(model.url, config, token) }}</guid>
2629
<pubDate>{{ model.date.strftime("%a, %d %b %Y %H:%M:%S %Z") }}</pubDate>

API/gimvicurnik/utils/ical.py

+48-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def datecount(start_date, i):
1919

2020

2121
@with_span(op="generate")
22-
def create_calendar(details, timetables, hours, name, timetable=True, substitutions=True):
22+
def create_school_calendar(details, timetables, hours, name, timetable=True, substitutions=True):
2323
logger = logging.getLogger(__name__)
2424

2525
calendar = Calendar()
@@ -93,7 +93,6 @@ def create_calendar(details, timetables, hours, name, timetable=True, substituti
9393
logger.info("Preparing iCalendar event", extra={"type": "substitution", "source": subject})
9494

9595
event = Event()
96-
9796
event.add("dtstamp", datetime.now())
9897
event.add("CATEGORIES", vText("SUBSTITUTION"))
9998
event.add("COLOR", vText("darkred"))
@@ -132,5 +131,51 @@ def create_calendar(details, timetables, hours, name, timetable=True, substituti
132131

133132
response = make_response(calendar.to_ical().decode("utf-8").replace("\\", ""))
134133
response.headers["Content-Disposition"] = "attachment; filename=calendar.ics"
135-
response.headers["Content-Type"] = "text/calendar"
134+
response.headers["Content-Type"] = "text/calendar; charset=utf-8"
135+
return response
136+
137+
138+
@with_span(op="generate")
139+
def create_schedule_calendar(query, name):
140+
logger = logging.getLogger(__name__)
141+
142+
calendar = Calendar()
143+
calendar.add("prodid", "gimvicurnik")
144+
calendar.add("version", "2.0")
145+
calendar.add("X-WR-TIMEZONE", "Europe/Ljubljana")
146+
calendar.add("X-WR-CALNAME", name)
147+
calendar.add("X-WR-CALDESC", name)
148+
calendar.add("NAME", name)
149+
calendar.add("X-PUBLISHED-TTL", vDuration(timedelta(hours=12)))
150+
calendar.add("REFRESH-INTERVAL", vDuration(timedelta(hours=12)))
151+
152+
for model in query:
153+
with start_span(op="event") as span:
154+
span.set_tag("event.type", "lunch-schedule")
155+
span.set_tag("event.date", model.date)
156+
span.set_tag("event.time", model.time)
157+
span.set_data("event.source", model)
158+
159+
logger.info("Preparing iCalendar event", extra={"type": "lunch-schedule", "source": model})
160+
161+
event = Event()
162+
event.add("dtstamp", datetime.now())
163+
event.add("CATEGORIES", vText("LUNCH"))
164+
event.add("COLOR", vText("darkblue"))
165+
event.add(
166+
"UID",
167+
sha256((str(model.date) + str(model.time) + str(model.class_.name) + str(model.location)).encode()).hexdigest(),
168+
)
169+
170+
event.add("summary", "Kosilo")
171+
event.add("description", model.notes or "")
172+
event.add("location", vText(model.location))
173+
event.add("dtstart", datetime.combine(model.date, model.time))
174+
event.add("dtend", datetime.combine(model.date, model.time) + timedelta(minutes=15))
175+
176+
calendar.add_component(event)
177+
178+
response = make_response(calendar.to_ical().decode("utf-8").replace("\\", ""))
179+
response.headers["Content-Disposition"] = "attachment; filename=calendar.ics"
180+
response.headers["Content-Type"] = "text/calendar; charset=utf-8"
136181
return response

website/src/views/Subscribe.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
<url-display label="Okrožnice" :value="`${vueAppApi}/feeds/circulars.atom`"></url-display>
66
<url-display label="Nadomeščanja" :value="`${vueAppApi}/feeds/substitutions.atom`"></url-display>
77
<url-display label="Jedilniki" :value="`${vueAppApi}/feeds/menus.atom`"></url-display>
8-
<url-display label="Razporedi kosil" :value="`${vueAppApi}/feeds/schedules.atom`"></url-display>
8+
<url-display label="Razporedi delitve kosila" :value="`${vueAppApi}/feeds/schedules.atom`"></url-display>
99
</div>
1010

1111
<div class="pt-6" ref="calendarLinks">
1212
<h2 class="text-h5 pb-4">Koledar</h2>
1313
<url-display label="Urnik & Nadomeščanja" :value="`${vueAppApi}/calendar/combined/${selectedEntity}`"></url-display>
1414
<url-display label="Urnik" :value="`${vueAppApi}/calendar/timetable/${selectedEntity}`"></url-display>
1515
<url-display label="Nadomeščanja" :value="`${vueAppApi}/calendar/substitutions/${selectedEntity}`"></url-display>
16+
<url-display label="Razporedi delitve kosila" :value="`${vueAppApi}/calendar/schedules/${selectedEntity}`"></url-display>
1617
</div>
1718
</div>
1819
</template>

0 commit comments

Comments
 (0)