Compare commits
1 Commits
main
...
c7f89a9ab4
| Author | SHA1 | Date | |
|---|---|---|---|
| c7f89a9ab4 |
@ -1 +0,0 @@
|
|||||||
**/*.pyc
|
|
||||||
51
Dockerfile
51
Dockerfile
@ -1,51 +0,0 @@
|
|||||||
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" \
|
|
||||||
]
|
|
||||||
21
Makefile
21
Makefile
@ -1,7 +1,3 @@
|
|||||||
CONTAINER_NAME=django-htmx
|
|
||||||
IMAGE_NAME=django-htmx
|
|
||||||
|
|
||||||
|
|
||||||
ifeq ($(VIRTUAL_ENV),)
|
ifeq ($(VIRTUAL_ENV),)
|
||||||
RUN_IN_ENV=poetry run
|
RUN_IN_ENV=poetry run
|
||||||
else
|
else
|
||||||
@ -23,20 +19,3 @@ migrations:
|
|||||||
|
|
||||||
migrate:
|
migrate:
|
||||||
@ $(RUN_IN_ENV) python manage.py 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)
|
|
||||||
|
|||||||
35
README.md
35
README.md
@ -1,36 +1 @@
|
|||||||
# Django-htmx demo
|
# 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).
|
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
27
poetry.lock
generated
27
poetry.lock
generated
@ -19,13 +19,13 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django"
|
name = "django"
|
||||||
version = "5.0.6"
|
version = "5.0.4"
|
||||||
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.10"
|
python-versions = ">=3.10"
|
||||||
files = [
|
files = [
|
||||||
{file = "Django-5.0.6-py3-none-any.whl", hash = "sha256:8363ac062bb4ef7c3f12d078f6fa5d154031d129a15170a1066412af49d30905"},
|
{file = "Django-5.0.4-py3-none-any.whl", hash = "sha256:916423499d75d62da7aa038d19aef23d23498d8df229775eb0a6309ee1013775"},
|
||||||
{file = "Django-5.0.6.tar.gz", hash = "sha256:ff1b61005004e476e0aeea47c7f79b85864c70124030e95146315396f1e7951f"},
|
{file = "Django-5.0.4.tar.gz", hash = "sha256:4bd01a8c830bb77a8a3b0e7d8b25b887e536ad17a81ba2dce5476135c73312bd"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -74,13 +74,13 @@ tornado = ["tornado (>=0.2)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jinja2"
|
name = "jinja2"
|
||||||
version = "3.1.4"
|
version = "3.1.3"
|
||||||
description = "A very fast and expressive template engine."
|
description = "A very fast and expressive template engine."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
|
{file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
|
||||||
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
|
{file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -171,18 +171,19 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlparse"
|
name = "sqlparse"
|
||||||
version = "0.5.0"
|
version = "0.4.4"
|
||||||
description = "A non-validating SQL parser."
|
description = "A non-validating SQL parser."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.5"
|
||||||
files = [
|
files = [
|
||||||
{file = "sqlparse-0.5.0-py3-none-any.whl", hash = "sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663"},
|
{file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"},
|
||||||
{file = "sqlparse-0.5.0.tar.gz", hash = "sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93"},
|
{file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["build", "hatch"]
|
dev = ["build", "flake8"]
|
||||||
doc = ["sphinx"]
|
doc = ["sphinx"]
|
||||||
|
test = ["pytest", "pytest-cov"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
@ -208,5 +209,5 @@ files = [
|
|||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">= 3.10, < 3.12"
|
python-versions = "^3.10"
|
||||||
content-hash = "26984438b0e835c052014186db2a76cbe044e19fd5b56ba3fc3d642b2ed530dc"
|
content-hash = "6c86d5721314c92afa919983780e664a1573ebb7c25a8b54622393b97990e509"
|
||||||
|
|||||||
@ -13,7 +13,7 @@ class MainConfig(AppConfig):
|
|||||||
"""copy template database if db not exists"""
|
"""copy template database if db not exists"""
|
||||||
|
|
||||||
db_fname = settings.DATABASES["default"]["NAME"]
|
db_fname = settings.DATABASES["default"]["NAME"]
|
||||||
template_db_fname = settings.BASE_DIR / "db.template.sqlite3"
|
template_db_fname = settings.BASE_DIR / "db_template.sqlite3"
|
||||||
|
|
||||||
if os.path.exists(db_fname):
|
if os.path.exists(db_fname):
|
||||||
return
|
return
|
||||||
|
|||||||
@ -1,46 +0,0 @@
|
|||||||
# 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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,26 +1,10 @@
|
|||||||
from django.core.validators import MaxLengthValidator, MinLengthValidator
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class Person(models.Model):
|
class Person(models.Model):
|
||||||
name = models.CharField(
|
name = models.CharField(max_length=100)
|
||||||
max_length=100,
|
address = models.CharField(max_length=100)
|
||||||
blank=False,
|
city = models.CharField(max_length=100)
|
||||||
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:
|
class Meta:
|
||||||
db_table = "persons"
|
db_table = "persons"
|
||||||
|
|||||||
@ -1,78 +1,43 @@
|
|||||||
{% macro inline_table_row(person) %}
|
{% macro inline_table_row(person, is_editing) %}
|
||||||
<tr hx-target="this" hx-swap="outerHTML">
|
<tr hx-target="this" hx-swap="outerHTML">
|
||||||
<td>{{ person.name }}</td>
|
{% if is_editing %}
|
||||||
<td>{{ person.address }}</td>
|
<td>
|
||||||
<td>{{ person.city }}</td>
|
<input class="form-control" name="name" value="{{ person.name }}">
|
||||||
<td>
|
</td>
|
||||||
<button
|
<td>
|
||||||
class="btn btn-outline-primary"
|
<input class="form-control" name="address" value="{{ person.address }}">
|
||||||
hx-get="{{ url("table-inline-edit-row", pk=person.pk) }}"
|
</td>
|
||||||
|
<td>
|
||||||
>
|
<input class="form-control" name="city" value="{{ person.city }}">
|
||||||
<i class="bi bi-pencil-square"></i>
|
</td>
|
||||||
</button>
|
<td>
|
||||||
</td>
|
<button
|
||||||
</tr>
|
class="btn btn-outline-success"
|
||||||
{% endmacro %}
|
hx-get="{{ url("table-inline-edit-row", pk=person.pk) }}"
|
||||||
|
hx-vals='{"action": "save"}'
|
||||||
|
>
|
||||||
{% macro inline_table_row_edit(person, cities, errors={}) %}
|
<i class="bi bi-check-circle-fill"></i>
|
||||||
{% macro render_input(field_name, value) %}
|
</button>
|
||||||
{% set has_error = field_name in errors %}
|
<button
|
||||||
<input
|
class="btn btn-outline-danger"
|
||||||
class="form-control {% if has_error %}is-invalid{% endif %}"
|
hx-get="{{ url("table-inline-edit-row", pk=person.pk) }}"
|
||||||
name="{{ field_name }}"
|
hx-vals='{"action": "cancel"}'
|
||||||
value="{{ value }}"
|
>
|
||||||
{% if has_error %}title="{{ errors[field_name] }}"{% endif %}
|
<i class="bi bi-x-circle-fill"></i>
|
||||||
>
|
</button>
|
||||||
{% endmacro %}
|
</td>
|
||||||
|
{% else %}
|
||||||
{% macro render_select(field_name, value, options) %}
|
<td>{{ person.name }}</td>
|
||||||
{% set has_error = field_name in errors %}
|
<td>{{ person.address }}</td>
|
||||||
<select
|
<td>{{ person.city }}</td>
|
||||||
class="form-select {% if has_error %}is-invalid{% endif %}"
|
<td>
|
||||||
name="{{ field_name }}"
|
<button
|
||||||
{% if has_error %}title="{{ errors[field_name] }}"{% endif %}
|
class="btn btn-outline-primary"
|
||||||
>
|
hx-get="{{ url("table-inline-edit-row", pk=person.pk) }}"
|
||||||
{% for option in options %}
|
>
|
||||||
{% set selected = value == option %}
|
<i class="bi bi-pencil-square"></i>
|
||||||
<option value="{{ option }}" {% if selected %}selected{% endif %}>
|
</button>
|
||||||
{{ option }}
|
</td>
|
||||||
</option>
|
{% endif %}
|
||||||
{% 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>
|
</tr>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for person in persons %}
|
{% for person in persons %}
|
||||||
{{ inline_table_row(person) }}
|
{{ inline_table_row(person, is_editing=False) }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -1,7 +1,3 @@
|
|||||||
{% from "main/components/inline_table_row.html" import inline_table_row, inline_table_row_edit %}
|
{% from "main/components/inline_table_row.html" import inline_table_row %}
|
||||||
|
|
||||||
{% if is_editing %}
|
{{ inline_table_row(person, is_editing=is_editing) }}
|
||||||
{{ inline_table_row_edit(person, cities=cities, errors=errors) }}
|
|
||||||
{% else %}
|
|
||||||
{{ inline_table_row(person) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Any, Optional, Iterator
|
from typing import Any, Optional
|
||||||
|
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, Q
|
||||||
|
|
||||||
@ -6,19 +6,18 @@ from project.main.models import CatBreed
|
|||||||
from project.main.views.demo_view_base import DemoViewBase
|
from project.main.views.demo_view_base import DemoViewBase
|
||||||
|
|
||||||
|
|
||||||
def get_countries() -> Iterator[str]:
|
def get_countries() -> list[str]:
|
||||||
ann = (
|
ann = (
|
||||||
CatBreed.objects.values("country")
|
CatBreed.objects.values("country")
|
||||||
.annotate(Count("country"))
|
.annotate(Count("country"))
|
||||||
.order_by("country")
|
.order_by("country")
|
||||||
)
|
)
|
||||||
for a in ann:
|
return [a["country"] for a in ann]
|
||||||
yield a["country"]
|
|
||||||
|
|
||||||
|
|
||||||
def filter_cat_breeds(
|
def filter_cat_breeds(
|
||||||
breed_filter: Optional[str] = None, country_filter: Optional[str] = None
|
breed_filter: Optional[str] = None, country_filter: Optional[str] = None
|
||||||
) -> Iterator[CatBreed]:
|
) -> list[CatBreed]:
|
||||||
q = Q()
|
q = Q()
|
||||||
|
|
||||||
if breed_filter:
|
if breed_filter:
|
||||||
@ -26,8 +25,7 @@ def filter_cat_breeds(
|
|||||||
if country_filter:
|
if country_filter:
|
||||||
q &= Q(country=country_filter)
|
q &= Q(country=country_filter)
|
||||||
|
|
||||||
for c in CatBreed.objects.filter(q).order_by("name"):
|
return CatBreed.objects.filter(q).order_by("name")
|
||||||
yield c
|
|
||||||
|
|
||||||
|
|
||||||
class FilterListView(DemoViewBase):
|
class FilterListView(DemoViewBase):
|
||||||
|
|||||||
@ -1,29 +1,10 @@
|
|||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.http import Http404
|
||||||
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.models import Person
|
||||||
from project.main.views.demo_view_base import DemoViewBase
|
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):
|
class TableInlineEditView(DemoViewBase):
|
||||||
template_name = "main/table_inline_edit.html"
|
template_name = "main/table_inline_edit.html"
|
||||||
@ -49,40 +30,17 @@ class TableInlineEditRowView(DemoViewBase):
|
|||||||
def get_context_data(self, **kwargs) -> dict[str, Any]:
|
def get_context_data(self, **kwargs) -> dict[str, Any]:
|
||||||
context_data = super().get_context_data(**kwargs)
|
context_data = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
person = get_person(pk=kwargs.get("pk"))
|
try:
|
||||||
action = self.request.GET.get("action", "edit")
|
person = Person.objects.get(pk=kwargs.get("pk"))
|
||||||
|
except Person.DoesNotExist:
|
||||||
|
raise Http404("Person not found")
|
||||||
|
|
||||||
|
action = self.request.GET.get("action")
|
||||||
|
|
||||||
context_data.update(
|
context_data.update(
|
||||||
{
|
{
|
||||||
"person": person,
|
"person": person,
|
||||||
"cities": CITIES,
|
"is_editing": action != "cancel",
|
||||||
"is_editing": action == "edit",
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return context_data
|
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,
|
|
||||||
)
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ authors = ["Eden Kirin <eden@ekirin.com>"]
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">= 3.10, < 3.12"
|
python = "^3.10"
|
||||||
django = "^5.0.4"
|
django = "^5.0.4"
|
||||||
django-jinja = "^2.11.0"
|
django-jinja = "^2.11.0"
|
||||||
gunicorn = "^21.2.0"
|
gunicorn = "^21.2.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user