domingo, 22 de junio de 2014

Registro de Usuarios para un sitio web con Django 1.6 usando Class Based Views y reCAPTCHA

En esta nueva entrada veremos como registrar usuarios en un sitio web creado con Django 1.6 y utilizando las vistas basadas en clases (Class Based Views), es importante recordar que desde la versión 1.5 Django introduce soporte para un modelo de usuario configurable, donde el modelo básico sigue presente, solo que ahora podemos especificar nuestro propio modelo y el sistema de autenticación de Django lo usa perfectamente.

Hay un cambio de paradigma total en este aspecto, si eres un programador de versiones anteriores a la 1.5, donde completabas el modelo del usuario con perfiles, esto ha quedado oficialmente desaprobado en la versión 1.6 en favor de modelos de usuarios personalizados. Dichos modelos deben ser configurados en la creación inicial del proyecto. les invito a ver la documentación para ampliar mas este tema.

También se aprovecha al  máximo el uso de las vistas basadas en clases (CBV), yo voy asumir que quienes están siguiendo este tutorial debe tener los conocimientos básicos del uso de Django.

También utilizaremos ReCaptcha que nos ayudara a prevenir el abuso automatizado del sitio web (por ejemplo, los comentarios no deseados o registros falsos) usando un CAPTCHA, garantizamos que sólo los seres humanos realizan estas acciones. Nos apoyaremos con una aplicación llamada django-recaptcha, para facilitarnos esta tarea.

La aplicación de registro le permitirá a un usuario ingresar sus datos y correo electrónico donde recibirá un enlace con una clave que activara su cuenta. Así de sencillo, quizás algunos se pregunten, ¿Por qué no utilizar django-registration? primero, esto lo hago con fines didácticos, segundo, si alguien quiere tener mas el control de su código esta es la mejor manera, de todas formas estoy abierto a sugerencias y mejoras. Bien, luego de esta breve introducción empecemos con el tutorial.

1.- Requisitos de Instalación: 

Para este ejemplo utilizare algunos programas para facilitar la realización del tutorial, en este caso utilizare django-bootstrap3 para instalar mas sencillo el framework de css bootstrap con filtros y tags de Django templates. Y por supuesto como dije previamente instalar django-recaptcha para el captcha en el formulario de registro. Es importante cumplir con los requisitos de recaptcha y generar sus claves para tu sitio web, también puedes crearlas para tu ambiente de desarrollo. ve la documentación de recaptcha. Para instalar estas aplicaciones solo tienes que instalarlas via pip.
pip install django-bootstrap3 django-recaptcha
Revisa la documentación de ambas aplicaciones si quieres obtener mas información.

2.- Creación del modelo:

Supongamos que hemos creado el proyecto de Django y dentro de el una aplicación llamada "registro", y basándonos en la nueva forma de crear modelos de usuario, creamos models.py
from django.db import models
from django.contrib.auth.models import AbstractUser

class UserRegister(AbstractUser):
    activation_key = models.CharField(max_length=30)
En este caso solo le hemos agregado a la clase abstracta de User un campo adicional, como he comentado brevemente, ustedes pueden crear sus propios modelos de usuario, de acuerdo a sus necesidades. En este caso, solo necesito un campo adicional de los que trae por defecto Django y es un campo para guardar la clave de registro. Luego se agrega el siguiente campo en el archivo settings.py:
AUTH_USER_MODEL = 'registro.UserRegister'
Revisamos que tenemos agregadas las aplicaciones que vamos a utilizar en INSTALLED_APPS como por ejemplo:
INSTALLED_APPS = (
    ...
    'bootstrap3',
    'registrar',
    'captcha',
)
Se crea el archivo admin.py, para poder seguir lo que esta registrando la base de datos por el administrador
from django.contrib import admin
from .models import UserRegister

admin.site.register(UserRegister)
Ahora creamos la base de datos con el comando python manage.py syncdb, hasta ahora solo hemos creado una aplicación con el sistema de autenticación por defecto de Django con un campo adicional. Ahora a realizar el sistema de registro de usuarios.

3.- Registro de Usuarios:

Primero vamos a crear el formulario en forms.py dentro de la aplicación. En principio se importa la clase creada en models.py, y como vamos a usar captcha para validar que no es un robot quien esta tratando de registrarse, importamos también el tipo de campo para el mismo. Creamos la clase heredada de forms y creamos los campos donde el usuario va registrar su clave y el campo captcha.
# -*- coding: UTF-8 -*-
from django import forms
from .models import UserRegister
from captcha.fields import ReCaptchaField

class RegistrationForm(forms.ModelForm):
    #Información de la clave
    password = forms.CharField( label='Clave', widget=forms.PasswordInput()
    )
    password_check = forms.CharField(label='Ingrese su clave nuevamente',
        widget=forms.PasswordInput()
    )
    captcha = ReCaptchaField(attrs={'theme' : 'clean'})
Agregamos la clase Meta para indicarle a la clase RegistrationForm con que modelo va trabajar y que campos va mostrar:
class Meta:
        model = UserRegister
        fields = ["username", "first_name", "last_name", "email",]
Ahora creamos los métodos para la clase RegistrationForm que haran las validaciones respectivas, asegurándose que las claves coincidan, que el usuario no exista o agreguen caracteres inválidos y evitar correos ya registrados, así nos debe quedar forms.py completo:
# -*- coding: UTF-8 -*-
from django import forms
from .models import UserRegister

import re
from captcha.fields import ReCaptchaField

class RegistrationForm(forms.ModelForm):
    #Información de la clave
    password = forms.CharField(
        label='Clave',
        widget=forms.PasswordInput()
    )
    password_check = forms.CharField(
        label='Ingrese su clave nuevamente',
        widget=forms.PasswordInput()
    )
    captcha = ReCaptchaField(attrs={'theme' : 'clean'})
   
    class Meta:
        model = UserRegister
        fields = ["username", "first_name", "last_name", "email",]

    def clean_password_check(self):
        """
        Revisar que ambas claves dadas coincidan
        """
        if 'password' in self.cleaned_data:
                password = self.cleaned_data['password']
                password_check = self.cleaned_data['password_check']
                if password == password_check:
                        return password_check
                raise forms.ValidationError('Claves no coinciden.')

    def clean_username(self):
        """
        Chequear que el usuario no exista y validar los caracteres ingresados
        """
        username = self.cleaned_data['username']
        if not re.search(r'^\w+$', username):
                raise forms.ValidationError('Usuario solo puede contener '
                                            'caracteres alfanumericos y underscore o piso.')
        try:
                UserRegister.objects.get(username=username)
        except UserRegister.DoesNotExist:
                return username
        raise forms.ValidationError('Nombre de usuario ya existe')

    def clean_email(self):
        """
        Validar que la direccion de correos dada sea unica en el proyecto
        """
        if UserRegister.objects.filter(email__iexact=self.cleaned_data['email']).count():
            raise forms.ValidationError(u'Esta dirección de correos esta en uso, por favor utilice otra')
        return self.cleaned_data['email']
Ahora en views.py se crea la clase para el registro de usuario, donde utilizaremos las librerías de Python random y string para crear la clave de 20 dígitos y grabarla en el campo activation_key que agregamos en el modelo y preparar el correo electrónico con el enlace a ser enviado al nuevo usuario para activar su cuenta, ya que hasta este momento esta registrado pero inactivo. Para esto hay que configurar a Django para que envié correos electrónicos, solo agrega estas lineas en settings.py:
#Aqui va la configuracion del servidor de correo (en este caso GMAIL)
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'usuario@gmail.com'
EMAIL_HOST_PASSWORD = 'suclave'
EMAIL_USE_TLS = True
Así queda views.py
# -*- coding: UTF-8 -*-
from django.views.generic import CreateView
from django.core.mail import EmailMultiAlternatives
from django.http import HttpResponseRedirect
import random, string
from .models import UserRegister
from .forms import RegistrationForm
class UserCreateView(CreateView):
    model = UserRegister
    form_class = RegistrationForm
    template_name = 'register/register.html'
   
    def form_valid(self, form):
        form.instance.is_active = False
        form.instance.activation_key = ''.join(random.choice(string.ascii_uppercase + string.digits)
                                               for n in range(20))
        usuario = form.cleaned_data.get('email')
        html_content = 'Por favor visite http://midominio.com/activar/%s/ para activar su cuenta' \
                       %(form.instance.activation_key)
        msg = EmailMultiAlternatives('Código de Activación',html_content,\
                                     'registration@midiminio.com',[usuario])
        msg.attach_alternative(html_content,'text/html') # Definimos el contenido como HTML
        msg.send() # Enviamos el correo
        return super(UserCreateView, self).form_valid(form)
