Skip to content

Instantly share code, notes, and snippets.

@MattSegal
Created August 12, 2021 01:52
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MattSegal/912069f629ea59414727d9c1366de7a6 to your computer and use it in GitHub Desktop.
Save MattSegal/912069f629ea59414727d9c1366de7a6 to your computer and use it in GitHub Desktop.
from django.forms import ModelForm
from django.template.loader import render_to_string
from django.shortcuts import render
from django.http import Http404
from django.contrib import messages
class DynamicTableForm(ModelForm):
"""
Renders a table which can be optionally edited by a user using htmx.
Updates the provided instance when the form is submitted.
Suggested usage within a view:
DYNAMIC_FORMS = {
"foo": BarForm,
"bar": FooForm,
}
def thing_view(request, pk, form_slug: str = ""):
# ...
forms = DynamicTableForm.build_forms(request, form_slug, client, DYNAMIC_FORMS)
context = {"whatever": 1, "forms": forms}
form_resp = DynamicTableForm.get_response(request, form_slug, forms, context)
if form_resp:
return form_resp
# ...
Needs to be hooked up in urls.py as follows:
urlpatterns = [
# ...
# Thing
path("thing/<int:pk>/", views.thing.thing_view, name="thing-detail"),
path(
"thing/<int:pk>/<str:form_slug>/",
views.thing.thing_view,
name="thing-detail-form",
),
# ...
]
"""
template = "case/snippets/_dynamic_table_form.html"
def __init__(self, slug: str, *args, editable: bool = False, **kwargs):
super().__init__(*args, **kwargs)
self.editable = editable
self.slug = slug
# Build display value for fields
for bound_field in self:
if hasattr(bound_field.field, "display_value"):
bound_field.display_value = bound_field.field.display_value(bound_field)
else:
bound_field.display_value = bound_field.value()
def render_to_string(self):
context = {"form": self}
return render_to_string(self.template, context)
def render_to_response(self, request, context):
context = {**context, "form": self}
return render(request, self.template, context)
def __str__(self):
return self.render_to_string()
@staticmethod
def build_forms(request, slug: str, instance, view_forms: dict) -> dict:
form_instances = {}
for form_slug, form_cls in view_forms.items():
form_kwargs = dict(instance=instance, slug=form_slug, editable=False)
if request.method == "POST" and form_slug == slug:
form_kwargs["data"] = request.POST
form_instances[form_slug] = form_cls(**form_kwargs)
return form_instances
@staticmethod
def get_response(request, slug: str, forms: dict, context: dict):
if not slug:
return
form = forms.get(slug)
if not form:
raise Http404()
ctx = {**context, "slug": slug}
if request.method == "GET":
is_editable = request.GET.get("edit")
form.editable = is_editable
return form.render_to_response(request, ctx)
elif request.method == "POST":
if form.is_valid():
form.save()
messages.success(request, "Edit success")
else:
form.editable = True
return form.render_to_response(request, ctx)
else:
raise Http404()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment