Aula 14 - Loja Online - Django - SlugField
Loja Virtual - Ecommerce - Django - SlugField
Nossas URLs não estão legais, não são amigáveis.
127.0.0.1:8000/featured/3/
URLS assim não são boas para o usuário, nem para o SEO do sua loja online, além de expor o id do produto na url, enfim, precisamos de uma forma de solucionar isso e a solução é usar
SlugField.
Esse é o link da documentação oficial do django. Dêem uma olhada lá também:
De acordo com a documentação, slug é um rótulo curto para algo, contendo apenas letras, números, sublinhados ou hifens.
Eles geralmente são usados em URLs.
Assim como no
CharField, você pode especificar
max_length para o
SlugField também.
Se um
max_length não for especificado, o Django usará o tamanho padrão que é 50.
Então, mãos a obra.
Vamos criar um campo a mais no nosso modelo produto, será o campo slug:
src/products/models.py
from django.db import models
#Custom queryset
class ProductQuerySet(models.query.QuerySet):
def active(self):
return self.filter(active = True)
def featured(self):
return self.filter(featured = True, active = True)
class ProductManager(models.Manager):
def get_queryset(self):
return ProductQuerySet(self.model, using = self._db)
def all(self):
return self.get_queryset().active()
def featured(self):
#return self.get_queryset().filter(featured = True)
return self.get_queryset().featured()
def get_by_id(self, id):
qs = self.get_queryset().filter(id = id)
if qs.count() == 1:
return qs.first()
return None
# Create your models here.
class Product(models.Model): #product_category
title = models.CharField(max_length=120)
slug = models.SlugField()
description = models.TextField()
price = models.DecimalField(decimal_places=2, max_digits=20, default=100.00)
image = models.FileField(upload_to = 'products/', null = True, blank = True)
featured = models.BooleanField(default = False)
active = models.BooleanField(default = True)
objects = ProductManager()
#python 3
def __str__(self):
return self.title
#python 2
def __unicode__(self):
return self.title
Migrações
Como fizemos uma alteração no nosso modelo, devemos fazer as migrações:
python manage.py makemigrations
Note que como não definimos nenhum parâmetro como:
blank = True,
null = True ou
default = 'slug_padrao' em
models.SlugField(), na hora que a gente roda o
makemigrations, ele pede um valor padrão para por no campo
slug dos produtos já cadastrados.
Digite 1 como opção e forneça uma string para ser o slug padrão.
Pode ser a string que você quiser, para o exemplo, vou utilizar:
'slug_padrao'.
Poderia também, ter passado o slug padrão como parâmetro:
models.SlugField(default = 'slug_padrao')
Feita as migrations, rode então o migrate:
python manage.py migrate
Com o servidor rodando, abra
127.0.0.1:8000/admin/
Acesse os produtos e veja que agora eles tem o campo slug, e estão preenchidos com:
'slug_padrao'
Rode novamente:
python manage.py makemigrations
python manage.py migrate
Vamos criar a classe ProductAdmin no src/products/admin.py
from django.contrib import admin
from .models import Product
class ProductAdmin(admin.ModelAdmin):
list_display = ('__str__', 'slug')
class meta:
model = Product
admin.site.register(Product, ProductAdmin)
O
'__str__' pega o nome do objeto, se fosse no python 2 seria:
__unicode__()
Isso significa que, se você não o sobrescreveu lá no src/products/models.py o método __str__(), você vai ter uma lista de products, todos denominados "Product object", como já fizemos o método __str__() que sobrescreve o default, o nosso __str__() vai retornar o que foi definido nele, que foi o title do product.
Então em
list_display = ('__str__', 'slug') é atribuída a lista com o
title do produto em
__str__ e o segundo elemento é o
slug.
Por isso quando você entra na
http://127.0.0.1:8000/admin/products/product/ você vê não só o nome do produto, que vem do
title retornado pelo
__str__ do nosso
src/products/models.py como também o SLUG.
No
class meta é definido que a classe
ProductAdmin vai usar o modelo
product.
Confira agora que além do
product é mostrado também o
slug.
Agora vamos mudar o slug da Camiseta para fazer um teste.
Acesse
http://127.0.0.1:8000/admin/products/product/ e escolha
Camiseta.
Em
slug substitua
slug_padrao por
camiseta.
Agora acesse
http://127.0.0.1:8000/products/camiseta/
Não deu certo!
Precisamos alterar o src/e_commerce/urls.py, as alterações estão em laranja, como sempre:
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path
from products.views import (ProductListView,
product_list_view,
ProductDetailView,
product_detail_view,
ProductDetailSlugView,
ProductFeaturedListView,
ProductFeaturedDetailView)
from .views import home_page, about_page, contact_page, login_page, register_page
urlpatterns = [
path('', home_page),
path('about/', about_page),
path('contact/', contact_page),
path('login/', login_page),
path('register/', register_page),
path('featured/', ProductFeaturedListView.as_view()),
path('featured/', ProductDetailView.as_view()),
path('products/', ProductListView.as_view()),
path('products-fbv/', product_list_view),
path('products/', ProductDetailView.as_view()),
path('products-fbv/', product_detail_view),
path('products/<slug:slug>/', ProductDetailSlugView.as_view()),
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)
Temos que criar nossa classe
ProductDetailSlugView em
src/products/views.py
from django.http import Http404
from django.views.generic import ListView, DetailView
from django.shortcuts import render, get_object_or_404
from .models import Product
class ProductFeaturedListView(ListView):
template_name = "products/list.html"
def get_queryset(self, *args, **kwargs):
request = self.request
return Product.objects.featured()
class ProductFeaturedDetailView(DetailView):
queryset = Product.objects.all().featured()
template_name = "products/featured-detail.html"
#Class Based View
class ProductListView(ListView):
#traz todos os produtos do banco de dados sem filtrar nada
queryset = Product.objects.all()
template_name = "products/list.html"
# def get_context_data(self, *args, **kwargs):
# context = super(ProductListView, self).get_context_data(*args, **kwargs)
# print(context)
# return context
#Function Based View
def product_list_view(request):
queryset = Product.objects.all()
context = {
'object_list': queryset
}
return render(request, "products/list.html", context)
class ProductDetailSlugView(DetailView):
queryset = Product.objects.all()
template_name = "products/detail.html"
def get_object(self, *args, **kwargs):
slug = self.kwargs.get('slug')
#instance = get_object_or_404(Product, slug = slug, active = True)
try:
instance = Product.objects.get(slug = slug, active = True)
except Product.DoesNotExist:
raise Http404("Não encontrado!")
except Product.MultipleObjectsReturned:
qs = Product.objects.filter(slug = slug, active = True)
instance = qs.first()
return instance
#Class Based View
class ProductDetailView(DetailView):
template_name = "products/detail.html"
def get_context_data(self, *args, **kwargs):
context = super(ProductDetailView, self).get_context_data(*args, **kwargs)
print(context)
return context
def get_object(self, *args, **kwargs):
pk = self.kwargs.get('pk')
instance = Product.objects.get_by_id(pk)
if instance is None:
raise Http404("Esse produto não existe!")
return instance
#Function Based View
def product_detail_view(request, pk = None, *args, **kwargs):
instance = Product.objects.get_by_id(pk)
print(instance)
if instance is None:
raise Http404("Esse produto não existe!")
context = {
'object': instance
}
return render(request, "products/detail.html", context)
Vamos garantir que os slugs sejam unicos, então em
src/products/models.py modifique o slug field inserindo
unique = True.
from django.db import models
#Custom queryset
class ProductQuerySet(models.query.QuerySet):
def active(self):
return self.filter(active = True)
def featured(self):
return self.filter(featured = True, active = True)
class ProductManager(models.Manager):
def get_queryset(self):
return ProductQuerySet(self.model, using = self._db)
def all(self):
return self.get_queryset().active()
def featured(self):
#return self.get_queryset().filter(featured = True)
return self.get_queryset().featured()
def get_by_id(self, id):
qs = self.get_queryset().filter(id = id)
if qs.count() == 1:
return qs.first()
return None
# Create your models here.
class Product(models.Model): #product_category
title = models.CharField(max_length=120)
slug = models.SlugField(unique = True)
description = models.TextField()
price = models.DecimalField(decimal_places=2, max_digits=20, default=100.00)
image = models.FileField(upload_to = 'products/', null = True, blank = True)
featured = models.BooleanField(default = False)
active = models.BooleanField(default = True)
objects = ProductManager()
#python 3
def __str__(self):
return self.title
#python 2
def __unicode__(self):
return self.title
Antes de testar, atribua slugs unicos aos produtos já cadastrados.
Acesse:
http://127.0.0.1:8000/admin/products/product/ e faça as alterações nos slugs dos produtos.
Rode novamente:
python manage.py makemigrations
python manage.py migrate
Vamos criar o
src/products/utils.py para gerar strings aleatórias para os slugs.
Se não tiver o
slugify instalado no ambiente, execute:
pip install slugify
So pra vocês entenderem, o slugify faz o seguinte
>>> slugify(' Isso é um slug ')
'isso-e-um-slug'
src/products/utils.py
import random
import string
from django.utils.text import slugify
def random_string_generator(size = 10, chars = string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))
def unique_slug_generator(instance, new_slug=None):
"""
This is for a Django project and it assumes your instance
has a model with a slug field and a title character (char) field.
"""
if new_slug is not None:
slug = new_slug
else:
slug = slugify(instance.title)
Klass = instance.__class__
qs_exists = Klass.objects.filter(slug = slug).exists()
if qs_exists:
new_slug = "{slug}-{randstr}".format(
slug = slug,
randstr = random_string_generator(size = 4)
)
return unique_slug_generator(instance, new_slug = new_slug)
return slug
Explicando os códigos
No código acima,
src/products/utils.py, temos os
imports de
random,
string e
slugify.
Depois temos a definição do método
random_string_generator, que gera uma string aleatória de tamanho do
size passado.
Caso não seja passado
size, o padrão é definido como
10.
Em seguida a biblioteca string é utilizada para gerar os caracteres(
caracteres ascii minúsculos e números em string) a serem usados na composição da string aleatória, e guarda na variável
chars.
No retorno da
random_string_generator ocorre um
for de
size iterações, ou seja, vai iterar o número de vezes que o
size tá guardando.
O uso do
underscore é para ignorar o
index, já que o que interessa é somente os caracteres guardados na variável
chars e não seus índices.
A cada iteração, o
random pega um
char aleatório em
chars e vai compondo a
string aleatória, retornando a
string final usando o
join, para concatenar todos os
chars da
string.
O random.choice() funciona da seguinte maneira, vamos supor que chars tenha a string "abcdef123456".
A cada vez que ele é chamado, ele escolhe um caracter aleatório na string
chars.
random.choice(chars) #saída e
random.choice(chars) #saída a
random.choice(chars) #saída 4
random.choice(chars) #saída b
O método
unique_slug_generator recebe a instância de um produto, o parâmetro
new_slug é setado como
None se esse parâmetro tiver sem valor.
Em seguida ocorre a verificação que testa se
new_slug não é
None, aí é atribuído a
new_slug o
slug, senão, pega o
title da instância do produto, joga no
slugify e atribui ao
slug.
Em seguida, uma instância da classe
product é atribuída a
klass.
Através da
klass é feito um filtro, para verificar se o
slug já existe.
Se
qs não estiver vazio é porque já tem um
slug igual, então vai ser atribuído um
new_slug ao
slug desse
produto, usando a função
random_string_generator e
unique_slug_generator.
No final tem um
return slug, porque se não tem produto com aquele
slug passado, ele simplesmente retorna o
slug.
Signals
Agora em
src/products/models.py importe o
src/products/utils.py e
pre_save para que antes do produto ser salvo no banco, ele tenha um slug associado a ele.
O
pre_save é o que é chamado de
signals.
O Django inclui um “despachante de sinal” (“signal dispatcher”) que ajuda a permitir que aplicativos desacoplados sejam notificados quando ações ocorrem em outras partes da estrutura.
Em suma, os sinais permitem que certos remetentes notifiquem um conjunto de receptores de que alguma ação ocorreu.
Eles são especialmente úteis quando muitos códigos podem estar interessados nos mesmos eventos.
Esse conjunto de sinais(signals) integrados permitem que o código do usuário seja notificado pelo próprio Django de certas ações.
Vamos importar
unique_slug_generator,
pre_save e criar então o método
product_pre_save_receiver e chamar ele com:
pre_save.connect(product_pre_save_receiver, sender = Product)
src/products/models.py
from django.db import models
from .utils import unique_slug_generator
from django.db.models.signals import pre_save
#Custom queryset
class ProductQuerySet(models.query.QuerySet):
def active(self):
return self.filter(active = True)
def featured(self):
return self.filter(featured = True, active = True)
class ProductManager(models.Manager):
def get_queryset(self):
return ProductQuerySet(self.model, using = self._db)
def all(self):
return self.get_queryset().active()
def featured(self):
#return self.get_queryset().filter(featured = True)
return self.get_queryset().featured()
def get_by_id(self, id):
qs = self.get_queryset().filter(id = id)
if qs.count() == 1:
return qs.first()
return None
# Create your models here.
class Product(models.Model): #product_category
title = models.CharField(max_length=120)
slug = models.SlugField(unique = True)
description = models.TextField()
price = models.DecimalField(decimal_places=2, max_digits=20, default=100.00)
image = models.FileField(upload_to = 'products/', null = True, blank = True)
featured = models.BooleanField(default = False)
active = models.BooleanField(default = True)
objects = ProductManager()
#python 3
def __str__(self):
return self.title
#python 2
def __unicode__(self):
return self.title
def product_pre_save_receiver(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = unique_slug_generator(instance)
pre_save.connect(product_pre_save_receiver, sender = Product)
Para testar acesse:
http://127.0.0.1:8000/admin/products/product/ e crie outro produto camiseta e coloque o slug igual, camiseta também e veja o que acontece.
Não funcionou, vamos a uma pequena alteração no models.py.
src/products/models.py
from django.db import models
from .utils import unique_slug_generator
from django.db.models.signals import pre_save
#Custom queryset
class ProductQuerySet(models.query.QuerySet):
def active(self):
return self.filter(active = True)
def featured(self):
return self.filter(featured = True, active = True)
class ProductManager(models.Manager):
def get_queryset(self):
return ProductQuerySet(self.model, using = self._db)
def all(self):
return self.get_queryset().active()
def featured(self):
#return self.get_queryset().filter(featured = True)
return self.get_queryset().featured()
def get_by_id(self, id):
qs = self.get_queryset().filter(id = id)
if qs.count() == 1:
return qs.first()
return None
# Create your models here.
class Product(models.Model): #product_category
title = models.CharField(max_length=120)
slug = models.SlugField(blank = True, unique = True)
description = models.TextField()
price = models.DecimalField(decimal_places=2, max_digits=20, default=100.00)
image = models.FileField(upload_to = 'products/', null = True, blank = True)
featured = models.BooleanField(default = False)
active = models.BooleanField(default = True)
objects = ProductManager()
#python 3
def __str__(self):
return self.title
#python 2
def __unicode__(self):
return self.title
def product_pre_save_receiver(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = unique_slug_generator(instance)
pre_save.connect(product_pre_save_receiver, sender = Product)
Agora sim, teste novamente, deixando o campo slug em branco, veja que o slug é criado automaticamente baseado no title do produto.
Vou deixar meu link de referidos na digitalocean pra vocês.
Quem se cadastrar por esse link, ganha $100.00 dólares de crédito na digitalocean:
Esse outro link é da one.com:
Obrigado, até a próxima e bons estudos. ;)