Skip to content

Commit

Permalink
Tree structure (#34)
Browse files Browse the repository at this point in the history
Tree structure 

Allow to store pages in folders and allow paths in urls.
Add url_for_slug() and url_for_slug_path() jinja2 fonctions.

---------

Co-authored-by: Clément <[email protected]>
  • Loading branch information
ClmntBcqt and Clément authored Jul 12, 2024
1 parent 7318a06 commit 1b67b84
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 25 deletions.
3 changes: 1 addition & 2 deletions content/pages/a_page.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,4 @@ template_engine jinja2

<h2>Page 1</h2>

<p>Go to <a href="{{ url('page', args=['page2']) }}">page 2</a> </p>

<p>Go to <a href="{{ url_for_slug('page2') }}">page 2</a> </p>
2 changes: 1 addition & 1 deletion content/pages/another_page.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ template_engine jinja2

<h2> Page 2 </h2>

<p>Go to <a href="{{ url('page', args=['page1']) }}">page 1</a> </p>
<p>Go to <a href="{{ url_for_slug('page1') }}">page 1</a> </p>
2 changes: 1 addition & 1 deletion content/templates/jinja2/blocks/en/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
CTA_LABEL="See pricing",
CTA_TARGET="",
CTA_URL="#pricing",
CHANGE_LANG_URL="fr-index",
CHANGE_LANG_URL="/fr-index",
CHANGE_LANG_FLAG_URL="assets/img/lang-fr.svg",
CHANGE_LANG_ALT="Version française"
) }}
2 changes: 1 addition & 1 deletion content/templates/jinja2/blocks/fr/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
CTA_LABEL="Découvrir les tarifs",
CTA_TARGET="",
CTA_URL="#pricing",
CHANGE_LANG_URL="en-index",
CHANGE_LANG_URL="/en-index",
CHANGE_LANG_FLAG_URL="assets/img/lang-gb.svg",
CHANGE_LANG_ALT="English version"
) }}
2 changes: 1 addition & 1 deletion content/templates/jinja2/widgets/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<div class="navbar__container__right">
<a class="btn btn-sm btn-primary" href="{{ CTA_URL }}"
target="{{ CTA_TARGET }}">{{ CTA_LABEL }}</a>
<a href="{{ url('page', args=[CHANGE_LANG_URL]) }}">
<a href="{{ url_for_slug_path(CHANGE_LANG_URL) }}">
<img class="languageFlag" src="{{ static(CHANGE_LANG_FLAG_URL)}}" alt="{{ CHANGE_LANG_ALT }}"/>
</a>
</div>
Expand Down
83 changes: 81 additions & 2 deletions jssg/jinja2.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,94 @@

from jssg.templatetags.filter_opengraph_metadata import filter_opengraph_metadata

from jssg.models import Document
from django.conf import settings
from jssg.models import Page

import re

def url_for_slug(slug) :
"""
@param slug: the slug of the page to search
@return: the string of the url corresponding to the page
@error: raise an exception if the slug does not exist or it is not unique (eg same slug found in several folders)
>>> url_for_slug('index') # the slug exists and is unique
/en/index.html
>>> url_for_slug('index-duplicated') # the slug exists in several pages
Traceback (most recent call last):
...
Exception: slug 'index-duplicated' is not unique, found in : [pages/fr-index.md, pages/en-index.md] ; use url_for_slug_path()
>>> url_for_slug('index-removed') # the slug does not exists
Traceback (most recent call last):
...
Exception: slug 'index-removed' not found
"""

url = ""
pages_with_slug = []

for page in Page.load_glob(all=True) :
if page.slug == slug : # the page exists
if pages_with_slug == [] : # the slug has not been found yet
if page.rel_folder_path != '' :
url = "/" + page.rel_folder_path + "/" + page.slug + ".html"
else :
url = "/" + page.slug + ".html"
else : # the slug already exists
url = ""
pages_with_slug.append(str(page.path.relative_to(page.content_page_dir.parent)))

if url == "" and pages_with_slug != [] :
raise Exception("slug '%s' is not unique, found in : [%s] ; use url_for_slug_path()" % (slug, ", ".join(pages_with_slug)))
elif url == "" :
raise Exception("slug '%s' not found" % slug)
return url

def url_for_slug_path(url_path) :
"""
@param url_path: the url of the page to search (absolute path)
@return: the string of the slug url corresponding to the page
@error: raise an exception if the url is a dead link
>>> url_for_slug_path('/en/index') # the page exists
/en/index.html
>>> url_for_slug_path('/en/index-removed') # the page does not exist
Traceback (most recent call last):
...
Exception: page for '/en/index-removed' url not found (dead link)
>>> url_for_slug_path('folder/index')
Traceback (most recent call last):
...
Exception: url 'folder/index' is not valid ; correct urls are /<dir>/<slug> or /<slug>
"""
# Valid url are /<dir>/<slug>.html or /<slug>.html
# Example: if url_path is "/tmp/folder/subfolder/thefile.html", then slug will be "thefile" and the dir will be "tmp/folder/subfolder"
# Note : the dir does not start with '/' since the url parsed in url.py do not either
try :
_, dir, slug = re.findall(r"(^|^/([a-zA-Z0-9/-]+))/([a-zA-Z0-9-]+)$", url_path)[0]
except :
raise Exception("url '%s' is not valid ; correct urls are /<dir>/<slug> or /<slug>" % url_path)

# Verify that the page exists
for page in Page.load_glob(all=True) :
if page.slug == slug and page.rel_folder_path == dir :
return url_path + ".html"

raise Exception("page for '%s' url not found (dead link)" % url_path)

def environment(**options):
env = Environment(**options)
env.globals.update(
{
"static": static,
"url": reverse,
"url_for_slug": url_for_slug,
"url_for_slug_path" : url_for_slug_path
}
)
env.filters.update(
Expand Down
38 changes: 25 additions & 13 deletions jssg/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def load(cls, path: Path) -> "Document":

@classmethod
def load_glob(
cls, path: Optional[List[Path]] = None, glob: str = "*.md"
cls, path: Optional[List[Path]] = None, dir = "", glob: str = "*.md", all=False
) -> Iterator["Document"]:
"""Load multiple document.
Expand All @@ -212,7 +212,10 @@ def load_glob(

files = []
for p in path :
files += p.glob(glob)
if all :
files += (p / dir).rglob(glob)
else :
files += (p / dir).glob(glob)
# print(files)
return map(cls.load, files)

Expand All @@ -235,20 +238,29 @@ def __init__(self, content: str, **metadata) -> None:
except KeyError:
self.slug = slugify(self.title)

self.content_page_dir = self.path
while (self.content_page_dir not in self.BASE_DIR) :
self.content_page_dir = self.content_page_dir.parent

# page folder path relative to its content_page_dir
self.rel_folder_path = str(self.path.relative_to(self.content_page_dir).parent)
if self.rel_folder_path == '.' :
self.rel_folder_path = ''

@classmethod
def load_page_with_slug(cls, slug: str) -> "Page":
return next(filter(lambda p: p.slug == slug, cls.load_glob()))
def load_page_with_slug(cls, slug: str, dir : str) -> "Page":
return next(filter(lambda p: p.slug == slug, cls.load_glob(dir = dir)))

@classmethod
def load_glob(
cls, path: Optional[List[Path]] = None, glob: str = "*.md"
cls, path: Optional[List[Path]] = None, dir = "", glob: str = "*.md", all = False
) -> Iterator["Page"]:
"""Overridden only to make the static typing happy."""
return super().load_glob(path, glob)
return super().load_glob(path, dir, glob, all)

@classmethod
def get_pages(cls):
return ({"slug": p.slug} for p in Page.load_glob())
def get_pages(cls) :
return ({"slug": p.slug} if p.rel_folder_path == '' else {"dir": p.rel_folder_path, "slug" : p.slug} for p in Page.load_glob(all = True))


class Post(Page):
Expand All @@ -267,14 +279,14 @@ def __init__(self, content: str, **metadata) -> None:

@classmethod
def load_glob(
cls, path: Optional[List[Path]] = None, glob: str = "*.md"
cls, path: Optional[List[Path]] = None, dir = "", glob: str = "*.md", all = False
) -> Iterator["Post"]:
"""Overridden only to make the static typing happy."""
return super().load_glob(path, glob)

return super().load_glob(path, dir, glob, all)
@classmethod
def get_posts(cls):
return ({"slug": p.slug} for p in Post.load_glob())
def get_posts(cls) :
return ({"slug": p.slug} if p.rel_folder_path == '' else {"dir": p.rel_folder_path, "slug" : p.slug} for p in Post.load_glob(all = True))

class Sitemap :
BASE_DIR = settings.JFME_PAGES_DIRS + settings.JFME_POSTS_DIRS
Expand Down
20 changes: 18 additions & 2 deletions jssg/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,24 @@

from jssg import views
from jssg.models import Page, Post
from jssg import settings


# print([p for p in Page.get_pages()])

urlpatterns = [
distill_path(
"", views.IndexView.as_view(), name="index", distill_file="index.html"
),
distill_path(
"pages/<slug:slug>.html",
distill_path("atom.xml", views.PostFeedsView(), name="atom_feed"),
distill_re_path(
r'^(?!posts/)(?P<slug>[a-zA-Z0-9-]+).html$',
views.PageView.as_view(),
name="page",
distill_func=Page.get_pages,
),
distill_re_path(
r'^(?!posts/)(?P<dir>[a-zA-Z-/]+)/(?P<slug>[a-zA-Z0-9-]+).html$',
views.PageView.as_view(),
name="page",
distill_func=Page.get_pages,
Expand All @@ -35,6 +45,12 @@
name="post",
distill_func=Post.get_posts,
),
distill_path(
"posts/<path:dir>/<slug:slug>.html",
views.PostView.as_view(),
name="post",
distill_func=Post.get_posts,
),
distill_path(
"sitemap.xml",
views.SitemapView.as_view(),
Expand Down
7 changes: 5 additions & 2 deletions jssg/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,11 @@ class PageView(DetailView):
template_name = "page.html"

def get_object(self, queryset=None) -> Model:
print(self.kwargs["slug"])
model = self.model.load_page_with_slug(self.kwargs["slug"])
if "dir" not in self.kwargs.keys() :
self.kwargs["dir"] = ""
print("dir : " + self.kwargs["dir"])
print("slug : " + self.kwargs["slug"])
model = self.model.load_page_with_slug(self.kwargs["slug"], self.kwargs["dir"])
return model


Expand Down

0 comments on commit 1b67b84

Please sign in to comment.