Modificación de Datos con Django Formsets

Escriba su publicación aquí.

Siguiendo con nuestro ejemplo anterior ahora vamos a hacer la modificación de la compra usando formsets:

Como primer paso debemos agregar la opción de modificación en nuestro listado de compras, por lo que editamos nuestro archivo compras.html:

<a href="{% url 'productos:modificar_compra' compra.pk %}" class="btn">
    <span class="glyphicon glyphicon-edit"></span>
</a>

Este cambio se hace en la parte donde se obtiene el listado de las compras:

<tbody>
{% for compra in compras %}
<tr>
    <td>{{ compra.proveedor }}</td>
    <td>{{ compra.fecha }}</td>
    <td class="text-center">
        <a href="#" class="btn">
            <span class="glyphicon glyphicon-eye-open"></span>
        </a>
        <a onclick="return abrir_modal('{% url 'productos:modificar_compra' compra.pk %}')" class="btn">
            <span class="glyphicon glyphicon-edit"></span>
        </a>
    </td>
        </tr>
{% endfor %}
</tbody>

Creamos la url "modificar_compra":

from productos.views import ..., ModificarCompra

urlpatterns = [
        ...
        url(r'^modificar_compra/(?P<pk>.+)/$',ModificarCompra.as_view(), name="modificar_compra"),
]

Creamos la vista ModificarCompra que use el formulario CompraForm:

class ModificarCompra(UpdateView):
    model = Compra
    template_name = 'compra.html'
    form_class = CompraForm
    success_url = reverse_lazy('productos:listado_compras')

Es bastante parecido a la vista de creación de la compra, tenemos el modelo, la plantilla, el formulario y la url donde se redirigirá cuando la operación haya concluido con éxito.

Agregamos el método get para poder obtener los datos a modificar:

def get(self, request, *args, **kwargs):
    #Obtenemos el objeto Compra
    self.object = self.get_object()
    #Obtenemos el formulario
    form_class = self.get_form_class()
    form = self.get_form(form_class)
    #Obtenemos los detalles de la compra
    detalles = DetalleCompra.objects.filter(compra=self.object).order_by('pk')
    detalles_data = []
    #Guardamos los detalles en un diccionario
    for detalle in detalles:
        d = {'producto': detalle.producto,
             'cantidad': detalle.cantidad,
             'precio_compra': detalle.precio_compra}
        detalles_data.append(d)
    #Ponemos como datos iniciales del formset el diccionario que hemos obtenido
    detalle_compra_form_set = DetalleCompraFormSet(initial=detalles_data)
    #Renderizamos el formulario y el formset
    return self.render_to_response(self.get_context_data(form=form,
                                                         detalle_compra_form_set=detalle_compra_form_set))

Modificamos la plantilla compra.html para poder utilizarla también en la modificación y no crear otra:

{% if object %}
<form role="form" action="{% url 'productos:modificar_compra' object.pk %}" method="post">
        <h3>Modificar Compra</h3>
{% else %}
<form role="form" action="{% url 'productos:crear_compra' %}" method="post">
        <h3>Crear Compra</h3>
{% endif %}

Al consultar por la variable "object" estamos comprobando si el objeto compra existe, si es así entonces es una modificación y sino es una creación, esto es muy útil para reutilizar plantillas.

Con lo que tenemos ahora ya podemos ver un resultado como el siguiente:

/images/blog/modificar_compra.png

Debemos implementar el método post para poder guardar las modificaciones que hagamos:

def post(self, request, *args, **kwargs):
    #Obtenemos el objeto compra
    self.object = self.get_object()
    form_class = self.get_form_class()
    form = self.get_form(form_class)
    detalle_compra_form_set = DetalleCompraFormSet(request.POST)
    #Verificamos que los formularios sean validos
    if form.is_valid() and detalle_compra_form_set.is_valid():
        return self.form_valid(form, detalle_compra_form_set)
    else:
        return self.form_invalid(form, detalle_compra_form_set)


def form_valid(self, form, detalle_compra_form_set):
    #Guardamos el objeto compra
    self.object = form.save()
    detalle_compra_form_set.instance = self.object
    #Eliminamos todas los detalles de la compra
    DetalleCompra.objects.filter(compra = self.object).delete()
    #Guardamos los nuevos detalles de la compra
    detalle_compra_form_set.save()
    return HttpResponseRedirect(self.success_url)

def form_invalid(self, form, detalle_compra_form_set):
    #Renderizamos los errores
    return self.render_to_response(self.get_context_data(form=form,
                                                         detalle_compra_form_set = detalle_compra_form_set))

Para comprobar que los cambios estén funcionando debemos guardarlos y volver a abrirlos usando la misma modificación:

/images/blog/modificar_compra_datos.png

Como podemos ver, por ahora no existe la posibilidad de hacer eliminación de los formularios que nos vienen por defecto, pero ya corregiremos eso en las pŕoximas publicaciones.

Recuerden que el código del proyecto lo pueden encontrar en la siguiente ubicación:

Proyecto Modales

Saludos.

Implementando Django Formsets

En esta oportunidad vamos a trabajar con los formsets de Django que son muy interesantes y pueden ser de mucha ayuda cuando tenemos múltiples filas con detalles a rellenar, como por ejemplo los detalles de una compra. Vamos a seguir utilizando el proyecto llamado modales que hemos venido usando en posts anteriores y que ya tiene varios modelos listos para ser usados:

