platzky 0.1.19__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
platzky/plugin_loader.py CHANGED
@@ -1,42 +1,39 @@
1
+ import importlib.util
1
2
  import os
2
3
  import sys
3
- from os.path import dirname, abspath
4
- import importlib.util
5
- import pkgutil
6
-
7
-
8
- def get_selected_not_installed_plugins(enabled_plugins):
9
- plugins_root_dir = os.path.join(dirname(abspath(__file__)), 'plugins')
10
- plugins_dirs = set(os.listdir(plugins_root_dir))
11
-
12
- return enabled_plugins - plugins_dirs
4
+ from os.path import abspath, dirname
5
+
6
+
7
+ def find_plugin(plugin_name):
8
+ """Find plugin by name and return it as module.
9
+ :param plugin_name: name of plugin to find
10
+ :return: module of plugin
11
+ """
12
+ plugins_dir = os.path.join(dirname(abspath(__file__)), "plugins")
13
+ module_name = plugin_name.removesuffix(".py")
14
+ spec = importlib.util.spec_from_file_location(
15
+ module_name, os.path.join(plugins_dir, plugin_name, "entrypoint.py")
16
+ )
17
+ assert spec is not None
18
+ plugin = importlib.util.module_from_spec(spec)
19
+ sys.modules[module_name] = plugin
20
+ assert spec.loader is not None
21
+ spec.loader.exec_module(plugin)
22
+ return plugin
13
23
 
14
24
 
15
- def find_plugins(enabled_plugins):
16
- plugins_dir = os.path.join(dirname(abspath(__file__)), 'plugins')
17
- plugins = []
18
-
19
- if selected_not_enabled := get_selected_not_installed_plugins(enabled_plugins):
20
- raise Exception(f"Plugins {selected_not_enabled} has been selected in config, but has not been installed.")
21
-
22
- for plugin_dir in enabled_plugins:
23
- module_name = plugin_dir.removesuffix('.py')
24
- spec = importlib.util.spec_from_file_location(module_name,
25
- os.path.join(plugins_dir, plugin_dir, "entrypoint.py"))
26
- plugin = importlib.util.module_from_spec(spec)
27
- sys.modules[module_name] = plugin
28
- spec.loader.exec_module(plugin)
29
- plugins.append(plugin)
30
-
31
- for finder, name, ispkg in pkgutil.iter_modules():
32
- if name.startswith('platzky_'):
33
- plugins.append(importlib.import_module(name))
25
+ def plugify(app):
26
+ """Load plugins and run their entrypoints.
27
+ :param app: Flask app
28
+ :return: Flask app
29
+ """
34
30
 
35
- return plugins
31
+ plugins_data = app.db.get_plugins_data()
36
32
 
33
+ for plugin_data in plugins_data:
34
+ plugin_config = plugin_data["config"]
35
+ plugin_name = plugin_data["name"]
36
+ plugin = find_plugin(plugin_name)
37
+ plugin.process(app, plugin_config)
37
38
 
38
- def plugify(app):
39
- plugins = set(app.config["PLUGINS"])
40
- for plugin in find_plugins(plugins):
41
- plugin.process(app)
42
39
  return app
@@ -1,17 +1,21 @@
1
1
  from flask import redirect
2
- from functools import partial
3
2
  from gql import gql
3
+ from pydantic import BaseModel
4
4
 
5
5
 
6
- def json_get_redirections(self):
6
+ def json_db_get_redirections(self):
7
7
  return self.data.get("redirections", {})
8
8
 
9
9
 
10
- def google_get_redirections(self):
10
+ def json_file_db_get_redirections(self):
11
+ return json_db_get_redirections(self)
12
+
13
+
14
+ def google_json_db_get_redirections(self):
11
15
  return self.data.get("redirections", {})
12
16
 
13
17
 
14
- def graphql_get_redirections(self):
18
+ def graph_ql_db_get_redirections(self):
15
19
  redirections = gql(
16
20
  """
17
21
  query MyQuery{
@@ -22,25 +26,43 @@ def graphql_get_redirections(self):
22
26
  }
23
27
  """
24
28
  )
