Back to the main page

Django newforms: How to create dynamic forms

Django is a great framework for building web applications. Recently they started to create the newforms library that makes it easy to handle forms.

But the newforms library works only for static forms. Forms that have a fixed number of fields. What I wanted to do, is create a page that allows you to edit multiple instances of a model at once. It should be similar to edit_inline, but there should be a JavaScript link that allows you to add another instance of the object.

That's why I have written classes that make it possible to handle this type of forms using Django. No AJAX is utilized to make this work.

What will it look like?

dynamicforms

Getting started

Note: This howto won't work on Django 0.95. You need a recent SVN checkout of Django (I was using revision 4389).

I created a Python and JavaScript file that make it pretty easy to create dynamic forms. To be able to use my samples, you will have to download them and put the Python file into your application directory and the JavaScript file into your media directory:

Now let me show you have to use dynamicforms. Let's start with a simple model (models.py):

  1. from django.db import models
  2.  
  3. # Example model
  4. class Page(models.Model):   
  5.     title = models.CharField(maxlength=200, core=True)
  6.     content = models.TextField(null=True, blank=True)
  7.    
  8.     class Admin:
  9.         pass

Now the view (views.py). I'll first import some stuff and create a form:

  1. from django.template import RequestContext
  2. from django.http import HttpResponseRedirect
  3. from django.shortcuts import render_to_response
  4. from django import newforms as forms
  5.    
  6. import models
  7. import dynamicforms
  8.      
  9. class PageForm(dynamicforms.Form):
  10.     TEMPLATE = 'form.html' # Path to the form template
  11.     CORE = ('title',)
  12.  
  13.     title = forms.CharField(max_length=200)
  14.     content = forms.CharField(widget=forms.Textarea(attrs={'rows': 5, 'cols': 60}), required=False)

Note that I specified the template that will be used to render the form and the core fields, in this example title. Core fields are required to be able to add new elements.

The form template (form.html) itself is simple:

  1. <div class="form-row">{{ form.title.errors }}<label for="{{ form.title.auto_id }}" class="required">{{ form.title.label }}</label>{{ form.title }}</div>
  2. <div class="form-row">{{ form.content.errors }}<label for="{{ form.content.auto_id }}">{{ form.content.label }}</label>{{ form.content }}</div>

Feel free to style your form as you want. There are no restrictions.

