From 780c55e25526a5cc32505a1e57359a52ebac9ce2 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Tue, 14 Oct 2014 03:40:46 -0400 Subject: [PATCH] Add dynamic_search app API endpoints, simplify ModelSearch class and search views --- mayan/apps/documents/__init__.py | 3 +- mayan/apps/dynamic_search/__init__.py | 8 ++++ mayan/apps/dynamic_search/api_views.py | 61 ++++++++++++++++++++++++ mayan/apps/dynamic_search/classes.py | 31 ++---------- mayan/apps/dynamic_search/models.py | 7 ++- mayan/apps/dynamic_search/serializers.py | 11 +++++ mayan/apps/dynamic_search/urls.py | 9 ++++ mayan/apps/dynamic_search/views.py | 29 +++++------ 8 files changed, 113 insertions(+), 46 deletions(-) create mode 100644 mayan/apps/dynamic_search/api_views.py create mode 100644 mayan/apps/dynamic_search/serializers.py diff --git a/mayan/apps/documents/__init__.py b/mayan/apps/documents/__init__.py index 4b80a6f094..bf4d916eee 100644 --- a/mayan/apps/documents/__init__.py +++ b/mayan/apps/documents/__init__.py @@ -58,6 +58,7 @@ from .permissions import (PERMISSION_DOCUMENT_DELETE, PERMISSION_DOCUMENT_VERSION_REVERT, PERMISSION_DOCUMENT_VIEW) from .settings import THUMBNAIL_SIZE +from .serializers import DocumentSerializer from .statistics import DocumentStatistics, DocumentUsageStatistics from .urls import api_urls from .widgets import document_thumbnail @@ -141,7 +142,7 @@ class_permissions(Document, [PERMISSION_DOCUMENT_DELETE, PERMISSION_DOCUMENT_VIEW, PERMISSION_HISTORY_VIEW]) -document_search = SearchModel('documents', 'Document') +document_search = SearchModel('documents', 'Document', serializer=DocumentSerializer) document_search.add_model_field('document_type__name', label=_(u'Document type')) # TODO: move these to their respective apps diff --git a/mayan/apps/dynamic_search/__init__.py b/mayan/apps/dynamic_search/__init__.py index 308797e3f6..60525f7f59 100644 --- a/mayan/apps/dynamic_search/__init__.py +++ b/mayan/apps/dynamic_search/__init__.py @@ -1,10 +1,18 @@ from __future__ import absolute_import +from django.utils.translation import ugettext_lazy as _ + from navigation.api import register_links, register_top_menu +from rest_api.classes import APIEndPoint from .links import search, search_advanced, search_again, search_menu +from .urls import api_urls register_links(['search:search', 'search:search_advanced', 'search:results'], [search, search_advanced], menu_name='form_header') register_links(['search:results'], [search_again], menu_name='sidebar') register_top_menu('search', search_menu) + +endpoint = APIEndPoint('search') +endpoint.register_urls(api_urls) +endpoint.add_endpoint('recentsearch-list', _(u'Returns a list of all recent searches.')) diff --git a/mayan/apps/dynamic_search/api_views.py b/mayan/apps/dynamic_search/api_views.py new file mode 100644 index 0000000000..3b91a2dff9 --- /dev/null +++ b/mayan/apps/dynamic_search/api_views.py @@ -0,0 +1,61 @@ +from __future__ import absolute_import + +from django.core.exceptions import PermissionDenied +from django.shortcuts import get_object_or_404 + +from rest_framework import generics, status +from rest_framework.response import Response + +from acls.models import AccessEntry +from permissions.models import Permission +from rest_api.filters import MayanObjectPermissionsFilter +from rest_api.permissions import MayanPermission + +from .classes import SearchModel +from .models import RecentSearch +from .serializers import RecentSearchSerializer + + +class APIRecentSearchListView(generics.ListAPIView): + """ + Returns a list of all the recent searches. + """ + + serializer_class = RecentSearchSerializer + queryset = RecentSearch.objects.all() + + # TODO: Add filter_backend so that users can only see their own entries + + +class APIRecentSearchView(generics.RetrieveDestroyAPIView): + """ + Returns the selected recent search details. + """ + + serializer_class = RecentSearchSerializer + queryset = RecentSearch.objects.all() + + # TODO: Add filter_backend so that users can only see their own entries + + +class APISearchView(generics.ListAPIView): + """ + Perform a search operaton + q -- Term that will be used for the search. + """ + + def get_queryset(self): + document_search = SearchModel.get('documents.Document') + self.serializer_class = document_search.serializer + + if 'q' in self.request.GET: + # Simple query + query_string = self.request.GET.get('q', u'').strip() + queryset, ids, timedelta = document_search.simple_search(query_string) + else: + # Advanced search + queryset, ids, timedelta = document_search.advanced_search(self.request.GET) + + RecentSearch.objects.add_query_for_user(self.request.user, self.request.GET, len(ids)) + + return queryset diff --git a/mayan/apps/dynamic_search/classes.py b/mayan/apps/dynamic_search/classes.py index 1b46f55df3..886895c14c 100644 --- a/mayan/apps/dynamic_search/classes.py +++ b/mayan/apps/dynamic_search/classes.py @@ -23,12 +23,13 @@ class SearchModel(object): def get(cls, full_name): return cls.registry[full_name] - def __init__(self, app_label, model_name, label=None): + def __init__(self, app_label, model_name, serializer, label=None): self.app_label = app_label self.model_name = model_name self.search_fields = {} self.model = get_model(app_label, model_name) self.label = label or self.model._meta.verbose_name + self.serializer = serializer self.__class__.registry[self.get_full_name()] = self def get_full_name(self): @@ -126,12 +127,9 @@ class SearchModel(object): return self.execute_search(search_dict, global_and_search=True) def execute_search(self, search_dict, global_and_search=False): - model_list = {} - flat_list = [] - result_count = 0 - shown_result_count = 0 elapsed_time = 0 start_time = datetime.datetime.now() + result_set = set() for model, data in search_dict.items(): logger.debug('model: %s' % model) @@ -175,30 +173,11 @@ class SearchModel(object): else: model_result_set |= field_result_set - logger.debug('model_result_set: %s' % model_result_set) - - # Update the search result total count - result_count += len(model_result_set) - - # Search the field results return values (PK) in the SearchModel's model - results = self.model.objects.in_bulk(list(model_result_set)[: LIMIT]).values() - logger.debug('query model_result_set: %s' % model_result_set) - - # Update the search result visible count (limited by LIMIT config option) - shown_result_count += len(results) - - if results: - model_list[data['label']] = results - for result in results: - if result not in flat_list: - flat_list.append(result) - - logger.debug('model_list: %s' % model_list) - logger.debug('flat_list: %s' % flat_list) + result_set = result_set | model_result_set elapsed_time = unicode(datetime.datetime.now() - start_time).split(':')[2] - return model_list, flat_list, shown_result_count, result_count, elapsed_time + return self.model.objects.in_bulk(list(result_set)[: LIMIT]).values(), result_set, elapsed_time def assemble_query(self, terms, search_fields): """ diff --git a/mayan/apps/dynamic_search/models.py b/mayan/apps/dynamic_search/models.py index 192e14fdc8..8688d7159a 100644 --- a/mayan/apps/dynamic_search/models.py +++ b/mayan/apps/dynamic_search/models.py @@ -18,7 +18,12 @@ class RecentSearch(models.Model): """ Keeps a list of the n most recent search keywords for a given user """ - user = models.ForeignKey(User, verbose_name=_(u'User'), editable=False) + user = models.ForeignKey(User, verbose_name=_(u'User'), editable=True) + # Setting editable to True to workaround Django REST framework issue + # 1604 - https://github.com/tomchristie/django-rest-framework/issues/1604 + # Should be fixed by DRF v2.4.4 + # TODO: Fix after upgrade to DRF v2.4.4 + query = models.TextField(verbose_name=_(u'Query'), editable=False) datetime_created = models.DateTimeField(verbose_name=_(u'Datetime created'), editable=False) hits = models.IntegerField(verbose_name=_(u'Hits'), editable=False) diff --git a/mayan/apps/dynamic_search/serializers.py b/mayan/apps/dynamic_search/serializers.py new file mode 100644 index 0000000000..de2066fdba --- /dev/null +++ b/mayan/apps/dynamic_search/serializers.py @@ -0,0 +1,11 @@ +from __future__ import absolute_import + +from rest_framework import serializers + +from .models import RecentSearch + + +class RecentSearchSerializer(serializers.ModelSerializer): + class Meta: + model = RecentSearch + read_only_fields = ('user', 'query', 'datetime_created', 'hits') diff --git a/mayan/apps/dynamic_search/urls.py b/mayan/apps/dynamic_search/urls.py index 37d2206f47..dcb42c8ff5 100644 --- a/mayan/apps/dynamic_search/urls.py +++ b/mayan/apps/dynamic_search/urls.py @@ -1,8 +1,17 @@ from django.conf.urls import patterns, url +from .api_views import (APIRecentSearchListView, APIRecentSearchView, + APISearchView) + urlpatterns = patterns('dynamic_search.views', url(r'^$', 'search', (), 'search'), url(r'^advanced/$', 'search', {'advanced': True}, 'search_advanced'), url(r'^again/$', 'search_again', (), 'search_again'), url(r'^results/$', 'results', (), 'results'), ) + +api_urls = patterns('', + url(r'^recent_searches/$', APIRecentSearchListView.as_view(), name='recentsearch-list'), + url(r'^recent_searches/(?P[0-9]+)/$', APIRecentSearchView.as_view(), name='recentsearch-detail'), + url(r'^search/$', APISearchView.as_view(), name='search-view'), +) diff --git a/mayan/apps/dynamic_search/views.py b/mayan/apps/dynamic_search/views.py index 62d1ef8987..07ca6448fb 100644 --- a/mayan/apps/dynamic_search/views.py +++ b/mayan/apps/dynamic_search/views.py @@ -34,36 +34,28 @@ def results(request, extra_context=None): # Simple query logger.debug('simple search') query_string = request.GET.get('q', u'').strip() - model_list, flat_list, shown_result_count, result_count, elapsed_time = document_search.simple_search(query_string) + queryset, ids, timedelta = document_search.simple_search(query_string) else: # Advanced search logger.debug('advanced search') - model_list, flat_list, shown_result_count, result_count, elapsed_time = document_search.advanced_search(request.GET) - - if shown_result_count != result_count: - title = _(u'Results, (showing only %(shown_result_count)s out of %(result_count)s)') % { - 'shown_result_count': shown_result_count, - 'result_count': result_count} - - else: - title = _(u'Results') + queryset, ids, timedelta = document_search.advanced_search(request.GET) # Update the context with the search results context.update({ - 'found_entries': model_list, - 'object_list': flat_list, - 'title': title, - 'time_delta': elapsed_time, + 'object_list': queryset, + 'time_delta': timedelta, + 'title': _(u'Results'), }) - RecentSearch.objects.add_query_for_user(request.user, request.GET, result_count) + RecentSearch.objects.add_query_for_user(request.user, request.GET, len(ids)) if extra_context: context.update(extra_context) if SHOW_OBJECT_TYPE: - context.update({'extra_columns': - [{'name': _(u'Type'), 'attribute': lambda x: x._meta.verbose_name[0].upper() + x._meta.verbose_name[1:]}]}) + context.update({ + 'extra_columns': [{'name': _(u'Type'), 'attribute': lambda x: x._meta.verbose_name[0].upper() + x._meta.verbose_name[1:]}] + }) return render_to_response('search_results.html', context, context_instance=RequestContext(request)) @@ -72,7 +64,8 @@ def results(request, extra_context=None): def search(request, advanced=False): if advanced: form = AdvancedSearchForm(data=request.GET, search_model=document_search) - return render_to_response('main/generic_form.html', + return render_to_response( + 'main/generic_form.html', { 'form': form, 'title': _(u'Advanced search'),