2017年10月18日 星期三

[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






沒有留言:

張貼留言