platzky 0.3.1__tar.gz → 0.3.4__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 (44) hide show
  1. {platzky-0.3.1 → platzky-0.3.4}/PKG-INFO +2 -1
  2. platzky-0.3.4/platzky/admin/fake_login.py +104 -0
  3. {platzky-0.3.1 → platzky-0.3.4}/platzky/admin/templates/admin.html +1 -0
  4. {platzky-0.3.1 → platzky-0.3.4}/platzky/admin/templates/login.html +4 -2
  5. {platzky-0.3.1 → platzky-0.3.4}/platzky/blog/blog.py +0 -1
  6. platzky-0.3.4/platzky/db/README.md +70 -0
  7. platzky-0.3.4/platzky/db/github_json_db.py +69 -0
  8. {platzky-0.3.1 → platzky-0.3.4}/platzky/db/google_json_db.py +2 -2
  9. {platzky-0.3.1 → platzky-0.3.4}/platzky/db/json_db.py +2 -2
  10. {platzky-0.3.1 → platzky-0.3.4}/platzky/db/json_file_db.py +2 -2
  11. {platzky-0.3.1 → platzky-0.3.4}/platzky/engine.py +0 -1
  12. {platzky-0.3.1 → platzky-0.3.4}/platzky/platzky.py +12 -7
  13. {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/base.html +11 -0
  14. {platzky-0.3.1 → platzky-0.3.4}/pyproject.toml +2 -1
  15. {platzky-0.3.1 → platzky-0.3.4}/README.md +0 -0
  16. {platzky-0.3.1 → platzky-0.3.4}/platzky/__init__.py +0 -0
  17. {platzky-0.3.1 → platzky-0.3.4}/platzky/admin/admin.py +0 -0
  18. {platzky-0.3.1 → platzky-0.3.4}/platzky/admin/templates/module.html +0 -0
  19. {platzky-0.3.1 → platzky-0.3.4}/platzky/blog/__init__.py +0 -0
  20. {platzky-0.3.1 → platzky-0.3.4}/platzky/blog/comment_form.py +0 -0
  21. {platzky-0.3.1 → platzky-0.3.4}/platzky/config.py +0 -0
  22. {platzky-0.3.1 → platzky-0.3.4}/platzky/db/__init__.py +0 -0
  23. {platzky-0.3.1 → platzky-0.3.4}/platzky/db/db.py +0 -0
  24. {platzky-0.3.1 → platzky-0.3.4}/platzky/db/db_loader.py +0 -0
  25. {platzky-0.3.1 → platzky-0.3.4}/platzky/db/graph_ql_db.py +0 -0
  26. {platzky-0.3.1 → platzky-0.3.4}/platzky/locale/en/LC_MESSAGES/messages.po +0 -0
  27. {platzky-0.3.1 → platzky-0.3.4}/platzky/locale/pl/LC_MESSAGES/messages.po +0 -0
  28. {platzky-0.3.1 → platzky-0.3.4}/platzky/models.py +0 -0
  29. {platzky-0.3.1 → platzky-0.3.4}/platzky/plugin/plugin.py +0 -0
  30. {platzky-0.3.1 → platzky-0.3.4}/platzky/plugin/plugin_loader.py +0 -0
  31. {platzky-0.3.1 → platzky-0.3.4}/platzky/seo/seo.py +0 -0
  32. {platzky-0.3.1 → platzky-0.3.4}/platzky/static/blog.css +0 -0
  33. {platzky-0.3.1 → platzky-0.3.4}/platzky/static/styles.css +0 -0
  34. {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/404.html +0 -0
  35. {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/blog.html +0 -0
  36. {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/body_meta.html +0 -0
  37. {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/dynamic_css.html +0 -0
  38. {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/feed.xml +0 -0
  39. {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/head_meta.html +0 -0
  40. {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/page.html +0 -0
  41. {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/post.html +0 -0
  42. {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/robots.txt +0 -0
  43. {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/sitemap.xml +0 -0
  44. {platzky-0.3.1 → platzky-0.3.4}/platzky/www_handler.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: platzky
3
- Version: 0.3.1
3
+ Version: 0.3.4
4
4
  Summary: Not only blog engine
5
5
  License: MIT
6
6
  Requires-Python: >=3.10,<4.0
@@ -21,6 +21,7 @@ Requires-Dist: google-cloud-storage (>=2.5.0,<3.0.0)
21
21
  Requires-Dist: gql (>=3.4.0,<4.0.0)
22
22
  Requires-Dist: humanize (>=4.9.0,<5.0.0)
23
23
  Requires-Dist: pydantic (>=2.7.1,<3.0.0)
24
+ Requires-Dist: pygithub (>=2.6.1,<3.0.0)
24
25
  Description-Content-Type: text/markdown
25
26
 
26
27
  ![Github Actions](https://github.com/platzky/platzky/actions/workflows/tests.yml/badge.svg?event=push&branch=main)
@@ -0,0 +1,104 @@
1
+ """
2
+ Fake login functionality for development environments only.
3
+
4
+ WARNING: This module provides fake login functionality and should NEVER be used in production
5
+ environments as it bypasses proper authentication and authorization controls.
6
+ """
7
+
8
+ import os
9
+ from typing import Any, Callable
10
+
11
+ from flask import Blueprint, flash, redirect, render_template_string, session, url_for
12
+ from flask_wtf import FlaskForm
13
+ from markupsafe import Markup
14
+
15
+ ROLE_ADMIN = "admin"
16
+ ROLE_NONADMIN = "nonadmin"
17
+ VALID_ROLES = [ROLE_ADMIN, ROLE_NONADMIN]
18
+
19
+
20
+ class FakeLoginForm(FlaskForm):
21
+ """
22
+ Empty form class that inherits CSRF protection from FlaskForm.
23
+
24
+ Used specifically for the fake login functionality to enable
25
+ CSRF token validation on form submissions.
26
+ """
27
+
28
+ pass
29
+
30
+
31
+ def get_fake_login_html() -> Callable[[], str]:
32
+ """Return a callable that generates HTML for fake login buttons."""
33
+
34
+ def generate_html() -> str:
35
+ admin_url = url_for("admin.handle_fake_login", role="admin")
36
+ nonadmin_url = url_for("admin.handle_fake_login", role="nonadmin")
37
+
38
+ # Create a form instance to get the CSRF token
39
+ form = FakeLoginForm()
40
+
41
+ html = render_template_string(
42
+ """
43
+ <div class="col-md-6 mb-4">
44
+ <div class="card">
45
+ <div class="card-header">
46
+ Development Login
47
+ </div>
48
+ <div class="card-body">
49
+ <p class="text-danger"><strong>Warning:</strong> For development only</p>
50
+ <div class="d-flex justify-content-around">
51
+ <form method="post" action="{{ admin_url }}" style="display: inline;">
52
+ {{ form.csrf_token }}
53
+ <button type="submit" class="btn btn-primary">Login as Admin</button>
54
+ </form>
55
+ <form method="post" action="{{ nonadmin_url }}" style="display: inline;">
56
+ {{ form.csrf_token }}
57
+ <button type="submit" class="btn btn-secondary">Login as Non-Admin</button>
58
+ </form>
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ """,
64
+ form=form,
65
+ admin_url=admin_url,
66
+ nonadmin_url=nonadmin_url,
67
+ )
68
+
69
+ return Markup(html)
70
+
71
+ return generate_html
72
+
73
+
74
+ def setup_fake_login_routes(admin_blueprint: Blueprint) -> Blueprint:
75
+ """Add fake login routes to the provided admin_blueprint."""
76
+
77
+ env = os.environ
78
+ is_testing = "PYTEST_CURRENT_TEST" in env.keys() or env.get("FLASK_DEBUG") in (
79
+ "1",
80
+ "true",
81
+ "True",
82
+ True,
83
+ )
84
+
85
+ if not is_testing:
86
+ raise RuntimeError(
87
+ "SECURITY ERROR: Fake login routes are enabled outside of a testing environment! "
88
+ "This functionality must only be used during development or testing."
89
+ )
90
+
91
+ @admin_blueprint.route("/fake-login/<role>", methods=["POST"])
92
+ def handle_fake_login(role: str) -> Any:
93
+ form = FakeLoginForm()
94
+ if form.validate_on_submit() and role in VALID_ROLES:
95
+ if role == ROLE_ADMIN:
96
+ session["user"] = {"username": ROLE_ADMIN, "role": ROLE_ADMIN}
97
+ else:
98
+ session["user"] = {"username": "user", "role": ROLE_NONADMIN}
99
+ return redirect(url_for("admin.admin_panel_home"))
100
+
101
+ flash(f"Invalid role: {role}. Must be one of: {', '.join(VALID_ROLES)}", "error")
102
+ return redirect(url_for("admin.admin_panel_home"))
103
+
104
+ return admin_blueprint
@@ -17,6 +17,7 @@
17
17
 
18
18
  {% block left_panel %}
19
19
  <div id="admin-panel">
20
+ <p>User: {{ user.username }}</p>
20
21
  {% for cms_module_name, cms_entries in cms_modules.items() %}
21
22
  <div class="cms-module mb-2">
22
23
  <p>{{ cms_module_name }}</p>
@@ -12,9 +12,11 @@
12
12
 
13
13
  <div class="row align-items-center">
14
14
 
15
- {% for login_method in login_methods %}
16
- {{ login_method | safe }}
15
+ {% for method in login_methods %}
16
+ {{ method() }}
17
+
17
18
  {% endfor %}
19
+
18
20
  </div>
19
21
  </div>
20
22
 
@@ -51,7 +51,6 @@ def create_blog_blueprint(db, blog_prefix: str, locale_func):
51
51
 
52
52
  @blog.route("/<post_slug>", methods=["GET"])
53
53
  def get_post(post_slug):
54
- post = db.get_post(post_slug)
55
54
  try:
56
55
  post = db.get_post(post_slug)
57
56
  return render_template(
@@ -0,0 +1,70 @@
1
+ # Platzky Database Modules
2
+
3
+ This directory contains the database abstraction layer for the Platzky application. The database modules provide a consistent interface for accessing content regardless of where it's stored.
4
+
5
+ ## Architecture
6
+
7
+ The database layer is built on an abstract base class (DB) that defines a common interface. Multiple implementations are provided for different storage backends:
8
+
9
+ - **Json**: Base implementation for JSON data sources
10
+ - **JsonFile**: Local JSON file storage
11
+ - **GithubJsonDb**: JSON files stored in GitHub repository
12
+ - **GoogleJsonDb**: JSON files stored in Google Cloud Storage
13
+ - **GraphQL**: Content stored in a GraphQL API
14
+
15
+
16
+ ## Configuration
17
+
18
+ Database configuration is specified in your application config file. Each database type has its own configuration schema.
19
+
20
+ ### JSON File Database
21
+
22
+ ```yaml
23
+ DB:
24
+ TYPE: json_file
25
+ PATH: "/path/to/data.json"
26
+
27
+ ```
28
+ ### GitHub JSON Database
29
+
30
+ ```yaml
31
+ DB:
32
+ TYPE: github_json
33
+ REPO_NAME: "username/repository"
34
+ GITHUB_TOKEN: "your_github_token"
35
+ BRANCH_NAME: "main"
36
+ PATH_TO_FILE: "data.json"
37
+ ```
38
+
39
+ ### Google JSON Database
40
+
41
+ ```yaml
42
+ DB:
43
+ TYPE: google_json
44
+ BUCKET_NAME: "your-bucket-name"
45
+ SOURCE_BLOB_NAME: "data.json"
46
+ ```
47
+
48
+ ### GraphQL Database
49
+
50
+ ```yaml
51
+ DB:
52
+ TYPE: graph_ql
53
+ CMS_ENDPOINT: "https://your-graphql-endpoint.com/api"
54
+ CMS_TOKEN: "your_graphql_token"
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ The database is automatically initialized based on your configuration. The application will use the appropriate database implementation
60
+
61
+ ```python
62
+ from platzky.db.db_loader import get_db
63
+
64
+ # db_config is loaded from your application config
65
+ db = get_db(db_config)
66
+
67
+ # Now you can use any of the standard DB methods
68
+ posts = db.get_all_posts("en")
69
+ menu_items = db.get_menu_items_in_lang("en")
70
+ ```
@@ -0,0 +1,69 @@
1
+ import json
2
+
3
+ import requests
4
+ from github import Github
5
+ from pydantic import Field
6
+
7
+ from platzky.db.db import DBConfig
8
+ from platzky.db.json_db import Json as JsonDB
9
+
10
+
11
+ def db_config_type():
12
+ return GithubJsonDbConfig
13
+
14
+
15
+ class GithubJsonDbConfig(DBConfig):
16
+ github_token: str = Field(alias="GITHUB_TOKEN")
17
+ repo_name: str = Field(alias="REPO_NAME")
18
+ path_to_file: str = Field(alias="PATH_TO_FILE")
19
+ branch_name: str = Field(alias="BRANCH_NAME", default="main")
20
+
21
+
22
+ def db_from_config(config: GithubJsonDbConfig):
23
+ return GithubJsonDb(
24
+ config.github_token, config.repo_name, config.branch_name, config.path_to_file
25
+ )
26
+
27
+
28
+ def get_db(config):
29
+ github_json_db_config = GithubJsonDbConfig.model_validate(config)
30
+ return GithubJsonDb(
31
+ github_json_db_config.github_token,
32
+ github_json_db_config.repo_name,
33
+ github_json_db_config.branch_name,
34
+ github_json_db_config.path_to_file,
35
+ )
36
+
37
+
38
+ class GithubJsonDb(JsonDB):
39
+ def __init__(self, github_token: str, repo_name: str, branch_name: str, path_to_file: str):
40
+ self.branch_name = branch_name
41
+ self.repo = Github(github_token).get_repo(repo_name)
42
+ self.file_path = path_to_file
43
+
44
+ try:
45
+ file_content = self.repo.get_contents(self.file_path, ref=self.branch_name)
46
+
47
+ if isinstance(file_content, list):
48
+ raise ValueError(f"Path '{self.file_path}' points to a directory, not a file")
49
+
50
+ if file_content.content:
51
+ raw_data = file_content.decoded_content.decode("utf-8")
52
+ else:
53
+
54
+ download_url = file_content.download_url
55
+ response = requests.get(download_url, timeout=40)
56
+ response.raise_for_status()
57
+ raw_data = response.text
58
+
59
+ self.data = json.loads(raw_data)
60
+
61
+ except (json.JSONDecodeError, requests.RequestException) as e:
62
+ raise ValueError(f"Error parsing JSON content: {e}")
63
+ except Exception as e:
64
+ raise ValueError(f"Error retrieving GitHub content: {e}")
65
+
66
+ super().__init__(self.data)
67
+
68
+ self.module_name = "github_json_db"
69
+ self.db_name = "GithubJsonDb"
@@ -3,8 +3,8 @@ import json
3
3
  from google.cloud.storage import Client
4
4
  from pydantic import Field
5
5
 
6
- from .db import DBConfig
7
- from .json_db import Json
6
+ from platzky.db.db import DBConfig
7
+ from platzky.db.json_db import Json
8
8
 
9
9
 
10
10
  def db_config_type():
@@ -3,8 +3,8 @@ from typing import Any, Dict
3
3
 
4
4
  from pydantic import Field
5
5
 
6
- from ..models import MenuItem, Post
7
- from .db import DB, DBConfig
6
+ from platzky.db.db import DB, DBConfig
7
+ from platzky.models import MenuItem, Post
8
8
 
9
9
 
10
10
  def db_config_type():
@@ -2,8 +2,8 @@ import json
2
2
 
3
3
  from pydantic import Field
4
4
 
5
- from .db import DBConfig
6
- from .json_db import Json
5
+ from platzky.db.db import DBConfig
6
+ from platzky.db.json_db import Json
7
7
 
8
8
 
9
9
  def db_config_type():
@@ -18,7 +18,6 @@ class Engine(Flask):
18
18
  directory = os.path.dirname(os.path.realpath(__file__))
19
19
  locale_dir = os.path.join(directory, "locale")
20
20
  config.translation_directories.append(locale_dir)
21
-
22
21
  babel_translation_directories = ";".join(config.translation_directories)
23
22
  self.babel = Babel(
24
23
  self,
@@ -3,6 +3,7 @@ import urllib.parse
3
3
 
4
4
  from flask import redirect, render_template, request, session
5
5
  from flask_minify import Minify
6
+ from flask_wtf import CSRFProtect
6
7
 
7
8
  from platzky.admin import admin
8
9
  from platzky.blog import blog
@@ -81,10 +82,19 @@ def create_engine(config: Config, db) -> Engine:
81
82
 
82
83
 
83
84
  def create_app_from_config(config: Config) -> Engine:
84
- engine = create_engine_from_config(config)
85
+ db = get_db(config.db)
86
+ engine = create_engine(config, db)
87
+
85
88
  admin_blueprint = admin.create_admin_blueprint(
86
89
  login_methods=engine.login_methods, db=engine.db, locale_func=engine.get_locale
87
90
  )
91
+
92
+ if config.feature_flags and config.feature_flags.get("FAKE_LOGIN", False):
93
+ from platzky.admin.fake_login import get_fake_login_html, setup_fake_login_routes
94
+
95
+ engine.login_methods.append(get_fake_login_html())
96
+ admin_blueprint = setup_fake_login_routes(admin_blueprint)
97
+
88
98
  blog_blueprint = blog.create_blog_blueprint(
89
99
  db=engine.db,
90
100
  blog_prefix=config.blog_prefix,
@@ -98,15 +108,10 @@ def create_app_from_config(config: Config) -> Engine:
98
108
  engine.register_blueprint(seo_blueprint)
99
109
 
100
110
  Minify(app=engine, html=True, js=True, cssless=True)
111
+ CSRFProtect(app=engine)
101
112
  return engine
102
113
 
103
114
 
104
- def create_engine_from_config(config: Config) -> Engine:
105
- """Create an engine from a config."""
106
- db = get_db(config.db)
107
- return create_engine(config, db)
108
-
109
-
110
115
  def create_app(config_path: str) -> Engine:
111
116
  config = Config.parse_yaml(config_path)
112
117
  return create_app_from_config(config)
@@ -14,6 +14,17 @@
14
14
  {% block body_meta %}
15
15
  {% include "body_meta.html" %}
16
16
  {% endblock %}
17
+
18
+ {% with messages = get_flashed_messages(with_categories=true) %}
19
+ {% if messages %}
20
+ <div class="flash-messages">
21
+ {% for category, message in messages %}
22
+ <div class="alert alert-{{ category }}">{{ message }}</div>
23
+ {% endfor %}
24
+ </div>
25
+ {% endif %}
26
+ {% endwith %}
27
+
17
28
  <div class="container-fluid d-flex flex-column h-100 g-0">
18
29
  <div class="row header-row bg-light g-0">
19
30
  <nav class="navbar navbar-expand-lg navbar-light px-3 py-1" id="mainNav">
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "platzky"
3
- version = "0.3.1"
3
+ version = "0.3.4"
4
4
  description = "Not only blog engine"
5
5
  authors = []
6
6
  license = "MIT"
@@ -19,6 +19,7 @@ google-cloud-storage = "^2.5.0"
19
19
  humanize = "^4.9.0"
20
20
  pydantic = "^2.7.1"
21
21
  deprecation = "^2.1.0"
22
+ pygithub = "^2.6.1"
22
23
 
23
24
  [tool.poetry.group.dev.dependencies]
24
25
  pytest = "^8.2.1"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes