From 99354b40d9d1a8a04293d201cedb64a0704fdd5b Mon Sep 17 00:00:00 2001 From: Girik1105 Date: Tue, 17 Feb 2026 11:24:41 -0700 Subject: [PATCH 1/3] [HOP-18] Added middleware, templates, views, urls, updated sidebar to have a user option dropdown --- hospexplorer/ask/admin.py | 19 ++++++- hospexplorer/ask/context_processors.py | 8 +++ hospexplorer/ask/middleware.py | 56 +++++++++++++++++++ hospexplorer/ask/migrations/0001_initial.py | 31 ++++++++++ hospexplorer/ask/models.py | 21 ++++++- hospexplorer/ask/templates/_base.html | 37 +++++++++--- hospexplorer/ask/templates/index.html | 1 + .../ask/templates/terms/terms_accept.html | 30 ++++++++++ .../templates/terms/terms_of_use_content.html | 17 ++++++ .../ask/templates/terms/terms_view.html | 25 +++++++++ hospexplorer/ask/urls.py | 2 + hospexplorer/ask/utils.py | 5 ++ hospexplorer/ask/views.py | 50 ++++++++++++++++- hospexplorer/hospexplorer/settings.py | 5 ++ 14 files changed, 294 insertions(+), 13 deletions(-) create mode 100644 hospexplorer/ask/context_processors.py create mode 100644 hospexplorer/ask/middleware.py create mode 100644 hospexplorer/ask/migrations/0001_initial.py create mode 100644 hospexplorer/ask/templates/terms/terms_accept.html create mode 100644 hospexplorer/ask/templates/terms/terms_of_use_content.html create mode 100644 hospexplorer/ask/templates/terms/terms_view.html create mode 100644 hospexplorer/ask/utils.py diff --git a/hospexplorer/ask/admin.py b/hospexplorer/ask/admin.py index 8c38f3f..6f28567 100644 --- a/hospexplorer/ask/admin.py +++ b/hospexplorer/ask/admin.py @@ -1,3 +1,20 @@ from django.contrib import admin +from ask.models import TermsAcceptance -# Register your models here. + +@admin.register(TermsAcceptance) +class TermsAcceptanceAdmin(admin.ModelAdmin): + list_display = ("user", "terms_version", "accepted_at", "ip_address") + list_filter = ("terms_version", "accepted_at") + search_fields = ("user__username", "user__email", "ip_address") + readonly_fields = ("user", "terms_version", "accepted_at", "ip_address") + ordering = ("-accepted_at",) + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False diff --git a/hospexplorer/ask/context_processors.py b/hospexplorer/ask/context_processors.py new file mode 100644 index 0000000..fb92320 --- /dev/null +++ b/hospexplorer/ask/context_processors.py @@ -0,0 +1,8 @@ +from django.conf import settings + + +def terms_status(request): + if hasattr(request, "user") and request.user.is_authenticated: + accepted = request.session.get("terms_accepted_version") == settings.TERMS_VERSION + return {"terms_accepted": accepted, "terms_version": settings.TERMS_VERSION} + return {} diff --git a/hospexplorer/ask/middleware.py b/hospexplorer/ask/middleware.py new file mode 100644 index 0000000..c507bbb --- /dev/null +++ b/hospexplorer/ask/middleware.py @@ -0,0 +1,56 @@ +from django.shortcuts import redirect +from django.conf import settings +from django.urls import resolve +from ask.models import TermsAcceptance + + +class TermsAcceptanceMiddleware: + EXEMPT_URL_NAMES = {"terms-accept", "terms-view"} + + EXEMPT_URL_PREFIXES = ("accounts/", "admin/") + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + if self._requires_terms_check(request): + current_version = settings.TERMS_VERSION + + # Check session cache first (no DB query) + if request.session.get("terms_accepted_version") == current_version: + return self.get_response(request) + + # Session miss — check DB once + has_accepted = TermsAcceptance.objects.filter( + user=request.user, + terms_version=current_version, + ).exists() + + if has_accepted: + # Cache in session so we never hit DB again this session + request.session["terms_accepted_version"] = current_version + else: + return redirect("ask:terms-accept") + + return self.get_response(request) + + def _requires_terms_check(self, request): + if not hasattr(request, "user") or not request.user.is_authenticated: + return False + + path = request.path + app_root = getattr(settings, "APP_ROOT", "") + + for prefix in self.EXEMPT_URL_PREFIXES: + full_prefix = f"/{app_root}{prefix}" + if path.startswith(full_prefix): + return False + + try: + resolved = resolve(path) + if resolved.url_name in self.EXEMPT_URL_NAMES: + return False + except Exception: + return False + + return True diff --git a/hospexplorer/ask/migrations/0001_initial.py b/hospexplorer/ask/migrations/0001_initial.py new file mode 100644 index 0000000..0eeba58 --- /dev/null +++ b/hospexplorer/ask/migrations/0001_initial.py @@ -0,0 +1,31 @@ +# Generated by Django 6.0.1 on 2026-02-17 17:35 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='TermsAcceptance', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('terms_version', models.CharField(max_length=20)), + ('accepted_at', models.DateTimeField(auto_now_add=True)), + ('ip_address', models.GenericIPAddressField()), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terms_acceptances', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-accepted_at'], + 'indexes': [models.Index(fields=['user', 'terms_version'], name='ask_termsac_user_id_b13742_idx')], + }, + ), + ] diff --git a/hospexplorer/ask/models.py b/hospexplorer/ask/models.py index 71a8362..a8a4a18 100644 --- a/hospexplorer/ask/models.py +++ b/hospexplorer/ask/models.py @@ -1,3 +1,22 @@ from django.db import models +from django.conf import settings -# Create your models here. + +class TermsAcceptance(models.Model): + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name="terms_acceptances", + ) + terms_version = models.CharField(max_length=20) + accepted_at = models.DateTimeField(auto_now_add=True) + ip_address = models.GenericIPAddressField() + + class Meta: + ordering = ["-accepted_at"] + indexes = [ + models.Index(fields=["user", "terms_version"]), + ] + + def __str__(self): + return f"{self.user.username} accepted v{self.terms_version} on {self.accepted_at}" diff --git a/hospexplorer/ask/templates/_base.html b/hospexplorer/ask/templates/_base.html index 6665c53..c2ceb3f 100644 --- a/hospexplorer/ask/templates/_base.html +++ b/hospexplorer/ask/templates/_base.html @@ -17,19 +17,40 @@
-