feat(schedule): update view

This commit is contained in:
2026-04-16 23:05:41 +03:00
parent 29fa6435ce
commit a0e6f30e3b
11 changed files with 84 additions and 39 deletions

View File

@@ -89,6 +89,8 @@ app
ul.app-list { ul.app-list {
list-style: none; list-style: none;
padding-left: 0; padding-left: 0;
width: 30rem;
margin: auto;
} }
ul.app-list > li { ul.app-list > li {
@@ -104,9 +106,9 @@ ul.app-list > li > a {
} }
ul.app-list > li:hover { ul.app-list > li:hover {
border-color: blue; border-color: rgb(125, 125, 255);
} }
ul.app-list > li:hover > a { ul.app-list > li:hover > a {
color: blue; color: rgb(125, 125, 255);
} }

View File

@@ -44,11 +44,7 @@ def mount(app: FastAPI):
async def get_schedule_tag(request: AppRequest, tag: str, live: bool = False): async def get_schedule_tag(request: AppRequest, tag: str, live: bool = False):
tag_value = TagUtil.parse_tag(tag) tag_value = TagUtil.parse_tag(tag)
schedule_api = request.app.state.api.schedule schedule_api = request.app.state.api.schedule
channels = await schedule_api.get_channels() results = await schedule_api.get_all_schedules(tag_value.date)
responses = [
await schedule_api.get_channel_schedule(channel, tag_value.date)
for channel in channels
]
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, request=request,
name="schedule.html", name="schedule.html",
@@ -56,9 +52,8 @@ def mount(app: FastAPI):
"version": __version__, "version": __version__,
"tag_util": TagUtil, "tag_util": TagUtil,
"datetime": datetime, "datetime": datetime,
"channels": channels, "response": results[0],
"response": responses[0], "responses": results,
"responses": responses,
"live": live, "live": live,
}, },
) )

View File

@@ -1,18 +1,25 @@
tr { table.schedule-table {
width: 60rem;
margin: auto;
table-layout: auto
}
table.schedule-table tr {
border-bottom: 1px solid lightgray; border-bottom: 1px solid lightgray;
} }
td { table.schedule-table td {
text-align: left; text-align: left;
} }
tr.live { table.schedule-table tr.live {
font-weight: bold; font-weight: bold;
} }
.title { table.schedule-table .title {
margin-top: 0.5rem; margin-top: 0.5rem;
font-style: italic; font-style: italic;
font-weight: bold; font-weight: bold;
font-size: 120%; font-size: 120%;
background-color: lightgray;
} }

View File

@@ -22,7 +22,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<table> <table class="schedule-table">
<thead> <thead>
<tr> <tr>
<td></td> <td></td>

View File

@@ -23,7 +23,7 @@
{% block content %} {% block content %}
<div> <div>
<table class="{{'live' if live else ''}}"> <table class="schedule-table {{'live' if live else ''}}">
<thead> <thead>
<tr> <tr>
<td></td> <td></td>
@@ -35,9 +35,9 @@
{% for response in responses %} {% for response in responses %}
{% set values = (response.values|selectattr('live') if live else response.values)|list %} {% set values = (response.values|selectattr('live') if live else response.values)|list %}
{% if values|length > 0 %} {% if values|length > 0 %}
<tr> <tr class="title">
<td colspan="3"> <td colspan="3">
<div class="title">{{response.channel.name}}</div> <div>{{response.channel.name}}</div>
</td> </td>
<td></td> <td></td>
<td></td> <td></td>

View File

@@ -18,13 +18,20 @@ CHANNELS_MAP: dict[ChannelId, str] = {
ChannelId.MATCH_FUTBOL_3: "match-futbol-3-797", ChannelId.MATCH_FUTBOL_3: "match-futbol-3-797",
ChannelId.MATCH_STRANA: "match-strana-1356", ChannelId.MATCH_STRANA: "match-strana-1356",
ChannelId.MATCH_PLANETA: "match-planeta-1177", ChannelId.MATCH_PLANETA: "match-planeta-1177",
ChannelId.EUROSPORT: "eurosport-677", # ChannelId.EUROSPORT: "eurosport-677",
ChannelId.EUROSPORT_2: "eurosport-2-720", # ChannelId.EUROSPORT_2: "eurosport-2-720",
ChannelId.START: "start-103", ChannelId.START: "start-103",
} }
HEADERS: dict[str, str] = { HEADERS: dict[str, str] = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept": (
"text/html,"
"application/xhtml+xml,"
"application/xml;q=0.9,"
"image/avif,image/webp,"
"image/apng,*/*;q=0.8,"
"application/signed-exchange;v=b3;q=0.9"
),
"Accept-Encoding": "gzip, deflate, br", "Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9", "Accept-Language": "en-US,en;q=0.9",
"Connection": "keep-alive", "Connection": "keep-alive",
@@ -37,7 +44,12 @@ HEADERS: dict[str, str] = {
"Sec-Fetch-Site": "none", "Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1", "Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1", "Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.133 Safari/537.36", "User-Agent": (
"Mozilla/5.0 (X11; Linux x86_64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/100.0.4896.133 "
"Safari/537.36"
),
} }

