Wednesday, October 18, 2017

[Django] Queries - Edit form with ModelForm

 Python   Django   Queries   ModelForm  


▌Introduction


We will make a edit form with ModelForm and post the form to view and save the updated data into database.

▌Environment


▋Python 3.6.2
▋Django 1.8.18     



▌Implement


▋Expected result






▋Url Pattern

▋urls.py

from app.views import home, productList, productCreate, productEdit, productRemove

urlpatterns = [
    url(r'^$', home, name='home'),
    url(r'^product/(?P<prodtype>\w+)/$', productList, name='productList'),
    url(r'^product/create$', productCreate, name='productCreate'),
    url(r'^product/edit/(?P<prodId>\w+)$', productEdit, name='productEdit'),
    url(r'^admin/', admin.site.urls),
]


▋ModelForm

We can reuse the ModelForm: productForm, which was created in previous tutorial.




▋View  

We have two situations in our product-edit view.

1.  HttpGet:
Get the product entity by prodId (named group in url) from database, and create a ProductForm instance with the product entity. Finally place it into the template context for rendering.

2.  HttpPost:
Validate the form data and update the product entity to database if ok, then redirect to product list page.

▋views.py

from django.shortcuts import render
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from app.models import Product
from share.enum import ProductTypeEnum

from app.forms.productForm import ProductForm

#region Product: Edit
def productEdit(request, prodId):
    form = ProductForm(request.POST or None)
    if request.method == 'POST':

        if form.is_valid():
          
            # id = form.cleaned_data["Id"] # Using will ModelForm will not get Primary key
            title = form.cleaned_data["Title"]
            price = form.cleaned_data["Price"]
            prodType = form.cleaned_data["ProdType"]

            entity = Product.objects.filter(Id=prodId).first()
            if entity is not None:
                entity.Title = title
                entity.Price = price
                entity.ProdType=prodType
                entity.save()
            else:
                pass  

            prodtypeStr = str(form.cleaned_data["ProdType"].Name.lower())
            prodtypeEnum = ProductTypeEnum[prodtypeStr]
            return HttpResponseRedirect(reverse('productList', args=[prodtypeEnum.name.lower()]))
        else:
            pass
    else:
        entity = Product.objects.filter(Id=prodId).first()
        if entity is not None:
            form = ProductForm(instance=entity)
        else:
            pass  

    return render(request, 'product-edit.html', {'form': form})
#endregion


n   We can get form field data from form.cleaned_data, however the primary-key field: Id, will be cleared by ModelForm and cause exception if we try to get the value by id = form.cleaned_data["Id"].

One of the solution is creating abother form with forms.Form instead of ModelForm.
In this sample, we will just get the Id thru the url’s named group: prodId.

n   This is how to create a form with an model object (instance).

form = ProductForm(instance=somthing)

thus we can get the instance by template tag like following

{{ form.instance.Title }}


▋Update Product List template

Let’s update the link in product-list.html.

▋product-list.html (Only show the modified html)

<input type="button" class="btn btn-success" value="Edit" onclick="location.href='{% url 'productEdit' prodId=prod.Id %}'" />



▋Template

▋product-edit.html

Copy the product-create.html as product-edit.html, we will modify it.
The original template:

{% extends "base.html" %}

{% block header %}Product-create{% endblock %}

{% block content %}

<form method="post" autocomplete="off">
    {% csrf_token %}
    {% if form.errors %}
        {% for field in form %}
            {% for error in field.errors %}
                <div class="alert alert-danger">
                    <strong>{{ field.name }}: {{ error|escape }}</strong>
                </div>
            {% endfor %}
        {% endfor %}
    {% endif %}

    <div class="form-group">
        <lable for="ProdType"></lable>
        <select id="ProdType" name="ProdType" class="custom-select mb-2 mr-sm-2 mb-sm-0">
                {% for prodtype in form.ProdType.field.queryset %}
                    <option value="{{ prodtype.Id }}">{{ prodtype.Name }}</option>
                {% endfor %}
            </select>
    </div>
    <div class="form-group">
        <label for="Title">Title</label>
        <input type="text" id="Title" name="Title" class="form-control">
    </div>
    <div class="form-group">
        <label for="Price">Price</label>
        <input type="number" id="Price" name="Price" class="form-control">
    </div>
    <input type="submit" class="form-control" value="Save" />
</form>

{% endblock %}


The only thing we have to modify the template is binding the form.instance’s field value into the dom.

Id (Optional, cus ModelForm will clear it)
<input type="hidden" Id="Id" name="Id" value="{{ form.instance.Id }}" />


Title
<input type="text" id="Title" name="Title" class="form-control" value="{{ form.instance.Title }}">


Price
<input type="number" id="Price" name="Price" class="form-control" value="{{ form.instance.Price }}">


ProdType
A tip on setting the selected option is using {% ifequal %}{% endifequal %}

<select id="ProdType" name="ProdType" class="custom-select mb-2 mr-sm-2 mb-sm-0">
                {% for prodtype in form.ProdType.field.queryset %}
                  {% ifequal  form.instance.ProdType.Id prodtype.Id %}
                    <option value="{{ prodtype.Id }}" selected>{{ prodtype.Name }}</option>
                  {% endifequal %} 
                  {% ifnotequal  form.instance.ProdType.Id prodtype.Id %}
                    <option value="{{ prodtype.Id }}">{{ prodtype.Name }}</option>
                  {% endifnotequal %} 
                {% endfor %}
            </select>


Here is the final template for product-edit.html.

{% extends "base.html" %}

{% block header %}Product-edit{% endblock %}

{% block content %}

<form method="post" autocomplete="off">
    {% csrf_token %}
    {% if form.errors %}
        {% for field in form %}
            {% for error in field.errors %}
                <div class="alert alert-danger">
                    <strong>{{ field.name }}: {{ error|escape }}</strong>
                </div>
            {% endfor %}
        {% endfor %}
    {% endif %}
    <input type="hidden" Id="Id" name="Id" value="{{ form.instance.Id }}" />
    <div class="form-group">
        <lable for="ProdType"></lable>
        <select id="ProdType" name="ProdType" class="custom-select mb-2 mr-sm-2 mb-sm-0">
                {% for prodtype in form.ProdType.field.queryset %}
                  {% ifequal  form.instance.ProdType.Id prodtype.Id %}
                    <option value="{{ prodtype.Id }}" selected>{{ prodtype.Name }}</option>
                  {% endifequal %} 
                  {% ifnotequal  form.instance.ProdType.Id prodtype.Id %}
                    <option value="{{ prodtype.Id }}">{{ prodtype.Name }}</option>
                  {% endifnotequal %} 
                {% endfor %}
            </select>
    </div>
    <div class="form-group">
        <label for="Title">Title</label>
        <input type="text" id="Title" name="Title" class="form-control" value="{{ form.instance.Title }}">
    </div>
    <div class="form-group">
        <label for="Price">Price</label>
        <input type="number" id="Price" name="Price" class="form-control" value="{{ form.instance.Price }}">
    </div>
    <input type="submit" class="form-control" value="Save" />
</form>

{% endblock %}


▌Demo





▌Github





▌Reference






No comments:

Post a Comment