Robert C. Martin rocks
This is nothing new, Uncle Bob rocks we all know that. But... man, you've got to check out these podcasts, they're just impressive.
I am looking forward to buy some Robert's books.
This is nothing new, Uncle Bob rocks we all know that. But... man, you've got to check out these podcasts, they're just impressive.
I am looking forward to buy some Robert's books.
Impresionante el movimiento que está habiendo en Agile Spain últimamente. Prueba de ello es la reciente celebración de Agile Madrid y los podcasts y videos que están ya disponibles para descarga. No tienen desperdicio. Hay que felicitar a JM Beas por toda la energía que está poniendo en la comunidad.
Enlaces:
Que siga así la cosa
Ayer terminé de impartir mi primer curso de Test Driven Development de 10 horas. Anteriormente habia dado otro de unas 5 horas de duración que me dejó muy buen sabor y tenía ganas de intentarlo con otro más largo. Gracias a J.L. Roda, Pedro González y la Agencia Canaria de Investigación hemos podido montar un curso a modo de taller en la Universidad de La Laguna para un pequeño grupo de 15 intrépidos desarrolladores. El nivel de los asistentes ha sido muy bueno y su actitud también. La gente joven es menos reacia a nuevas metodologías y aunque en sólo 10 horas es muy complicado estudiar un proyecto de software relativamente grande, algunas de las ideas más importantes se asimilaron bien. Ha sido un placer. Espero que en el futuro podamos organizar un curso de mayor duración para abordar un desarrollo mayor y así demostrar que lo que hemos aplicado para un pequeño es exactamente igual para uno grande, aunque les cueste creerlo.
Gracias a todos los asistentes
Eclipe or Netbeans? Netbeans is great for the Swing designer, it is my favorite tool for desktop UI design.
However for the rest of the tasks I prefer Eclipse. Eclipse integration with Ant is very good as the editor reads all the dependencies within the buid.xml and provides all the autocompletion just by loading the project (create a new Java project from existing Ant file). Running the JUnit targets from the build.xml works fine which is something I couldn't make with Netbeans. Netbeans didn't find the tests.
Once the project is loaded go to Window menu -> Show view -> Ant. See the Ant view at your right hand side and click on the tiny "Add Buildfiles" button on the top of it. Select the build.xml and you're done.
My JUnit configuration (slightly modified from this post):
<path id="classpath.unit_test"> <pathelement location="${lib}/comun/junit-3.8.2.jar" /> <pathelement path="${build.datos}"/> <pathelement path="${build.cliente}"/> <pathelement location="./test/unitarios" /> <fileset dir="${lib.comun}"> <include name="**/*.jar"/> </fileset> <fileset dir="${lib}/cliente"> <include name="**/*.jar"/> </fileset> <fileset dir="${lib}/control"> <include name="**/*.jar"/> </fileset> </path> <target name="unit_test.compile" depends="unit_test.clean"> <javac srcdir="./test/unitarios" verbose="yes" includeAntRuntime="true" debug="on" debuglevel="lines,vars,source"> <classpath refid="classpath.unit_test"/> </javac> </target> <target name="unit_test.clean"> <delete verbose="yes"> <fileset dir="./test/unitarios" includes="**/*.class" /> </delete> </target> <target name="unit_tests.run" depends="unit_test.compile" description="Run unit tests"> <junit showoutput="yes" printsummary="yes" filtertrace="off" fork="yes" haltonfailure="no"> <classpath refid="classpath.unit_test" /> <formatter type="plain"/> <batchtest> <fileset dir="./test/unitarios/"> <include name="**/*Tests.class"/> </fileset> </batchtest> </junit> </target>
Exceptions' stack traces are sent to a file in the project main folder called Test-your_test_case_name.txt
For better output in the Eclipse Console while running the tests, click over the unit_test.run target with the right mouse button (in the Ant view) then "Run as" and then "Ant Build..." and set "-v" in the arguments in the "Main" tab. Note the "debug=on" in the javac target (thanks Nilesh).
The test execution summary is presented for each TestCase rather than altogether so scroll up to watch all
the results (I was wondering why just one TestCase was ran)
---------------------------
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:
class WizardStepForm(forms.Form): step_order = 0 confirmQuestion = "Proceed?" def process_dynamic_step(self, form_list): raise NotImplementedError("Please implement this") def get_field_name(self): raise NotImplementedError("Please implement this") def get_field_name_for_summary(self): fieldname = self.get_field_name() firstUpper = fieldname.upper()[0] + fieldname[1:] return firstUpper + ":" def append_end(self, form_list): return "", form_list
Now we assume our FormWizard will work with WizardStepForms instead of regular Forms:
class MyWizard(FormWizard): def get_summary(self, form_list): """ To show the user the summary at the last step """ summary = "" for i in form_list: if hasattr(i, "cleaned_data"): if isinstance(i, WizardStepForm): model = i.cleaned_data[i.cleaned_data.keys()[0]] append_text = i.get_field_name_for_summary() append_text = append_text + " " + str(model) summary = summary + append_text + "<br>" return summary def get_hidden_summary(self, form_list): """ Collect the information from all the steps which is in the input hidden field """ summary = "" for i in form_list: if hasattr(i, "cleaned_data"): model = i.cleaned_data[i.cleaned_data.keys()[0]] if hasattr(model, "id"): summary = summary + str(model.id) + ":" else: summary = summary + str(model) + ":" summary = summary[:-1] return summary def done(self, request, form_list): summary = self.get_summary(form_list) hidden_summary = self.get_hidden_summary(form_list) return render_to_response('confirm.html', { 'summary': summary, 'hidden_summary': hidden_summary, 'form': ConfirmationForm(), # a regular Form }, RequestContext(request)) def get_template(self, step): return 'wizard.html' def process_step(self, request, form, step): self.extra_context = {'info': ''} if hasattr(form, "cleaned_data"): info, self.form_list = form.process_dynamic_step(self.form_list) self.extra_context = {'info': info}
Now two forms for the wizard (two steps):
class SearchWhateverForm(WizardStepForm): step_order = 1 someChoice = forms.ChoiceField(choices=WHATEVER_CHOICES) @classmethod def get_field_name(self): return "whatever" def process_dynamic_step(self, form_list): form_list = form_list[:self.step_order] form_list.append(WhatToGenerateForm) return "How many items do you need to generate?", form_list class WhatToGenerateForm(WizardStepForm): step_order = 2 generate = forms.ChoiceField(choices=GENERATION_CHOICES) @classmethod def get_field_name(self): return "generate" def process_dynamic_step(self, form_list): form_list = form_list[:self.step_order] generate = self.cleaned_data['generate'] if generate == GENERATE_EXACT or generate == GENERATE_EXACT_90: form_list.append(HowManyToGenerateForm) return "Please set number", form_list else: return self.append_end(form_list)
File urls.py would be like this:
url(r'^wizard/$', MyWizard([SearchWhateverForm])
Only one template is needed for all the steps:
<form action="." method="post">
<table>
{{ form }}
</table>
<input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />
{{ previous_fields|safe }}
<input class="button" type="submit" value="{% trans "Next Step" %}" />
</form>
As you don't need views or templates to run the sequence, it is easy:
def test_StepsAreOk(self): form_list = [] form_list.append(SearchWhateverForm) # define somedata form_list[-1].cleaned_data = {SearchWhateverForm.get_field_name(): somedata} info, form_list = form_list[-1]().process_dynamic_step(form_list) self.assertEqual(form_list[-1].get_field_name(), WhatToGenerateForm.get_field_name())
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:
class SomeWizardStepForm(WizardStepForm): step_order = 1 #field is not defined @classmethod def get_field_name(self): return "whatever" def process_dynamic_step(self, form_list): form_list = form_list[:self.step_order] form_list.append(TheNextForm) return "whatever", form_list def createDynamicForm(self, someInput): class SomeDynamicForm(SomeWizardStepForm): field = forms.ModelChoiceField(queryset=someInput.get_whatever()) return SomeDynamicForm # returning a datatype not an instance.
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.
Initialize the FormWizard with an empty list:
url(r'^wizard/(?P<someInput>\d+)/$', MyWizard([]),
Add logic to MyWizard to populate and place the first form:
class MyWizard(FormWizard): someInput = None # this is a model in my actual code def _initialize(self, id): self.someInput = SomeModel.objects.get(id=id) self.extra_context = {'someInput': self.someInput} self.form_list = [] self.form_list.append(self.createDynamicForm(self.someInput)) def parse_params(self, request, *args, **kwargs): """ Get the parameters sent through the URL """ if self.someInput is None: # first time self._initialize(kwargs['someInput']) ....
It the code snippets are not clear enough let me know.
Do you want to work (part time) on development projects using Django?. We're looking for candidates all over the planet, we'll work over the internet. If you've got:
Django knowledge is also interesting. Remember this is just part time, not any permanent position.
Hackers please send me an email with your CV, or just your blog and your rates. Thanks
He tenido la suerte de entrar en el equipo de desarrollo de Novasoft Canarias en el proyecto de software que usa el Servicio Canario de Empleo. Muy buenas condiciones laborales y muy buen ambiente. La verdad que es una oportunidad estupenda. Esta vez estoy con Java. Ahora toca compaginar mi trabajo en ésta gran empresa con mis colaboraciones particulares con otras empresas que han tenido a bien confiar en mí para desarrollos varios. Se preveen meses de mucho trabajo y nuevos posts sobre cuestiones relativas a Java.
Por cierto, estoy buscando colaboradores con conocimientos en Django, HTML, CSS, y Javascript que puedan trabajar via internet a tiempo parcial. Si alguien esta interesado puede encontrar mi email en el enlace de Contacto.
Se acaba un buen año de trabajo en la Oficina de Software Libre de la ULL. Ha sido un año donde he aprendido muchísimo y disfrutado de muchos buenos momentos. Un jefe excelente, y en general, grandes compañeros. Sin duda ha sido una experiencia profesional y personal muy positiva. El proyecto software que he dirigido está a punto de publicarse y en producción en todos los servidores de las aulas de informática de la Universidad y estoy bastante satisfecho con el resultado. Es un buen momento para el cambio.
Asi que me toca cambiar de rumbo y afrontar nuevos retos. Es hora de ser profesional independiente y colaborar con empresas para proyectos concretos, aportando conocimiento, ideas y trabajo. Desde coordinar a desarrolladores hasta participar como desarrollador de refuerzo o de apoyo en momentos críticos de los proyectos, o llevar el rol de arquitecto. Crisis tambíen significa oportunidad. En ésta nueva etapa la docencia jugará un papel importante porque considero necesaria la difusión del agilismo, particularmente metodologías como eXtreme Programming. Se avecinan varios cursos sobre TDD pronto, ya os daré más detalles.
Si lees ésto y necesitas ayuda para tu empresa o proyecto, o personal para emprender uno nuevo, no dudes en ponerte en contacto conmigo
I am glad to blog about Pinsor 0.6 release. Pinsor is an Inversion of Control container for Python created by Ryan Svihla. I've started collaborating with Ryan on this release and committed a few changes (not much). It's been nice to talk about design decisions and see that there is a new release after all. Pinsor is useful from now and looks promising for the future.
We've developed a decorator intended to be placed on top of the "soapmethod" decorator that allows you to catch any exception raised by the web method.
Usage:
@catchWsExceptions @soapmethod(String, _returns=Array(String)) def srv_whatever(self, someParam): # just business logic, no exception handling required.
Decorator code:
import inspect import traceback def catchWsExceptions(func): def wrapped(*args, **kwargs): if func.__module__ <> "soaplib.service": raise ApplicationException("catchWsExceptions decorator can only be used before soapmethod decorator") try: result = func(*args, **kwargs) return result except Exception, e: tb= sys.exc_info()[2] tpl = traceback.extract_tb(tb) failedCall = str(tpl[-1][0]) if re.search("soaplib", failedCall): Logger.getInstance().logException(e) raise # this is not an exception within the function but before calling the actual function if type(e) == type(TypeError): raise # API mistmach exception should not be filtered msg = "" if hasattr(e, "message"): msg = e.message Logger.getInstance().logException(e) return ['2', msg + str(e.args)] wrapped.__doc__ = func.__doc__ wrapped.func_name = func.func_name wrapped._is_soap_method = True return wrapped
Logger and ApplicationException belong in our code base but you can just replace them. You may also raise the caught exception rather than return an array with an error code. It is a design decision.