Aula 78 – Loja Online – Segurança e Organização dos Javascripts

Aula 78 – Loja Online – Segurança e Organização dos Javascripts

Loja Online - Django

Loja Online – Django

Pacote Programador Fullstack

Pacote Programador Fullstack

Voltar para página principal do blog

Todas as aulas desse curso

Aula 77                       Aula 79

Redes Sociais:

facebook

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

Canais do Youtube

Toti

Backing Track / Play-Along

Código Fluente

Putz!

Vocal Techniques and Exercises

PIX para doações

PIX Nubank

PIX Nubank


Aula 78 – Loja Online – Segurança e Organização dos Javascripts

Vou criar a branch js-organization-and-security para as mudanças que vamos fazer nessa aula.

Colocamos o javascript diretamente no django_ecommerce/e_commerce/templates/base.html.

O que vamos fazer é criar uma pasta dentro de django_ecommerce/e_commerce/static_local/ chamada js.

Dentro de django_ecommerce/e_commerce/static_local/js vamos criar um arquivo chamado ecommerce.js.

Dentro dele vamos colocar todo o javascript que tá no base, ou seja, vamos transferir esse Javascript de lugar.

django_ecommerce/e_commerce/static_local/js/ecommerce.js


$(document).ready(function(){
  // Contact Form Handler
  var contactForm = $(".contact-form")
  var contactFormMethod = contactForm.attr("method")
  var contactFormEndpoint = contactForm.attr("action")
  function displaySubmitting(submitBtn, defaultText, doSubmit){
    if (doSubmit){
      submitBtn.addClass("disabled")
      submitBtn.html("<i class='fa fa-spin fa-spinner'></i> Enviando...")
    } else {
      submitBtn.removeClass("disabled")
      submitBtn.html(defaultText)
    }
  }
  contactForm.submit(function(event){
    event.preventDefault()
    const contactFormSubmitBtn = contactForm.find("[type='submit']")
    const contactFormSubmitBtnTxt = contactFormSubmitBtn.text()
    const contactFormData = contactForm.serialize()
    const thisForm = $(this)
    displaySubmitting(contactFormSubmitBtn, "", true)
    $.ajax({
      method: contactFormMethod,
      url: contactFormEndpoint,
      data: contactFormData,
      success: function(data){
        contactForm[0].reset()
        $.alert({
          title: "Success!",
          content: data.message,
          theme: "modern",
        })
        setTimeout(function(){
          displaySubmitting(contactFormSubmitBtn, contactFormSubmitBtnTxt, false)
        }, 500)
      },
      error: function(error){
        console.log(error.responseJSON)
        const jsonData = error.responseJSON
        let msg = ""
        $.each(jsonData, function(key, value){
          msg += key + ": " + value[0].message + "<br/>"
        })
        $.alert({
          title: "Oops!",
          content: msg,
          theme: "modern",
        })
        setTimeout(function(){
          displaySubmitting(contactFormSubmitBtn, contactFormSubmitBtnTxt, false)
        }, 500)
      }
    })
  })
  // Auto Search
  const searchForm = $(".search-form")
  const searchInput = searchForm.find("[name='q']") // input name='q'
  const typingTimer = 0;
  const typingInterval = 500 // .5 seconds
  const searchBtn = searchForm.find("[type='submit']")
  searchInput.keyup(function(event){
    //key released
    clearTimeout(typingTimer)
    typingTimer = setTimeout(performSearch, typingInterval)
  })
  searchInput.keydown(function(event){
    // key pressed
    clearTimeout(typingTimer)
  })
  function displaySearching(){
    searchBtn.addClass("disabled")
    searchBtn.html("<i class='fa fa-spin fa-spinner'></i> Searching...")
  }
  function performSearch(){
    displaySearching()
    var query = searchInput.val()
    setTimeout(function(){
      window.location.href='/search/?q=' + query
    }, 1000)
  }
  //Cart + Add Product
  const productForm = $(".form-product-ajax")
  productForm.submit(function(event){
  event.preventDefault();
  // console.log("O formulário não foi enviado!");
  // o this pega os dados relacionados a esse form
  const thisForm = $(this);
  //const actionEndpoint = thisForm.attr("action");
  const actionEndpoint = thisForm.attr("data-endpoint");
  const httpMethod = thisForm.attr("method");
  const formData = thisForm.serialize();
  $.ajax({
    url: actionEndpoint,
    method: httpMethod,
    data: formData,
    success: function(data){
      // console.log("Sucesso")
      // console.log(data)
      // console.log("Adicionado", data.added)
      // console.log("Removido", data.removed)
      const submitSpan = thisForm.find(".submit-span")
      if(data.added){
        submitSpan.html("No carrinho <button type='submit' class='btn btn-link'>Excluir</button>")
      } else {
        submitSpan.html("<button type='submit' class='btn btn-success'>Adicionar</button>")
      }
      const navbarCount = $(".navbar-cart-count")
      navbarCount.text(data.cartItemCount)
      const currentPath = window.location.href
      if(currentPath.indexOf("cart") != -1){
        refreshCart()
      }
    },
    error: function(errorData){
      $.alert({
          title: 'Oops!',
          content: 'Ocorreu um erro, tente novamente mais tarde!',
          theme: 'modern'
        });
      }
    })
  })
  function refreshCart(){
    //console.log("Excluído do carrinho atual!")
    const cartTable = $(".cart-table")
    const cartBody = cartTable.find(".cart-body")
    //cartBody.html("<h1>Mudou!</h1>")
    const productsRow = cartBody.find(".cart-product")
    const currentUrl = window.location.href 
    const refreshCartUrl = '/api/cart/';
    const refreshCartMethod = "GET";
    const data = {};
    $.ajax({
      url: refreshCartUrl,
      method: refreshCartMethod,
      data: data,
      success: function(data){
        console.log(data)
        const hiddenCartItemRemoveForm = $(".cart-item-remove-form")
        if(data.products.length > 0){
          productsRow.html(" ")
          let i = data.products.length
          $.each(data.products, function(index, value){
            const newCartItemRemove = hiddenCartItemRemoveForm.clone()
            newCartItemRemove.css("display", "block")
            newCartItemRemove.find(".cart-item-product-id").val(value.id)
            cartBody.prepend("<tr><th scope=\"row\">" + i + 
            "</th><td><a href='" + value.url + "'>" + 
            value.name + "</a>" + newCartItemRemove.html() + "</td><td>" + value.price + "</td></tr>")
            i--
          })
          cartBody.find(".cart-subtotal").text(data.subtotal)
          cartBody.find(".cart-total").text(data.total)
        } else {
          window.location.href = currentUrl
        }
      },
      error: function(errorData){
        console.log("Erro")
        console.log(errorData)
      }
    })
  }
})

E podemos tirar tudo que tá em vermelho no base.html.

e_commerce/templates/base.html

{% load static %}
<!doctype html>
<html lang="en">
  <head>
  <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Base Template</title>
    {% include 'base/css.html' %}
    {% block base_head %}{% endblock base_head %}
  </head>
  <body>
    {% include 'base/navbar.html' with nome_da_marca='Loja virtual' %}
    <div class='container'>
      {% block content %} {% endblock %}
    </div>
    {% include 'base/js.html' %}
    <script>
        $(document).ready(function(){
            // Contact Form Handler
            var contactForm = $(".contact-form")
            var contactFormMethod = contactForm.attr("method")
            var contactFormEndpoint = contactForm.attr("action")
            function displaySubmitting(submitBtn, defaultText, doSubmit){
                if (doSubmit){
                    submitBtn.addClass("disabled")
                    submitBtn.html("<i class='fa fa-spin fa-spinner'></i> Enviando...")
                } else {
                    submitBtn.removeClass("disabled")
                    submitBtn.html(defaultText)
                }
            }
            contactForm.submit(function(event){
                event.preventDefault()
                const contactFormSubmitBtn = contactForm.find("[type='submit']")
                const contactFormSubmitBtnTxt = contactFormSubmitBtn.text()
                const contactFormData = contactForm.serialize()
                const thisForm = $(this)
                displaySubmitting(contactFormSubmitBtn, "", true)
                $.ajax({
                    method: contactFormMethod,
                    url: contactFormEndpoint,
                    data: contactFormData,
                    success: function(data){
                        contactForm[0].reset()
                        $.alert({
                            title: "Success!",
                            content: data.message,
                            theme: "modern",
                        })
                        setTimeout(function(){
                            displaySubmitting(contactFormSubmitBtn, contactFormSubmitBtnTxt, false)
                        }, 500)
                    },
                    error: function(error){
                        console.log(error.responseJSON)
                        const jsonData = error.responseJSON
                        let msg = ""
                        $.each(jsonData, function(key, value){
                            msg += key + ": " + value[0].message + "<br/>"
                        })
                        $.alert({
                            title: "Oops!",
                            content: msg,
                            theme: "modern",
                        })
                        setTimeout(function(){
                            displaySubmitting(contactFormSubmitBtn, contactFormSubmitBtnTxt, false)
                        }, 500)
                }
            })
        })

        // Auto Search
        const searchForm = $(".search-form")
        const searchInput = searchForm.find("[name='q']") // input name='q'
        const typingTimer = 0;
        const typingInterval = 500 // .5 seconds
        const searchBtn = searchForm.find("[type='submit']")
        searchInput.keyup(function(event){
            // key released
            clearTimeout(typingTimer)
            typingTimer = setTimeout(performSearch, typingInterval)
        })
        searchInput.keydown(function(event){
            // key pressed
            clearTimeout(typingTimer)
        })
        function displaySearching(){
            searchBtn.addClass("disabled")
            searchBtn.html("<i class='fa fa-spin fa-spinner'></i> Searching...")
        }

        function performSearch(){
            displaySearching()
            var query = searchInput.val()
            setTimeout(function(){
              window.location.href='/search/?q=' + query
           }, 1000)
        }

        //Cart + Add Product
        const productForm = $(".form-product-ajax")
        productForm.submit(function(event){
        event.preventDefault();
        // console.log("O formulário não foi enviado!");
        // o this pega os dados relacionados a esse form
        const thisForm = $(this);
        //const actionEndpoint = thisForm.attr("action");
        const actionEndpoint = thisForm.attr("data-endpoint");
        const httpMethod = thisForm.attr("method");
        const formData = thisForm.serialize();
        $.ajax({
          url: actionEndpoint,
          method: httpMethod,
          data: formData,
          success: function(data){
            // console.log("Sucesso")
            // console.log(data)
            // console.log("Adicionado", data.added)
            // console.log("Removido", data.removed)
            const submitSpan = thisForm.find(".submit-span")
            if(data.added){
              submitSpan.html("No carrinho <button type='submit' class='btn btn-link'>Excluir</button>")
            } else {
              submitSpan.html("<button type='submit' class='btn btn-success'>Adicionar</button>")
            }
            const navbarCount = $(".navbar-cart-count")
            navbarCount.text(data.cartItemCount)
            const currentPath = window.location.href
            if(currentPath.indexOf("cart") != -1){
              refreshCart()
            }
          },
          error: function(errorData){
            $.alert({
                title: "Oops!",
                content: "Ocorreu um erro, tente mais tarde novamente!",
                theme: "modern",
            })
            console.log("Erro")
            console.log(errorData)
          }
        })
      })
      function refreshCart(){
        //console.log("Excluído do carrinho atual!")
        const cartTable = $(".cart-table")
        const cartBody = cartTable.find(".cart-body")
        //cartBody.html("<h1>Mudou!</h1>")
        const productsRow = cartBody.find(".cart-product")
        const currentUrl = window.location.href 
        const refreshCartUrl = '/api/cart/';
        const refreshCartMethod = "GET";
        const data = {};
        $.ajax({
          url: refreshCartUrl,
          method: refreshCartMethod,
          data: data,
          success: function(data){
            console.log(data)
            const hiddenCartItemRemoveForm = $(".cart-item-remove-form")
            if(data.products.length > 0){
              productsRow.html(" ")
              let i = data.products.length
              $.each(data.products, function(index, value){
                const newCartItemRemove = hiddenCartItemRemoveForm.clone()
                newCartItemRemove.css("display", "block")
                newCartItemRemove.find(".cart-item-product-id").val(value.id)
                cartBody.prepend("<tr><th scope=\"row\">" + i + 
                "</th><td><a href='" + value.url + "'>" + 
                value.name + "</a>" + newCartItemRemove.html() + "</td><td>" + value.price + "</td></tr>")
                i--
              })
              cartBody.find(".cart-subtotal").text(data.subtotal)
              cartBody.find(".cart-total").text(data.total)
            } else {
                  window.location.href = currentUrl
              }
            },
            error: function(errorData){
              $.alert({
                title: "Oops!",
                content: "Ocorreu um erro, tente mais tarde novamente!",
                theme: "modern",
              })
              console.log("Erro")
              console.log(errorData)
            }
          })
        }
      })
    </script>
  </body>