25
- return {x['source']:x['destination'] for x in self.client.execute(redirections)['redirections']}
29
+ return {
30
+ x["source"]: x["destination"]
31
+ for x in self.client.execute(redirections)["redirections"]
32
+ }
26
33
 
27
34
 
28
- def get_proper_redirections(db_type):
29
- redirections = {
30
- "json_file": json_get_redirections,
31
- "graph_ql": graphql_get_redirections,
32
- "google_json": google_get_redirections
35
+ class Redirection(BaseModel):
36
+ source: str
37
+ destiny: str
33
38
 
34
- }
35
- return redirections[db_type]
36
39
 
40
+ def parse_redirections(config: dict) -> list[Redirection]:
41
+ return [
42
+ Redirection(source=source, destiny=destiny)
43
+ for source, destiny in config.items()
44
+ ]
45
+
46
+
47
+ def setup_routes(app, redirections):
48
+ for redirection in redirections:
49
+ func = redirect_with_name(
50
+ redirection.destiny,
51
+ code=301,
52
+ name=f"{redirection.source}-{redirection.destiny}",
53
+ )
54
+ app.route(rule=redirection.source)(func)
55
+
56
+
57
+ def redirect_with_name(destiny, code, name):
58
+ def named_redirect(*args, **kwargs):
59
+ return redirect(destiny, code, *args, **kwargs)
60
+
61
+ named_redirect.__name__ = name
62
+ return named_redirect
37
63
 
38
- def process(app):
39
- app.db.get_redirections = get_proper_redirections(app.config["DB"]["TYPE"])
40
- redirects = app.db.get_redirections(app.db)
41
- for source, destiny in redirects.items():
42
- func = partial(redirect, destiny, code=301)
43
- func.__name__ = f"{source}-{destiny}"
44
- app.route(rule=source)(func)
45
64
 
65
+ def process(app, config: dict) -> object:
66
+ redirections = parse_redirections(config)
67
+ setup_routes(app, redirections)
46
68
  return app
@@ -1,10 +1,14 @@
1
1
  import smtplib
2
- from functools import partial
3
2
 
3
+ from pydantic import BaseModel, Field
4
4
 
5
- def send_mail(sender_email, password, smtp_server, port, receiver_email, subject, message):
6
- full_message = f'From: {sender_email}\nTo: {receiver_email}\nSubject: {subject}\n\n{message}'
7
5
 
6
+ def send_mail(
7
+ sender_email, password, smtp_server, port, receiver_email, subject, message
8
+ ):
9
+ full_message = (
10
+ f"From: {sender_email}\nTo: {receiver_email}\nSubject: {subject}\n\n{message}"
11
+ )
8
12
  server = smtplib.SMTP_SSL(smtp_server, port)
9
13
  server.ehlo()
10
14
  server.login(sender_email, password)
