In my app I have a page called products. In this page I display records form my database in a table.
Every row of the table has two buttons, to add and edit the record that is in the specific row.
The add and edit will be achieved by a form created by my model called Product. This form will be displayed in a modal which will show when the add or edit button is clicked.
I have implemented the add and edit function, showing a form on a seperate page, not in a modal.
Below it is my models:
# models.py
from django.db import models
class Manufacturer(models.Model):
name = models.CharField(max_length=264)
def __str__(self):
return self.name
class Meta:
ordering = ["name"]
class Product(models.Model):
title = models.CharField(max_length=264)
description = models.CharField(max_length=1000, blank=True, null=True)
year_manufactured = models.PositiveIntegerField(blank=True, null=True)
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
def __str__(self):
return self.title
class Meta:
ordering = ["title"]
This is my urls.py:
from django.conf.urls import url
from . import views
app_name = "products"
urlpatterns = [url(r'^products', views.ProductsView.as_view(), name="products"),
url(r"^product/new", views.add_new_product_view, name="add_new_product"),
url(r"^product/(?P<id>[0-9]+)/edit/", views.edit_product_view, name="edit_product")]
Below is my views:
from django.views.generic import DetailView, ListView, TemplateView
from django.http import JsonResponse
from django.shortcuts import render, get_object_or_404
from . import models
from products.forms import AddNewProductForm, EditProductForm
def index(request):
return render(request, 'products/products.html')
class ProductsView(ListView):
context_object_name = "products"
model = models.Product
template_name = "products/products.html"
form = AddNewProductForm()
def get_context_data(self, **kwargs):
context = super(ProductsView, self).get_context_data(**kwargs)
context["products"] = models.Product.objects.all().order_by("title")
context["form"] = self.form
return context
def add_new_product_view(request):
if request.method == "POST":
form = AddNewProductForm(request.POST)
if form.is_valid():
form.save(commit=True)
return JsonResponse({'msg': 'Data saved'})
else:
print("ERROR FORM INVALID")
return JsonResponse({'msg': 'ERROR FORM INVALID'})
else:
form = AddNewProductForm()
return JsonResponse({'form': form})
def edit_product_view(request, id):
instance = get_object_or_404(models.Product, id=id)
form = EditProductForm(instance=instance)
if request.method == "POST":
form = EditProductForm(request.POST, instance=instance)
if form.is_valid():
form.save(commit=True)
return JsonResponse({'form': form})
else:
print("ERROR FORM INVALID")
return JsonResponse({'form': form})
I have this on products.html:
{% extends "products/base.html" %}
{% load static %}
{% block title %}My Products{% endblock %}
{% block content %}
<div class="container" id="my-products-table-container">
<h2 class="text-left caption">Add, view and edit products</h2>
<hr>
<table class="table table-striped table-sm table-bordered" id="my-products-table">
<thead class="thead-inverse">
<tr class="head-row">
<th>Title</th>
<th>Description</th>
<th>Year</th>
<th>Manufacturer</th>
</thead>
<tbody>
{% for product in products %}
<tr class="table-row">
<td>{{ product.title }}</td>
<td>{{ product.description }}</td>
<td>{{ product.year_manufactured }}</td>
<td>{{ product.manufacturer }}</td>
<td><button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addNewProductModalForm">Add New product</button></td>
<td><button onclick="findMyForm({{ product.pk }})">Update product</button></td>
{% endfor %}
</tbody>
</table>
</div>
<!-- Modal Add New Product-->
<div class="modal fade" id="addNewProductModalForm" tabindex="-1" role="dialog" aria-labelledby="addNewProductModalFormLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<form class="form" id="add_new_product_form">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addNewProductModalFormLabel">Add New Product</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
{% csrf_token %}
{{ form.as_p }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="addNewProduct()">Submit</button>
</div>
</div>
</form>
</div>
</div>
<!-- Modal Edit-->
<div class="modal fade" id="editProductModalForm" tabindex="-1" role="dialog" aria-labelledby="editProductModalFormLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<form class="form" id="edit_product_form" >
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editProductModalFormLabel">Edit Product</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
{% csrf_token %}
<div id='showForm'></div>
</div>
<div class="modal-footer">
<input type="submit" class="btn btn-primary" value="Submit!">
</div>
</div>
</form>
</div>
</div>
<!-- JS Scripts -->
<script src="{% static "products/js/addProduct.js" %}"></script>
<script>
function findMyForm(productKey) {
$('#editProductModalForm').modal('show');
$.ajax({
type: 'GET',
url: '/product/' + productKey + '/edit/',
success: function(res) {
$("#showForm").html(res);
}
})}
</script>
{% endblock %}
My JS scripts:
#addProduct.js
function addNewProduct(e) {
var addNewProductForm = $("#add_new_product_form");
$.ajax({
type: 'POST',
url: '/product/new/',
data: addNewProductForm.serialize(),
success: function(res){
alert(res['msg'])
}
})
}
I have updated my code to illustrate that I can successfully add a new product in my database. Also, I have been working on editing a product from my database.
When this button is pressed:
<button onclick="findMyForm({{ product.pk }})">Update product</button>
the modal that should contain the edit form appears, then the function findMyForm with the products.pk argument is called. After that an ajax get request is performed on the products edit url.
Based on my urls.py, the edit_product_view is called.
This where I encounter the error that the EditProductForm is not serializable.
Also, huge thanks to Tico, for his continuous help.
It's a bit hard to understand what you want from the beggining. I assume that you want the user to see the products catalog then, when he clicks the update button from a particular product, you need to trigger the modal with a form with correct loaded data from that product.
So I base my answer in this assumption
First change needed, in your products_catalog.html, is from this :
<a class="btn btn-outline-warning" href="{% url "simple_app:edit_product" product.pk %}"><i class="fa fa-pencil-square-o fa-1x" aria-hidden="true"></i></a>
to a button that triggers a function: <button onclick="findMyForm()">Update product</button>
Then you should add that modal snippet to the end of your products_catalog.html, except the launch button. You don't need that. Also add an empty div inside the modal body <div id='showForm'></div>.
Now the findMyForm function should do three things. Lauch the modal, pick up the correct form from the server and put the form on modal body.
findMyForm(){
//the name myModal should be ID, as jquery notation suggests.
$('#myModal').modal('show');
$.ajax({
type: 'GET',
url: '/product/'+{{product.pk}}+'/edit/',
success: function(res){
$(#showForm).html(res)
}
})
GET is important here, so the call go into the right if of the view. So this should call your edit_product_view, acording to your url settings, and it should return the form you want. The form is picked up by the success callback on ajax and put inside our empty div in the modal body.
But there is still a big problem. Your view is rendering the template again from scratch, and you don't want that. So the trick is to use JsonResponse instead of render to avoid reloading (well, it wont work with render anyway). So this part in your view:
return render(request, 'simple_app/edit_product.html', {'form': form})
should turn into (remember to import from django.http import JsonResponse):
return JsonResponse({'form': form})
See that you don't need edit_product.html any longer. The above procedure builds its content inside the modal dynamically in the product_catalog.html page.
Ok. Now you should be able to push a button in products catalog that will pop a modal with the form revelant to the product without reloading. Let's say you make changes and want to save (POST part of the view). Pattern is similar:
save_form. {{form.as_p}} renders to) and make an ajax POST call to '/product/'+{{product.pk}}+'/edit/' passing form data.EDIT:
To add a new product in a modal:
{{form.as_p}}. In the modal footer you should change the <input> to a <button> tag and link the button to a js function addNew(). Doing this will prevent default form submission.addNew is like this:
addNew(){
var data = {
title: $('#title').val() //Not sure if title is the correct selector.
description: $('#description').val() // get correct id with browser tools.
...
}
// Ajax calls view. Stringify format your js object to a json string.
$.ajax({
type: 'POST',
url: '/product/new/',
data:JSON.stringify(data),
success: function(res){
alert(res['msg'])
}
})
}
The data formatting is the tricky part. I'm not sure how django forms expects the data to come. You probably have to print this in your view and manipulate before passing default validation functions. Let me know how this goes to you.
return index(request) to return JsonResponse({'msg':'Data saved'}) to be sure you understand it works.So your ADD NEW button is the default modal button. You press it and you get modal openned with an pre rendered empty form that comes from your class view. Then you have a ok BUTTON tied to a function. This function collects data from your form and send to server via Ajax, where you save data to your model. You can also send something back from the view (for other use cases) with JsonResponse, which will be picked up by success callback.
Hope this helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With