Vamos a crear un nuevo modelo llamado compra:

class Compra(models.Model):
    proveedor = models.ForeignKey(Proveedor)
    fecha = models.DateField(auto_now_add=True)

Como podemos ver este modelo Compra tiene dos atributos proveedor, donde se utiliza el modelo Proveedor que ya hemos usado, y fecha, que se asigna automáticamente la fecha actual.

También creamos el modelo DetalleCompra:

class DetalleCompra(models.Model):
    compra = models.ForeignKey(Compra)
    producto = models.ForeignKey(Producto)
    cantidad = models.DecimalField(max_digits=5, decimal_places=2)
    precio_compra = models.DecimalField(max_digits=5,decimal_places=2)

Este modelo tiene el detalle de la compra con los siguientes atributos: "compra" que referencia al modelo "cabecera"(por llamarlo de alguna manera), "producto" que hace la referencia al modelo Producto, cantidad para guardar la cantidad del producto comprado y el precio_compra para guardar el precio del producto en esa compra.

Igual que con los ejemplos anteriores primero vamos a mostrar un listado de las compras para ello necesitamos una vista, un template y su correspondiente url.

Creamos la clase ListadoCompras basada en la clase ListView:

class ListadoCompras(ListView):
    model = Compra
    template_name = 'compras.html'
    context_object_name = 'compras'

Creamos la plantilla compras.html:

{% extends "base.html" %}
{% block cuerpo %}
<h3>Compras</h3>
<div class="row">
        <div class="col-lg-10">
                <a href="{% url 'productos:crear_compra' %}" class="btn btn-primary">
                        Crear
                </a>
        </div>
</div>
<hr/>
<div class="row">
        <div class="col-lg-12">
                <table id="tabla" class="display" cellspacing="0" width="100%">
                        <thead>
                                <tr>
                                        <th class="text-center">PROVEEDOR</th>
                                        <th class="text-center">FECHA</th>
                                        <th class="text-center">ACCIONES</th>
                                </tr>
                        </thead>
                        <tbody>
                        {% for compra in compras %}
                <tr>
                    <td>{{ compra.proveedor }}</td>
                    <td>{{ compra.fecha }}</td>
                    <td class="text-center">
                        <a href="#" class="btn">
                            <span class="glyphicon glyphicon-eye-open"></span>
                        </a>
                        <a href="#" class="btn">
                            <span class="glyphicon glyphicon-edit"></span>
                        </a>
                    </td>
                                </tr>
                        {% endfor %}
                        </tbody>
                </table>
        </div>
</div>

<script>

$(document).ready(function()
{
    var table = $('#tabla').dataTable( {
        "language": {
                url: "/static/localizacion/es_ES.json"
        }
    } );
});
</script>
{% endblock cuerpo %}

Añadimos la url correspondiente, nótese que también se usa en esa plantilla la url "productos:crear_compra" por lo que debemos incluirla y crear una clase vacia para no tener problemas:

class CrearCompra(CreateView):
        pass

Ahora editamos el archivo urls.py:

from productos.views import ListadoCompras, CrearCompra

urlpatterns = [
        ...
        url(r'^compras/$', ListadoCompras.as_view(), name="listado_compras"),
        url(r'^crear_compra/$', CrearCompra.as_view(), name="crear_compra"),
]

Si en el navegador ponemos la url http://localhost:8000/compras/ nos debe aparecer la siguiente pantalla:

/images/blog/listado_compras.png

Editamos el archivo forms.py para crear los formularios que necesitamos.

Creamos el formulario para la compra usando un ModelForm y con el campo proveedor:

class CompraForm(forms.ModelForm):

    class Meta:
        model = Compra
        fields = ['proveedor']

    def __init__(self, *args, **kwargs):
        super(CompraForm, self).__init__(*args, **kwargs)
        for field in iter(self.fields):
            self.fields[field].widget.attrs.update({
                'class': 'form-control'
            })

Creamos nuestro formulario del Detalle de la compra usando también un ModelForm y con los campos producto, cantidad y precio_compra:

class DetalleCompraForm(forms.ModelForm):

    class Meta:
        model = DetalleCompra
        fields = ['producto','cantidad','precio_compra']

    def __init__(self, *args, **kwargs):
        super(DetalleCompraForm, self).__init__(*args, **kwargs)
        for field in iter(self.fields):
            self.fields[field].widget.attrs.update({
                'class': 'form-control'
            })

    def clean_cantidad(self):
        cantidad = self.cleaned_data['cantidad']
        if cantidad == '':
            raise forms.ValidationError("Debe ingresar una cantidad valida")
        return cantidad

    def clean_precio_compra(self):
        precio = self.cleaned_data['precio_compra']
        if precio == '':
            raise forms.ValidationError("Debe ingresar un precio valido")
        return precio

Nótese que hemos creados dos métodos "clean" para hacer las validaciones correspondientes tanto de cantidad como de precio_compra.

Ahora si vamos a crear un formset en linea, estos son una pequeña capa de abstracción que simplifica trabajar con objetos relacionados a través de una clave foránea:

from django.forms.models import inlineformset_factory

DetalleCompraFormSet = inlineformset_factory(Compra, DetalleCompra, form=DetalleCompraForm, extra=4)

