Aula 61 – Loja Online – App Address

Aula 61 – Loja Online – App Address

Loja Online - Django - App Address

Loja Online – Django – 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)

É isso, ficamos por aqui e até a próxima. 😉

Voltar para página principal do blog

Todas as aulas desse curso

Aula 60               Aula 62

Código final da aula:

https://github.com/toticavalcanti

Outros canais

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

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

Nos vemos na próxima então, \o/  😉 Bons Estudos!

About The Author
-

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>