Unable to get submit buttons to work using the django-formset library (nothing happens upon click)

I am using the django-formset library to design an invoicing program. When I fill out a new form and click the submit button, nothing happens. I've pored over the documentation and followed the examples but no luck. Django(5.0.2) and django-formset(1.3.8) are updated.

I have a few ideas what could be causing the problem and corresponding questions but am unsure how to test them out as I this is the first time I'm working with css / bootstrap / javascript / typescript (I only have a small background in Python).

  1. Does it matter whether you load scripts in the base template or child template?
  2. Do the order of scripts matter and does it matter where in the head we're loading scripts relative to other elements?
  3. Do I have to move the static files from the formset library to my static folder in my project? (I tried copy pasting but to no effect)

A fresh set of eyes on this would be greatly appreciated because I am stuck! I get the feeling this is something very simple I'm overlooking.

models.py:

class Customer(models.Model):
    first_name = models.CharField('First Name', max_length=55, blank=True)
    last_name = models.CharField('Last Name', max_length=55, blank=True)
    phone = models.CharField('Phone Number', max_length=10, blank=True)
    alt_phone = models.CharField('Alt Phone Number', max_length=10, blank=True)
    company = models.CharField('Company', max_length=55, blank=True)
    email = models.EmailField('Email', null=True, blank=True)
    address = models.CharField('Address', max_length=55, blank=True)
    city = models.CharField('City', max_length=55, blank=True)
    state = models.CharField('State', max_length=55, blank=True)
    zip = models.CharField('Zip Code', max_length=9, blank=True)
    customer_notes = models.TextField('Customer Notes', blank=True)
    date_created = models.DateTimeField(auto_created=True, null=True, blank=True)

    def __str__(self):
        return f'{self.first_name} {self.last_name}'

forms.py:

class CustomerForm(ModelForm):
    default_renderer = FormRenderer(
        form_css_classes='row border p-1 m-1',
        field_css_classes={
            '*': 'mb-1 col-4',
            'city': 'mb-1 col-3',
            'state': 'mb-1 col-1',
            'customer_notes': 'mb-1 col-12',
            'new': 'mb-5'
        },
    )

    hide_if = 'new_customer.extant'


    class Meta:
        model = Customer
        fields = [
            'first_name',
            'last_name',
            'company',
            'phone',
            'alt_phone',
            'email',
            'address',
            'city',
            'state',
            'zip',
            'customer_notes',
        ]
        widgets = {
            'first_name': forms.TextInput(attrs={
                'class': 'form-control',
                'id': 'first_name',
                'placeholder': 'Customer First Name',
            }),
            'last_name': forms.TextInput(attrs={
                'class': 'form-control',
                'id': 'last_name',
                'placeholder': 'Customer Last Name',
            }),
            'phone': forms.NumberInput(attrs={
                'class': 'form-control',
                'id': 'phone',
            }),
            'alt_phone': forms.NumberInput(attrs={
                'class': 'form-control',
                'id': 'alt_phone',
            }),
            'company': forms.TextInput(attrs={
                'class': 'form-control',
                'id': 'company',
            }),
            'email': forms.EmailInput(attrs={
                'class': 'form-control',
                'id': 'email',
            }),
            'address': forms.TextInput(attrs={
                'class': 'form-control',
                'id': 'address',
            }),
            'city': forms.TextInput(attrs={
                'class': 'form-control',
                'id': 'city',
            }),
            'state': forms.TextInput(attrs={
                'class': 'form-control',
                'id': 'state',
                'value': 'CA',
            }),
            'zip': forms.TextInput(attrs={
                'class': 'form-control',
                'id': 'zip',
            }),
            'customer_notes': forms.Textarea(attrs={
                'class': 'form-control',
                'id': 'customer_notes',
                'placeholder': 'Enter customer notes',
                'rows': '1'
            }),
            'date_created': forms.DateTimeInput(attrs={
                'class': 'form-control',
                'id': 'date_created',
                'value': datetime.now()
            })
        }

views.py:

class EditView(UpdateView, FormViewMixin):
    def get_object(self, queryset=None):
        if self.extra_context['add'] is False:
            return super().get_object(queryset)

    def form_valid(self, form):
        if extra_data := self.get_extra_data():
            if extra_data.get('delete') is True:
                self.object.delete()
                success_url = self.get_success_url()
                response_data = {'success_url': force_str(success_url)} if success_url else {}
                return JsonResponse(response_data)
        return super().form_valid(form)

class CustomerEditView(EditView):
    model = Customer
    template_name = 'invoice/customer-add.html'
    form_class = CustomerForm
    extra_context = None

    def get_success_url(self):
        if pk := self.object.id:
            return reverse('customer-edit', kwargs={'pk': pk})
        else:
            return reverse('customer-list')

urls.py

urlpatterns = [
    path('customers', CustomerListView.as_view(), name='customer-list'),  # list view not handled here
    path('customer/add/', CustomerEditView.as_view(extra_context={'add': True}), name='customer-add', ),
    path('customer/<int:pk>/', CustomerEditView.as_view(extra_context={'add': False}), name='customer-edit', ),
]

base.html

{% load static %}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <title>Invoice Management</title>

    <!-- bootstrap5 css -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
    <link href="{% static '/css/sb-admin-2.css' %}" rel="stylesheet"></link>
    <link href="{% static '/css/style.css' %}" rel="stylesheet">

    <!-- FONT AWESOME -->
    <script src="https://kit.fontawesome.com/016d1bf7c0.js" crossorigin="anonymous"></script>
    <!-- bootstrap5 javascript -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
            integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
            crossorigin="anonymous"></script>

    <!-- django-formset -->
    <script type="module" src="{% static 'formset/js/django-formset.js' %}"></script>


    {% block head_script %}
    {% endblock %}
</head>

<div class="card w-100 shadow my-2">
    {% block content %}{% endblock %}
</div>

customer-add.html

{% extends "invoice/base/base.html" %}
{% load i18n %}
{% load static %}


<!-- Content Row -->
{% block content %}
    <!-- CUSTOMER FORM -->
    <div id="customer-info" class="card-body collapse show">
        {% load render_form from formsetify %}
        <django-formset endpoint="{{ request.path }}" csrf-token="{{ csrf_token }}">
            {% render_form form %}

            {% if add %}
                <button type="button" df-click="submit({add: true}) -> proceed">{% trans "Add" %}</button>
            {% else %}
                <button type="button" df-click="submit({update: true}) -> proceed">{% trans "Update" %}</button>
                <button type="button" df-click="submit({delete: true}) -> proceed">{% trans "Delete" %}</button>
            {% endif %}
        </django-formset>
    </div>
{% endblock %}
Back to Top