Taller Django: de 0 a CRUD Miguel González (@migonzalvar) GDG Vigo, 5 de abril de 2013
Crear un proyecto de Django Urls y vistas Modelos Plantillas Baterías incluidas: administración Formularios Edición de objetos Autenticación
Este obra está bajo una licencia de Creative Commons Reconocimiento 3.0 España.
Información de interés Contraseña WiFi Repositorio: https: //code.google.com/p/gdg-vigo-django-crud/ Documentación: https://docs.djangoproject.com/en/1.5/ Back channel: #gdgvigo
Prerrequisitos Versión de Pyhton correcta $ python --version Python 2.7.3 Virtualenv instalado $ virtualenv --version 1.9.1 Clonado el repositorio (opcional) $ git clone \ > https://code.google.com/p/gdg-vigo-django-crud/ $ cd gdg-vigo-django-crud
Crear un proyecto de Django
Conceptos 3 niveles de carpeta: Proyecto = repositorio Proyecto Django Aplicaciones Entorno virtual: aislar dependencias
E1: Preparación del entorno 1. Crea una carpeta para trabajar con el proyecto, por ejemplo gdg-vigo-django-crud. (Si has clonado el repositorio, se trata de la carpeta raíz) Pista: mkdir. 2. Crea un entorno virtual dentro de la carpeta principal del proyecto. Un buen nombre para la carpeta puede ser.venv. Pista: virtualenv.. 3 Instala Django dentro del entorno virtual recién creado. Pistas: source, pip.
Solución E1 1. Crear carpeta para el proyecto $ mkdir gdg-vigo-django-crud $ cd gdg-vigo-django-crud 2. Crear entorno virtual de Python $ virtualenv.venv --distribute 3. Instalar Django dentro del entorno virtual $ source.venv/bin/activate $ pip install Django
E2: Inicialización de un proyecto Django 1. Inicializar un proyecto Django. Un buen nombre puede ser taller. Pista: django-admin.py. 2. Lanzar servidor HTTP de desarrollo sobre proyecto recién creado. Pista: runserver.. 3 Crear y activar una aplicación Django. Un buen nombre puede ser contacts.
Solución E2 1. Inicializar un proyecto Django $ django-admin.py startproject taller Este comando crea esta estructura: taller manage.py taller init.py settings.py urls.py wsgi.py
. 2 Lanza el servidor $ cd taller $ python manage.py runserver Levanta servidor en http://127.0.0.1:8000/
3. Crear app y activarla $ python manage.py startapp contacts contacts init.py models.py tests.py views.py Para activar hay que editar settings.py # taller/settings.py INSTALLED_APPS = (... 'contacts', )
Dónde estamos?
Dónde vamos?
Urls y vistas
Historia de una petición convierte la petición en un objeto HttpRequest enruta la petición a la función que le corresponde la función devuelve un objeto HttpResponse
E3: Hola mundo 1. Crea una vista que devuelva la cadena "Hola mundo". Pista: views.py, HttpResponse. 2. Mapea la URL http://localhost:8000/hola/ con la vista recién creada. Pista: urls.py. 3 EXTRA Crea la URL /extra que devuelve el User Agent de la petición en formato JSON. Pista: HttpRequest.META
Solución E3 1. Vista hola mundo # contacts/views.py from django.http import HttpResponse def my_view(request): return HttpResponse("Hola mundo") 2. Mapea la URL # taller/urls.py urlpatterns = patterns('', url(r'^hola/$', 'contacts.views.my_view'),... $ python manage.py runserver
3. EXTRA: manipula petición y respuesta from django.http import HttpResponse import json def extra(request): user_agent = request.meta['http_user_agent'] response = HttpResponse(json.dumps(user_agent), content_type='application/json') return response
Modelos
ORM Django incluye un ORM para interactuar con base de datos Se crean los modelos programáticamente Comandos para inicializar y sincronizar base de datos Métodos para validar y grabar objetos API para consultas
E4: ORM 1. Configura base de datos SQLite3 e inicializarla. Pista: settings.py, syncdb 2. Crear un modelo de datos para el objeto Person que tenga los siguientes campos: name, texto, 80 caracteres máximo, obligatorio. email, dirección de correo electrónico, opcional. birthday, fecha, opcional. No te olvides de sincronizar al terminar para que se cree la tabla en la base de datos.
Solución E4 1. Configurar en settings.py # settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'database.sqlite',... Y sincronizar $ python manage.py syncdb Se puede ver el esquema creado con el comando sqlite3 database.sqlite
2. Crear modelo programáticamente # contacts/models.py from django.db import models class Person(models.Model): name = models.charfield(max_length=80) email = models.emailfield(blank=true) birthday = models.datefield(null=true, blank=tr def unicode (self): return self.name Más https://docs.djangoproject.com/en/1.5/ topics/db/models/
2. (cont) No te olvides de sincronizar! $ python manage.py syncdb $ sqlite3 database.sqlite sqlite>.schema contacts_person CREATE TABLE "contacts_person" ( "id" integer NOT NULL PRIMARY KEY, "name" varchar(80) NOT NULL, "email" varchar(75) NOT NULL, "birthday" date );
E5: La API de models 1. A través de un shell interactivo crear 3 objetos Person y grabarlos en base de datos. Nacido en 1970 sin email Nacido en 1990 con email Sin fecha de nacimiento con email 2. EXTRA Genera un archivo en JSON con datos inciales para insertar en la tabla de Person cada vez que se sincronice. Pista: initial_data.json. 3 EXTRA EXTRA Programa un generador de datos de prueba ficticios que sirva para poblar una base de datos en un entorno de test.
Solución E5 1. Crear objetos: $ python manage.py shell >>> from contacts.models import Person >>> me = Person() >>> me.full_clean() Traceback (most recent call last):... raise ValidationError(errors) ValidationError: {'name': [u'this field cannot be b >>> me.name = u'miguel González' >>> me.full_clean() >>> me.save()
2. Crear un archivo contacts/fixtures/initial_data.json [ { } ] "pk": 1, "model": "contacts.person", "fields": { "birthday": "1976-05-12", "name": "Miguel Gonz\u00e1lez", "email": "migonzalvar@gmail.com" }
3. Ummm
E6: Querys 1. Averigua Personas mayores que tú Personas menores que tú Personas sin fecha de nacimiento registrada Pista: filter
Solución E6 1. Desde la shell >>> from contacts.models import Person >>> people = Person.objects.all() >>> for p in people: print p >>> olders = Person.objects.filter(birthday lt='197 >>> youngers = Person.objects.filter(birthday gt='1 >>> unknown = Person.objects.filter(birthday isnull Más https://docs.djangoproject.com/en/1.5/topics/ db/queries/
Plantillas
Django templates Variables: sustitución por el valor entre {{ }} Tags: comandos, bucles, lógica entre {% %} Filters: modificadores de las variables dentro de variables se concatenan usando Permite herencia https: //docs.djangoproject.com/en/1.5/topics/templates/
YATL from contacts.models import Person from django.template import Template, Context me = Person.objects.get(pk=1) t = Template("Me llamo {{ person.name }}") c = Context({'person': me}) print t.render(c)
people = Person.objects.all() t = Template(""" {% for p in people %} <li> {{ p.name }} ({{ p.birthday date:"j-f" default:"n/a" }}) </li> {% endfor %}""") c = Context({'people': people}) print t.render(c)
E7: All together now 1. Crea una vista accesible a través de la URL /list/ que muestre una lista de todos los objetos Person en la base de datos.. 2 Crea una vista accesible a través de la URL /person/<pk>/ siendo pk un entero que muestre en pantalla los detalles del objeto Person correspondiente.
Solución E7 1. Lista # taller/urls.py urlpatterns = patterns('', url(r'^list/$', 'contacts.views.my_list_view'), # contacts/views.py def my_list_view(request): people = Person.objects.all() t = Template(""" {% for p in people %} <li> {{ p.name }} ({{ p.birthday date:"j-f" default:"n/a" }}) </li> {% endfor %}""") c = Context({'people': people}) return HttpResponse(t.render(c))
2. Detalles # taller/urls.py urlpatterns = patterns('', url(r'^person/(?p<pk>\d+)/$', 'contacts.views.my_view'),... # contacts/views.py def my_view(request, pk): person = Person.objects.get(pk=pk) t = Template("Me llamo {{ person.name }}") c = Context({'person': person}) return HttpResponse(t.render(c))
Necesita mejorar 1. Plantillas en archivo independiente 2. Reutilizar código
Plantillas separadas $ mkdir --parents contacts/templates/contacts #contacts/templates/contacts/person_detail.html Me llamo {{ person.name }} #contacts/templates/contacts/person_list.html {% for p in object_list %} <li> {{ p.name }} ({{ p.birthday date:"j-f" default:"n/a" }}) </li> {% endfor %}
Vistas genéricas # contacts/views.py from django.views.generic.detail import DetailView from django.views.generic.list import ListView from.models import Person class PersonDetailView(DetailView): model = Person class PersonListView(ListView): model = Person
Enrutado # taller/urls.py from contacts.views import (PersonDetailView, PersonListView) urlpatterns = patterns('', url(r'^person/(?p<pk>\d+)/$', PersonDetailView.as_view()), url(r'^list/$', PersonListView.as_view()),...
Magia? 1. dispatch() 2. http_method_not_allowed() 3. get_template_names() 4. get_slug_field() 5. get_queryset() 6. get_object() 7. get_context_object_name() 8. get_context_data() 9. get() 10. render_to_response() https://docs.djangoproject.com/en/1.5/ref/ class-based-views/generic-display/
Baterías incluidas: administración
E8: Activar módulo administración. 1 Activa el módulo de administración que incluye Django de serie. Pista: django.contrib.admin, miras los comentarios
Solución E8 1. Activar aplicación de administración # settings.py INSTALLED_APPS = (... 'django.contrib.admin', ) 2. Mapear las URL # urls.py from django.contrib import admin admin.autodiscover() urlpatterns = patterns('',... url(r'^admin/', include(admin.site.urls)), )
3. También hay que activar cada modelo # contacts/admin.py from django.contrib import admin from.models import Person admin.site.register(person)
Al final hay que sincronizar la base de datos. $ python manage.py syncdb Necesitas un superusuario. $ python manage.py createsuperuser --username=admin Prueba: http://127.0.0.1:8000/admin/
Formularios
Para que sirven Renderizar HTML Limpiar y chequear la entrada de usuario Renderizar HTML de un formulario con datos erróneos Grabar en base de datos!
Ciclo de vida 3 posibles estados de un formulario: 1. Formulario vacío y sin enviar (GET) 2. Formulario enviado pero con datos erróneos (POST). 3 Formulario enviado con datos incorrectos (POST redirige a)
Demo: Formularios 1. Nueva aplicación: feedback $ python manage.py startapp feedback 2. Archivo forms.py, Feedback from django import forms KIND_OF_REQUEST = ( ('info', u'información'), ('complaint', u'queja'), ) class ContactForm(forms.Form): kind = forms.choicefield(choices=kind_of_reques sender = forms.emailfield() subject = forms.charfield(max_length=80) text = forms.charfield(required=false)
3. Formulario en blanco from feedback.forms import ContactForm form = ContactForm() form.is_bound form.as_p()
4. Formulario con datos erróneos data = {'subject': "OLA K ASE", 'kind': 'info', 'text': "TRABAJA O K ASE"} form = ContactForm(data) form.is_bound form.is_valid() form.cleaned_data form.errors form.as_p()
5. Formulario correcto data = {'subject': "OLA K ASE", 'kind': 'info', 'text': "TRABAJA O K ASE", 'sender': 'hoygan@example.com'} form = ContactForm(data) form.is_bound form.is_valid() form.cleaned_data form.errors form.as_p()
6. Vista clásica from django.shortcuts import render from django.http import HttpResponseRedirect def contact(request): if request.method == 'POST': form = ContactForm(request.POST) if form.is_valid(): form.send_email() return HttpResponseRedirect('/thanks/') else: form = ContactForm() return render(request, 'contact.html', { 'form': form, })
5. DRY from django.views.generic.edit import FormView from.forms import ContactForm class ContactView(FormView): template_name = 'contact.html' form_class = ContactForm success_url = '/thanks/' def form_valid(self, form): form.send_email() return super(contactview, self).form_valid(form
Edición de objetos
Model + Form = ModelForm
Demo: ModelForm # contacts/views.py from django.views.generic.edit import CreateView, Updat from django.core.urlresolvers import reverse_lazy class PersonCreate(CreateView): model = Person class PersonUpdate(UpdateView): model = Person class PersonDelete(DeleteView): model = Person success_url = reverse_lazy('person-list')
# contacts/models.py class Person(models.Model):... def get_absolute_url(self): return reverse("person_detail", kwargs={"pk": s {# person_form.html #} <form action="" method="post"> {% csrf_token %} {{ form.as_p }} <input type="submit"> </form>
Autenticación
Explicación 1. Crear usuario a través de interfaz admin 2. Demostrar flujo en shell from django.contrib.auth import authenticate user = authenticate(username='fulano', password='secret if user is not None: # Usuario y contraseña correctos if user.is_active: print(u"usuario válido, activo y autenticado") else: print(u"contraseña válida pero usuario desactiv else: # Contraseña incorrecta o usuario inexistente print(u"contraseña y/o usuarios incorrectos") 3. En una vista hay que guardar sesión
Módulos de terceros
django-taggit Añade etiquetas a tus modelos 1. Instalar $ pip install django-taggit 2. Activar app
3. Añadir al modelo # contacts/models.py... from taggit.managers import TaggableManager class Person(models.Model):... tags = TaggableManager() 4. Sincronizar base de datos
5. Comprobar en admin ]
En el tintero
AJAX: 2 aproximaciones Ligera, hay que implmentar la lógica (django-braces, dajaxproject) Construye una API REST desde el modelo (rest-framework, tastypie)
Despliegue en producción Servidor web WSGI implmentado en Pythyon: gunicorn Servidor HTTP nginx como proxy inverso
Fin
Por donde seguir Documentación: https: //docs.djangoproject.com/en/1.5/contents/ Tutorial: http: //effectivedjango.com/tutorial/forms.html Paquetes: aplicaciones y meta frameworsks https://www.djangopackages.com/ Blogs: http://www.planetdjango.org/
Gracias!