Django Guia Rapida VI - CRUD para usuarios normales


Continuacion de las guias de Django, guia previa: Django Guia Rapida V - Sesiones y login

En la segunda guia de Django, de esta serie, explique como hacer un CRUD para el modelo de nuestras apps en Django, mmmhhh.. mas que hacer lo que se hizo fue configurar el proyecto para que de forma automatica en el area de administracion se nos proveyera de un CRUD para nuestras apps.

En esta guia, vamos a hacer un CRUD pero para usuarios normales, con normales me refiero a usuarios registrados pero sin privilegios de administrador del sistema, como sabemos estos usuarios no pueden entrar al area de administracion (donde ya tenemos un CRUD) sino que al iniciar sesion solo entran como usuarios registrados normales y la idea es que ellos puedan capturar o manejar registros de nuestras apps, pero solo de las apps necesarias.

Recordemos que estamos haciendo un web de practica de una muebleria, entonces queremos que usuarios registrados (a quienes para este ejemplo se consideran empleados de la muebleria) tengan la posibilidad de, por ejemplo, registrar productos, pero al no ser administradores no pueden, por ejemplo, crear o modificar registros de usuarios.

A diferencia de lo facil que fue configurar un CRUD en el area de administracion aqui sera un poco mas complicado, pero no demasiado, esta entrada esta un poco larga y el proceso tiene muchos detalles que hay que comentar. Coooooomenzamos....



Paso 1-
Como recordaran tenemos las apps clientes, home, productos y ventas, hagamos el CRUD de productos para nuestros usuarios. Abrimos el archivo views.py de nuestra app productos:

/muebleria/productos/views.py

Le ponemos el siguiente contenido:

from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.forms import ModelForm

from productos.models import Producto
from productos.models import Categoria

class ProductoForm(ModelForm):
class Meta:
model = Producto

@login_required(login_url='/login/')
def productoList(request, template_name='productos/list_producto.html'):
listproductos = Producto.objects.all()
data = {}
data['productoslist'] = listproductos
return render(request, template_name, data)
@login_required(login_url='/login/')
def productoCreate(request, template_name='productos/form_producto.html'):
form = ProductoForm(request.POST or None)
if form.is_valid():
form.save()
return redirect('Listado_productos')
return render(request, template_name, {'form':form})
@login_required(login_url='/login/')
def productoUpdate(request, pk, template_name='productos/form_producto.html'):
objproducto = get_object_or_404(Producto, pk=pk)
form = ProductoForm(request.POST or None, instance=objproducto)
if form.is_valid():
form.save()
return redirect('Listado_productos')
return render(request, template_name, {'form':form})
@login_required(login_url='/login/')
def productoDelete(request, pk, template_name='productos/confirm_delete_producto.html'):
objproducto = get_object_or_404(Producto, pk=pk)
if request.method=='POST':
objproducto.delete()
return redirect('Listado_productos')
return render(request, template_name, {'producto':objproducto})


class CategoriaForm(ModelForm):
class Meta:
model = Categoria

@login_required(login_url='/login/')
def categoriaList(request, template_name='productos/list_categoria.html'):
listcategorias = Categoria.objects.all()
data = {}
data['categoriaslist'] = listcategorias
return render(request, template_name, data)
@login_required(login_url='/login/')
def categoriaCreate(request, template_name='productos/form_categoria.html'):
form = CategoriaForm(request.POST or None)
if form.is_valid():
form.save()
return redirect('Listado_categorias')
return render(request, template_name, {'form':form})
@login_required(login_url='/login/')
def categoriaUpdate(request, pk, template_name='productos/form_categoria.html'):
objcategoria = get_object_or_404(Categoria, pk=pk)
form = CategoriaForm(request.POST or None, instance=objcategoria)
if form.is_valid():
form.save()
return redirect('Listado_categorias')
return render(request, template_name, {'form':form})
@login_required(login_url='/login/')
def categoriaDelete(request, pk, template_name='productos/confirm_delete_categoria.html'):
objcategoria = get_object_or_404(Categoria, pk=pk)
if request.method=='POST':
objcategoria.delete()
return redirect('Listado_categorias')
return render(request, template_name, {'categoria':objcategoria})

