Aula 61 – Loja Online – App Address
Aula 61 – Loja Online – App Address
Voltar para página principal do blog
Todas as aulas desse curso
Aula 60 Aula 62
Se gostarem do conteúdo dêem um joinha 👍 na página do Código Fluente no
Facebook
Esse é o link do código fluente no Pinterest
Meus links de afiliados:
Hostinger
Digital Ocean
One.com
Melhore seu NETWORKING
https://digitalinnovation.one/
Participe de comunidades de desenvolvedores:
Fiquem a vontade para me adicionar ao linkedin.
E também para me seguir no https://github.com/toticavalcanti.
Código final da aula:
https://github.com/toticavalcanti
Quer aprender python3 de graça e com certificado? Acesse então:
https://workover.com.br/python-codigo-fluente
Toti:
https://www.youtube.com/channel/UCUEtjLuDpcOvR3mIUr-viOA
Backing track / Play-along:
https://www.youtube.com/channel/UCT3TryVMqTqYBjf5g5WAHfA
Código Fluente
https://www.youtube.com/channel/UCgn-O-88XBAwdG9gUWkkb0w
Putz!
https://www.youtube.com/channel/UCZXop2-CECwyFYmHbhnAkAw
Aula 61 – Loja Online – App Address
Vamos criar mais um componente da loja, é o componente endereço.
Lembrando que um componente em Django é chamado de app.
Então vamos criar o app chamado addresses, que vai representar o endereço do usuário, e vamos associar ao perfil de pagamento (billing profile) do usuário.
Dentro da pasta onde está o manage.py, vamos rodar:
python manage.py startapp addresses
O Address poderia ficar no model do BillingProfile, ou no do Order e até mesmo no Account.
Já o BillingProfile provavelmente não poderia viver no Addresses e nem no Orders.
Criamos o Address para deixar o código mais atômico, fazendo com que cada parte do código seja especialista em fazer algo bem.
Além do que, facilita na hora de fazer um CRUD (Create, Read, Update, Delete) no addresses.
Por isso, melhor que o addresses seja realmente um app.
Então, no model.py do addresses vamos colocar o seguinte conteúdo.
django_ecommerce/e_commerce/addresses/models.py
from django.db import models
from billing.models import BillingProfile
ADDRESS_TYPES = (
('billing', 'Billing'),
('shipping', 'Shipping'),
)
class Address(models.Model):
billing_profile = models.ForeignKey(BillingProfile, on_delete=models.CASCADE, null = True, blank = True)
address_type = models.CharField(max_length = 120, choices = ADDRESS_TYPES)
address_line_1 = models.CharField(max_length = 120)
address_line_2 = models.CharField(max_length = 120, null = True, blank = True)
city = models.CharField(max_length = 120)
country = models.CharField(max_length = 120, default = 'Brazil')
state = models.CharField(max_length = 120)
postal_code = models.CharField(max_length = 120)
def __str__(self):
return str(self.billing_profile)
Criamos a classe Address, extendendo o models do django, e seus campos.
Definimos a tupla ADDRESS_TYPES com as opções para endereço de cobrança e endereço de entrega.
E por último o método __str__() da classe.
Registrando o app address no admin do projeto
django_ecommerce/e_commerce/addresses/admin.py
from django.contrib import admin
from .models import Address
admin.site.register(Address)
Registrando o app address no settings.py do projeto
django_ecommerce/e_commerce/e_commerce/settings.py
"""
Django settings for e_commerce project.
Generated by 'django-admin startproject' using Django 2.1.4.
For more information on this file, see
https://docs.djangoproject.com/en/2.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.1/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'xjmv-0^l__duq4-xp54m94bsf02lx4&1xka_ykd_(7(5#9^1o^'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
#our apps
'addresses'
'billing',
'accounts',
'carts',
'orders',
'products',
'search',
'tags',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
LOGOUT_REDIRECT_URL = '/login/'
ROOT_URLCONF = 'e_commerce.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'e_commerce.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static_local")
]
STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static_cdn", "static_root")
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static_cdn", "media_root")
Migrações
Vamos fazer as migrações para repercutir o address no banco de dados.
python manage.py makemigrations
Agora o migrate.
python manage.py migrate
Criando o Form do Addresses
Crie o arquivo forms.py do addresses.
django_ecommerce/e_commerce/addresses/forms.py
from django import forms
from .models import Address
class AddressForm(forms.ModelForm):
class Meta:
model = Address
fields = [
#'billing_profile',
#'address_type',
'address_line_1',
'address_line_2',
'city',
'country',
'state',
'postal_code'
]
Criamos uma classe para o formulário do endereço e uma classe interna com os metadados.
A classe AddressForm importa o modelo Address e o usa no atributo model dentro classe interna Meta.
Além do model na classe Meta, temos também uma lista com os campos que existem na classe AddressForm.
Agora no views.py do carts
django_ecommerce/e_commerce/carts/views.py
from django.shortcuts import render, redirect
from accounts.forms import LoginForm, GuestForm
from accounts.models import GuestEmail
from addresses.forms import AddressForm
from billing.models import BillingProfile
from orders.models import Order
from products.models import Product
from .models import Cart
def cart_home(request):
cart_obj, new_obj = Cart.objects.new_or_get(request)
return render(request, "carts/home.html", {"cart": cart_obj})
def cart_update(request):
print(request.POST)
product_id = request.POST.get('product_id')
if product_id is not None:
try:
product_obj = Product.objects.get(id = product_id)
except Product.DoesNotExist:
print("Mostrar mensagem ao usuário, esse produto acabou!")
return redirect("cart:home")
cart_obj, new_obj = Cart.objects.new_or_get(request)
if product_obj in cart_obj.products.all():
cart_obj.products.remove(product_obj)
else:
cart_obj.products.add(product_obj)
request.session['cart_items'] = cart_obj.products.count()
return redirect("cart:home")
def checkout_home(request):
#aqui a gente pega o carrinho
cart_obj, cart_created= Cart.objects.new_or_get(request)
order_obj = None
#se o carrinho acabou de ser criado, ele tá zerado
#ou se o carrinho já existir mas não tiver nada dentro
if cart_created or cart_obj.products.count() == 0:
return redirect("cart:home")
login_form = LoginForm()
guest_form = GuestForm()
address_form = AddressForm()
billing_profile, billing_profile_created = BillingProfile.objects.new_or_get(request)
if billing_profile is not None:
order_obj, order_obj_created = Order.objects.new_or_get(billing_profile, cart_obj)
context = {
"object": order_obj,
"billing_profile": billing_profile,
"login_form": login_form,
"guest_form": guest_form,
"address_form": address_form,
}
return render(request, "carts/checkout.html", context)
Importamos o AddressForm no views do carts e criamos uma instância.
checkout.html do carts
Agora vamos alterar o checkout.html do carts.
django_ecommerce/e_commerce/carts/templates/carts/checkout.html
{% extends "base.html" %}
{% block content %}
{{ object.order_id }} -- {{ object.cart }}
{% if not billing_profile %}
<div class='row text-center'>
<div class='col-12 col-md-6'>
<p class='lead'>Login</p>
{% include 'accounts/snippets/form.html' with form=login_form next_url=request.build_absolute_uri %}
</div>
<div class='col-12 col-md-6'>
<p class='lead'>Continuar como Convidado</p>
{% url "guest_register" as guest_register_url %}
{% include 'accounts/snippets/form.html' with form=guest_form
next_url=request.build_absolute_uri action_url=guest_register_url %}
</div>
</div>
{% else %}
{% if not object.shipping_address %}
<div class='row'>
<div class='col-md-6 mx-auto col-10'>
<p class='lead'>Shipping Address</p>
<hr/>
{% url "checkout_address_create" as checkout_address_create %}
{% include 'addresses/form.html' with form=address_form
next_url=request.build_absolute_uri action_url=checkout_address_create address_type='shipping' %}
</div>
</div>
{% elif not object.billing_address %}
<div class='row'>
<div class='col-md-6 mx-auto col-10'>
<p class='lead'>Billing Address</p>
<hr/>
{% url "checkout_address_create" as checkout_address_create %}
{% include 'addresses/form.html' with form=address_form next_url=request.build_absolute_uri action_url=checkout_address_create address_type='billing' %}
</div>
</div>
<h1>Finalizando a Compra</h1>
<p>Total do carrinho: {{ object.cart.total }}</p>
<p>Frete: {{ object.shipping_total }}</p>
<p>Total: {{ object.total }}</p>
<button>Checkout</button>
{% endif %}
{% endblock %}
Resumindo o que foi feito no checkout do carts.
Se não tiver endereço de entrega definido (shipping address) o formulário de endereço vai ser mostrado no browser, para pegar esses dados do endereço de entrega, usando o tipo de endereço shipping.
Se não for esse o caso, e se não tiver endereço de cobrança (billing address) definido, o formulário de endereço vai ser mostrado no browser, para pegar os dados do endereço de cobrança, com o tipo de endereço billing.
Colocamos um botão de checkout também.
Agora vamos criar o form do endereço residencial do usuário.
É um form bem parecido com o do accounts.
django_ecommerce/e_commerce/addresses/templates/addresses/form.html
<form method='POST' action='{% if action_url %}{{ action_url }}{% else %}{% url "login" %}{% endif %}'> {% csrf_token %}
{% if next_url %}
<input type='hidden' name='next' value='{{ next_url }}' />
{% endif %}
{% if address_type %}
<input type='hidden' name='address_type' value='{{ address_type }}' />
{% endif %}
{{ form.as_p }}
<button type='submit' class='btn btn-default'>Submit</button>
</form>
Isso diz ao navegador para retornar os dados do formulário para a URL checkout/address/create/, que vem do action_url se existir, usando o método POST.
Se não existir, redireciona para login/
Logo em seguida, ele verifica se precisa next_url tá definida e se tiver coloca no input, baseado no redirect_path que vai retornar do views.py do addresses.
Em seguida, verifica se tem um tipo de endereço (address_type) definido e coloca no input.
No views.py do addresses coloque o seguinte conteúdo:
django_ecommerce/e_commerce/addresses/views.py
from django.shortcuts import render, redirect
from django.utils.http import url_has_allowed_host_and_scheme
from billing.models import BillingProfile
from .forms import AddressForm
def checkout_address_create_view(request):
form = AddressForm(request.POST or None)
context = {
"form": form
}
next_ = request.GET.get('next')
next_post = request.POST.get('next')
redirect_path = next_ or next_post or None
if form.is_valid():
print(request.POST)
instance = form.save(commit=False)
billing_profile, billing_profile_created = BillingProfile.objects.new_or_get(request)
if billing_profile is not None:
instance.billing_profile = billing_profile
instance.address_type = request.POST.get('address_type', 'shipping')
instance.save()
else:
print("Error here")
return redirect("cart:checkout")
if url_has_allowed_host_and_scheme(redirect_path, request.get_host()):
return redirect(redirect_path)
else:
return redirect("cart:checkout")
return redirect("cart:checkout")
No código acima, onde instanciamos o form, criamos o contexto, em seguida o redirect_path é definido como get ou post, ou fica indefinido como None.
Depois, se o form estiver válido, faz um print do request.POST, salva o form em instance com commit False.
Salvando com commit = False obtemos um objeto de modelo, então você pode adicionar dados extras e salvá-los.
Seguindo, logo depois, verificamos se o billing_profile tá definido, se tiver, o atribuimos ao billing_profile da instância do form.
Definimos na linha seguinte o tipo de endereço na instância do form e o salvamos.
Quando o billing_profile não estiver definido, a execução vai cair no else, e imprimir: “Error here” e redireciona para a página de checkout.
O if logo abaixo, garante que a url para a qual será feito o redirecionamento é uma url segura.
Se a URL não for validada, é uma vulnerabilidade de “redirecionamento aberto“.
Pessoas mal-intencionadas, podem tirar proveito de um redirecionamento aberto para ocultar URLs maliciosas atrás de um URL que pareça confiável.
O url_has_allowed_host_and_scheme() garante que a URL tenha os nomes de host e esquema esperados.
Agora para finalizar, abra o url.py do projeto e insira o path do address, é o código em azul.
django_ecommerce/e_commerce/urls.py
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.contrib.auth.views import LogoutView
from django.urls import path, include
from django.views.generic import TemplateView
from carts.views import cart_home
from accounts.views import login_page, register_page, logout_page, guest_register_view
from addresses.views import checkout_address_create_view
from .views import (home_page,
about_page,
contact_page
)
urlpatterns = [
path('', home_page, name='home'),
path('about/', about_page, name='about'),
path('contact/', contact_page, name='contact'),
path('cart/', include("carts.urls", namespace="cart")),
path('checkout/address/create/', checkout_address_create_view, name='checkout_address_create'),
path('login/', login_page, name='login'),
path('register/guest/', guest_register_view, name='guest_register'),
path('logout/', LogoutView.as_view(), name='logout'),
path('register/', register_page, name='register'),
path('bootstrap/', TemplateView.as_view(template_name='bootstrap/example.html')),
path('search/', include("search.urls", namespace="search")),
path('products/', include("products.urls", namespace="products")),
path('admin/', admin.site.urls),
]
if settings.DEBUG:
urlpatterns = urlpatterns + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns = urlpatterns + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)