Django search utilities for Elasticsearch integration with permission-aware filtering
Project description
project-management-search-utils
Django search utilities for Elasticsearch integration with permission-aware filtering.
Overview
This package provides high-level search functionality for project management applications using Elasticsearch as the search backend. It includes:
- SearchService: Main search service for querying Elasticsearch
- SearchPermissionMixin: Permission-aware filtering for search results
- SearchIndexManager: Utilities for managing Elasticsearch indexes
Installation
From PyPI (after publishing)
pip install project-management-search-utils
From Source
pip install -e .
Development Installation
pip install -e ".[dev]"
This will also install development dependencies (pytest, black, flake8, isort).
Requirements
- Django >= 5.0
- djangorestframework >= 3.14
- django-haystack >= 3.2
- elasticsearch >= 8.0, < 9.0
- Python >= 3.11
Quick Start
Basic Search
from project_search import SearchService
from django.contrib.auth.models import User
# Create service instance
service = SearchService()
# Get user
user = User.objects.get(username='john')
# Simple search
results = service.search(
query="mobile app",
user=user
)
print(f"Found {results['total']} results")
for result in results['results']:
print(f" - {result['title']}")
Search with Filters and Pagination
from project_search import SearchService
service = SearchService()
# Search with filters
results = service.search(
query="mobile app",
filters={
"status": "active",
"health": "healthy"
},
page=1,
page_size=20,
user=request.user
)
print(f"Total results: {results['total']}")
print(f"Current page: {results['page']}")
print(f"Total pages: {results['total_pages']}")
# Print results
for result in results['results']:
print(f"- {result['title']} (Status: {result['status']})")
# Print available filters
print("Available filters:")
for filter_name, options in results['facets'].items():
print(f"\n{filter_name}:")
for option, count in options.items():
print(f" - {option}: {count}")
Autocomplete Suggestions
from project_search import SearchService
service = SearchService()
# Get autocomplete suggestions
suggestions = service.autocomplete(
query="mo", # At least 2 characters
limit=10,
user=request.user
)
for suggestion in suggestions:
print(f"- {suggestion['title']} (Type: {suggestion['type']})")
API Reference
SearchService
Main service for querying Elasticsearch.
Methods
search(query, filters=None, page=1, page_size=20, user=None)
Search for projects using Elasticsearch.
Parameters:
query(str): Search query stringfilters(dict, optional): Filter criteriastatus: Project status (e.g., 'active', 'archived')health: Project health status (e.g., 'healthy', 'at_risk')owner: Project owner username
page(int): Page number (1-indexed, default: 1)page_size(int): Results per page (default: 20)user(User): Django User object for permission filtering
Returns: Dictionary with:
{
"results": [
{
"id": 1,
"title": "Mobile App",
"description": "iOS and Android app",
"owner": {"id": 1, "username": "john"},
"status": "active",
"health": "healthy",
"progress": 75,
"tags": [
{"id": 1, "name": "frontend", "color": "#FF5733"},
{"id": 2, "name": "mobile", "color": "#33FF57"}
]
}
],
"facets": {
"status": {"active": 45, "archived": 12},
"health": {"healthy": 40, "at_risk": 17},
"owner": {"john": 30, "jane": 27}
},
"total": 57,
"page": 1,
"page_size": 20,
"total_pages": 3
}
Raises:
ValueError: If query is emptyPermissionDenied: If user lacks permission
Example:
results = service.search(
query="dashboard",
filters={"status": "active"},
page=1,
user=request.user
)
autocomplete(query, limit=10, user=None)
Get autocomplete suggestions.
Parameters:
query(str): Partial query for prefix matching (minimum 2 characters)limit(int): Maximum number of suggestions (default: 10)user(User): Django User object for permission filtering
Returns: List of suggestions:
[
{
"id": 1,
"title": "Mobile App",
"type": "project"
},
{
"id": 2,
"title": "Mobile First Design",
"type": "project"
}
]
Raises:
ValueError: If query is less than 2 characters
Example:
suggestions = service.autocomplete(
query="mob",
limit=5,
user=request.user
)
SearchPermissionMixin
Provides permission-aware filtering for search results.
Methods
get_accessible_projects(user)
Get all projects accessible to the given user.
Parameters:
user(User): Django User object
Returns: QuerySet of accessible projects
Raises:
ValueError: If user is not authenticated
Example:
from project_search import SearchPermissionMixin
mixin = SearchPermissionMixin()
accessible = mixin.get_accessible_projects(user=request.user)
filter_by_permissions(queryset, user)
Filter a queryset to include only accessible projects.
Parameters:
queryset(QuerySet): QuerySet to filteruser(User): Django User object
Returns: Filtered QuerySet
Example:
projects = Project.objects.all()
filtered = mixin.filter_by_permissions(projects, user=request.user)
has_project_access(user, project_id)
Check if user has access to a specific project.
Parameters:
user(User): Django User objectproject_id(int): Project ID
Returns: Boolean indicating access
Example:
if mixin.has_project_access(user=request.user, project_id=42):
print("User has access")
get_accessible_project_ids(user)
Get list of accessible project IDs (useful for Elasticsearch filtering).
Parameters:
user(User): Django User object
Returns: List of project IDs
Example:
project_ids = mixin.get_accessible_project_ids(user=request.user)
# Use for filtering Elasticsearch results
SearchIndexManager
Utilities for managing Elasticsearch indexes.
Methods
rebuild_all_indexes()
Rebuild all search indexes from database.
Returns: Dictionary with rebuild statistics
Example:
from project_search import SearchIndexManager
manager = SearchIndexManager()
result = manager.rebuild_all_indexes()
print(f"Indexed {result['total_documents']} documents")
rebuild_index(index_name)
Rebuild a specific index.
Parameters:
index_name(str): Name of index ('projects', 'milestones', 'activities', or 'tags')
Returns: Dictionary with rebuild information
Example:
result = manager.rebuild_index('projects')
optimize_index(index_name)
Optimize an index for better performance.
Parameters:
index_name(str): Name of index to optimize
Returns: Dictionary with optimization results
delete_index(index_name)
Delete an index (WARNING: irreversible).
Parameters:
index_name(str): Name of index to delete
Returns: Dictionary with deletion status
get_index_stats()
Get statistics for all search indexes.
Returns: Dictionary with index statistics including document counts and health status
Example:
stats = manager.get_index_stats()
print(f"Total documents: {stats['total_documents']}")
print(f"Health: {stats['health_status']}")
get_index_mapping(index_name)
Get the mapping (schema) for an index.
Parameters:
index_name(str): Name of index
Returns: Dictionary with index mapping
reindex_document(doc_id, doc_type)
Reindex a single document.
Parameters:
doc_id(int): Document IDdoc_type(str): Document type ('project', 'milestone', 'activity', 'tag')
Returns: Dictionary with reindexing status
clear_cache()
Clear Elasticsearch request cache.
Returns: Dictionary with cache clear status
health_check()
Check the health of Elasticsearch.
Returns: Dictionary with health information and any issues found
Integration with Django
Settings Configuration
Add to your settings.py:
INSTALLED_APPS = [
# ...
'haystack',
'projects',
]
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchEngine',
'URL': os.getenv('ELASTICSEARCH_URL', 'http://elasticsearch:9200/'),
'INDEX_NAME': 'projects_index',
},
}
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
Django Admin Integration
Use the SearchPermissionMixin to filter admin querysets:
from django.contrib import admin
from project_search import SearchPermissionMixin
from projects.models import Project
class ProjectAdmin(admin.ModelAdmin, SearchPermissionMixin):
list_display = ('title', 'owner', 'status')
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
return self.filter_by_permissions(qs, user=request.user)
admin.site.register(Project, ProjectAdmin)
Usage in Views
Class-Based View with SearchService
from django.views.generic import ListView
from project_search import SearchService
from projects.models import Project
class ProjectSearchView(ListView):
model = Project
template_name = 'projects/search.html'
context_object_name = 'results'
def get_queryset(self):
service = SearchService()
query = self.request.GET.get('q', '')
if not query:
return Project.objects.none()
results = service.search(
query=query,
user=self.request.user
)
return results['results']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
service = SearchService()
query = self.request.GET.get('q', '')
results = service.search(query=query, user=self.request.user)
context['facets'] = results['facets']
context['total'] = results['total']
return context
REST API View with DRF
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from project_search import SearchService
class SearchAPIView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
query = request.query_params.get('q', '')
filters = {
'status': request.query_params.get('status'),
'health': request.query_params.get('health'),
}
page = int(request.query_params.get('page', 1))
service = SearchService()
results = service.search(
query=query,
filters={k: v for k, v in filters.items() if v},
page=page,
user=request.user
)
return Response(results)
Performance Considerations
Elasticsearch Configuration
For production deployments:
- Index Sharding: Configure appropriate number of shards based on data volume
- Replicas: Set replica count for high availability
- Refresh Interval: Adjust for your throughput requirements
- Bulk Indexing: Use bulk API for initial index creation
Caching
Results are not cached by default. Implement your own caching strategy:
from django.core.cache import cache
def cached_search(query, user):
cache_key = f"search:{query}:{user.id}"
results = cache.get(cache_key)
if results is None:
service = SearchService()
results = service.search(query=query, user=user)
cache.set(cache_key, results, timeout=300) # 5 minutes
return results
Query Optimization
- Limit page size: Don't return excessive results per page
- Use filters: Narrow results before full-text search
- Debounce autocomplete: Implement client-side debouncing for suggestions
- Index optimization: Periodically optimize indexes after bulk operations
Testing
Run tests with:
pytest
Run tests with coverage:
pytest --cov=project_search
Run specific test file:
pytest tests/test_search_service.py
Documentation
For detailed documentation on Elasticsearch integration, see the full project documentation:
Contributing
Contributions are welcome! Please ensure:
- Code follows PEP 8 style guide (checked with flake8)
- Code is formatted with black
- Import statements are sorted with isort
- All tests pass
- New features include tests
Format code before submitting:
black project_search/
isort project_search/
flake8 project_search/
License
MIT License - see LICENSE file for details
Support
For issues, questions, or suggestions, please visit: https://github.com/paulkokos/project-management-dashboard/issues
Related Projects
- @paulkokos/search-components: NPM package for React search UI components
- project-management-dashboard: Full project management application
See PACKAGES.md for more information on available packages.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file project_management_search_utils-1.0.9.tar.gz.
File metadata
- Download URL: project_management_search_utils-1.0.9.tar.gz
- Upload date:
- Size: 11.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
36d85cd9c0ce2cd2af73a128d0c13592ba5f45e257c29e4e9118aa2966519dd4
|
|
| MD5 |
6a1f398b15e95ba9b02d74ed21cf1f77
|
|
| BLAKE2b-256 |
b215a159c7d6b9f39cd8b6924c4dc563cbe090dc0b13b6b7b95b9e4e45e170cb
|
File details
Details for the file project_management_search_utils-1.0.9-py3-none-any.whl.
File metadata
- Download URL: project_management_search_utils-1.0.9-py3-none-any.whl
- Upload date:
- Size: 12.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6194eb71496a4019111413a937a0a61891b01a111fc9764b8f7e2dfec6d2506b
|
|
| MD5 |
dc2536c74113b1dc207487c4b63a7a9e
|
|
| BLAKE2b-256 |
15c3e3e2dcd621decd53860edd68f9bc0c6613e10051b3871bf90d740bf40fd8
|