</html>

O base.html agora tá limpo, dessa forma o código vai ficar melhor organizado em pastas e arquivos específicos.

e_commerce/templates/base.html

{% load static %}
<!doctype html>
<html lang="en">
  <head>
  <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Base Template</title>
    {% include 'base/css.html' %}
    {% block base_head %}{% endblock base_head %}
  </head>
  <body>
    {% include 'base/navbar.html' with nome_da_marca='Loja virtual' %}
    <div class='container'>
      {% block content %} {% endblock %}
    </div>
    {% include 'base/js.html' %}
  </body>
</html>

Agora no js.html, vamos colocar o conteúdo que tá em azul, logo abaixo.

django_ecommerce/e_commerce/templates/base/js.html


{% load static %}

<!-- Optional JavaScript -->
<!-- jQuery first, Popper.js, Bootstrap JS and jquery-confirm JS -->
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.js"></script>
<!-- eCommerce Custom JS -->
<script src='{% static "js/ecommerce.js" %}'></script>

Toda vez que adicionamos alguma coisa aos estáticos do projeto, precisamos rodar o collectstatic com o comando:


python manage.py collectstatic

Digite Yes e pronto, os estáticos foram copiados.

Esse comando vai fazer uma cópia dos arquivos estáticos da pasta django_ecommerce/e_commerce/static_local/ para a pasta django_ecommerce/static_cdn/.

