Tutorial de
Tutorial escrito por Jos Plana
Tuto
rial D
jang
o ndice Introduccin: Cmo iniciar una aplicacin con Django; lbum de fotos.
Modelos y BBDD: Donde guardar la informacin.
Vistas y Urls: Procesar los datos
Formularios y Templates: Guardar y ensear nuestras fotos.
Cmo usar Git y Bootstrap con Django
Tuto
rial D
jang
o Introduccin: Cmo iniciar una aplicacin con Django; lbum de fotos. Si an no conoces Django, el framework de Python que nos permite crear
aplicaciones web de forma rpida, no te puedes perder este tutorial en el que iremos
creando un lbum de fotos, para ver de forma prctica los conceptos bsicos. Como
punto de inicio es una buena prctica trabajar sobre un entorno virtual, que nos
permita tener instalaciones especficas del proyecto en el que estamos y para
hacerlo vamos a usar virtualenvwrapper. Comprobamos que lo tenemos instalado.
(Aqu puedes ver como instalarlo)
openwebinars@~/aplicaciones: pip show virtualenvwrapper --- Name: virtualenvwrapper Version: 4.2 Location: /usr/local/lib/python2.7/dist-packages Requires: virtualenv, virtualenv-clone, stevedore openwebinars@~/aplicaciones:
Ahora podemos crear el entorno virtual en el que vamos a trabajar, que llamaremos tutorial
openwebinars@~/aplicaciones: mkvirtualenv tutorial New python executable in tutorial/bin/python Installing setuptools, pip...done. (tutorial)openwebinars@~/aplicaciones:
Podemos ver que ahora tenemos delante del prompt el nombre de nuestro entorno entre parntesis, esto nos indica que estamos dentro. Otros comandos tiles son: deactivate: Para salir del entorno. workon: Para ver los entornos disponibles. workon [entorno]: Para activar un entorno; workon tutorial en nuestro caso. rmvirtualenv [entorno]: Para borrar un entorno. Recuerda! antes tienes que haber salido de l. Con nuestro entorno listo, podemos empezar a prepararlo, para ello vamos a instalar Django y, como nuestra aplicacin va a trabajar con imgenes, tambin necesitamos Pillow.
2
Tuto
rial D
jang
o
(tutorial)openwebinars@~/aplicaciones: pip install Django==1.7, Pillow Downloading/unpacking Django==1.7 Downloading Django-1.7-py2.py3-none-any.whl (7.4MB): 7.4MB downloaded Downloading/unpacking Pillow Downloading Pillow-2.6.1.tar.gz (7.3MB): 7.3MB downloaded --- Successfully installed Django Pillow Cleaning up... (tutorial)openwebinars@~/aplicaciones: (tutorial)openwebinars@~/aplicaciones: pip list argparse (1.2.1) Django (1.7) ipython (2.3.0) Pillow (2.6.1) pip (1.5.6) setuptools (3.6) wsgiref (0.1.2) (tutorial)openwebinars@~/aplicaciones:
Con el proceso de instalacin terminado podemos crear nuestro proyecto que
llamamos myapps, usa el comando:
(tutorial)openwebinars@~/aplicaciones: django-admin.py startproject myapps (tutorial)openwebinars@~/aplicaciones:
Ya tenemos el proyecto creado y sobre este podremos ir aadiendo aplicaciones a
parte de las que ya vienen con Django, pero esto lo veremos ms adelante, antes
vamos a ver qu aspecto tiene; El comando tree nos muestra la estructura.
(tutorial)openwebinars@~/aplicaciones: tree myapps myapps manage.py myapps __init__.py settings.py urls.py wsgi.py
1 directory, 5 files (tutorial)openwebinars@~/aplicaciones:
En este primer post vamos a usar solo dos de estos ficheros, ./manage.py
y./myapps/settings.py. El primero nos va a permitir realizar acciones sobre el
3
Tuto
rial D
jang
o
proyecto desde la lnea de comandos, el segundo es el fichero de configuracin
propio del proyecto. En este punto ya podemos crear la aplicacin, vamos al
directorio ./myapps y ejecutamos el siguiente comando:
(tutorial)openwebinars@~/aplicaciones/myapps: python manage.py startapp album (tutorial)openwebinars@~/aplicaciones/myapps: (tutorial)openwebinars@~/aplicaciones/myapps: tree album album admin.py __init__.py migrations __init__.py models.py tests.py views.py 1 directory, 6 files (tutorial)openwebinars@~/aplicaciones/myapps:
Aqu podemos ver como es el directorio de nuestra aplicacin lbum. Vamos ahora al directorio ./myapps y editamos el fichero de configuracin settings.py para aadir nuestra aplicacin en la tupla INSTALLED_APPS; Debera tener este aspecto.
INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'album',
)
Antes de guardar los cambios en el fichero de configuracin, buscamos el
diccionario DATABASES, en este tutorial usaremos SQLite, la base de datos que nos
ofrece Django por defecto.
DATABASES = { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
} }
4
Tuto
rial D
jang
o Y por ltimo establecemos la zona horaria en la que nos encontramos, aqu puedes verlas todas, Europa/Madrid en nuestro caso.
TIME_ZONE = 'Europe/Madrid'
Ya podemos guardar los cambios en settings.py. Una de las aplicaciones que nos
ofrece Django por defecto es un administrador desde el que podremos aadir o
editar contenidos en nuestra aplicacin, esto nos va a facilitar mucho las cosas.
Para iniciarlo necesitamos crear las tablas necesarias y posteriormente podremos
crear el usuario con el que tendremos acceso. Para el primer paso ejecutamos el
siguiente comando.
(tutorial)openwebinars@~/aplicaciones/myapps: python manage.py migrate Operations to perform: Apply all migrations: admin, contenttypes, auth, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying sessions.0001_initial... OK (tutorial)openwebinars@~/aplicaciones/myapps:
Con esto lo que hacemos es recorrer la lista de aplicaciones que tenemos en
nuestro proyecto, en INSTALLED_APPS, dentro del fichero settings.py e ir creando
las tablas necesarias en la base de datos. Tambin podemos ver que se ha creado
un fichero db.sqlite3 que ser nuestra base de datos.
(tutorial)openwebinars@~/aplicaciones/myapps: ll -rt total 56 -rwxrwxr-x 1 jose jose 249 oct 21 20:24 manage.py* drwxrwxrwx 4 jose jose 4096 oct 22 19:43 ../ drwxrwxr-x 2 jose jose 4096 oct 22 20:22 myapps/ drwxrwxr-x 3 jose jose 4096 oct 22 20:22 album/ -rw-r--r-- 1 jose jose 36864 oct 22 20:22 db.sqlite3 drwxrwxr-x 4 jose jose 4096 oct 22 20:22 ./ (tutorial)openwebinars@~/aplicaciones/myapps:
5
Tuto
rial D
jang
o Con la base de datos lista, vamos a crear el usuario.
(tutorial)openwebinars@~/aplicaciones/myapps: python manage.py createsuperuser Username (leave blank to use 'administrador'): admin Email address: [email protected] Password: ***** Password (again): ***** Superuser created successfully. (tutorial)openwebinars@~/aplicaciones/myapps:
Ya tenemos nuestro proyecto listo para empezar a desarrollar nuestra aplicacin,
para probarlo iniciamos el servidor de desarrollo.
(tutorial)openwebinars@~/aplicaciones/myapps: python manage.py runserver Performing system checks... System check identified no issues (0 silenced). October 22, 2014 - 20:32:42 Django version 1.7, using settings 'myapps.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
En un navegador buscamos la direccin http://127.0.0.1:8000/
6
Tuto
rial D
jang
o
Para visitar el administrador vamos a http://127.0.0.1:8000/admin/
Django nos ofrece mucho trabajo hecho para que solo tengamos que pensar en
nuestra aplicacin.
7
Tuto
rial D
jang
o Modelos y BBDD: Donde guardar la informacin. En el captulo anterior vimos cmo poner en marcha nuestro proyecto Django y
cmo arrancar una aplicacin. Estamos creando un lbum de fotos, hemos
preparado un entorno virtual en el que estamos trabajando y tenemos instalado
Django y Pillow. Tambin creamos nuestra base de datos con SQLite y un usuario de
administracin para la aplicacin.
En este nuevo post vamos a crear el modelo de datos y lo incluiremos en el
administrador que nos ofrece Django para poder gestionarlo sin tener que recurrir a
la consola.
Si recordamos la estructura de carpetas que creaba Django para nuestra aplicacin,
tenemos lo siguiente.
(tutorial)openwebinars@~/aplicaciones/myapps: tree album album admin.py __init__.py migrations __init__.py models.py tests.py views.py
1 directory, 6 files (tutorial)openwebinars@~/aplicaciones/myapps:
Es en el fichero models.py donde vamos a definir el modelo de datos para nuestro
lbum, lo editamos para dejarlo as:
from django.db import models
class Category(models.Model): """ Categorias para clasificar las fotos """
name = models.CharField(max_length=50)
def __unicode__(self): return self.name
8
Tuto
rial D
jang
o class Photo(models.Model): """ Fotos del album """
category = models.ForeignKey(Category, null=True, blank=True) title = models.CharField(max_length=50, default='No title') photo = models.ImageField(upload_to='photos/') pub_date = models.DateField(auto_now_add=True) favorite = models.BooleanField(default=False) comment = models.CharField(max_length=200, blank=True)
def __unicode__(self): return self.title
Lo primero que hacemos es importar el mdulo models ya que cada una de nuestras
clases ser una subclase de models.Model.
Para nuestra aplicacin hemos definido dos clases, Category que representar las
distintas categoras en las que podemos clasificar nuestras fotos y Photo que
representa las fotos de nuestro lbum. Si las clases que definimos en models.py
representan las tablas en nuestra base de datos, las columnas estn representadas
por las instancias de la clase Field. Nosotros hemos usado los siguientes campos:
CharField: para campos de caracteres. Tiene un argumento obligatorio, max_length.
ImageField: para ficheros con extensiones de imagen. Aqu usamos el argumento
upload_to para especificarle en que directorio queremos que se guarden las
imgenes de nuestra aplicacin.
DateField: para fechas. Al indicarle auto_now_add como True, estamos
almacenando la fecha en la que la foto se ha guardado por primera vez.
BooleanField: para valores booleanos, indicndole que el valor por defecto es false,
en caso de no definirlo estaramos usando None como valor por defecto.
ForeignKey: para establecer relaciones entre clases. null y blank para especificar que
este campo puede estar vaco.
Adems de esto, definimos en cada una de las clases el mtodo __unicode__() de
esta forma le podemos indicar como queremos que quede representado cada uno
9
Tuto
rial D
jang
o
de los objetos, en nuestro caso usaremos el nombre para las categoras y el ttulo
para las fotos.
Con el modelo de datos definido podemos trasladarlo a nuestra base de datos,
primero ejecutamos el siguiente comando.
(tutorial)openwebinars@~/aplicaciones/myapps: python manage.py makemigrations Migrations for 'album': 0001_initial.py: - Create model Category - Create model Photo (tutorial)openwebinars@~/aplicaciones/myapps:
Veremos una salida como esta, que nos indica que se ha creado un fichero,
0001_initial.py en el que estn las modificaciones que se harn en la base de datos
al ejecutar el siguiente comando.
(tutorial)openwebinars@~/aplicaciones/myapps: python manage.py migrate Operations to perform: Apply all migrations: admin, album, contenttypes, auth, sessions Running migrations: Applying album.0001_initial... OK (tutorial)openwebinars@~/aplicaciones/myapps:
Con esto tendremos en la base de datos el modelo que acabamos de definir. Pero
antes de probarlo tenemos que editar el fichero settings.py para definir
MEDIA_ROOT como:
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
Lo hacemos porque Django no guarda en la base de datos el fichero si no la ruta
donde est el fichero, de forma relativa a esta configuracin; As, nuestras fotos se
guardarn en./media/photos/.
Ahora ya podemos abrir el shell y probar a grabar la primera imagen.
(tutorial)openwebinars@~/aplicaciones/myapps: python manage.py shell
from album.models import Category, Photo from django.core.files import File
10
Tuto
rial D
jang
o c = Category(name='Lagos') c.save() p = Photo(category=c, title='Maly') f = open('/home/jose/Escritorio/maly.jpg') p.photo.save('maly.jpg', File(f))
c.name Out: 'Lagos'
p.title Out: 'Maly'
p.photo.url Out: 'photos/maly.jpg'
exit
Podemos ver que se ha creado un nuevo directorio ./media/photos/ que contiene
nuestra imagen.
(tutorial)openwebinars@~/aplicaciones/myapps: tree media media photos maly.jpg
1 directory, 1 file
Aunque vemos que funciona correctamente, esta forma de trabajar es lenta si se
trata de alimentar la base de datos con ms de un registro, para hacerlo ms
llevadero vimos en el captulo anterior que Django nos ofreca un administrador con
el que poder gestionar el contenido de nuestra app. Vamos a prepararlo.
Lo primero es decirle que queremos que nuestra aplicacin, lbum, aparezca en l.
Editamos el fichero ./album/admin.py para dejarlo as.
from django.contrib import admin from album.models import Category, Photo
admin.site.register(Category) admin.site.register(Photo)
11
Tuto
rial D
jang
o
Iniciamos el servidor de pruebas y vamos a la direccin http://127.0.0.1:8000/admin,
entramos con el usuario que creamos en el captulo anterior y vemos la pgina
principal del administrador.
Ahora es ms fcil crear una categora nueva y mucho ms fcil aadir imgenes. A
la derecha de Photos tenemos el botn Add.
En Category podemos elegir entre las que tenemos creadas o directamente crear
una nueva, Montaas en nuestro caso. Abajo a la derecha podemos decidir si
guardar y crear otra, guardar y continuar editando o guardar.
Tambin podemos borrar las fotos que tenemos cargadas, con el botn que aparece
abajo a la izquierda.
12
Tuto
rial D
jang
o
Al hacerlo detectamos un problema, aunque el registro se ha borrado de nuestra
base de datos, vemos que el fichero que contiene la imagen sigue estando en
nuestro directorio./media/photos/; Como dijimos antes lo que almacena Django en
la base de datos solo es el directorio donde est la imagen.
Para solucionarlo podemos usar las seales. Estas nos informan de cundo est
ocurriendo una determinada accin. A nosotros nos interesa saber cuando se est
llamando al mtodo delete() de nuestro objeto Photo, de esta forma una vez borrado
el registro de la base de datos, podremos borrar la imagen a la que hace referencia.
Volvemos a nuestro fichero models.py y lo editamos.
from django.db.models.signals import post_delete from django.dispatch import receiver
@receiver(post_delete, sender=Photo) def photo_delete(sender, instance, **kwargs): """ Borra los ficheros de las fotos que se eliminan. """ instance.photo.delete(False)
Con esto queda solucionado nuestro problema.
Vistas y Urls: Procesar los datos En los post anteriores vimos cmo se iniciaba una aplicacin, creamos la base de
datos y los modelos; y vimos como podamos introducir las imgenes de nuestro
lbum de fotos de dos formas, usando el shell y a travs del administrador que nos
ofrece Django por defecto. Hoy usaremos las vistas para mostrar nuestra galera.
13
Tuto
rial D
jang
o
Para ver como gestiona Django las vistas, las relaciona con el modelo de datos y
enva esa informacin al navegador vamos a empezar creando una vista sencilla,
simplemente vamos a mostrar un mensaje por pantalla.
Editamos el fichero ./album/views.py
from django.http import HttpResponse
def first_view(request): return HttpResponse('Esta es mi primera vista!')
Una vista es una funcin o un mtodo que bsicamente hace dos cosas, toma como
argumento un objeto HttpRequest, en el que va la informacin referente a la solicitud
que estamos haciendo, como por ejemplo si el mtodo empleado es POST o GET o
el directorio donde est la pgina solicitada; y devuelve un objeto HttpResponse con
la informacin de la pgina que va a mostrar o una excepcin si algo ha ido mal.
Ahora tenemos que asociar la vista que acabamos de definir con una direccin, para
hacerlo vamos a manejar dos ficheros, uno lo crea Django al iniciar el proyecto y
est en ./myapps/urls.py, el otro lo tendremos que crear nosotros dentro del
directorio de nuestra aplicacin,./album/urls.py.
Vamos a editar el primero ./myapps/urls.py.
from django.conf.urls import patterns, include, url from django.contrib import admin
urlpatterns = patterns('', url(r'^admin/', include(admin.site.urls)),
)
Si nos olvidamos de las lneas que tiene comentadas vemos que tiene una relacin
entre la direccin /admin y el mdulo admin.site.urls. Esta es la forma que tiene
Django de enlazar ladireccin con el fichero urls.py propio de la aplicacin admin
que ya hemos usado en los captulos anteriores. De esta forma al dirigirnos a
http://127.0.0.1:8000/admin/ lo que hacemos es empezar a usar dicho fichero para
14
Tuto
rial D
jang
o
los siguientes enlaces que usemos, como por ejemplo
http://127.0.0.1:8000/admin/album/photo/.
Vamos a hacer lo mismo con nuestra aplicacin lbum. Aadimos la siguiente lnea
como nuevo argumento de la funcin patterns() para dejarla de la siguiente forma.
from django.conf.urls import patterns, include, url from django.contrib import admin
urlpatterns = patterns('', url(r'^album/', include('album.urls')), url(r'^admin/', include(admin.site.urls)),
)
Ahora ya sabemos que al incluir en el navegador una direccin que comience por
/albumenviaremos el resto de la cadena como argumento a la funcin patterns que
tenemos en el fichero./album/urls.py.
Creamos y editamos este fichero para dejarlo as.
from django.conf.urls import patterns, url from album import views
urlpatterns = patterns('', url(r'^$', views.first_view, name='first-view'), )
De esta forma, la direccin /album estar apuntando a la vista first_view().
La funcin url() necesita dos argumentos, el primero ser una expresin regular
(regex) que utiliza para identificar la direccin que escribimos en el navegador, el
segundo es la funcin a la que llamar, first_view() para nosotros, pasndole como
primer argumento el objeto HttpRequest que mencionbamos antes. Adems se
pueden pasar tres argumentos opcionales, name lo usaremos para identificar
nuestra url de forma nica y nos servir para poder cambiar la url sin tener que
modificar las partes del proyecto donde hacemos referencia a ella. Los otros dos
argumentos son kwargs y prefix que no usaremos en nuestra aplicacin.
15
Tuto
rial D
jang
o
Si arrancamos el servidor de pruebas y buscamos en el navegador la direccin
http://127.0.0.1:8000/album/ veremos lo siguiente.
Con esto hemos visto cmo unir la direccin del navegador con nuestras vistas,
veamos ahora cmo relacionar la vista con el modelo de datos y devolver esta
informacin al navegador. Para hacerlo vamos a crear un nuevo directorio dentro de
nuestra aplicacin./myapps/album/templates/album/ de modo que tengamos esto.
(tutorial)openwebinars@~/aplicaciones/myapps: tree album album admin.py __init__.py migrations 0001_initial.py 0001_initial.pyc __init__.py models.py templates album tests.py urls.py views.py
En l incluiremos los ficheros html propios de nuestra aplicacin. Creamos el fichero
category.html de la siguiente forma.
{{ object_list }}
16
Tuto
rial D
jang
o
Ahora veremos que es object_list.
Editamos de nuevo el fichero ./album/views.py
from django.http import HttpResponse from django.shortcuts import render from album.models import Category, Photo
def first_view(request): return HttpResponse('Esta es mi primera vista')
def category(request): category_list = Category.objects.all() context = {'object_list': category_list} return render(request, 'album/category.html', context)
category_list contiene todas las categoras disponibles y las pasamos como
argumento a la funcin render(). Esta funcin es un atajo que nos proporciona
Django para devolver el objeto HttpResponse a partir de una plantilla (category.html)
y la informacin necesaria para componerla (context).
Lo unimos con ./album/urls.py
from django.conf.urls import patterns, url from album import views
urlpatterns = patterns('', url(r'^$', views.first_view, name='first-view'), url(r'^category/$', views.category, name='category-list'), )
y buscamos la nueva direccin en el navegador,
http://127.0.0.1:8000/album/category/
17
Tuto
rial D
jang
o
Como vemos, con object_list hemos incluido la lista de objetos Category dentro de
nuestra plantilla category.html, por tanto ya tenemos toda la informacin relativa a
las categoras.
Vamos a incluir una nueva vista que nos muestre el detalle de una de las categoras;
editamos de nuevo el fichero ./album/views.py
def category_detail(request, category_id):
category = Category.objects.get(id=category_id) context = {'object': category} return render(request, 'album/category_detail.html', context)
Ahora la funcin category_detail() recibe dos argumentos, request y el identificador
de la categora que queremos mostrar.
En el directorio de ./album/templates/album creamos una nueva plantilla
category_detail.html
{{ object.id }}, {{ object.name }}, {{ object.photo_set.all }}
De forma similar a la vista anterior, en la variable context que pasamos a la funcin
render() tenemos un objeto Category del que podemos extraer su id, name y las
fotos que pertenecen a dicha categora entre otras cosas.
Por ltimo conectamos la vista con una url, esta vez tenemos que indicar de qu
categora queremos extraer el detalle, para conseguirlo usaremos (?P\d+) siendo
category_id el segundo parmetro que hemos pasado a la funcin category_detail().
18
Tuto
rial D
jang
o from django.conf.urls import patterns, url from album import views urlpatterns = patterns('',
url(r'^$', views.first_view, name='first-view'), url(r'^category/$', views.category, name='category-list'), url(r'^category/(?P\d+)/detail/$', views.category_detail,
name='category-detail'), )
Si abrimos el enlace http://127.0.0.1:8000/album/category/2/detail/ en el
navegador veremos el resultado.
Ya vimos en posts anteriores que Django nos ofrece soluciones para facilitarnos el
trabajo y en esta ocasin podemos aprovecharnos de las vistas genricas, como
ListView y DetailView para obtener una lista de todos los objetos pertenecientes a un
modelo y el detalle de uno de ellos. Vamos a hacerlo para la clase Photo.
Abrimos ./album/views.py e incluimos este cdigo.
from django.views.generic import ListView, DetailView
class PhotoListView(ListView): model = Photo
class PhotoDetailView(DetailView): model = Photo
Como podemos ver solamente tenemos que decirle sobre que modelo queremos
trabajar.
19
Tuto
rial D
jang
o
Creamos dos nuevos templates, photo_list.html para el listado de fotos y
photo_detail.html para el detalle. Las vistas genricas usan por defecto el siguiente
sistema a la hora de buscar la plantilla html, /_list.html y
/_detail.html, paraListView y DetailView
respectivamente. Si queremos especificar un nombre de plantilla distinto podemos
usar el atributo template_name.
Creamos ./album/templates/album/photo_list.html
{{ object_list }}
y ./album/templates/album/photo_detail.html
{{ object.id }}, {{ object.title }}
Por ltimo, unimos la vista con urls editando ./album/urls.py para dejarlo as.
from django.conf.urls import patterns, url from album import views
urlpatterns = patterns('', url(r'^$', views.first_view, name='first-view'), url(r'^category/$', views.category, name='category-list'), url(r'^category/(?P\d+)/detail/$', views.category_detail, name='category-detail'),
url(r'^photo/$', views.PhotoListView.as_view(), name='photo-list'),
url(r'^photo/(?P\d+)/detail/$', views.PhotoDetailView.as_view(),
name='photo-detail'), )
Aqu tenemos que tener en cuenta dos cosas, al usar el sistema de vistas genricas
estamos trabajando con clases no con funciones por lo que tenemos que llamar al
mtodo as_view(), tambin hemos dejado de usar (?P\d+) y lo hemos sustituido por
(?P\d+).
Ya podemos ver en el navegador que el resultado es el mismo aunque esta vez
nuestras vistas son mucho ms sencillas.
20
Tuto
rial D
jang
o
http://127.0.0.1:8000/album/photo/
http://127.0.0.1:8000/album/photo/1/detail/
Esto es solo una parte ya que el sistema de vistas genricas es mucho ms potente
y lo veremos con ms detalle en el siguiente captulo.
Como ejercicio podemos cambiar las funciones category() y category_detail() para
usar generic views, teniendo en cuenta que estamos trabajando con la plantilla
category.html y no es lo que usar por defecto la vista ListView.
Formularios y Templates: Guardar y ensear nuestras fotos. En este nuevo captulo vamos trabajar con las plantillas que utilizamos en el post
anterior para mostrar la informacin de forma ms visual. Continuaremos viendo las
21
Tuto
rial D
jang
o
vistas genricas para crear formularios de forma que podamos modificar o aadir
registros a nuestra base de datos.
Como punto de partida vamos a modificar la plantilla category_list.html que
tenamos en el captulo anterior para dejarla as:
ALBUM
Category
{% for c in object_list %}
{{ c.name }}
{% endfor %}
De nuestra nueva plantilla podemos destacar dos elementos, las variables que estn
identificadas por {{ }} y ya usamos en el captulo anterior y los tags que estn
representados por {% %}.
Las variables son evaluadas y sustituidas por su valor en la representacin de la
plantilla. La forma en que se muestran las variables puede ser modificada mediante
el uso de filtros que veremos ms adelante.
Los tags nos ofrecen la posibilidad de incluir lgica o control de flujo dentro de las
plantillas entre otras cosas.
En nuestra plantilla estamos usando {% for %} para recorrer las distintas categorias
que tenemos y poder mostrar su nombre con la variable {{ c.name }}, el valor name
del objeto c extraido de la lista object_list.
{% url %} es otro tag que usaremos muy a menudo, en este caso le pasamos el
nombre de la url a la que queremos apuntar y el parmetro que necesita.
Si vemos el resultado en el navegador http://127.0.0.1:8000/album/category/,
tenemos lo siguiente:
22
Tuto
rial D
jang
o
Si lo pensamos bien, vemos que el ttulo Album y la cabecera que indica si estamos
en la seccin Category o Photo se van a repetir para el resto de plantillas. Para evitar
tener que duplicar cdigo y seguir el principio DRY de Django, tenemos a nuestra
disposicin dos tags que nos van a permitir usar una nica plantilla como esqueleto
de nuestra aplicacin e ir insertando en esta las partes propias del resto de
plantillas, esto se conoce como Template inheritance. Vamos a usarlo.
Lo primero que haremos es modificar la plantilla category_list.html para dejarla as:
{% extends 'base.html' %}
{% block section %} Category {% endblock section %}
{% block maincontent %}
{% for c in object_list %}
{{ c.name }}
{% endfor %} {% endblock maincontent %}
Aqu tenemos los dos nuevos tags, {% extends %} y {% block %}. El primero va a
cargar nuestra plantilla base.html, que ser la plantilla esqueleto e incluir las
secciones contenidas en los tags block donde se lo indiquemos.
23
Tuto
rial D
jang
o
Ahora vamos a crear un nuevo directorio ./templates en el que incluiremos la
plantilla base.html
(tutorial)openwebinars@~/aplicaciones/myapps: tree templates templates base.html
0 directories, 1 file (tutorial)openwebinars@~/aplicaciones/myapps:
base.html quedar de la siguiente forma:
Album Album
Category
Photo
{% block section %} {% endblock section %}
{% block maincontent %} {% endblock maincontent %}
Hemos incluido dos nuevos links para poder navegar entre categoras y fotos.
Vamos a modificar la que era nuestra url principal, para que deje de mostrar la
primera vista que hicimos y la usaremos como pgina de inicio. Editamos
./album/views.py y ./album/urls.py
En la ./album/views.py aadimos:
24
Tuto
rial D
jang
o EnOpenWebinars.nettenemostodosestoscursosatuenteradisposicin
LinuxLPIC-1Examen101
LinuxLPIC-1Examen102
NodeJS,ExpressJSyMongoDB
AngularJSyTypeScript
AppsmvilesconPhoneGap
ServidoresVoIPconAsterisk
DesarrolloFrontend
Profesional
VirtualizacindeServidorescon
Promox
AppsMvilesconTitaniumAlloy
DesarrolloBackendcon
Django
Tuto
rial D
jang
o from django.shortcuts import render def base(request): return render(request, 'base.html')
y en ./album/urls.py:
from django.conf.urls import patterns, url from album import views
urlpatterns = patterns('', url(r'^$', views.base, name='base'), url(r'^category/$', views.CategoryListView.as_view(),
name='category-list'), url(r'^category/(?P\d+)/detail/$',
views.CategoryDetailView.as_view(), name='category-detail'),
url(r'^photo/$', views.PhotoListView.as_view(), name='photo-list'),
url(r'^photo/(?P\d+)/detail/$', views.PhotoDetailView.as_view(),
name='photo-detail'), )
Adems, vamos a indicar la nueva ruta donde estar la plantilla base.html aadiendo
en./myapps/settings.py
TEMPLATE_DIRS = ( os.path.join(BASE_DIR, 'templates/'),
)
Comprobamos el resultado en el navegador, http://127.0.0.1:8000/album/.
25
Tuto
rial D
jang
o
y http://127.0.0.1:8000/album/category/
Ahora ya podemos practicar con category_detail.html para que herede de base.html
y experimentar con las distintas etiquetas html. Nosotros lo hemos dejado as,
http://127.0.0.1:8000/album/category/1/detail/
26
Tuto
rial D
jang
o
{% extends 'base.html' %} {% load static %}
{% block section %} {{ object.name }} {% endblock section %}
{% block maincontent %}
{% for p in object.photo_set.all %}
{{ p.title }}
{% endfor %}
27
Tuto
rial D
jang
o {% endblock maincontent %}
Vemos que en cada categora se muestra una miniatura con los enlaces a las
distintas fotos que pertenecen, para mostrar las imagenes necesitamos incluir
STATICFILES_DIRS en ./album/settings.py
STATICFILES_DIRS = ( os.path.join(BASE_DIR, 'media'),
)
Ahora que tenemos los enlaces al detalle de las fotos, vamos a completar la
navegacin modificando photo_detail.html para que nos muestre la foto y los
detalles.
Editamos photo_detail.html.
{% extends 'base.html' %} {% load static %}
{% block section %}
{{ object.title }} {% if object.favorite %}
{% else %}
{% endif %} {% if object.category_id %} ( {{
object.category }} ) {% endif %}
comments: {{ object.comment|default:"no comments" }} edit delete
{% endblock section %}
{% block maincontent %}
28
Tuto
rial D
jang
o {% endblock maincontent %}
En esta nueva plantilla hemos incluido varias cosas interesantes. Con la etiqueta
load estamos cargando el modulo static.py que necesitamos para mostrar las
imagenes. Para mostrar los comentarios usamos la variable {{ object.comment }}
con un filtro default para incluir un comentario por defecto cuando el valor es una
cadena vaca , adems tenemos dos nuevos enlaces para editar y borrar.
Como ya hemos visto, estos enlaces apuntan a una vista a travs de ./album/urls.py.
Con el primero modificaremos las fotos mientras que con el segundo vamos a poder
borrar un registro de nuestra base de datos.
Vamos a crear estas nuevas funcionalidades y aprovechamos para recordar como
se trabajaba con las vistas genricas.
Editamos ./album/views.py para aadir lo siguiente.
from django.core.urlresolvers import reverse_lazy from django.views.generic.edit import UpdateView, CreateView, DeleteView
class PhotoUpdate(UpdateView): model = Photo
class PhotoCreate(CreateView): model = Photo
class PhotoDelete(DeleteView): model = Photo success_url = reverse_lazy('photo-list')
Tambin hemos incluido la vista PhotoCreate que usaremos enseguida.
Enlazamos las vistas con urls editando ./album/urls.py y aadiendo lo siguiente
dentro de patterns.
# Update url(r'^photo/(?P\d+)/update/$', views.PhotoUpdate.as_view(), name='photo-update'), #Create url(r'^photo/create/$', views.PhotoCreate.as_view(), name='photo-create'), #Delete url(r'^photo/(?P\d+)/delete/$', views.PhotoDelete.as_view(),
29
Tuto
rial D
jang
o name='photo-delete'),
Ahora bien, para las dos primeras vistas, update y create, necesitaremos un
formulario desde el que podamos interactuar bien modificando los valores de
nuestros registros o bien creando uno nuevo. Para crearla usaremos el nombre que
buscar Django por defecto, photo_form.html, esta plantilla es comn para las dos
vistas PhotoUpdate y PhotoCreate ya que muestra todos los campos definidos en el
modelo y dependiendo de la llamada que se haga, desde el modo Update o Create
mostrar los campos con los valores o vacos respectivamente.
Este es el aspecto que tendr photo_form.html
{% extends "base.html" %}
{% block maincontent %} {% csrf_token %}
{{ form.as_p }} Save Cancel
{% endblock maincontent%}
Volvemos a usar como plantilla principal base.html. Dentro de la etiqueta form
usamos el atributo method como POST en lugar de GET ya que en update y create
estamos modificando la informacin contenida en la base de datos. Tambin es
importante destacar el uso del tag {% csrf_token %}; usaremos este tag dentro de las
etiquetas form con atributos POST para evitar ataques CSRF. Bsicamente estos
ataques usan los datos de conexin de un usuario conocido y de confianza para
realizar acciones no autorizadas sobre el servidor.
La variable form contiene los campos de nuestro modelo y para representarlos
podemos usar as_p para mostrarlos entre etiquetas en html, as_table para usar
etiquetas o as_ul para usar etiquetas
Tenemos que tener en cuenta una cosa ms, cuando queramos cancelar la edicin
de un registro estamos redirigiendo la navegacin hacia la vista nombrada como
photo-list, de igual modo tenemos que definir hacia dnde iremos cuando grabemos
30
Tuto
rial D
jang
o
los datos usando el botn save, para ello editamos ./album/models.py y aadimos el
siguiente mtodo dentro de la clase Photo.
def get_absolute_url(self): return reverse('photo-list')
Como estamos usando reverse es necesario importarlo desde
django.core.urlresolvers
Si abrimos el detalle de una foto y pulsamos sobre el enlace edit veremos esto:
donde podemos modificar los valores que tenamos.
Con estos pasos que acabamos de hacer tenemos lista tambin la opcin de crear
un nuevo registro para aadir fotos, solo tenemos que incluir el enlace en la plantilla
que queramos. Nosotros lo hemos hecho en base.html como otro elemento ms de
la lista.
31
Tuto
rial D
jang
o
Vamos a ver como preparar la opcin de borrado. Aunque ya tenemos enlazados las
vistas con la url y aparentemente no es necesaria ninguna plantilla para borrar un
registro de la base de datos, si intentamos borrar una foto veremos el error
TemplateDoesNotExist, esto es as porque Django nos pide una plantilla para
confirmar la accin, justo debajo del error podemos ver el nombre que espera
encontrar Django.
Vamos a crearla, en nuestro directorio ./album/templates/album incluimos una
nueva plantillaphoto_confirm_delete.html
{% extends 'base.html' %}
32
Tuto
rial D
jang
o {% block maincontent %} {% csrf_token %}
Delete "{{ object.title }}"? Cancel
{% endblock maincontent %}
Igual que al crear o modificar un registro tenemos que decirle el enlace al que debe
dirigirse una vez terminada la accin, al borrar tambin tenemos que hacerlo, ya lo
incluimos en la propia clase con el atributo success_url.
Con esto tenemos las herramientas necesarias para cambiar la plantilla
photo_list.html y hacer que tenga mejor aspecto, tambin podemos generar nuevos
formularios para crear, editar o eliminar categoras as como jugar con todas las
opciones que nos ofrece html para personalizar nuestra aplicacin.
En el siguiente captulo veremos cmo usar repositorios y mejoraremos la
apariencia de nuestro lbum con Bootstrap.
Cmo usar Git y Bootstrap con Django Continuando con nuestro lbum de fotos, y ahora que el proyecto va tomando forma,
es buena idea empezar a usar un control de versiones de forma que queden
reflejados los cambios que vamos haciendo, as podremos recuperar el proyecto en
un estado anterior y tendremos control sobre las fechas en que se hicieron
modificaciones, entre otras cosas. Adems, aunque en nuestro caso slo
trabajamos nosotros en el lbum, en caso de formar parte de un equipo de
desarrollo tendramos la posibilidad de trabajar de forma independiente sobre los
mismos ficheros sin pisar el trabajo de nuestros compaeros.
Tenemos a nuestra disposicin distintas herramientas que nos permiten trabajar de
esta forma, en nuestro caso usaremos Git, as podemos trabajar en un repositorio
propio y una vez tengamos los cambios hechos, hacer push de nuestro repositorio a
un repositorio compartido.
33
Tuto
rial D
jang
o
Lo primero que vamos a hacer es crear un repositorio local, en el que incluiremos
nuestro proyecto con el comando git init .
(tutorial)openwebinars@~/aplicaciones/myapps: git init Initialized empty Git repository in /home/jose/aplicaciones/myapps/.git/ (tutorial)openwebinars@~/aplicaciones/myapps:
Vemos que se ha creado un nuevo directorio .git.
Para ver el estado actual de nuestro repositorio usaremos git status; es conveniente
revisar el estado de nuestro proyecto para tener identificados qu ficheros se han
aadido para la siguiente subida al repositorio comn.
(tutorial)openwebinars@~/aplicaciones/myapps: git status En la rama master
Commit inicial
Archivos sin seguimiento: (use git add ... para incluir lo que se ha de ejecutar)
.idea/ album/ db.sqlite3 manage.py media/ myapps/ templates/
no se ha agregado nada al commit pero existen archivos sin seguimiento (use git add para darle seguimiento) (tutorial)openwebinars@~/aplicaciones/myapps:
Se ha creado una rama de trabajo llamada master, adems git ha identificado todos
los archivos que forman nuestro proyecto y nos informa de que no se han incluido
para el siguiente commit. Para incluir los archivos usaremos el comando git add
o git add. Para incluir todo, pero antes vamos a ver si queremos compartir
todo lo que hay en el directorio, por ejemplo en nuestro proyecto tenemos muchos
ficheros del tipo *.pyc que no queremos subir, tampoco queremos subir las fotos ni
la base de datos, de esta forma al repositorio llegarn copias limpias de nuestro
proyecto. Para descartar este tipo de ficheros usamos un archivo .gitignore en el que
podemos decir qu debe dejar sin seguimiento.
34
Tuto
rial D
jang
o
(tutorial)openwebinars@~/aplicaciones/myapps: cat .gitignore *.jpg *.pyc .idea db.sqlite3 (tutorial)openwebinars@~/aplicaciones/myapps:
Si volvemos a consultar el estado
(tutorial)openwebinars@~/aplicaciones/myapps: git status En la rama master
Commit inicial
Archivos sin seguimiento: (use git add ... para incluir lo que se ha de ejecutar)
.gitignore album/ manage.py myapps/ templates/
no se ha agregado nada al commit pero existen archivos sin seguimiento (use git add para darle seguimiento) (tutorial)openwebinars@~/aplicaciones/myapps:
Vemos que ya no est incluido el archivo db.sqlite3, tampoco estarn dentro de los
directorios los ficheros .pyc y .jpg.
Ahora ya podemos decir a Git que inicie el seguimiento de los ficheros de nuestro
proyecto.
(tutorial)openwebinars@~/aplicaciones/myapps: git add . (tutorial)openwebinars@~/aplicaciones/myapps: git status En la rama master
Commit inicial
Cambios para hacer commit: (use git rm --cached ... para eliminar stage)
new file: .gitignore new file: album/__init__.py new file: album/admin.py
35
Tuto
rial D
jang
o new file: album/migrations/0001_initial.py new file: album/migrations/__init__.py new file: album/models.py new file: album/templates/album/category_detail.html new file: album/templates/album/category_list.html new file: album/templates/album/photo_confirm_delete.html new file: album/templates/album/photo_detail.html new file: album/templates/album/photo_form.html new file: album/templates/album/photo_list.html new file: album/tests.py new file: album/urls.py new file: album/views.py new file: manage.py new file: myapps/__init__.py new file: myapps/settings.py new file: myapps/urls.py new file: myapps/wsgi.py new file: templates/base.html
(tutorial)openwebinars@~/aplicaciones/myapps:
Aqu podemos ver qu ficheros se han incluido en el primer commit que podemos
ejecutar con el comando git commit -m "Initial commit".
(tutorial)openwebinars@~/aplicaciones/myapps: git commit -m "Initial commit" [master (root-commit) e300e4a] Initial commit 21 files changed, 439 insertions(+) create mode 100644 .gitignore create mode 100644 album/__init__.py create mode 100644 album/admin.py create mode 100644 album/migrations/0001_initial.py create mode 100644 album/migrations/__init__.py create mode 100644 album/models.py create mode 100644 album/templates/album/category_detail.html create mode 100644 album/templates/album/category_list.html create mode 100644 album/templates/album/photo_confirm_delete.html create mode 100644 album/templates/album/photo_detail.html create mode 100644 album/templates/album/photo_form.html create mode 100644 album/templates/album/photo_list.html create mode 100644 album/tests.py create mode 100644 album/urls.py create mode 100644 album/views.py create mode 100755 manage.py create mode 100644 myapps/__init__.py create mode 100644 myapps/settings.py create mode 100644 myapps/urls.py create mode 100644 myapps/wsgi.py create mode 100644 templates/base.html (tutorial)openwebinars@~/aplicaciones/myapps:
36
Tuto
rial D
jang
o Ahora que hemos hecho el primer commit en nuestro repositorio local podemos pasar nuestro proyecto a un repositorio compartido. En este captulo usaremos
GitHub para gestionar nuestro repositorio remoto. Una vez hayamos creado nuestra
cuenta en GitHub veremos la opcin Create Repository, nosotros lo hemos
nombrado como my_album por lo que GitHub asigna la direccin
https://github.com/usuario/my_album.git, donde usuario ser el nombre de usuario
de nuestra cuenta GitHub. Con el siguiente comando podemos crear un repositorio
remoto, que normalmente se llama origin, asociado a esta direccin.
(tutorial)openwebinars@~/aplicaciones/myapps: git remote add origin https://github.com/usuario/my_album.git (tutorial)openwebinars@~/aplicaciones/myapps:
Ya podemos enviar nuestro repositorio local al repositorio remoto, origin, que
acabamos de crear.
(tutorial)openwebinars@~/aplicaciones/myapps: git push -u origin master Username for 'https://github.com': usuario Password for 'https://[email protected]': ******* Counting objects: 27, done. Delta compression using up to 2 threads. Compressing objects: 100% (23/23), done. Writing objects: 100% (27/27), 6.03 KiB | 0 bytes/s, done. Total 27 (delta 1), reused 0 (delta 0) To https://github.com/usuario/my_album.git *[new branch] master -> master Branch master set up to track remote branch master from origin. (tutorial)openwebinars@~/aplicaciones/myapps:
Con este comando estamos diciendo que pase el contenido de la rama local, que
por defecto Git llama mster al repositorio origin que hemos creado, el parmetro -u
le dice a Git que recuerde estos dos nombres, por lo que la prxima vez slo
tendremos que usar git push y ya sabr que tiene que pasar el contenido de master a
origin. Si volvemos a la direccin https://github.com/usuario/my_album.git veremos
el contenido de nuestro proyecto.
37
Tuto
rial D
jang
o
Ahora que ya tenemos nuestro repositorio remoto, volvamos a trabajar sobre la
copia local que tenemos.
Aunque en el captulo anterior vimos cmo mejorar el aspecto de nuestra aplicacin,
en esta ocasin vamos a ir un paso ms all y vamos a usar Bootstrap, que aparte
de ofrecernos un diseo preestablecido es web responsive, es decir veremos cmo
podemos hacer que nuestra aplicacin mantenga un buen aspecto
independientemente del dispositivo que estemos usando para verla. Lo primero que
tenemos que hacer es indicar a nuestra aplicacin que tiene que cargar Bootstrap,
para hacerlo podemos descargarlo e incluirlo en nuestro proyecto o simplemente
referenciar al CDN, esto ltimo es lo que haremos nosotros. Editamos el fichero
./templates/base.html para dejarlo de la siguiente forma.
Album
Toggle navigation
Album
38
Tuto
rial D
jang
o
Category(current) Photo
Action
Upload
Log In
{% block section %} {% endblock section %}
{% block maincontent %} {% endblock maincontent %}
Tambin hemos aadido una barra de navegacin como men principal, que est
bajo el comentario . Vemos que las etiquetas que estbamos usando
hasta ahora tienen clases del tipo collapse o navbar-header, stas son clases
propias de Bootstrap. Si refrescamos nuestra aplicacin en el navegador veremos
que ha cambiado la fuente, el color del texto y nuestro men principal.
39
Tuto
rial D
jang
o
Para ver con ms detalle lo que nos ofrece Bootstrap vamos a construir nuestra
plantilla de fotos ./album/templates/photo_list.html. Partimos de esta plantilla
{% extends 'base.html' %}
{% load static %}
{% block section %} Photo {% endblock section %}
{% block maincontent %} {{ object_list }} {% endblock maincontent %}
y sobre ella vamos a incluir el sistema de grid de Bootstrap para dejarla as.
{% extends 'base.html' %}
{% load static %}
{% block section %} Photo {% endblock section %}
{% block maincontent %}
{{ object_list }}
{% endblock maincontent %}
40
Tuto
rial D
jang
o
Aqu lo importante est en las etiquetas div, vemos tres tipos de clases, row,
col-md-6 y well.
row: Siempre tiene que estar incluida en una clase container, en nuestro caso en
./templates/base.html. Se usa para organizar grupos de columnas.
col-md-6: Con esta clase creamos una columna (col-), adaptada a terminales
medianos (col-md-), de tamao 6 (col-md-6); debemos saber que Bootstrap divide el
terminal en doce unidades, por tanto nuestra columna ocupar la mitad del terminal
siempre que este sea mediano o grande, en terminales pequeos como tablets o
mviles ocupar el total del terminal. De igual forma podramos incluir una columna
de dos unidades, adaptada a terminales pequeos y separada una unidad del
margen izquierdo agrupando las clases .col-xs-2 y .col-xs-offset-1 o usar columnas
de tamao 3 para terminales medianos y tamao 7 para terminales pequeos
agrupando .col-sm-7 .col-md-3
well: Simplemente pone el fondo de nuestras etiquetas div en un tono ms oscuro.
Para verlo en accin solo tenemos que refrescar la pantalla y veremos esto en
nuestro porttil.
Vemos que la columna ocupa la mitad del espacio de la fila que la contiene, y
nuestro men aparece tal y como lo dejamos.
Esto sera lo que veramos en un mvil
41
Tuto
rial D
jang
o
Ahora vemos que la columna ocupa todo el espacio dentro de la fila, adems el
men ha cambiado, como no hay espacio suficiente para mostrar todas las
opciones con claridad ahora aparece un desplegable a la derecha.
Con esto podemos dejar la lista de fotos de nuestro lbum de la siguiente forma.
{% extends 'base.html' %}
{% load static %}
{% block section %} Photo
42
Tuto
rial D
jang
o
{% endblock section %}
{% block maincontent %}
{% for p in object_list.all %}
{{ p.title }}
{% if p.comment %} {{ p.comment }}
{% else %} edit
comments {% endif %}
{% endfor %}
{% endblock maincontent %}
Donde tambin hemos usado Panels, uno de los componentes de Bootstrap.
43
Tuto
rial D
jang
o
Como habis podido ver, este framework nos est facilitando mucho las cosas y hay
que tener en cuenta que lo que hemos visto es una mnima parte de lo que nos
ofrece, os animo a que lo exploris a fondo.
Con esto podemos ver los cambios que tiene Git identificados respecto a la ltima
versin que tenemos en el repositorio local.
(tutorial)openwebinars@~/aplicaciones/myapps: git status En la rama master Your branch is up-to-date with 'origin/master'.
Cambios no preparados para el commit: (use git add ... para actualizar lo que se ejecutar) (use git checkout -- ... para descartar cambios en le directorio de trabajo)
modificado: album/templates/album/photo_list.html modificado: templates/base.html
no hay cambios agregados al commit (use git add o git commit -a) (tutorial)openwebinars@~/aplicaciones/myapps:
Los aadimos para el siguiente commit, hacemos commit y lo volcamos al
repositorio remoto.
(tutorial)openwebinars@~/aplicaciones/myapps: git add . (tutorial)openwebinars@~/aplicaciones/myapps: git commit -m "Bootstrap" [master 77dadc0] Bootstrap 2 files changed, 75 insertions(+), 44 deletions(-) rewrite album/templates/album/photo_list.html (79%) (tutorial)openwebinars@~/aplicaciones/myapps: git push -u origin master Username for 'https://github.com': usuario Password for 'https://[email protected]': Counting objects: 15, done. Delta compression using up to 2 threads. Compressing objects: 100% (6/6), done. Writing objects: 100% (8/8), 1.45 KiB | 0 bytes/s, done. Total 8 (delta 4), reused 0 (delta 0) To https://github.com/usuario/my_album.git e300e4a..77dadc0 master -> master Branch master set up to track remote branch master from origin. (tutorial)openwebinars@~/aplicaciones/myapps:
y podemos ver los cambios incluidos en el repositorio remoto.
44
Tuto
rial D
jang
o
Hasta aqu hemos dado una vuelta por lo ms bsico de Bootstrap y Git, pero queda
mucho para investigar y mejorar vuestro lbum experimentando.
En el siguiente post veremos cmo mejorarlo an ms usando el trabajo de la
comunidad Django.
45
Tuto
rial D
jang
o EnOpenWebinars.nettenemostodosestoscursosatuenteradisposicin
LinuxLPIC-1Examen101
LinuxLPIC-1Examen102
NodeJS,ExpressJSyMongoDB
AngularJSyTypeScript
AppsmvilesconPhoneGap
ServidoresVoIPconAsterisk
DesarrolloFrontend
Profesional
VirtualizacindeServidorescon
Promox
AppsMvilesconTitaniumAlloy
DesarrolloBackendcon
Django
Top Related