Skip to content

Commit

Permalink
Add create project page (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
berrydenhartog authored Jul 18, 2024
2 parents e92972f + d95f93a commit fb8ebe4
Show file tree
Hide file tree
Showing 101 changed files with 1,578 additions and 344 deletions.
8 changes: 6 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
},
"forwardPorts": [8000,8080],
"forwardPorts": [
8000,
8080
],
"postCreateCommand": ".devcontainer/postCreateCommand.sh",
"customizations": {
"vscode": {
Expand All @@ -25,7 +28,8 @@
"mhutchie.git-graph",
"markis.code-coverage",
"qwtel.sqlite-viewer",
"ms-vsliveshare.vsliveshare"
"ms-vsliveshare.vsliveshare",
"wholroyd.jinja"
],
"settings": {
"editor.formatOnPaste": false,
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ __pypackages__/

# pytest
.pytest_cache/
pytestdebug.log

# ruff linter
.ruff_cache/
Expand All @@ -36,3 +37,7 @@ __pypackages__/
tad.log*
database.sqlite3
output/


# ignore playwright
test-results/
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"mhutchie.git-graph",
"markis.code-coverage",
"qwtel.sqlite-viewer",
"ms-vsliveshare.vsliveshare"
"ms-vsliveshare.vsliveshare",
"wholroyd.jinja"
]
}
3 changes: 3 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
"args": [
"--log-level",
"warning",
"--reload",
"--port",
"8000",
"tad.main:app"
],
"cwd": "${workspaceFolder}/",
Expand Down
2 changes: 1 addition & 1 deletion babel-mapping.ini
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[jinja2: **/templates/**.jinja]
[jinja2: **/templates/**.j2]
extensions=jinja2.ext.i18n
silent=False
2 changes: 1 addition & 1 deletion compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ services:
environment:
- ENVIRONMENT=demo
ports:
- 8000:8000
- 8070:8000
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8000/health/live || exit 1"]

Expand Down
43 changes: 36 additions & 7 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ psycopg2-binary = "^2.9.9"
uvicorn = {extras = ["standard"], version = "^0.30.1"}
pyyaml = "^6.0.1"
babel = "^2.15.0"
httpx = "^0.27.0"


[tool.poetry.group.test.dependencies]
Expand All @@ -40,12 +41,15 @@ coverage = "^7.6.0"
httpx = "^0.27.0"
playwright = "^1.45.0"
pytest-playwright = "^0.5.1"
pytest-httpx = "^0.30.0"

[tool.poetry.group.dev.dependencies]
ruff = "^0.5.2"
pre-commit = "^3.7.0"
pyright = "^1.1.371"
liccheck = "^0.9.2"
types-pyyaml = "^6.0.12.20240311"


[tool.poetry.urls]
"Issue Tracker" = "https://github.com/MinBZK/tad/issues"
Expand All @@ -64,13 +68,13 @@ src = ["tad","tests"]
line-ending = "lf"

[tool.ruff.lint]
select = ["I", "SIM", "B", "UP", "F", "E", "S", "C90", "DTZ", "LOG", "PIE", "PT", "ERA", "W", "C", "TRY", "RUF"]
select = ["I", "SIM", "B", "UP", "F", "E", "S", "C90", "DTZ", "LOG", "PIE", "PT", "ERA", "W", "C", "TRY", "RUF", "ANN"]
fixable = ["ALL"]
task-tags = ["TODO"]
ignore = ["TRY003"]
ignore = ["TRY003", "ANN101"]

[tool.ruff.lint.per-file-ignores]
"tests/**.py" = ["S101"]
"tests/**.py" = ["S101", "ANN201"]

[tool.ruff.lint.mccabe]
max-complexity = 8
Expand Down
9 changes: 5 additions & 4 deletions script/pybabel-for-tad
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import re
import sys
from typing import Any

from babel.core import get_global
from babel.messages.frontend import main


def hack_babel(custom_locales: dict):
def hack_babel(custom_locales: dict[str, str]) -> None:
"""Hack Babel core to make it support custom locale names.
Copied from https://github.com/python-babel/babel/issues/454
Expand All @@ -20,7 +21,7 @@ def hack_babel(custom_locales: dict):
# This database is pickle-loaded from a .dat file and cached, so we only have to do it once.
db = get_global("likely_subtags")
for custom_name in custom_locales:
db[custom_name] = custom_name
db[custom_name] = custom_name # pyright: ignore [reportIndexIssue]

# Also, monkey-patch the exists() and load() functions that load locale data from 'babel/locale-data'
import babel.localedata
Expand All @@ -31,12 +32,12 @@ def hack_babel(custom_locales: dict):
# Make sure we do not patch twice
if o_exists.__module__ != __name__:
# Definitions
def exists(name):
def exists(name: str) -> bool:
# Convert custom names to normalized names
name = custom_locales.get(name, name)
return o_exists(name)

def load(name, merge_inherited=True):
def load(name: str, merge_inherited: bool = True) -> dict[str, Any]:
# Convert custom names to normalized names
name = custom_locales.get(name, name)
return o_load(name, merge_inherited)
Expand Down
54 changes: 40 additions & 14 deletions tad/api/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
from os import PathLike

from fastapi import Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from jinja2 import Environment
from starlette.templating import _TemplateResponse # type: ignore
from jinja2 import Environment, StrictUndefined, Undefined
from starlette.background import BackgroundTask
from starlette.templating import _TemplateResponse # pyright: ignore [reportPrivateUsage]

from tad.core.config import VERSION
from tad.core.config import VERSION, get_settings
from tad.core.internationalization import (
format_datetime,
get_dynamic_field_translations,
Expand All @@ -30,24 +32,48 @@ def custom_context_processor(request: Request) -> dict[str, str | list[str] | di
}


def get_undefined_behaviour() -> type[Undefined]:
return StrictUndefined if get_settings().DEBUG else Undefined


# we use a custom override so we can add the translation per request, which is parsed in the Request object in kwargs
class LocaleJinja2Templates(Jinja2Templates):
def _create_env(
self,
directory: str | PathLike[typing.AnyStr] | typing.Sequence[str | PathLike[typing.AnyStr]],
**env_options: typing.Any,
**env_options: typing.Any, # noqa: ANN401
) -> Environment:
env: Environment = super()._create_env(directory, **env_options) # type: ignore
env.add_extension("jinja2.ext.i18n") # type: ignore
return env # type: ignore
env: Environment = super()._create_env(directory, **env_options) # pyright: ignore [reportUnknownMemberType, reportUnknownVariableType]
env.add_extension("jinja2.ext.i18n") # pyright: ignore [reportUnknownMemberType]
return env # pyright: ignore [reportUnknownVariableType]

def TemplateResponse(self, *args: typing.Any, **kwargs: typing.Any) -> _TemplateResponse:
content_language = get_supported_translation(get_requested_language(kwargs["request"]))
def TemplateResponse( # pyright: ignore [reportIncompatibleMethodOverride]
self,
request: Request,
name: str,
context: dict[str, typing.Any] | None = None,
status_code: int = 200,
headers: dict[str, str] | None = None,
media_type: str | None = None,
background: BackgroundTask | None = None,
) -> _TemplateResponse:
content_language = get_supported_translation(get_requested_language(request))
translations = get_translation(content_language)
kwargs["headers"] = {"Content-Language": ",".join(supported_translations)}
self.env.install_gettext_translations(translations, newstyle=True) # type: ignore
return super().TemplateResponse(*args, **kwargs)
if headers is not None and "Content-Language" not in headers:
headers["Content-Language"] = ",".join(supported_translations)
self.env.install_gettext_translations(translations, newstyle=True) # pyright: ignore [reportUnknownMemberType]

if context is None:
context = {}

return super().TemplateResponse(request, name, context, status_code, headers, media_type, background)

templates = LocaleJinja2Templates(directory="tad/site/templates/", context_processors=[custom_context_processor])
templates.env.filters["format_datetime"] = format_datetime # type: ignore
def Redirect(self, request: Request, url: str) -> HTMLResponse:
headers = {"HX-Redirect": url}
return self.TemplateResponse(request, "redirect.html.j2", headers=headers)


templates = LocaleJinja2Templates(
directory="tad/site/templates/", context_processors=[custom_context_processor], undefined=get_undefined_behaviour()
)
templates.env.filters["format_datetime"] = format_datetime # pyright: ignore [reportUnknownMemberType]
4 changes: 3 additions & 1 deletion tad/api/main.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from fastapi import APIRouter

from tad.api.routes import health, pages, root, tasks
from tad.api.routes import health, pages, project, projects, root, tasks

api_router = APIRouter()
api_router.include_router(root.router)
api_router.include_router(health.router, prefix="/health", tags=["health"])
api_router.include_router(pages.router, prefix="/pages", tags=["pages"])
api_router.include_router(tasks.router, prefix="/tasks", tags=["tasks"])
api_router.include_router(projects.router, prefix="/projects", tags=["projects"])
api_router.include_router(project.router, prefix="/project", tags=["projects"])
4 changes: 2 additions & 2 deletions tad/api/routes/health.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@


@router.get("/live", response_class=JSONResponse)
async def liveness():
async def liveness() -> dict[str, str]:
return {"status": "ok"}


@router.get("/ready", response_class=JSONResponse)
async def readiness():
async def readiness() -> dict[str, str]:
# todo(berry): Add database connection check
return {"status": "ok"}
4 changes: 2 additions & 2 deletions tad/api/routes/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ async def default_layout(
request: Request,
status_service: Annotated[StatusesService, Depends(StatusesService)],
tasks_service: Annotated[TasksService, Depends(TasksService)],
):
) -> HTMLResponse:
context = {
"page_title": "This is the page title",
"tasks_service": tasks_service,
"statuses_service": status_service,
}
return templates.TemplateResponse(request=request, name="index.html.jinja", context=context)
return templates.TemplateResponse(request, "pages/index.html.j2", context)
35 changes: 35 additions & 0 deletions tad/api/routes/project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import logging
from typing import Annotated

from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse

from tad.api.deps import templates
from tad.services.projects import ProjectsService
from tad.services.statuses import StatusesService
from tad.services.tasks import TasksService

router = APIRouter()
logger = logging.getLogger(__name__)


@router.get("/{project_id}")
async def get_root(
request: Request,
project_id: int,
projects_service: Annotated[ProjectsService, Depends(ProjectsService)],
status_service: Annotated[StatusesService, Depends(StatusesService)],
tasks_service: Annotated[TasksService, Depends(TasksService)],
) -> HTMLResponse:
project = projects_service.get(project_id)

context = {
"tasks_service": tasks_service,
"statuses_service": status_service,
"project": project,
}

if not project:
return templates.TemplateResponse(request, "pages/404.html.j2")

return templates.TemplateResponse(request, "pages/index.html.j2", context)
Loading

0 comments on commit fb8ebe4

Please sign in to comment.