From 10b81bc471c600482265c1dc4cef7725869abb3f Mon Sep 17 00:00:00 2001 From: pierreNomazy Date: Wed, 28 Oct 2015 19:24:01 +0100 Subject: [PATCH 1/4] Add SearchNotFound model SearchNotFound is a query from a customer which no product response for catalog improvement --- lfs/search/models.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/lfs/search/models.py b/lfs/search/models.py index 52ea99525..6747ed4f0 100644 --- a/lfs/search/models.py +++ b/lfs/search/models.py @@ -1 +1,30 @@ -# just for manage.py test +# django imports +from django.contrib.auth.models import User +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class SearchNotFound(models.Model): + """ + A SearchNotFound is a query from a customer which no product response + A SearchNotFound is only created from a search for a product not found because the livesearch will provide two many queries + with product found and we need only the queries not found to complete our catalog. + **Attributes** + user + The user to which the cart belongs to + session + The session to which the cart belongs to + creation_date + The creation date of the cart + + A SearchNotFound can be assigned either to the current logged in User (in case + the shop user is logged in) or to the current session (in case the shop + user is not logged in). + """ + user = models.ForeignKey(User, verbose_name=_(u"User"), blank=True, null=True) + session = models.CharField(_(u"Session"), blank=True, max_length=100) + creation_date = models.DateTimeField(_(u"Creation date"), auto_now_add=True) + query = models.CharField("Query not Found", blank=True, max_length=100) + + def __unicode__(self): + return u"%s, %s" % (self.query, self.session) From cbbdaab7030e09c4afc6b79c6c4f0945098f87b3 Mon Sep 17 00:00:00 2001 From: pierreNomazy Date: Wed, 28 Oct 2015 19:38:21 +0100 Subject: [PATCH 2/4] Improve Search experience in lfs Add multi words search (like google ...) and save Query with Search not found --- lfs/search/views.py | 80 +++++++++++++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/lfs/search/views.py b/lfs/search/views.py index 89d45da22..5bb3258a8 100644 --- a/lfs/search/views.py +++ b/lfs/search/views.py @@ -1,16 +1,18 @@ -import json - # django imports +import re from django.db.models import Q +from django.core.exceptions import FieldError from django.http import HttpResponse from django.shortcuts import render_to_response from django.template import RequestContext from django.template.loader import render_to_string +from django.utils import simplejson # lfs imports +from lfs.catalog.models import Category from lfs.catalog.models import Product -from lfs.catalog.settings import STANDARD_PRODUCT, PRODUCT_WITH_VARIANTS, VARIANT - +#from lfs.catalog.settings import STANDARD_PRODUCT, PRODUCT_WITH_VARIANTS, VARIANT +from lfs.search.models import SearchNotFound def livesearch(request, template_name="lfs/search/livesearch_results.html"): """ @@ -18,19 +20,23 @@ def livesearch(request, template_name="lfs/search/livesearch_results.html"): q = request.GET.get("q", "") if q == "": - result = json.dumps({ + result = simplejson.dumps({ "state": "failure", }) else: # Products - query = Q(active=True) & \ - (Q(name__icontains=q) | - Q(manufacturer__name__icontains=q) | - Q(sku_manufacturer__icontains=q) - ) # & \ - # Q(sub_type__in=(STANDARD_PRODUCT, PRODUCT_WITH_VARIANTS, VARIANT)) + query = Q(active=True) & get_query(q, ['name', 'description']) + temp = Product.objects.filter(query) + if not temp: + # Creates a SearchNotFound for the current session and/or user. + if request.session.session_key is None: + request.session.save() + searchnotfound = SearchNotFound(session=request.session.session_key) + if request.user.is_authenticated(): + searchnotfound.user = request.user + searchnotfound.query = q[0:99] + searchnotfound.save() - temp = Product.objects.filter(query) total = temp.count() products = temp[0:5] @@ -40,11 +46,11 @@ def livesearch(request, template_name="lfs/search/livesearch_results.html"): "total": total, })) - result = json.dumps({ + result = simplejson.dumps({ "state": "success", "products": products, }) - return HttpResponse(result, content_type='application/json') + return HttpResponse(result, mimetype='application/json') def search(request, template_name="lfs/search/search_results.html"): @@ -54,14 +60,18 @@ def search(request, template_name="lfs/search/search_results.html"): q = request.GET.get("q", "") # Products - query = Q(active=True) & \ - (Q(name__icontains=q) | - Q(manufacturer__name__icontains=q) | - Q(sku_manufacturer__icontains=q) - ) # & \ - # Q(sub_type__in=(STANDARD_PRODUCT, PRODUCT_WITH_VARIANTS, VARIANT)) - + # TODO add a view in manage to select the attributs of Product modele : manufacturer__name, sku_manufacturer, ... + query = Q(active=True) & get_query(q, ['name', 'description']) products = Product.objects.filter(query) + if not products: + # Creates a SearchNotFound for the current session and/or user. + if request.session.session_key is None: + request.session.save() + searchnotfound = SearchNotFound(session=request.session.session_key) + if request.user.is_authenticated(): + searchnotfound.user = request.user + searchnotfound.query = q + searchnotfound.save() # Sorting sorting = request.session.get("sorting") @@ -75,3 +85,31 @@ def search(request, template_name="lfs/search/search_results.html"): "q": q, "total": total, })) + +def normalize_query(query_string, + findterms=re.compile(ur'"([^"]+)"|(\S+)').findall, + normspace=re.compile(r'\s{2,}').sub): + ''' Splits the query string in invidual keywords, getting rid of unecessary spaces + and grouping quoted words together. + ''' + return [normspace(' ', (t[0] or t[1]).strip()) for t in findterms(query_string)] + +def get_query(query_string, search_fields): + ''' Returns a query, that is a combination of Q objects. That combination + aims to search keywords within a model by testing the given search fields. + ''' + query = None # Query to search for every search term + terms = normalize_query(query_string) + for term in terms: + or_query = None # Query to search for a given term in each field + for field_name in search_fields: + q = Q(**{"%s__icontains" % field_name: term}) + if or_query is None: + or_query = q + else: + or_query = or_query | q + if query is None: + query = or_query + else: + query = query & or_query + return query From 064b892b35f8d9f14fce3a3ca82af8e80510d412 Mon Sep 17 00:00:00 2001 From: pierreNomazy Date: Wed, 28 Oct 2015 19:41:59 +0100 Subject: [PATCH 3/4] Manage the table Search Not found Used to export data for analytics. TODO : change from /admin to /manage --- lfs/search/admin.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 lfs/search/admin.py diff --git a/lfs/search/admin.py b/lfs/search/admin.py new file mode 100644 index 000000000..8db697bb2 --- /dev/null +++ b/lfs/search/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin +from lfs.search.models import SearchNotFound + +class SearchNotFoundAdmin(admin.ModelAdmin): + list_display = ('creation_date', 'user', 'query', 'session' ) + search_fields = ('creation_date','user', 'query', 'session' ) + +admin.site.register(SearchNotFound, SearchNotFoundAdmin) From 4a6a5ec3db489be5a7a27ace6255aa109e10bdca Mon Sep 17 00:00:00 2001 From: pierreNomazy Date: Wed, 28 Oct 2015 19:55:50 +0100 Subject: [PATCH 4/4] Update views.py --- lfs/search/views.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lfs/search/views.py b/lfs/search/views.py index 5bb3258a8..c642cf185 100644 --- a/lfs/search/views.py +++ b/lfs/search/views.py @@ -6,7 +6,7 @@ from django.shortcuts import render_to_response from django.template import RequestContext from django.template.loader import render_to_string -from django.utils import simplejson +import json # lfs imports from lfs.catalog.models import Category @@ -20,13 +20,13 @@ def livesearch(request, template_name="lfs/search/livesearch_results.html"): q = request.GET.get("q", "") if q == "": - result = simplejson.dumps({ + result = json.dumps({ "state": "failure", }) else: # Products query = Q(active=True) & get_query(q, ['name', 'description']) - temp = Product.objects.filter(query) + temp = Product.objects.filter(query) if not temp: # Creates a SearchNotFound for the current session and/or user. if request.session.session_key is None: @@ -46,11 +46,11 @@ def livesearch(request, template_name="lfs/search/livesearch_results.html"): "total": total, })) - result = simplejson.dumps({ + result = json.dumps({ "state": "success", "products": products, }) - return HttpResponse(result, mimetype='application/json') + return HttpResponse(result, content_type='application/json') def search(request, template_name="lfs/search/search_results.html"):