Para hacerlo llamamos al método inlineformset_factory que usa a su vez al método modelformset_factory() pero con el argumento para borrar can_delete=True. Este método tiene como argumentos el modelo foráneo Compra, el modelo DetalleCompra que aparecerá en cada una de las filas, el formulario DetalleCompraForm que se corresponde con el modelo DetalleCompra y el parámetro extra que indica que aparecerán por defecto 4 filas.

Modificamos la vista CrearCompra para poder utilizar los formularios, para ello editamos el archivo views.py:

from productos.forms import CompraForm, DetalleCompraFormSet

class CrearCompra(CreateView):
    model = Compra
    template_name = 'compra.html'
    form_class = CompraForm
    success_url = reverse_lazy('productos:listado_compras')

En esta primera parte tenemos una clase basada en CreateView para facilitarnos la vida, con el modelo Compra, la plantilla 'compra.html' y el formulario CompraForm, el atributo success_url indica la dirección donde se va a retornar una vez que la creación del objeto haya sido concretada con éxito.

A continuación pasamos a describir el método get que va a ser necesario para mostrar el formulario cuando se llama a la url, vamos a explicar cada una de las lineas con comentarios:

def get(self, request, *args, **kwargs):
    """Primero ponemos nuestro object como nulo, se debe tener en
    cuenta que object se usa en la clase CreateView para crear el objeto"""
    self.object = None
    #Instanciamos el formulario de la Compra que declaramos en la variable form_class
    form_class = self.get_form_class()
    form = self.get_form(form_class)
    #Instanciamos el formset
    detalle_orden_compra_formset=DetalleCompraFormSet()
    #Renderizamos el formulario de la compra y el formset
    return self.render_to_response(self.get_context_data(form=form,
                                                         detalle_compra_form_set=detalle_orden_compra_formset))

Es necesario ahora crear un método post para recibir el contenido cuando se guarde:

def post(self, request, *args, **kwargs):
        #Obtenemos nuevamente la instancia del formulario de Compras
    form_class = self.get_form_class()
    form = self.get_form(form_class)
    #Obtenemos el formset pero ya con lo que se le pasa en el POST
    detalle_compra_form_set = DetalleCompraFormSet(request.POST)
    """Llamamos a los métodos para validar el formulario de Compra y el formset, si son válidos ambos se llama al método
    form_valid o en caso contrario se llama al método form_invalid"""
    if form.is_valid() and detalle_compra_form_set.is_valid():
        return self.form_valid(form, detalle_compra_form_set)
    else:
        return self.form_invalid(form, detalle_compra_form_set)

def form_valid(self, form, detalle_compra_form_set):
        #Aquí ya guardamos el object de acuerdo a los valores del formulario de Compra
    self.object = form.save()
    #Utilizamos el atributo instance del formset para asignarle el valor del objeto Compra creado y que nos indica el modelo Foráneo
    detalle_compra_form_set.instance = self.object
    #Finalmente guardamos el formset para que tome los valores que tiene
    detalle_compra_form_set.save()
    #Redireccionamos a la ventana del listado de compras
    return HttpResponseRedirect(self.success_url)

def form_invalid(self, form, detalle_compra_form_set):
        #Si es inválido el form de Compra o el formset renderizamos los errores
    return self.render_to_response(self.get_context_data(form=form,
                                                         detalle_compra_form_set = detalle_compra_form_set))

Creamos el template para la compra en el archivo "compra.html":

{% extends "base.html" %}
{% block cuerpo %}

<form role="form" action="{% url 'productos:crear_compra' %}" method="post">
        <h3>Crear Compra</h3>
        {% csrf_token %}
        <div class="panel panel-default">
                <div class="panel-body">
                        {{ form.as_p }}
                        {{ detalle_compra_form_set.management_form }}
                        {% for detalle_compra_form in detalle_compra_form_set %}
                                <div class="row">
                                        <div class="col-lg-4">
                                                <label>Producto</label>
                                                {% if detalle_compra_form.producto.errors %}
                                                        {% for error in detalle_compra_form.producto.errors %}
                                                        <div class="alert alert-danger alert-dismissible" role="alert">
                                                                <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                                                                        <span aria-hidden="true">&times;</span>
                                                                </button>
                                                                <strong>Error: </strong> {{ error|escape }}
                                                        </div>
                                                        {% endfor %}
                                                {% endif %}
                                                {{ detalle_compra_form.producto }}
                                        </div>
                                        <div class="col-lg-4">
                                                <label>Cantidad</label>
                                                {% if detalle_compra_form.cantidad.errors %}
                                                        {% for error in detalle_compra_form.cantidad.errors %}
                                                        <div class="alert alert-danger alert-dismissible" role="alert">
                                                                <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                                                                        <span aria-hidden="true">&times;</span>
                                                                </button>
                                                                <strong>Error: </strong> {{ error|escape }}
                                                        </div>
                                                        {% endfor %}
                                                {% endif %}
                                                {{ detalle_compra_form.cantidad }}
                                        </div>
                                        <div class="col-lg-4">
                                                <label>Precio</label>
                                                {% if detalle_compra_form.precio_compra.errors %}
                                                        {% for error in detalle_compra_form.precio_compra.errors %}
                                                        <div class="alert alert-danger alert-dismissible" role="alert">
                                                                <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                                                                        <span aria-hidden="true">&times;</span>
                                                                </button>
                                                                <strong>Error: </strong> {{ error|escape }}
                                                        </div>
                                                        {% endfor %}
                                                {% endif %}
                                                {{ detalle_compra_form.precio_compra }}
                                        </div>
                                </div>
                        {% endfor %}
                </div>
        </div>
        <div class="col-lg-12 text-right">
                <input type="submit" class="btn btn-primary" name="submit" value="Guardar">
                <a type="button" class="btn btn-default" href="{% url 'productos:listado_compras' %}">
                        Cancelar
                </a>
        </div>
