intro_Django/TiendaOnline/README.md

934 lines
22 KiB
Markdown
Raw Normal View History

2020-11-16 21:26:55 -03:00
**Ir a:**
[*Repositorio*](https://gitea.kickto.net/jp.av.dev/intro_Django#user-content-django-wiki),
[*Wiki*](https://gitea.kickto.net/jp.av.dev/intro_Django/wiki/_pages)
2020-11-16 21:21:10 -03:00
-------
## Proyecto y Aplicacion(es)
### Modularización
*ej. Proyecto1*
```
__________________________________
| Proyecto Tienda Online |
| |
| App1 App2 |
| [Panel Control] [Stock] |
| |
| App3 App4 |
| [Ventas] [Pagos] |
| |
| App5 App6 |
| [Envíos] [Promos] |
|__________________________________|
```
*ej. Proyecto2*
```
__________________________________
| Proy. Gestión Almacen |
| |
| App1 App2 |
| [Proveedores] [Stock] |
| |
| App3 App4 |
| [Ventas] [Pagos] |
| |
| App5 App6 |
| [Envíos] [Promos] |
|__________________________________|
```
--------
**Recordar activar entorno virtual**
*source /home/sat/weblocal/.django-env/bin/activate
Creación de proyecto : ` django-admin startproject TiendaOnline`
Cambiar a carpeta de proy.: ` cd /TiendaOnline`
Ajustar TZ en settings.py : ` TIME_ZONE = 'America/Santiago'`
Creación primera app : ` python3 manage.py startapp gestionPedidos`
*/TiendaOnline/gestionPedidos/*
```
- migrations/
- admin.py
- apps.py
- __init__.py
- models.py
- tests.py
- views.py
```
### SQLite3 (default)
*/gestionPedidos/models.py*
```
from django.db import models
class Clientes(models.Model):
nombre = models.CharField(max_length=30)
direccion = models.CharField(max_length=50)
email = models.EmailField()
fono = models.CharField(max_length=10)
class Articulos(models.Model):
nombre = models.CharField(max_length=30)
seccion = models.CharField(max_length=20)
precio = models.IntegerField()
class Pedidos (models.Model):
numero = models.IntegerField()
fecha = models.DateField()
entregado = models.BooleanField()
```
*settings.py*
```
...
INSTALLED_APPS = [
...
'gestionPedidos',
]
...
```
### Chequear código :
```
python3 manage.py check gestionPedidos
```
### Creación del modelo Base de Datos :
```
python3 manage.py makemigrations
# Ver instrucciones SQL
python3 manage.py slqmigrate gestionPedidos 0001
```
### Creación de BD:
```
python3 manage.py migrate
```
🔸️ ***acceder al 'shell de django':*** `python3 manage.py shell`
### Insertar Registros
*(InteractiveConsole)*
```
>>> from gestionPedidos.models import Articulos
>>>
>>> art = Articulos(nombre='mesa', seccion='decoracion', precio=96)
>>> art.save()
>>>
>>> art2 = Articulos(nombre='camisa', seccion='vestuario', precio=25)
>>> art2.save()
>>>
>>> art3 = Articulos.objects.create(nombre='taladro', seccion='ferreteria', precio=65)
>>>
```
### Actualizar Registros
```
>>> art.precio=99
>>> art.save()
>>>
```
### Borrar Registros
```
>>> art5 = Articulos.objects.get(id=2)
>>> art5.delete()
(1, {'gestionPedidos.Articulos': 1})
>>>
```
### Select
```
>>> ResConsulta = Articulos.objects.all()
>>> ResConsulta
<QuerySet [<Articulos: Articulos object (1)>, <Articulos: Articulos object (3)>]>
>>>
>>> # Ver instrucción SQL:
>>> ResConsulta.query.__str__()
'SELECT "gestionPedidos_articulos"."id", "gestionPedidos_articulos"."nombre", "gestionPedidos_articulos"."seccion", "gestionPedidos_articulos"."precio" FROM "gestionPedidos_articulos"'
>>>
```
-------
## PostgreSQL
### Install
```
sudo apt install postgresql postgresql-contrib libpq-dev
Success. You can now start the database server using:
pg_ctlcluster 11 main start
# Cambiar a user postgres
sudo -i -u postgres
# sql cli
postgres@sat:~$ psql
# salir
postgres=# \q
# Crear usuario *ambiente de desarrollo
postgres@sat:~$ createuser -P --interactive
Enter name of role to add: django
Shall the new role be a superuser? (y/n) y
# Crear BD con nombre de usuario
(postgres automaticamente se conecta a un DB con el mismo nombre de usuario)
postgres@sat:~$ createdb django
# Crear password
postgres@ratsat:~$ psql
psql (11.9 (Raspbian 11.9-0+deb10u1))
Type "help" for help.
postgres=# \password django
postgres=# \q
postgres@ratsat:~$ psql -U django -W
```
### Problemas con Log-in ?
```
sudo vim /etc/postgresql/11/main/pg_hba.conf
# CAMBIAR
# Database administrative login by Unix domain socket
local all postgres peer
por
# Database administrative login by Unix domain socket
local all postgres md5
# "local" is for Unix domain socket connections only
local all all md5
# Reiniciar servicio
sudo service postgresql restart
# Para borrar usuario
DROP OWNED BY your_user;
DROP USER your_user;
# Modificar
vim /etc/postgresql/10/main/postgresql.conf
...
listen_addresses = 'localhost,your-server-ip'
...
sudo vim /etc/postgresql/11/main/postgresql.conf
# IPv4 local connections:
host all all 192.168.0.0/24 md5
# Regla Firewall
sudo ufw allow proto tcp from 192.168.0.0/24 to any port 5432
```
Usar algun DBbrowser y crear bd ArticulosClientes
### Instalar Python-Django PostgreSQL connector
```
pip3 install psycopg2
vim settings.py
# Modificar
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
ej.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'articulosclientes',
'USER': 'nombre_usuario',
'PASSWORD': 'clave_usuario',
'HOST': '127.0.0.1',
'DATABASE_PORT': '5432',
}
}
# Check
python3 manage.py check
# Hacer migraciones
python3 manage.py makemigrations
# Migrar
python3 manage.py migrate
```
### Crear un registro
```
python3 manage.py shell
(InteractiveConsole)
>>> from gestionPedidos.models import Clientes
>>>
>>> cli = Clientes(nombre='Pedro', direccion='dnd vive', email='pe@mail.ru', fono=123456789)
>>> cli.save()
>>>
```
Valores en tabla articulos
|id|nombre|seccion|precio|
|--|------|-------|------|
|1|mesa|deco|90|
|2|lámpara|deco|50|
|3|pantalón|vestuario|45|
|4|destornillador|ferreteria|5|
|5|balón|deporte|25|
|6|raqueta|deporte|105|
|7|muñeca|juguetes|15|
|8|tren eléctrico|juguetes|50|
### Intruccion SQL desde Django
```
# Select * ... where seccion='deporte';
>>> from gestionPedidos.models import Articulos
>>>
>>> Articulos.objects.filter(seccion='deporte')
<QuerySet [<Articulos: Articulos object (5)>, <Articulos: Articulos object (6)>]>
>>>
```
*/TiendaOnline/gestionPedidos/models.py*
```
class Articulos(models.Model):
nombre = models.CharField(max_length=30)
seccion = models.CharField(max_length=20)
precio = models.IntegerField()
def __str__(self):
return 'Nombre: %s, Depto. %s, Precio $ %s' % (self.nombre, self.seccion, self.precio)
```
```
python3 manage.py makemigrations
python3 manage.py migrate
python3 manage.py shell
>>> from gestionPedidos.models import Articulos
>>>
>>> Articulos.objects.filter(seccion='deporte')
>>> <QuerySet [<Articulos: Nombre: balón, Depto. deporte, Precio $ 25>,
<Articulos: Nombre: raqueta, Depto. deporte, Precio $ 105>]>
>>>
# Select * ... where nombre='mesa' seccion='deco';
>>> Articulos.objects.filter(nombre='mesa', seccion='deco')
<QuerySet [<Articulo: Nombre: mesa, Depto. deco, Precio $ 90>]>
>>>
# Para buscar por ej. un elemento con precio mayor a 100 desde la shell django
>>> Articulos.objects.filter(precio__gte=100)
<QuerySet [<Articulos: Articulo: raqueta, Depto. deporte, Precio $ 105>]>
otros: __tle, __range, ...).order_by(precio o -precio para inverso)
>>> Articulos.objects.filter(precio__range=(40,90))
>>>
<QuerySet [<Articulos: Nombre: mesa, Depto. deco, Precio $ 90>, <Articulos: Nombre: lámpara, Depto. deco, Precio $ 50>, <Articulos: Nombre: pantalón, Depto. vestuario, Precio $ 45>, <Articulos: Nombre: tren eléctrico, Depto. juguetes, Precio $ 50>]>
>>>
>>> Articulos.objects.filter(precio__gte=50).order_by('precio')
<QuerySet [<Articulos: Articulo: lámpara, Depto. deco, Precio $ 50>, <Articulos: Articulo: tren eléctrico, Depto. juguetes, Precio $ 50>, <Articulos: Articulo: mesa, Depto. deco, Precio $ 90>, <Articulos: Articulo: raqueta, Depto. deporte, Precio $ 105>]>
```
2020-11-16 21:26:55 -03:00
-------
2020-11-16 21:21:10 -03:00
## Panel de Administrador
### Activo por defecto
*settings.py*
```
...
INSTALLED_APPS = [
'django.contrib.admin',
...
```
*urls.py*
```
...
urlpatterns = [
path('admin/', admin.site.urls),
...
```
[*enlace a admin*](http://192.168.0.4:8000/admin/)
### Crear SuperUsuario, con perfíl de administrador
```
python3 manage.py createsuperuser
Username (leave blank to use 'sat'): Roberrrt
Email address: admin@gitea.com
Password:
Password (again):
Superuser created successfully.
```
### Explorando la BD
*tabla auth_user*
|id|password|last_login|is_superuser|username|first_name|last_name|email|is_staff|is_active|date_joined|
|--|--------|----------|------------|--------|----------|---------|-----|--------|---------|-----------|
|1|pbkdf2_sha256$216000$zw4Zuc6weyCN$Vwn8SM6zPA3hIofcmQiz4mIuU6tL7U/vvWs=|2020-11-13 02:40:20|true|Roberrrt|||admin@gitea.com|true|true|2020-11-13 02:38:41|
|2|pbkdf2_sha256$216000$188Gs3iRpsWb$X49lPG/2IlRlsaanXqUBZ6YOMIvctZ+U0Gg=||false|Roberrrt2||||false|true|2020-11-13 02:41:43|
### Administrar tablas desde el Panel
*admin.py*
```
from django.contrib import admin
from gestionPedidos.models import Clientes
admin.site.register(Clientes)
```
### Configurar campo 'email' como opcional, clase Cliente
*models.py*
```
...
class Clientes(models.Model):
...
email = models.EmailField(blank=True, null=True)
...
```
*cambios en el modelo, requieren migración*
`python3 manage.py makemigrations`
`python3 manage.py migrate`
### Personalización del Panel
[*Admin. Clientes*](http://192.168.0.4:8000/admin/gestionPedidos/clientes/add/)
Django por defecto presenta los nombres de los campos capitalizados y elimina el texto posterior a '_' ( ej. modelo nombre_clientes ).
Columnas en el Panel de admin.
```
Add clientes
Nombre:
Direccion:
Email:
Fono:
```
### Modificar nombre visible de tablas
Personalizar nombre a mostrar en panel, en el Modelo.
*models.py*
```
class Clientes(models.Model):
...
direccion = models.CharField(max_length=50, verbose_name="La Direcc.:")
...
```
Vista Panel
```
Add clientes
Nombre:
La Direcc.:
Email:
Fono:
```
### Ver otros campos de tablas a modificar en panel
*gestionPedidos/admin.py*
```
from django.contrib import admin
from gestionPedidos.models import Clientes, Articulos, Pedidos
class ClientesAdm(admin.ModelAdmin):
list_display("nombre", "direccion", "fono")
admin.site.register(Clientes, ClientesAdmin)
```
*vista en Panel*
|Nombre|La Direcc.|Fono|
|------------|----------------|--------------|
|Elejendre|calle 16|2445234234|
|Zerafín|dnd vive|123456789|
|Pedro|ruta 2|9873456789|
### Agregar campo de busqueda
*gestionPedidos/admin.py*
```
class ClientesAdmin(admin.ModelAdmin):
list_display = ("nombre", "direccion", "fono")
# Campos de busqueda en la barra
search_fields = ("nombre","fono")
```
### Agregar Filtros
*admin.py*
```
class ArticulosAdmin(admin.ModelAdmin):
list_filter = ("seccion",)
```
*vista filtro en panel*
|Filter by Secion|
|----------------|
|All|
|deco|
|deporte|
|ferreteria|
|jugetes|
|vestuario|
**Tambien se puede filtar por fecha**
*admin.py*
```
class PedidosAdmin(admin.ModelAdmin):
list_display = ("numero", "fecha")
list_filter = ("fecha",)
admin.site.register(Pedidos, PedidosAdmin)
```
*vista filtro en panel*
|Filter by Fecha|
|----------------|
|Any date|
|Today|
|Past 7 days|
|This month|
|This Year|
**Filtro de disposicion horizontal, estilo menú**
*admin.py*
```
class PedidosAdmin(admin.ModelAdmin):
list_display = ("numero", "fecha")
list_filter = ("fecha",)
# Filtro-barra*
date_hierarchy = "fecha"
```
**Vista del Panel de Administrador**
![img](https://gitea.kickto.net/jp.av.dev/intro_Django/raw/branch/master/TiendaOnline/gestionPedidos/img/admin-filtro-fecha.png)
### Cambiar Idioma
*settings.py*
```
#LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'es-CL'
```
- [LANGUAGE_CODE](http://www.i18nguy.com/unicode/language-identifiers.html)
- [Django-doc](https://docs.djangoproject.com/en/3.1/ref/contrib/admin/)
### Agregar usuarios, perfiles
Usuarios STAFF, puede administrar el sitio desde el panel, se puede limitar.
Usuarios ACTIVO, puede entrar en partes del sitio que requieran autenticación.
### Agregar grupos
Permite crear Grupos, con permisos específicos.
Se pueden agregar permisos extra pro usuario.
2020-11-16 21:26:55 -03:00
-------
2020-11-16 21:21:10 -03:00
2020-11-16 21:26:55 -03:00
2020-11-16 21:21:10 -03:00
## Formularios
[*Forms doc*](https://docs.djangoproject.com/en/3.1/topics/forms/)
### Creacion del formulario
*/TiendaOnline/gestionPedidos/templates/busqueda_prods.html*
```
<html>
<head>
<title>Búsqueda de productos</title>
</head>
<body>
<form action="/buscar/" method="GET">
<input type="text" name="prod">
<input type="submit" value="Buscar">
</form>
</body>
</html>
```
### Creación vista formulario
*gestionPedidos/views.py*
```
from django.shortcuts import render
# Create your views here.
def busqueda_productos(request):
return render(request, "busqueda_prods.html")
```
### Restistrar url *(path)*
*urls.py*
```
...
from gestionPedidos import views
urlpatterns = [
....
path('buscar_productos/', views.busqueda_productos),
....
```
### Crear vista para el *submit* 'buscar'
*gestionPedidos/views.py*
```
...
from django.http import HttpResponse
...
def buscar(request):
msj = "Estas búscando por: %r" %request.GET["prod"]
return HttpResponse(msj)
```
### Registrar url
*urls.py*
```
...
urlpatterns = [
...
path('buscar/', views.buscar),
]
```
***Metodo***
*GET http://192.168.0.4:8000/buscar/?prod=alicate*
### Busqueda en BBDD
*views.py*
```
...
from gestionPedidos.models import Articulos
...
def buscar(request):
# Validación campo vacio
if request.GET["prod"]:
#msj = "Estas búscando por: %r" %request.GET["prod"]
prod_buscar = request.GET["prod"]
articulos = Articulos.objects.filter(nombre__icontains=prod_buscar)
return render(request, "resultado_busqueda.html", {"articulos":articulos, "query":prod_buscar})
else:
msj = "Debes introducir un termino de búsqueda"
return HttpResponse(msj)
```
**__icontains** *similar a like SQL, busca en el campo indicado,
articulos que CONTENGAN la palabra a buscar.*
*resultado_busqueda.html*
```
<body>
<p>Estás buscando <strong>{{query}}</strong></p>
{% if articulos %}
<p>Encontrados : {{articulos|length}} artículos</p>
<ul>
{% for articulo in articulos %}
<li>{{articulo.nombre}} &nbsp; {{articulo.seccion}} &nbsp;${{articulo.precio}}</li>
{% endfor %}
</ul>
{% else %}
<p>Artículo no encontrado</p>
{% endif %}
</body>
```
### Limitar cantidad de caracteres en busqueda
*views.py*
```
...
def buscar(request):
# Validación campo vacio
if request.GET["prod"]:
prod_buscar = request.GET["prod"]
if len(prod_buscar) > 20:
msj = "Termino de búsqueda demasiado largo"
else:
articulos = Articulos.objects.filter(nombre__icontains=prod_buscar)
return render(request, "resultado_busqueda.html", {"articulos":articulos, "query":prod_buscar})
...
```
### Formulario de contacto
*views.py*
*contacto.html*
```
<body>
<h1>Formulario de contacto</h1>
<form action="/contacto/" method="POST">
{% csrf_token %}
<p>Asunto: <input type="text" name="asunto"></p>
<p>Mail : <input type="text" name="mail"></p>
<p>Mensaje: </p>
<p><textarea name="mensaje" rows="15" cols="45"></textarea></p>
<input type="submit" value="Enviar">
</form>
</body>
```
***{% csrf_token %}*** Protección contra [**CSRF**](https://docs.djangoproject.com/en/3.0/ref/csrf/)
*This should not be done for POST forms that target external URLs, since that would cause the CSRF token to be leaked, leading to a vulnerability.*
*gracias.html*
```
...
<h1>Gracias por contactarnos</h1>
...
```
*views.py*
```
...
def contacto(request):
if request.method == "POST":
return render(request, "gracias.html")
return render(request, "contacto.html")
```
*urlpatterns urls.py*
```
...
path('contacto/', views.contacto),`
...
```
2020-11-16 21:26:55 -03:00
-------
2020-11-16 21:21:10 -03:00
## Envio de Mails
### Libreria core.mail
*settings.py*
```
EMAIL_BACKEND = 'django.core.imail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST_USER = 'ejemplo@gmail.com'
EMAIL_HOST_PASSWORD = 'clave-ejemplo'
```
[settings-doc](https://docs.djangoproject.com/en/dev/ref/settings/#email-host)
[django.core.mail-doc](https://docs.djangoproject.com/en/3.0/topics/email/#module-django.core.mail)
🔸️***python3 manage.py shell***
```
(InteractiveConsole)
>>> from django.core.mail import send_mail
>>>
>>> send_mail('Test Django Admin',
'Mensaje desde la consola de django',
'webmaster@django.net',
['destinatario@mail.com'],
fail_silently = False,
)
```
### Envio de mail desde formulario de contacto
*views.py*
```
...
from django.core.mail import send_mail
from django.conf import settings
...
def contacto(request):
if request.method == "POST":
subject = request.POST['asunto']
message = request.POST['mensaje']+' '+request.POST['email']
email_from = settings.EMAIL_HOST_USER
recipient_list = ['ratablastard@gmail.com']
send_mail(subject, message, email_from, recipient_list)
return render(request, "gracias.html")
return render(request, "contacto.html")
```
2020-11-16 21:26:55 -03:00
-------
2020-11-16 21:21:10 -03:00
## API Forms
### Creación de formularios con API Forms
Simplifica la creación de formularios e incluye validacion.
[*API Forms doc*](https://docs.djangoproject.com/en/3.1/ref/forms/api/)
[*Indice Forms Api doc*](https://docs.djangoproject.com/en/3.1/ref/forms/#forms)
Crear */TiendaOnline/gestionPedidos/forms.py*
```
from django import forms
class FormContacto(forms.Form):
asunto = forms.CharField(max_length=35, required=False, initial='Contacto')
email = forms.EmailField(max_length=35)
msj = forms.CharField(widget=forms.Textarea(attrs={'rows': 5, 'cols': 26}))
```
🔸️*python3 manage.py shell*
```
(InteractiveConsole)
>>> from gestionPedidos.forms import FormContacto
>>>
>>> miForm = FormContacto()
>>>
>>> print(miForm)
<tr><th><label for="id_asunto">Asunto:</label></th><td><input type="text" name="asunto" required id="id_asunto"></td></tr>
<tr><th><label for="id_email">Email:</label></th><td><input type="email" name="email" required id="id_email"></td></tr>
<tr><th><label for="id_msj">Msj:</label></th><td><input type="text" name="msj" required id="id_msj"></td></tr>
>>>
```
El formulario esta formateado como tabla.
Crea las etiquetas 'label', les da un nombre.
Crea los asuntos e inputs.
Por defecto estos son requeridos.
**Cambiando el formato del formulario**
ej. como ***parrafo***
```
>>> print(miForm.as_p())
<p><label for="id_asunto">Asunto:</label> <input type="text" name="asunto" required id="id_asunto"></p>
<p><label for="id_email">Email:</label> <input type="email" name="email" required id="id_email"></p>
<p><label for="id_msj">Msj:</label> <input type="text" name="msj" required id="id_msj"></p>
>>>
```
ej. como ***lista*** (unsorted list)
```
>>> print(miForm.as_ul())
<li><label for="id_asunto">Asunto:</label> <input type="text" name="asunto" required id="id_asunto"></li>
<li><label for="id_email">Email:</label> <input type="email" name="email" required id="id_email"></li>
<li><label for="id_msj">Msj:</label> <input type="text" name="msj" required id="id_msj"></li>
>>>
```
**Probando el formulario** ***is_valid() y cleaned_data***
```
>>> miForm = FormContacto({'asunto':'prueba', 'email':'test@mail.com', 'msj':'mensaje de prueba'})
>>>
>>> miForm.is_valid()
True
>>>
>>> miForm.cleaned_data
{'asunto': 'prueba', 'email': 'test@mail.com', 'msj': 'mensaje de prueba'}
>>>
```
**Campo email invalido**
```
>>> miForm = FormContacto({'asunto':'prueba', 'email':'test@mailcom', 'msj':'mensaje de prueba'})
>>>
>>> miForm.is_valid()
False
>>>
>>> miForm.cleaned_data
{'asunto': 'prueba', 'msj': 'mensaje de prueba'}
>>>
```
### Cambiando views.py para usar el Api Forms
```
...
from gestionPedidos.forms import FormContacto
...
def contacto(request):
if request.method == "POST":
miForm = FormContacto(request.POST)
if miForm.is_valid():
contenido = miForm.cleaned_data
send_mail(contenido['asunto'], contenido['msj'],
contenido.get('email',''),['webmasterd@test.com'],)
return render(request, 'gracias.html')
else:
miForm = FormContacto()
return render(request, 'form_contacto.html', {"form":miForm})
```
*templates/form_contacto.html*
```
...
<body>
<h1>Formulario de contacto</h1>
{% if forms.errors %}
<p style="color:red;"> Por favor revisa este campo</p>
{% endif %}
<form action="" method="POST">{% csrf_token %}
<table>
{{ form.as_table}}
</table>
<input type="submit" value="Enviar">
</form>
</body>
...
```
2020-11-16 21:26:55 -03:00
-------
**Ir a:**
[*Repositorio*](https://gitea.kickto.net/jp.av.dev/intro_Django#user-content-django-wiki),
[*Wiki*](https://gitea.kickto.net/jp.av.dev/intro_Django/wiki/_pages)