Puntos a comprender de lo anterior:

  • Estamos definiendo las vistas del CRUD de nuestra app
  • Lineas 5 y 6 importamos las entidades del modelo de nuestra app (en esta caso 2 pueden ser N)
  • Lineas 9 a 10 estamos declarando una clase que creara en automatico nuestro formulario para la entidad producto, la clase se llama ProductoForm y hereda de ModelForm, en la linea 10 solo le indicamos la entidad a usar.
  • Antes de declarar cada vista le ponemos una linea que pone: @login_required(login_url='/login/') esto indica que solo usuarios con sesion iniciada (empleados) pueden acceder a esta vista, si lo intenta un visitante sera reenviado a /login/
  • Lineas 13 a 17 definimos la vista de listado de productos:
    • Le llamamos productList (linea 13)
    • Al declararla le pasamos dos parametros (request, template_name) el segundo es el nombre de la plantilla que usa esta vista (linea 13)
    • Obtenemos una lista de registros de nuestro modelo, lo equivalente a hacer una consulta select en lenguaje sql para obtener los registros (linea 14)
    • Declaramos un array (linea 15)
    • Dentro de nuestro array cargamos la lista de registros y la identificamos con el indice 'productoslist' (linea 16)
    • En el return le pasamos los tres parametros que se usaran por nuestra vista (request, template_name, data) (linea 17)
  • Lineas 19 a 24 definimos la vista de crear productos:
    • Le llamamos productoCreate (linea 19)
    • Al declararla le pasamos dos parametros (request, template_name) el segundo es el nombre de la plantilla que usa esta vista (linea 19)
    • Creamos nuestro objeto formulario, el cual es de la clase ProductoForm (linea 20)
    • Indicamos que si los datos del form son validos se guarde el registro y se reenvie a la vista Listado_productos, cuando el usuario de click en guardar (lineas 21 a 23)
    • En el return le pasamos los tres parametros que se usaran por nuestra vista (request, template_name, {'form':form}) (linea 24)
  • Lineas 26 a 32 definimos la vista de modificar productos:
    • Le llamamos productoUpdate (linea 26)
    • Al declararla le pasamos tres parametros (request, pk, template_name) el segundo es el id del registro a modificar, el tercero es el nombre de la plantilla que usa esta vista (linea 26)
    • Cargamos en un objeto el registro a editar (linea 27)
    • Creamos nuestro objeto formulario, el cual es de la clase ProductoForm (linea 28)
    • Indicamos que si los datos del form son validos se guarde el registro y se reenvie a la vista Listado_productos (lineas 29 a 31)
    • En el return le pasamos los tres parametros que se usaran por nuestra vista (request, template_name, {'form':form}) (linea 32)
  • Lineas 34 a 39 definimos la vista de borrar productos:
    • Le llamamos productoDelete (linea 34)
    • Al declararla le pasamos tres parametros (request, pk, template_name) el segundo es el id del registro a borrar, el tercero es el nombre de la plantilla que usa esta vista (linea 34)
    • Cargamos en un objeto el registro a borrar (linea 35)
    • Indicamos que al enviar el formulario (donde se pregunta si desea eliminar el registro) el registro se borre y se reenvie a la vista Listado_productos (lineas 38 a 38)
    • En el return le pasamos los tres parametros que se usaran por nuestra vista (request,template_name,{'producto':objproducto}) (linea 39)

Fiuuuf... es importante comprender bien todo lo anterior ya que si algo se omite, se escribe mal o se malinterpreta (por ejemplo confundir un objeto de vista con uno de entidad) nos dara error al ejecutar y puede ser dificil o tedioso encontrarlo ese error. En el resto del archivo, de la linea 35 en delante son los mismos puntos pero para le entidad Categoria de nuestro modelo.

Paso 2-
Creamos un archivo llamado urls.py en la carpeta de nuestra app productos:

/muebleria/productos/urls.py

Le ponemos el siguiente contenido:

from django.conf.urls import patterns, url
from productos import views

urlpatterns = patterns('',
url(r'^$', views.productoList, name='Listado_productos'),
url(r'^new$', views.productoCreate, name='Crear_producto'),
url(r'^edit/(?P\d+)$', views.productoUpdate, name='Editar_producto'),  
url(r'^delete/(?P\d+)$', views.productoDelete, name='Borrar_producto'), 
url(r'^categoria/$', views.categoriaList, name='Listado_categorias'),  
url(r'^categoria/new$', views.categoriaCreate, name='Crear_categoria'),  
url(r'^categoria/edit/(?P\d+)$', views.categoriaUpdate, name='Editar_categoria'),  
url(r'^categoria/delete/(?P\d+)$', views.categoriaDelete, name='Borrar_categoria'),
)

