Lab 6: Búsqueda e Imágenes de Productos y Despliegue
Introducción
En este lab vamos a aprender cómo hacer que nuestros usuarios puedan buscar productos. También, veremos cómo anadir imágenes de los productos y mejoraremos un poco la aparencia general de la tienda. Finalmente, vamos preparar nuestra app para su despliegue (¡Sí!, esto significa que vas a poder entrar desde cualquier lado con tu link personal ).
¿Cómo te encuentro?
Una funcionalidad básica para cualquier ecommerce es permitir que sus usuarios puedan buscar los productos por nombre, categoría, entre otros. En este ejemplo vamos a implementar una caja de búsqueda por nombre. Para ello debemos realizar una comunicación entre cliente y servidor, como ya hemos visto anteriormente para esto se utilizan los formularios. Los formularios pueden realizar peticiones de POST o GET. Para simplificar las cosas, quedemos en que las peticiones POST implican envío de datos sensibles (normalmente privados) del cliente hacia el servidor con los cuales se realizarán modificaciones en la base de datos. Por otro lado las peticiones GET envian datos no sensibles mediante la URL hacia el servidor (Por ejemplo: https://pythonprogramming.net/search/?q=django), estos datos no modifican la base de datos.
Para agregar nuestra caja de búsqueda debemos adicionar el siguiente código en el archivo main/templates/base.html
debajo del título de la página:
...
<div class="content">
<form action="{% url 'product-list' %}" method="get">
<input class="input is-rounded" type="text" name="q" placeholder="Busca tu producto...">
</form>
</div>
...
Al escribir una palabra en la caja de búsqueda y apretar la tecla Enter estaremos enviando dicha palabra hacia la vista de lista de productos ProductListView
mediante el parámetro q
en la URL: http://localhost:8000/productos?q=drone. Ahora para utilizar este parámetro debemos personalizar el método get_queryset
de ProductoListView
, para ello vamos al archivo main/views.py
:
def get_queryset(self):
query = self.request.GET.get('q')
if query is not None:
object_list = Producto.objects.filter(nombre__icontains=query)
return object_list
else:
return Producto.objects.all()
¡Listo! Probemos nuestra cajita de búsqueda.
Con fotito pues …
Como nosotros sabemos que la belleza entra por los ojos vamos a agregar imágenes a nuestros productos para que nuestra página se vea mejor.
Trabajemos el back-end
Gracias a el módulo models
de Django
anadir un atributo de imagen es muy sencillo, solo debemos usar la clase models.ImageField
. ¿Y si quiero anadir más de 1 imagen? Fácil, creamos un modelo que tenga una relación de uno a muchos (Un producto - muchas imágenes de productos) utilizando el viejo conocido ForeignKey
del mismo módulo. Para ello en el archivo main/models.py
debemos colocar:
...
class ProductoImage(models.Model):
product = models.ForeignKey('Producto', on_delete=models.CASCADE, related_name='images')
image = models.ImageField(upload_to="products", null=True, blank=True)
...
Importante: Antes de aplicar los comandos de migraciones debemos instalar la librería Pillow - con el comando
pip install Pillow
- que es necesaria para el uso de las imágenes en Python.
Vemos un parámetro nuevo al usar ForeignKey
: related_name
. Este nombre (images
) que hemos colocado nos permitira acceder a todas las imágenes asociadas a un producto de manera sencilla: producto.images.all()
. Por otro lado en ImageField
estamos indicando el parámetro upload_to
, esto para que las imágenes sean almacenadas en una carpeta llamada products
.
Ya casi terminamos, para facilitar la subida de imágenes de cada producto vamos a colocar un Inline
en el admin de Producto
(al igual que hicimos con Profile
, Cliente
y Colaborador
). Debemos ir al archivo main/admin.py
y colocar:
from .models import *
...
class ProductoImageInline(admin.TabularInline):
model=ProductoImage
class ProductoAdmin(admin.ModelAdmin):
inlines = [
ProductoImageInline,
]
...
# admin.site.register(Producto) <-- Borra o comenta esta linea
admin.site.register(Producto, ProductoAdmin)
...
Finalmente para que nuestra aplicación pueda acceder al folder donde se almacenarán las imágenes debemos configurar las variables MEDIA_ROOT
y MEDIA_URL
en los archivos setttings.py
y urls.py
:
En settings.py
:
...
import os
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
En urls.py
:
...
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Nota: Es recomendable agregar la carpeta “media” en su archivo “.gitignore” para no subir imágenes al repositorio de GitHub.
Subamos imágenes de prueba mediante el admin, preferiblemente cuadradas, para probar nuestra implementación.
Ahora toca el front-end
En nuestro template para el detalle de producto vamos a mostrar todas las imágenes del producto. En el archivo producto_detail.html
debemos colocar el siguiente código debajo del título:
...
<div class="columns">
{% for image in object.images.all %}
<div class="column is-3">
<figure class="image is-square">
<a href="{{ image.image.url }}">
<img src="{{ image.image.url }}" alt="No hay imagen disponible">
</a>
</figure>
</div>
{% empty %}
<div class="column content">
<p>Aún no hay imágenes disponibles</p>
</div>
{% endfor %}
</div>
...
Ahora aprovechando que tenemos imágenes para cada producto cambiemos nuestras simples listas por unas tarjetas con imágenes para mostrar los productos disponibles en la tienda.
Para esto debemos ir al archivo producto_list.html
y reemplazar el código que se encuentra debajo del título y antes de la lista de links por lo siguiente:
<div class="columns is-multiline">
{% for producto in object_list %}
<div class="column is-4">
<div class="card">
<div class="card-image">
<figure class="image">
<img src="{{ producto.images.first.image.url|default:'https://via.placeholder.com/128' }}" alt="Imagen no disponible">
</figure>
</div>
<div class="card-content">
<h6>
<a href="{% url 'product-detail' producto.pk %}">
{{ producto.nombre }}
</a>
</h6>
<p>$ {{ producto.precio }}</p>
</div>
</div>
</div>
{% empty %}
<div class="content">
<h6>Aun no hay productos disponibles.</h6>
</div>
{% endfor %}
</div>
Gracias al framework de CSS, podemos utilizar un sistema de columnas (columns
) y tarjetas (cards
) para motrar nuestros productos de una manera más amigable con el usuario. Asimismo, usando el templatetag |default:
hemos podido indicar que si el producto no tiene ninguna imagen relacionada coloque una imagen placeholder desde un servidor externo, para evitar que las tarjetas se desencajen.
¿Y cómo se lo comparto a mi mamá?
Bueno, ya llevamos unas cuantas semanas desarrollando nuestra app, esta va tomando forma y hasta podria decirse que estamos orgullosos de haber construido algo desde 0, pero todo este tiempo hemos accedido a nuestra app mediante un servidor local (localhost:8000). Esto significa que si queremos compartir con alguien nuestra web no podemos hacerlo. Para solucionar este problema debemos desplegar nuestra web de manera pública. Existen muchas maneras de hacerlo, pero en esta guía utilizaremos Heroku, que es un PaaS (Platform as a Service) que abstrae la complejidad de construir un servidor web desde 0 y nos permite desplegar nuestra web app con unos cuantos ajustes a nuestro código.
Preparando archivos de configuracion de Heroku
Un poco de magia
Existen dos librerías que parece que hacen magia: Gunicorn, que se encarga del servidor web, y Django-Heroku, que se encarga de configurar la base de datos y otras variables necesarias para desplegar nuestra aplicación Django en Heroku. Para instalar Gunicorn ejecutemos el siguiente comando:
pip install gunicorn
Para que Django-Heroku haga su magia, al final del archivo setting.py
debemos agregar el siguiente código:
...
# Django Heroku
if 'ON_HEROKU' in os.environ:
import django_heroku
django_heroku.settings(locals())
El condicional if
nos servirá para identificar si estamos corriendo nuestro código desde Heroku y en caso sí estemos en Heroku ejecuta las líneas necesarias. Para que esto funcione debemos ir desde nuestra aplicacion de Heroku a Settings / Config Vars / Reveal Config Vars
y en el recuadro donde dice ‘KEY’ pondremos ON_HEROKU
, en ‘VALUE’ pondremos 1
, y finalmente damos click en Add
para guardar la variable de ambiente.
Como si fuera la primera vez: Heroku no tiene memoria
Heroku administra una cantidad enorme de servidores y por ello usa algunos trucos extraños para sacarle el mayor provecho. Por este motivo cada app de Heroku no tiene un sistema de archivo persistente como el de nuestras computadoras, esto significa que las imágenes que subamos de nuestros productos debemos almacenarlas fuera de esta plataforma. Una forma sencilla de hacerlo es utilizando un almacenamiento cloud como Dropbox mediante la libreria de Python django-storages
.
Primero debemos instalar django-storages
y dropbox
con el comando:
pip install django-storages dropbox
Luego debemos anadirla a nuestra app, en el archivo settings.py
agregamos 'storages'
a la lista INSTALLED_APPS
:
...
INSTALLED_APPS = [
'storages',
...
]
...
En el mismo archivo debemos colocar la configuración de Dropbox:
if 'ON_HEROKU' in os.environ:
...
# Django Storages
DEFAULT_FILE_STORAGE = 'storages.backends.dropbox.DropBoxStorage'
DROPBOX_OAUTH2_TOKEN = '[AQUÍ VA SU TOKEN]'
¿Como obtengo mi token? Para obtener su token deben crear una cuenta en Dropbox, iniciar sesión, ir a la página de desarrolladores y crear una aplicación (Scoped access > App folder > [Nombre de tu app]). Una vez creada, debes ir a la pestaña Permissions y habilitar files.content.write y files.content.read. Finalmente, en la pestana Settings en la sección OAuth 2, Elige la opción No token expiration y haz click en el botón Generate para obtener tu token de acceso.
Dependencias
Heroku nos pide que le indiquemos qué librerias necesita nuestra aplicación para funcionar en un archivo llamado requirements.txt
ubicado en la carpeta raíz de nuestro proyecto. Este archivo lo podemos generar automáticamente con los siguientes comandos:
pip freeze > requirements.txt
echo "django-heroku" >> requirements.txt
El primero crea automáticamente el archivo requirements.txt
con casi todas las librerias necesarias. El segundo agregar la librería django-heroku
al final de este archivo para que sea instalada sólo en los servidores de Heroku. Igualmente el archivo requirements.txt
lo puedes encontrar en en este link
Nota: Si dentro del archivo
requirements.txt
encuentras una línea que diga ` pkg-resources==0.0.0` elimínala para evitar tener problemas al desplegar nuestra aplicación.
Definimos los comandos de despligue
En la carpeta raíz de nuestro proyecto debemos crear el archivo Procfile
y colocar el siguiente codigo:
release: python manage.py migrate
web: gunicorn [NOMBRE DE TU CARPETA RAIZ DEL PROYECTO].wsgi
El primer comando se asegurará que las migraciones realizadas (makemigrations) sean aplicadas en la base de datos de la nube que nos brinda Heroku. El segundo comando utiliza Gunicorn para leer nuestra aplicación e iniciar un servidor web.
Subimos nuestro código a GitHub
Para subir nuestro código a GitHub mediante la terminal ejecutamos los siguientes comandos:
git fetch origin master
git pull origin master
git add .
git commit -m 'heroku ready commit'
git push origin master
Enlazamos la app con la repo
Una vez creada nuestra cuenta en Heroku vamos al dashboard y creamos una nueva aplicación, estando en la pestaña Deploy, seleccionamos el método de despliegue GitHub, buscamos nuestro repositorio y lo elegimos. Finalmente, en la sección de despliegue manual debemos hacer click en el botón Deploy Branch, esperamos que acabe el proceso.
Finalmente debemos ingresar a la consola de nuestra app de Heroku, desde More en la parte superior derecha Run console y ejecutar dos comandos. Primero para poblar nuestra base de datos en la nube:
python manage.py loaddata linioData.json
Segundo, para crearnos un usuario de administrador:
python manage.py createsuperuser
De preferencia llama a tu usuario “admin” y de contraseña usa “root”, ignora si te dicen que no es una pasword segura.
¡Listo! Podemos darle click a view app para ver nuestra app en un link personalizado que compartir con quien quieras, este link debe ser similar a https://{nombre-app}.herokuapp.com.
No te preocupes si no funciona a la primera, siempre hay algunas complicaciones pero revisa minuciosamente todo el procedimiento y lo lograrás.
Siguiente Lab
-
Se evaluará la presentación del proyecto funcionando hasta la busqueda de productos y mostrar imagenes (si el grupo tiene más avances son bienvenidos).
-
Evaluación de pre-presentación final.