platzky 0.1.19__tar.gz → 0.2.0__tar.gz

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.
Files changed (53) hide show
  1. {platzky-0.1.19 → platzky-0.2.0}/PKG-INFO +16 -20
  2. platzky-0.2.0/README.md +16 -0
  3. platzky-0.2.0/platzky/blog/__init__.py +0 -0
  4. platzky-0.2.0/platzky/blog/blog.py +93 -0
  5. platzky-0.2.0/platzky/blog/comment_form.py +15 -0
  6. platzky-0.2.0/platzky/config.py +66 -0
  7. platzky-0.2.0/platzky/db/__init__.py +0 -0
  8. platzky-0.2.0/platzky/db/db.py +107 -0
  9. platzky-0.2.0/platzky/db/db_loader.py +32 -0
  10. platzky-0.2.0/platzky/db/google_json_db.py +56 -0
  11. {platzky-0.1.19 → platzky-0.2.0}/platzky/db/graph_ql_db.py +117 -27
  12. platzky-0.2.0/platzky/db/json_db.py +110 -0
  13. platzky-0.2.0/platzky/db/json_file_db.py +41 -0
  14. platzky-0.2.0/platzky/models.py +64 -0
  15. platzky-0.2.0/platzky/platzky.py +154 -0
  16. platzky-0.2.0/platzky/plugin_loader.py +39 -0
  17. platzky-0.2.0/platzky/plugins/redirections/entrypoint.py +68 -0
  18. platzky-0.2.0/platzky/plugins/sendmail/entrypoint.py +43 -0
  19. platzky-0.2.0/platzky/seo/seo.py +77 -0
  20. {platzky-0.1.19 → platzky-0.2.0}/platzky/static/blog.css +5 -12
  21. platzky-0.2.0/platzky/templates/base.html +167 -0
  22. {platzky-0.1.19 → platzky-0.2.0}/platzky/templates/blog.html +3 -2
  23. platzky-0.2.0/platzky/templates/body_meta.html +7 -0
  24. {platzky-0.1.19 → platzky-0.2.0}/platzky/templates/feed.xml +1 -1
  25. {platzky-0.1.19 → platzky-0.2.0}/platzky/templates/head_meta.html +5 -15
  26. {platzky-0.1.19 → platzky-0.2.0}/platzky/templates/post.html +2 -2
  27. {platzky-0.1.19 → platzky-0.2.0}/platzky/www_handler.py +7 -4
  28. platzky-0.2.0/pyproject.toml +47 -0
  29. platzky-0.1.19/README.md +0 -21
  30. platzky-0.1.19/platzky/blog/blog.py +0 -67
  31. platzky-0.1.19/platzky/blog/comment_form.py +0 -11
  32. platzky-0.1.19/platzky/blog/db.py +0 -18
  33. platzky-0.1.19/platzky/blog/post_formatter.py +0 -16
  34. platzky-0.1.19/platzky/config.py +0 -63
  35. platzky-0.1.19/platzky/db/google_json_db.py +0 -33
  36. platzky-0.1.19/platzky/db/json_db.py +0 -46
  37. platzky-0.1.19/platzky/db/json_file_db.py +0 -25
  38. platzky-0.1.19/platzky/db_loader.py +0 -11
  39. platzky-0.1.19/platzky/platzky.py +0 -94
  40. platzky-0.1.19/platzky/plugin_loader.py +0 -42
  41. platzky-0.1.19/platzky/plugins/redirections/entrypoint.py +0 -46
  42. platzky-0.1.19/platzky/plugins/sendmail/entrypoint.py +0 -22
  43. platzky-0.1.19/platzky/seo/seo.py +0 -67
  44. platzky-0.1.19/platzky/templates/base.html +0 -52
  45. platzky-0.1.19/platzky/templates/body_meta.html +0 -24
  46. platzky-0.1.19/pyproject.toml +0 -28
  47. platzky-0.1.19/setup.py +0 -43
  48. {platzky-0.1.19 → platzky-0.2.0}/platzky/__init__.py +0 -0
  49. {platzky-0.1.19 → platzky-0.2.0}/platzky/templates/404.html +0 -0
  50. {platzky-0.1.19 → platzky-0.2.0}/platzky/templates/home.html +0 -0
  51. {platzky-0.1.19 → platzky-0.2.0}/platzky/templates/page.html +0 -0
  52. {platzky-0.1.19 → platzky-0.2.0}/platzky/templates/robots.txt +0 -0
  53. {platzky-0.1.19 → platzky-0.2.0}/platzky/templates/sitemap.xml +0 -0
