21 """Fragments are parts of a page which can be reused across different
22 pages. Think headers and footers, navigation bars, widgets,
and such.
26 'rid': (
'INT',
'NOT NULL PRIMARY KEY',),
28 'content': (
'TRANSLATION',
'NOT NULL'),
29 'markdown': (
'TRANSLATION',
"NOT NULL DEFAULT ''"),
30 'contentfunc': (
'TEXT',
"NOT NULL DEFAULT ''",),
31 'translations': (
'NEWLINELIST',
"NOT NULL DEFAULT ''",),
32 'flags': (
'INT',
'NOT NULL DEFAULT 0',),
35 (
'unique', (
'path',)),
37 _dbdisplay = [
'path',
'content']
47 """Returns the text representation of this fragment in the current
51 for t
in self.translations:
53 appname, translationname = t.split(
'/')
55 g.page.jsfiles.append(f
'/{appname}/translations/{langloaded}/{translationname}.js')
56 except Exception
as e:
57 print(
'!!!\tProblem loading translation ', t, e)
59 if not self.flags & PAGE_DONT_CACHE:
60 result = g.cache.Load(
'pagefragment_' + self.path +
'_' + g.session.lang)
63 return result.decode(
'utf-8')
71 exec(self.contentfunc)
72 except Exception
as e:
73 content.append(
'<pre>' + traceback.format_exc() +
'</pre>')
75 result =
'\n'.join(content).encode(
'utf-16',
'surrogatepass').decode(
'utf-16')
77 if not self.flags & PAGE_DONT_CACHE:
78 g.cache.Store(
'pagefragment_' + self.path +
'_' + g.session.lang, result.encode(
'utf-8'))
84 """Represents a webpage in the system. Note that database pages can
85 contain a variety of page fragments in addition to different languages,
86 so they are quite versatile
in what they can do.
88 requiredgroups are a list of group names that are needed to view
91 headerid, contentid,
and the rest are used to load page fragments
92 into various sections.
94 path
is the URL
for the page such
as /index.html
96 pyfile
is the module
and name of a python function which will be
97 called
with the
global object g
and add content to the page
98 by returning a string.
100 template
is the template file used to create the page. It
's
101 unused as of now since we only use one standard template.
103 jsfiles
is a list of URLs to load JavaScript files. They can be
104 local
or remote. Note that the system will automatically load a
105 JavaScript file of the same path
and name. Thus,
if you create
106 a page at /newapp/testing.html, the system will
try to find
and load
107 /newapp/testing.js without you having to do anything.
109 cssfiles
is the same but
for CSS files.
111 translations are system translation files to automatically load
112 for use
in the page. They will also be available to any JavaScript
113 files included
in the system.
115 title, content,
and markdown are all translation fields, meaning
116 that you can have completely separate content
for each language
117 at the same URL. markdown text will be automatically converted
118 by the Python markdown library
and stored
in content.
120 contentfunc
is pure python code run
in the context of the
121 page.Render() function
and passed to the exec() function. Yes,
122 I
'm quite aware of the dangers of dynamic code evaluation, but
123 honestly, if you have someone running around randomly inserting
124 code into your database, you already have much bigger problems
125 than a page function. Look at the entry
for /logout.html to see
126 a simple
and convenient use of this field.
128 wallpaper
is the URL to a file which will automatically fill
129 the background of the page. It
's a quick way to give different
130 pages distinctive feels.
132 flags can contain PAGE_DONT_CACHE, which is self-explanatory,
and
133 PAGE_LOAD_HOOKS. Hooks are JavaScript files
with the same name
134 as this page
in other apps which can add functionality to this
135 app
's pages. See /system/manageusers.html for an example of this.
139 'rid': (
'INT',
'NOT NULL PRIMARY KEY',),
140 'flags': (
'INT',
'NOT NULL DEFAULT 0',),
141 'requiredgroups': (
'NEWLINELIST',
"NOT NULL DEFAULT ''",),
143 'headerid': (
'INT',
'NOT NULL DEFAULT 0',),
144 'contentid': (
'INT',
'NOT NULL DEFAULT 0',),
145 'footerid': (
'INT',
'NOT NULL DEFAULT 0',),
146 'leftbarid': (
'INT',
'NOT NULL DEFAULT 0',),
147 'topbarid': (
'INT',
'NOT NULL DEFAULT 0',),
148 'rightbarid': (
'INT',
'NOT NULL DEFAULT 0',),
149 'bottombarid': (
'INT',
'NOT NULL DEFAULT 0',),
152 'pyfile': (
'TEXT',
"NOT NULL DEFAULT ''",),
153 'template': (
'TEXT',
"NOT NULL DEFAULT ''",),
154 'jsfiles': (
'NEWLINELIST',
"NOT NULL DEFAULT ''",),
155 'cssfiles': (
'NEWLINELIST',
"NOT NULL DEFAULT ''",),
156 'translations': (
'NEWLINELIST',
"NOT NULL DEFAULT ''",),
160 'contentfunc': (
'TEXT',
"NOT NULL DEFAULT ''",),
161 'markdown': (
'TRANSLATION',
"NOT NULL DEFAULT ''"),
162 'wallpaper': (
'TEXT',
"NOT NULL DEFAULT ''",),
165 (
'unique', (
'path',)),
167 _dbdisplay = [
'path',
'title',
'content',
'cssfiles',
'jsfiles',
'translations',
'headerid',
'footerid']
174G.EditPageFragment = function(fragmentid)
176 if (fragmentid && fragmentid !=
'0')
178 G.fragmentid = fragmentid;
184 model:
'system_pagefragment',
189 G.EditPageFragmentForm(d.data);
194 G.EditPageFragmentForm({});
198G.EditPageFragmentForm = function(fragmentobj)
200 let flags = fragmentobj.flags ? fragmentobj.flags : 0;
204 [
'rid',
'hidden',
'',
'',
'',
''],
205 [
'path',
'text',
'',
'Path',
'',
'required'],
206 [
'content',
'translation',
'',
'Content',
'',
''],
207 [
'contentfunc',
'multitext',
'',
'Content Function',
'',
''],
208 [
'flags',
'bitflags', flags,
'Flags',
210 [
"Don't Cache",
'dontcache', 0x01, flags & 0x01 ?
'on' :
'unset']
214 function(formdiv, data, resultsdiv, e)
220 model:
'system_pagefragment',
225 P.HTML(resultsdiv,
'<div class="Pad50 greeng">' + T.allgood +
'</div>');
227 G.fragmentinput.value = d.data.rid;
229 if (e.target.classList.contains(
'SaveAndContinueButton'))
231 P.HTML(
'.EditFragmentAreaResults',
'<div class="Center Pad25 greenb">' + T.allgood +
'</div>');
238 P.RemoveFullScreen();
246 cancelfunc: function() { P.RemoveFullScreen(); },
247 enabletranslation: 1,
248 formdiv:
'.EditFragmentArea',
250 <a
class=
"Color5" onclick=
"G.FragmentNew()">New</a>
251 <a
class=
"Color4" onclick=
"G.FragmentNone()">
None</a>`,
258G.FragmentNew = function(start)
260 G.EditPageFragmentForm({});
263G.FragmentNone = function(start)
265 G.fragmentinput.value =
'';
266 P.RemoveFullScreen();
269G.ListFragments = function(start)
271 if (start == undefined)
273 start = G.fragmentstart;
276 G.fragmentstart = start;
278 let filter = E(
'.FragmentFilter').value;
281 '.EditFragmentTiles',
285 model:
'system_pagefragment',
286 condition: filter ? `WHERE path LIKE
'%${EscapeSQL(filter)}%'` :
'',
291 function(d, resultsdiv)
295 G.fragmentobjs = data;
296 G.fragmentcount = d.count;
300 for (let i = 0, l = data.length; i < l; i++)
303 <div
class=
"Color3 Pad50 Raised FragmentTile" data-objid=
"${data[i].rid}">${data[i].path}</div>
307 P.HTML(resultsdiv, ls);
309 P.HTML(
'.EditFragmentTilesPageBar', P.PageBar(G.fragmentcount, G.fragmentstart, 20, G.fragmentobjs.length,
'G.ListFragments'));
314G.displayfuncs[
'system_page'] = function(type, fields, item, ls, modellinks, data)
316 let modelid = G.modelids[
'system_page'];
317 let modelinfo = G.modelfields[
'system_page'];
321 ls.push(`<div
class=
"blueg Center">${item[
'rid']}</div>`);
324 ls.push(`<th
">${item['rid']}</th>`);
327 for (let j = 1, m = fields.length; j < m; j++)
332 let valuetype = modelinfo[k][0];
336 if (k ==
'content' || k ==
'title')
338 ls.push(
'<div>' + StripTags(P.BestTranslation(value)) +
'</div>');
339 }
else if (valuetype ==
'JSON')
341 ls.push(
'<pre>' + JSON.stringify(value, null, 2) +
'</pre>');
344 ls.push(
'<div>' + value +
'</div>');
348 if (k ==
'content' || k ==
'title')
350 ls.push(
'<td>' + StripTags(P.BestTranslation(value)) +
'</td>');
351 }
else if (valuetype ==
'JSON')
353 ls.push(
'<td><pre>' + JSON.stringify(value, null, 2) +
'</pre></td>');
356 ls.push(
'<td>' + value +
'</td>');
362G.customeditfuncs[
'system_page'] = function(obj, editfields)
364 let flagsrow = E(
'.DBObjFormForm .flagsRow');
368 let value = obj.flags ? obj.flags : 0;
371 '.DBObjFormForm .flagsRow',
372 `<div
class=
"BitFlags flagsFlags"
374 data-value=
"${value}">
379 '.DBObjFormForm .flagsFlags',
381 [
"Don't Cache",
'dontcache', 0x01, value & 0x01 ?
'on' :
'unset'],
382 [
"Load Hooks",
'loadhooks', 0x02, value & 0x02 ?
'on' :
'unset']
386 ontoggle: function(el, flagname, newstate)
388 let flagsel = E(
'.DBObjFormForm .flagsFlags');
390 flagsel.dataset.value = (newstate ==
'on')
391 ? parseInt(flagsel.dataset.value) | parseInt(el.dataset.value)
392 : parseInt(flagsel.dataset.value) & (~parseInt(el.dataset.value));
399 '.DBObjFormForm input[type=number]',
403 let targetname = e.target.getAttribute(
'name');
405 if (targetname.endsWith(
'id') && targetname !=
'rid')
410 `<h2
class=
"Center">Edit Fragment</h2>
411 <div
class=
"ButtonBar EditFragmentBar">
412 <input type=
"text" class=
"FragmentFilter">
413 <a
class=
"Color1" onclick=
"G.ListFragments()">Search</a>
415 <div
class=
"FlexGrid20 Gap50 EditFragmentTiles"></div>
416 <div
class=
"EditFragmentTilesPageBar"></div>
417 <div
class=
"EditFragmentArea"></div>`
421 '.EditFragmentTiles',
425 let tile = P.TargetClass(e,
'.FragmentTile');
430 G.EditPageFragment(tile.dataset.objid);
434 G.fragmentinput = e.target;
436 G.EditPageFragment(e.target.value);
450 """Converts a stylesheet URL into the full link tag.
456 return f
'<link rel="stylesheet" type="text/css" href="{url}" />'
460 """Converts a stylesheet URL into the full script tag.
463 return f
'<script src="{url}"></script>'
467 """Returns a string containing the full HTML of the page.
475 if self.translations:
476 for t
in self.translations:
478 appname, translationname = t.split(
'/')
480 self.
jsfiles.insert(0, f
'/{appname}/translations/{langloaded}/{translationname}.js')
481 except Exception
as e:
482 print(
'!!!\tProblem loading translation ', t, e)
484 lang = g.session.lang
486 topbar = g.db.Load(system_pagefragment, self.topbarid)
if self.topbarid
else ''
487 bottombar = g.db.Load(system_pagefragment, self.bottombarid)
if self.bottombarid
else ''
488 leftbar = g.db.Load(system_pagefragment, self.leftbarid)
if self.leftbarid
else ''
489 rightbar = g.db.Load(system_pagefragment, self.rightbarid)
if self.rightbarid
else ''
494 layout[
'top'] = topbar.size
497 layout[
'bottom'] = bottombar.size
500 layout[
'left'] = leftbar.size
503 layout[
'right'] = rightbar.size
510 content.append(g.db.Load(system_pagefragment, self.headerid).
Render(g))
516 content.append(g.db.Load(system_pagefragment, self.contentid).
Render(g))
520 exec(self.contentfunc)
521 except Exception
as e:
522 content.append(f
'<div class="Error">{e}</div>')
524 if not g.db.dbflags & DB_PRODUCTION:
525 content.append(
'<pre>' + traceback.format_exc() +
'</pre>')
530 parts = self.pyfile.split(
'.')
532 modfile =
'.'.join(parts[0:-1])
533 renderfunc = parts[-1]
535 mod = importlib.import_module(modfile)
537 content.append(mod.ROUTES[renderfunc](g))
538 except Exception
as e:
539 content.append(f
'<div class="Error">{e}</div>')
541 if not g.db.dbflags & DB_PRODUCTION:
542 content.append(
'<pre>' + traceback.format_exc() +
'</pre>')
545 content.append(g.db.Load(system_pagefragment, self.footerid).
Render(g))
547 content =
'\n'.join(content)
550 pagejsfile =
'static/' + g.apppath +
'/' + g.pagename[:-5] +
'.js'
553 if os.path.exists(pagejsfile):
554 pagejs = self.
RenderJS(f
'''/{g.apppath}/{g.pagename[:-5]}.js''')
556 if g.siteconfig[
'production']:
557 cssfiles = [
'/system/all.min.css'] + self.
cssfiles
559 cssfiles = g.siteconfig[
'cssfiles'] + self.
cssfiles
561 cssfiles =
'\n'.join([self.
RenderCSS(x)
for x
in cssfiles])
563 if g.siteconfig[
'production']:
564 jsfiles = [
'/system/all.min.js'] + self.
jsfiles
566 jsfiles = g.siteconfig[
'jsfiles'] + self.
jsfiles
570 if self.flags & PAGE_LOAD_HOOKS:
571 parts = os.path.split(self.path)
574 appname, pagename = parts
577 appname = appname[1:]
579 hookfile = os.path.splitext(pagename)[0] +
'.js'
581 for dirname
in os.listdir(
'static'):
582 if dirname == appname:
585 if os.path.isdir(os.path.join(
'static', dirname)):
586 hookjs = os.path.join(
'static', dirname, hookfile)
588 if os.path.exists(hookjs):
589 hookjsfiles.append(
'/' + dirname +
'/' + hookfile)
591 hookjsfiles =
'\n'.join([self.
RenderJS(x)
for x
in hookjsfiles])
593 jsfiles =
'\n'.join([self.
RenderJS(x)
for x
in jsfiles])
598 wallpaper = f
"""class="BackgroundCover" style="background-image: url('/system/wallpapers/{self.wallpaper}.webp')" """
600 rendered = f
"""<!DOCTYPE html>
604 <meta charset="UTF-8">
605 <meta name=
"apple-mobile-web-app-capable" content=
"yes" />
606 <meta name=
"mobile-web-app-capable" content=
"yes" />
607 <meta name=
"viewport" content=
"width=device-width,initial-scale=1">
609 <link rel=
"manifest" href=
"/manifest.json">
611 <title>{title}</title>
614 // Preload translations
618 {self.
RenderJS(
'/system/translations/en/system.js')}
623 <link rel=
"stylesheet" type=
"text/css" href=
"/system/singlepageapp.css" />
624 <link id=
"ScreenSizeStylesheet" rel=
"stylesheet" type=
"text/css" href=
"/system/blank.css" />
630if (!document.addEventListener)
632 window.location =
'/outdated.{lang}.html';
637<div
class=
"ViewportGrid">
638 <div
class=
"TopBarGrid">{topbar.Render(g)
if topbar
else ''}</div>
639 <div
class=
"LeftBarGrid">{leftbar.Render(g)
if leftbar
else ''}</div>
640 <div
class=
"PageBodyGrid">
642 <pre
class=
"DebugMessages white"></pre>
644 <div
class=
"RightBarGrid">{rightbar.Render(g)
if rightbar
else ''}</div>
645 <div
class=
"BottomBarGrid">{bottombar.Render(g)
if bottombar
else ''}</div>
649 <div
class=
"Error FullScreen">This site uses advanced technologies
in order to provide a faster
and more efficient experience
for our visitors. Please enable JavaScript to use this site properly.</div>
652{self.
RenderJS(
'/system/translations/' + g.session.lang +
'/system.js')}
653{self.
RenderJS(
'/system/sessionvars')}
659P.AddHandler(
'pageload', function() {{ P.SetLayout({json.dumps(layout)}); }});
667 return rendered.encode(
'utf-16',
'surrogatepass').decode(
'utf-16')
Base class for all database models.
Throws an exception on blank values.
Represents a webpage in the system.
def Render(self, g)
Returns a string containing the full HTML of the page.
def RenderJS(self, url)
Converts a stylesheet URL into the full script tag.
def __init__(self)
Initialize all fields at creation like a good programmer should.
def RenderCSS(self, url)
Converts a stylesheet URL into the full link tag.
Fragments are parts of a page which can be reused across different pages.
def Render(self, g)
Returns the text representation of this fragment in the current language.
def __init__(self)
Initialize all fields at creation like a good programmer should.
def BestTranslation(translations, languages, defaultlang='en')
Returns the best translation found in a dict of different translations.
def LoadTranslation(g, app, translation, languages)
Load the translation into the global language dict in g.T.