Puntos a comprender de lo anterior:

  • Estamos definiendo las urls a cada una de nuestras vistas definidas en views.py
  • Linea 2 importamos las vistas de nuestra app
  • Dentro del arreglo urlpatterns agregamos una linea por cada vista, cada linea es la definicion de la url para esa vista
  • Cada definicion de url lleva tres parametros
    • 1er parametro expresion regular de una url. Muy importante decir que no son urls absolutas sino relativas, la url definida en nuestra expresion se suma al fina de la url de nuestro sitio mas el nombre de nuestra app, asi: misitio.com/productos/XYZ donde XYZ es la url de nuestra expresion regular.
    • 2do parametro llamada a la vista destino de esa url, asi views.NMV donde NMV es el nombre de la vista, el nombre que pusimos en la definicion (linea que comienza con def) de cada vista en el archivo views.py
    • 3er parametro definimos un nombre de nuestra vista: name="un_nombre_para_la_vista", hay que decir que este nombre se puede usar en los enlaces que creemos en nuestras plantillas,
      de hecho ya lo estamos usando en views como valor de redireccion cuando las vistas editar-crear-borrar reenvian hacia la vista listado

Paso 3-
Ahora hemos de registrar esas urls en las urls del proyecto, abrimos el archivo de urls del proyecto:

/muebleria/muebleria/urls.py

Y al arreglo de urlpatterns le agregamos lo siguiente:

url(r'^productos/', include('productos.urls')),

Ahora tenemos que crear los templates, a diferencia del area del administrador aqui hay que crear los templates. Pero no es tan complicado haciendolo de forma basica, como lo haremos, a futuro lo interesante sera hacerlos de modo adecuado a distintas logicas de negocio que necesitemos ;)

Paso 4-
En nuestra carpeta de templates vamos a crear una carpeta con el nombre de nuestra app:

/muebleria/muebleria/templates/productos

Paso 5-
En esa carpeta creamos un archivo html que contendra un formulario para usar en las vistas de crear y editar, le llamaremos form_producto:

/muebleria/muebleria/templates/productos/form_producto.html

Le ponemos el siguiente contenido:

{% extends 'index.html' %}

{% block content %}
<form method="post">{% csrf_token %}
{{ form.as_p }}
<a href="{% url "Listado_productos"%}">Cancelar</a> <input type="submit" value="Guardar" />
</form>
{% endblock %}

Recuerden que nuestras vistas heredan de index.html (lo hablamos en la guia III). Con lo anterior Django se encargara de generar el formulario de acuerdo a los campos de nuestra entidad (que se definio en views.py para esta vista) Noten que he incluido un enlace con el texto cancelar y cuya destino es la vista de listado de productos, podria poner la url directamente pero estoy usando lenguaje de plantillas de Django y le pongo el nombre de la vista tal como esta definido en urls.py

Paso 6-
Creamos un archivo html que contendra la vista de listado, le llamaremos list_producto.html:

/muebleria/muebleria/templates/productos/list_producto.html

Le ponemos el siguiente contenido:

{% extends 'index.html' %}

{% block content %}
<h1>Lista de productos</h1>
<table>
<thead>
<tr> <th>Nombre</th> <th>Precio</th> <th><a href="{% url "Crear_producto" %}">Crear nuevo</a></th> </tr>
</thead>
<tbody>
{% for producto in productoslist %}
<tr>
<td> {{ producto.nombre }} </td>
<td> {{ producto.precio }} </td>
<td>
<a href="{% url "Editar_producto" producto.id %}">Editar</a> |
<a href="{% url "Borrar_producto" producto.id %}">Borrar</a>
</td>
</tr>
{% endfor %}
<tbody>
</table>
{% endblock %}


En esta vista sí tuvimos que generar todo el html, he puesto el listado en una tabla pero obviamente eso se puede cambiar, pueden jugar con el maquetado html y usar listas, divs o lo que se les ocurra. Regresando a lo que nos interesa, Django, quiero hacer notar aqui lo siguiente:

  • La linea {% for producto in productoslist %} como es facil de comprender es un ciclo,
    en lenguaje de plantillas de django
  • El objeto productoslist es el mismo que declaramos en views.py (linea 14)
  • Cada objeto producto (dentro del for) es un registro de nuestra listado, asi que accedemos a sus atributos con el operador punto
  • He agregado enlaces a las vistas crear, editar o borrar, usando la notacion de url del lenguaje de plantillas Django y de acuerdo a los nombres de vistas definidos en urls.py de nuestra app

Paso 7-
Creamos un archivo html que contendra la vista de borrar, es decir un formulario que pregunta si desea en verdad borrar el registro, le llamaremos confirm_delete_producto.html:

/muebleria/muebleria/templates/productos/confirm_delete_producto.html

Le ponemos el siguiente contenido:

{% extends 'index.html' %}

{% block content %}
<form method="post">{% csrf_token %}
¿En verdad deseas eliminar "{{ producto.nombre }}" ?
<a href="{% url "Listado_productos"%}">Cancelar</a> <input type="submit" value="Borrar" />
</form>
{% endblock %}

En esta vista no hay nada nuevo y es incluso mas sencilla que las anteriores.

