import json import re import structlog from django.core.cache import cache logger = structlog.get_logger(__name__) # TODO: refactor so that the query from the client is loaded COURSE_QUERY = """ query courseQuery($slug: String!, $user: String) { course(slug: $slug) { id title slug category_name profiles course_session_users(id: $user) { id __typename chosen_profile course_session { id __typename } } configuration { id enable_circle_documents enable_learning_mentor enable_competence_certificates is_uk is_vv __typename } action_competences { competence_id ...CoursePageFields performance_criteria { competence_id learning_unit { id slug evaluate_url __typename } ...CoursePageFields __typename } __typename } learning_path { ...CoursePageFields topics { is_visible ...CoursePageFields circles { description goals profiles is_base_circle ...CoursePageFields learning_sequences { icon ...CoursePageFields learning_units { evaluate_url ...CoursePageFields performance_criteria { ...CoursePageFields __typename } learning_contents { can_user_self_toggle_course_completion content_url minutes description ...CoursePageFields ... on LearningContentAssignmentObjectType { assignment_type content_assignment { id assignment_type __typename } competence_certificate { ...CoursePageFields __typename } __typename } ... on LearningContentEdoniqTestObjectType { checkbox_text has_extended_time_test content_assignment { id assignment_type __typename } competence_certificate { ...CoursePageFields __typename } __typename } ... on LearningContentRichTextObjectType { text __typename } __typename } __typename } __typename } __typename } __typename } __typename } __typename } } fragment CoursePageFields on CoursePageInterface { title id slug content_type frontend_url __typename } """ COURSE_SESSION_USER_QUERY = """ query courseQuery($slug: String!, $user: String) { course(slug: $slug) { course_session_users(id: $user) { id __typename chosen_profile course_session { id __typename } } } } """ class CourseQueryCachingMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): # Check if the request is a GraphQL request if request.content_type == "application/json" and "/graphql" in request.path: try: # Parse the GraphQL query from the request body body = json.loads(request.body) query = body.get("query", "") if self.normalize_string(query) == self.normalize_string(COURSE_QUERY): logger.debug("CourseQuery detected") slug = body.get("variables", {}).get("slug", "") if slug: key = f"course_query_{slug}" cache_data = cache.get(key) if cache_data: # Cache hit: only make course session user query and add to cache data logger.debug("cache hit", key=key) body["query"] = COURSE_SESSION_USER_QUERY request._body = json.dumps(body).encode("utf-8") response = self.get_response(request) content = json.loads(response.content) cache_data["data"]["course"]["course_session_users"] = ( content["data"]["course"]["course_session_users"] ) response.content = json.dumps(cache_data) return response else: # Cache miss: make the original query and cache the result logger.debug("cache miss", key=key) response = self.get_response(request) content = json.loads(response.content) del content["data"]["course"]["course_session_users"] cache.set(key, content, 60 * 10) return response except Exception as e: # Handle any exceptions in parsing or filtering logger.error("Error in CourseQueryCachingMiddleware", exc_info=e) # Continue processing the request if not blocked response = self.get_response(request) return response def normalize_string(self, s): # Remove all whitespace characters (space, tabs, newlines) return re.sub(r"\s+", "", s)