Compare commits

...

3 Commits

Author SHA1 Message Date
ffa1ae346f Merge pull request 'mvc' (#1) from mvc into main
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Failing after 4m28s
Reviewed-on: #1
2024-12-11 23:59:51 +01:00
e4887345a5 Lets test 2024-12-11 23:56:30 +01:00
82228fdb27 Lets test 2024-12-11 23:56:15 +01:00
29 changed files with 647 additions and 151 deletions

103
app.py
View File

@@ -1,100 +1,5 @@
from fastapi import FastAPI, Request import uvicorn
from fastapi.staticfiles import StaticFiles from app.main import app
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
from contextlib import asynccontextmanager
import json
import os
from markdown_render import render_markdown_with_jinja
if __name__ == "__main__":
class App: uvicorn.run(app, host="0.0.0.0", port=8000, reload=True)
def __init__(self):
"""Initialize the FastAPI app."""
self.app = FastAPI(lifespan=self.lifespan)
self.templates = Jinja2Templates(directory="templates")
self.data = self.load_mock_data()
# Mount directories
self.app.mount("/data", StaticFiles(directory="data"), name="data")
self.app.mount("/static", StaticFiles(directory="static"), name="static")
# Add routes
self.add_routes()
# 1. Lifespan events
@asynccontextmanager
async def lifespan(self, app: FastAPI):
print("App startup: Processing Markdown files...")
self.process_markdown_files("./data", "./data") # Process all Markdown files
print("Markdown processing complete!")
yield # Allow the app to start
print("App shutdown: Cleanup complete.")
# 2. Load JSON data
def load_mock_data(self):
"""Load mock data from a JSON file."""
with open("mock_data.json") as file:
return json.load(file)
# 3. Markdown processing logic
@staticmethod
def process_markdown_files(input_dir: str, output_dir: str):
"""
Recursively process all Markdown files in the input directory,
render them to HTML, and save them in the output directory.
"""
for root, _, files in os.walk(input_dir):
for file in files:
if file.endswith(".md"):
input_file_path = os.path.join(root, file)
relative_path = os.path.relpath(input_file_path, input_dir)
output_file_path = os.path.join(output_dir, os.path.splitext(relative_path)[0] + ".html")
os.makedirs(os.path.dirname(output_file_path), exist_ok=True)
with open(input_file_path, "r", encoding="utf-8") as md_file:
markdown_content = md_file.read()
print(f"Processing: {input_file_path} -> {output_file_path}")
rendered_html = render_markdown_with_jinja(markdown_content)
with open(output_file_path, "w", encoding="utf-8") as html_file:
html_file.write(rendered_html)
# 4. Routes
def add_routes(self):
"""Add all routes to the FastAPI app."""
@self.app.get("/", response_class=HTMLResponse)
async def get_index(request: Request):
"""Index route."""
return self.templates.TemplateResponse(
"index.html",
{"request": request, "data": self.data, "page_title": "Forside", "author": "Henrik"},
)
@self.app.get("/category/{category_name}", response_class=HTMLResponse)
async def get_category(request: Request, category_name: str):
"""Category route."""
category = next((cat for cat in self.data["categories"] if cat["path"] == category_name), None)
if category:
category_file = f"data/{category_name}/index.html"
if os.path.exists(category_file):
with open(category_file) as file:
category_content = file.read()
return self.templates.TemplateResponse(
"category.html",
{
"request": request,
"data": self.data,
"page_title": category["name"],
"author": category["author"],
"content": category_content,
},
)
return HTMLResponse("Kategori ikke fundet", status_code=404)
# Create the app instance
app_instance = App()
app = app_instance.app

0
app/__init__.py Normal file
View File

View File

View File

@@ -0,0 +1,56 @@
import os
import json
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
class CategoryController:
def __init__(self):
"""Initialize the controller."""
self.router = APIRouter()
self.templates = Jinja2Templates(directory="templates")
self.data = self._load_mock_data()
self._add_routes()
def _load_mock_data(self):
"""Load mock data from a JSON file."""
with open("mock_data.json") as file:
return json.load(file)
def _add_routes(self):
"""Add routes to the router."""
self.router.add_api_route("/", self.get_index, methods=["GET"], response_class=HTMLResponse)
self.router.add_api_route(
"/category/{category_name}",
self.get_category,
methods=["GET"],
response_class=HTMLResponse,
)
async def get_index(self, request: Request):
"""Index route."""
return self.templates.TemplateResponse(
"index.html",
{"request": request, "data": self.data, "page_title": "Forside", "author": "Henrik"},
)
async def get_category(self, request: Request, category_name: str):
"""Category route."""
category = next((cat for cat in self.data["categories"] if cat["path"] == category_name), None)
if category:
category_file = f"data/{category_name}/index.html"
if os.path.exists(category_file):
with open(category_file) as file:
category_content = file.read()
return self.templates.TemplateResponse(
"category.html",
{
"request": request,
"data": self.data,
"page_title": category["name"],
"author": category["author"],
"content": category_content,
},
)
return HTMLResponse("Kategori ikke fundet", status_code=404)

View File

@@ -0,0 +1,65 @@
import os
import json
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, Response
from fastapi.templating import Jinja2Templates
class DynamicController:
def __init__(self, data_dir: str):
"""Initialize the dynamic controller."""
self.router = APIRouter()
self.templates = Jinja2Templates(directory="templates")
self.data_dir = data_dir
self.data = self._load_mock_data()
self._add_dynamic_routes()
def _load_mock_data(self):
"""Load mock data from a JSON file."""
with open("mock_data.json") as file:
return json.load(file)
def _add_dynamic_routes(self):
"""Scan data directory and create dynamic routes."""
for root, dirs, files in os.walk(self.data_dir):
for directory in dirs:
route_path = f"/{directory}" # Create route based on directory name
directory_path = os.path.join(root, directory)
# Register route dynamically
self.router.add_api_route(
route_path,
self._serve_dynamic_template(directory, directory_path),
methods=["GET"],
response_class=HTMLResponse,
)
def _serve_dynamic_template(self, route_name: str, directory_path: str):
"""Closure to serve templates for each route."""
async def route_handler(request: Request):
# Look for index.html or render fallback content
index_html = os.path.join(directory_path, "index.html")
if os.path.exists(index_html):
with open(index_html, "r", encoding="utf-8") as file:
content = file.read()
# Find the author for this route from preloaded data
for category in self.data.get("categories", []):
if category["path"] == route_name:
author_name = category["author"]
break
# Pass required data to the template
return self.templates.TemplateResponse(
"category.html",
{
"request": request,
"page_title": route_name.capitalize(),
"content": content,
"author": author_name,
"data": self.data, # Pass additional data if needed
},
)
# Fallback: Return a 404 if no content is found
return Response(f"No content found for {route_name}", status_code=404)
return route_handler

53
app/main.py Normal file
View File

@@ -0,0 +1,53 @@
from fastapi import FastAPI
from contextlib import asynccontextmanager
from fastapi.staticfiles import StaticFiles
from app.services.markdown_processor import MarkdownProcessor
from app.services.metadata_processor import MetadataProcessor
from app.controllers.dynamic_controller import DynamicController
from app.controllers.category_controller import CategoryController
class Application:
def __init__(self):
"""Initialize the FastAPI app and configure it."""
self.app = FastAPI(lifespan=self._lifespan_event)
self._setup_static_files()
self._include_routers()
@asynccontextmanager
async def _lifespan_event(self, app: FastAPI):
"""Lifespan event for startup and shutdown logic."""
print("App startup: Processing Markdown files...")
# Process Markdown files into HTML
processor = MarkdownProcessor(input_dir="./data", templates_dir="./templates")
processor.run()
# Generate dynamic JSON data
metadata_processor = MetadataProcessor(input_dir="./data", output_file="generated_data.json")
metadata_processor.generate_json()
print("Generated dynamic data file.")
print("Markdown processing complete!")
yield
print("App shutdown: Cleanup complete.")
def _setup_static_files(self):
"""Mount static file directories."""
self.app.mount("/data", StaticFiles(directory="data"), name="data")
self.app.mount("/static", StaticFiles(directory="static"), name="static")
def _include_routers(self):
"""Include all route controllers."""
category_controller = CategoryController()
dynamic_controller = DynamicController("./data")
self.app.include_router(category_controller.router)
self.app.include_router(dynamic_controller.router)
def get_app(self):
"""Return the FastAPI app instance."""
return self.app
application = Application()
app = application.get_app()

0
app/services/__init__.py Normal file
View File

View File

@@ -0,0 +1,87 @@
import os
from app.services.markdown_render import render_markdown_with_jinja # Your custom renderer
from jinja2 import Environment, FileSystemLoader
class MarkdownProcessor:
"""
A class to process Markdown files, extract metadata, and generate a single
'index.html' per category directory using a custom rendering engine.
"""
def __init__(self, input_dir: str, templates_dir: str):
"""
Initialize the MarkdownProcessor.
Args:
input_dir (str): Root directory containing category subdirectories.
templates_dir (str): Directory containing Jinja2 templates.
"""
self.input_dir = input_dir
self.env = Environment(loader=FileSystemLoader(templates_dir))
def _process_markdown_files_in_directory(self, directory_path: str) -> list:
"""
Process all Markdown files in a directory using Markdown and Jinja2 custom tags.
Args:
directory_path (str): Path to the category directory.
Returns:
list: A list of processed sections containing metadata and rendered content.
"""
sections = []
for file in sorted(os.listdir(directory_path)):
if file.endswith(".md"):
file_path = os.path.join(directory_path, file)
with open(file_path, "r", encoding="utf-8") as md_file:
markdown_content = md_file.read()
# Process Markdown and Jinja2
rendered_content, metadata = render_markdown_with_jinja(markdown_content)
# Append the section to the list
sections.append({
"name": metadata.get("title", "Untitled"),
"content": rendered_content,
"summary": metadata.get("summary", ""),
"author": metadata.get("author", "Unknown"),
})
return sections
def _generate_index_html(self, directory_path: str, sections: list, output_file: str):
"""
Generate the index.html file for a category using the combined sections.
Args:
directory_path (str): Path to the category directory.
sections (list): List of processed Markdown content and metadata.
output_file (str): Path to save the generated index.html.
"""
# Render the template with the combined sections
template = self.env.get_template("combined_template.html")
rendered_html = template.render(
title=os.path.basename(directory_path).capitalize(),
sections=sections
)
# Write the rendered HTML to index.html
os.makedirs(directory_path, exist_ok=True)
with open(output_file, "w", encoding="utf-8") as output:
output.write(rendered_html)
print(f"Generated: {output_file}")
def run(self):
"""
Run the Markdown processing workflow: one 'index.html' per category.
"""
for root, dirs, _ in os.walk(self.input_dir):
for directory in dirs:
category_path = os.path.join(root, directory)
output_file = os.path.join(category_path, "index.html")
# Process all Markdown files in the current category directory
sections = self._process_markdown_files_in_directory(category_path)
if sections:
self._generate_index_html(category_path, sections, output_file)

View File

@@ -1,6 +1,7 @@
from jinja2 import Environment, FileSystemLoader
import markdown import markdown
from jinja2 import Environment, DictLoader
# Define Jinja2 custom functions
def img_left_overlay(src): def img_left_overlay(src):
"""Render an image with overlay.""" """Render an image with overlay."""
return f''' return f'''
@@ -35,9 +36,9 @@ def warning(content):
</div> </div>
''' '''
def create_jinja_environment(template_dir="templates"): def create_jinja_environment():
"""Set up Jinja2 environment and register custom components.""" """Set up Jinja2 environment and register custom components."""
env = Environment(loader=FileSystemLoader(template_dir)) env = Environment(loader=DictLoader({"base_template": "{{ content | safe }}"}))
env.globals.update({ env.globals.update({
"img_left_overlay": img_left_overlay, "img_left_overlay": img_left_overlay,
"box": box, "box": box,
@@ -46,14 +47,24 @@ def create_jinja_environment(template_dir="templates"):
}) })
return env return env
def render_markdown_with_jinja(markdown_content, template_data=None): def render_markdown_with_jinja(markdown_content: str):
"""Process Markdown first, then inject Jinja2 components.""" """
# Step 1: Convert Markdown to HTML Convert Markdown to HTML and apply Jinja2 rendering for custom tags.
md_html = markdown.markdown(markdown_content, extensions=['extra', 'nl2br'])
# Step 2: Use Jinja2 to inject components into the already-rendered HTML Args:
markdown_content (str): Raw Markdown content.
Returns:
tuple: Rendered HTML content and metadata as a dictionary.
"""
# Step 1: Convert Markdown to HTML and extract metadata
md = markdown.Markdown(extensions=["extra", "nl2br", "meta"])
html_content = md.convert(markdown_content)
metadata = {key: " ".join(value) for key, value in md.Meta.items()} if md.Meta else {}
# Step 2: Render the HTML with Jinja2 to apply custom tags
env = create_jinja_environment() env = create_jinja_environment()
template = env.from_string(md_html) template = env.get_template("base_template")
rendered_html = template.render(template_data or {}) final_html = template.render(content=html_content)
return rendered_html return final_html, metadata

View File

@@ -0,0 +1,83 @@
import os
import markdown
import json
from typing import List, Dict
class MetadataProcessor:
"""
A class to scan Markdown files, extract front matter metadata,
and generate a structured JSON file.
"""
def __init__(self, input_dir: str, output_file: str):
"""
Initialize the MetadataProcessor.
Args:
input_dir (str): Directory containing Markdown files.
output_file (str): Path to save the generated JSON file.
"""
self.input_dir = input_dir
self.output_file = output_file
self.data = {"categories": [], "favorites": []}
def _extract_metadata(self, file_path: str) -> Dict:
"""
Extract front matter metadata using the 'markdown' package.
Args:
file_path (str): Path to the Markdown file.
Returns:
dict: A dictionary containing the extracted metadata.
"""
with open(file_path, "r", encoding="utf-8") as file:
markdown_content = file.read()
# Initialize Markdown with meta extension
md = markdown.Markdown(extensions=["extra", "nl2br", "meta"])
md.convert(markdown_content)
# Metadata is stored in md.Meta as a dictionary of lists
meta = {key: " ".join(value) for key, value in md.Meta.items()} if md.Meta else {}
return meta
def _process_directory(self):
"""
Recursively scan the input directory for Markdown files
and extract metadata to build the JSON structure.
"""
for root, _, files in os.walk(self.input_dir):
for file in files:
if file.endswith(".md"):
file_path = os.path.join(root, file)
metadata = self._extract_metadata(file_path)
if metadata:
# Add to 'categories'
self.data["categories"].append({
"name": metadata.get("name", "Unknown"),
"path": os.path.relpath(root, self.input_dir).replace(os.sep, "/"),
"author": metadata.get("author", "Unknown")
})
# Add to 'favorites' if 'favorite' is true
if metadata.get("favorite") and metadata["favorite"].lower() == "true":
self.data["favorites"].append({
"name": metadata.get("name", "Unknown"),
"image": metadata.get("image", "images/default.jpg"),
"description": metadata.get("summary", "No description provided")
})
def generate_json(self):
"""
Generate the JSON structure and save it to the output file.
"""
self._process_directory()
# Save JSON to file
with open(self.output_file, "w", encoding="utf-8") as json_file:
json.dump(self.data, json_file, indent=4, ensure_ascii=False)
print(f"Generated JSON saved to {self.output_file}")

View File

@@ -1 +1,16 @@
<P> Lidt om Bolig </P> <main>
<section>
<h2>Untitled</h2>
<p><strong>Summary:</strong> Lidt omkring job situationen og hvordan det fungere</p>
<p><strong>Author:</strong> Henrik Jess</p>
<div>
<h1>Bolig Bolig Bolig Bolig - Hvor skal sengen placeres</h1>
<p>Nu bliver det spænde!</p>
<p>{{ note("Dette er stadig en test side") }}</p>
<p>{img-left-overlay: images/my-cat.png}</p>
</div>
<hr>
</section>
</main>

View File

@@ -1,10 +0,0 @@
<p>PortugalFAQ - Henriks og Erikas lille side</p>
<p></p>
<h1>BoligBolig Bolig Bolig - Hvor skal sengen placeres</h1>
<p>Nu bliver det spænde!</p>
<p>
<div class="note">
<p>Dette er stadig en test side</p>
</div>
</p>
<p></p>

View File

@@ -1,8 +1,12 @@
---
{% block title %}PortugalFAQ - Henriks og Erikas lille side{% endblock %} name: Generalt
description: Hvem, hvad og hvor
{% block content %} author: Henrik Jess
date: ons 11 dec 22:16:13 CET 2024
summary: Lidt omkring job situationen og hvordan det fungere
favorite: true
image: images/pic07.jpg
---
# Bolig Bolig Bolig Bolig - Hvor skal sengen placeres # Bolig Bolig Bolig Bolig - Hvor skal sengen placeres
@@ -10,4 +14,4 @@ Nu bliver det spænde!
{{ note("Dette er stadig en test side") }} {{ note("Dette er stadig en test side") }}
{% endblock %} {img-left-overlay: images/my-cat.png}

View File

@@ -1,7 +0,0 @@
<h1>Lidt mere info om job</h1>
<p>Der skal langt mere tekst her</p>
<p>
<div class="note">
<p>Husk alpha side</p>
</div>
</p>

View File

@@ -1,3 +1,13 @@
---
name: Job
description: Hvem, hvad og hvor
author: Henrik Jess
date: ons 11 dec 22:16:13 CET 2024
summary: Lidt omkring job situationen og hvordan det fungere
favorite: true
image: images/pic04.jpg
---
# Lidt mere info om job # Lidt mere info om job
Der skal langt mere tekst her Der skal langt mere tekst her

View File

@@ -1 +1,15 @@
<P> Lidt om job </P> <main>
<section>
<h2>Untitled</h2>
<p><strong>Summary:</strong> Lidt omkring job situationen og hvordan det fungere</p>
<p><strong>Author:</strong> Henrik Jess</p>
<div>
<h1>Lidt mere info om job</h1>
<p>Der skal langt mere tekst her</p>
<p>{{ note("Husk alpha side") }}</p>
</div>
<hr>
</section>
</main>

15
data/kontor/index.html Normal file
View File

@@ -0,0 +1,15 @@
<main>
<section>
<h2>Untitled</h2>
<p><strong>Summary:</strong> Lad os snakke kontor fælleskaber</p>
<p><strong>Author:</strong> Henrik Jess</p>
<div>
<h1>Kontorfællesskab!</h1>
<p>Der skal langt mere tekst her</p>
<p>{{ note("Husk alpha side") }}</p>
</div>
<hr>
</section>
</main>

View File

@@ -0,0 +1,16 @@
---
name: Job
description: Kontorfælleskaber osv
author: Henrik Jess
date: today
summary: Lad os snakke kontor fælleskaber
favorite: false
image: images/pic05.jpg
---
# Kontorfællesskab!
Der skal langt mere tekst her
{{ note("Husk alpha side") }}

View File

@@ -1 +1,14 @@
<P> Lidt om skat </P> <main>
<section>
<h2>Untitled</h2>
<p><strong>Summary:</strong> Jeg er langt fra expert, men her er lidt hvad jeg har indsamlet omkring skat</p>
<p><strong>Author:</strong> Henrik Jess</p>
<div>
<h1>Skat! - Det skal jo også være sjovt og leve</h1>
<p>dette er mere tekst omkring skat</p>
</div>
<hr>
</section>
</main>

View File

@@ -0,0 +1,13 @@
---
name: Skat
description: SKAT,SKAT, SKAT - Det skal betales den slags
author: Henrik Jess
date: today
summary: Jeg er langt fra expert, men her er lidt hvad jeg har indsamlet omkring skat
favorite: false
image: images/pic07.jpg
---
# Skat! - Det skal jo også være sjovt og leve
dette er mere tekst omkring skat

View File

@@ -1 +1,16 @@
<P> Lidt om skole </P> <main>
<section>
<h2>Untitled</h2>
<p><strong>Summary:</strong> Nørj det er lidt spændende..</p>
<p><strong>Author:</strong> Erika Nielsen</p>
<div>
<h1>Skole start!</h1>
<p>dette er mere tekst omkring skole</p>
<h1>Skole efter lidt tid</h1>
<p>HA!</p>
</div>
<hr>
</section>
</main>

17
data/skole/skole.md Normal file
View File

@@ -0,0 +1,17 @@
---
name: Skole
description: Skole, ny kultur og menesker
author: Erika Nielsen
date: today
summary: Nørj det er lidt spændende..
favorite: false
image: images/pic07.jpg
---
# Skole start!
dette er mere tekst omkring skole
# Skole efter lidt tid
HA!

View File

@@ -1,5 +1,5 @@
import os import os
from markdown_render import render_markdown_with_jinja from app.services.markdown_render import render_markdown_with_jinja
def process_markdown_files(input_dir: str, output_dir: str): def process_markdown_files(input_dir: str, output_dir: str):
""" """

View File

@@ -0,0 +1,25 @@
import os
from app.services.markdown_render import render_markdown_with_jinja
def process_markdown_files(input_dir: str, output_dir: str):
"""
Recursively process all Markdown files in the input directory,
render them to HTML, and save them in the output directory.
"""
for root, _, files in os.walk(input_dir):
for file in files:
if file.endswith(".md"):
input_file_path = os.path.join(root, file)
relative_path = os.path.relpath(input_file_path, input_dir)
output_file_path = os.path.join(output_dir, os.path.splitext(relative_path)[0] + ".html")
os.makedirs(os.path.dirname(output_file_path), exist_ok=True)
with open(input_file_path, "r", encoding="utf-8") as md_file:
markdown_content = md_file.read()
print(f"Processing: {input_file_path} -> {output_file_path}")
rendered_html = render_markdown_with_jinja(markdown_content)
with open(output_file_path, "w", encoding="utf-8") as html_file:
html_file.write(rendered_html)

13
depricated/mock_data.json Normal file
View File

@@ -0,0 +1,13 @@
{
"categories": [
{"name": "SKAT", "path": "skat", "author": "Henrik"},
{"name": "Skole", "path": "skole", "author": "Erika"},
{"name": "Bolig", "path": "bolig", "author": "Henrik"},
{"name": "Job", "path": "job", "author": "Henrik"}
],
"favorites": [
{"name": "SKAT", "image": "images/pic07.jpg", "description": "Favorit Kategori"},
{"name": "Skole", "image": "images/pic08.jpg", "description": "Skole information"},
{"name": "Bolig", "image": "images/pic09.jpg", "description": "Bolig detaljer"}
]
}

View File

@@ -1,4 +1,4 @@
from markdown_render import MarkdownRenderer from app.services.markdown_render import MarkdownRenderer
# Initialize MarkdownRenderer # Initialize MarkdownRenderer
renderer = MarkdownRenderer() renderer = MarkdownRenderer()

41
generated_data.json Normal file
View File

@@ -0,0 +1,41 @@
{
"categories": [
{
"name": "Skole",
"path": "skole",
"author": "Erika Nielsen"
},
{
"name": "Generalt",
"path": "bolig",
"author": "Henrik Jess"
},
{
"name": "Job",
"path": "job",
"author": "Henrik Jess"
},
{
"name": "Skat",
"path": "skat",
"author": "Henrik Jess"
},
{
"name": "Job",
"path": "kontor",
"author": "Henrik Jess"
}
],
"favorites": [
{
"name": "Generalt",
"image": "images/pic07.jpg",
"description": "Lidt omkring job situationen og hvordan det fungere"
},
{
"name": "Job",
"image": "images/pic04.jpg",
"description": "Lidt omkring job situationen og hvordan det fungere"
}
]
}

View File

@@ -1,13 +1,41 @@
{ {
"categories": [ "categories": [
{"name": "SKAT", "path": "skat", "author": "Henrik"}, {
{"name": "Skole", "path": "skole", "author": "Erika"}, "name": "Skole",
{"name": "Bolig", "path": "bolig", "author": "Henrik"}, "path": "skole",
{"name": "Job", "path": "job", "author": "Henrik"} "author": "Erika Nielsen"
},
{
"name": "Generalt",
"path": "bolig",
"author": "Henrik Jess"
},
{
"name": "Job",
"path": "job",
"author": "Henrik Jess"
},
{
"name": "Skat",
"path": "skat",
"author": "Henrik Jess"
},
{
"name": "Job",
"path": "kontor",
"author": "Henrik Jess"
}
], ],
"favorites": [ "favorites": [
{"name": "SKAT", "image": "images/pic07.jpg", "description": "Favorit Kategori"}, {
{"name": "Skole", "image": "images/pic08.jpg", "description": "Skole information"}, "name": "Generalt",
{"name": "Bolig", "image": "images/pic09.jpg", "description": "Bolig detaljer"} "image": "images/pic07.jpg",
"description": "Lidt omkring job situationen og hvordan det fungere"
},
{
"name": "Job",
"image": "images/pic04.jpg",
"description": "Lidt omkring job situationen og hvordan det fungere"
}
] ]
} }

View File

@@ -0,0 +1,14 @@
<main>
{% for section in sections %}
<section>
<h2>{{ section.name }}</h2>
<p><strong>Summary:</strong> {{ section.summary }}</p>
<p><strong>Author:</strong> {{ section.author }}</p>
<div>
{{ section.content | safe }}
</div>
<hr>
</section>
{% endfor %}
</main>