|
1 | 1 | import os |
2 | 2 |
|
| 3 | +import requests |
3 | 4 | from django.utils import timezone |
4 | 5 |
|
5 | 6 | from urllib.parse import urljoin |
|
16 | 17 | HttpResponse, |
17 | 18 | HttpResponseNotFound, |
18 | 19 | HttpResponseRedirect, |
| 20 | + HttpRequest, |
19 | 21 | ) |
20 | 22 | from django.shortcuts import redirect |
21 | 23 | from django.template.loader import render_to_string |
22 | 24 | from django.urls import reverse |
| 25 | +from django.utils.decorators import method_decorator |
23 | 26 | from django.views import View |
| 27 | +from django.views.decorators.cache import never_cache |
24 | 28 | from django.views.generic import TemplateView |
25 | 29 |
|
26 | 30 | from config.settings import ENABLE_DB_CACHE |
@@ -942,3 +946,57 @@ def get(self, request, requested_version): |
942 | 946 | if requested_version == "release": |
943 | 947 | new_path = "/libraries/" |
944 | 948 | return HttpResponseRedirect(new_path) |
| 949 | + |
| 950 | + |
| 951 | +@method_decorator(never_cache, name="dispatch") |
| 952 | +class QRCodeView(View): |
| 953 | + """Handles QR code urls, sending them to Plausible, then redirecting to the desired url. |
| 954 | +
|
| 955 | + QR code urls are formatted /qrc/<campaign_identifier>/desired/path/to/content/, and will |
| 956 | + result in a redirect to /desired/path/to/content/. |
| 957 | +
|
| 958 | + E.g. https://www.boost.org/qrc/pv-01/library/latest/beast/ will send this full url to Plausible, |
| 959 | + then redirect to https://www.boost.org/library/latest/beast/ |
| 960 | + """ |
| 961 | + |
| 962 | + def get(self, request: HttpRequest, campaign_identifier: str, main_path: str = ""): |
| 963 | + absolute_url = request.build_absolute_uri(request.path) |
| 964 | + referrer = request.headers.get("referer", "") |
| 965 | + user_agent = request.headers.get("user-agent", "") |
| 966 | + |
| 967 | + plausible_payload = { |
| 968 | + "name": "pageview", |
| 969 | + "domain": "qrc.boost.org", |
| 970 | + "url": absolute_url, |
| 971 | + "referrer": referrer, |
| 972 | + } |
| 973 | + |
| 974 | + headers = {"Content-Type": "application/json", "User-Agent": user_agent} |
| 975 | + |
| 976 | + client_ip = request.headers.get("x-forwarded-for", "").split(",")[0].strip() |
| 977 | + client_ip = client_ip or request.META.get("REMOTE_ADDR") |
| 978 | + |
| 979 | + if client_ip: |
| 980 | + headers["X-Forwarded-For"] = client_ip |
| 981 | + |
| 982 | + try: |
| 983 | + requests.post( |
| 984 | + "https://plausible.io/api/event", |
| 985 | + json=plausible_payload, |
| 986 | + headers=headers, |
| 987 | + timeout=2.0, |
| 988 | + ) |
| 989 | + except Exception as e: |
| 990 | + # Don’t interrupt the redirect - just log it |
| 991 | + logger.error(f"Plausible event post failed: {e}") |
| 992 | + |
| 993 | + # Now that we've sent the request url to plausible, we can redirect to the main_path |
| 994 | + # Preserve the original querystring, if any. |
| 995 | + # Example: /qrc/3/library/latest/algorithm/?x=1 -> /library/latest/algorithm/?x=1 |
| 996 | + # `main_path` is everything after qrc/<campaign>/ thanks to <path:main_path>. |
| 997 | + redirect_path = "/" + main_path if main_path else "/" |
| 998 | + qs = request.META.get("QUERY_STRING") |
| 999 | + if qs: |
| 1000 | + redirect_path = f"{redirect_path}?{qs}" |
| 1001 | + |
| 1002 | + return HttpResponseRedirect(redirect_path) |
0 commit comments