Cansada de ser feliz

Bienvenidos a mi flujo de conciencia

Pruebas para formularios en Django

| Comments

En esta entrada de blog quiero compartir la forma en la que escribo ruebas para formularios en un proyecto de Django.

Primero vamos a instalar los paquetes necesarios:

1
2
$ pip install django-webtest
$ pip install factory-boy

WebTest es una biblioteca que nos ayuda a escribir pruebas para las aplicaciones wsgi. Es mucho más poderosa comparando con django.test.Client que viene con Django.

En el archivo de configuración especificamos que vamos a usar la base de datos SQLite para correr nuestras pruebas:

test_settings.py
1
2
3
4
5
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
    }
}

Suponemos que queremos escribir una prueba unitaria para una vista que crea un objeto del modelo Event:

models.py
1
2
3
4
5
6
7
8
class Event(models.Model):
    title = models.CharField(max_length=500)
    start_date = models.DateTimeField()
    end_date = models.DateTimeField(blank=True, null=True)
    people = models.ManyToManyField('history.Person', blank=True, related_name='events')

class Person(models.Model):
    name = models.CharField(max_length=255)

Aquí está la vista que maneja el formulario para crear un evento:

views.py
1
2
3
4
class EventCreateView(LoginRequiredMixin, CreateView):
    model = Event
    form_class = EventForm
    success_url = reverse_lazy('history:timeline')

Ahora escribimos nuestra prueba. Vamos a extender la clase django_webtest.WebTest, que en su lugar extiende django.test.TestCase de Django, y crear a un usuario:

tests.py
1
2
3
4
5
6
7
8
9
10
11
12
13
from django_webtest import WebTest

class HistoryViewsTests(WebTest):
    def setUp(self):
        self.superuser = get_user_model().objects.create_superuser(
            email='superuser@example.com',
            username='superuser',
            password='secret',
        )
        super(HistoryViewsTests, self).setUp()

    def test_create_event(self):
        # código para nuestra prueba

Ahora podemos probar que cuando un usuario trata de acceder nuestra página, se redirige al formulario de acceso (el códigos de estado HTTP es 302):

1
2
url = reverse('history:event_create')
self.app.get(url, status=302)

Ahora vamos a acceder la vista como superusuario:

1
response = self.app.get(url, user=self.superuser, status=200)

Como tenemos solo un formulario en la página, podemos obtenerlo por una de tres formas: por el atributo .form: response.form, por índice response.forms[0] o por id del formulario (artibuto HTML)response.forms['event_form'].

Para facilitar la depuración de nuestras pruebas, podemos pedir a mostrar el response en navegador predeterminado:

1
response.showbrowser()

Ahora podemos diligenciar nuestro formulario con los datos de prueba:

tests.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class HistoryViewsTests(WebTest):
    # ...

    def test_create_event(self):
        person1, person2, person3 = PersonFactory.create_batch(3)
        response = self.app.get(url, user=self.superuser, status=200)

        self.assertFalse(Event.objects.exists())

        form = response.forms['event_form']
        form['title'] = u'Título del evento'
        form['start_date'] = datetime.datetime.now()
        form['people'] = [person1.id, person2.id]
        form.submit().follow()

        self.assertEqual(Event.objects.count(), 1)

        event = Event.objects.first()
        self.assertEqual(event.title, u'Título del evento')

        people = event.people.all()
        self.assertIn(person1, people)
        self.assertIn(person2, people)
        self.assertNotIn(person3, people)

Para generar objetos del modelo Person usamos la biblioteca Factory Boy:

factories.py
1
2
3
4
5
6
7
8
9
10
from ..models import Person

import factory
import factory.fuzzy

class PersonFactory(factory.django.DjangoModelFactory):
    name = factory.fuzzy.FuzzyText(length=50)

    class Meta:
        model = Person

Así podemos acceder los opciones de una selección o selección múltiple:

1
2
3
4
5
>>> print form['people'].options
[(u'1', False, u'GLUZLvZfyjdZEmjNtnAJvsIVljodQjZpzLRDKrqJtYGiDLmSrN'), (u'2', False, u'vOxDBbmLaUXxJkJzcqYgLQpBieSoLtXJcpHCEPUpYUIzybhsAh'), (u'3', False, u'tfXSXCTIQICDwVPYvxZGSXgclFTnHbeYSQaMntxJNcgUJjzAwX')]
>>> form['people'] = [person1.id, person2.id]
>>> print form['people'].value
[u'1', u'2']

