From dd8bb346bf982130984096c299c6cb9aa605c7b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Thu, 15 Jul 2021 09:47:56 +0200 Subject: [PATCH 01/14] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 2239ff5..4f261fe 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,10 @@ If you need several pages for a model, then you will not use "/sunshine/foo" and ## Opinionated Best Practices +I switched from Django class-based-views (CBV) to function-based-views (FBV). This simplifies things. +One URL corresponds to one Python method. If an action requires two HTTP verbs (GET and POST), then I use **two URLs**. Posts +always go to hx-methods, not to URLs returning full pages. + I like it conditionless. I try to avoid to have too many "if" and "else". I avoid to use `if request.method == 'POST'`. From e171939451657a80770f4bb7fe99bc8561b76e9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Thu, 15 Jul 2021 09:51:01 +0200 Subject: [PATCH 02/14] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4f261fe..128e375 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ Returns a HttpResponse with a full page. URL: `/foo` +This servers only http GET. Updates (http POST) go to _hxpost URLs. + --- **_hx():** From 56fec9797e318742940faca552e4f5485e238b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Thu, 15 Jul 2021 09:52:34 +0200 Subject: [PATCH 03/14] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 128e375..52f818a 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,8 @@ contains a HTML fragment. URL: `/foo_hxpost` +If you are lazy like me, then you don't use [require_POST decorator](https://docs.djangoproject.com/en/dev/topics/http/decorators/#django.views.decorators.http.require_POST), since it provides no big benefit. + --- **_json():** From cb8d8eccd2f107d32f9dc15bd83cd7c94a6d541f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Thu, 15 Jul 2021 09:58:07 +0200 Subject: [PATCH 04/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52f818a..79c16a5 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ contains a HTML fragment. URL: `/foo_hxpost` -If you are lazy like me, then you don't use [require_POST decorator](https://docs.djangoproject.com/en/dev/topics/http/decorators/#django.views.decorators.http.require_POST), since it provides no big benefit. +It makes sense to use the [require_POST decorator](https://docs.djangoproject.com/en/dev/topics/http/decorators/#django.views.decorators.http.require_POST),if you have concerns that a GET request (where request.POST is empty) could accidently change data. --- From 35b170e1cb7245f8d66fd154d0ae01d7d99b08af Mon Sep 17 00:00:00 2001 From: Saifur Rahman Date: Sat, 7 Aug 2021 11:03:06 +0530 Subject: [PATCH 05/14] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 79c16a5..932c522 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,10 @@ pip install -e git+ssh://git@github.com/guettli/django-htmx-fun.git#egg=django_h ``` The source code is now in src/django-htmx-fun/ +### Note +If you get `permission denied` error while running `pip install -e git+ssh://git@github.com/guettli/django-htmx-fun.git#egg=django_htmx_fun` you need to first set up ssh key in your machine. Follow the links provided below: +1. [Generating a new SSH key and adding it to the ssh-agent](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) +1. [Adding a new SSH key to your GitHub account](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account) ## Run Database Migrations From 5e5f37ecf8e806aa49dedf9bf002446e0105aa87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Mon, 16 Aug 2021 12:48:55 +0200 Subject: [PATCH 06/14] use https, not ssh. Since this works without extra setup step. --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 932c522..df24686 100644 --- a/README.md +++ b/README.md @@ -41,14 +41,10 @@ If you want to install my small htmx demo application: python3 -m venv django-htmx-fun-env cd django-htmx-fun-env . bin/activate -pip install -e git+ssh://git@github.com/guettli/django-htmx-fun.git#egg=django_htmx_fun +pip install -e git+https://github.com/guettli/django-htmx-fun.git#egg=django-htmx-fun ``` The source code is now in src/django-htmx-fun/ -### Note -If you get `permission denied` error while running `pip install -e git+ssh://git@github.com/guettli/django-htmx-fun.git#egg=django_htmx_fun` you need to first set up ssh key in your machine. Follow the links provided below: -1. [Generating a new SSH key and adding it to the ssh-agent](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) -1. [Adding a new SSH key to your GitHub account](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account) ## Run Database Migrations From 57acb06d5fb48cdad97ba502d98c5ec8a6c69985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Tue, 12 Oct 2021 21:05:55 +0200 Subject: [PATCH 07/14] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index df24686..68cebe0 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ are more colors than black and white. For more about htmx see the homepage: [htmx.org](//htmx.org) -[HTMX: Frontend Revolution (Slides from DjangoCon EU 2021)](https://docs.google.com/presentation/d/1Gx1UGVAgD2ALLOucsIm9myF5mDflbP06-M6_d-RdZAY/edit?usp=sharing) +[HTMX: Frontend Revolution (Slides from DjangoCon 2021)](https://docs.google.com/presentation/d/12dgaBnUgl4cmEkiOhUJL5hsbGQ6hB5sslDuozmBjVUA/edit?usp=sharing) ## Install @@ -154,6 +154,9 @@ I avoid to use `if request.method == 'POST'`. I don't use the special http headers which get added by htmx. I avoid this (pseudo code): "if request is a htmx request, then ...". Instead I create two endpoints: One which returns a full page, one which returns a fragment. +Goodbye formsets. With I use several `
` tags in one page. This means I hardly use formsets. Some for the "prefix" of forms: Since +I don't put several Django form instances into one `` tag, I don't need the prefix any more. + ## Screenshot From cf15d48f9275f3e7f1ea9ae26ac4fbeb8ad7efb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Sat, 23 Oct 2021 22:36:53 +0200 Subject: [PATCH 08/14] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 68cebe0..09c7f27 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,9 @@ always go to hx-methods, not to URLs returning full pages. I like it conditionless. I try to avoid to have too many "if" and "else". -I avoid to use `if request.method == 'POST'`. +I avoid to use `if request.method == 'POST'`. This means I don't handle different http verbs in one function based view. A function based view handles either GET xor a POST. URLs are cheap I create two URLs if I need a a readonly view and a view which does something. + +I only use the http verbs GET and POST, although htmx can do http UPDATE, http DELETE, ... I don't use the special http headers which get added by htmx. I avoid this (pseudo code): "if request is a htmx request, then ...". Instead I create two endpoints: One which returns a full page, one which returns a fragment. From e2a9d4330d832f9398cd7980bf584cbe487881ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Sat, 23 Oct 2021 22:49:12 +0200 Subject: [PATCH 09/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 09c7f27..54cd549 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ I like it conditionless. I try to avoid to have too many "if" and "else". I avoid to use `if request.method == 'POST'`. This means I don't handle different http verbs in one function based view. A function based view handles either GET xor a POST. URLs are cheap I create two URLs if I need a a readonly view and a view which does something. -I only use the http verbs GET and POST, although htmx can do http UPDATE, http DELETE, ... +I only use the http verbs GET and POST, although htmx can do http PUT, http PATCH, http DELETE, ... I don't use the special http headers which get added by htmx. I avoid this (pseudo code): "if request is a htmx request, then ...". Instead I create two endpoints: One which returns a full page, one which returns a fragment. From 982b37238fb1fcfa3ea096de49e845e0a8a8fb0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Sat, 23 Oct 2021 22:51:10 +0200 Subject: [PATCH 10/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54cd549..7738616 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ I only use the http verbs GET and POST, although htmx can do http PUT, http PATC I don't use the special http headers which get added by htmx. I avoid this (pseudo code): "if request is a htmx request, then ...". Instead I create two endpoints: One which returns a full page, one which returns a fragment. -Goodbye formsets. With I use several `` tags in one page. This means I hardly use formsets. Some for the "prefix" of forms: Since +Goodbye formsets. I use several `` tags in one page. This means I hardly use formsets. Some for the "prefix" of forms: Since I don't put several Django form instances into one `` tag, I don't need the prefix any more. From a76a153478ee7c3aeed6b43ac10d47e9ab509da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Sat, 23 Oct 2021 23:02:03 +0200 Subject: [PATCH 11/14] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7738616..05524ff 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ For more about htmx see the homepage: [htmx.org](//htmx.org) [HTMX: Frontend Revolution (Slides from DjangoCon 2021)](https://docs.google.com/presentation/d/12dgaBnUgl4cmEkiOhUJL5hsbGQ6hB5sslDuozmBjVUA/edit?usp=sharing) +Youtube video of the Talk [DjangoCon US 2021: HTMX, Frontend Revolution](https://www.youtube.com/watch?v=z0yPTv15Fjk) + ## Install If you want to install my small htmx demo application: From 317718b47fd0ed04806b6ae8cb971486c89d49da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Wed, 17 Nov 2021 11:52:42 +0100 Subject: [PATCH 12/14] Update README.md --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 05524ff..57c9635 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,24 @@ manage.py createsuperuser ``` Admin: http://127.0.0.1:8000/admin +## No need for the POST/Redirect/GET pattern + +If you are used to django's form handling, then you are used to the [POST/Redirect/GET Pattern](https://en.wikipedia.org/wiki/Post/Redirect/Get). + +This is not needed if you submit a form via htmx and you just want to update on part of the whole page. + +I use this pattern now: + +Case 1: The http POST was successful, and data was changed. The server returns the status code 201 "Created". I use this even if data was changed, and not "created". I use this and not the usual 200 to simplify testing. I never ever want to confuse the http status of a successful htmx POST with the http status of a invalid traditional django http POST. The response contains the new HTML. No need for a redirect. + +Case 2: The http POST was not successful, since the data in the form was not valid. Then I return 422. + +Related question: [Which http status codes to use when processing http post?](https://stackoverflow.com/q/69773241/633961) + + + + + ## Naming Pattern Here is my personal naming pattern, which helps me to read the source more easily From 6ba5a4366c55299caf53daeb781c75be3e64b916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Wed, 17 Nov 2021 12:48:07 +0100 Subject: [PATCH 13/14] Update README.md --- README.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 57c9635..4d021ac 100644 --- a/README.md +++ b/README.md @@ -70,21 +70,29 @@ Admin: http://127.0.0.1:8000/admin ## No need for the POST/Redirect/GET pattern -If you are used to django's form handling, then you are used to the [POST/Redirect/GET Pattern](https://en.wikipedia.org/wiki/Post/Redirect/Get). +If you are used to django's form handling, then you are used to the [POST/Redirect/GET Pattern](https://en.wikipedia.org/wiki/Post/Redirect/Get). This means after the client submitted a valid form, the server response has the http status 302 with a new location URL. -This is not needed if you submit a form via htmx and you just want to update on part of the whole page. +This is not needed if you submit a form via htmx and you just want to update one part of the whole page. I use this pattern now: -Case 1: The http POST was successful, and data was changed. The server returns the status code 201 "Created". I use this even if data was changed, and not "created". I use this and not the usual 200 to simplify testing. I never ever want to confuse the http status of a successful htmx POST with the http status of a invalid traditional django http POST. The response contains the new HTML. No need for a redirect. +Case 1: The http POST was successful, and data was changed. The server returns the status code 201 "Created". I use this even if data was changed, and not "created". I use this and not the usual 200 to simplify testing. I never ever want to confuse the http status of a successful htmx POST with the http status of an invalid traditional django http POST. The response contains the new HTML. No need for a redirect. -Case 2: The http POST was not successful, since the data in the form was not valid. Then I return 422. +Case 2: The http POST was not successful, since the data in the form was not valid. Then my server code returns 422. Related question: [Which http status codes to use when processing http post?](https://stackoverflow.com/q/69773241/633961) +## Full Page (aka "client-side") Redirect +If you use htmx, then most http responses will contains html fragments which will inserted into the current page. +But sometimes you want to do a traditional full page redirect. In the htmx docs it is called "client-side" redirect. +Then you need return a http response which has the http header "HX-Redirect" set to the URL of the new location. + +A common mistake is the set the status code if this response to 302. But this will trigger a redirect inside htmx. + +Here is some code to do a full page redirect with Django: [hx-target: swap html vs full page reload](https://stackoverflow.com/a/65569741/633961) ## Naming Pattern From 3e7606df3dcea4cb10f770339d6c34177809e406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Wed, 17 Nov 2021 12:50:00 +0100 Subject: [PATCH 14/14] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4d021ac..7d1e3b3 100644 --- a/README.md +++ b/README.md @@ -84,11 +84,11 @@ Related question: [Which http status codes to use when processing http post?](ht ## Full Page (aka "client-side") Redirect -If you use htmx, then most http responses will contains html fragments which will inserted into the current page. +If you use htmx, then most http responses will contains html fragments which will get swapped into the current page. But sometimes you want to do a traditional full page redirect. In the htmx docs it is called "client-side" redirect. -Then you need return a http response which has the http header "HX-Redirect" set to the URL of the new location. +Then you need return a http response which has the http header "HX-Redirect" set to the URL of the new location. Docs: [Response Headers](https://htmx.org/docs/#response-headers) A common mistake is the set the status code if this response to 302. But this will trigger a redirect inside htmx.