19 Commits

Author SHA1 Message Date
8aa8eda6ce Tweaks 2024-05-23 06:54:48 +02:00
2d56d06649 Dockerize 2024-05-18 19:19:16 +02:00
382e514d03 City as select 2024-05-16 21:40:28 +02:00
fc6b3a7fa0 Validation 2024-05-16 07:55:27 +02:00
2fc2d07a7d Persons constraint 2024-05-16 07:35:01 +02:00
178377cfb6 Finished 2024-05-15 22:56:30 +02:00
af61d45fa4 Basic table inline edit functionality 2024-05-15 22:16:59 +02:00
7c20d4d23e BS icons 2024-05-15 21:56:28 +02:00
4ca502c6bc Finish filter list 2024-05-15 17:25:58 +02:00
4c7b18d07b Filter list with db model 2024-05-14 22:34:21 +02:00
1456ba8538 Cat breeds 2024-04-16 15:23:08 +02:00
f06b72343b Reformat 2024-04-07 10:41:21 +02:00
76e81be6bf JS alert toasts 2024-04-07 10:13:41 +02:00
84a34d8049 Local settings 2024-04-07 09:51:49 +02:00
126caff1ba Toast 2024-04-07 09:30:57 +02:00
b33ed21949 Warehouse management 2024-04-07 00:08:09 +02:00
1b1d0f1b5c Reports 2024-04-06 23:32:41 +02:00
5e1989a60f Complex form 2024-04-06 22:04:57 +02:00
ad14f2fe12 Complex form 2024-04-06 19:42:32 +02:00
50 changed files with 8966 additions and 60 deletions

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
**/*.pyc

1
.gitignore vendored
View File

@ -2,4 +2,5 @@
/.vscode
/.venv
__pycache__
/project/settings_local.py
/db.sqlite3

51
Dockerfile Normal file
View File

@ -0,0 +1,51 @@
FROM python:3.11-slim-bookworm AS env-builder
WORKDIR /app
COPY pyproject.toml .
COPY poetry.lock .
# create virtual environment
RUN python -m venv /venv
# set python thingies, set environment variables and activate virtual environment
ENV \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/venv/bin:$PATH"
RUN \
pip install poetry && \
# dump python dependencies into requirements file
poetry export --without-hashes --format=requirements.txt > requirements.txt && \
# install python libs
pip install -r requirements.txt --no-cache-dir --prefer-binary --no-deps --no-compile
FROM python:3.11-slim-bookworm
WORKDIR /app
COPY --from=env-builder /venv /venv
# set python thingies and activate virtual environment
ENV \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/venv/bin:$PATH"
COPY manage.py .
COPY pyproject.toml .
COPY poetry.lock .
COPY db.template.sqlite3 db.sqlite3
COPY ./project ./project
# run as user www-data
USER 33
ENTRYPOINT [ "/venv/bin/gunicorn" ]
CMD [ \
"--bind", "0.0.0.0:8000", \
"--workers", "4", \
"project.wsgi" \
]

View File

@ -1,3 +1,7 @@
CONTAINER_NAME=django-htmx
IMAGE_NAME=django-htmx
ifeq ($(VIRTUAL_ENV),)
RUN_IN_ENV=poetry run
else
@ -19,3 +23,20 @@ migrations:
migrate:
@ $(RUN_IN_ENV) python manage.py migrate
docker-build:
- @docker image rm $(IMAGE_NAME) --force
@docker \
build . \
-t $(IMAGE_NAME)
@docker \
build . \
-t $(IMAGE_NAME)
docker-run:
@docker run \
--publish 8000:8000 \
--name $(CONTAINER_NAME) \
$(IMAGE_NAME)

View File

@ -1 +1,36 @@
# Django-htmx demo
## Run demo
### As docker container
```
make docker-build
make docker-run
```
Browse to [localhost:8000](http://localhost:8000).
Later, start and stop docker container using:
```
docker start django-htmx
```
and
```
docker stop django-htmx
```
### As standard local Django app
- requirements: Python 3.10 or 3.11
- [poetry](https://python-poetry.org)
```
poetry install
make run
```
Browse to [localhost:8000](http://localhost:8000).

BIN
db.template.sqlite3 Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

58
poetry.lock generated
View File

@ -19,13 +19,13 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
[[package]]
name = "django"
version = "5.0.4"
version = "5.0.6"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
optional = false
python-versions = ">=3.10"
files = [
{file = "Django-5.0.4-py3-none-any.whl", hash = "sha256:916423499d75d62da7aa038d19aef23d23498d8df229775eb0a6309ee1013775"},
{file = "Django-5.0.4.tar.gz", hash = "sha256:4bd01a8c830bb77a8a3b0e7d8b25b887e536ad17a81ba2dce5476135c73312bd"},
{file = "Django-5.0.6-py3-none-any.whl", hash = "sha256:8363ac062bb4ef7c3f12d078f6fa5d154031d129a15170a1066412af49d30905"},
{file = "Django-5.0.6.tar.gz", hash = "sha256:ff1b61005004e476e0aeea47c7f79b85864c70124030e95146315396f1e7951f"},
]
[package.dependencies]
@ -52,15 +52,35 @@ files = [
django = ">=3.2"
jinja2 = ">=3"
[[package]]
name = "gunicorn"
version = "21.2.0"
description = "WSGI HTTP Server for UNIX"
optional = false
python-versions = ">=3.5"
files = [
{file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"},
{file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"},
]
[package.dependencies]
packaging = "*"
[package.extras]
eventlet = ["eventlet (>=0.24.1)"]
gevent = ["gevent (>=1.4.0)"]
setproctitle = ["setproctitle"]
tornado = ["tornado (>=0.2)"]
[[package]]
name = "jinja2"
version = "3.1.3"
version = "3.1.4"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
files = [
{file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
{file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
]
[package.dependencies]
@ -138,21 +158,31 @@ files = [
{file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
]
[[package]]
name = "packaging"
version = "24.0"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.7"
files = [
{file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
{file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
]
[[package]]
name = "sqlparse"
version = "0.4.4"
version = "0.5.0"
description = "A non-validating SQL parser."
optional = false
python-versions = ">=3.5"
python-versions = ">=3.8"
files = [
{file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"},
{file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"},
{file = "sqlparse-0.5.0-py3-none-any.whl", hash = "sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663"},
{file = "sqlparse-0.5.0.tar.gz", hash = "sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93"},
]
[package.extras]
dev = ["build", "flake8"]
dev = ["build", "hatch"]
doc = ["sphinx"]
test = ["pytest", "pytest-cov"]
[[package]]
name = "typing-extensions"
@ -178,5 +208,5 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "5221e53a0bd37605e95d6feb1c511e8a66ea71fbc94c47d0651a1846aea3a6dc"
python-versions = ">= 3.10, < 3.12"
content-hash = "26984438b0e835c052014186db2a76cbe044e19fd5b56ba3fc3d642b2ed530dc"

View File

@ -1,3 +1,4 @@
import random
from typing import Any
from jinja2 import Environment
@ -11,10 +12,17 @@ def conditional_cls(conditions: dict[str, Any]) -> str:
return " ".join(result)
def random_id() -> str:
return f"_{random.randint(0, 10**8)}"
def environment(**options):
env = Environment(**options)
env.globals.update({
env.globals.update(
{
"conditional_cls": conditional_cls,
})
"random_id": random_id,
}
)
return env

View File

@ -1,6 +1,21 @@
import os
import shutil
from django.apps import AppConfig
from django.conf import settings
class MainConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "main"
name = "project.main"
def ready(self):
"""copy template database if db not exists"""
db_fname = settings.DATABASES["default"]["NAME"]
template_db_fname = settings.BASE_DIR / "db.template.sqlite3"
if os.path.exists(db_fname):
return
shutil.copyfile(template_db_fname, db_fname)

View File

@ -0,0 +1,34 @@
# Generated by Django 5.0.4 on 2024-05-14 20:16
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="CatBreed",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
("country", models.CharField(max_length=100)),
("origin", models.CharField(max_length=100)),
("coat", models.CharField(max_length=100)),
("pattern", models.CharField(max_length=100)),
],
options={
"db_table": "cat_breeds",
},
),
]

View File

@ -0,0 +1,717 @@
from dataclasses import dataclass
from django.db import migrations
@dataclass
class Breed:
name: str
country: str
origin: str
coat: str
pattern: str
cat_breeds = [
Breed(
name="Abyssinian",
country="Ethiopia",
origin="Natural/Standard",
coat="Short",
pattern="Ticked",
),
Breed(
name="Aegean",
country="Greece",
origin="Natural/Standard",
coat="Semi-long",
pattern="Bi- or tri-colored",
),
Breed(
name="American Curl",
country="United States",
origin="Mutation",
coat="Short/Long",
pattern="All",
),
Breed(
name="American Bobtail",
country="United States",
origin="Mutation",
coat="Short/Long",
pattern="All",
),
Breed(
name="American Shorthair",
country="United States",
origin="Natural",
coat="Short",
pattern="All but colorpoint",
),
Breed(
name="American Wirehair",
country="United States",
origin="Mutation",
coat="Rex",
pattern="All but colorpoint",
),
Breed(
name="Arabian Mau",
country="Arabian Peninsula",
origin="Natural",
coat="Short",
pattern="",
),
Breed(
name="Australian Mist",
country="Australia",
origin="Crossbreed",
coat="Short",
pattern="Spotted and Classic tabby",
),
Breed(
name="Asian",
country="United Kingdom",
origin="",
coat="Short",
pattern="Evenly solid",
),
Breed(
name="Asian Semi-longhair",
country="United Kingdom",
origin="Crossbreed",
coat="Semi-long",
pattern="Solid",
),
Breed(
name="Balinese",
country="United States",
origin="Crossbreed",
coat="Long",
pattern="Colorpoint",
),
Breed(
name="Bambino",
country="United States",
origin="Crossbreed",
coat="Hairless/Furry down",
pattern="",
),
Breed(
name="Bengal",
country="United States",
origin="Hybrid",
coat="Short",
pattern="Spotted/Marbled",
),
Breed(
name="Birman",
country="France",
origin="Natural",
coat="Semi Long",
pattern="Colorpoint",
),
Breed(
name="Bombay",
country="United States",
origin="Crossbred",
coat="Short",
pattern="Solid",
),
Breed(
name="Brazilian Shorthair",
country="Brazil",
origin="Natural",
coat="Short",
pattern="All",
),
Breed(
name="British Semi-longhair",
country="United Kingdom",
origin="",
coat="Medium",
pattern="All",
),
Breed(
name="British Shorthair",
country="United Kingdom",
origin="Natural",
coat="Short",
pattern="All",
),
Breed(
name="British Longhair",
country="United Kingdom",
origin="",
coat="Long",
pattern="",
),
Breed(
name="Burmese",
country="Burma and Thailand",
origin="Natural",
coat="Short",
pattern="Solid",
),
Breed(
name="Burmilla",
country="United Kingdom",
origin="Crossbreed",
coat="Short/Long",
pattern="",
),
Breed(
name="California Spangled",
country="United States",
origin="Crossbreed",
coat="Short",
pattern="Spotted",
),
Breed(
name="Chantilly-Tiffany",
country="United States",
origin="",
coat="",
pattern="",
),
Breed(
name="Chartreux",
country="France",
origin="Natural",
coat="Short",
pattern="Solid",
),
Breed(
name="Chausie",
country="France",
origin="Hybrid",
coat="Short",
pattern="Ticked",
),
Breed(
name="Cheetoh",
country="United States",
origin="Hybrid Crossbreed",
coat="Short",
pattern="Spotted",
),
Breed(
name="Cornish Rex",
country="United Kingdom",
origin="Mutation",
coat="Rex",
pattern="All",
),
Breed(
name="Cymric or Manx Longhair",
country="United Kingdom",
origin="Natural/Mutation",
coat="Long",
pattern="",
),
Breed(
name="Cyprus",
country="Cyprus",
origin="Natural",
coat="All",
pattern="All",
),
Breed(
name="Devon Rex",
country="United Kingdom",
origin="Mutation",
coat="Rex",
pattern="All",
),
Breed(
name="Donskoy, or Don Sphynx",
country="Russia",
origin="",
coat="Hairless",
pattern="",
),
Breed(
name="Dragon Li",
country="China",
origin="Natural",
coat="Short",
pattern="Striped tabby",
),
Breed(
name="Dwarf cat, or Dwelf",
country="",
origin="Crossbreed",
coat="",
pattern="Hairless",
),
Breed(
name="Egyptian Mau",
country="Egypt",
origin="Natural",
coat="Short",
pattern="Spotted",
),
Breed(
name="European Shorthair",
country="Finland and Sweden",
origin="Natural",
coat="Short",
pattern="",
),
Breed(
name="Exotic Shorthair",
country="United States",
origin="Crossbreed",
coat="Short",
pattern="All",
),
Breed(
name="Foldex[4]",
country="Canada",
origin="Crossbreed",
coat="Short",
pattern="All",
),
Breed(
name="German Rex",
country="East Germany",
origin="Mutation",
coat="Rex",
pattern="",
),
Breed(
name="Havana Brown",
country="United Kingdom",
origin="",
coat="Short",
pattern="Solid",
),
Breed(
name="Highlander",
country="United States",
origin="Crossbreed",
coat="Short/Long",
pattern="All",
),
Breed(
name="Himalayan, or Colorpoint Persian",
country="United States/United Kingdom",
origin="Crossbreed",
coat="Long",
pattern="Colorpoint",
),
Breed(
name="Japanese Bobtail",
country="Japan",
origin="Natural",
coat="Short/Long",
pattern="All but colorpoint and ticked",
),
Breed(
name="Javanese",
country="United States",
origin="Crossbreed",
coat="Long/Short",
pattern="Colorpoint",
),
Breed(
name="Karelian Bobtail",
country="Western Russia",
origin="Natural",
coat="",
pattern="",
),
Breed(
name="Khao Manee",
country="Thailand",
origin="Natural",
coat="Short",
pattern="Solid",
),
Breed(
name="Korat",
country="Thailand",
origin="Natural",
coat="Short",
pattern="Solid",
),
Breed(
name="Korean Bobtail",
country="Korea",
origin="Natural",
coat="Short/Long",
pattern="Colorprint",
),
Breed(
name="Korn Ja",
country="Thailand",
origin="Natural",
coat="Short/Hairless",
pattern="Solid",
),
Breed(
name="Kurilian Bobtail, or Kuril Islands Bobtail",
country="Eastern Russia,Japan",
origin="Natural",
coat="Short/Long",
pattern="",
),
Breed(
name="LaPerm",
country="United States",
origin="Mutation",
coat="Rex",
pattern="All",
),
Breed(
name="Lykoi",
country="United States",
origin="Natural/Mutation",
coat="Partly Hairless",
pattern="Ticked",
),
Breed(
name="Maine Coon",
country="United States",
origin="Natural",
coat="Long",
pattern="All but colorpoint and ticked",
),
Breed(
name="Manx",
country="United Kingdom",
origin="Mutation",
coat="Short/Long",
pattern="All but colorpoint",
),
Breed(
name="Mekong Bobtail",
country="Russia",
origin="Natural/Mutation",
coat="Short",
pattern="Colorpoint",
),
Breed(
name="Minskin",
country="United States",
origin="Crossbreed",
coat="Short/Hairless",
pattern="All",
),
Breed(
name="Munchkin",
country="United States",
origin="Mutation",
coat="",
pattern="",
),
Breed(
name="Nebelung",
country="United States",
origin="",
coat="Semi-long",
pattern="Solid",
),
Breed(
name="Napoleon",
country="",
origin="",
coat="Long/short",
pattern="Varied",
),
Breed(
name="Norwegian Forest cat",
country="Norway",
origin="Natural",
coat="Long",
pattern="All but colorpoint",
),
Breed(
name="Ocicat",
country="United States",
origin="Crossbreed",
coat="Short",
pattern="Spotted",
),
Breed(
name="Ojos Azules",
country="United States",
origin="",
coat="",
pattern="",
),
Breed(
name="Oregon Rex",
country="United States",
origin="Mutation",
coat="Rex",
pattern="",
),
Breed(
name="Oriental Bicolor",
country="",
origin="",
coat="",
pattern="Bicolor",
),
Breed(
name="Oriental Shorthair",
country="",
origin="",
coat="Short",
pattern="All but colorpoint",
),
Breed(
name="Oriental Longhair",
country="",
origin="",
coat="Semi-long",
pattern="",
),
Breed(
name="PerFoldæ(Experimental Breed - WCF),",
country="Europe",
origin="Crossbreed",
coat="Long",
pattern="All",
),
Breed(
name="Persian (Modern Persian Cat),",
country="Iran (Persia),",
origin="Crossbreed",
coat="Long",
pattern="All",
),
Breed(
name="Persian (Traditional Persian Cat),",
country="Greater Iran",
origin="Natural",
coat="Long",
pattern="All",
),
Breed(
name="Peterbald",
country="Russia",
origin="Crossbreed",
coat="Hairless",
pattern="All",
),
Breed(
name="Pixie-bob",
country="United States",
origin="Natural",
coat="Short",
pattern="Spotted",
),
Breed(
name="Raas",
country="Indonesia",
origin="Natural",
coat="Short",
pattern="",
),
Breed(
name="Ragamuffin",
country="United States",
origin="Crossbreed",
coat="Long",
pattern="All",
),
Breed(
name="Ragdoll",
country="United States",
origin="Crossbreed",
coat="Long",
pattern="Colorpoint/Mitted/Bicolor",
),
Breed(
name="Russian Blue",
country="Russia",
origin="Natural",
coat="Short",
pattern="Solid",
),
Breed(
name="Russian White, Black and Tabby",
country="Australia",
origin="Crossbreed",
coat="Short",
pattern="",
),
Breed(
name="Sam Sawet",
country="Thailand",
origin="Natural",
coat="Short",
pattern="Solid",
),
Breed(
name="Savannah",
country="United States",
origin="Hybrid",
coat="Short",
pattern="Spotted",
),
Breed(
name="Scottish Fold",
country="United Kingdom",
origin="Natural/Mutation",
coat="Short/Long",
pattern="All",
),
Breed(
name="Selkirk Rex",
country="United States",
origin="Mutation/Cross",
coat="Rex (Short/Long),",
pattern="All",
),
Breed(
name="Serengeti",
country="United States",
origin="Hybrid Crossbreed",
coat="Short",
pattern="Spotted",
),
Breed(
name="Serrade petit",
country="France",
origin="Natural",
coat="Short",
pattern="",
),
Breed(
name="Siamese",
country="Thailand",
origin="Natural",
coat="Short",
pattern="Colorpoint",
),
Breed(
name="Siberian",
country="Russia",
origin="Natural",
coat="Semi-long",
pattern="All",
),
Breed(
name="Singapura",
country="Singapore",
origin="Natural",
coat="Short",
pattern="Ticked",
),
Breed(
name="Snowshoe",
country="United States",
origin="Crossbreed",
coat="Short",
pattern="Colorpoint",
),
Breed(
name="Sokoke",
country="Kenya",
origin="Natural",
coat="Short",
pattern="Classic tabby with ticking",
),
Breed(
name="Somali",
country="Somalia",
origin="Mutation",
coat="Long",
pattern="Ticked",
),
Breed(
name="Sphynx",
country="Canada",
origin="Mutation",
coat="Hairless",
pattern="All",
),
Breed(
name="Suphalak",
country="Thailand",
origin="Natural",
coat="Short",
pattern="Solid",
),
Breed(
name="Thai",
country="Thailand",
origin="Natural",
coat="Short",
pattern="Colorpoint",
),
Breed(
name="Thai Lilac",
country="Thailand",
origin="Natural",
coat="Short",
pattern="Solid",
),
Breed(
name="Tonkinese",
country="Canada",
origin="Crossbreed",
coat="Short",
pattern="Colorpoint/Mink/Solid",
),
Breed(
name="Toyger",
country="United States",
origin="Crossbreed",
coat="Short",
pattern="Mackerel",
),
Breed(
name="Turkish Angora",
country="Turkey",
origin="Natural",
coat="Semi-long",
pattern="All but colorpoint",
),
Breed(
name="Ukrainian Levkoy",
country="Ukraine",
origin="",
coat="Hairless",
pattern="",
),
Breed(
name="York Chocolate",
country="United States",
origin="Natural",
coat="Long",
pattern="Solid",
),
]
def create_cat_breeds(apps, schema_editor):
CatBreed = apps.get_model("main", "CatBreed")
bulk = []
for cat_breed in cat_breeds:
bulk.append(
CatBreed(
name=cat_breed.name,
country=cat_breed.country,
origin=cat_breed.origin,
coat=cat_breed.coat,
pattern=cat_breed.pattern,
)
)
CatBreed.objects.bulk_create(bulk)
class Migration(migrations.Migration):
dependencies = [
("main", "0001_initial"),
]
operations = [
migrations.RunPython(create_cat_breeds),
]

View File

@ -0,0 +1,60 @@
from django.db import migrations, models
def create_persons_data(apps, schema_editor):
Person = apps.get_model("main", "Person")
bulk = [
Person(
name="Pero",
address="Perina ulica 15",
city="Zagreb",
),
Person(
name="Mirko",
address="Mirkova ulica 17",
city="Zagreb",
),
Person(
name="Šime",
address="Šimina ulica 19",
city="Split",
),
Person(
name="Furio",
address="Furiozna ulica 21",
city="Pula",
),
]
Person.objects.bulk_create(bulk)
class Migration(migrations.Migration):
dependencies = [
("main", "0002_example_data"),
]
operations = [
migrations.CreateModel(
name="Person",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
("address", models.CharField(max_length=100)),
("city", models.CharField(max_length=100)),
],
options={
"db_table": "persons",
},
),
migrations.RunPython(create_persons_data),
]

View File

@ -0,0 +1,46 @@
# Generated by Django 5.0.4 on 2024-05-16 05:33
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0003_person"),
]
operations = [
migrations.AlterField(
model_name="person",
name="address",
field=models.CharField(
max_length=100,
validators=[
django.core.validators.MinLengthValidator(2),
django.core.validators.MaxLengthValidator(100),
],
),
),
migrations.AlterField(
model_name="person",
name="city",
field=models.CharField(
max_length=100,
validators=[
django.core.validators.MinLengthValidator(2),
django.core.validators.MaxLengthValidator(100),
],
),
),
migrations.AlterField(
model_name="person",
name="name",
field=models.CharField(
max_length=100,
validators=[
django.core.validators.MinLengthValidator(2),
django.core.validators.MaxLengthValidator(100),
],
),
),
]

View File

@ -1,3 +0,0 @@
from django.db import models
# Create your models here.

View File

@ -0,0 +1,2 @@
from .cat_breed import CatBreed
from .person import Person

View File

@ -0,0 +1,12 @@
from django.db import models
class CatBreed(models.Model):
name = models.CharField(max_length=100)
country = models.CharField(max_length=100)
origin = models.CharField(max_length=100)
coat = models.CharField(max_length=100)
pattern = models.CharField(max_length=100)
class Meta:
db_table = "cat_breeds"

View File

@ -0,0 +1,26 @@
from django.core.validators import MaxLengthValidator, MinLengthValidator
from django.db import models
class Person(models.Model):
name = models.CharField(
max_length=100,
blank=False,
null=False,
validators=[MinLengthValidator(2), MaxLengthValidator(100)],
)
address = models.CharField(
max_length=100,
blank=False,
null=False,
validators=[MinLengthValidator(2), MaxLengthValidator(100)],
)
city = models.CharField(
max_length=100,
blank=False,
null=False,
validators=[MinLengthValidator(2), MaxLengthValidator(100)],
)
class Meta:
db_table = "persons"

View File

@ -5,6 +5,8 @@
<meta name="viewport"
content="width=100%, user-scalable=yes, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js" integrity="sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<script src="https://unpkg.com/htmx.org@1.9.11" integrity="sha384-0gxUXCCR8yv9FM2b+U3FDbsKthCI66oH5IA9fHppQq9DDMHuMauqq1ZHBpJxQ0J0" crossorigin="anonymous"></script>
<title>{{ title or "Django-html demo" }}</title>
</head>

View File

@ -1,8 +0,0 @@
{% extends "main/base/layout.html" %}
{% block content %}
<p>
This is some complex form content bellow.
</p>
{% endblock %}

View File

@ -0,0 +1,47 @@
{% extends "main/base/layout.html" %}
{% from "main/components/js_alert.html" import no_js_alert %}
{% block content %}
{{ no_js_alert() }}
<form>
<div class="row mb-4">
<div
class="col-md-6"
hx-post="{{ url("complex-form-handle-route-module") }}"
hx-trigger="change"
hx-target="#route-module-content"
>
<div id="route-module-content" class="h-100">
{% set state = form_state.route_module %}
{% include "main/complex_form/route_module.html" %}
</div>
</div>
<div
class="col"
hx-post="{{ url("complex-form-handle-reports") }}"
hx-trigger="change"
hx-target="#reports-content"
>
<div id="reports-content">
{% set state = form_state.reports %}
{% include "main/complex_form/reports.html" %}
</div>
</div>
</div>
<div class="row">
<div
class="col-md-6"
hx-post="{{ url("complex-form-handle-warehouse-management") }}"
hx-trigger="change"
hx-target="#warehouse-management-content"
>
<div id="warehouse-management-content">
{% set state = form_state.warehouse_management %}
{% include "main/complex_form/warehouse_management.html" %}
</div>
</div>
</div>
</form>
{% endblock %}

View File

@ -0,0 +1,45 @@
{% from "main/components/inputs.html" import checkbox, select %}
{% set indent_1 = "ms-4" %}
{% set indent_2 = "ms-5" %}
<div class="card p-3 h-100">
{{ checkbox(title="Enable Reports", name="reports", state=state.enabled) }}
<hr>
{{ checkbox(title="Allow empty cashbag", name="allow_empty_cashbag", state=state.allow_empty_cashbag) }}
{{ checkbox(title="Reports builder", name="reports_builder", state=state.reports_builder) }}
{{ checkbox(title="Tax reports", name="tax_reports", state=state.tax_reports) }}
{{ checkbox(title="Transaction list", name="transaction_list", state=state.transaction_list) }}
{{ checkbox(title="Reports generator", name="reports_generator", state=state.reports_generator) }}
{{ checkbox(title="Technical center reports", name="technical_center_reports", state=state.technical_center_reports) }}
{{ checkbox(title="Dispense list", name="dispense_list", state=state.dispense_list) }}
{{ checkbox(title="Cash conformity", name="cash_conformity", state=state.cash_conformity) }}
{{ checkbox(title="Scan 2nd barcode", name="scan_2nd_bardcode", state=state.scan_2nd_bardcode, cls=indent_1) }}
{{ select(
title="Days between CC",
name="days_between_cc",
options={
"1": "1",
"2": "2",
"3": "3",
"4": "4",
"5": "5",
"6": "6",
"7": "7",
"8": "8",
"9": "9",
"14": "14",
"30": "30",
"31": "31",
"60": "60",
"90": "90",
"365": "365",
},
state=state.days_between_cc,
cls=indent_1
) }}
</div>

View File

@ -0,0 +1,35 @@
{% from "main/components/inputs.html" import checkbox, select %}
{% set indent_1 = "ms-4" %}
{% set indent_2 = "ms-5" %}
<div class="card p-3 h-100">
{{ checkbox(title="Enable Route module", name="route_module", state=state.enabled) }}
<hr>
{{ checkbox(title="Smart routing", name="smart_routing", state=state.smart_routing) }}
{{ checkbox(title="Predictive pickup", name="predictive_pickup", state=state.predictive_pickup, cls=indent_1) }}
{{ checkbox(title="Automatic planning", name="automatic_planning", state=state.automatic_planning, cls=indent_1) }}
{{ checkbox(title="Geo routing", name="geo_routing", state=state.geo_routing) }}
{{ checkbox(title="Warehouse", name="warehouse", state=state.warehouse) }}
{{ checkbox(title="End warehouse tracking", name="end_warehouse_tracking", state=state.end_warehouse_tracking, cls=indent_1) }}
{{ checkbox(title="Pick&Pack application", name="pick_and_pack_application", state=state.pick_and_pack_application, cls=indent_1) }}
{{ checkbox(title="Custom forms in routing", name="custom_forms_in_routing", state=state.custom_forms_in_routing) }}
{{ checkbox(title="Money bag tracking", name="money_bag_tracking", state=state.money_bag_tracking) }}
{{ select(
title="Packing model",
name="packing_model",
options={
"": "No packing model",
"per_route": "Packing model per route",
"per_machine": "Packing model per machine",
},
state=state.packing_model
) }}
{{ checkbox(title="Prekitting to box", name="prekitting_to_box", state=state.prekitting_to_box, cls=indent_1) }}
{{ checkbox(title="Prekitting to pallet", name="prekitting_to_pallet", state=state.prekitting_to_pallet, cls=indent_1) }}
{{ checkbox(title="Real time stock", name="real_time_stock", state=state.real_time_stock, cls=indent_1) }}
</div>

View File

@ -0,0 +1,24 @@
{% from "main/components/inputs.html" import checkbox, select %}
{% set indent_1 = "ms-4" %}
{% set indent_2 = "ms-5" %}
<div class="card p-3 h-100">
{{ checkbox(title="Product warehouse", name="product_warehouse", state=state.product_warehouse) }}
{{ select(
title="Delivery option",
name="delivery_option",
options={
"direct": "Direct",
"vehicle": "Vehicle",
},
state=state.delivery_option,
cls=indent_1
) }}
{{ checkbox(title="Allow negative stock", name="allow_negative_stock", state=state.allow_negative_stock, cls=indent_1) }}
{{ checkbox(title="Route reserve stock", name="route_reserve_stock", state=state.route_reserve_stock, cls=indent_1) }}
{{ checkbox(title="Refill log", name="refill_log", state=state.refill_log, cls=indent_1) }}
{{ checkbox(title="Product order", name="product_order", state=state.product_order, cls=indent_1) }}
{{ checkbox(title="Spare parts warehouse", name="spare_parts_warehouse", state=state.spare_parts_warehouse) }}
{{ checkbox(title="Purchase module", name="purchase_module", state=state.purchase_module) }}
</div>

View File

@ -0,0 +1,78 @@
{% macro inline_table_row(person) %}
<tr hx-target="this" hx-swap="outerHTML">
<td>{{ person.name }}</td>
<td>{{ person.address }}</td>
<td>{{ person.city }}</td>
<td>
<button
class="btn btn-outline-primary"
hx-get="{{ url("table-inline-edit-row", pk=person.pk) }}"
>
<i class="bi bi-pencil-square"></i>
</button>
</td>
</tr>
{% endmacro %}
{% macro inline_table_row_edit(person, cities, errors={}) %}
{% macro render_input(field_name, value) %}
{% set has_error = field_name in errors %}
<input
class="form-control {% if has_error %}is-invalid{% endif %}"
name="{{ field_name }}"
value="{{ value }}"
{% if has_error %}title="{{ errors[field_name] }}"{% endif %}
>
{% endmacro %}
{% macro render_select(field_name, value, options) %}
{% set has_error = field_name in errors %}
<select
class="form-select {% if has_error %}is-invalid{% endif %}"
name="{{ field_name }}"
{% if has_error %}title="{{ errors[field_name] }}"{% endif %}
>
{% for option in options %}
{% set selected = value == option %}
<option value="{{ option }}" {% if selected %}selected{% endif %}>
{{ option }}
</option>
{% endfor %}
</select>
{% endmacro %}
<tr
id="person-row-{{ person.pk }}"
hx-target="this"
hx-swap="outerHTML"
>
<td>
{{ render_input(field_name="name", value=person.name) }}
</td>
<td>
{{ render_input(field_name="address", value=person.address) }}
</td>
<td>
{# {{ render_input(field_name="city", value=person.city) }}#}
{{ render_select(field_name="city", value=person.city, options=cities) }}
</td>
<td>
<button
class="btn btn-outline-success"
hx-post="{{ url("table-inline-edit-row", pk=person.pk) }}"
hx-include="#person-row-{{ person.pk }} input, #person-row-{{ person.pk }} select"
>
<i class="bi bi-check-circle-fill"></i>
</button>
<button
class="btn btn-outline-danger"
hx-get="{{ url("table-inline-edit-row", pk=person.pk) }}"
hx-vals='{"action": "cancel"}'
>
<i class="bi bi-x-circle-fill"></i>
</button>
</td>
</tr>
{% endmacro %}

View File

@ -0,0 +1,46 @@
{% macro checkbox(title, name, state, cls="") %}
{% if state.visible %}
{% set id = random_id() %}
<div class="form-check {{ cls }} mb-2">
<input
class="form-check-input"
type="checkbox"
id="{{ id }}"
name="{{ name }}"
{% if state.checked and state.enabled %}checked {% endif %}
{% if not state.enabled %}disabled {% endif %}
>
<label class="form-check-label" for="{{ id }}">
{{ title }}
</label>
</div>
{% endif %}
{% endmacro %}
{% macro select(title, name, options, state, cls="") %}
{% if state.visible %}
<div class="{{ cls }}">
{% if title %}
<label class="form-label {% if not state.enabled %}text-secondary{% endif %}">
{{ title }}
</label>
{% endif %}
<select
name="{{ name }}"
class="form-select mb-2"
{% if not state.enabled %}disabled{% endif %}
>
{% for value, title in options.items() %}
<option
value="{{ value }}"
{% if value == state.value %}selected{% endif %}
>
{{ title }}
</option>
{% endfor %}
</select>
</div>
{% endif %}
{% endmacro %}

View File

@ -0,0 +1,17 @@
{% macro no_js_alert() %}
<div class="alert alert-success d-flex align-items-center mb-5" role="alert">
<i class="bi bi-check-circle-fill me-3"></i>
No JavaScript is used creating this page.
</div>
{% endmacro %}
{% macro js_alert(content) %}
<div class="alert alert-warning d-flex align-items-center mb-5" role="alert">
<i class="bi bi-exclamation-triangle-fill me-3"></i>
{{ content }}
</div>
{% endmacro %}
{% macro js_alert_work_in_progress() %}
{{ js_alert("Work in progress.") }}
{% endmacro %}

View File

@ -0,0 +1,9 @@
{% macro toast(title, id) %}
<div class="toast-container position-absolute top-0 start-50 translate-middle-x mt-2">
<div id="{{ id }}" class="toast text-bg-success">
<div class="toast-body">
{{ title }}
</div>
</div>
</div>
{% endmacro %}

View File

@ -1,8 +1,54 @@
{% extends "main/base/layout.html" %}
{% from "main/components/js_alert.html" import no_js_alert %}
{% block content %}
<p>
This is some filter list content bellow.
</p>
{{ no_js_alert() }}
<div class="card mb-2">
<div class="card-body">
<form
class="row g-3 align-items-center"
>
<div class="col-auto">
<label class="form-label mb-0">Breed:</label>
</div>
<div class="col-auto">
<input
type="text"
name="breed"
class="form-control"
hx-get="{{ url("filter-list-filter") }}"
hx-target="#cat-breeds-table"
hx-trigger="keyup"
hx-include="select[name='country']"
/>
</div>
<div class="col-auto">
<label class="form-label mb-0">Country:</label>
</div>
<div class="col-auto">
<select
name="country"
class="form-select"
hx-get="{{ url("filter-list-filter") }}"
hx-target="#cat-breeds-table"
hx-trigger="change"
hx-include="input[name='breed']"
>
<option value="">
- All -
</option>
{% for country in countries %}
<option value="{{ country }}">
{{ country }}
</option>
{% endfor %}
</select>
</div>
</form>
</div>
</div>
{% include "main/filter_list_content.html" %}
{% endblock %}

View File

@ -0,0 +1,22 @@
<table class="table" id="cat-breeds-table">
<thead>
<tr>
<th>Breed</th>
<th>Country</th>
<th>Origin</th>
<th>Coat</th>
<th>Pattern</th>
</tr>
</thead>
<tbody>
{% for breed in cat_breeds %}
<tr>
<td>{{ breed.name }}</td>
<td>{{ breed.country }}</td>
<td>{{ breed.origin }}</td>
<td>{{ breed.coat }}</td>
<td>{{ breed.pattern }}</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@ -1,7 +1,11 @@
{% extends "main/base/layout.html" %}
{% from "main/components/toast.html" import toast %}
{% from "main/components/js_alert.html" import js_alert %}
{% block content %}
{{ js_alert("Just few lines of JavaScript used to pop toast.") }}
<form
hx-post="{{ url("form-validation") }}"
hx-target="#validation-form-content"
@ -16,4 +20,18 @@
</div>
</div>
</form>
{{ toast(title="Form validated and saved successfully. Sorry, but I used few lines of JS code for this toast.", id="toast-success") }}
<script>
let toastSuccess = null;
document.addEventListener("DOMContentLoaded", function () {
const toastElement = document.querySelector("#toast-success");
toastSuccess = new bootstrap.Toast(toastElement, {
delay: 3000,
})
});
</script>
{% endblock %}

View File

@ -57,3 +57,10 @@
{% endif %}
</div>
</div>
{% if validation.is_valid %}
<script>
toastSuccess.show();
</script>
{% endif %}

View File

@ -1,4 +1,5 @@
{% extends "main/base/layout.html" %}
{% from "main/components/js_alert.html" import no_js_alert %}
{% block content %}
@ -13,6 +14,8 @@
</button>
{% endmacro %}
{{ no_js_alert() }}
{{ render_btn(title="Initial", cls="btn-outline-secondary", content="initial") }}
{{ render_btn(title="Swap to content 1", cls="btn-info", content="info") }}
{{ render_btn(title="Swap to content 2", cls="btn-warning", content="warning") }}

View File

@ -1,8 +1,24 @@
{% extends "main/base/layout.html" %}
{% from "main/components/js_alert.html" import no_js_alert %}
{% from "main/components/inline_table_row.html" import inline_table_row %}
{% block content %}
<p>
This is some table inline edit content bellow.
</p>
{{ no_js_alert() }}
<table class="table align-middle">
<thead>
<tr>
<th style="width: 25%">Name</th>
<th>Address</th>
<th style="width: 25%">City</th>
<th style="width: 15%">&nbsp;</th>
</tr>
</thead>
<tbody>
{% for person in persons %}
{{ inline_table_row(person) }}
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% from "main/components/inline_table_row.html" import inline_table_row, inline_table_row_edit %}
{% if is_editing %}
{{ inline_table_row_edit(person, cities=cities, errors=errors) }}
{% else %}
{{ inline_table_row(person) }}
{% endif %}

View File

@ -1,6 +1,11 @@
from .complex_form import ComplexFormView
from .filter_list import FilterListView
from .complex_form.views import (
ComplexFormView,
ReportsHandleView,
RouteModuleHandleView,
WarehouseManagementHandleView,
)
from .filter_list import FilterListFilterView, FilterListView
from .form_validation import FormValidationView
from .home import HomeView
from .swap import SwapView
from .table_inline_edit import TableInlineEditView
from .table_inline_edit import TableInlineEditView, TableInlineEditRowView

View File

@ -1,7 +0,0 @@
from project.main.views.demo_view_base import DemoViewBase
class ComplexFormView(DemoViewBase):
template_name = "main/complex_form.html"
active_section = "complex-form"
title = "Complex Form"

View File

@ -0,0 +1,46 @@
from dataclasses import dataclass
from project.main.views.complex_form.state_models import CheckboxState, SelectState
@dataclass
class ReportsState:
enabled: CheckboxState
cash_conformity: CheckboxState
days_between_cc: SelectState
scan_2nd_bardcode: CheckboxState
allow_empty_cashbag: CheckboxState
reports_builder: CheckboxState
tax_reports: CheckboxState
transaction_list: CheckboxState
reports_generator: CheckboxState
technical_center_reports: CheckboxState
dispense_list: CheckboxState
@staticmethod
def from_form(values: dict[str, str]) -> "ReportsState":
return ReportsState(
enabled=CheckboxState(checked=values.get("reports") == "on"),
cash_conformity=CheckboxState(checked=values.get("cash_conformity") == "on"),
days_between_cc=SelectState(value=values.get("days_between_cc")),
scan_2nd_bardcode=CheckboxState(checked=values.get("scan_2nd_bardcode") == "on"),
allow_empty_cashbag=CheckboxState(checked=values.get("allow_empty_cashbag") == "on"),
reports_builder=CheckboxState(checked=values.get("reports_builder") == "on"),
tax_reports=CheckboxState(checked=values.get("tax_reports") == "on"),
transaction_list=CheckboxState(checked=values.get("transaction_list") == "on"),
reports_generator=CheckboxState(checked=values.get("reports_generator") == "on"),
technical_center_reports=CheckboxState(checked=values.get("technical_center_reports") == "on"),
dispense_list=CheckboxState(checked=values.get("dispense_list") == "on"),
)
def control_state(self) -> None:
self.cash_conformity.enabled = self.enabled.checked
self.days_between_cc.visible = self.cash_conformity.enabled and self.cash_conformity.checked
self.scan_2nd_bardcode.enabled = self.cash_conformity.enabled and self.cash_conformity.checked
self.allow_empty_cashbag.enabled = self.enabled.checked
self.reports_builder.enabled = self.enabled.checked
self.tax_reports.enabled = self.enabled.checked
self.transaction_list.enabled = self.enabled.checked
self.reports_generator.enabled = self.enabled.checked
self.technical_center_reports.enabled = self.enabled.checked
self.dispense_list.enabled = self.enabled.checked

View File

@ -0,0 +1,92 @@
from dataclasses import dataclass
from project.main.views.complex_form.state_models import CheckboxState, SelectState
@dataclass
class RouteModuleState:
enabled: CheckboxState
smart_routing: CheckboxState
predictive_pickup: CheckboxState
automatic_planning: CheckboxState
geo_routing: CheckboxState
packing_model: SelectState
prekitting_to_box: CheckboxState
prekitting_to_pallet: CheckboxState
real_time_stock: CheckboxState
warehouse: CheckboxState
end_warehouse_tracking: CheckboxState
pick_and_pack_application: CheckboxState
custom_forms_in_routing: CheckboxState
money_bag_tracking: CheckboxState
@staticmethod
def from_form(values: dict[str, str]) -> "RouteModuleState":
return RouteModuleState(
enabled=CheckboxState(checked=values.get("route_module") == "on"),
smart_routing=CheckboxState(checked=values.get("smart_routing") == "on"),
predictive_pickup=CheckboxState(
checked=values.get("predictive_pickup") == "on"
),
automatic_planning=CheckboxState(
checked=values.get("automatic_planning") == "on"
),
geo_routing=CheckboxState(checked=values.get("geo_routing") == "on"),
packing_model=SelectState(value=values.get("packing_model")),
prekitting_to_box=CheckboxState(
checked=values.get("prekitting_to_box") == "on"
),
prekitting_to_pallet=CheckboxState(
checked=values.get("prekitting_to_pallet") == "on"
),
real_time_stock=CheckboxState(
checked=values.get("real_time_stock") == "on"
),
warehouse=CheckboxState(checked=values.get("warehouse") == "on"),
end_warehouse_tracking=CheckboxState(
checked=values.get("end_warehouse_tracking") == "on"
),
pick_and_pack_application=CheckboxState(
checked=values.get("pick_and_pack_application") == "on"
),
custom_forms_in_routing=CheckboxState(
checked=values.get("custom_forms_in_routing") == "on"
),
money_bag_tracking=CheckboxState(
checked=values.get("money_bag_tracking") == "on"
),
)
def control_state(self) -> None:
self.smart_routing.enabled = self.enabled.checked
self.geo_routing.enabled = self.enabled.checked
self.warehouse.enabled = self.enabled.checked
self.custom_forms_in_routing.enabled = self.enabled.checked
self.predictive_pickup.visible = (
self.smart_routing.checked and self.smart_routing.enabled
)
self.automatic_planning.visible = (
self.smart_routing.checked and self.smart_routing.enabled
)
self.end_warehouse_tracking.visible = (
self.warehouse.checked and self.warehouse.enabled
)
self.pick_and_pack_application.visible = (
self.warehouse.checked and self.warehouse.enabled
)
if not self.enabled.checked:
self.packing_model.value = None
self.packing_model.enabled = self.enabled.checked
self.prekitting_to_box.visible = (
self.packing_model.enabled and self.packing_model.value
)
self.prekitting_to_pallet.visible = (
self.packing_model.enabled and self.packing_model.value
)
self.real_time_stock.visible = (
self.packing_model.enabled and self.packing_model.value
)
self.money_bag_tracking.enabled = self.enabled.checked

View File

@ -0,0 +1,15 @@
from dataclasses import dataclass
@dataclass
class CheckboxState:
checked: bool = False
visible: bool = True
enabled: bool = True
@dataclass
class SelectState:
value: str = None
visible: bool = True
enabled: bool = True

View File

@ -0,0 +1,93 @@
from dataclasses import dataclass
from typing import Any
from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpResponse
from django.views.generic import TemplateView
from project.main.views.complex_form.reports_state import ReportsState
from project.main.views.complex_form.route_module_state import RouteModuleState
from project.main.views.complex_form.warehouse_management_state import (
WarehouseManagementState,
)
from project.main.views.demo_view_base import DemoViewBase
@dataclass
class FormState:
route_module: RouteModuleState
reports: ReportsState
warehouse_management: WarehouseManagementState
def control_state(self) -> None:
self.route_module.control_state()
self.reports.control_state()
self.warehouse_management.control_state()
class ComplexFormView(DemoViewBase):
template_name = "main/complex_form/container.html"
active_section = "complex-form"
title = "Complex Form"
def get_context_data(self, **kwargs) -> dict[str, Any]:
context = super().get_context_data(**kwargs)
state = FormState(
route_module=RouteModuleState.from_form(values={}),
reports=ReportsState.from_form(values={}),
warehouse_management=WarehouseManagementState.from_form(values={}),
)
state.control_state()
context.update(
{
"form_state": state,
}
)
return context
def post(self, request: WSGIRequest, *args, **kwargs) -> HttpResponse:
...
class RouteModuleHandleView(TemplateView):
template_name = "main/complex_form/route_module.html"
def post(self, request: WSGIRequest, *args, **kwargs) -> HttpResponse:
state = RouteModuleState.from_form(values=request.POST)
state.control_state()
return self.render_to_response(
context={
"state": state,
}
)
class ReportsHandleView(TemplateView):
template_name = "main/complex_form/reports.html"
def post(self, request: WSGIRequest, *args, **kwargs) -> HttpResponse:
state = ReportsState.from_form(values=request.POST)
state.control_state()
return self.render_to_response(
context={
"state": state,
}
)
class WarehouseManagementHandleView(TemplateView):
template_name = "main/complex_form/warehouse_management.html"
def post(self, request: WSGIRequest, *args, **kwargs) -> HttpResponse:
state = WarehouseManagementState.from_form(values=request.POST)
state.control_state()
return self.render_to_response(
context={
"state": state,
}
)

View File

@ -0,0 +1,48 @@
from dataclasses import dataclass
from project.main.views.complex_form.state_models import CheckboxState, SelectState
@dataclass
class WarehouseManagementState:
product_warehouse: CheckboxState
delivery_option: SelectState
allow_negative_stock: CheckboxState
route_reserve_stock: CheckboxState
refill_log: CheckboxState
product_order: CheckboxState
spare_parts_warehouse: CheckboxState
purchase_module: CheckboxState
@staticmethod
def from_form(values: dict[str, str]) -> "WarehouseManagementState":
return WarehouseManagementState(
product_warehouse=CheckboxState(
checked=values.get("product_warehouse") == "on"
),
delivery_option=SelectState(value=values.get("delivery_option")),
allow_negative_stock=CheckboxState(
checked=values.get("allow_negative_stock") == "on"
),
route_reserve_stock=CheckboxState(
checked=values.get("route_reserve_stock") == "on"
),
refill_log=CheckboxState(checked=values.get("refill_log") == "on"),
product_order=CheckboxState(checked=values.get("product_order") == "on"),
spare_parts_warehouse=CheckboxState(
checked=values.get("spare_parts_warehouse") == "on"
),
purchase_module=CheckboxState(
checked=values.get("purchase_module") == "on"
),
)
def control_state(self) -> None:
self.delivery_option.visible = self.product_warehouse.checked
self.allow_negative_stock.visible = self.product_warehouse.checked
self.route_reserve_stock.visible = self.product_warehouse.checked
self.refill_log.visible = self.product_warehouse.checked
self.product_order.visible = self.product_warehouse.checked
self.purchase_module.enabled = (
self.product_warehouse.checked or self.spare_parts_warehouse.checked
)

View File

@ -1,7 +1,67 @@
from typing import Any, Optional, Iterator
from django.db.models import Count, Q
from project.main.models import CatBreed
from project.main.views.demo_view_base import DemoViewBase
def get_countries() -> Iterator[str]:
ann = (
CatBreed.objects.values("country")
.annotate(Count("country"))
.order_by("country")
)
for a in ann:
yield a["country"]
def filter_cat_breeds(
breed_filter: Optional[str] = None, country_filter: Optional[str] = None
) -> Iterator[CatBreed]:
q = Q()
if breed_filter:
q &= Q(name__icontains=breed_filter)
if country_filter:
q &= Q(country=country_filter)
for c in CatBreed.objects.filter(q).order_by("name"):
yield c
class FilterListView(DemoViewBase):
template_name = "main/filter_list.html"
active_section = "filter-list"
title = "Filter List"
def get_context_data(self, **kwargs) -> dict[str, Any]:
context_data = super().get_context_data(**kwargs)
context_data.update(
{
"cat_breeds": filter_cat_breeds(),
"countries": get_countries(),
}
)
return context_data
class FilterListFilterView(DemoViewBase):
template_name = "main/filter_list_content.html"
def get_context_data(self, **kwargs) -> dict[str, Any]:
context_data = super().get_context_data(**kwargs)
cat_breeds = filter_cat_breeds(
breed_filter=self.request.GET.get("breed"),
country_filter=self.request.GET.get("country"),
)
context_data.update(
{
"cat_breeds": cat_breeds,
"countries": get_countries(),
}
)
return context_data

View File

@ -11,6 +11,7 @@ from project.main.views.demo_view_base import DemoViewBase
@dataclass
class Validation:
validated: bool = False
is_valid: bool = False
name: Optional[str] = None
consent: Optional[bool] = None
@ -57,6 +58,10 @@ class FormValidationView(DemoViewBase):
if not validation.consent:
validation.consent_error = "You should consent"
validation.is_valid = not (
validation.name_error or validation.age_error or validation.consent_error
)
return render(
context={
"validation": validation,

View File

@ -1,7 +1,88 @@
from typing import Any, Optional
from django.core.exceptions import ValidationError
from django.core.handlers.wsgi import WSGIRequest
from django.http import Http404, HttpResponse
from django.shortcuts import render
from project.main.models import Person
from project.main.views.demo_view_base import DemoViewBase
CITIES: list[str] = [
"",
"Zagreb",
"Split",
"Pula",
"Rijeka",
"Kozari bok",
]
def get_person(pk: int) -> Person:
try:
return Person.objects.get(pk=pk)
except Person.DoesNotExist:
raise Http404("Person not found")
class TableInlineEditView(DemoViewBase):
template_name = "main/table_inline_edit.html"
active_section = "table-inline-edit"
title = "Table Inline Edit"
def get_context_data(self, **kwargs) -> dict[str, Any]:
context_data = super().get_context_data(**kwargs)
persons = Person.objects.all()
context_data.update(
{
"persons": persons,
}
)
return context_data
class TableInlineEditRowView(DemoViewBase):
template_name = "main/table_inline_table_row.html"
def get_context_data(self, **kwargs) -> dict[str, Any]:
context_data = super().get_context_data(**kwargs)
person = get_person(pk=kwargs.get("pk"))
action = self.request.GET.get("action", "edit")
context_data.update(
{
"person": person,
"cities": CITIES,
"is_editing": action == "edit",
}
)
return context_data
def post(self, request: WSGIRequest, *args, **kwargs) -> HttpResponse:
errors: Optional[dict[str, str]] = None
person = get_person(pk=kwargs.get("pk"))
person.name = request.POST.get("name")
person.address = request.POST.get("address")
person.city = request.POST.get("city")
try:
person.clean_fields()
except ValidationError as ex:
errors = {key: value[0].message for key, value in ex.error_dict.items()}
else:
person.save()
return render(
context={
"person": person,
"errors": errors,
"cities": CITIES,
"is_editing": errors is not None,
},
template_name=self.template_name,
request=request,
)

View File

@ -37,6 +37,7 @@ INSTALLED_APPS = [
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"project.main",
]
MIDDLEWARE = [
@ -146,3 +147,5 @@ STATIC_URL = "static/"
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
from project.settings_local import *

View File

@ -0,0 +1,2 @@
ALLOWED_HOSTS = ["*"]
DEBUG = True

View File

@ -6,7 +6,12 @@ urlpatterns = [
path("", views.HomeView.as_view(), name="home"),
path("swap", views.SwapView.as_view(), name="swap"),
path("filter-list", views.FilterListView.as_view(), name="filter-list"),
path("filter-list-filter", views.FilterListFilterView.as_view(), name="filter-list-filter"),
path("form-validation", views.FormValidationView.as_view(), name="form-validation"),
path("complex-form", views.ComplexFormView.as_view(), name="complex-form"),
path("complex-form/handle/route-module", views.RouteModuleHandleView.as_view(), name="complex-form-handle-route-module"),
path("complex-form/handle/reports", views.ReportsHandleView.as_view(), name="complex-form-handle-reports"),
path("complex-form/handle/warehouse-management", views.WarehouseManagementHandleView.as_view(), name="complex-form-handle-warehouse-management"),
path("table-inline-edit", views.TableInlineEditView.as_view(), name="table-inline-edit"),
path("table-inline-edit/edit/<int:pk>", views.TableInlineEditRowView.as_view(), name="table-inline-edit-row"),
]

View File

@ -6,9 +6,10 @@ authors = ["Eden Kirin <eden@ekirin.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.10"
python = ">= 3.10, < 3.12"
django = "^5.0.4"
django-jinja = "^2.11.0"
gunicorn = "^21.2.0"
[build-system]