Teste o projeto e veja se tudo continua funcionando.

Seguindo!

Agora, para usar o JQuery com segurança em projetos Django, precisamos fazer algumas coisas.

Usaremos Tokens CSRF.

Vamos criar um arquivo chamado csrf.ajax.js.

django_ecommerce/e_commerce/static_local/js/csrf.ajax.js


$(document).ready(function(){
  // using jQuery
  function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
      const cookies = document.cookie.split(';');
      for (let i = 0; i < cookies.length; i++) {
        const cookie = jQuery.trim(cookies[i]);
        // Does this cookie string begin with the name we want?
        if (cookie.substring(0, name.length + 1) === (name + '=')) {
          cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
          break;
        }
      }
    }
    return cookieValue;
  }
  const csrftoken = getCookie('csrftoken');

  function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
  }
  $.ajaxSetup({
    beforeSend: function(xhr, settings) {
      // csrfSafeMethod() return false to POST and 
      if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
        // console.log("Settings Type: " + settings.type)
        // console.log("CSRF TOKEN: " + csrftoken)
        // console.log("XHR: " + xhr.global)
        xhr.setRequestHeader("X-CSRFToken", csrftoken);
      }
    }
  });
})

O xhr é uma função usada para criar o objeto XMLHttpRequest, o settings.type é o método, nesse caso, um POST, e o csrftoken o token que foi pego através da função getCookie().

Antes do JSON virar meio que padrão para troca de dados, o formato mais usado era o XML.

O XMLHttpRequest() é uma função do JavaScript que tornou possível obter dados de APIs que retornavam dados em XML.

XMLHttpRequest() dá suporte a outros formatos de dados além do XML, inclusive o JSON e texto simples.

O Fetch API é uma versão mais simples e fácil de usar a XMLHttpRequest() para consumir recursos de modo assíncrono.

O Fetch permite que você trabalhe com as APIs REST com opções adicionais como cache de dados, leitura de respostas em streaming, etc.

A comparação if (!csrfSafeMethod(settings.type) && !this.crossDomain) no código acima, verifica se o método HTTP precisa de TOKEN CSRF, e se a request não é cross domain, ou seja, um domínio diferente do domínio do site.

Abra o js.html e adicione o js do csrf.ajax.js que acabamos de criar.

django_ecommerce/e_commerce/templates/base/js.html


{% load static %}

<!-- Optional JavaScript -->
<!-- jQuery first, Popper.js, Bootstrap JS and jquery-confirm JS -->
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.js"></script>
<!-- eCommerce Custom JS -->
<script src='{% static "js/ecommerce.js" %}'></script>
<!-- Django Secure AJAX JS -->
<script src='{% static "js/csrf.ajax.js" %}'></script>

Toda vez que adicionamos alguma coisa aos estáticos do projeto, precisamos rodar o collectstatic para fazer uma cópia desses estáticos para a pasta onde vai ficar os estáticos do projeto quando ele já tiver no ar.


python manage.py collectstatic

Digite yes e pronto, todos os estáticos que estão, no meu caso, na pasta django_ecommerce/e_commerce/static_local/ foram copiados para a pasta django_ecommerce/static_cdn/.

Nessa pasta irão ficar todos os estáticos de todos os apps que formam o projeto como um todo.

No caso desse projeto do eCommerce, temos basicamente uma pasta só, a static_local.

O processo do collectstatic é muito útil principalmente quando é preciso fazer deploy de projetos com várias pastas, de vários apps, com arquivos estáticos.

Ficamos por aqui e até a próxima. 😉

Voltar para página principal do blog

Todas as aulas desse curso

Aula 77                       Aula 79

Código final da aula:

https://github.com/toticavalcanti

Canais do Youtube

Toti

Backing Track / Play-Along

Código Fluente

Putz!

Vocal Techniques and Exercises

Dêem um joinha 👍 na página do Código Fluente no
Facebook.

Sigam o Código Fluente no Instagram e no TikTok.

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>