Con lo anterior ya tenemos nuestro CRUD para la app productos, pero solo para la entidad productos nos falta para la entidad Categoria. Para el CRUD de la entidad categoria ya hicimos las configuraciones en views.py y urls.py, lo que falta es crear los html de las vistas, y ponerlos en muebleria/muebleria/templates/productos para no extendernos sobre eso (son los mismos pasos a partir del paso 5) los contenidos de esas vistas seran asi:

/muebleria/muebleria/templates/productos/list_categoria.html
{% extends 'index.html' %}

{% block content %}
<h1>Lista de categorias de productos</h1>
<table>
<thead>
<tr> <th>Nombre</th> <th><a href="{% url "Crear_categoria" %}">Crear nueva</a></th> </tr>
</thead>
<tbody>
{% for categoria in categoriaslist %}
<tr>
<td> {{ categoria.categoria }} </td>
<td>
<a href="{% url "Editar_categoria" categoria.id %}">Editar</a> |
<a href="{% url "Borrar_categoria" categoria.id %}">Borrar</a>
</td>
</tr>
{% endfor %}
<tbody>
</table>
{% endblock %}

/muebleria/muebleria/templates/productos/form_categoria.html
{% extends 'index.html' %}

{% block content %}
<form method="post">{% csrf_token %}
{{ form.as_p }}
<a href="{% url "Listado_categorias"%}">Cancelar</a> <input type="submit" value="Guardar" />
</form>
{% endblock %}

/muebleria/muebleria/templates/productos/confirm_delete_categoria.html
{% extends 'index.html' %}

{% block content %}
<form method="post">{% csrf_token %}
¿En verdad deseas eliminar <em>{{ categoria.categoria }}</em>?
<a href="{% url "Listado_categorias"%}">Cancelar</a> <input type="submit" value="Borrar" />
</form>
{% endblock %}

Ahora sí, tenemos nuestro CRUD completo para la app productos (y).

Por ultimo, ya para terminar esta guia, vamos a modificar dos cositas, nuestra vista base (index.html) para
agregar un par de enlaces en la barra de menu, para que el empleado con sesion activa pueda ir hacia el CRUD que acamos de crear. Abrimos nuestra vista base

/muebleria/muebleria/templates/index.html

Cuando creamos esta vista yo deje este elemento: <nav class="menu"> Catalogo </nav> con la idea de usarlo como menu, ahora es momento de usarlo, le agregamos dos enlaces a categoria de productos y a productos pero que solo sean visibles para un usuario con sesion activa, el nav nos queda ahora asi:

<nav class="menu">
<a href="{% url "la_vista_principal" %}">Inicio</a> |
{% if user.is_authenticated %}
<a href="{% url "Listado_productos"%}">Productos</a> |
<a href="{% url "Listado_categorias"%}">Categorias de Productos</a>
{% else %}
Catalogo
{% endif %}
</nav>

Ahora sí, tenemos nuestro CRUD completo para la app productos. Para que no se vea tan sencillo podemos agregar estilos a las nuevas vistas, por ejemplo yo agrege algunas reglas para las vistas de listados, en nuestra hoja de estilos css (creada en la guia IV):

/muebleria/muebleria/static/css/basico.css

He agregado estas reglas:

table {
margin:0.5em auto;
}
table th{
border:solid 2px rgb(59,59,59);
padding:1px 1em;
text-align:center;
}
table td{
border:solid 1px rgb(209,209,209);
padding:1px 1em;
}
nav a:visited{
color:white;
}

Si ahora ejecutamos el servidor y vemos la pagina, como visitante se vera igual, pero al iniciar sesion ya podremos ver nuestro CRUD completo y funcional






Y ahora sí ¡por fin! damos por terminada esta guia. Espero os sea util, en la siguiente parte seguiremos completando nuestra web de practica 'La muebleria' ¡saludos!

Comentarios

  1. buenísimo, amigo podria explicar como hacer lo mismo pero que te remita a un template personalizado con css y javascript que tiene datepicker y timepicke?

    ResponderBorrar
    Respuestas
    1. Para lo de incluir css y javascript lo explique en la parte IV, en la siguiente ruta:
      http://mydevelopnotepad.blogspot.mx/2014/01/django-guia-rapida-iv-agregar-css-js-e.html

      Para lo de templates personalizados, pues tendrias que editar los archivos de templatescomo el form_producto.html, y cargar ahi tu template con el fuente html, pero modificanco la carga de datos de acuerdo a los comandos tales como {% for producto in productoslist %} para acomodar los datos a mostrar, y tambien tendrias qeu cuidar que tu template quede acorde a la vista de que hereda (si hereda de alguna como en el ejemplo de esta entrada)

      Borrar
  2. amigo me podrías ayudar con el menu me da problemas

    ResponderBorrar

Publicar un comentario

Entradas populares