]> git.parisson.com Git - mezzo.git/commitdiff
Search : gather selected Pages in search result
authorEmilie <zawadzki@ircam.fr>
Fri, 14 Oct 2016 10:08:28 +0000 (12:08 +0200)
committerEmilie <zawadzki@ircam.fr>
Fri, 14 Oct 2016 10:08:28 +0000 (12:08 +0200)
app/local_settings.py
app/organization/core/managers.py [new file with mode: 0644]
app/organization/core/views.py
app/organization/pages/models.py

index 1f1545349de2e78036fceb9badb895aa1738b148..9d4386cbf70725a8c6779fc9da37a632a1ce7a5f 100644 (file)
@@ -136,6 +136,15 @@ SEARCH_MODEL_CHOICES = ('organization-pages.CustomPage',
                         'organization-media.Audio',
                         'organization-media.Video',
                         'mezzanine_agenda.Event')
+
+
+PAGES_MODELS = ('organization-pages.CustomPage',
+                'organization-magazine.Topic',
+                'organization-network.DepartmentPage',
+                'organization-network.TeamPage',
+                'organization-projects.ProjectTopicPage',
+                'organization-pages.PageLink')
+
 SEARCH_PER_PAGE = 10
 MAX_PAGING_LINKS = 10
 
diff --git a/app/organization/core/managers.py b/app/organization/core/managers.py
new file mode 100644 (file)
index 0000000..b16ae9d
--- /dev/null
@@ -0,0 +1,166 @@
+from __future__ import unicode_literals
+
+import django
+from future.builtins import int, zip
+
+from functools import reduce
+from operator import ior, iand
+from string import punctuation
+
+from django.apps import apps, AppConfig
+from django.core.exceptions import ImproperlyConfigured
+from django.db.models import Manager, Q, CharField, TextField
+from django.db.models.manager import ManagerDescriptor
+from django.db.models.query import QuerySet
+from django.contrib.sites.managers import CurrentSiteManager as DjangoCSM
+from django.utils.timezone import now
+from django.utils.translation import ugettext_lazy as _
+
+from mezzanine.conf import settings
+from mezzanine.utils.sites import current_site_id
+from mezzanine.utils.urls import home_slug
+from mezzanine.core.managers import search_fields_to_dict, SearchableQuerySet
+
+class CustomSearchableManager(Manager):
+
+    """
+    Manager providing a chainable queryset.
+    Adapted from http://www.djangosnippets.org/snippets/562/
+    search method supports spanning across models that subclass the
+    model being used to search.
+    """
+
+    def __init__(self, *args, **kwargs):
+        self._search_fields = kwargs.pop("search_fields", {})
+        super(CustomSearchableManager, self).__init__(*args, **kwargs)
+
+    def get_search_fields(self):
+        """
+        Returns the search field names mapped to weights as a dict.s
+        Used in ``get_queryset`` below to tell ``SearchableQuerySet``
+        which search fields to use. Also used by ``DisplayableAdmin``
+        to populate Django admin's ``search_fields`` attribute.
+        Search fields can be populated via
+        ``SearchableManager.__init__``, which then get stored in
+        ``SearchableManager._search_fields``, which serves as an
+        approach for defining an explicit set of fields to be used.
+        Alternatively and more commonly, ``search_fields`` can be
+        defined on models themselves. In this case, we look at the
+        model and all its base classes, and build up the search
+        fields from all of those, so the search fields are implicitly
+        built up from the inheritence chain.
+        Finally if no search fields have been defined at all, we
+        fall back to any fields that are ``CharField`` or ``TextField``
+        instances.
+        """
+        search_fields = self._search_fields.copy()
+        if not search_fields:
+            for cls in reversed(self.model.__mro__):
+                super_fields = getattr(cls, "search_fields", {})
+                search_fields.update(search_fields_to_dict(super_fields))
+        if not search_fields:
+            search_fields = []
+            for f in self.model._meta.fields:
+                if isinstance(f, (CharField, TextField)):
+                    search_fields.append(f.name)
+            search_fields = search_fields_to_dict(search_fields)
+        return search_fields
+
+    def get_queryset(self):
+        search_fields = self.get_search_fields()
+        return SearchableQuerySet(self.model, search_fields=search_fields)
+
+    def contribute_to_class(self, model, name):
+        """
+        Newer versions of Django explicitly prevent managers being
+        accessed from abstract classes, which is behaviour the search
+        API has always relied on. Here we reinstate it.
+        """
+        super(CustomSearchableManager, self).contribute_to_class(model, name)
+        setattr(model, name, ManagerDescriptor(self))
+
+    def search(self, *args, **kwargs):
+        """
+        Proxy to queryset's search method for the manager's model and
+        any models that subclass from this manager's model if the
+        model is abstract.
+        """
+        if not settings.SEARCH_MODEL_CHOICES:
+            # No choices defined - build a list of leaf models (those
+            # without subclasses) that inherit from Displayable.
+            models = [m for m in apps.get_models()
+                      if issubclass(m, self.model)]
+            parents = reduce(ior, [set(m._meta.get_parent_list())
+                                   for m in models])
+            models = [m for m in models if m not in parents]
+        elif getattr(self.model._meta, "abstract", False):
+            # When we're combining model subclasses for an abstract
+            # model (eg Displayable), we only want to use models that
+            # are represented by the ``SEARCH_MODEL_CHOICES`` setting.
+            # Now this setting won't contain an exact list of models
+            # we should use, since it can define superclass models such
+            # as ``Page``, so we check the parent class list of each
+            # model when determining whether a model falls within the
+            # ``SEARCH_MODEL_CHOICES`` setting.
+            search_choices = set()
+            models = set()
+            parents = set()
+            errors = []
+            for name in settings.SEARCH_MODEL_CHOICES:
+                try:
+                    model = apps.get_model(*name.split(".", 1))
+                except LookupError:
+                    errors.append(name)
+                else:
+                    search_choices.add(model)
+            if errors:
+                raise ImproperlyConfigured("Could not load the model(s) "
+                        "%s defined in the 'SEARCH_MODEL_CHOICES' setting."
+                        % ", ".join(errors))
+
+            for model in apps.get_models():
+                # Model is actually a subclasses of what we're
+                # searching (eg Displayabale)
+                is_subclass = issubclass(model, self.model)
+                # Model satisfies the search choices list - either
+                # there are no search choices, model is directly in
+                # search choices, or its parent is.
+                this_parents = set(model._meta.get_parent_list())
+                in_choices = not search_choices or model in search_choices
+                in_choices = in_choices or this_parents & search_choices
+                if is_subclass and (in_choices or not search_choices):
+                    # Add to models we'll seach. Also maintain a parent
+                    # set, used below for further refinement of models
+                    # list to search.
+                    models.add(model)
+                    parents.update(this_parents)
+            # Strip out any models that are superclasses of models,
+            # specifically the Page model which will generally be the
+            # superclass for all custom content types, since if we
+            # query the Page model as well, we will get duplicate
+            # results.
+            models -= parents
+        elif self.model.__name__ == "CustomPage":
+            # gather all pages defined in PAGES_MODELS settings
+            models = set()
+            errors = []
+            for name in settings.PAGES_MODELS:
+                try:
+                    models.add(apps.get_model(name))
+                except LookupError:
+                    errors.append(name)
+            if errors:
+                raise ImproperlyConfigured("Could not load the model(s) "
+                        "%s defined in the 'SEARCH_MODEL_CHOICES' setting."
+                        % ", ".join(errors))
+        else:
+            models = [self.model]
+        all_results = []
+        user = kwargs.pop("for_user", None)
+        for model in models:
+            try:
+                queryset = model.objects.published(for_user=user)
+            except AttributeError:
+                queryset = model.objects.get_queryset()
+            all_results.extend(queryset.search(*args, **kwargs))
+        return sorted(all_results, key=lambda r: r.result_count, reverse=True)
index 1371806772bda9bc3b54009829a10e8532b028e5..2b7bda422805d36233ef64e3de02cf3023cc74a9 100644 (file)
@@ -8,6 +8,8 @@ from django.http import QueryDict
 from mezzanine.conf import settings
 from mezzanine.utils.views import paginate
 from organization.core.models import *