Now let me add the actual view (views.py):

  1. # Example using a custom form (PageForm) and a form template
  2. def dynamicform_using_form(request):
  3.     if request.method == 'POST':
  4.         page_forms = PageForm.get_forms(request)
  5.         page_data = [page_form.render_js('from_template') for page_form in page_forms]
  6.        
  7.         if page_forms.are_valid():
  8.             for form in page_forms:
  9.                 # Do something with your data here
  10.                 print form.id, form.cleaned_data
  11.             return HttpResponseRedirect('.')
  12.     else:   
  13.         # Get your initial data here
  14.         page_data = [PageForm(initial={'title':page.title, 'content':page.content}, id=page.id).render_js('from_template') for page in models.Page.objects.all()]
  15.        
  16.     return render_to_response('edit.html', {
  17.         'page_template':PageForm().render_js('from_template'),
  18.         'page_data':page_data,
  19.        
  20.     }, context_instance=RequestContext(request)

If there's no POST data, the form will be initialized with a list of rendered forms that are JavaScript-ready. I am passing the page's ID to make it easy to save the objects later. There's also a dummy-form that will be used for adding new objects (page_template). Note that render_js('from_template') uses the from_template function which I implemented and renders the form using the given template. You can also pass the template name via the constructor of the form. Instead of form_template, you can also use e.g. as_p.

When posting the form, a list (FormCollection) of posted forms is returned via the get_forms classmethod. We are again preparing the page_data in case the forms don't validate. If the forms are valid, their IDs and their cleaned data will be printed. This example doesn't save anything, but I'll show you later how to use the built-in form_for_instance method to save the objects.

Here's the template of the edit page (edit.html):

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  2. <html xmlns="http://www.w3.org/1999/xhtml" lang="en-us" xml:lang="en-us">
  3. <head>
  4. <script src="/media/mootools.js" type="text/javascript"></script>
  5. <script src="/media/dynamicforms.js" type="text/javascript"></script>
  6. <link rel="stylesheet" type="text/css" href="/media/css/forms.css" />
  7. <script type="text/javascript">
  8. var PageForm = Form.extend({
  9.     initialize: function(value) {
  10.         this.parent({{ page_template }}, value);
  11.         this.fieldset = new Element('fieldset');
  12.         this.fieldset.setProperty('class', 'module aligned');
  13.         this.fieldset.innerHTML = this.html;
  14.         $('page-forms').adopt(this.fieldset);
  15.     }
  16. });
  17.  
  18. Window.onDomReady(function() {
  19.     {% for page in page_data %}
  20.     new PageForm({{ page }});
  21.     {% endfor %}
  22. });
  23. </script>
  24. </head>
  25. <body>
  26.  
  27. <div id="content">
  28.  
  29.     <form action="." method="post">
  30.  
  31.     <div id="page-forms"></div>
  32.  
  33.     <p><a href="#" onclick="new PageForm();">Add new page</a></p>
  34.  
  35.     <input type="submit" name="submit" value="submit">
  36.  
  37.     </form>
  38.  
  39. </div>
  40.  
  41. </body>
  42. </html>

Note that I am using the mootools JavaScript library, because it already implements stuff like class inheritance (you will need to enable Window.Base and it's dependencies when downloading mootools). You can rewrite the JavaScript code if you use another JavaScript library.

The PageForm JavaScript class uses the page_template to add a new form if no values are specified. Forms with existing data will be created in the Window.onDomReady function.

Another example using form_for_model and form_for_instance

Here's a working view (views.py) that let's you add and edit Page objects without using the manually created PageForm:

  1. # Example using form_for_model/instance and form.as_p()
  2. def dynamicform_using_model(request):
  3.     page_form = forms.form_for_model(models.Page, dynamicforms.BaseForm)
  4.  
  5.     if request.method == 'POST':
  6.         is_valid = True
  7.         page_forms = page_form.get_forms(request, {'core':('title',)})
  8.         page_data = [page_form.render_js('as_p') for page_form in page_forms]
  9.  
  10.         if page_forms.are_valid():
  11.             for form in page_forms:
  12.                 if form.id:
  13.                     instance_form = forms.form_for_instance(models.Page.objects.get(pk=form.id))(form.cleaned_data)
  14.                     assert instance_form.is_valid()
  15.                     instance_form.save()
  16.                 else:
  17.                     form.save()
  18.             return HttpResponseRedirect('.')
  19.     else:
  20.         page_data = [forms.form_for_instance(page, dynamicforms.BaseForm)(id=page.id).render_js('as_p') for page in models.Page.objects.all()]
  21.  
  22.     return render_to_response('edit.html', {
  23.         'page_template':page_form().render_js('as_p'),
  24.         'page_data':page_data,
  25.  
  26.     }, context_instance=RequestContext(request))

You have to specify dynamicforms.BaseForm in form_for_model, because Django requires a BaseForm. Saving objects is as easy as calling form.save(). For updating objects I had to create another form_for_instance and pass the page's id.

Comments?

Have a lot of fun using dynamicforms and please leave a comment on this page if you like (or don't) dynamicforms or if you have suggestions.

Update (February 18th): Improved get_forms() in dynamicforms.py to allow deletion of objects (now there are no premature breaks when a form is missing).

Update (May 6th): Improved dynamicforms.js to prevent data loss when storing the template in a variable and creating multiple objects at once.

Update (August 4th): Changed clean_data to cleaned_data, improved dynamicforms.js (unicode, from_template takes now an optional extra_context argument)

101 Responses to “Django newforms: How to create dynamic forms”

  1. Jason Davies Says:

    Wow, this is really cool. Great work.

  2. David Marko Says:

    Strange is the concept of having contraints on both: model and form

    title = models.CharField(maxlength=200, core=True)
    title = forms.CharField(max_length=200)

    Why do we define constraints on model, when we cant use it when saving form?

  3. tom Says:

    Did you see my second example? It uses form_for_model and form_for_instance and doesn’t need the PageForm.

  4. David Marko Says:

    I was blind and now can see :-)
    .. and nice article.

    David

  5. zyhua Says:

    hi,When I use your code,I can’t run these code correctly.

    can you paste a full code?thanks!

  6. tom Says:

    zyhua: I don’t have a sample project right now. Can you tell me what errors you’re getting?

  7. » Using dynamic choices with Django newforms and custom widgets Says:

    […] How to render dynamic forms using Django newforms. From the author’s blog: the newforms library works only for static forms. Forms that have a fixed number of fields. What I wanted to do, is create a page that allows you to edit multiple instances of a model at once.. Uses Javascript, no AJAX. […]

  8. Dave Says:

    Thanks for posting this - I was able to fix a little syntax problem by reading through your code.

  9. cheshire Says:

    return render_to_response(’edit.html’, {
    ‘page_template’:page_form().render_js(’as_p’),
    ‘page_data’:page_data,

    }, context_instance=RequestContext(request))

    returns syntaxerror, is it supposed to work?

  10. tom Says:

    cheshire: I don’t have a working setup right now so I can’t test it, but what Django version are you using and what’s the exact error message? Maybe your code indentation is wrong.

  11. cheshire Says:

    My bad, it does work :) It’s funny I’ve left out last closing bracket in my code, but actually pasted it here.

  12. cheshire Says:

    I just wanted to say this is really fantastic thing! I was going to spend few weeks writing similiar stuff in java-script, because I needed it for my project, but this thing really saved me. I like the way you can customize it - change form template, add many dynamicforms on one page.
    Thank you!

  13. cheshire Says:

    One question about this forms: is it possible to create nested dynamicforms? i.e. dynamicform inside dynamic form?
    Example - there is an object in database, which can have multiple tables associated with it. And each table can have multiple rows. It’s quite tricky to create all this stuff on one page, isn’t it?

  14. tom Says:

    cheshire: I didn’t try it, but as you said, this might be quite tricky. If you succeed, please let me know :-)

  15. cheshire Says:

    Hmm, it looks like you can’t upload images this way :’(
    According to my experience, it just puts the first image in request.FILES and ignores the rest of them :’(

  16. tom Says:

    I didn’t try it with files, but file uploads are always tricky. I might want to do a picture gallery, so maybe I will take a look at it, but right now I don’t have time. Let me know if you succeed.

    Are there only problems if you try to upload multiple forms/images simultaneously?

  17. cheshire Says:

    Nope, it doesn’t seem to work at all. You need a bit of *magic* if you want to use file upload with newforms - http://www.djangosnippets.org/snippets/95/ is a good example.
    But I’ve noticed there is some kind of magic in your code - when I try to call for request.POST it says request doesn’t have attribute ‘POST’, though request.method == ‘POST’ returns True.
    When I try to upload files without any magic, it tells me that field with photo is empty(?).
    I suppose it happens because it needs
    post_data = request.POST.copy()
    post_data.update(request.FILES)
    statements, but I can’t use those because request.POST fails.
    But it’s really weird - I’ve looked through dynamicforms.py and haven’t found any magic that can vary request so strong.

  18. cheshire Says:

    Yarr!! It works now.
    The change was really simple:
    Method get_forms in dynamicforms.py should accept request.POST as the argument, not the request. So it now looks like this:
    http://dpaste.com/hold/10986/
    Views function looks like this:
    http://dpaste.com/hold/10987/
    And finally, save function inside form class:
    http://dpaste.com/hold/10988/
    I hope this will hope someone.
    By the way, I’ve spotted few more bugs - if you create two different dynamicforms in one template, and those two different classes have same field name (say, uploading photos with description and uploading document with description) on one page they interfere with each other and somehow form which was not even touched becomes not_valid. I’m working on it now.

  19. tom Says:

    Cool! Thanks for your work! Did you already try to implement nested forms?

  20. cheshire Says:

    Not yet… I am working on quite a largescale project at the moment, so I am trying to do that as late as possible, because it is not that necessary.
    However I’ve made one more improvement to dynamicforms. Because I had about 10 forms, so it was really boring to write templates for all of them… So I’ve changed the code in the following way - if you specify TEMPLATE inside class, it will use it, if you won’t - it will dynamically create it.
    http://dpaste.com/hold/11004/
    I know it is slower, but don’t really care :)

  21. cheshire Says:

    I think I’ve found one more bug in your work… This form do not seem to go together with complicated widgets (multiple input), say, SelectDateWidget - http://code.djangoproject.com/browser/django/trunk/tests/regressiontests/forms/tests.py#L3412
    For some strange reason, it doesn’t add prefix before that one. I have few ideas, why it happens, but I need to doublecheck it.

  22. cheshire Says:

    Can you help me? Apparently, you module doesn’t like UnicodeBranch. May be, it secretly hates it - used with it, it raises UnicodeDecode error, when validation error is raised. Any idea why?

  23. tom Says:

    cheshire: Sorry, I didn’t try the unicode branch…

  24. cheshire Says:

    I’ve fixed it - if you’re using unicode branch with non-ascii characters you have to change

    def render(self, how):
    return ‘%s%s’ % (self.header(), getattr(self, how)())

    to

    def render(self, how):
    return ‘%s%s’ % (str(self.header()), getattr(self, how)())

    Might help someone :)

  25. cheshire Says:

    Actually, I’ve asked Ivan Sagalaev about that, and he asked Malcolm - http://groups.google.com/group/django-developers/browse_frm/thread/ef6d31c156527294 and Malcolm has fixed it in svn edition of unicode branch - http://code.djangoproject.com/changeset/5487 .
    So now your module works without any problems.

  26. cheshire Says:

    Can you give me a hint on how to rewrite this script to use jQuery?

  27. tom Says:

    What exactly is your problem? I never used jQuery, but here are some hints:
    1. Use $(document).ready instead of Window.onDomReady
    2. The PageForm is a subclass of the Form class. Instead of Form.extend you might use something like jQuery.fn.extend.

  28. Thad Wheeler Says:

    Thanks for this script, its perfect for a project I am working on. I do have a question however, I am building a form that will be using date fields, and I am having some trouble getting them to populate when I come back to the form.
    When I look at the javascript output I am seeing
    start_date.#_month for the inputs and I get the default date for each of my fields. (01/01/2007) instead of the date in the db.

    Any thoughts??

  29. tom Says:

    That’s probably because the regex (which replaces the “#”) isn’t compatible with the SelectDateWidget. I don’t have time to take a look at it at the moment, but you can try to modify the replace_res in dynamicforms.py, so that they replace the “#” with “FormId” (which is done in the render_js method).

  30. Thad Wheeler Says:

    Cool, thanks. I will look at that.

  31. cheshire Says:

    Neither jQuery.fn.extend nor jQuery.extend work :’-(
    Mostly, my problem is I don’t know java-script and i don’t want to learn it ) Probably I need to fix that.

  32. tom Says:

    cheshire: I recently took a look at jQuery, and it looks like jQuery doesn’t support extending of objects like it is possible in mootools [1]. So you will either have to use a third-party script like Base.js [2] which allows you to extend classes, or, you can simply do it the non-object-oriented way using functions.

    [1] http://www.coryhudson.com/blog/2006/09/12/extending-objects-and-classes-with-mootools/
    [2] http://dean.edwards.name/weblog/2006/03/base/

  33. cheshire Says:

    Hi again :)
    Can you please briefly explain how get_forms function works?
    I was trying to get it, but I don’t understand 1) what is classmethod decorator and why do you need it?
    2) why do you need “Super” function in add_prefix?

  34. tom Says:

    get_forms() takes a look into request.POST and returns the form instances.

    1. @classmethod means that it is a class method (i.e. not an instance method), so I can call it like MyForm.get_forms() (where MyForm is the class name). cls refers to the class.
    2. I’m calling the add_prefix of the superclass (BaseForm), because my method only adds a postfix, not the prefix.

  35. Matt Says:

    Hi Tom,

    This is a really nice idea, and I’d love to integrate it into my Django app. I’m quite new to Django and having a few problems finding my way through your code and the newforms docs.

    If you wouldn’t mind I could really use some help with the following:

    1. I can’t find any examples of how to save the data from newforms to the DB without using form_for_instance / form_for_model. Do you know of any examples out there?

    2. I’d like to be able to delete objects from this page as well as create them. Could you give me a couple of pointers on the best way to do that?

    Many thanks,
    Matt.

    P.S. Using your form_for_model approach I was getting some errors with the current SVN version (5794). If the form doesn’t validate, it produces “‘PageForm’ object is not callable” when “‘page_template’:page_form().render_js(’as_p’)” got called. To fix it, you have to re-call “page_form = forms.form_for_model(models.Page, dynamicforms.BaseForm)” to get a fresh copy of the page_form object.

  36. Matt Says:

    Oh, also in the current SVN version “clean_data” –> “cleaned_data”. Hopefully that will save someone some time.

  37. tom Says:

    Hi Matt,

    Thanks for your comment.

    1. Are you talking about newforms in general or about dynamicforms? Using dynamicforms, just use the object id as the form id and update the model’s fields like that:

    for form in forms:
        try:
            page = models.Page.objeccs.get(pk=form.id)
        except models.Page.DoesNotExist:
            page = models.Page()
        page.something = form.cleaned_data.get(’something’)
        page.save()

    2. Use a delete checkbox or AJAX.

    I didn’t try the form_for_model approach with latest SVN. I updated the dynamicforms.py file and changed clean_data to cleaned_data in the article.

    tom

  38. Matt Says:

    Hi Tom,

    Thanks for that help. I got so caught up in trying to understand Python that I neglected to read the docs properly! I also didn’t quite get the link between form.id and the pk of the model being modified. Your example helped a bunch!

    I spent some time converting the javascript from mootools to Ext. The end result is that there’s no longer any inline javascript in the template, and external javascript file is totally generic!

    To use it (assuming you’ve got the mootools version working), follow these steps:

    1. Put these three divs at the bottom of your template:

        {% for page in page_data %}
    
        		{{page}}
    
        {% endfor %}
    
    	{{ page_template }}
    
    	{{ form_name }}
    

    2. Include this in your css to hide them:

    div.dynamic-form-data {
    	visibility:hidden;
    	position:absolute;
    	top:0px;
    }
    
    div.dynamic-form-template {
    	visibility:hidden;
    	position:absolute;
    	top:0px;
    }
    
    div.dynamic-form-name {
    	visibility:hidden;
    	position:absolute;
    	top:0px;
    }
    

    3. Include your desired “form_name” in the template context. In your example this would probably be ‘Page’. This step isn’t required, but it’s referenced the javascript below.

    4. Replace dynamicforms.js with this file:

    ---------------------------------------------
    dynamicforms_ext.js:
    
    dynamicform = function() {
    
    	//private vars:
    	var template, instances, form_name
    
    	var form = new Ext.Template('{form_name} {number}{form}')
    
    	//public functions defined in return object
    	return {
    		init : function() {
    
    			//set instances to zero
    			instances = 0
    
    			//save form name
    			form_name = Ext.DomQuery.select('div[class*=dynamic-form-name]')[0].innerHTML;
    
    			//add click listener to the 'add new' button/whatever
    			var addnew = Ext.get(Ext.DomQuery.select('*[class*=add-form]')[0]);
    			addnew.on("click",function() {
    				dynamicform.add_form();
    			});
    
    			//get and save template
    			template = Ext.get(Ext.DomQuery.select('div[class*=dynamic-form-template]')[0]);
    
    			//grab any existing forms
    			var forms = Ext.DomQuery.select('div[class*=dynamic-form-data] div[class*=dynamic-form-entry]')
    
    			//add the existing forms (backwards so the most recently added is at the bottom)
    			for(var i=forms.length; i>0; i--) {
    				dynamicform.add_form(forms[i-1]);
    			}
    
    		},
    		add_form : function(value) {
    
    			//increment instance count
    			++instances
    
    			//regex the innerHTML to insert the instance number
    			if (value) {
    				var html = value.innerHTML
    			} else {
    				var html = template.dom.innerHTML
    			}
    			var regex = new RegExp('.#',"g");
    			var idstring = '.'+instances+'"';
    			html = html.replace(regex,idstring);
    
    			form.append('dynamic-forms',{'form_name':form_name, 'number':instances, 'form':html})
    
    		}
    	}
    }();
    
    Ext.onReady(dynamicform.init, dynamicform);
    
    ---------------------------------------------
    

    Your forms will be added to an element in the template called ‘dynamic-forms’.

    5. Change your view to render the template normally, rather than using render_js. You can now remove render_js and the associated regex stuff from dynamicforms.py

    6. Remove the inline javascript in your template

    7. Enjoy!

    Hope someone finds that useful.

    Thanks again Tom,
    Matt.

  39. tom Says:

    Thanks for your comment, Matt. I fixed the formatting of the code.

  40. Matt Says:

    Thanks Tom. The div tags from the first codeblock got lost, but there’s nothing special about them. They each just have an appropriate class so that the css hides them.

    Also I forgot to mention that you need something in your template with the class “add-form”, so that the javascript can attach a click listener to it.

  41. Martin Johnson Says:

    Hi there,

    Firstly, thanks for this ace bit of code! Really really handy. Needless to say someone (i.e. me) is trying to fiddle it to use it to do something slightly different. I’m nearly there, but have hit a brick wall. I wonder if you could help?

    More details below, but the problem boils down to the fact that the ‘dummy form for adding new objects’ (page_template in the example above) doesn’t have an id associated with it. Therefore (it appears), no form objects are returned to get_forms() after request.POST. Looking back at the example, I’m not sure how it works there either. Maybe I’ve missed something…

    Anyway, here are some details

    I’m using dynamic forms to ‘inline’ edit / add instances of a model which are related by foreign key to a parent model:

    Publication(models.Model)
     some=Fields()
    
    Authorship(models.Model)
     some=Fields()
     publication = models.ForeignKey(Publication)
    

    (2nd model named authorship as it is in fact an explicit join table between Publication and a Person/User model. The reasons for doing this rather than using a M2M relationship are long and ivolved).

    When I add / edit a publication I want to be able to add / edit the authorships (i.e. authors associated).

    I have a custom AuthorshipForm with a select(choices=[a.person for a in Authorship.objects.all()]

    and the view:

    def edit_publication(request, **kwargs):
    
     blah # A whole bunch of irrelevant stuff here about
     blah #publication model/ instance and whethere we're
     blah #editing or adding a publication
    
     if request.method=='POST':
      authorship_forms = AuthorshipForm.get_forms(request)
      authorship_data = [authorship_form.render_js('as_p')   for authorship_form in authorship_forms]
      if authorship_forms.are_valid(): #and pubform is_valid
       #test form output for debugging
       form_output=[]
       for form in authorship_forms:
        form_output.append('iterationtest')
        form_output.append(form.cleaned_data)
       print form_output
     else:
      if pubinstance: # i.e. if we're editing
       authorship_data = [#data retrieved from authorship on publication instance]
     else:
      #if we're adding a new publication make a new authorship form with ('none','----------') selected in drop-down. The form thus created will only return form instance to get_forms if we arbitrarily set the id to a value (1 in the case below)
      authorship_data = [AuthorshipForm(initial={'author':'none'},id=1).render_js(as_p)]
    
     return render_to_response('publications/pub_form.html',{
      #some other things which aren't important,
      'authorship_data': authorship_data,
      'authorship_template': AuthorshipForm(initial={'author': 'none').render_js('as_p')},
      RequestContext(request))
    

    So the problem is that the form we render in the case of adding publications (unless an arbitrary id is stated), or any form rendered by hitting the ‘add author’ button (i.e. onclick = “new AuthorForm” - from authorship_template) for either add or edit cases, cannot return a form object to get_forms unless they have an id (or so it appears). The reason I have concluded this is that I don’t see any ‘iterationtest’s in my debugging output for any form which doesn’t have an id stated.

    I think I must have got something wrong somewhere because the page_template in your example doesn’t require an id for get_forms to get anything back. Can anyone see where I’m going wrong.

    Thanks for your help

    Martin

  42. cheshire Says:

    Funny enough, in my app i needed to use, like, 8 dynamicforms one on single page. To get rid of repeated sections of code, i even created helper module, which automated rendering forms to template and getting them back, and now i see the module that does all those things automatically - cool! )
    I still have sufficiently large problem with dynamicforms - when I upload, say, twenty images with it, server returns 502 error. But it looks like it manages to save stuff to database before crash :)
    First thing that came to my mind was “RAM” and then “patch 2070″ - but.. I’ve monitored memory usage during serving response, and it never reached 100mb, while my limit is ~120.

  43. Martin Johnson Says:

    Hello again. Problem solved. In very bad form I changed two things at once - removed the custom MultiWidget I was using to add authorships (replaced with a simple multi-field form) and changed to rendering ‘from_template’ rather than ‘as_p’. Can’t say for sure which of those fixed the problem, but my guess would be the former.

  44. Martin Johnson Says:

    I cam across another issue and just wanted to share the resolution in case anyone else is trying to do the same thing:

    On my ‘Publications’ model and related models, the significance of the explicit join table ‘Authorships’ (foreignkey to both Publication and Author) is that the order of the authors must be preserved (not possible with m2m at the time I created these models).

    Thus, the order of authorship instances saved in the dynamicforms within my Publications form is key (models and view code summarised in earlier post). Unfortunately dicts (i.e. request.POST) are not ordered so the iterations conducted by get_forms to produce the FormCollection do not necessarily follow the order of the dynamic forms’ ids. Thus the authors end up in the wrong order when I iterate over the forms in the edit_publication view.

    The solution is pretty simple: we just need to sort the key values after the list comprehension ‘form_ids’ in get_form:

    dynamicforms.py:


    form_ids = [key[len(id_name)-1:] for key in request.POST.keys() if key.startswith(id_name[:-1])]
    form_ids.sort()

    Then the order in which forms are compiled into the FormCollection returned by get_forms is the same as the order authorships were added into the dynamic forms. Bob’s your uncle.

  45. cheshire Says:

    Martin,

    You comment actually helped me to solve my problem.
    Thanks!

  46. Martin Johnson Says:

    Fantastic! Teamwork eh? Cheers all

  47. cheshire Says:

    I’m in the process of rewriting this thing to use jQuery library (I prefer not to use Ext - it’s too heavy).
    client-side part works, but not the server side. I’m still struggling to understand dynamicforms.py. There’s a following section in render_js:

    data = re.sub(replace_re, '\1 \2', FormId, '\3\4', data)
    

    The question is, what are those 1s, 2s three’s and four’s are doing?
    Maybe it’s just my lack of knowledge of regexp and python.
    I’ve needed all names to look like id_destruction_description.$, so after trial and error I came up with:

    data = re.sub(replace_re, '\1 \2$\3\4', data)
    

    It works, but I don’t know why it works. It’s not really good, is it? :)

  48. cheshire Says:

    Tom,

    why in the first line in get_forms you have to initialise form with request.POST? O_O
    form = cls(request.POST, **kwargs)
    Why not just
    form = cls(**kwargs)?

  49. cheshire Says:

    And somehow id_name is alway id.# in all tests.

  50. tom Says:

    I don’t like the jQuery library (Mootools’ source code is prettier and more object-oriented – you can also choose which parts you need).

    The regex just replaces the # with “, FormId, “, which is later converted to a number by the JavaScript. I’m using cls(request.POST, **kwargs), because that’s how you have to initialize a form in newforms.

  51. cheshire Says:

    I agree with you, but I can’t be bothered looking inside the source code :) And I don’t like oop that much - java-script is object-oriented, but it never meant to had classes or inheritance. I don’t think that bringing those things to js when you can do without it is a smart idea.

    I just feel like your function has a lot of unneeded functionality, that’s why I’m confused about it :)

    And thanks for the reply

  52. cheshire Says:

    Hello,
    For anyone who is interested, I’ve finished rewriting the code to work with jQuery :)
    dynamicforms.py - http://dpaste.com/hold/17593/
    Things I’ve changed -
    1) Due to another client-side java-script, there’s no need for regex!
    2) There’s not get_forms function. Due to specific of my app, I have about 10 dynamicforms on one page, most of them were uploading images, so I’ve decided it would be more efficient to do that in a different way.
    I don’t really want to post my get_forms function here because it is quite ugly, but i can do that in case someone needs working version with jQuery :)
    3) As far as I get it - there’s no need for metaclass, so it was removed. This code works with latest trunk.
    4) Template is generated automatically - it was quite tedious to write it for 10 forms, and rewrite it each time manually.
    5) Function render() was renamed to as_div(). Overall flow was simplified, maybe I have lost some functionality, but I didn’t get it yet.

    Client side:
    http://dpaste.com/hold/17596/

    Code in the view and template:
    To create a form, one should create array forms, something like that:

    js_create_forms = “forms[’My Form’] = MyForm.render_js()


    java-script inside onDOMReady now looks like

    AddForm.add_prebuild_form(’formname’, form.render_js);

    And OnDOMReady was changed to

    $(document).ready(
    function() {
    {{ js_populate_forms }}
    }
    );

    Hope it would help someone %)

  53. tom Says:

    Does your code work correctly if you type a string containing # into the input field?

  54. cheshire Says:

    Good point :)
    It replaces all hashes with corresponding numbers.
    Thanks! I’ll try to fix that.

  55. cheshire Says:

    Ok,
    Somehow when I’ve tested that thing in my unittests, it worked. I’ve tried to do that on the webpage, and it worked properly that time. Maybe I’ve accidentally typed in the wrong value in the first example.
    So,
    What exactly do you mean? You type in # into the actual rendered field on the webpage? Is following unittest testing the thing you are talking about?
    Thanks %)

    Unittest:

    def add_monument_with_hashes(self):
            self.login()
            data = self.data()
            data['year.1'] = 'year###'
            data['description.1'] = 'description####'
            data['year.1'] = '#'
            data['description.1'] = 'description####'
            response = self.client.post('/add/', data)
            monument = Monument.objects.get(pk=4)
            rw = monument.revision_set.all()[0].restavrationworks_set.all()[0]
            rw2 = monument.revision_set.all()[0].restavrationworks_set.all()[0]
            self.failUnlessEqual(rw.year, 'year###')
            self.failUnlessEqual(rw.year, 'description####')
            self.failUnlessEqual(rw2.year, '#')
  56. cheshire Says:

    In my previous comment, it should be
    data[’year.2′] = ‘#’
    data[’description.2′] = ‘description####’
    Sorry for the spam.

  57. cheshire Says:

    Oh, and one more wrong line. Anyway, unittest - http://dpaste.com/hold/17664/ works.

  58. cheshire Says:

    And just in case you are talking about something like using
    self.fields[’my#field’] = blahblah inside form, then no, it wouldn’t work. But why on earth someone would do that? And python does not support hashes in variable names anyway.

  59. tom Says:

    What about displaying an input field with a hash?
    Something like <input type=”text” value=”#”>?

  60. cheshire Says:

    Yep, it turns hash into corresponding number. That’s why I though it wasn’t working for the first time.
    Thanks for that remark.
    Though I believe that fixing that thing is possible without restructuring the whole program - for example, using smart regexp in java-script to catch only those hashes that we need to.
    But I don’t think that I will fix that thing in nearest time - it’s quite tolerable bug.
    Thanks :)

  61. links for 2007-08-31 « PaxoBlog Says:

    […] http://eggdrop.ch/blog/2007/02/15/django-dynamicforms/ What I wanted to do, is create a page that allows you to edit multiple instances of a model at once. It should be similar to edit_inline, but there should be a JavaScript link that allows you to add another instance of the object. […]

  62. Martin Johnson Says:

    Hello again. I came across a problem with my ordered dynamicforms (see comment above), where I added form_ids.sort to dynamicforms.py to preserve the order of the objects added in the authors section of my publications form. This was all working beautifully until I tried to add a publication with >10 authors. Then, as form_ids were strings rather than numbers, the sorting gave the following order: [’1′,’10′,’11′,’12′,’2′,’3′,’4′… etc]. Disaster! as everytime the form was saved, the authors moved around. The fix was pretty simple:
    form_ids = [int(key[len(id_name)-1:]) for key in request.POST.keys() if key.startswi
    form_ids.sort()
    form_ids = [str(id) for id in form_ids]

    We get form_ids from request.POST.keys() as integers, sort them, then turn them back to strings so the rest of the code is happy with them.

    Cheers

    Martin

  63. Українська спільнота пайтоністів, блоґ » Django. Побудова динамічних форм Says:

    […] http://eggdrop.ch/blog/2007/02/15/django-dynamicforms  (англ., в новому вікні) […]

  64. James Says:

    Hi,

    Not sure if you can help with this but I’m not sure where else to ask. In your javascript code you create a NewForm object by calling the mootools extend() method on the global Form object. Running this (Firefox 2 on ubuntu), I get a “Form is not defined” error. This is odd, as the Form object is well documented in every Javascript reference I’ve looked at. Has anyone else seen this?

  65. tom Says:

    Hi James,

    Form is defined in dynamicforms.js, which you need to include.

    tom

  66. James Says:

    “Form is defined in dynamicforms.js, which you need to include.”

    I knew it had to be something stupid like that. Thanks Tom.

  67. Kenza Says:

    Hello,

    I am havig trouble using mootools I think, I get an error that Class is not defined in dynamicforms.js. I downloaded it and included Window.DomReady (There is no Window.Base like you say) and put it in the folder where form.html and edit.html is (I removed the /media/ from the script inclusion in the html files) how can I get it to work? Thank you

  68. Kenza Says:

    Ok, I figured out where to put the javascript, but I still get the “Class not defined” error (when I look at the javascript error console) in the dynamicforms.js. Does anybody know why this is happening? Did I forget to declaire something?

    Thank you,
    K

  69. webster Says:

    I am able to run this example just fine in IE6 - code unchanged - but when I try it in Firefox 2, I get a Form is not defined error at var PageForm = Form.extend({….

    Any ideas? I am not very familiar with Javascript.

  70. tom Says:

    webster: Is dynamicforms.js properly included?

  71. webster Says:

    Hi Tom

    I have what you have in edit.html but with the full path. What is confusing me is why does it work just fine in IE but not Firefox.

    Thanks
    W

  72. tom Says:

    webster: Maybe I can help you if you provide a link to an example page. Maybe there’s a syntax error in your HTML which prevents Firefox from including dynamicforms.js.

  73. webster Says:

    Hi Tom

    The site is not public yet so I can’t give you a link. But I can include the code here. What is the best way to do that?

    Webster

  74. tom Says:

    Paste it on http://dpaste.com/

  75. webster Says:

    OK it is here http://dpaste.com/26258/

    Thanks again for your help

    w

  76. tom Says:

    Haha. I do not think you can include a JavaScript using <script src=”C:/…”>. You can use a file:/// URL to solve your problem. However, I would reommend doing it the right way and setting up a web server for this purpose.

  77. webster Says:

    TOm, thanks for spending the tme to look at this. I will try out your suggestion.

  78. nuclearpanda.com » Blog Archive » Django method rename just ate hours of my life Says:

    […] Apparently, clean_data has been renamed to cleaned_data. Yikes! This isn’t a new change, either (http://eggdrop.ch/blog/2007/02/15/django-dynamicforms/). This has been around since February. […]

  79. manuq Says:

    Hello. I’m trying whis with the latest SVN. Is it still working? I ask because I’m having a syntax error in the inline JavaScript. See here:

    http://dpaste.com/34268/

    I was happy that I’ve found this, is what I was trying to do.

  80. manuq Says:

    The problem above was because of this:

    http://www.djangoproject.com/documentation/templates/#automatic-html-escaping

    I’ve found a solution: sorround the inline JavaScript with:

    {% autoescape off %}

    ….

    {% endautoescape %}

    It works very fine. Thanks!

  81. chrisbraybrook Says:

    Okay, I’ve got the better part of this set up the way that it is in the examples. (A few minor changes for CSS class settings, etc.)

    Here’s the inline javascript (inside a tag) on the page I’m using this for:

    var ItemForm = Form.extend({
    initialize: function(value) {
    this.parent({{ item_form }}, value);
    this.fieldset = new Element(’fieldset’);
    this.fieldset.innerHTML = this.html;

    $(’item-forms’).adopt(this.fieldset);
    }
    });
    window.onDomReady(function() {
    new ItemForm();
    })

    This is the content of the template file:

    {{ form.sku }}{{ form.description }}{{ form.serial_no }}{{ form.qty }}{{ form.taken }}{{ form.price }}

    And for some reason that I am unable to ascertain, all of the html markup I have in the template is stripped out when this is called while output is being returned in views.py:

    item_form(template=’inv_item_form.html’).render_js(’from_template’)

    Any ideas? I’ve been staring at this for too long already.

  82. chrisbraybrook Says:

    ammendment:

    since the /real/ content of my template got lost in that submission…

    <tr><td>{{ form.sku }}</td><td>{{ form.description }}</td><td>{{ form.serial_no }}</td><td>{{ form.qty }}</td><td>{{ form.taken }}</td><td>{{ form.price }}</td></tr>

  83. tom Says:

    @chrisbraybrook:
    If I understand you correctly, you are saying that my regular expressions strip out all the HTML in your template. If that’s so, can you put the output of .render(’from_template’) and the output of .render_js(’from_template’) on dpaste.com and post the link here?

  84. chrisbraybrook Says:

    Hmmm…now I’m not sure what it is up to.

    http://dpaste.com/35166/

  85. tom Says:

    @chrisbraybrook:
    Everything looks correct, so I don’t really know what and where your problem is. Can you explain?

  86. chrisbraybrook Says:

    I think I’ve figured out that it’s an issue with my understanding of the DOM.

    I pretty much copied your script verbatim, and as a result was getting all the data inserted as an HTML Fieldset. With the type of data that I’m using, I need it inserted as a proper table. (Which, I’m now realizing is going to be kind of difficult.)

    However, I’ll push forward, and see what I get. :) Thanks though.

  87. Ryan Says:

    Just wanted to drop you a thanks for this. This was exactly what I needed to get something done today. I had to do a bit of tweaking because I was working with a form only previously (not based on a model.) but I did get it to work!

    Thanks again, if you’re ever in London, I owe you a pint.

  88. Mintaka Says:

    Some sample project will be helpfull.

  89. Erik Dalén Says:

    Is it possible to use this with the new ModelForms in svn version?

    http://www.djangoproject.com/documentation/modelforms/

    Thanks for the really cool work.

  90. Erik Dalén Says:

    Or more spcifically, how do you modify it to use it with the new ModelForms?

  91. Hide IP Says:

    Thank you! I’ll learn Django and this tutorial help me.

  92. Caster Says:

    Thanks very much for the great work!

    But I also have problem of getting the expected results. The error is : “Line 35, Object Expected”. I refer back to the edit.html, which is

    href=”#” onclick=”new PageForm();

    So I think the problem should be the setting of urls.py. My urls.py is like

    (r’^showitem/$’, ‘dynamicform_using_form’),

    By the way, the only parts I changed are the path setting in the edit.html

    Thanks!

  93. Rex Says:

    Is the solution on the following page the same or different as the one presented here?
    http://guillaume.segu.in/blog/home/116/tip-of-the-day-dynamic-forms-with-django-newforms/

  94. tom Says:

    @Rex:
    It’s different. The biggest difference is that my approach passes the forms via JavaScript to the browser, i.e. the user can add as many forms as necessary by clicking on the “Add new” link.

    Also, my solution uses one form instance per form, instead of putting everything into one form instance.

  95. Django: Datumsfeld in ein DropDown Feld verwandeln | Wangoo Blog Says:

    […] 3. Lösung: Man benutzt ein "SelectDateWidget" - die Handhabung ist denkbar einfach: from django.forms.extras import widgets class ReservationDate(ModelForm): datum = djangoforms.DateField(widget=widgets.SelectDateWidget()) id = djangoforms.CharField(widget=djangoforms.HiddenInput) class Meta: model = Reservierungen fields = (’datum’,'id’) […]

  96. Django Tutorials | lonerunners.net Says:

    […] Django newforms: How to create dynamic forms […]

  97. Yiyang’s Weblog » Django newforms: How to create dynamic forms Says:

    […] http://eggdrop.ch/blog/2007/02/15/django-dynamicforms/ […]

  98. Dorian Says:

    Hey!
    Does anyone adapted this for Django 1.0 and have it working?

    Thanks!

  99. tom Says:

    Dorian: Actually we use this in the admin interface of django-cms. You might want to take a look at the demo site at http://django-cms.org/.

  100. Dorian Says:

    Thanks for the ultraquick answer :)

    I will have a look now…

  101. Dorian Says:

    I used the dynamicform.py, dynamicform.js and mootools.js from django-cms and all works just fine :)
    I cannot understand what I did wrong before when “migrating” the scripts, but… anyway…

    Thanks! :)

eggdrop.ch blog is powered by WordPress
Entries (RSS) and Comments (RSS).