Archive for category Django

Create a Django Dynamic Form with JQuery - Dynamic Field Addition and Removal

So I found a nice piece of Jquery - Jquery-Dynamic-Form ( http://code.google.com/p/jquery-dynamic-form/ ), and thought it would be just the thing to spice up the static forms on my latest venture (a django website, of course). So I downloaded the jquery file and tested it out, and I thought, to myself that it would be no problem to integrate - well, it took longer than I thought, but I finally got it to work nicely.

Here’s how I was able to integrate dynamic addition of form fields on my django form using jquery- dynamic-form:


0. First, fix the JQUERY. This particular jquery library used a ‘php’ style naming scheme for its variables - namely, something like:

<input type="text" name="myfield[]" id="0">
<input type="text" name="myfield[]" id="1">
<input type ="text" name="myfield[]" id="2" >

As you can see, it used the php array as the name - instead, i make a quick fix to the jquery to make it instead just do this naming scheme:

<input type="text" name="myfield0" id="0">
<input type="text" name="myfield1" id="1">
<input type ="text" name="myfield2" id="2" >

To do that was rather easy, simply change a couple lines in the jquery; to look like this:

/* Normalize field name attributes */
            if (!nameAttr) {
				jQuery(this).attr("name", "field" + fieldId);
			}

			if (!/\[\]$/.exec(nameAttr)) {
				jQuery(this).attr("name", nameAttr + fieldId);
			}

As you can see, we only changed 2 lines of code, so that it appends the ‘fieldId’ to the end of the name attribute.


1. Set-up the form template properly with jquery-dynamic-form (easy) - lets you clone whatever field you want, in my case, just a + and - buttons to click on to add / remove text fields on the fly (this template needs a dynamic element to it, but we’ll get to that in step 3)
The JQuery Stuff:

<script type="text/javascript" src="/media/js/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="/media/js/jquery-ui-1.7.2.highlight.min.js"></script>
<script type="text/javascript" src="/media/js/jquery-dynamic-form.js"></script>
<script type="text/javascript">
    $(document).ready (function() {
        $("#duplicate").dynamicForm("#plus", "#minus", {limit:6, createColor:"yellow", removeColor:"red"});

    });
</script>

The template form stuff:

<form method="post" action=".">
 <fieldset>
        <h3>My dynamic text fields</h3>

		<div id="fields">
		    <div class="field">
		        {{ dynamic_html }}
		        <p><span><div id="plus">Add Another <a href="">[+]</a></div>
		        <div id="minus">Remove <a href="">[-]</a></div></span></p>
                    </div>
               </div>
    <p><input type="submit" value="Build it!" /><br/></p>
 </fieldset>
</form>

As you can see, we dynamically create the guts of this template with a variable called {{ dynamic_html }} - I’ll show you what happens with this in a second.

But in general, its just some code that we generate that shows our bound form fields, but it works just like you think it would - clicking the plus adds a field, clicking the minus removes it, intuitive! Here is a quick preview:

A Preview of the Form

A Preview of the Form

2. When the form is submitted, the VIEW dynamically creates a form to match the submitted amount of fields. I used what i call the ‘type’ method to dynamically create the form- we’ll show you this in step three. When the form gets submitted, here is the view that handles it:

Here is the view code:

def myview(request, template_name='myapp/mytemplate.html'):

    if request.method == 'POST':
        #create the dynamic form class here on the fly
        form_class = make_dynamic_form(post=request.POST)

        form = form_class(data=request.POST)

        #this is the template that we insert into our general template
        dynamic_fields_template = make_dynamic_fields_template(form.data)

        if form.is_valid():
            #hooray! A Valid form - process the form fields as necessary!
           return HttpResponseRedirect('/')
    else:
        initial_vals = {'empty_textbox':'',}

        form_class = make_dynamic_form(post=initial_vals)
        form = form_class()
        dynamic_fields_template = make_dynamic_fields_template({'blog_url':''})

    dynamic_html = dynamic_fields_template.render(Context({'form':form}))
    return render_to_response(template_name,
                                {'form':form, 'dynamic_html':dynamic_html},
                                context_instance=RequestContext(request))

#Input: dictionary of fields into this method
#Output: Html (with our field variables not yet bound to context)
def make_dynamic_fields_template(fields):
    template_html = ""
    for f in fields:
        template_html += """
        <p id="duplicate">
        My Form Label {{ form.%s }}
        {{form.%s.errors}}
        </p>
        """ % (f, f)

    return Template(template_html)

Here you can see that we dynamically create our form on the fly from the POST information using a custom method that we show in the next step. If the form is valid, perfect, and you can process it like any other form. If it was the first time we accessed the page (and didn’t POST), then you can see that we simply create a form with one field on the fly, render the template for this one field, and then pass this dynamic_template inside the other template; this variable gets rendered it like a regular variable using {{ dynamic_html }} inside the template.

Why do we have to render the template on the fly? Well, if the template (with a bunch of new, say, text fields you added dynamically) doesn’t validate - well, then you need to return that form you just used (with its corresponding errors) - but you still want it to be a dynamic form, but the original template you used doesn’t know how many fields you added or removed dynamically (using jquery), or what their names are! Thus, we need to dynamically generate a template again from our new form, which we do by created the html and passing it into our template as a variable (and rendering it there)

2.5 To create the form on the fly, we use this method:
http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/ to create the form on the fly. You can see we override the ‘clean’ method; We validate all the dynamic fields, and if there are errors, assign the errors back to the individual fields (otherwise, errors would go in non_field_errors) - this is done using this technique: http://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other

And here is how the form is created on the fly:

def make_dynamic_form(myfields={}):
    fields = {}

    #create the fields dynamically!
    for p in myfields.keys():
        fields[p] = forms.CharField(max_length=200, required=False, widget=forms.TextInput(attrs={'class':'text' }))
    #this creates our new form with the given fields
    newform = type('MyDynamicForm', (forms.BaseForm,), { 'base_fields': fields })

    #now lets define our clean method
    def clean(self):
        cleaned_data = self.cleaned_data

        #iterate through each value
        for val in cleaned_data.keys():
            if not val:
                continue;

             if (self.cleaned_data[val] is None) or (self.cleaned_data[val] == ''):
                    continue;

            # clean the individual value below

             ...run your own cleaning tests here....
             ...say it doesn't validate.....

             #we defined assign_error function to assign the error to the correct field
             self.assign_error(val,u'This is an error we want to attach to that specific dynamic field!')
             #this error goes in non_field_errors - we call this method anyway
             raise forms.ValidationError(u'Error!')

    #attaches an error to a specific field instead of non_field_errors
    def assign_error(self,val,msg):
        if not self._errors.has_key(val):
            self._errors[val]= ErrorList([msg])
            del self.cleaned_data[val]

    #create the methods for the form
    newform.clean = clean
    newform.assign_error = assign_error

    return newform

What we do here is create a fields dictionary, pass this dictionary into the ‘type’ function along with some other stuff, and we create our form. Now, we define the methods we want, and set these as attributes for the form, and finally return the new form. In this case, we defined the clean method, and also a helper method to assign the errors to their respective fields - otherwise, django would put all our errors in non_field_errors, because thats what happens when you raise form validation errors inside of the ‘clean’ method.

Thanks! Any questions / comments are welcome!

Tags: , , , , , , , ,

An Important Question When Moving into Unknown Programming Territory

So you’re about to undertake a project that you have little experience with (whether it be the language) or little knowledge about at all. The first question to ask oneself is “What level do I need to be working at?”

Now, let’s say I want to write a web application, and I choose to use python-django. We start with a problem, say, ‘how do I create a user-login interface (i.e. a standard across websites - e-mail activation, etc). First, you should consider what levels are available to you. In this case, the levels (from top down) are as follows:

Django-App (Pre-Built)

Django-App (customized with Python)

Combingin Pre-Built Python Libraries That Each Do A Significant Piece (i.e. loggin in to website, storing a session)

Develop Using much smaller Python Modules that Each Do A Smaller Piece (handle an http request, parse html)

… (more levels)

Using C/C++ To Build Python modules and work with those

(lower than this is absurd for common web stuff usually!)

Often, developers can strangle themselves because they start working at too low of a level, instead of searching for higher-level solutions that will either work out of the box, or work with a small bit of customization (my preferred way). While more control is great - time is almost always a factor (plus if you use django, then you probably subscribe to the DRY principle (Don’t Repeat Yourself), and do this anyway!)

So before you start your next project, do your research (usually in the form of multiple google searches) and make sure you are operating at the correct level.

Tags: , , , ,

Extend the Django-registration Form - Adding the Extra Fields you Want

Django-registration is cool, but often you want to add some more fields to the Registration Form. At first, it doesn’t quite jump out at you how to do this. This blog post is all about how to add some extra fields into django-registration while having to write the least code. We’ll go through all the steps just because its nice to have completness. Let’s see what we can do.

I used easy_install django-registration, and it installed this egg file to my site-packages: django_registration-0.7-py2.5.egg

Steps:
1. Create your own app, ours is called ‘mynewapp’ (which is a very poor name, by the way), and add it to your settings.py file in the regular way by registering it under INSTALLED_APPS

2. In your app, create a model in your models.py file using this method: http://www.djangobook.com/en/1.0/chapter12/#cn222 and make sure to set the AUTH_PROFILE_MODULE in your settings.py file.

This model is the ‘extended’ fields that you want to see in the form, such as user ‘location’, ‘address’, or ‘favorite stripper’ to name a few example.

It should look something like this and should be in models.py:

from django.db import models
from django.contrib.auth.models import User
class ZProfile(models.Model):
user = models.ForeignKey(User, unique=True)
favorite_band = models.CharField(max_length=200, blank=True)

In our case, we created a class model called ZProfile. Then, we added this in our settings.py file:

AUTH_PROFILE_MODULE = "mynewapp.ZProfile"

Note: you MUST put ‘appname.ClassName’ NOT like ‘projectname.appname.modulename’ or anything like that. Our class is ZProfile (inside a module named something like zforms.py or something), our app is mynewapp.

3. Now create a new registration form for yourself. You can create a new python file, and name it like newform.py.In it, define new RegistrationFormZ that extends RegistrationForm; this should have the new fields you want to add, and also it should override the save() method of RegistrationForm (its superclass).
Something like this:

from django import forms
from registration.forms import RegistrationForm
from django.utils.translation import ugettext_lazy as _
from registrationz.models import ZProfile
from registration.models import RegistrationProfile
attrs_dict = { 'class': 'required' }
class RegistrationFormZ(RegistrationForm):
band = forms.CharField(widget=forms.TextInput(attrs=attrs_dict))
def save(self, profile_callback=None):
new_user = RegistrationProfile.objects.create_inactive_user(username=self.cleaned_data['username'],
password=self.cleaned_data['password1'],
email=self.cleaned_data['email'])
new_profile = ZProfile(user=new_user, favorite_band=self.cleaned_data['band'])
new_profile.save()
return new_user

So what we see is that when a new user account is created, so too do we create our profile account. Nice! But we’re not done yet.

We have to tell django-registration to use our new form, RegistrationFormZ, instead of the default RegistrationForm. Django-registration is well designed in that it lets just simply pass this in as a keyword argument to the ‘register’ view, specifying which registration form we want to use. This is done using url patterns. So, let’s create a urls.py file WITHIN OUR APP (i.e. within the app we created earlier) that looks something like ‘registration.urls’ (django-registration default). We only have to change the url pattern that maps to the register view, with this line:

url(r'^register/$',
register,
{'form_class' : RegistrationFormZ},
name='registration_register'),

Here is the whole of the urls.py inside my app, with this change:

from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template
from django.contrib.auth import views as auth_views

from registration.views import activate
from registration.views import register
from mynewapp.registrationz.formsz import RegistrationFormZ

urlpatterns = patterns('',
# Activation keys get matched by w+ instead of the more specific
# [a-fA-F0-9]{40} because a bad activation key should still get to the view;
# that way it can return a sensible "invalid key" message instead of a
# confusing 404.
url(r'^activate/(?Pw+)/$',
activate,
name='registration_activate'),
url(r'^login/$',
auth_views.login,
{'template_name': 'registration/login.html'},
name='auth_login'),
url(r'^logout/$',
auth_views.logout,
{'template_name': 'registration/logout.html'},
name='auth_logout'),
url(r'^password/change/$',
auth_views.password_change,
name='auth_password_change'),
url(r'^password/change/done/$',
auth_views.password_change_done,
name='auth_password_change_done'),
url(r'^password/reset/$',
auth_views.password_reset,
name='auth_password_reset'),
url(r'^password/reset/confirm/(?P[0-9A-Za-z]+)-(?P.+)/$',
auth_views.password_reset_confirm,
name='auth_password_reset_confirm'),
url(r'^password/reset/complete/$',
auth_views.password_reset_complete,
name='auth_password_reset_complete'),
url(r'^password/reset/done/$',
auth_views.password_reset_done,
name='auth_password_reset_done'),
url(r'^register/$',
register,
{'form_class' : RegistrationFormZeta},
name='registration_register'),
url(r'^register/complete/$',
direct_to_template,
{'template': 'registration/registration_complete.html'},
name='registration_complete'),
)

Now, you need to make sure to include this urls.py file in your PROJECT urls.py.

So go to your project’s global urls.py, and add these lines:

(r'^accounts/', include('myproject.mynewapp.urls')),
(r'^$', direct_to_template, { 'template': 'index.html' }, 'index'),

The first line simply includes the URL file that we created in our app; the second line uses direct_to_template notation, which means don’t do any djangoey stuff (i.e. dont’ redirect to a view) instead just show a plain static template. The second line is only necessary if you used those template files I provided a link to earlier (on the other blog), and you want to show your index.html when people go to your site.

Now, run python manage.py syncdb, and it should update the correct models. Now when you create a user account, it will created a profile for him/her that is accessible by calling the get_profile() method (because we created a ForeignKey relationship between our user and the profile account, remember?). This profile will have all the fields you defined, in our case, it just has one field, favorite_band (which I really don’t even have, sadly).

One thing, is that while the ‘management’ script to clean up expired users accounts works for RegistrationProfiles or whatever, you will probably have to modify this if you to say you also want to delete the user’s accompanying ZProfile as well when it is checking for expired accounts.

Hope this was helpful! Feel free to leave me a comment or question and I will try to reply.

Tags: , , , , , , , ,