|
2 | 2 | import re |
3 | 3 | from urllib.parse import urljoin |
4 | 4 |
|
| 5 | +import requests |
5 | 6 | import structlog |
6 | 7 | from bs4 import BeautifulSoup |
7 | 8 | from dateutil.parser import parse |
|
13 | 14 | HttpResponse, |
14 | 15 | HttpResponseNotFound, |
15 | 16 | HttpResponseRedirect, |
| 17 | + HttpRequest, |
16 | 18 | ) |
17 | 19 | from django.shortcuts import redirect |
18 | 20 | from django.template.loader import render_to_string |
19 | 21 | from django.urls import reverse |
| 22 | +from django.utils.decorators import method_decorator |
20 | 23 | from django.views import View |
| 24 | +from django.views.decorators.cache import never_cache |
21 | 25 | from django.views.generic import TemplateView |
22 | 26 | from requests.compat import chardet |
23 | 27 |
|
@@ -898,3 +902,57 @@ def get(self, request, requested_version): |
898 | 902 | if requested_version == "release": |
899 | 903 | new_path = "/libraries/" |
900 | 904 | return HttpResponseRedirect(new_path) |
| 905 | + |
| 906 | + |
| 907 | +@method_decorator(never_cache, name="dispatch") |
| 908 | +class QRCodeView(View): |
| 909 | + """Handles QR code urls, sending them to Plausible, then redirecting to the desired url. |
| 910 | +
|
| 911 | + QR code urls are formatted /qrc/<campaign_identifier>/desired/path/to/content/, and will |
| 912 | + result in a redirect to /desired/path/to/content/. |
| 913 | +
|
| 914 | + E.g. https://www.boost.org/qrc/pv-01/library/latest/beast/ will send this full url to Plausible, |
| 915 | + then redirect to https://www.boost.org/library/latest/beast/ |
| 916 | + """ |
| 917 | + |
| 918 | + def get(self, request: HttpRequest, campaign_identifier: str, main_path: str = ""): |
| 919 | + absolute_url = request.build_absolute_uri(request.path) |
| 920 | + referrer = request.META.get("HTTP_REFERER", "") |
| 921 | + user_agent = request.META.get("HTTP_USER_AGENT", "") |
| 922 | + |
| 923 | + plausible_payload = { |
| 924 | + "name": "pageview", |
| 925 | + "domain": "qrc.boost.org", |
| 926 | + "url": absolute_url, |
| 927 | + "referrer": referrer, |
| 928 | + } |
| 929 | + |
| 930 | + headers = {"Content-Type": "application/json", "User-Agent": user_agent} |
| 931 | + |
| 932 | + client_ip = request.META.get("HTTP_X_FORWARDED_FOR", "").split(",")[0].strip() |
| 933 | + client_ip = client_ip or request.META.get("REMOTE_ADDR") |
| 934 | + |
| 935 | + if client_ip: |
| 936 | + headers["X-Forwarded-For"] = client_ip |
| 937 | + |
| 938 | + try: |
| 939 | + requests.post( |
| 940 | + "https://plausible.io/api/event", |
| 941 | + json=plausible_payload, |
| 942 | + headers=headers, |
| 943 | + timeout=2.0, |
| 944 | + ) |
| 945 | + except Exception as e: |
| 946 | + # Don’t interrupt the redirect - just log it |
| 947 | + logger.error(f"Plausible event post failed: {e}") |
| 948 | + |
| 949 | + # Now that we've sent the request url to plausible, we can redirect to the main_path |
| 950 | + # Preserve the original querystring, if any. |
| 951 | + # Example: /qrc/3/library/latest/algorithm/?x=1 -> /library/latest/algorithm/?x=1 |
| 952 | + # `main_path` is everything after qrc/<campaign>/ thanks to <path:main_path>. |
| 953 | + redirect_path = "/" + main_path if main_path else "/" |
| 954 | + qs = request.META.get("QUERY_STRING") |
| 955 | + if qs: |
| 956 | + redirect_path = f"{redirect_path}?{qs}" |
| 957 | + |
| 958 | + return HttpResponseRedirect(redirect_path) |
0 commit comments