View File

@@ -1,13 +1,19 @@
from typing import Generic from typing import Generic, NamedTuple
from gallery.util import TimeUnit from gallery.util import TimeUnit
from .api import API, Api from .api import API, Api
class CachePreset(NamedTuple):
ttl: int = TimeUnit.HOUR
alias: str = "redis"
DEFAULT_CACHE_PRESET = CachePreset()
class CachedApi(Api, Generic[API]): class CachedApi(Api, Generic[API]):
CACHE_TTL: int = TimeUnit.HOUR
CACHE_ALIAS: str = "redis"
CACHE_KEY: str CACHE_KEY: str
def __init__(self, api: API): def __init__(self, api: API):

View File

@@ -1,3 +1,4 @@
import asyncio
import datetime import datetime
from ..api import Api from ..api import Api
@@ -5,6 +6,8 @@ from .model import ChannelId, Schedule
class ScheduleApi(Api): class ScheduleApi(Api):
INTERVAL: float = 0.5
async def get_channels(self) -> list[ChannelId]: async def get_channels(self) -> list[ChannelId]:
raise NotImplementedError raise NotImplementedError
@@ -12,3 +15,14 @@ class ScheduleApi(Api):
self, channel_id: ChannelId, date: datetime.date self, channel_id: ChannelId, date: datetime.date
) -> Schedule: ) -> Schedule:
raise NotImplementedError raise NotImplementedError
async def get_all_schedules(self, date: datetime.date) -> list[Schedule]:
channels = await self.get_channels()
results = []
for channel in channels:
results.append(
await self.get_channel_schedule(channel_id=channel, date=date)
)
if self.INTERVAL > 0:
await asyncio.sleep(self.INTERVAL)
return results

View File

@@ -2,21 +2,21 @@ import datetime
from aiocache import cached from aiocache import cached
from gallery.sketch.cached import CachedApi from gallery.sketch.cached import CachedApi, CachePreset
from gallery.util import TimeUnit from gallery.util import TimeUnit
from .api import ScheduleApi from .api import ScheduleApi
from .model import ChannelId, Schedule from .model import ChannelId, Schedule
CACHE_PRESET = CachePreset(ttl=TimeUnit.HOUR * 6)
class CachedScheduleApi(ScheduleApi, CachedApi[ScheduleApi]): class CachedScheduleApi(ScheduleApi, CachedApi[ScheduleApi]):
CACHE_KEY = "schedule" CACHE_KEY = "schedule"
CACHE_TTL = TimeUnit.HOUR * 6
@cached( @cached(
key_builder=lambda fun, self: f"api.{self.CACHE_KEY}.{self.provider}.channels", key_builder=lambda fun, self: f"api.{self.CACHE_KEY}.{self.provider}.channels",
alias=CachedApi.CACHE_ALIAS, **CACHE_PRESET._asdict(),
ttl=CachedApi.CACHE_TTL,
) )
async def get_channels(self) -> list[ChannelId]: async def get_channels(self) -> list[ChannelId]:
return await self._api.get_channels() return await self._api.get_channels()
@@ -25,10 +25,18 @@ class CachedScheduleApi(ScheduleApi, CachedApi[ScheduleApi]):
key_builder=lambda fun, self, channel_id, date: ( key_builder=lambda fun, self, channel_id, date: (
f"api.{self.CACHE_KEY}.{self.provider}.channel.{channel_id}.{date}" f"api.{self.CACHE_KEY}.{self.provider}.channel.{channel_id}.{date}"
), ),
alias=CachedApi.CACHE_ALIAS, **CACHE_PRESET._asdict(),
ttl=CachedApi.CACHE_TTL,
) )
async def get_channel_schedule( async def get_channel_schedule(
self, channel_id: ChannelId, date: datetime.date self, channel_id: ChannelId, date: datetime.date
) -> Schedule: ) -> Schedule:
return await self._api.get_channel_schedule(channel_id, date) return await self._api.get_channel_schedule(channel_id, date)
@cached(
key_builder=lambda fun, self, date: (
f"api.{self.CACHE_KEY}.{self.provider}.all.{date}"
),
**CACHE_PRESET._asdict(),
)
async def get_all_schedules(self, date: datetime.date) -> list[Schedule]:
return await self._api.get_all_schedules(date)