</form>
{% endblock cuerpo %}

Análicemos parte por parte esta plantilla:

Primero renderizamos el formulario de la compra:

{{ form.as_p }}

Luego renderizamos el management_form del formset que nos indica varios parámetros importantes del formset tales como el total de formularios dentro del formset y la cantidad mínima y máxima de formularios que se pueden crear en el formset:

{{ detalle_compra_form_set.management_form }}

Hacemos un recorrido al formset renderizando form por form con cada uno de sus campos y sus respectivos errores:

{% for detalle_compra_form in detalle_compra_form_set %}
        <div class="row">
                <div class="col-lg-4">
                        <label>Producto</label>
                        {% if detalle_compra_form.producto.errors %}
                                {% for error in detalle_compra_form.producto.errors %}
                                <div class="alert alert-danger alert-dismissible" role="alert">
                                        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                                                <span aria-hidden="true">&times;</span>
                                        </button>
                                        <strong>Error: </strong> {{ error|escape }}
                                </div>
                                {% endfor %}
                        {% endif %}
                        {{ detalle_compra_form.producto }}
                </div>
                <div class="col-lg-4">
                        <label>Cantidad</label>
                        {% if detalle_compra_form.cantidad.errors %}
                                {% for error in detalle_compra_form.cantidad.errors %}
                                <div class="alert alert-danger alert-dismissible" role="alert">
                                        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                                                <span aria-hidden="true">&times;</span>
                                        </button>
                                        <strong>Error: </strong> {{ error|escape }}
                                </div>
                                {% endfor %}
                        {% endif %}
                        {{ detalle_compra_form.cantidad }}
                </div>
                <div class="col-lg-4">
                        <label>Precio</label>
                        {% if detalle_compra_form.precio_compra.errors %}
                                {% for error in detalle_compra_form.precio_compra.errors %}
                                <div class="alert alert-danger alert-dismissible" role="alert">
                                        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                                                <span aria-hidden="true">&times;</span>
                                        </button>
                                        <strong>Error: </strong> {{ error|escape }}
                                </div>
                                {% endfor %}
                        {% endif %}
                        {{ detalle_compra_form.precio_compra }}
                </div>
        </div>
{% endfor %}

Finalmente nos debe quedar una imagen para crear compra como la siguiente:

/images/blog/crear_compra.png

Si llenamos los datos correspondientes:

/images/blog/crear_compra_datos.png

Cuando le demos click al botón guardar automáticamente nos guarda los elementos y retorna a la ventana con el listado de las compras:

/images/blog/listado_compras_datos.png

Por ahora no tenemos un detalle de lo guardado pero si lo está haciendo correctamente, en el siguiente post veremos como mostrar los datos guardados en el admin, recuerden que el código del proyecto lo pueden encontrar en la siguiente ubicación:

Proyecto Modales

Hasta la pŕoxima, saludos.

Modales en Django con Vistas Basadas en Clases y Bootstrap

Vamos a hacer lo mismo que en el post anterior pero con la diferencia que ahora usaremos los modales de bootstrap, para ello ya tenemos creados nuestro proyecto "modales" y la aplicación "productos", para hacer las pruebas debemos crear un nuevo modelo llamado "proveedores":

class Proveedor(models.Model):
    ruc = models.CharField(unique=True,max_length=11)
    razon_social = models.CharField(max_length=150)
    direccion = models.CharField(max_length=200)
    telefono = models.CharField(max_length=15,null=True)
    correo = models.EmailField(null=True)
    estado = models.BooleanField(default=True)

Creamos las vistas correspondientes al listado, creación, modificación y detalle de los proveedores:

archivo: views.py

class ListadoProveedores(ListView):
    model = Proveedor
    template_name = 'proveedores.html'
    context_object_name = 'proveedores'

class CrearProveedor(CreateView):
    template_name = 'proveedor.html'
    form_class = ProveedorForm
    success_url = reverse_lazy('productos:listado_proveedores')

class ModificarProveedor(UpdateView):
    model = Proveedor
    template_name = 'proveedor.html'
    form_class = ProveedorForm
    success_url = reverse_lazy('productos:listado_proveedores')

class DetalleProveedor(DetailView):
    model = Proveedor
    template_name = 'detalle_proveedor.html'

El formulario a utilizar tanto para modificación como para la creación del proveedor es uno solo:

archivo: forms.py

class ProveedorForm(forms.ModelForm):

    class Meta:
        model = Proveedor
        fields = ['ruc', 'razon_social', 'direccion', 'telefono','correo', 'estado']

    def __init__(self, *args, **kwargs):
        super(ProveedorForm, self).__init__(*args, **kwargs)
        for field in iter(self.fields):
            if field <> 'estado':
                self.fields[field].widget.attrs.update({
                    'class': 'form-control'
                })

