Complete Search App - lhmisho/django-eCommerce GitHub Wiki

Creating Search app

create a search app at the same lebel of your project (myproj/search)

....myproj . ....seaarch

django-admin startapp search

Register app to settings

myproj/settings.py

INSTALLED_APPS = [
    ...............
    'search',
    ...............
]

Create Search view

search/view.py

from django.shortcuts import render
from django.views.generic import ListView
from products.models import Product
# Create your views here.

class SearchProductView(ListView):
    # first way to manage list view
    #queryset = Product.objects.all()
    template_name = 'search/view.html'

    # passing request.GET.q as a query to the template
    def get_context_data(self, *args, **kwargs):
        request = self.request
        context = super(SearchProductView, self).get_context_data(*args, **kwargs)
        query   = request.GET.get('q')
        context['query'] = query
        return context


    def get_queryset(self, *args, **kwargs):
        request = self.request
        query = request.GET.get('q',None)
        print(query)
        if query is not None:
            return Product.objects.search(query)  # using custom query from products.models
        return Product.objects.featured()

Creating Tags for search tag

Now i am going to create tag for better search result which means i can add some tag on my product for better result Now create a tags app at the same level of search and products app...

django-admin startapp search

Than registering the tags app on my projects settings.py

INSTALLED_APPS = [
    ...............
    'search',
    'tags',
    ...............
]

Now i am going to create tag model --- tags/model.py

from django.db import models
from django.urls import reverse
from products.utils import unique_slug_generator
from django.db.models.signals import pre_save, post_save
from products.models import Product
# Create your models here.
class Tag(models.Model):
    title       = models.CharField(max_length=120)
    slug        = models.SlugField()
    timestamp   = models.DateTimeField(auto_now_add=True)
    active      = models.BooleanField(default=True)
    products    = models.ManyToManyField(Product, blank=True)
    def __str__(self):
        return self.title

def tag_pre_save_receiver(sender, instance, *args, **kwargs):
    if not instance.slug:
        instance.slug = unique_slug_generator(instance)
pre_save.connect(tag_pre_save_receiver, sender=Tag)

Products.utils.py code .....

# link for slug generator:- https://www.codingforentrepreneurs.com/blog/a-unique-slug-generator-for-django/
# link for random string generator:- https://www.codingforentrepreneurs.com/blog/random-string-generator-in-python/
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))

# creating unique order id for order apps
def unique_order_id_generator(instance, new_slug=None):
    """ 
    This is for django project with an order_id field 
    """
    new_order_id = random_string_generator().upper() # key : 1DSWA3FG somthing like that

    Klass = instance.__class__
    qs_exists = Klass.objects.filter(order_id=new_order_id).exists()
    if qs_exists:
        return unique_slug_generator(instance)
    return new_order_id