@@ -1,22 +1,23 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: platzky
3
- Version: 0.1.19
4
- Summary: Another blog in python
3
+ Version: 0.2.0
4
+ Summary: Not only blog engine
5
5
  License: MIT
6
6
  Requires-Python: >=3.10,<4.0
7
7
  Classifier: License :: OSI Approved :: MIT License
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.10
10
10
  Classifier: Programming Language :: Python :: 3.11
11
- Requires-Dist: Flask (>=2.2.2,<3.0.0)
12
- Requires-Dist: Flask-Babel (>=2.0.0,<3.0.0)
13
- Requires-Dist: Flask-Minify (>=0.39,<0.40)
14
- Requires-Dist: Flask-WTF (>=1.0.1,<2.0.0)
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Dist: Flask (==3.0.3)
13
+ Requires-Dist: Flask-Babel (>=4.0.0,<5.0.0)
14
+ Requires-Dist: Flask-Minify (>=0.42,<0.43)
15
+ Requires-Dist: Flask-WTF (>=1.2.1,<2.0.0)
15
16
  Requires-Dist: PyYAML (>=6.0,<7.0)
16
- Requires-Dist: aiohttp (>=3.8.3,<4.0.0)
17
+ Requires-Dist: aiohttp (>=3.9.5,<4.0.0)
17
18
  Requires-Dist: google-cloud-storage (>=2.5.0,<3.0.0)
18
19
  Requires-Dist: gql (>=3.4.0,<4.0.0)
19
- Requires-Dist: humanize (>=4.3.0,<5.0.0)
20
+ Requires-Dist: humanize (>=4.9.0,<5.0.0)
20
21
  Description-Content-Type: text/markdown
21
22
 