Ahora debemos modificar nuestro archivo urls.py para poder agregar las nuevas urls:

archivo: urls.py

urlpatterns = [
        #.....
    url(r'^proveedores/$', ListadoProveedores.as_view(), name="listado_proveedores"),
    url(r'^crear_proveedor/$', CrearProveedor.as_view(), name="crear_proveedor"),
    url(r'^modificar_proveedor/(?P<pk>.+)/$',ModificarProveedor.as_view(), name="modificar_proveedor"),
    url(r'^detalle_proveedor/(?P<pk>.+)/$',DetalleProveedor.as_view(), name="detalle_proveedor"),
]

Veamos primero el archivo proveedores.html que es el template desde donde se van a realizar las demás operaciones:

archivo: proveedores.html

{% extends "base.html" %}
{% block cuerpo %}
<h3>Proveedores</h3>
<div class="row">
        <div class="col-lg-10">
                <a onclick="return abrir_modal('{% url 'productos:crear_proveedor' %}')" class="btn btn-primary">
                        Crear
                </a>
        </div>
</div>
<hr/>
<div class="row">
        <div class="col-lg-12">
                <table id="tabla" class="display" cellspacing="0" width="100%">
                        <thead>
                                <tr>
                                        <th class="text-center">RUC</th>
                                        <th class="text-center">RAZON SOCIAL</th>
                                        <th class="text-center">DIRECCION</th>
                                        <th class="text-center">ESTADO</th>
                                        <th class="text-center">ACCIONES</th>
                                </tr>
                        </thead>
                        <tbody>
                        {% for proveedor in proveedores %}
                <tr>
                    <td>{{ proveedor.ruc }}</td>
                    <td>{{ proveedor.razon_social }}</td>
                    <td>{{ proveedor.direccion }}</td>
                    {% if proveedor.estado %}
                    <td>ACTIVO</td>
                    {% else %}
                    <td>INACTIVO</td>
                    {% endif %}
                    <td class="text-center">
                        <a onclick="return abrir_modal('{% url 'productos:detalle_proveedor' proveedor.pk %}')" class="btn">
                            <span class="glyphicon glyphicon-eye-open"></span>
                        </a>
                        <a onclick="return abrir_modal('{% url 'productos:modificar_proveedor' proveedor.pk %}')" class="btn">
                            <span class="glyphicon glyphicon-edit"></span>
                        </a>
                    </td>
                                </tr>
                        {% endfor %}
                        </tbody>
                </table>
        </div>
</div>
<div id="popup" class="modal fade" role="dialog">

</div>

Tenemos la tabla con el listado de los proveedores ya creados junto con dos enlaces en cada fila de proveedor para poder desplegar el detalle y la modificación del mismo en los modales correspondientes, en la parte final vemos que para mostrar el modal usaremos el div con el id "popup" y con las clases de bootstrap "modal" y "fade" para que la ventana a mostrar sea un modal con un ligero efecto al mostrarse.

Ahora vamos a ver la parte de javascript necesaria para poder mostrar el modal correspondiente:

function abrir_modal(url)
{
        $('#popup').load(url, function()
        {
                $(this).modal('show');
        });
        return false;
}

function cerrar_modal()
{
        $('#popup').modal('hide');
        return false;
}

$(document).ready(function()
{
    var table = $('#tabla').dataTable( {
        "language": {
                url: "/static/localizacion/es_ES.json"
        }
    } );
});

Notamos que aquí la forma de trabajo es mas sencilla que cuando la haciamos con JQuery-UI ya que solamente usamos el método load() y mostramos el contenido de la ruta que pasamos como argumento.

Ahora veamos los templates correspondientes a la creación, modificación y detalle del proveedor, en el caso de los dos primeros es un solo template:

archivo: proveedor.html

<div class="modal-dialog modal-lg">
        <div class="modal-content">
                {% if object %}
                <form role="form" action="{% url 'productos:modificar_proveedor' object.pk %}" method="post">
                {% else %}
                <form role="form" action="{% url 'productos:crear_proveedor' %}" method="post">
                {% endif %}
                        <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">x</button>
                <h3>Modificar Proveedor</h3>
            </div>
            <div class="modal-body">
                                {% csrf_token %}
                                <div class="panel panel-default">
                                        <div class="panel-body">
                                                {{ form.as_p }}
                                        </div>
                                </div>
                        </div>
                        <div class="modal-footer">
                                <div class="col-lg-12 text-right">
                                        <input type="submit" class="btn btn-primary" name="submit" value="Guardar">
                                        <button type="button" class="btn btn-default" onclick="return cerrar_modal()">
                                                Cancelar
                                        </button>
                                </div>
                        </div>
                </form>
        </div>
</div>

archivo: detalle_proveedor.html

