Aula 14 – Loja Online – Django – SlugField

More videos
Views
   

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:

https://docs.djangoproject.com/pt-br/2.1/ref/models/fields/

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.

Acesse http://127.0.0.1:8000/admin/products/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.

Dêem um joinha 👍 na página do Código Fluente no Facebook
https://www.facebook.com/Codigofluente-338485370069035/

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:

Digital Ocean

Esse outro link é da one.com:

One.com

Para baixar o código como está até agora, acesse o link abaixo:
https://github.com/toticavalcanti/django_ecommerce/tree/slug_field

Obrigado, até a próxima e 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>