Django custom form, custom template and custom field choices

It took a lot of time to create a completely custom form with a custom template (not using the form.as_table feature of Django) and also loading custom field choices. Here’s a detail of the url, view and template – done from scratch and done right.

Form

class FulfillmentEmailForm(forms.Form):
    # Note that I haven't provided the choices for the selects here and rather providing them in the form init. This way, this form can be a generic form and the values can be populated based on the uses. This is the right way of creating a custom form rather than feeding in the choices early and thereby having to create a new form if a choice value changes.
    email_from = forms.ChoiceField(required=True, widget=forms.Select())
    email_recipient = forms.MultipleChoiceField(required=True, widget=forms.CheckboxSelectMultiple())
    email_cc = forms.CharField(required=False, widget=forms.Textarea(), label=u'Cc', help_text='Enter multiple emails separated by comma(,)')
    email_subject = forms.CharField(max_length=300, required=True, widget=forms.TextInput())
    email_body = forms.CharField(required=True, widget=forms.Textarea())
    email_attachment = forms.MultipleChoiceField(required=False, widget=forms.CheckboxSelectMultiple())
    
    def __init__(self, *args, **kwargs):
        email_recipient_choices = None
        email_from_choices = None
        email_attachment_choices = None
        # Get the choice values first, before super is called
        if 'email_recipient_choices' in kwargs:
            email_recipient_choices = kwargs.pop('email_recipient_choices')
        if 'email_from_choices' in kwargs:
            email_from_choices = kwargs.pop('email_from_choices')
        if 'email_attachment_choices' in kwargs:
            email_attachment_choices = kwargs.pop('email_attachment_choices')
        # Call the super method on the form
        super(FulfillmentEmailForm, self).__init__(*args, **kwargs)
        # Modify the field properties after the super is called
        if email_recipient_choices:
            # The commented line here shows how a form can be generalized. Based on the inputs passed (if any), the required property of the field can be set accordingly therefore making the form generic for reuse. But here, I'm only modifying the choices property because that's what I need
            #self.fields['email_recipient'] = forms.ChoiceField(required=True, widget=forms.CheckboxSelectMultiple(), choices=email_recipient_choices)
            self.fields['email_recipient'].choices=email_recipient_choices
        if email_from_choices:
            #self.fields['email_from'] = forms.ChoiceField(required=True, widget=forms.Select(), choices=email_from_choices)
            self.fields['email_from'].choices=email_from_choices
        if email_attachment_choices:
            #self.fields['email_attachment'] = forms.MultipleChoiceField(required=False, widget=forms.CheckboxSelectMultiple(), choices=email_attachment_choices)
            self.fields['email_attachment'].choices=email_attachment_choices

    def clean_email_cc(self):
        '''
        should be a comma separated list of emails
        '''
        data = self.cleaned_data['email_cc']
        if data:
            for email in data.split(','):
                # Some function to validate a single email address
                validate_email(email.strip())
        return data

View

@log_info
@login_required
@any_group_required([ 'Support', 'Product Management', 'Sales Engineers',])
def sendProcessEmail(request, tenant_id):
    # tenant_id is just to get some information on deal and the default license recipients from the database. You can substitute this with your own code
    tenant = get_object_or_404(InstallationTenant, pk=tenant_id)
    deal = tenant.installation.getLastNonZeroDeal()
    email_recipient_choices = [(c.email, '%s %s (%s)' % (c.first_name, c.last_name, c.email)) for c in deal.getLRecipientContact()]
    email_from_choices = [('sender_email@domain.com','sender_email@domain.com')]
    email_attachment_choices = [('license', 'License file'),('deployment_guide','Deployment Guide'),('vmware','VMware'),
                                    ('hyper_v','Hyper-V (2008+2010)'),('ms_azure','Microsoft Azure')]
    # Getting email templates from the DB. Each template has a name, description, email_subject and email_body. Name and Description are used to select the appropriate template from a list. Subject and body are used to populate the form once the appropriate template is selected.
    fulfillment_email_templates = CommonEmailTemplate.objects.filter(type='kw-fulfillment')
    if request.method == 'POST':
        # Had to struggle to get this right. I was only using FulfillmentEmailForm(request.POST). As a result of that, the form after submission would lose the custom choices and would throw errors on page that the submitted value is not a valid choice. It's important to pass in the choices even after form POST which is being done below.
        fulfillment_email_form = FulfillmentEmailForm(request.POST, email_recipient_choices=email_recipient_choices,
                                                      email_from_choices=email_from_choices, email_attachment_choices=email_attachment_choices)
        if fulfillment_email_form.is_valid():
            email_from = fulfillment_email_form.cleaned_data['email_from'].encode('UTF-8')
            email_recipient = fulfillment_email_form.cleaned_data['email_recipient']
            email_cc = fulfillment_email_form.cleaned_data['email_cc'].encode('UTF-8')
            email_subject = fulfillment_email_form.cleaned_data['email_subject'].encode('UTF-8')
            email_body = fulfillment_email_form.cleaned_data['email_body'].encode('UTF-8')
            email_attachment = fulfillment_email_form.cleaned_data['email_attachment']
            # Now you can perform the required business logic based on the data
    else:
        initial_form_data = {'email_from': 'fulfillment@accellion.com',
                             'email_cc' : 'shipping@accellion.com', 
                             'email_attachment' : 'license'
                             }
        # Pass in the initial form data as well as choices for the fields
        fulfillment_email_form = FulfillmentEmailForm(initial=initial_form_data, email_recipient_choices=email_recipient_choices,
                                                      email_from_choices=email_from_choices, email_attachment_choices=email_attachment_choices)
    template = 'module/fulfillment_email_form.html'
    
    return render_to_response(template, locals(), context_instance=RequestContext(request))