<div class="modal-dialog modal-lg">
        <div class="modal-content">
                <div class="modal-header">
                        <button type="button" class="close" data-dismiss="modal">x</button>
                        <h3>Detalle Proveedor</h3>
                </div>
                <div class="modal-body">
                        <div class="row">
                                <div class="col-lg-4">
                                        <label>RUC:</label>
                                        <p>{{ object.ruc }}</p>
                                        <label>RAZÓN SOCIAL:</label>
                                        <p>{{ object.razon_social }}</p>
                                        <label>DIRECCIÓN:</label>
                                        <p>{{ object.direccion }}</p>
                                        <label>TELÉFONO:</label>
                                        <p>{{ object.telefono }}</p>
                                        <label>CORREO:</label>
                                        <p>{{ object.correo }}</p>
                                        <label>ESTADO:</label>
                                        {% if object.estado %}
                                                <p>ACTIVO</p>
                                        {% else %}
                                                <p>INACTIVO</p>
                                        {% endif %}
                                </div>
                        </div>
                </div>
                <div class="modal-footer">
                        <div class="col-lg-12 text-right">
                                <button type="button" class="btn btn-primary" onclick="return cerrar_modal()">
                                        Aceptar
                                </button>
                        </div>
                </div>
        </div>
</div>

Si todo ha salido bien podemos tener una pantalla como la siguiente:

/images/blog/proveedores.png

Y los modales:

Creacion de Nuevo Proveedor:

/images/blog/creacion_proveedor.png

Detalle de Proveedor:

/images/blog/detalle_proveedor.png

Modificacion de Proveedor:

/images/blog/modificacion_proveedor.png

Para ver los archivos de configuración del proyecto y todo lo demás que no ha sido explicado en este post, pueden acceder al repositorio:

Proyecto Modales

Saludos.

Modales en Django con Vistas Basadas en Clases y JQuery-UI

En este post vamos a usar ventanas modales para poder hacer operaciones de visualización, creación y modificación de entidades. Para este fin hemos creado un proyecto llamado "modales" y una aplicación "productos" donde vamos a tener el modelo Producto, para poder mostrar los modales haremos uso de la librería JQuery-UI y de bootstrap para ayudarnos con el diseño, jquery como librería javascript y Datatables para mostrar las tablas de la manera mas cómoda. Los pormenores de creación del proyecto y la correspondiente aplicación serán obviados por haber sido tratados en artículos anteriores.

Primero creamos nuestro archivo base.html que va a contener la carga de las librerias necesarias:

base.html

<!DOCTYPE html>
{% load staticfiles %}
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Modales</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}">
    <link rel="stylesheet" type="text/css" href="{% static 'css/jquery-ui.css' %}">
    <link rel="stylesheet" type="text/css" href="{% static 'css/jquery.dataTables.min.css'  %}"/>
    <script type="text/javascript" src="{% static 'js/jquery.js' %}" charset="UTF-8"></script>
    <script type="text/javascript" src="{% static 'js/bootstrap.min.js' %}" charset="UTF-8"></script>
    <script type="text/javascript" src="{% static 'js/jquery-ui.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/jquery.dataTables.min.js' %}"></script>
</head>
<body>
<div id="page-wrapper">
    <div class="container-fluid">
    {% block cuerpo %}

    {% endblock cuerpo %}
    </div>
</div>
</body>
</html>

Vamos a proceder a crear nuestras vistas en el archivos views.py:

views.py

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.views.generic.edit import UpdateView, CreateView
from django.views.generic.list import ListView
from productos.forms import ProductoForm
from productos.models import Producto
from django.core.urlresolvers import reverse_lazy
from django.views.generic.detail import DetailView

class ListadoProductos(ListView):
    model = Producto
    template_name = 'productos.html'
    context_object_name = 'productos'

class CrearProducto(CreateView):
    template_name = 'producto.html'
    form_class = ProductoForm
    success_url = reverse_lazy('productos:listado_productos')

class ModificarProducto(UpdateView):
    model = Producto
    template_name = 'producto.html'
    form_class = ProductoForm
    success_url = reverse_lazy('productos:listado_productos')


class DetalleProducto(DetailView):
    model = Producto
    template_name = 'detalle_producto.html'

La vista ListadoProductos es la vista principal de nuestra aplicación desde aquí vamos a interactuar con las demás vistas, como se ve todas están basadas en clases.

Debemos crear un formulario llamado ProductoForm para utilizarlo tanto en ModificarProducto como en CrearProducto:

forms.py

from django import forms

from productos.models import Producto


class ProductoForm(forms.ModelForm):

    class Meta:
        model = Producto
        fields = ['descripcion', 'marca', 'precio', 'estado']

    def __init__(self, *args, **kwargs):
        super(ProductoForm, self).__init__(*args, **kwargs)
        for field in iter(self.fields):
            if field <> 'estado':
                self.fields[field].widget.attrs.update({
                    'class': 'form-control'
                })

Ĺo único nuevo en la parte de los formularios es que estamos implementando el método __init__ para poder agregar la clase 'form-control' de bootstrap.

Ahora vamos a crear nuestro archivo urls.py:

from django.conf.urls import url
from productos.views import ListadoProductos, CrearProducto, ModificarProducto, DetalleProducto

urlpatterns = [
    url(r'^$', ListadoProductos.as_view(), name="listado_productos"),
    url(r'^crear_producto/$', CrearProducto.as_view(), name="crear_producto"),
    url(r'^modificar_producto/(?P<pk>.+)/$',ModificarProducto.as_view(), name="modificar_producto"),
    url(r'^detalle_producto/(?P<pk>.+)/$',DetalleProducto.as_view(), name="detalle_producto"),
]

Ahora si haremos nuestra plantilla principal, llamada productos.html, primero haremos la parte del html y luego la de javascript:

productos.html