22
23
  ![Github Actions](https://github.com/platzky/platzky/actions/workflows/tests.yml/badge.svg?event=push&branch=main)
@@ -24,20 +25,15 @@ Description-Content-Type: text/markdown
24
25
 
25
26
  # platzky
26
27
 
27
- Blog engine in python
28
+ Platzky is engine which aims to provide simple and easy way to create and run web applications in python.
28
29
 
29
- # How to run?
30
+ # How to use?
30
31
 
31
- 1. Install platzky with your favorite dependency management tool (`pip install platzky`)
32
- 2. run `flask --app "platzky:create_app(PATH_TO_YOUR_CONFIG_FILE)" run`
32
+ 1. Install platzky with your favorite dependency management tool (`pip install platzky` or `poetry add platzky`).
33
+ 2. Copy `config-template.yml` to your project directory and fill it with your data.
34
+ 3. Run `flask --app "platzky.platzky:create_app(config_path='PATH_TO_YOUR_CONFIG_FILE')`
33
35
 
34
- ## Configuration
36
+ ## Example
35
37
 
36
- For details check `config.yml.tpl` file.
37
-
38
-
39
- # API
40
- `platzky.config.from_file(path_to_config)` - creates _platzky_ config from file (see __config.yml.tpl__)
41
- `platzky.create_app_from_config(config)` - creates _platzky_ application.
42
- `platzky.sendmail(receiver_email, subject, message)`- sends email from configured account
38
+ For examples check e2e tests in `tests/e2e` directory and Makefile.
43
39
 
@@ -0,0 +1,16 @@
1
+ ![Github Actions](https://github.com/platzky/platzky/actions/workflows/tests.yml/badge.svg?event=push&branch=main)
2
+ [![Coverage Status](https://coveralls.io/repos/github/platzky/platzky/badge.svg?branch=main)](https://coveralls.io/github/platzky/platzky?branch=main)
3
+
4
+ # platzky
5
+
6
+ Platzky is engine which aims to provide simple and easy way to create and run web applications in python.
7
+
8
+ # How to use?
9
+
10
+ 1. Install platzky with your favorite dependency management tool (`pip install platzky` or `poetry add platzky`).
11
+ 2. Copy `config-template.yml` to your project directory and fill it with your data.
12
+ 3. Run `flask --app "platzky.platzky:create_app(config_path='PATH_TO_YOUR_CONFIG_FILE')`
13
+
14
+ ## Example
15
+
16
+ For examples check e2e tests in `tests/e2e` directory and Makefile.
File without changes
@@ -0,0 +1,93 @@
1
+ from os.path import dirname
2
+
3
+ from flask import Blueprint, make_response, render_template, request
4
+ from markupsafe import Markup
5
+
6
+ from . import comment_form
7
+
8
+
9
+ def create_blog_blueprint(db, blog_prefix: str, locale_func):
10
+ url_prefix = blog_prefix
11
+ blog = Blueprint(
12
+ "blog",
13
+ __name__,
14
+ url_prefix=url_prefix,
15
+ template_folder=f"{dirname(__file__)}/../templates",
16
+ )
17
+
18
+ @blog.app_template_filter()
19
+ def markdown(text):
20
+ return Markup(text)
21
+
22
+ @blog.errorhandler(404)
23
+ def page_not_found(e):
24
+ return render_template("404.html", title="404"), 404
25
+
26
+ @blog.route("/", methods=["GET"])
27
+ def all_posts():
28
+ lang = locale_func()
29
+ posts = db.get_all_posts(lang)
30
+ if not posts:
31
+ return page_not_found("no posts")
32
+ posts_sorted = sorted(posts, reverse=True)
33
+ return render_template("blog.html", posts=posts_sorted)
34
+
35
+ @blog.route("/feed", methods=["GET"])
36
+ def get_feed():
37
+ lang = locale_func()
38
+ response = make_response(
39
+ render_template("feed.xml", posts=db.get_all_posts(lang))
40
+ )
41
+ response.headers["Content-Type"] = "application/xml"
42
+ return response
43
+
44
+ @blog.route("/<post_slug>", methods=["POST"])
45
+ def post_comment(post_slug):
46
+ comment = request.form.to_dict()
47
+ db.add_comment(
48
+ post_slug=post_slug,
49
+ author_name=comment["author_name"],
50
+ comment=comment["comment"],
51
+ )
52
+ return get_post(post_slug=post_slug)
53
+
54
+ @blog.route("/<post_slug>", methods=["GET"])
55
+ def get_post(post_slug):
56
+ post = db.get_post(post_slug)
57
+ try:
58
+ post = db.get_post(post_slug)
59
+ return render_template(
60
+ "post.html",
61
+ post=post,
62
+ post_slug=post_slug,
63
+ form=comment_form.CommentForm(),
64
+ comment_sent=request.args.get("comment_sent"),
65
+ )
66
+ except ValueError:
67
+ return page_not_found(f"no post with slug {post_slug}")
68
+ except Exception as e:
69
+ return page_not_found(str(e))
70
+
71
+ @blog.route("/page/<path:page_slug>", methods=["GET"])
72
+ def get_page(
73
+ page_slug,
74
+ ): # TODO refactor to share code with get_post since they are very similar
75
+ try:
76
+ page = db.get_page(page_slug)
77
+ if cover_image := page.coverImage:
78
+ cover_image_url = cover_image.url
79
+ else:
80
+ cover_image_url = None
81
+ return render_template("page.html", page=page, cover_image=cover_image_url)
82
+ except ValueError:
83
+ return page_not_found("no page with slug {page_slug}")
84
+ except Exception as e:
85
+ return page_not_found(str(e))
86
+
87
+ @blog.route("/tag/<path:tag>", methods=["GET"])
88
+ def get_posts_from_tag(tag):
89
+ lang = locale_func()
90
+ posts = db.get_posts_by_tag(tag, lang)
91
+ return render_template("blog.html", posts=posts, subtitle=f" - tag: {tag}")
92
+
93
+ return blog
@@ -0,0 +1,15 @@
1
+ from flask_babel import lazy_gettext
2
+ from flask_wtf import FlaskForm
3
+ from wtforms import StringField, SubmitField
4
+ from wtforms.validators import DataRequired
5
+ from wtforms.widgets import TextArea
6
+
7
+
8
+ class CommentForm(FlaskForm):
9
+ author_name = StringField(str(lazy_gettext("Name")), validators=[DataRequired()])
10
+ comment = StringField(
11
+ str(lazy_gettext("Type comment here")),
12
+ validators=[DataRequired()],
13
+ widget=TextArea(),
14
+ )
15
+ submit = SubmitField(str(lazy_gettext("Comment")))
@@ -0,0 +1,66 @@
1
+ import sys
2
+ import typing as t
3
+ import yaml
4
+ from pydantic import ConfigDict, BaseModel, Field
5
+
6
+ from .db.db import DBConfig
7
+ from .db.db_loader import get_db_module
8
+
9
+
10
+ class StrictBaseModel(BaseModel):
11
+ model_config = ConfigDict(frozen=True)
12
+
13
+
14
+ class LanguageConfig(StrictBaseModel):
15
+ name: str = Field(alias="name")
16
+ flag: str = Field(alias="flag")
17
+ domain: t.Optional[str] = Field(default=None, alias="domain")
18
+
19
+
20
+ Languages = dict[str, LanguageConfig]
21
+ LanguagesMapping = t.Mapping[str, t.Mapping[str, str]]
22
+
23
+
24
+ def languages_dict(languages: Languages) -> LanguagesMapping:
25
+ return {name: lang.model_dump() for name, lang in languages.items()}
26
+
27
+
28
+ class Config(StrictBaseModel):
29
+ app_name: str = Field(alias="APP_NAME")
30
+ secret_key: str = Field(alias="SECRET_KEY")
31
+ db: DBConfig = Field(alias="DB")
32
+ use_www: bool = Field(default=True, alias="USE_WWW")
33
+ seo_prefix: str = Field(default="/", alias="SEO_PREFIX")
34
+ blog_prefix: str = Field(default="/", alias="BLOG_PREFIX")
35
+ languages: Languages = Field(default_factory=dict, alias="LANGUAGES")
36
+ domain_to_lang: dict[str, str] = Field(default_factory=dict, alias="DOMAIN_TO_LANG")
37
+ translation_directories: list[str] = Field(
38
+ default_factory=list,
39
+ alias="TRANSLATION_DIRECTORIES",
40
+ )
41
+ debug: bool = Field(default=False, alias="DEBUG")
42
+ testing: bool = Field(default=False, alias="TESTING")
43
+
44
+ @classmethod
45
+ def model_validate(
46
+ cls,
47
+ obj: t.Any,
48
+ *,
49
+ strict: bool | None = None,
50
+ from_attributes: bool | None = None,
51
+ context: dict[str, t.Any] | None = None,
52
+ ) -> "Config":
53
+ db_cfg_type = get_db_module(obj["DB"]["TYPE"]).db_config_type()
54
+ obj["DB"] = db_cfg_type.model_validate(obj["DB"])
55
+ return super().model_validate(
56
+ obj, strict=strict, from_attributes=from_attributes, context=context
57
+ )
58
+
59
+ @classmethod
60
+ def parse_yaml(cls, path: str) -> "Config":
61
+ try:
62
+ with open(path, "r") as f:
63
+ return cls.model_validate(yaml.safe_load(f))
64
+ except FileNotFoundError:
65
+ print(f"Config file not found: {path}", file=sys.stderr)
66
+ raise SystemExit(1)
File without changes
@@ -0,0 +1,107 @@
1
+ from functools import partial
2
+ from typing import Any, Callable
3
+
4
+ from pydantic import BaseModel, Field
5
+
6
+ from abc import abstractmethod, ABC
7
+ from ..models import MenuItem, Post, Page, Color
8
+
9
+
10
+ class DB(ABC):
11
+ db_name: str = "DB"
12
+ module_name: str = "db"
13
+ config_type: type
14
+
15
+ def __init_subclass__(cls, *args, **kw):
16
+ """Check that all methods defined in the subclass exist in the superclasses.
17
+ This is to prevent subclasses from adding new methods to the DB object.
18
+ """
19
+ super().__init_subclass__(*args, **kw)
20
+ for name in cls.__dict__:
21
+ if name.startswith("_"):
22
+ continue
23
+ for superclass in cls.__mro__[1:]:
24
+ if name in dir(superclass):
25
+ break
26
+ else:
27
+ raise TypeError(
28
+ f"Method {name} defined in {cls.__name__} does not exist in superclasses"
29
+ )
30
+
31
+ def extend(self, function_name: str, function: Callable) -> None:
32
+ """
33
+ Add a function to the DB object. The function must take the DB object as first parameter.
34
+
35
+ Parameters:
36
+ function_name (str): The name of the function to add.
37
+ function (Callable): The function to add to the DB object.
38
+ """
39
+ if not callable(function):
40
+ raise ValueError(
41
+ f"The provided func for '{function_name}' is not callable."
42
+ )
43
+ try:
44
+ bound_function = partial(function, self)
45
+ setattr(self, function_name, bound_function)
46
+ except Exception as e:
47
+ raise ValueError(f"Failed to extend DB with function {function_name}: {e}")
48
+
49
+ @abstractmethod
50
+ def get_all_posts(self, lang) -> list[Post]:
51
+ pass
52
+
53
+ @abstractmethod
54
+ def get_menu_items(self) -> list[MenuItem]:
55
+ pass
56
+
57
+ @abstractmethod
58
+ def get_post(self, slug) -> Post:
59
+ pass
60
+
61
+ @abstractmethod
62
+ def get_page(self, slug) -> Page:
63
+ pass
64
+
65
+ @abstractmethod
66
+ def get_posts_by_tag(self, tag, lang) -> Any:
67
+ pass
68
+
69
+ @abstractmethod
70
+ def add_comment(self, author_name, comment, post_slug) -> None:
71
+ pass
72
+
73
+ @abstractmethod
74
+ def get_logo_url(self) -> str:
75
+ pass
76
+
77
+ @abstractmethod
78
+ def get_primary_color(self) -> Color:
79
+ pass
80
+
81
+ @abstractmethod
82
+ def get_secondary_color(self) -> Color:
83
+ pass
84
+
85
+ @abstractmethod
86
+ def get_plugins_data(self) -> list:
87
+ pass
88
+
89
+ @abstractmethod
90
+ def get_font(self) -> str:
91
+ pass
92
+
93
+ @abstractmethod
94
+ def get_all_providers(self): # TODO providers are not part of the DB
95
+ pass
96
+
97
+ @abstractmethod
98
+ def get_all_questions(self): # TODO questions are not part of the DB
99
+ pass
100
+
101
+ @abstractmethod
102
+ def get_site_content(self) -> str:
103
+ pass
104
+
105
+
106
+ class DBConfig(BaseModel):
107
+ type: str = Field(alias="TYPE")
@@ -0,0 +1,32 @@
1
+ import importlib.util
2
+ import os
3
+ import sys
4
+ from os.path import abspath, dirname
5
+
6
+
7
+ def get_db(db_config):
8
+ db_name = db_config.type
9
+ db = get_db_module(db_name)
10
+ return db.db_from_config(db_config)
11
+
12
+
13
+ def get_db_module(db_type):
14
+ """
15
+ Load db module from db_type
16
+ This function is used to load db module dynamically as it is specified in config file.
17
+ :param db_type: name of db module
18
+ :return: db module
19
+ """
20
+ db_dir = dirname(abspath(__file__))
21
+ parent_module_name = ".".join(__name__.split(".")[:-1])
22
+ module_name = f"{parent_module_name}.{db_type}_db"
23
+ spec = importlib.util.spec_from_file_location(
24
+ module_name, os.path.join(db_dir, f"{db_type}_db.py")
25
+ )
26
+ assert spec is not None
27
+ db = importlib.util.module_from_spec(spec)
28
+ sys.modules[f"{db_type}_db"] = db
29
+ assert spec.loader is not None
30
+ spec.loader.exec_module(db)
31
+
32
+ return db
@@ -0,0 +1,56 @@
1
+ import json
2
+
3
+ from google.cloud.storage import Client
4
+ from pydantic import Field
5
+
6
+ from .db import DBConfig
7
+ from .json_db import Json
8
+
9
+
10
+ def db_config_type():
11
+ return GoogleJsonDbConfig
12
+
13
+
14
+ class GoogleJsonDbConfig(DBConfig):
15
+ bucket_name: str = Field(alias="BUCKET_NAME")
16
+ source_blob_name: str = Field(alias="SOURCE_BLOB_NAME")
17
+
18
+
19
+ def db_from_config(config: GoogleJsonDbConfig):
20
+ return GoogleJsonDb(config.bucket_name, config.source_blob_name)
21
+
22
+
23
+ def get_db(config):
24
+ google_json_db_config = GoogleJsonDbConfig.model_validate(config)
25
+ return GoogleJsonDb(
26
+ google_json_db_config.bucket_name, google_json_db_config.source_blob_name
27
+ )
28
+
29
+
30
+ def get_blob(bucket_name, source_blob_name):
31
+ storage_client = Client()
32
+ bucket = storage_client.bucket(bucket_name)
33
+ return bucket.blob(source_blob_name)
34
+
35
+
36
+ def get_data(blob):
37
+ raw_data = blob.download_as_text(client=None)
38
+ return json.loads(raw_data)
39
+
40
+
41
+ class GoogleJsonDb(Json):
42
+ def __init__(self, bucket_name, source_blob_name):
43
+ self.bucket_name = bucket_name
44
+ self.source_blob_name = source_blob_name
45
+
46
+ self.blob = get_blob(self.bucket_name, self.source_blob_name)
47
+ data = get_data(self.blob)
48
+ super().__init__(data)
49
+
50
+ self.module_name = "google_json_db"
51
+ self.db_name = "GoogleJsonDb"
52
+
53
+ def __save_entry(self, entry):
54
+ data = get_data(self.blob)
55
+ data["data"].append(entry)
56
+ self.blob.upload_from_string(json.dumps(data), content_type="application/json")
@@ -1,22 +1,59 @@
1
- #TODO rename file, extract it to another library, remove qgl and aiohttp from dependencies
1
+ # TODO rename file, extract it to another library, remove qgl and aiohttp from dependencies
2
2
 
3
- from gql import gql, Client
4
- from gql.transport.aiohttp import AIOHTTPTransport
5
3
  import json
6
- from platzky.blog.db import DB
7
4
 
5
+ from gql import Client, gql
6
+ from gql.transport.aiohttp import AIOHTTPTransport
7
+ from pydantic import Field
8
+
9
+ from .db import DB, DBConfig
10
+ from ..models import Color, Post
11
+
12
+
13
+ def db_config_type():
14
+ return GraphQlDbConfig
15
+
16
+
17
+ class GraphQlDbConfig(DBConfig):
18
+ endpoint: str = Field(alias="CMS_ENDPOINT")
19
+ token: str = Field(alias="CMS_TOKEN")
20
+
21
+
22
+ def get_db(config: GraphQlDbConfig):
23
+ return GraphQL(config.endpoint, config.token)
24
+
25
+
26
+ def db_from_config(config: GraphQlDbConfig):
27
+ return GraphQL(config.endpoint, config.token)
8
28
 
9
- def get_db(config):
10
- endpoint = config["DB"]["CMS_ENDPOINT"]
11
- token = config["DB"]["CMS_TOKEN"]
12
- return GraphQL(endpoint, token)
29
+
30
+ def _standarize_post(post):
31
+ return {
32
+ "author": post["author"]["name"],
33
+ "slug": post["slug"],
34
+ "title": post["title"],
35
+ "excerpt": post["excerpt"],
36
+ "contentInMarkdown": post["contentInRichText"]["html"],
37
+ "comments": post["comments"],
38
+ "tags": post["tags"],
39
+ "language": post["language"],
40
+ "coverImage": {
41
+ "url": post["coverImage"]["image"]["url"],
42
+ },
43
+ "date": post["date"],
44
+ }
13
45
 
14
46
 
15
47
  class GraphQL(DB):
16
48
  def __init__(self, endpoint, token):
17
- full_token = 'bearer ' + token
18
- transport = AIOHTTPTransport(url=endpoint, headers={'Authorization': full_token})
49
+ self.module_name = "graph_ql_db"
50
+ self.db_name = "GraphQLDb"
51
+ full_token = "bearer " + token
52
+ transport = AIOHTTPTransport(
53
+ url=endpoint, headers={"Authorization": full_token}
54
+ )
19
55
  self.client = Client(transport=transport)
56
+ super().__init__()
20
57
 
21
58
  def get_all_posts(self, lang):
22
59
  all_posts = gql(
@@ -24,22 +61,38 @@ class GraphQL(DB):
24
61
  query MyQuery($lang: Lang!) {
25
62
  posts(where: {language: $lang}, orderBy: date_DESC, stage: PUBLISHED){
26
63
  createdAt
64
+ author {
65
+ name
66
+ }
67
+ contentInRichText {
68
+ html
69
+ }
70
+ comments {
71
+ comment
72
+ author
73
+ createdAt
74
+ }
27
75
  date
28
76
  title
29
77
  excerpt
30
78
  slug
31
79
  tags
80
+ language
32
81
  coverImage {
33
82
  alternateText
34
83
  image {
35
84
  url
36
85
  }
37
- }
86
+ }
38
87
  }
39
88
  }
40
89
  """
41
90
  )
42
- return self.client.execute(all_posts, variable_values={"lang": lang})['posts']
91
+ raw_ql_posts = self.client.execute(all_posts, variable_values={"lang": lang})[
92
+ "posts"
93
+ ]
94
+
95
+ return [Post.model_validate(_standarize_post(post)) for post in raw_ql_posts]
43
96
 
44
97
  def get_menu_items(self):
45
98
  menu_items = gql(
@@ -52,17 +105,23 @@ class GraphQL(DB):
52
105
  }
53
106
  """
54
107
  )
55
- return self.client.execute(menu_items)['menuItems']
108
+ return self.client.execute(menu_items)["menuItems"]
56
109
 
57
110
  def get_post(self, slug):
58
111
  post = gql(
59
112
  """
60
113
  query MyQuery($slug: String!) {
61
114
  post(where: {slug: $slug}, stage: PUBLISHED) {
115
+ date
116
+ language
62
117
  title
118
+ slug
119
+ author {
120
+ name
121
+ }
63
122
  contentInRichText {
64
- text
65
123
  markdown
124
+ html
66
125
  }
67
126
  excerpt
68
127
  tags
@@ -76,13 +135,16 @@ class GraphQL(DB):
76
135
  author
77
136
  comment
78
137
  date: createdAt
79
- }
138
+ }
80
139
  }
81
140
  }
82
- """)
83
- return self.client.execute(post, variable_values={"slug": slug})['post']
141
+ """
142
+ )
143
+
144
+ post_raw = self.client.execute(post, variable_values={"slug": slug})["post"]
145
+ return Post.model_validate(_standarize_post(post_raw))
84
146
 
85
- #TODO Cleanup page logic of internationalization (now it depends on translation of slugs)
147
+ # TODO Cleanup page logic of internationalization (now it depends on translation of slugs)
86
148
  def get_page(self, slug):
87
149
  post = gql(
88
150
  """
@@ -96,8 +158,9 @@ class GraphQL(DB):
96
158
  }
97
159
  }
98
160
  }