Template
Note that you have to provide your own error notifications on form submit in a custom template. The {{fulfillment_email_form.field_name.errors}} is being used to show errors for each field. Also, there is some JavaSript and CSS capabilities in the form to populate the form that have been described in the previous article.

{% extends "../../opportunities/templates/opportunities/base.html" %}

{% block title %}Send fulfillment email{% endblock %}

{% block account %}

{% endblock %}

{% block navigation %}
{% endblock %}

{% block content %}

<fieldset class="module" style="width: 800px">
	<h2>Send fulfillment email</h2>
	<form action={% url sendFulfillmentEmail tenant_id %} method="post">{% csrf_token %}
	<div class="table-row">
		<div class="table-cell">
			Select fullfillment email template:
		</div> 
		<div class="table-cell">
			<input type="button" name="btn_fulfillment_template" value="Email templates" onclick="location.href='#kw_fulfillment_templates'">
		</div>
		<div class="table-cell">
			<span id="selected_template_name" style="color: RGB(0,0,100)"></span>
		</div>
	</div>
	<div id="kw_fulfillment_templates" class="modalDialog">
		<div>
		<a href="#close" title="Close" class="modalclose">X</a>
		<h2>Fulfillment email templates for kiteworks orders</h2>
		<table>
			{% for row in fulfillment_email_templates %}
				<tr class="{% cycle "row1" "row2" %}">
					<td><input type="button" id="tmp_sel_{{forloop.counter}}" onclick="setTemplateData({{forloop.counter}})" value="Select"></td>
					<td id="tmp_name_{{forloop.counter}}">{{row.name}}</td>
					<td id="tmp_desc_{{forloop.counter}}">{{row.description}}</td>
					<td id="tmp_subj_{{forloop.counter}}" style="display: none">{{row.email_subject}}</td>
					<td id="tmp_body_{{forloop.counter}}" style="display: none">{{row.email_body}}</td>
				</tr>
			{% endfor %}
		</table>
		</div>		
	</div>
	<div class="table-row">
		<div class="table-cell">
			Email from:
		</div> 
		<div class="table-cell">
			{{fulfillment_email_form.email_from}}	
		</div>
	</div>
	<div class="table-row">{{fulfillment_email_form.email_from.errors}}</div>
	<div class="table-row">
		<div class="table-cell">
			Email recipient(s):
		</div>
		<div class="table-cell">
			{{fulfillment_email_form.email_recipient}}
		</div>
	</div>
	<div class="table-row">{{fulfillment_email_form.email_recipient.errors}}</div>
	<div class="table-row">
		<div class="table-cell">
			Cc:
		</div>
		<div class="table-cell">
			{{fulfillment_email_form.email_cc}}
			<br/>Enter multiple email addresses separated by comma (,)
		</div>
	</div>
	<div class="table-row">{{fulfillment_email_form.email_cc.errors}}</div>
	<div class="table-row">
		<div class="table-cell">
			Subject:
		</div>
		<div class="table-cell">
			{{fulfillment_email_form.email_subject}}
		</div>
	</div>
	<div class="table-row">{{fulfillment_email_form.email_subject.errors}}</div>
	<div class="table-row">
		<div class="table-cell">
			Message:
		</div>
		<div class="table-cell">
			{{fulfillment_email_form.email_body}}
		</div>
	</div>
	<div class="table-row">{{fulfillment_email_form.email_body.errors}}</div>
	<div class="table-row">
		<div class="table-cell">
			Attachments:
		</div>
		<div class="table-cell">
			{{fulfillment_email_form.email_attachment}}
		</div>
	</div>
	<div class="table-row">{{fulfillment_email_form.email_attachment.errors}}</div>	
	<input type="submit" id="id_submit" value="Submit" class="prevent_doubleclick"><br><br>
	</form>
</fieldset>

{% endblock %}



Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s