Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More

Content Management System (CMS)

The noi2 demo project shows Lino as a Content Management System.

It includes a page with usage examples of the [file] and [gallery] commands. To see it, say go noi2 followed by pm runserver and then point your browser to http://127.0.0.1:8000/p/9

>>> from lino_book.projects.noi2.startup import *
>>> mp = settings.SITE.plugins.memo.parser
>>> rt.models.uploads.Upload.objects.get(description__startswith="Murder")
Upload #13 ('Murder on the orient express cover')
>>> print(mp.parse("[file 13] Some text."))
...
<a href="/admin/#/api/uploads/Uploads/13" target="_blank"><img
src="/media/thumbs/uploads/2024/05/MurderontheOrientExpress.jpg"
title="Murder on the orient express cover"/></a> Some text.
>>> print(mp.parse('[file 13 title="My caption"] Some text.'))
...
<a href="/admin/#/api/uploads/Uploads/13" target="_blank"><img
src="/media/thumbs/uploads/2024/05/MurderontheOrientExpress.jpg"
title="My caption"/></a> Some text.
>>> print(mp.parse('[file 13 style="max-height: 20ex; max-width: 100%; float: right; padding: 4px;" title="My caption"] Some text.'))
...
<a href="/admin/#/api/uploads/Uploads/13" target="_blank"><img
src="/media/thumbs/uploads/2024/05/MurderontheOrientExpress.jpg"
style="max-height: 20ex; max-width: 100%; float: right; padding: 4px;" title="My
caption"/></a> Some text.
>>> print(mp.parse('[file 13 style="max-height: 20ex; max-width: 100%; float: right; padding: 4px;" title="My caption"] Some text.'))
...
<a href="/admin/#/api/uploads/Uploads/13" target="_blank"><img
src="/media/thumbs/uploads/2024/05/MurderontheOrientExpress.jpg"
style="max-height: 20ex; max-width: 100%; float: right; padding: 4px;" title="My
caption"/></a> Some text.
>>> print(mp.parse("[file 13 right|thumb|My caption] Some text."))
...
[ERROR Invalid format name 'right' (allowed names are ('thumb', 'tiny', 'wide',
'solo', 'duo', 'trio')). in '[file 13 right|thumb|My caption]' at position
0-32] Some text.
>>> print(mp.parse('[file 13 style="max-height: 20ex; max-width: 100%; float: right; padding: 4px;"] Some text.'))
...
<a href="/admin/#/api/uploads/Uploads/13" target="_blank"><img
src="/media/thumbs/uploads/2024/05/MurderontheOrientExpress.jpg"
style="max-height: 20ex; max-width: 100%; float: right; padding: 4px;" title="Murder
on the orient express cover"/></a> Some text.

The only difference between thumb and tiny is the size of the image. For thumb it has a height of 10em and for tiny the height is 5em.

We don’t specify the width in order to let the browser compute it. We specify the height and not the width because we don’t care about whether the image is landscape or portrait.

>>> print(mp.parse('[file 13 style="max-height: 15ex; max-width: 100%; padding: 4px;" title="My caption"] Some text.'))
...
<a href="/admin/#/api/uploads/Uploads/13" target="_blank"><img
src="/media/thumbs/uploads/2024/05/MurderontheOrientExpress.jpg"
style="max-height: 15ex; max-width: 100%; padding: 4px;" title="My caption"/></a>
Some text.
>>> print(mp.parse('[file 13 style="max-height: 30ex; max-width: 100%; padding: 4px;" title="A wide image"] Some text.'))
...
<a href="/admin/#/api/uploads/Uploads/13" target="_blank"><img
src="/media/thumbs/uploads/2024/05/MurderontheOrientExpress.jpg"
style="max-height: 30ex; max-width: 100%; padding: 4px;" title="A wide image"/></a>
Some text.

Spaces around the pipe character don’t count:

>>> print(mp.parse('[file 13 style="max-height: 30ex; max-width: 100%; padding: 4px;" title="A wide image"] Some text.'))
...
<a href="/admin/#/api/uploads/Uploads/13" target="_blank"><img
src="/media/thumbs/uploads/2024/05/MurderontheOrientExpress.jpg"
style="max-height: 30ex; max-width: 100%; padding: 4px;" title="A wide image"/></a>
Some text.

The syntax is given by the rstgen.sphinxconf.sigal_image.parse_image_spec() function.

“image URL” versus “download URL”

>>> obj = uploads.Upload.objects.get(pk=13)
>>> mf = obj.get_media_file()
>>> print(mf.get_download_url())
/media/uploads/2024/05/MurderontheOrientExpress.jpg
>>> print(mf.get_image_url())
/media/thumbs/uploads/2024/05/MurderontheOrientExpress.jpg
>>> # obj = uploads.Upload.objects.get(pk=18)
>>> obj = uploads.Upload.objects.get(description__startswith="History")
>>> obj
Upload #20 ('History of PDF')
>>> mf = obj.get_media_file()
>>> print(mf.get_download_url())
/media/uploads/2024/05/History_of_PDF.pdf
>>> print(mf.get_image_url())
/media/thumbs/uploads/2024/05/History_of_PDF.pdf.png

Don’t read me

The following request had caused a traceback:

>>> res = test_client.get("/s/1")
>>> txt = beautiful_soup(res.content.decode()).text
>>> "Private collection by Luc Saffre" in txt
True
>>> res = test_client.get("/b/1")

But let’s extend above test to systematically loop over all publisher locations and GET each item:

>>> for loc, dv in dd.plugins.publisher.locations:
...     for obj in dv.model.objects.all():
...         url = "/{}/{}".format(loc, obj.pk)
...         # print(dv, url)
...         if obj.is_public():
...             res = test_client.get(url)
...             if res.status_code not in {200, 302}:
...                 print(f"{url} failed with {res.status_code}")
...                 # print(f"({res.content.decode()})")
...         # elif res.status_code != 404:
...         #     print(f"{url} should return 404 but returned {res.status_code}")
>>> [obj.pk for obj in blogs.LatestEntries.create_request()]
[2, 3, 4, 5, 1]
>>> obj = blogs.Entry.objects.get(id=2)
>>> obj.publisher_tree
Tree #1 ('index')
>>> obj.publisher_tree.private
False