Ahora corremos nuestras pruebas y voilà:

1
2
3
4
5
6
7
8
$ ./manage.py test
Creating test database for alias 'default'...
..
----------------------------------------------------------------------
Ran 1 test in 0.463s

OK
Destroying test database for alias 'default'...

Enlaces:

Cómo configurar PyCharm para compilar los archivos SASS

| Comments

Suponemos que tenemos un proyecto con las siguientes carpetas:

app/
   static/
        css/
        sass/
            main.sass
            home.sass
            variables.sass

y queremos que cada vez cuando editemos alguno de los archivos .sass, PyCharm lo compile y ponga en la carpeta app/static/css/:

app/
   static/
        css/
            main.css
            home.css
        sass/
            main.sass
            home.sass
            variables.sass

Para esto debemos ir a File -> Settings -> File Watchers y agregar un nuevo watcher, escogiendo la opción SASS

y editar las configuraciones:

How to raise a form invalid event inside form_valid method of a FormView

| Comments

How to raise form invalid inside form_valid method of a FormView (CreateView/UpdateView) and add an error message to non_field_errors:

How to show a BooleanField of a ModelForm as radio select (yes/no) in Django

| Comments

Let’s suppose that we have a field is_active in our model. It is a boolean field, but in a template we want to show it as a radio select.

In this case we just need to add choices attribute for the model field and then change a widget of the corresponding form:

forms.py
1
2
3
4
5
6
class MyForm(forms.ModelForm):
    class Meta:
        model = MyModel
        widgets = {
            'is_active': forms.RadioSelect
        }
forms.py
1
2
3
4
5
6
7
8
9
10
11
12
from django.db import models

class MyModel(models.Model):
    STATE_CHOICES = (
        (True, u'Yes'),
        (False, u'No'),
    )
    is_active = models.BooleanField(
        verbose_name=u'Is it active?',
        default=False,
        choices=STATE_CHOICES,
    )

UPDATE:

For Django forms.

forms.py
1
2
3
4
5
6
7
8
from django import forms

class MyForm(forms.Form):
    is_active = forms.TypedChoiceField(
        coerce=lambda x: bool(int(x)),
        choices=((0, 'No'), (1, 'Yes')),
        widget=forms.RadioSelect
    )

How to send request to AJAX view in Django tests

| Comments

If you have a view that requires an AJAX request, in other words, checks if request.is_ajax(), here is a way you can write a unit test for this view:

¿Cómo concatenar querysets del mismo modelo en Django?

| Comments

Modo #1:

Podemos usar itertools.chain para unir dos o más querysets:

1
2
3
from itertools import chain
for item in chain(qs1, qs2, qs3):
    # ...

Modo #2:

Podemos usar el operador lógico OR:

1
2
res = qs1 | qs2 | qs3
res = res.distinct()

Algunos paquetes útiles para Django

| Comments

python-social-auth

Autentificación de usuarios con los redes sociales en Django.

django-recaptcha

Integración de reCAPTCHA en los formularios de Django.

django-taggit

Campo para los modelos de Django para crear etiquetas.

django-grappelli

Interface para el Administrador de Django.

django-constance

Para cambiar algunas variables de configuración desde el Administrador de Django.

django-recaptcha

Usar reCAPTCHA de Google en los formularios de Django.

django-markdown-deux

Templatetags y filtros que permiten usar Markdown en las plantillas de Django.

django-debug-toolbar

La herramienta que ayuda con la depuración del código en Django. Instrucciones para instalar el módulo: http://www.ruben-arranz.es/blog/instalar-django-debug-toolbar/

django-dynamic-fixture

Ayuda crear objetos de modelos de Django de la forma dinámica. Es muy útil para los test cuando hay necesidad de crear muchos objetos.

django-braces

La biblioteca de los mixins útiles para Django.

django-friendship

Biblioteca que ayuda a manejar las peticiones de amistad para los usuarios.

Django templates: render an array in two columns using Bootstrap grid

| Comments