@@ -12,11 +16,28 @@ def send_mail(sender_email, password, smtp_server, port, receiver_email, subject
12
16
  server.close()
13
17
 
14
18
 
15
- def process(app):
16
- smtp_setup = app.config["SMTP"]
17
- app.sendmail = partial(send_mail,
18
- smtp_setup["ADDRESS"],
19
- smtp_setup["PASSWORD"],
20
- smtp_setup["SERVER"],
21
- smtp_setup["PORT"])
19
+ class SendMailConfig(BaseModel):
20
+ user: str = Field(alias="sender_email")
21
+ password: str = Field(alias="password")
22
+ server: str = Field(alias="smtp_server")
23
+ port: int = Field(alias="port")
24
+ receiver: str = Field(alias="receiver_email")
25
+ subject: str = Field(alias="subject")
26
+
27
+
28
+ def process(app, config):
29
+ plugin_config = SendMailConfig.model_validate(config)
30
+
31
+ def notify(message):
32
+ send_mail(
33
+ sender_email=plugin_config.user,
34
+ password=plugin_config.password,
35
+ smtp_server=plugin_config.server,
36
+ port=plugin_config.port,
37
+ receiver_email=plugin_config.receiver,
38
+ subject=plugin_config.subject,
39
+ message=message,
40
+ )
41
+
42
+ app.add_notifier(notify)
22
43
  return app
platzky/seo/seo.py CHANGED
@@ -1,27 +1,34 @@
1
+ import typing as t
1
2
  import urllib.parse
2
3
  from os.path import dirname
3
- from flask import request, render_template, make_response, Blueprint, current_app
4
+ from flask import Blueprint, current_app, make_response, render_template, request
4
5
 
5
6
 
6
- def create_seo_blueprint(db, config):
7
- url_prefix = config["SEO_PREFIX"]
8
- seo = Blueprint('seo', __name__, url_prefix=url_prefix, template_folder=f'{dirname(__file__)}/../templates')
9
- # secure_headers = SecureHeaders()
7
+ def create_seo_blueprint(db, config: dict[str, t.Any]):
8
+ seo = Blueprint(
9
+ "seo",
10
+ __name__,
11
+ url_prefix=config["SEO_PREFIX"],
12
+ template_folder=f"{dirname(__file__)}/../templates",
13
+ )
10
14
 
11
15
  @seo.route("/robots.txt")
12
16
  def robots():
13
- robots_response = render_template("robots.txt", domain=request.host, mimetype='text/plain')
17
+ robots_response = render_template(
18
+ "robots.txt", domain=request.host, mimetype="text/plain"
19
+ )
14
20
  response = make_response(robots_response)
15
21
  response.headers["Content-Type"] = "text/plain"
16
22
  return response
17
23
 
18
24
  @seo.route("/sitemap.xml")
19
25
  def main_sitemap():
20
- if domain_to_lang := config.get("DOMAIN_TO_LANG", None):
21
- domains_lang = domain_to_lang[request.host]
22
- return sitemap(domains_lang)
26
+ if domain_to_lang := config["DOMAIN_TO_LANG"]:
27
+ return sitemap(domain_to_lang[request.host])
23
28
  else:
24
- return sitemap(config.get("BABEL_TRANSLATION_DIRECTORIES")) #TODO should be based on localization not on config
29
+ return sitemap(
30
+ config.get("TRANSLATION_DIRECTORIES")
31
+ ) # TODO should be based on localization not on config
25
32
 
26
33
  def sitemap(lang):
27
34
  """
@@ -38,28 +45,31 @@ def create_seo_blueprint(db, config):
38
45
  static_urls = list()
39
46
  for rule in current_app.url_map.iter_rules():
40
47
  if not str(rule).startswith("/admin") and not str(rule).startswith("/user"):
41
- if "GET" in rule.methods and len(rule.arguments) == 0:
42
- url = {
43
- "loc": f"{host_base}{str(rule)}"
44
- }
48
+ if (
49
+ rule.methods is not None
50
+ and "GET" in rule.methods
51
+ and len(rule.arguments) == 0
52
+ ):
53
+ url = {"loc": f"{host_base}{str(rule)}"}
45
54
  static_urls.append(url)
46
55
 
47
56
  # Dynamic routes with dynamic content
48
57
  dynamic_urls = list()
49
58
  seo_posts = db.get_all_posts(lang)
50
59
  for post in seo_posts:
51
- slug = post['slug']
52
- datet = post['date'].split('T')[0]
53
- url = {
54
- "loc": f"{host_base}/{slug}",
55
- "lastmod": datet
56
- }
60
+ slug = post["slug"]
61
+ datet = post["date"].split("T")[0]
62
+ url = {"loc": f"{host_base}/{slug}", "lastmod": datet}
57
63
  dynamic_urls.append(url)
58
64
 
59
- statics = list({v['loc']: v for v in static_urls}.values())
60
- dynamics = list({v['loc']: v for v in dynamic_urls}.values())
61
- xml_sitemap = render_template("sitemap.xml", static_urls=statics, dynamic_urls=dynamics,
62
- host_base=host_base)
65
+ statics = list({v["loc"]: v for v in static_urls}.values())
66
+ dynamics = list({v["loc"]: v for v in dynamic_urls}.values())
67
+ xml_sitemap = render_template(
68
+ "sitemap.xml",
69
+ static_urls=statics,
70
+ dynamic_urls=dynamics,
71
+ host_base=host_base,
72
+ )
63
73
  response = make_response(xml_sitemap)
64
74
  response.headers["Content-Type"] = "application/xml"
65
75
  return response
platzky/static/blog.css CHANGED
@@ -81,29 +81,22 @@ img::-moz-selection {
81
81
  background: transparent;
82
82
  }
83
83
 
84
- #mainNav {
85
- # position: absolute;
86
- # border-bottom: 1px solid #e9ecef;
87
- # background-color: white;
88
- font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
89
- }
90
-
91
84
  #mainNav .navbar-brand {
92
- font-weight: 800;
85
+ font-weight: 1000;
93
86
  color: #343a40;
94
87
  }
95
88
 
96
89
  #mainNav .navbar-toggler {
97
- font-size: 12px;
90
+ font-size: 20px;
98
91
  font-weight: 800;
99
- padding: 13px;
92
+ padding: 10px;
100
93
  text-transform: uppercase;
101
94
  color: #343a40;
102
95
  }
103
96
 
104
97
  #mainNav .navbar-nav > li.nav-item > a {
105
- font-size: 12px;
106
- font-weight: 800;
98
+ font-size: 15px;
99
+ font-weight: 1000;
107
100
  letter-spacing: 1px;
108
101
  text-transform: uppercase;
109
102
  }
@@ -1,16 +1,110 @@
1
1
  <!doctype html>
2
2
  <html lang="{{ language }}">
3
3
  <head>
4
+ {% block head_meta %}
4
5
  {% include "head_meta.html" %}
6
+
7
+ <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
8
+ integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
9
+ crossorigin=""/>
10
+ <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css" />
11
+ <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css" />
12
+ <link rel="stylesheet" href="{{ font.url }}">
13
+ {% endblock %}
5
14
  <title>{% block title %}{% endblock %}</title>
6
15
  <meta name="description" content=" {% block description %} {% endblock %} ">
16
+ <style>
17
+ html,
18
+ body {
19
+ height: 100%;
20
+
21
+ }
22
+ body {
23
+ font-family: {{ font.name }}
24
+ }
25
+ .logo {
26
+ max-height: 6rem;
27
+ max-width: 50vw;
28
+ }
29
+ .header-row {
30
+ z-index: 1;
31
+ background-color: {{ primary_color }};
32
+ height: min-content;
33
+ display: flex;
34
+ justify-content: center;
35
+ align-items: center;
36
+ }
37
+ #main-row {
38
+ position: relative;
39
+ flex-grow: 1;
40
+ z-index: 0;
41
+ }
42
+
43
+ .left-panel-contents {
44
+ padding: 16px;
45
+ }
46
+
47
+ .left-panel {
48
+ background-color: {{ secondary_color }};
49
+ }
50
+
51
+ #left-panel {
52
+ background-color: {{ secondary_color }};
53
+ position: absolute;
54
+ height: 100%;
55
+ max-width: 80vw;
56
+ overflow-y: auto;
57
+ color: white;
58
+ }
59
+
60
+ .offcanvas-lg.offcanvas-start {
61
+ top: auto;
62
+ width: auto;
63
+ }
64
+
65
+ #filter-form form > div {
66
+ padding-bottom: 1rem;
67
+ }
68
+
69
+ #filter-form form div > span {
70
+ font-weight: bold;
71
+ padding-bottom: 0.5rem;
72
+ }
73
+
74
+ .dropdown-menu {
75
+ z-index: 1;
76
+ }
77
+ .btn {
78
+ padding: 0;
79
+ }
80
+
81
+ .btn-close {
82
+ opacity: 1;
83
+ }
84
+ i.fi {
85
+ margin: 0 0.5em;
86
+ }
87
+ </style>
7
88
  </head>
8
89
  <body>
9
- <div class="container" style="max-width:1080px; height:100%">
10
- {% include "body_meta.html" %}
11
- <nav class="navbar navbar-expand-lg navbar-light bg-light" id="mainNav">
12
- <div class="container-fluid">
13
- <a class="navbar-brand" href="/">{{ _(app_name) }}</a>
90
+ {% block body_meta %}
91
+ {% include "body_meta.html" %}
92
+ {% endblock %}
93
+ <div class="container-fluid d-flex flex-column h-100 g-0">
94
+ <div class="row header-row bg-light g-0">
95
+ <nav class="navbar navbar-expand-lg navbar-light p-3" id="mainNav">
96
+ {% if self.left_panel() %}
97
+ <button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#left-panel" aria-label="Toggle left panel">
98
+ <i class="fas fa-sliders-h"></i>
99
+ </button>
100
+ {% endif %}
101
+ <a class="navbar-brand" href="/">
102
+ {% if logo_url %}
103
+ <img src="{{ logo_url }}" class="logo" >
104
+ {% else %}
105
+ {{ _(app_name) }}
106
+ {% endif %}
107
+ </a>
14
108
  <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
15
109
  aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
16
110
  <i class="fas fa-bars"></i>
@@ -19,34 +113,55 @@
19
113
  <ul class="navbar-nav">
20
114
  {% for menu_item in menu_items %}
21
115
  <li class="nav-item">
22
- <a class="nav-link" href={{menu_item["url"]}} >{{_(menu_item["name"])}}</a>
116
+ <a class="nav-link" href={{menu_item.url}} >{{_(menu_item.name)}}</a>
23
117
  </li>
24
118
  {% endfor %}
25
119
  </ul>
26
120
  <ul class="navbar-nav ms-auto">
27
121
  <li class="nav-item dropdown">
28
- <a class="nav-link dropdown-toggle" href="#" id="languages-menu" role="button"
29
- data-bs-toggle="dropdown" aria-expanded="false">
30
- <i class=" {{ current_flag }} flag"></i></a>
31
- <ul class="dropdown-menu" aria-labelledby="languages-menu">
32
- {% for lg,items in languages.items() %}
33
- <li><a class="dropdown-item" href={{url_for('change_language', lang=lg, _external=True)}}>
34
- <i class="{{ items['flag'] }} flag"></i>{{ items['name'] }}</a></li>
35
- {% endfor %}
36
- </ul>
122
+ <div id="language-dropdown" class="btn-group">
123
+ <button type="button" class="nav-link dropdown-toggle btn btn-link" id="languages-menu" role="button"
124
+ data-bs-toggle="dropdown" aria-expanded="false">
125
+ <i class="fi fi-{{ current_flag }}"></i>
126
+ </button>
127
+ <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="languages-menu">
128
+ {% for lg,items in languages.items() %}
129
+ <a class="dropdown-item" href={{url_for('change_language', lang=lg, _external=True)}}>
130
+ <i class="fi fi-{{ items['flag'] }} "></i>{{ items['name'] }}</a>
131
+ {% endfor %}
132
+ </ul>
133
+ </div>
37
134
  </li>
38
135
  </ul>
39
136
  </div>
40
- </div>
41
- </nav>
42
- </div>
137
+ </nav>
138
+ </div>
139
+ {% block header %}{% endblock %}
43
140
 
44
- {% block header %}
45
- {% endblock %}
141
+ <div class="row g-0" id="main-row">
142
+ {% if self.left_panel() %}
143
+ <div class="col-lg-2 left-panel" >
144
+ <div id="left-panel" class="left-panel offcanvas-lg offcanvas-start" aria-modal="true" role="dialog">
145
+ <div class="offcanvas-body d-flex">
146
+ <div class="left-panel-contents">
147
+ {% block left_panel %}{% endblock %}
148
+ </div>
149
+ <div class="offcanvas-header justify-content-between align-items-start">
150
+ <button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas" data-bs-target="#left-panel" ></button>
151
+ </div>
46
152
 
153
+ </div>
154
+ </div>
47
155
 
48
- {% block content %}
49
- {% endblock %}
156
+ </div>
157
+ {% endif %}
158
+ <div class="h-100 {% if self.left_panel() %} col-lg-10 {% else %} col-lg-12 {% endif %}">
159
+ <main class="h-100">
160
+ {% block content %}{% endblock %}
161
+ </main>
162
+ </div>
163
+ </div>
50
164
 
165
+ </div>
51
166
  </body>
52
167
  </html>
@@ -9,12 +9,13 @@
9
9
  {% endblock %}
10
10
 
11
11
  {% block content %}
12
- <div class="container">
12
+ <div class="blog-contents mx-auto w-75">
13
13
  <h1>{% block title %}Blog {{ subtitle|default('', true) }} {% endblock %}</h1>
14
14
  {% for post in posts %}
15
+
15
16
  <div class="row align-items-center">
16
17
  <div class="col-3">
17
- <img src="{{ post.coverImage.image.url }}" alt="{{ post.coverImage.alternateText }}" class="img-thumbnail align-middle">
18
+ <img src="{{ post.coverImage.url }}" alt="{{ post.coverImage.alternateText }}" class="img-thumbnail align-middle">
18
19
  </div>
19
20
  <div class="col-8 mx-auto">
20
21
  <div class="post-preview">
@@ -1,24 +1,7 @@
1
- <!-- Google Tag Manager (noscript) -->
2
- <noscript>
3
- <iframe src="https://www.googletagmanager.com/ns.html?id=GTM-MRQ7FDB"
4
- height="0" width="0" style="display:none;visibility:hidden"></iframe>
5
- </noscript>
6
- <!-- End Google Tag Manager (noscript) -->
1
+ {{ dynamic_body | safe }}
2
+
7
3
  <!-- Optional JavaScript -->
8
4
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
9
- <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
10
- integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
11
- crossorigin="anonymous"></script>
12
- <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"
13
- integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut"
14
- crossorigin="anonymous"></script>
15
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
16
- integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
17
- crossorigin="anonymous"></script>
18
5
 
19
- <!-- MDB -->
20
- <script
21
- type="text/javascript"
22
- src="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/3.0.0/mdb.min.js"
23
- ></script>
24
- <!-- End MDB -->
6
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
7
+ <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin="">
@@ -3,7 +3,7 @@
3
3
 
4
4
  <channel>
5
5
  <title>{{_(app_name)}}</title>
6
- <link>{{ url_for("blog.index", _external=True) }}</link>
6
+ <link>{{ url_for("blog.all_posts", _external=True) }}</link>
7
7
  <description>{{_("Everything about mailings and newsletters")}}</description>
8
8
  {% for post in posts %}
9
9
  <item>
@@ -7,32 +7,22 @@
7
7
  <link rel="canonical" href="{{ request.base_url }}"/>
8
8
 
9
9
  <!-- Bootstrap CSS -->
10
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
11
- integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
10
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
12
11
 
13
- <!-- MDB -->
14
- <link href="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/3.1.0/mdb.min.css" rel="stylesheet">
15
- <!-- End MDB -->
12
+ <!-- Flag icons -->
13
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@6.6.6/css/flag-icons.min.css"/>
14
+ <!-- End Flag icons -->
16
15
 
17
16
  <!-- Font Awesome -->
18
17
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" rel="stylesheet">
19
18
  <!-- End Font Awesome -->
20
19
 
21
-
22
20
  <!-- Custom fonts for this template -->
23
21
  <link href='https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
24
22
  <link
25
23
  href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800'
26
24
  rel='stylesheet' type='text/css'>
27
25
 
28
- <!-- Google Tag Manager -->
29
- <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
30
- new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
31
- j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
32
- 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
33
- })(window,document,'script','dataLayer','GTM-MRQ7FDB');
34
- </script>
35
- <!-- End Google Tag Manager -->
36
-
26
+ {{ dynamic_head | safe }}
37
27
 
38
28
  <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='blog.css') }}">
@@ -5,7 +5,7 @@
5
5
  {% endblock %}
6
6
 
7
7
  {% block content %}
8
- <header class="masthead" style="background-image: url({{post.coverImage.image.url}})">
8
+ <header class="masthead" style="background-image: url({{post.coverImage.url}})">
9
9
  <div class="overlay"></div>
10
10
  <div class="container">
11
11
  <div class="row">
@@ -23,7 +23,7 @@
23
23
  <div class="container">
24
24
  <div class="row">
25
25
  <div class="col-lg-8 col-md-10 mx-auto post-content">
26
- {{post.contentInRichText.markdown | markdown}}
26
+ {{post.contentInMarkdown | markdown}}
27
27
  </div>
28
28
  </div>
29
29
  </div>