Taller Django: de 0 a CRUD - Miguel González (@migonzalvar) GDG Vigo, 5 de abril de 2013
←
→
Transcripción del contenido de la página
Si su navegador no muestra la página correctamente, lea el contenido de la página a continuación
Crear un proyecto de Django Urls y vistas Modelos Plantillas Baterías incluidas: administración Formularios Edición de objetos Autenticación
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 %} {{ p.name }} ({{ p.birthday|date:"j-F"|default:"n/a" }}) {% 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// 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 %} {{ p.name }} ({{ p.birthday|date:"j-F"|default:"n/a" }}) {% endfor %}""") c = Context({'people': people}) return HttpResponse(t.render(c))
2. Detalles # taller/urls.py urlpatterns = patterns('', url(r'^person/(?P\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 %} {{ p.name }} ({{ p.birthday|date:"j-F"|default:"n/a" }}) {% 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\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 #} {% csrf_token %} {{ form.as_p }}
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!
También puede leer