-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathlayout.py
More file actions
249 lines (201 loc) · 7.87 KB
/
layout.py
File metadata and controls
249 lines (201 loc) · 7.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
from __future__ import annotations
import isodate
import numbers
import sedate
from datetime import datetime
from functools import cached_property
from functools import lru_cache
from onegov.core import utils
from onegov.core.templates import PageTemplate
from onegov.core.utils import format_date, format_number, number_symbols
from pytz import timezone
from typing import overload, Any, TypeVar, TYPE_CHECKING
if TYPE_CHECKING:
from chameleon import PageTemplateFile
from collections.abc import Callable, Collection, Iterable, Iterator
from datetime import date
from decimal import Decimal
from .framework import Framework
from .request import CoreRequest
from .templates import MacrosLookup, TemplateLoader
_T = TypeVar('_T')
class Layout:
""" Contains useful methods related to rendering pages in html. Think of it
as an API that you can rely on in your templates.
The idea is to provide basic layout functions here, if they are usful for
any kind of html application. You should then extend the core layout
classes with your own.
"""
#: The timezone is currently fixed to 'Europe/Zurich' since all our
#: business with onegov is done here. Once the need arises, we should
#: lookup the timezone from the IP of the user, or use a javascript
#: library that sets the timezone for the user session.
#:
#: There's also going to be the case where we want the timezone set
#: specifically for a certain layout (say a reservation of a room, where
#: the room's timezone is relevant). This is why this setting should
#: remain close to the layout, and not necessarily close to the request.
timezone = timezone('Europe/Zurich')
#: Just like the timezone, these values are fixed for Switzerland now,
#: though the non-numerical information is actually translated.
#: Format:
#
#: http://www.unicode.org/reports/tr35/tr35-39/tr35-dates.html
#: #Date_Format_Patterns
#: Skeleton Patterns:
#
#: http://cldr.unicode.org/translation/date-time-patterns
#:
#: Classes inheriting from :class:`Layout` may add their own formats, as
#: long as they end in ``_format``. For example::
#:
#: class MyLayout(Layout):
#: my_format = 'dd.MMMM'
#: my_skeleton_format = 'skeleton:yMMM'
#:
#: MyLayout().format_date(dt, 'my')
#:
#: XXX this is not yet i18n and could be done better
time_format = 'HH:mm'
date_format = 'dd.MM.yyyy'
datetime_format = 'dd.MM.yyyy HH:mm'
date_long_format = 'dd. MMMM yyyy'
datetime_long_format = 'd. MMMM yyyy HH:mm'
weekday_long_format = 'EEEE'
weekday_short_format = 'E'
month_long_format = 'MMMM'
custom_body_attributes: dict[str, Any]
custom_html_attributes: dict[str, Any]
def __init__(self, model: Any, request: CoreRequest):
self.model = model
self.request = request
self.custom_body_attributes = {}
self.custom_html_attributes = {
'data-version': self.request.app.version
}
if request.app.sentry_dsn:
self.custom_html_attributes[
'data-sentry-dsn'
] = request.app.sentry_dsn
@cached_property
def app(self) -> Framework:
""" Returns the application behind the request. """
return self.request.app
@overload
def batched(
self,
iterable: Iterable[_T],
batch_size: int,
container_factory: type[tuple] = ... # type:ignore[type-arg]
) -> Iterator[tuple[_T, ...]]: ...
@overload
def batched(
self,
iterable: Iterable[_T],
batch_size: int,
container_factory: type[list] # type:ignore[type-arg]
) -> Iterator[list[_T]]: ...
# NOTE: If there were higher order TypeVars, we could properly infer
# the type of the Container, for now we just add overloads for
# two of the most common container_factories
@overload
def batched(
self,
iterable: Iterable[_T],
batch_size: int,
container_factory: Callable[[Iterator[_T]], Collection[_T]]
) -> Iterator[Collection[_T]]: ...
def batched(
self,
iterable: Iterable[_T],
batch_size: int,
container_factory: Callable[[Iterator[_T]], Collection[_T]] = tuple
) -> Iterator[Collection[_T]]:
""" See :func:`onegov.core.utils.batched`. """
return utils.batched(
iterable,
batch_size,
container_factory
)
@property
def csrf_token(self) -> str:
""" Returns a csrf token for use with DELETE links (forms do their
own thing automatically).
"""
return self.request.csrf_token
def csrf_protected_url(self, url: str) -> str:
""" Adds a csrf token to the given url. """
return self.request.csrf_protected_url(url)
def format_date(self, dt: datetime | date | None, format: str) -> str:
fmt = getattr(self, f'{format}_format', format)
return format_date(dt, fmt, self.request.locale, self.timezone)
def isodate(self, date: datetime) -> str:
""" Returns the given date in the ISO 8601 format. """
return datetime.isoformat(date)
def parse_isodate(self, string: str) -> datetime:
""" Returns the given ISO 8601 string as datetime. """
return isodate.parse_datetime(string)
@staticmethod
@lru_cache(maxsize=8)
def number_symbols(locale: str) -> tuple[str, str]:
return number_symbols(locale)
def format_number(
self,
number: numbers.Number | Decimal | float | str | None,
decimal_places: int | None = None,
padding: str = ''
) -> str:
return format_number(
number, decimal_places, padding, self.request.locale)
@property
def view_name(self) -> str | None:
""" Returns the view name of the current view, or None if it is the
default view.
Note: This relies on morepath internals and is experimental in nature!
"""
return self.request.unconsumed and self.request.unconsumed[-1] or None
def today(self) -> date:
return self.now().date()
def now(self) -> datetime:
return sedate.to_timezone(sedate.utcnow(), self.timezone)
class ChameleonLayout(Layout):
""" Extends the base layout class with methods related to chameleon
template rendering.
This class assumes the existance of two templates:
- layout.pt -> Contains the page skeleton with headers, body and so on.
- macros.pt -> Contains chameleon macros.
"""
@cached_property
def template_loader(self) -> TemplateLoader:
""" Returns the chameleon template loader. """
return self.request.template_loader
@cached_property
def base(self) -> PageTemplateFile:
""" Returns the layout, which defines the base layout of all pages.
See ``templates/layout.pt``.
"""
return self.template_loader['layout.pt']
@cached_property
def macros(self) -> MacrosLookup:
""" Returns the macros, which offer often used html constructs.
See ``templates/macros.pt``.
"""
return self.template_loader.macros
@cached_property
def elements(self) -> PageTemplate | PageTemplateFile:
""" The templates used by the elements. Overwrite this with your
own ``templates/elements.pt`` if neccessary.
"""
try:
return self.template_loader['elements.pt']
except ValueError:
return PageTemplate(
"""<xml xmlns="http://www.w3.org/1999/xhtml">
<metal:b define-macro="link">
<a tal:attributes="e.attrs">${e.text or ''}</a>
</metal:b>
<metal:b define-macro="img">
<img tal:attributes="e.attrs" />
</metal:b>
</xml>"""
)