Carlos Ble

Carlos Ble

I am a professional software developer, I solve problems.

I also teach and mentor developers to build better software.

Developing software since 2001.

Can I help you?

  • Do you need high quality tailor-made software?
  • Need training on TDD, clean code or refactoring?
  • Is it risky? do you need advise?
  • May I pair with you to write better code?

Events

Upcoming public training courses:

  1. [Online - en Español] 25 y 26 de Junio
    Test Doubles con JavaScript - Online
  2. [in English] July 7, 8 & 9
    TDD (open to the public) - Tenerife, Canary Islands
  3. [en Español] 14 y 15 Julio
    TDD (en abierto) - Gran Canaria, Canary Islands
  4. [in English] October 13, 14 & 15
    TDD (open to the public) - London, UK
  5. [en Español] 29, 30 y 31 de Octubre.
    TDD (en abierto) - Madrid, Spain

Conferences:

  1. I'll be at SocratesUK 2014
  2. I'll be at the London Test Gathering Workshops.

Archive for the ‘Django’ Category



Request dummies

If you ever need to create a Django request for testing purposes you can use this:

  1.  
  2. import urllib
  3. from urlparse import urlparse, urlunparse, urlsplit
  4. from django.test.client import FakePayload
  5. from django.conf import settings
  6. from django.conf.urls.defaults import *
  7. from django.http import HttpRequest, HttpResponse
  8. from django.utils.http import urlencode
  9. from django.core.handlers.wsgi import WSGIRequest
  10. from django.http import SimpleCookie
  11.  
  12. def create_request(path, data={}, method="GET"):
  13. parsed = urlparse(path)
  14. environ = {
  15. 'HTTP_COOKIE': SimpleCookie().output(header='', sep='; '),
  16. 'REMOTE_ADDR': '127.0.0.1',
  17. 'SCRIPT_NAME': '',
  18. 'SERVER_NAME': 'testserver',
  19. 'SERVER_PORT': '80',
  20. 'SERVER_PROTOCOL': 'HTTP/1.1',
  21. 'wsgi.version': (1,0),
  22. 'wsgi.url_scheme': 'http',
  23. 'wsgi.errors': None,
  24. 'wsgi.multiprocess': True,
  25. 'wsgi.multithread': False,
  26. 'wsgi.run_once': False,
  27. 'CONTENT_TYPE': 'text/html; charset=utf-8',
  28. 'PATH_INFO': urllib.unquote(parsed[2]),
  29. 'QUERY_STRING': urlencode(data, doseq=True) or parsed[4],
  30. 'REQUEST_METHOD': method,
  31. 'wsgi.input': FakePayload('')
  32. }
  33. return WSGIRequest(environ)
  34.  

It is pretty much a copy&paste from different parts of Django source code. Once you get this dummy request you can invoke actions directly (functions from your views.py)

How to select the main template tabs status from a child template using Django:

base.html:

{% block tabs%}
<ul class="menu">

<li class="{{ SEARCH_TAB_STATUS }}"><a href="/">
<span>{% trans "search_professional" %}</span></a></li>
<li class="{{ REGISTER_TAB_STATUS }}"><a href="/register">

<span>{% trans "want_work" %}</span></a></li>
</ul>
{% endblock %}

register.html:

{% extends "base.html" %}

{% block tabs%}
{% with "inactive" as SEARCH_TAB_STATUS %}
{% with "active" as REGISTER_TAB_STATUS %}
{{ block.super }}
{% endwith %}
{% endwith %}
{% endblock %}

Nice one!!!

Very important update: DO NOT USE THE FORMWIZARD for dynamic steps as described below. Django stores the FormWizard as a Singleton so concurrent sessions produce crazy behavior on the wizard. Thanks to my friends at Galotecnia.com because they found the problem. Use the FormWizard just as described in Django documentation and always stateless.

---------------------------
Django FormWizard is really useful. But when it comes to variable steps (I mean, depending on the choosen answer the user gets different forms) you can't just initialize the FormWizard with a bunch of Forms as you don't know which ones. Using polymorphism makes it possible to save lots of code lines by letting each form decide which one is next.

Instead of define your form extending Form, use this base class:

  1.  
  2. class WizardStepForm(forms.Form):
  3. step_order = 0
  4. confirmQuestion = "Proceed?"
  5.  
  6. def process_dynamic_step(self, form_list):
  7. raise NotImplementedError("Please implement this")
  8.  
  9. def get_field_name(self):
  10. raise NotImplementedError("Please implement this")
  11.  
  12. def get_field_name_for_summary(self):
  13. fieldname = self.get_field_name()
  14. firstUpper = fieldname.upper()[0] + fieldname[1:]
  15. return firstUpper + ":"
  16.  
  17. def append_end(self, form_list):
  18. return "", form_list
  19.  

