2017年10月17日 星期二

[Django] Queries - Create form with ModelForm

 Python   Django   Queries   ModelForm  



Introduction


We will make a create form with ModelForm and post the form to view and save the 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'^admin/', admin.site.urls),
]




ModelForm

Create productForm.py in your app.




productForm.py

from django import forms
from app.models import ProductType, Product

class ProductForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = ['Id', 'ProdType', 'Title' , 'Price']
        # exclude = ['Id']
   

l   The ModelForm class must inherits forms.ModelForm.

l   In inner class: Meta, we have to set

n   model: The target Model
n   fields: Included fields
n   exclude: Excluded fields, optional

The fields will default use the field declaration from Model: Product, in models.py.


models.py (No need to modify it)

from django.db import models

class Product(models.Model):

    Id = models.AutoField(primary_key=True)
    Price = models.IntegerField(null=False)
    Title = models.CharField(max_length=100, null=False)
    ProdType = models.ForeignKey('ProductType', db_column='ProdType', related_name='Products_ProductTypes')

# ...

However, Product.ProdType is a foreign key and we would like to take Product.ProdType as ModelChoiceField with options from ProductType.
So we specify the ProdType field like following, and that we will get this field’s  queryset in the form.

from django import forms
from app.models import ProductType, Product

class ProductForm(forms.ModelForm):

    ProdType=forms.ModelChoiceField(queryset=ProductType.objects.all(),required=True)   

    class Meta:
        model = Product
        fields = ['Id', 'ProdType', 'Title' , 'Price']
        # exclude = ['Id']
   


View

We will have two situations in our product-create view.

1.  HttpGet:
Create an empty ProductForm instance and place it into the template context for rendering.

2.  HttpPost:
Validate the form data and save it 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: Create
def productCreate(request):
    # create a form instance and populate it with data from the request:
    form = ProductForm(request.POST or None)
    if request.method == 'POST':
        # check whether it's valid:
        if form.is_valid():
            entity = form.save(commit=False)
           entity.save()
            prodtypeStr = str(form.cleaned_data["ProdType"].Name.lower())
            prodtypeEnum = ProductTypeEnum[prodtypeStr]
            return HttpResponseRedirect(reverse('productList', args=[prodtypeEnum.name.lower()]))
        else:
            pass
    else:
        form = ProductForm()

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


n   We can get form field data from form.cleaned_data.

n   Notice that form.cleaned_data["ProdType"] will return a ProductType object since Product.ProdType is a foreign key.

n   We need reverse (from django.core.urlresolvers) to do the redirection as following,

return HttpResponseRedirect(reverse('url name', args=['XXXX']))



Which 'XXXX' is the value for the 
named group in the url pattern.




Template

product-create.html

Remember our expected form layout? Let’s write the html for it.

<form method="post" autocomplete="off">
    <div class="form-group">
        <lable for="ProdType"></lable>
        <select id="ProdType" name="ProdType" class="custom-select mb-2 mr-sm-2 mb-sm-0">
            <option value="1" selected>Book</option>
            <option value="2">Clothes</option>
            <option value="3">Toy</option>
         </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>



Now modify the ProdType html: <select id="ProdType"></select>
with ProductForm’s field.

<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>


Add the CSRF template tag in the form.

<form method="post" autocomplete="off">{% csrf_token %}
</form>


Finally add the validation-fail message from backend and here is the completed version.

{% 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 %}




Update Product List template

Don’t forget to update the link in product-list.html.

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

<input type="button" class="btn btn-success" value="Create"  
onclick="location.href='{% url 'productCreate' %}'" />



Demo




Github





Reference






沒有留言:

張貼留言