|
4 | 4 | import importlib |
5 | 5 | import logging |
6 | 6 | import re |
7 | | -import time |
8 | 7 | from copy import deepcopy |
9 | 8 | from datetime import datetime, timedelta |
10 | | -from functools import wraps |
11 | 9 | from io import BytesIO, StringIO |
12 | 10 | from pathlib import Path |
13 | 11 | from urllib.parse import parse_qs, urlencode, urlparse |
14 | 12 |
|
15 | 13 | import pandas as pd |
16 | 14 | from django.apps import apps |
17 | | -from django.db import connection, reset_queries |
18 | 15 | from django.db.migrations.operations.base import Operation |
19 | 16 | from django.forms.models import modelform_factory |
20 | 17 | from django.utils.cache import set_response_etag |
21 | 18 | from django.utils.timezone import now |
22 | 19 | from openpyxl.utils import get_column_letter |
23 | | -from rest_framework.response import Response |
24 | 20 | from treebeard.mp_tree import MP_Node |
25 | 21 |
|
26 | 22 | logger = logging.getLogger(__name__) |
@@ -396,10 +392,6 @@ def timekeeper(): |
396 | 392 | def get_etag_for_cachedrequest(request, *args, **kwargs): |
397 | 393 | """ |
398 | 394 | Generate an ETag for the request based on path and query parameters. |
399 | | -
|
400 | | - This is a simplified version that generates ETags without requiring a CachedRequest model |
401 | | - or permissions system. The ETag is deterministic based on the request URL. |
402 | | -
|
403 | 395 | If the request includes a _refresh parameter, returns None to force a cache miss. |
404 | 396 | """ |
405 | 397 | u = urlparse(request.get_full_path()) |
@@ -435,120 +427,3 @@ def set_etag_for_response(response): |
435 | 427 | logger.info("Created etag header in %s seconds" % round(t.elapsed().total_seconds(), 2)) |
436 | 428 |
|
437 | 429 | return response |
438 | | - |
439 | | - |
440 | | -def etag_response(etag_func=None, enable_etag=True): |
441 | | - """ |
442 | | - Decorator that adds ETag support with 304 Not Modified responses for DRF viewsets. |
443 | | - """ |
444 | | - |
445 | | - if etag_func is None: |
446 | | - etag_func = get_etag_for_cachedrequest |
447 | | - |
448 | | - def decorator(view_func): |
449 | | - @wraps(view_func) |
450 | | - def wrapped_view(self, request, *args, **kwargs): |
451 | | - start_time = time.time() |
452 | | - |
453 | | - # Reset query count for accurate measurement |
454 | | - reset_queries() |
455 | | - initial_query_count = len(connection.queries) |
456 | | - |
457 | | - # Generate ETag based on request using existing function |
458 | | - etag_start = time.time() |
459 | | - etag = etag_func(request, *args, **kwargs) |
460 | | - etag_time = time.time() - etag_start |
461 | | - |
462 | | - # Check if ETag caching is enabled and client sent matching ETag |
463 | | - client_etag = request.META.get("HTTP_IF_NONE_MATCH") |
464 | | - |
465 | | - # Normalize ETags for comparison (handle weak ETags with W/ prefix) |
466 | | - def normalize_etag(etag_value): |
467 | | - """Strip W/ prefix from weak ETags for comparison.""" |
468 | | - if etag_value: |
469 | | - return etag_value.replace("W/", "").strip() |
470 | | - return etag_value |
471 | | - |
472 | | - normalized_client_etag = normalize_etag(client_etag) |
473 | | - normalized_server_etag = normalize_etag(etag) |
474 | | - |
475 | | - # Log for debugging browser issues |
476 | | - if client_etag: |
477 | | - logger.debug( |
478 | | - f"Client ETag: {client_etag} (normalized: {normalized_client_etag}), " |
479 | | - f"Server ETag: {etag} (normalized: {normalized_server_etag})" |
480 | | - ) |
481 | | - |
482 | | - if enable_etag and etag and normalized_client_etag == normalized_server_etag: |
483 | | - # Cache HIT - return 304 |
484 | | - response = Response(status=304) |
485 | | - response["ETag"] = etag |
486 | | - # Ensure Vary header is set for browser caching |
487 | | - response["Vary"] = "Accept, Accept-Language, Cookie" |
488 | | - |
489 | | - elapsed_time = time.time() - start_time |
490 | | - query_count = len(connection.queries) - initial_query_count |
491 | | - |
492 | | - logger.info( |
493 | | - f"[ETAG HIT] " |
494 | | - f"path={request.path} | " |
495 | | - f"etag={etag[:12]}... | " |
496 | | - f"time={elapsed_time:.3f}s | " |
497 | | - f"etag_gen={etag_time:.3f}s | " |
498 | | - f"queries={query_count} | " |
499 | | - f"size=0 bytes | " |
500 | | - f"status=304" |
501 | | - ) |
502 | | - return response |
503 | | - elif enable_etag and client_etag and etag: |
504 | | - # Client sent ETag but it doesn't match |
505 | | - logger.debug(f"[ETAG MISMATCH] Client: {client_etag} vs Server: {etag}") |
506 | | - |
507 | | - # Cache MISS or ETag disabled - render full response |
508 | | - render_start = time.time() |
509 | | - response = view_func(self, request, *args, **kwargs) |
510 | | - render_time = time.time() - render_start |
511 | | - |
512 | | - if enable_etag and etag: |
513 | | - response["ETag"] = etag |
514 | | - response["Cache-Control"] = "public, max-age=2592000" # 30 days |
515 | | - existing_vary = response.get("Vary", "") |
516 | | - if "Accept" not in existing_vary: |
517 | | - response["Vary"] = "Accept, Accept-Language, Cookie" |
518 | | - |
519 | | - # Calculate metrics |
520 | | - elapsed_time = time.time() - start_time |
521 | | - query_count = len(connection.queries) - initial_query_count |
522 | | - |
523 | | - # Get response size safely |
524 | | - response_size = 0 |
525 | | - try: |
526 | | - # Try to get size from data attribute first (DRF Response) |
527 | | - if hasattr(response, "data"): |
528 | | - # Estimate size from data length |
529 | | - response_size = len(str(response.data)) |
530 | | - elif hasattr(response, "content"): |
531 | | - response_size = len(response.content) |
532 | | - except (AssertionError, AttributeError): |
533 | | - # If we can't determine size, just log 0 |
534 | | - response_size = 0 |
535 | | - |
536 | | - # Log with different prefix based on whether ETag is enabled |
537 | | - log_prefix = "[ETAG MISS]" if enable_etag else "[NO ETAG]" |
538 | | - logger.info( |
539 | | - f"{log_prefix} " |
540 | | - f"path={request.path} | " |
541 | | - f"etag={etag[:12] if etag else 'None'}... | " |
542 | | - f"time={elapsed_time:.3f}s | " |
543 | | - f"etag_gen={etag_time:.3f}s | " |
544 | | - f"render={render_time:.3f}s | " |
545 | | - f"queries={query_count} | " |
546 | | - f"size={response_size:,} bytes (estimated) | " |
547 | | - f"status={response.status_code}" |
548 | | - ) |
549 | | - |
550 | | - return response |
551 | | - |
552 | | - return wrapped_view |
553 | | - |
554 | | - return decorator |
0 commit comments