{% extends "base.html" %}
{% block cuerpo %}
<h3>Productos</h3>
<div class="row">
        <div class="col-lg-10">
                <a onclick="return abrir_modal('{% url 'productos:crear_producto' %}','Productos / Nuevo')" class="btn btn-primary">
                        Crear
                </a>
        </div>
</div>

Aquí primero heredamos de base.html y luego tenemos un botón para crear un nuevo producto, para desplegar el modal se llama a la url correspondiente como primer argumento del método de javascript "abrir_modal" y como segundo el título del modal.:

onclick="return abrir_modal('{% url 'productos:crear_producto' %}','Productos / Nuevo')"

Sigamos viendo nuestro archivo productos.html:

<hr/>
<div class="row">
        <div class="col-lg-12">
                <table id="tabla" class="display" cellspacing="0" width="100%">
                        <thead>
                                <tr>
                                        <th class="text-center">DESCRIPCION</th>
                                        <th class="text-center">MARCA</th>
                                        <th class="text-center">PRECIO</th>
                                        <th class="text-center">ESTADO</th>
                                        <th class="text-center">ACCIONES</th>
                                </tr>
                        </thead>
                        <tbody>
                        {% for producto in productos %}
        <tr>
            <td>{{ producto.descripcion }}</td>
            <td>{{ producto.marca }}</td>
            <td>{{ producto.precio }}</td>
            {% if producto.estado %}
            <td>ACTIVO</td>
            {% else %}
            <td>INACTIVO</td>
            {% endif %}
            <td class="text-center">
                <a onclick="return abrir_modal('{% url 'productos:detalle_producto' producto.pk %}','Productos / {{ producto.descripcion }}')" class="btn">
                    <span class="glyphicon glyphicon-eye-open"></span>
                </a>
                <a onclick="return abrir_modal('{% url 'productos:modificar_producto' producto.pk %}','Productos / {{ producto.descripcion }}')" class="btn">
                    <span class="glyphicon glyphicon-edit"></span>
                </a>
            </td>
                        </tr>
                        {% endfor %}
                        </tbody>
                </table>
        </div>
</div>
<div id="popup"></div>

Tenemos la tabla con el listado de los productos ya creados junto con dos enlaces en cada fila de producto para poder desplegar el detalle y la modificación del mismo en los modales correspondientes, tal como se explico arriba, en la parte final vemos que para mostrar el modal usaremos el div con el id "popup".

Ahora vamos a ver la parte de javascript necesaria para poder mostrar el modal correspondiente:

var modal;

function abrir_modal(url, titulo)
{
    modal = $('#popup').dialog(
    {
        title: titulo,
        modal: true,
        width: 500,
        resizable: false
    }).dialog('open').load(url)
}

function cerrar_modal()
{
    modal.dialog("close");
}

$(document).ready(function()
{
    var table = $('#tabla').dataTable( {
        "language": {
                url: "/static/localizacion/es_ES.json"
        }
    } );
});

La función abrir_modal usa el método dialog() de JQuery-UI para mostrar un modal cuyo contenido en html va a ser cargado con el método load de acuerdo a la url que estemos tratando de visualizar. Este elemento se guarda en la variable modal.

La función cerrar_modal() utiliza la variable modal, para cerrar el dialogo pasando el parámetro "close".

Finalmente tenemos los templates correspondientes a detalle_producto.html y producto.html:

detalle_producto.html

<div class="panel panel-default">
        <div class="panel-body">
                <div class="row">
                        <div class="col-lg-7">
                                <h3>{{ object.descripcion }}</h3>
                        </div>
                </div>
                <div class="row">
                        <div class="col-lg-4">
                                <label>MARCA:</label>
                                <p>{{ object.marca }}</p>
                                <label>PRECIO:</label>
                                <p>{{ object.precio }}</p>
                                <label>ESTADO:</label>
                                {% if object.estado %}
                                        <p>ACTIVO</p>
                                {% else %}
                                        <p>INACTIVO</p>
                                {% endif %}
                        </div>
                </div>
        </div>
</div>

producto.html

{% if object %}
<form role="form" action="{% url 'productos:modificar_producto' object.pk %}" method="post">
{% else %}
<form role="form" action="{% url 'productos:crear_producto' %}" method="post">
{% endif %}
        {% csrf_token %}
        <div class="panel panel-default">
                <div class="panel-body">
                        {{ form.as_p }}
                </div>
        </div>
        <div class="row">
                <div class="col-lg-12 text-right">
                        <input type="submit" class="btn btn-primary" name="submit" value="Guardar">
                        <button type="button" class="btn btn-default" onclick="return cerrar_modal()">
                                Cancelar
                        </button>
                </div>
        </div>
</form>

Este template tiene de especial que va a ser usado tanto para crear como para modificar el producto, por lo que se procede a preguntar si existe un objeto object, en caso de que sea así la acción a realizar será modificar el producto y sino crear un nuevo producto.

Si todo ha salido bien podemos tener una pantalla como la siguiente:

/images/blog/modales_principal.png

Y los modales:

Creacion de Nuevo Producto:

/images/blog/creacion_producto.png

Detalle de Producto:

/images/blog/detalle_producto.png

Modificacion de Producto:

/images/blog/modificacion_producto.png

Para ver los archivos de configuración del proyecto y todo lo demás que no ha sido explicado en este post, pueden acceder al repositorio:

Proyecto Modales

Saludos.

Configurando Django con Postgresql

En este videotutorial les mostramos la forma de configurar un servidor postgresql y conectarlo con Django, además de utilizar la herramienta gráfica de administración de base de datos PgAdminIII, esperemos les sea útil:

Configuración de Entorno de Desarrollo Django

Después de algunos meses de ausencia y de haber superado los fenómenos climáticos que vivimos en nuestra región, volvemos con nuevos contenidos, esta vez vamos a probar con la realización de videotutoriales, empezamos desde lo básico configurando un entorno de desarrollo para Django usando el IDE Pycharm en su versión community y creando un entorno virtual, esperamos que lo disfruten:

Configurando la Ruta de los Addons en Odoo

La ruta de los addons es un parámetro de configuración, que lista los directorios donde se buscarán los módulos agregados a Odoo cuando se inicializa una nueva base de datos. Los directorios listados en la ruta addons se espera que contengan subdirectorios, cada uno de los cuales es un módulo agregado.

Primero ingresamos a la ruta donde tenemos nuestro entorno de Odoo 10 y creamos una carpeta llamada local-addons:

cd ~/odoo-dev-10/
mkdir local-addons

La carpeta local-addons contendrá los módulos que vamos a desarrollar.

Ahora editamos la configuración de la instancia:

nano myodoo.cfg

Localizamos la linea que empieza con addons_path =. Por defecto esta mostrará lo siguiente:

addons_path = /home/usuario/odoo-dev-10/odoo/odoo/addons,/home/usuario/odoo-dev-10/odoo/addons

Modificamos la linea agregando una coma al final de la linea y añadiendo la ruta donde van a estar nuestros módulos adicionales, en este caso la carpeta local-addons en la ruta /home/usuario/odoo-dev-10:

addons_path = /home/usuario/odoo-dev-10/odoo/odoo/addons,/home/usuario/odoo-dev-10/odoo/addons,/home/usuario/odoo-dev-10/local-addons

Ahora reiniciamos nuestra instancia de odoo:

python odoo/odoo-bin -c myodoo.cfg

Si nos detenemos a observar la terminal y los avisos que nos lanza Odoo, veremos que en la linea de addons_paths ya nos aparece la carpeta local-addons:

2017-01-21 17:03:58,845 8081 INFO ? odoo: addons paths: ['/home/usuario/.local/share/Odoo/addons/10.0', u'/home/usuario/odoo-dev-10/odoo/odoo/addons', u'/home/usuario/odoo-dev-10/odoo/addons', u'/home/usuario/odoo-dev-10/local-addons']

Instalación de módulos desde GitHub

GitHub es una gran fuente de módulos de terceros. Muchos desarrolladores de Odoo utilizan GitHub para compartir sus módulos, y la Asociación Comunitaria Odoo(OCA) mantiene colectivamente varios cientos de addons en GitHub.

Vamos a descargar un módulo de Asociación Comunitaria Odoo(OCA), para buscar los que necesitamos podemos ir a:

https://github.com/OCA/

En esta caso instalaremos algunos módulos que nos permiten tener funcionalidades para administrar centros de salud, para ello debemos situarnos en la carpeta de nuestro entorno de desarrollo y clonar el módulo allí:

cd ~/odoo-dev-10
git clone https://github.com/OCA/vertical-medical

De acuerdo a la documentación sabemos que estos módulos dependen de otro llamado partner contacto, por lo que también debemos descargarlo para que esté disponible:

git clone https://github.com/OCA/partner-contact.git

Ahora en nuestra carpeta tendremos dos carpetas adicionales tal como se muestra en la imagen:

/images/blog/addons4.png

Todos los repositorios de códigos de la Asociación Comunitaria Odoo(OCA) tienen sus complementos contenidos en subdirectorios separados, lo cual es coherente con lo que espera de Odoo con respecto a los directorios en la ruta addons, por lo que debemos agregar ambas a la ruta de addons:

nano myodoo.cfg

Modificamos la linea addons_path:

addons_path = /home/usuario/odoo-dev-10/odoo/odoo/addons,/home/usuario/odoo-dev-10/odoo/addons,/home/usuario/odoo-dev-10/local-addons, /home/usuario/odoo-dev-10/partner-contact, /home/usuario/odoo-dev-10/vertical-medical

Ahora lanzamos nuestra instancia:

python odoo/odoo-bin -c myodoo.cfg

Ahora nos vamos al navegador para instalar nuestros addons descargados:

https://localhost:8069

Recordemos que el usuario y la contraseña por defecto son admin y admin.

Una vez que hayamos ingresado, buscamos el menú Settings(acá lo tenemos como Configuración, debido a que ya cargamos el soporte para idioma Español) y activamos el modo desarrollador:

/images/blog/addons1.png

Ahora nos vamos al menú Apps y le damos click a la opción "Actualizar lista de aplicaciones":

/images/blog/addons2.png

Nos desplegará una ventana como la siguiente, donde presionamos el botón actualizar:

/images/blog/addons3.png

Finalmente vamos a las aplicaciones y buscamos el módulo Odoo Medical:

/images/blog/addons5.png

Le damos click al botón instalar y automáticamente instalará también el addon partner-contact:

Ya con esto podemos administrar un centro médico con Odoo.

/images/blog/addons6.png

Saludos.