Ahora crea urls.py, pero antes agrega la linea en urls.py del proyecto para incluir las urls de la aplicación:
#urls.py en el Proyecto
from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    url(r'^register/', include('register.urls')),  # Agrega esta linea

    url(r'^admin/', include(admin.site.urls)),
)
 Crea el archivo urls.py dentro de tu aplicación:
from django.conf.urls import patterns, include, url
from .views import *

urlpatterns = patterns('.views',
                       url(r'^$', UserCreateView.as_view(), name='register'),
)
Ahora configura los templates dentro de settings.py
TEMPLATE_DIRS = (
    os.path.join(BASE_DIR, "templates"),
)
Crea la carpeta templates donde esta manage.py (Esto por supuesto depende de la configuración). Este es un ejemplo como quedaría el árbol del proyecto:

Crea el template base y lo llamas base.html
{% load bootstrap3 %}
<!DOCTYPE html>

<html>
<head>
    {# Load CSS and JavaScript #}
    {% bootstrap_css %}
    {% bootstrap_javascript %}
    <title>{% block title %}Ejemplo{% endblock %}</title>
</head>

<body>

<div class="container">
    {% block body_block %}This is body_block's default content.{% endblock %}
</div>

</body>
</html>
Este template carga primeramente bootstrap3 gracias a la aplicación que cargamos al principio django-bootstrap3. Ahora crea el formulario, en este caso podemos crear una carpeta para ordenar los templeates de acuerdo a la aplicación que pertenecen:
{% extends 'base.html' %}
{% load bootstrap3 %}

{% block title %}Formulario de Registro{% endblock %}

{# Display django.contrib.messages as Bootstrap alerts #}
    {% bootstrap_messages %}

{# Display a form #}
{% block body_block %}
<form action="" method="post" class="form">
    {% csrf_token %}
    {% bootstrap_form form %}
    {% buttons %}
        <button type="submit" class="btn btn-primary">
            {% bootstrap_icon "star" %} Submit
        </button>
    {% endbuttons %}
</form>
{% endblock %}
Arrancamos el servidor y colocamos la dirección http://127.0.0.1:8000/register/ en el navegador.


Registramos el primer usuario él cual enviara el correo electrónico y al revisar por admin y vemos que el usuario existe, solo que esta inactivo. El cuerpo del correo sera:
Por favor visite http://midominio/activar/MNP54P4WB50W4N9NJIXG/ para activar su cuenta
Para activarlo, crea una clase en views.py que recibirá por la url el código de activación enviado al correo electrónico del usuario.
class ActivteUserView(View):
   
    def dispatch(self, *args, **kwargs):
        self.codigo = self.kwargs['codigo']
        return super(ActivteUserView, self).dispatch(*args, **kwargs)
       
    def get(self, request, *args, **kwargs):
        try:
            self.usuario = UserRegister.objects.get(activation_key=self.codigo)
            if self.usuario.is_active == False:
                self.usuario.is_active = True
                self.usuario.save()
                return HttpResponseRedirect("/register/activo_exito/")
            else:
                return HttpResponseRedirect("/register/activo/")
        except UserRegister.DoesNotExist:
            return HttpResponseRedirect('/register/error_activacion')
Como se ve en el codigo tenemos unas redirecciones en cada caso, si tuvo exito, si ya esta registrado o simplemente un codigo errado, para ellos creamos las vistas necesarias.
class ErrorActiveView(TemplateView):
    template_name = 'register/error_activacion.html'
   
class UserActiveErrorView(TemplateView):
    template_name = 'register/usuario_ya_activo.html'

class UserActiveView(TemplateView):
    template_name = 'register/usuario_activo.html'
Hay que importar las nuevas clases utilizadas: TemplateView y View
from django.views.generic import CreateView, TemplateView, View
Así queda views.py completo:
# -*- coding: UTF-8 -*-
from django.views.generic import CreateView, TemplateView, View
from django.core.mail import EmailMultiAlternatives
from django.http import HttpResponseRedirect
import random, string

from .models import UserRegister
from .forms import RegistrationForm

class ErrorActiveView(TemplateView):
    template_name = 'register/error_activacion.html'
   
class UserActiveErrorView(TemplateView):
    template_name = 'register/usuario_ya_activo.html'

class UserActiveView(TemplateView):
    template_name = 'register/usuario_activo.html'

class UserCreateView(CreateView):
    model = UserRegister
    form_class = RegistrationForm
    template_name = 'register/register.html'
   
    def form_valid(self, form):
        form.instance.is_active = False
        form.instance.activation_key = ''.join(random.choice(string.ascii_uppercase + string.digits)
                                               for n in range(20))
        usuario = form.cleaned_data.get('email')
        html_content = 'Por favor visite http://midominio.com/activar/%s/ para activar su cuenta' \
                       %(form.instance.activation_key)
        msg = EmailMultiAlternatives('Código de Activación',html_content,\
                                     'registration@
midominio.com',[usuario])
        msg.attach_alternative(html_content,'text/html') # Definimos el contenido como HTML
        msg.send() # Enviamos el correo
        return super(UserCreateView, self).form_valid(form)
   
class ActivteUserView(View):
   
    def dispatch(self, *args, **kwargs):
        self.codigo = self.kwargs['codigo']
        return super(ActivteUserView, self).dispatch(*args, **kwargs)
       
    def get(self, request, *args, **kwargs):
        try:
            self.usuario = UserRegister.objects.get(activation_key=self.codigo)
            if self.usuario.is_active == False:
                self.usuario.is_active = True
                self.usuario.save()
                return HttpResponseRedirect("/register/activo_exito/")
            else:
                return HttpResponseRedirect("/register/activo/")
        except UserRegister.DoesNotExist:
            return HttpResponseRedirect('/register/error_activacion')
Agregamos las urls que faltan en urls.py:
from django.conf.urls import patterns, include, url
from .views import *

urlpatterns = patterns('.views',
                       url(r'^$', UserCreateView.as_view(), name='register'),
                       url(r'^activar/(?P<codigo>.+)/$', ActivteUserView.as_view(), name='activar_user'),
                       url(r'^activo/$', UserActiveErrorView.as_view(), name='activo'),
                       url(r'^activo_exito/$', UserActiveView.as_view(), name='exito_activo'),
                       url(r'^error_activacion/$', ErrorActiveView.as_view(), name='error_activacion'),
)
Se realizan los templates html faltantes:
error_activacion.html:
{% extends 'base.html' %}
{% load bootstrap3 %}

{% block title %}Error de Activacion{% endblock %}

{# Display a form #}
{% block body_block %}
    <div class="container">
        <h1 class="text-muted">Error en Activacion</h1>
    </div>

{% endblock %}
usuario_activo.html
{% extends 'base.html' %}
{% load bootstrap3 %}

{% block title %}Usuario Activo{% endblock %}

{# Display a form #}
{% block body_block %}
    <div class="container">
        <h1 class="text-muted">Usuario Activo</h1>
    </div>

{% endblock %}
usuario_ya_activo.html:
{% extends 'base.html' %}
{% load bootstrap3 %}

{% block title %}Error de Activacion{% endblock %}

{# Display a form #}
{% block body_block %}
    <div class="container">
        <h1 class="text-muted">Error, Usuario ya activo</h1>
    </div>

{% endblock %}
Revisamos nuevamente por admin y veremos que el usuario aparece tildado activo y ya puedes controlar el sitio solo para usuarios registrados. Espero que sea de utilidad y no duden en dejar sus comentarios. Hasta la próxima.

No hay comentarios:

Publicar un comentario