99
- """)
100
- return self.client.execute(post, variable_values={"slug": slug})['page']
161
+ """
162
+ )
163
+ return self.client.execute(post, variable_values={"slug": slug})["page"]
101
164
 
102
165
  def get_posts_by_tag(self, tag, lang):
103
166
  post = gql(
@@ -117,8 +180,11 @@ class GraphQL(DB):
117
180
  }
118
181
  }
119
182
  }
120
- """)
121
- return self.client.execute(post, variable_values={"tag": tag, "lang": lang})['posts']
183
+ """
184
+ )
185
+ return self.client.execute(post, variable_values={"tag": tag, "lang": lang})[
186
+ "posts"
187
+ ]
122
188
 
123
189
  def get_all_providers(self):
124
190
  all_providers = gql(
@@ -151,7 +217,7 @@ class GraphQL(DB):
151
217
  """
152
218
  )
153
219
  query = self.client.execute(all_questions)
154
- return query['questions']
220
+ return query["questions"]
155
221
 
156
222
  def add_comment(self, author_name, comment, post_slug):
157
223
  add_comment = gql(
@@ -167,7 +233,31 @@ class GraphQL(DB):
167
233
  id
168
234
  }
169
235
  }
170
- """)
171
- self.client.execute(add_comment, variable_values={
172
- "author": author_name, "comment": comment, "slug": post_slug
173
- })
236
+ """
237
+ )
238
+ self.client.execute(
239
+ add_comment,
240
+ variable_values={
241
+ "author": author_name,
242
+ "comment": comment,
243
+ "slug": post_slug,
244
+ },
245
+ )
246
+
247
+ def get_font(self):
248
+ return str("")
249
+
250
+ def get_logo_url(self):
251
+ return ""
252
+
253
+ def get_primary_color(self) -> Color:
254
+ return Color()
255
+
256
+ def get_secondary_color(self):
257
+ return Color()
258
+
259
+ def get_site_content(self):
260
+ return ""
261
+
262
+ def get_plugins_data(self):
263
+ return []