Compare commits
4 commits
354a473f77
...
107dbfaf7b
Author | SHA1 | Date | |
---|---|---|---|
107dbfaf7b | |||
bc22b17f2a | |||
804e07b555 | |||
bfea5eeb06 |
25 changed files with 644 additions and 49 deletions
5
.env
Normal file
5
.env
Normal file
|
@ -0,0 +1,5 @@
|
|||
MYSQL_HOST=${REPO_NAME_SNAKE}_db
|
||||
MUSQL_PORT=3306
|
||||
MYSQL_USER=${REPO_NAME_SNAKE}
|
||||
MYSQL_PASSWORD=
|
||||
MYSQL_DATABASE=${REPO_NAME_SNAKE}
|
5
.env_debug
Normal file
5
.env_debug
Normal file
|
@ -0,0 +1,5 @@
|
|||
MYSQL_HOST=localhost
|
||||
MUSQL_PORT=3306
|
||||
MYSQL_USER=darkcat09
|
||||
MYSQL_PASSWORD=
|
||||
MYSQL_DATABASE=apptest
|
6
.gitea/template
Normal file
6
.gitea/template
Normal file
|
@ -0,0 +1,6 @@
|
|||
app/*.py
|
||||
templates/base.html
|
||||
docker-compose.yml
|
||||
Makefile
|
||||
.env
|
||||
README.md
|
52
.gitignore
vendored
52
.gitignore
vendored
|
@ -72,56 +72,23 @@ instance/
|
|||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
#.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
# PEP 582
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
|
@ -129,13 +96,6 @@ ENV/
|
|||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
|
@ -153,10 +113,6 @@ dmypy.json
|
|||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
|
|
7
Dockerfile
Normal file
7
Dockerfile
Normal file
|
@ -0,0 +1,7 @@
|
|||
FROM python:3-alpine
|
||||
RUN apk update && apk upgrade && apk add py-pip make
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN pip install -r requirements.txt
|
||||
CMD make prod
|
||||
EXPOSE 8000
|
20
Makefile
Normal file
20
Makefile
Normal file
|
@ -0,0 +1,20 @@
|
|||
dev:
|
||||
DEBUG="true" python3 -m uvicorn main:app --reload
|
||||
|
||||
prod:
|
||||
python3 -m uvicorn main:app
|
||||
|
||||
format:
|
||||
python3 -m autopep8 -r --in-place app/
|
||||
|
||||
check:
|
||||
python3 -m mypy app/
|
||||
python3 -m pylint app/
|
||||
|
||||
docker:
|
||||
docker build -t ${REPO_OWNER_LOWER}/${REPO_NAME_SNAKE} .
|
||||
|
||||
clean:
|
||||
rm -rf app/__pycache__
|
||||
rm -rf __pycache__
|
||||
rm -rf .mypy_cache
|
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
24
app/common.py
Normal file
24
app/common.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import secrets
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from pydantic import BaseSettings
|
||||
|
||||
|
||||
file_dir = Path(__file__).parent
|
||||
|
||||
class Settings(BaseSettings):
|
||||
secret_key: str = secrets.token_hex(32)
|
||||
templates_dir: str = str(
|
||||
file_dir.parent / 'templates'
|
||||
)
|
||||
static_dir: str = str(
|
||||
file_dir.parent / 'static'
|
||||
)
|
||||
|
||||
settings = Settings()
|
||||
|
||||
|
||||
templates = Jinja2Templates(
|
||||
directory=settings.templates_dir,
|
||||
)
|
50
app/errors.py
Normal file
50
app/errors.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
"""Custom error pages for FastAPI app"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import Request, Response
|
||||
from fastapi import HTTPException
|
||||
|
||||
from . import paths
|
||||
from . import respond
|
||||
from .common import settings
|
||||
|
||||
# Add other HTTP error codes
|
||||
codes = [404, 500]
|
||||
|
||||
|
||||
class ErrorsPaths(paths.Paths):
|
||||
"""Sets up custom error pages,
|
||||
inherited from paths.Paths"""
|
||||
|
||||
def add_paths(self) -> None:
|
||||
|
||||
for code in codes:
|
||||
self.add_handler(code)
|
||||
|
||||
def add_handler(self, code: int) -> None:
|
||||
"""Adds an error handler to FastAPI app.
|
||||
Only for internal use!
|
||||
|
||||
Args:
|
||||
code (int): HTTP error code
|
||||
"""
|
||||
|
||||
tmpl_dir = (
|
||||
Path(__file__).parent /
|
||||
settings.templates_dir
|
||||
)
|
||||
file = tmpl_dir / f'{code}.html'
|
||||
|
||||
if not file.exists():
|
||||
return
|
||||
|
||||
@self.app.exception_handler(code)
|
||||
async def handler(req: Request, exc: HTTPException) -> Response:
|
||||
|
||||
return respond.with_tmpl(
|
||||
f'{code}.html',
|
||||
code=code,
|
||||
request=req,
|
||||
exc=exc,
|
||||
)
|
25
app/main.py
Normal file
25
app/main.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from typing import List, Type
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from .common import settings
|
||||
|
||||
# Add your paths here
|
||||
from .paths import Paths
|
||||
from . import pages
|
||||
from . import errors
|
||||
|
||||
paths: List[Type[Paths]] = [
|
||||
pages.MainPaths,
|
||||
errors.ErrorsPaths,
|
||||
]
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.mount(
|
||||
'/static',
|
||||
StaticFiles(directory=settings.static_dir),
|
||||
name='static',
|
||||
)
|
||||
for p in paths:
|
||||
p(app).add_paths()
|
20
app/pages.py
Normal file
20
app/pages.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
"""Main FastAPI paths"""
|
||||
|
||||
from fastapi import Request, Response
|
||||
|
||||
from . import paths
|
||||
from . import respond
|
||||
|
||||
|
||||
class MainPaths(paths.Paths):
|
||||
"""Main FastAPI app paths,
|
||||
inherits paths.Paths"""
|
||||
|
||||
def add_paths(self) -> None:
|
||||
|
||||
@self.app.get('/')
|
||||
def index(req: Request) -> Response:
|
||||
return respond.with_tmpl(
|
||||
'index.html',
|
||||
request=req,
|
||||
)
|
21
app/paths.py
Normal file
21
app/paths.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import abc
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
|
||||
class Paths(abc.ABC):
|
||||
"""Abstract class for storing paths for FastAPI app"""
|
||||
|
||||
def __init__(self, app: FastAPI) -> None:
|
||||
"""Abstract class for storing paths
|
||||
for FastAPI app
|
||||
|
||||
Args:
|
||||
app (FastAPI): Application object
|
||||
"""
|
||||
|
||||
self.app = app
|
||||
|
||||
@abc.abstractmethod
|
||||
def add_paths(self) -> None:
|
||||
"""Add paths to the FastAPI application"""
|
77
app/respond.py
Normal file
77
app/respond.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
import os
|
||||
import mimetypes
|
||||
from typing import Optional, Mapping
|
||||
|
||||
from fastapi import Response
|
||||
from fastapi.responses import FileResponse
|
||||
from starlette.background import BackgroundTask
|
||||
|
||||
from .common import templates
|
||||
|
||||
|
||||
def with_tmpl(
|
||||
name: str,
|
||||
code: int = 200,
|
||||
headers: Optional[Mapping[str, str]] = None,
|
||||
background: Optional[BackgroundTask] = None,
|
||||
**context) -> Response:
|
||||
"""Render a Jinja2 template and return Response object.
|
||||
`response_class` parameter is not needed
|
||||
|
||||
Args:
|
||||
name (str): Template filename
|
||||
code (int, optional): HTTP response code
|
||||
headers (Optional[Mapping[str, str]], optional):
|
||||
Additional headers, passed to Response constructor
|
||||
background (Optional[BackgroundTask], optional):
|
||||
Background task, passed to Response constructor
|
||||
|
||||
Returns:
|
||||
FastAPI's TemplateResponse object
|
||||
"""
|
||||
|
||||
return templates.TemplateResponse(
|
||||
name=name,
|
||||
context=context,
|
||||
status_code=code,
|
||||
headers=headers,
|
||||
background=background,
|
||||
)
|
||||
|
||||
|
||||
def with_file(
|
||||
path: os.PathLike,
|
||||
mime: Optional[str] = None,
|
||||
code: int = 200,
|
||||
headers: Optional[Mapping[str, str]] = None,
|
||||
background: Optional[BackgroundTask] = None) -> FileResponse:
|
||||
"""Send a file specified in `path`
|
||||
automatically guessing mimetype if `mime` is None
|
||||
|
||||
Args:
|
||||
path (os.PathLike): File path
|
||||
mime (Optional[str], optional): File mimetype
|
||||
code (int, optional): HTTP response code
|
||||
headers (Optional[Mapping[str, str]], optional):
|
||||
Additional headers, passed to Response constructor
|
||||
background (Optional[BackgroundTask], optional):
|
||||
Background task, passed to Response constructor
|
||||
|
||||
Returns:
|
||||
FileResponse: FastAPI's FileResponse object
|
||||
"""
|
||||
|
||||
return FileResponse(
|
||||
path=path,
|
||||
media_type=(
|
||||
mime or
|
||||
mimetypes.guess_type(path)[0]
|
||||
),
|
||||
status_code=code,
|
||||
headers=headers,
|
||||
background=background,
|
||||
)
|
||||
|
||||
|
||||
# Alias
|
||||
with_template = with_tmpl
|
19
docker-compose.yml
Normal file
19
docker-compose.yml
Normal file
|
@ -0,0 +1,19 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
${REPO_NAME_SNAKE}:
|
||||
image: ${REPO_OWNER}/${REPO_NAME_SNAKE}:latest
|
||||
container_name: ${REPO_NAME_SNAKE}
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8080:8000"
|
||||
links:
|
||||
- ${REPO_NAME_SNAKE}_db
|
||||
env_file: .env
|
||||
${REPO_NAME_SNAKE}_db:
|
||||
image: mariadb:latest
|
||||
container_name: ${REPO_NAME_SNAKE}_db
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- "./database:/var/lib/mysql"
|
||||
env_file: .env
|
9
main.py
Executable file
9
main.py
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import uvicorn
|
||||
|
||||
from app.main import app
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
uvicorn.run(app)
|
187
pylintrc
Normal file
187
pylintrc
Normal file
|
@ -0,0 +1,187 @@
|
|||
[MAIN]
|
||||
analyse-fallback-blocks=no
|
||||
extension-pkg-allow-list=
|
||||
extension-pkg-whitelist=
|
||||
fail-on=
|
||||
fail-under=10
|
||||
ignore=CVS
|
||||
ignore-paths=
|
||||
ignore-patterns=^\.#
|
||||
ignored-modules=
|
||||
jobs=4
|
||||
limit-inference-results=100
|
||||
load-plugins=
|
||||
persistent=yes
|
||||
py-version=3.10
|
||||
recursive=no
|
||||
suggestion-mode=yes
|
||||
unsafe-load-any-extension=no
|
||||
|
||||
[REPORTS]
|
||||
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
|
||||
msg-template=
|
||||
reports=no
|
||||
score=yes
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
confidence=HIGH,
|
||||
CONTROL_FLOW,
|
||||
INFERENCE,
|
||||
INFERENCE_FAILURE,
|
||||
UNDEFINED
|
||||
disable=raw-checker-failed,
|
||||
bad-inline-option,
|
||||
locally-disabled,
|
||||
file-ignored,
|
||||
suppressed-message,
|
||||
useless-suppression,
|
||||
deprecated-pragma,
|
||||
use-symbolic-message-instead
|
||||
enable=c-extension-no-member
|
||||
|
||||
[SIMILARITIES]
|
||||
ignore-comments=yes
|
||||
ignore-docstrings=yes
|
||||
ignore-imports=yes
|
||||
ignore-signatures=yes
|
||||
min-similarity-lines=4
|
||||
|
||||
[MISCELLANEOUS]
|
||||
notes=FIXME,
|
||||
XXX,
|
||||
TODO
|
||||
notes-rgx=
|
||||
|
||||
[DESIGN]
|
||||
exclude-too-few-public-methods=
|
||||
ignored-parents=
|
||||
max-args=5
|
||||
max-attributes=7
|
||||
max-bool-expr=5
|
||||
max-branches=12
|
||||
max-locals=15
|
||||
max-parents=7
|
||||
max-public-methods=20
|
||||
max-returns=6
|
||||
max-statements=50
|
||||
min-public-methods=1
|
||||
|
||||
[STRING]
|
||||
check-quote-consistency=no
|
||||
check-str-concat-over-line-jumps=no
|
||||
|
||||
[CLASSES]
|
||||
check-protected-access-in-special-methods=no
|
||||
defining-attr-methods=__init__,
|
||||
__new__,
|
||||
setUp,
|
||||
__post_init__
|
||||
exclude-protected=_asdict,
|
||||
_fields,
|
||||
_replace,
|
||||
_source,
|
||||
_make
|
||||
valid-classmethod-first-arg=cls
|
||||
valid-metaclass-classmethod-first-arg=cls
|
||||
|
||||
[FORMAT]
|
||||
expected-line-ending-format=
|
||||
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
||||
indent-after-paren=4
|
||||
indent-string=' '
|
||||
max-line-length=100
|
||||
max-module-lines=1000
|
||||
single-line-class-stmt=no
|
||||
single-line-if-stmt=no
|
||||
|
||||
[IMPORTS]
|
||||
allow-any-import-level=
|
||||
allow-wildcard-with-all=no
|
||||
deprecated-modules=
|
||||
ext-import-graph=
|
||||
import-graph=
|
||||
int-import-graph=
|
||||
known-standard-library=
|
||||
known-third-party=enchant
|
||||
preferred-modules=
|
||||
|
||||
[VARIABLES]
|
||||
additional-builtins=
|
||||
allow-global-unused-variables=yes
|
||||
allowed-redefined-builtins=
|
||||
callbacks=cb_,
|
||||
_cb
|
||||
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
|
||||
ignored-argument-names=_.*|^ignored_|^unused_
|
||||
init-import=no
|
||||
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
|
||||
|
||||
[LOGGING]
|
||||
logging-format-style=old
|
||||
logging-modules=logging
|
||||
|
||||
[EXCEPTIONS]
|
||||
overgeneral-exceptions=BaseException,
|
||||
Exception
|
||||
|
||||
[BASIC]
|
||||
argument-naming-style=snake_case
|
||||
attr-naming-style=snake_case
|
||||
bad-names=foo,
|
||||
bar,
|
||||
baz,
|
||||
toto,
|
||||
tutu,
|
||||
tata
|
||||
bad-names-rgxs=
|
||||
class-attribute-naming-style=any
|
||||
class-const-naming-style=UPPER_CASE
|
||||
class-naming-style=PascalCase
|
||||
const-naming-style=any
|
||||
docstring-min-length=-1
|
||||
function-naming-style=snake_case
|
||||
good-names=i,
|
||||
j,
|
||||
k,
|
||||
f,
|
||||
db,
|
||||
ex,
|
||||
Run,
|
||||
_
|
||||
good-names-rgxs=
|
||||
include-naming-hint=no
|
||||
inlinevar-naming-style=any
|
||||
method-naming-style=snake_case
|
||||
module-naming-style=snake_case
|
||||
name-group=
|
||||
no-docstring-rgx=^_
|
||||
property-classes=abc.abstractproperty
|
||||
variable-naming-style=snake_case
|
||||
|
||||
[SPELLING]
|
||||
max-spelling-suggestions=4
|
||||
spelling-dict=
|
||||
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
|
||||
spelling-ignore-words=
|
||||
spelling-private-dict-file=
|
||||
spelling-store-unknown-words=no
|
||||
|
||||
[TYPECHECK]
|
||||
contextmanager-decorators=contextlib.contextmanager
|
||||
generated-members=
|
||||
ignore-none=yes
|
||||
ignore-on-opaque-inference=yes
|
||||
ignored-checks-for-mixins=no-member,
|
||||
not-async-context-manager,
|
||||
not-context-manager,
|
||||
attribute-defined-outside-init
|
||||
ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
|
||||
missing-member-hint=yes
|
||||
missing-member-hint-distance=1
|
||||
missing-member-max-choices=1
|
||||
mixin-class-rgx=.*[Mm]ixin
|
||||
signature-mutators=
|
||||
|
||||
[REFACTORING]
|
||||
max-nested-blocks=5
|
||||
never-returning-functions=sys.exit,argparse.parse_error
|
5
requirements.txt
Normal file
5
requirements.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
fastapi==0.92.0
|
||||
uvicorn[standard]==0.20.0
|
||||
jinja2==3.1.2
|
||||
starlette-wtf==0.4.3
|
||||
python-dotenv==0.21.1
|
60
static/css/style.css
Normal file
60
static/css/style.css
Normal file
|
@ -0,0 +1,60 @@
|
|||
body {
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: sans-serif;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
--bg: #fff;
|
||||
--fg: #000;
|
||||
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
--bg: #202023;
|
||||
--fg: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
header { margin-top: 5px; }
|
||||
footer { margin-bottom: 5px; }
|
||||
|
||||
a {
|
||||
color: #5b8a55;
|
||||
}
|
||||
a:hover {
|
||||
filter: brightness(120%);
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
form > div {
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: end;
|
||||
}
|
||||
form > div > label {
|
||||
width: 50%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
form > div > input {
|
||||
width: 50%;
|
||||
border: 1px solid var(--fg);
|
||||
outline: none;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
form > div > input:hover,
|
||||
form > div > input:focus {
|
||||
filter: brightness(130%);
|
||||
}
|
4
static/js/script.js
Normal file
4
static/js/script.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
addEventListener('load', () => {
|
||||
document.getElementById('js')
|
||||
.innerText = new Date().toLocaleString()
|
||||
})
|
11
templates/404.html
Normal file
11
templates/404.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}404{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>404: Not Found</h1>
|
||||
<p>
|
||||
Go to the
|
||||
<a href="/">main page</a>
|
||||
</p>
|
||||
{% endblock %}
|
15
templates/500.html
Normal file
15
templates/500.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}500{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>500: ISE</h1>
|
||||
<p>
|
||||
An error occured while
|
||||
processing your request.
|
||||
</p>
|
||||
<p>
|
||||
Please, try again later
|
||||
or contact web site admin.
|
||||
</p>
|
||||
{% endblock %}
|
21
templates/admin.html
Normal file
21
templates/admin.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Admin{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Add a person to DB</h1>
|
||||
<form action="/add" method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
{% for field in form %}
|
||||
{% if field.name != 'csrf_token' %}
|
||||
<div>
|
||||
{{ field.label }}
|
||||
{{ field }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div>
|
||||
<input type="submit" value="Add">
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
18
templates/base.html
Normal file
18
templates/base.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="${REPO_DESCRIPTION}">
|
||||
<title>{% block title %}{% endblock %} | ${REPO_NAME}</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
<script src="/static/js/script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>${REPO_NAME}</header>
|
||||
<article>
|
||||
{% block content %}{% endblock %}
|
||||
</article>
|
||||
<footer id="js"></footer>
|
||||
</body>
|
||||
</html>
|
12
templates/index.html
Normal file
12
templates/index.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Main page{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
This is the default main page of
|
||||
<a href="https://git.dc09.ru/DarkCat09/tmpl-fastapi">
|
||||
FastAPI app template
|
||||
</a>
|
||||
</h1>
|
||||
{% endblock %}
|
18
templates/table.html
Normal file
18
templates/table.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Database{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Sample database</h1>
|
||||
<table>
|
||||
<tbody>
|
||||
{% for row in rows %}
|
||||
<tr>
|
||||
{% for cell in row %}
|
||||
<td>{{ cell }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
Loading…
Add table
Reference in a new issue