+from functools import reduce
+from operator import ior, iand
 
 
 class SlugMixin(object):
@@ -17,18 +19,11 @@ class SlugMixin(object):
         return get_object_or_404(objects, slug=self.kwargs['slug'])
 
 
-# class CustomDisplayableView(SlugMixin, DetailView):
-#
-#     model = CustomDisplayable
-
-
 class CustomSearchView(TemplateView):
 
     template_name='search_results.html'
 
-
     def get(self, request, *args, **kwargs):
-
         """
         Display search results. Takes an optional "contenttype" GET parameter
         in the form "app-name.ModelName" to limit search results to a single model.
@@ -49,23 +44,23 @@ class CustomSearchView(TemplateView):
             search_type = search_model._meta.verbose_name_plural.capitalize()
 
         results = search_model.objects.search(query, for_user=request.user)
-        print("----------------------------")
-        print(results)
-        print("----------------------------")
+
         # count objects
         filter_dict = dict()
         for result in results:
 
             classname = result.__class__.__name__
             app_label = result._meta.app_label
-
+            full_classname = app_label+"."+classname
+            verbose_name = result._meta.verbose_name
             # aggregate all Page types : CustomPage, TeamPage, Topic etc...
             if result._meta.get_parent_list() :
                 parent_class = result._meta.get_parent_list()[0]
 
-                if parent_class.__name__ == 'Page':
-                    classname = parent_class.__name__
-                    app_label = parent_class._meta.app_label
+                if full_classname in settings.PAGES_MODELS:
+                    classname = "CustomPage"
+                    verbose_name = "Page"
+                    app_label = "organization-pages"
                 elif "Video" in parent_class.__name__:
                     classname = "Video"
                     app_label = parent_class._meta.app_label
@@ -77,18 +72,16 @@ class CustomSearchView(TemplateView):
                 filter_dict[classname]['count'] += 1
             else:
                 filter_dict[classname] = {'count' : 1}
-                filter_dict[classname].update({'verbose_name' : classname})
+                filter_dict[classname].update({'verbose_name' : verbose_name})
                 filter_dict[classname].update({'app_label' : app_label})
 
-
-
         # get url param
         current_query = QueryDict(mutable=True)
         current_query = request.GET.copy()
 
         # generate filter url
         for key, value in filter_dict.items():
-            current_query['type'] = value['app_label']+'.'+key
+            current_query['type'] = value['app_label']+'.'+ key
             filter_dict[key].update({'url' : request.path+"?"+current_query.urlencode(safe='/')})
 
         # pagination
index 387f92a6da49cd3977fad0dbdba83c3982492b58..f38143f27d1d9bcb84b3c7c24857e6bf1a7f1791 100644 (file)
@@ -5,10 +5,13 @@ from mezzanine.core.models import Displayable, Slugged, Orderable
 from mezzanine.pages.models import Link as MezzanineLink
 from organization.core.models import *
 from organization.media.models import *
+from organization.core.managers import *
 
 
 class CustomPage(Page, SubTitled, RichText):
 
+    objects = CustomSearchableManager()
+
     class Meta:
         verbose_name = 'custom page'
 
@@ -56,7 +59,8 @@ class PageVideo(Video):
 class PageLink(Link):
 
     page = models.ForeignKey(Page, verbose_name=_('page'), related_name='links', blank=True, null=True, on_delete=models.SET_NULL)
-
+    objects = CustomSearchableManager()
+    
     class Meta:
         verbose_name = _("link")
         verbose_name_plural = _("links")