Hi,
I’m trying to use HTMX to render a form inside a modal. I have the initial form rendering, but for form validation im not sure how i get the view to pass the erros of fields back to the view within the modal?
def add_wallet(request):
if request.method == 'POST':
form = WalletForm(request.POST)
if form.is_valid():
wallet = form.save(commit=False) # Don't save yet
wallet.user = request.user # Assign logged-in user
wallet.save() # Now save with user assigned
return redirect('portal') # Redirect to portal
else:
return render(request, 'portal/partials/wallet_form.html', {'form': form})
else:
form = WalletForm()
return render(request, 'portal/partials/wallet_form.html', {'form': form})
When the form is invalid on submit it is rendering the portal/partials/wallet_form.htm
rather than reloading the modal with the content and error messages.
I hope this makes sense?
I think we’d need to see the original template that contains the hx-post attribute to understand what’s happening in that template along with the response.
It would also be helpful to see the wallet_form.html
file to understand what the html is that is being rendered to understand how the response is going to be processed by htmx.
Hi, Ken.
Here is my form
<div class="modal-content">
<h3 class="mt-5 text-center">Add Wallet Address</h3>
<p class="text-center text-light-emphasis">Wallet Addresses are used for <span class="text-primary">alerts</span> and <span class="text-primary">notifications</span></p>
<div class="modal-body">
<form method="POST" action="{% url 'add_wallet' %}" class="p-4">
{% csrf_token %}
<!-- Wallet Name -->
<div class="mb-3">
<label class="form-label" for="{{ form.name.id_for_label }}">Wallet Name</label>
<input type="text" name="name" class="form-control {% if form.name.errors %}is-invalid{% endif %}"
value="{{ form.name.value|default_if_none:'' }}" placeholder="Enter a wallet name">
{% for error in form.name.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
<div class="row">
<!-- Blockchain -->
<div class="col-md-4 mb-3 mt-3">
<label class="form-label" for="{{ form.blockchain.id_for_label }}">Blockchain</label>
<select name="blockchain" class="form-select {% if form.blockchain.errors %}is-invalid{% endif %}">
<option value="">Select Chain</option>
{% for blockchain in form.blockchain.field.queryset %}
<option value="{{ blockchain.id }}" {% if blockchain.id == form.blockchain.value %}selected{% endif %}>{{ blockchain.name }}</option>
{% endfor %}
</select>
{% for error in form.blockchain.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
<!-- Wallet Address -->
<div class="col-md-8 mb-3 mt-3">
<label class="form-label" for="{{ form.address.id_for_label }}">Contract Address</label>
<input type="text" name="address" class="form-control {% if form.address.errors %}is-invalid{% endif %}"
value="{{ form.address.value|default_if_none:'' }}" placeholder="0x00000000000000000007">
{% for error in form.address.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
</div>
<!-- Notes -->
<div class="mb-3 mt-3">
<label class="form-label" for="{{ form.notes.id_for_label }}">Notes</label>
<textarea name="notes" class="form-control {% if form.notes.errors %}is-invalid{% endif %}" rows="3">{{ form.notes.value|default_if_none:'' }}</textarea>
{% for error in form.notes.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
<!-- Tags -->
<div class="mb-3 mt-3">
<label class="form-label" for="{{ form.tags.id_for_label }}">Tags</label>
<input type="text" name="tags" class="form-control {% if form.tags.errors %}is-invalid{% endif %}"
value="{{ form.tags.value|default_if_none:'' }}" placeholder="Apply any tags to help organize wallets">
{% for error in form.tags.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
<!-- Switch for Adding Users -->
<div class="mb-3 switch-container">
<label class="form-label">Adding Users by Team Members</label>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="adding_users_by_team" {% if form.adding_users_by_team.value %}checked{% endif %}>
</div>
</div>
<!-- Notifications -->
<div class="mb-3 d-flex justify-content-between align-items-center">
<div>
<label class="form-label">Notifications</label>
<p><small>Allow Notifications for wallet</small></p>
</div>
<div class="d-flex gap-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="alerts" {% if form.alerts.value %}checked{% endif %}>
<label class="form-check-label">Alerts</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="news" {% if form.news.value %}checked{% endif %}>
<label class="form-check-label">News</label>
</div>
</div>
</div>
<!-- Submit Button -->
<div class="text-center mb-5">
<button type="button" class="btn btn-secondary btn-sm float-start" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success text-white btn-sm float-start mx-2">Submit</button>
{% if form.instance.id %}
<!-- Delete Button (appears only for editing) -->
<button type="button" class="btn btn-danger btn-sm float-end" hx-delete="{% url 'delete_wallet' form.instance.id %}"
hx-target="#dialog" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
Delete Wallet
</button>
{% endif %}
</div>
</form>
</div>
</div>
This is the button that triggers the modal content
<button type="button" class="btn btn-success btn-sm text-white" hx-get="{% url 'add_wallet' %}" hx-target="#dialog">
Add Wallet Address
</button>
I use hx-get
and hx-target
to render the form inside my #dialog
modal.
Ok, you’re using HTMX to get the form, but you’re not using it to submit the data being posted. You’re using a regular form submit button.
This means that your browser would be expecting a full page refresh - your post handling would need to return a full page, not the page fragment.
If you use HTMX to submit the form data, then handling errors is handled like any other HTMX request - you’ll get back the proper div. What changes in this case is that you need to handle the successful response differently. Fortunately, HTMX has a built-in mechanism for that - the HX-Redirect
header that you can supply in the response.
(Or, if you’re building this as an entire HTMX-style SPA-ish application, then the successful response would not be a redirect - it would be a rendering of the content of the success url.)
1 Like
Ah that makes sense. Thanks, Ken.
This is the first time i’m using htmx so not that familar with how this works. Ill look at the hx-redirect
to see where i get.
Thanks
Tom.