def unique_slug_generator(instance, new_slug=None):
    """ 
    This is for django project with and it's assume that your 
    instance has a model with a slug field and 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

Create Custom Query and Model manager

Now it's time to create custom query and model manager for better handle products/models.py

import os
import random
from django.db.models import Q   # importing for search look-up
from django.db import models
from django.urls import reverse
from .utils import unique_slug_generator
from django.db.models.signals import pre_save, post_save

# creating my own custom queryset
class ProductQueryset(models.query.QuerySet):
    
    # this is for active products query
    def active(self):
        return self.filter(active=True)

    # this custom query is for featured products
    def featured(self):
        return self.filter(featured=True, active=True)
    # this is for search fields qery
    def search(self, query):
        lookups = (Q(title__icontains = query) | 
                  Q(description__icontains = query) | 
                  Q(price__icontains = query) | 
                  Q(tag__title__icontains = query))
        return self.filter(lookups).distinct()
# creating my own queryset
class ProductQueryset(models.query.QuerySet):

    def active(self):
        return self.filter(active=True)

    def featured(self):
        return self.filter(featured=True, active=True)

    def search(self, query):
        lookups = Q(title__icontains = query) | Q(description__icontains = query) | Q(price__icontains = query) | Q(tag__title__icontains = query)
        return self.filter(lookups).distinct()

# model manager for custom queryset
class ProductManager(models.Manager):

    def get_queryset(self):
        return ProductQueryset(self.model, using=self._db)

    # all is extend by active
    # if we do active false than all false item will not apear in all() method
    def all(self):
        return self.get_queryset().active()
    # custom query method for featured items
    def featured(self):
        query = self.get_queryset().featured()
        return query

    # model manager for getiing data as get_by_id()
    def get_by_id(self, pk):
        #return self.get_queryset().filter(id=pk) # i can use id as id or pk
        qs = self.get_queryset().filter(id=pk)
        if qs.count() == 1:
            return qs.first()
        return None
    # taking qury arg from search.views and sending it to customqury model
    def search(self,query):
        return self.get_queryset().active().search(query)

class Product(models.Model):
    title       = models.CharField(max_length=120)
    slug        = models.SlugField(blank=True, unique=True)
    description = models.TextField()
    ######################################
    ### and so on fields #################
    ######################################
    objects     = ProductManager()
    def get_absolute_url(self):
        # first way
        #return f"/products/{self.slug}/"

        # most effictive way
        return reverse('products:detail',kwargs={'slug':self.slug})

    def __str__(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)

Some Experiment of search apps on shell

>> python manage.py shell

#### for Tag Apps
>>> from tags.models import Tag
>>> Tag.objects.all()
<QuerySet [<Tag: my car>, <Tag: black cycle>]>>>> Tag.objects.last()
<Tag: black cycle>
>>> black = Tag.objects.all()
>>> black.titleTraceback (most recent call last):
  File "<console>", line 1, in <module>AttributeError: 'QuerySet' object has no attribute 'title'
>>> opt = Tag.objects.all()>>> opt.title
Traceback (most recent call last):
  File "<console>", line 1, in <module>AttributeError: 'QuerySet' object has no attribute 'title'
>>> opt = Tag.objects.first()>>> opt
<Tag: my car>
>>> opt = Tag.objects.last()
>>> opt
<Tag: black cycle>
>>> black = Tag.objects.last()
>>> black
<Tag: black cycle>
>>> bleack.title
Traceback (most recent call last):
  File "<console>", line 1, in <module>
NameError: name 'bleack' is not defined
>>> black = Tag.objects.all()
>>> black
<QuerySet [<Tag: my car>, <Tag: black cycle>]>
>>> Tag.objects.last()
<Tag: black cycle>
>>> black = Tag.objects.last()
>>> black
<Tag: black cycle>
>>> black.slug
'black-cycle'
>>> black.title
'black cycle'
>>> black.products
<django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager object at 0x7f49499a9518>
>>> black.products.all()
<ProductQueryset [<Product: car>, <Product: cycle>, <Product: cycle>]>
>>> product = black.products.all()
>>> products
Traceback (most recent call last):
  File "<console>", line 1, in <module>
NameError: name 'products' is not defined
>>> product
<ProductQueryset [<Product: car>, <Product: cycle>, <Product: cycle>]>
>>> products.title
Traceback (most recent call last):
  File "<console>", line 1, in <module>
NameError: name 'products' is not defined
>>> products
Traceback (most recent call last):
  File "<console>", line 1, in <module>
NameError: name 'products' is not defined
>>> product.title
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'ProductQueryset' object has no attribute 'title'
>>> exit
Use exit() or Ctrl-D (i.e. EOF) to exit
>>> exit(
... )


######Experiment for Product apps
(eCommerce)lhmisho@lhmisho:~/myPythonProjects/eCommerce/eshop$python manage.py shell
Python 3.6.6 (default, Sep 12 2018, 18:26:19)
[GCC 8.0.1 20180414 (experimental) [trunk revision 259383]] onlinux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from products.models import Product
>>> Product.objects.all()
<ProductQueryset [<Product: car>, <Product: truck>, <Product: but>, <Product: rikshaw>, <Product: cycle>, <Product: cycle>, <Product: shirt>]>
>>> shirt = Product.objects.first()
>>> shirt.title
'car'
>>> shirt.slug
'car-qj22'
>>> shirt.tags
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Product' object has no attribute 'tags'
>>> shirt.tag_set.all()
<Query>
⚠️ **GitHub.com Fallback** ⚠️