View File

@@ -2,19 +2,20 @@ import datetime
from aiocache import cached from aiocache import cached
from gallery.sketch.cached import CachedApi from gallery.sketch.cached import DEFAULT_CACHE_PRESET, CachedApi
from .api import WeatherApi from .api import WeatherApi
from .model import WeatherResponse from .model import WeatherResponse
CACHE_PRESET = DEFAULT_CACHE_PRESET
class CachedWeatherApi(WeatherApi, CachedApi[WeatherApi]): class CachedWeatherApi(WeatherApi, CachedApi[WeatherApi]):
CACHE_KEY = "weather" CACHE_KEY = "weather"
@cached( @cached(
key_builder=lambda fun, self: f"api.{self.CACHE_KEY}.{self.provider}.locations", key_builder=lambda fun, self: f"api.{self.CACHE_KEY}.{self.provider}.locations",
alias=CachedApi.CACHE_ALIAS, **CACHE_PRESET._asdict(),
ttl=CachedApi.CACHE_TTL,
) )
async def get_locations(self) -> list[str]: async def get_locations(self) -> list[str]:
return await self._api.get_locations() return await self._api.get_locations()
@@ -23,8 +24,7 @@ class CachedWeatherApi(WeatherApi, CachedApi[WeatherApi]):
key_builder=lambda fun, self, location_id, date: ( key_builder=lambda fun, self, location_id, date: (
f"api.{self.CACHE_KEY}.{self.provider}.day.{location_id}.{date}" f"api.{self.CACHE_KEY}.{self.provider}.day.{location_id}.{date}"
), ),
alias=CachedApi.CACHE_ALIAS, **CACHE_PRESET._asdict(),
ttl=CachedApi.CACHE_TTL,
) )
async def get_day(self, location_id: str, date: datetime.date) -> WeatherResponse: async def get_day(self, location_id: str, date: datetime.date) -> WeatherResponse:
return await self._api.get_day(location_id, date) return await self._api.get_day(location_id, date)
@@ -33,8 +33,7 @@ class CachedWeatherApi(WeatherApi, CachedApi[WeatherApi]):
key_builder=lambda fun, self, location_id, date: ( key_builder=lambda fun, self, location_id, date: (
f"api.{self.CACHE_KEY}.{self.provider}.day.{location_id}.{date}" f"api.{self.CACHE_KEY}.{self.provider}.day.{location_id}.{date}"
), ),
alias=CachedApi.CACHE_ALIAS, **CACHE_PRESET._asdict(),
ttl=CachedApi.CACHE_TTL,
) )
async def get_days(self, location_id: str, days: int) -> WeatherResponse: async def get_days(self, location_id: str, days: int) -> WeatherResponse:
return await self._api.get_days(location_id, days) return await self._api.get_days(location_id, days)

View File

@@ -2,7 +2,7 @@ import datetime
import pytest import pytest
from gallery.painting.yandextv.api import YandexTvApi from gallery.painting.yandextv.api import CHANNELS_MAP, YandexTvApi
from gallery.painting.yandextv.mock import YANDEXTV_MOCK_DATA from gallery.painting.yandextv.mock import YANDEXTV_MOCK_DATA
from gallery.sketch.schedule.model import ChannelId from gallery.sketch.schedule.model import ChannelId
@@ -15,6 +15,8 @@ def yandextv_api_fixture() -> YandexTvApi:
api = YandexTvApi() api = YandexTvApi()
api.SOURCE = MockSource() api.SOURCE = MockSource()
CHANNELS_MAP[ChannelId("test")] = "test"
return api return api