Now we assume our FormWizard will work with WizardStepForms instead of regular Forms:

  1.  
  2. class MyWizard(FormWizard):
  3. def get_summary(self, form_list):
  4. """
  5. To show the user the summary at the last step
  6. """
  7. summary = ""
  8. for i in form_list:
  9. if hasattr(i, "cleaned_data"):
  10. if isinstance(i, WizardStepForm):
  11. model = i.cleaned_data[i.cleaned_data.keys()[0]]
  12. append_text = i.get_field_name_for_summary()
  13. append_text = append_text + " " + str(model)
  14. summary = summary + append_text + "<br>"
  15. return summary
  16.  
  17. def get_hidden_summary(self, form_list):
  18. """
  19. Collect the information from all the steps which is in the input hidden field
  20. """
  21. summary = ""
  22. for i in form_list:
  23. if hasattr(i, "cleaned_data"):
  24. model = i.cleaned_data[i.cleaned_data.keys()[0]]
  25. if hasattr(model, "id"):
  26. summary = summary + str(model.id) + ":"
  27. else:
  28. summary = summary + str(model) + ":"
  29. summary = summary[:-1]
  30. return summary
  31.  
  32. def done(self, request, form_list):
  33. summary = self.get_summary(form_list)
  34. hidden_summary = self.get_hidden_summary(form_list)
  35. return render_to_response('confirm.html',
  36. {
  37. 'summary': summary,
  38. 'hidden_summary': hidden_summary,
  39. 'form': ConfirmationForm(), # a regular Form
  40. },
  41. RequestContext(request))
  42.  
  43. def get_template(self, step):
  44. return 'wizard.html'
  45.  
  46. def process_step(self, request, form, step):
  47. self.extra_context = {'info': ''}
  48. if hasattr(form, "cleaned_data"):
  49. info, self.form_list = form.process_dynamic_step(self.form_list)
  50. self.extra_context = {'info': info}
  51.  

Now two forms for the wizard (two steps):

  1.  
  2. class SearchWhateverForm(WizardStepForm):
  3. step_order = 1
  4.  
  5. someChoice = forms.ChoiceField(choices=WHATEVER_CHOICES)
  6.  
  7. @classmethod
  8. def get_field_name(self):
  9. return "whatever"
  10.  
  11. def process_dynamic_step(self, form_list):
  12. form_list = form_list[:self.step_order]
  13. form_list.append(WhatToGenerateForm)
  14. return "How many items do you need to generate?", form_list
  15.  
  16. class WhatToGenerateForm(WizardStepForm):
  17. step_order = 2
  18. generate = forms.ChoiceField(choices=GENERATION_CHOICES)
  19.  
  20. @classmethod
  21. def get_field_name(self):
  22. return "generate"
  23.  
  24. def process_dynamic_step(self, form_list):
  25. form_list = form_list[:self.step_order]
  26. generate = self.cleaned_data['generate']
  27. if generate == GENERATE_EXACT or generate == GENERATE_EXACT_90:
  28. form_list.append(HowManyToGenerateForm)
  29. return "Please set number", form_list
  30. else:
  31. return self.append_end(form_list)
  32.  

File urls.py would be like this:

  1.  
  2. url(r'^wizard/$', MyWizard([SearchWhateverForm])
  3.  

Only one template is needed for all the steps:

  1.  
  2. <form action="." method="post">
  3. <table>
  4. {{ form }}
  5. </table>
  6. <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />
  7. {{ previous_fields|safe }}
  8. <p>
  9. <input class="button" type="submit" value="{% trans "Next Step" %}" />
  10. </p>
  11. </form>
  12.  

So, How could you test drive the wizard?

As you don't need views or templates to run the sequence, it is easy:

  1.  
  2. def test_StepsAreOk(self):
  3. form_list = []
  4. form_list.append(SearchWhateverForm)
  5. # define somedata
  6. form_list[-1].cleaned_data = {SearchWhateverForm.get_field_name(): somedata}
  7. info, form_list = form_list[-1]().process_dynamic_step(form_list)
  8. self.assertEqual(form_list[-1].get_field_name(), WhatToGenerateForm.get_field_name())
  9.  

How can I populate the choices of one form dynamically?

The forms_list of the wizard is not populated with instances but data types so you've got to dynamically create a form type once you know which choices will it have available. One solution is to extend from one of your forms:

  1.  
  2. class SomeWizardStepForm(WizardStepForm):
  3. step_order = 1
  4.  
  5. #field is not defined
  6.  
  7. @classmethod
  8. def get_field_name(self):
  9. return "whatever"
  10.  
  11. def process_dynamic_step(self, form_list):
  12. form_list = form_list[:self.step_order]
  13. form_list.append(TheNextForm)
  14. return "whatever", form_list
  15.  
  16.  
  17. def createDynamicForm(self, someInput):
  18. class SomeDynamicForm(SomeWizardStepForm):
  19. field = forms.ModelChoiceField(queryset=someInput.get_whatever())
  20. return SomeDynamicForm # returning a datatype not an instance.
  21.  

Method createDynamicForm can be inserted in MyWizard class or in any WizardStepForm, in its process_dynamic_step method, so once the datatype is returned the form_list can hold the new form.

What if I need to populate the first step form with an incoming variable?

Initialize the FormWizard with an empty list:

  1.  
  2. url(r'^wizard/(?P<someInput>\d+)/$', MyWizard([]),
  3.  

Add logic to MyWizard to populate and place the first form:

  1.  
  2. class MyWizard(FormWizard):
  3.  
  4. someInput = None # this is a model in my actual code
  5.  
  6. def _initialize(self, id):
  7. self.someInput = SomeModel.objects.get(id=id)
  8. self.extra_context = {'someInput': self.someInput}
  9. self.form_list = []
  10. self.form_list.append(self.createDynamicForm(self.someInput))
  11.  
  12. def parse_params(self, request, *args, **kwargs):
  13. """
  14. Get the parameters sent through the URL
  15. """
  16. if self.someInput is None: # first time
  17. self._initialize(kwargs['someInput'])
  18.  
  19. ....
  20.  

It the code snippets are not clear enough let me know.