From 4e5a5c4b1b4e18054fe0d3c9b7fbd8d07fc08527 Mon Sep 17 00:00:00 2001 From: Kumi Date: Sat, 25 Oct 2025 20:05:05 +0200 Subject: [PATCH] feat: Pagination for hosts overrides --- mallarddns/frontend/forms.py | 24 +++++++ .../templates/frontend/hosts_list.html | 69 +++++++++++++++++++ mallarddns/frontend/templatetags/__init__.py | 0 .../frontend/templatetags/pagination_tags.py | 32 +++++++++ mallarddns/frontend/views.py | 34 ++++++++- mallarddns/records/models.py | 2 +- 6 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 mallarddns/frontend/templatetags/__init__.py create mode 100644 mallarddns/frontend/templatetags/pagination_tags.py diff --git a/mallarddns/frontend/forms.py b/mallarddns/frontend/forms.py index 498c533..fa948de 100644 --- a/mallarddns/frontend/forms.py +++ b/mallarddns/frontend/forms.py @@ -29,3 +29,27 @@ class RecordForm(forms.ModelForm): class Meta: model = Record fields = ["name", "type", "data", "ttl", "priority"] + + +class HostsOverrideRecordFilterForm(forms.Form): + search = forms.CharField( + required=False, + label="", + widget=forms.TextInput(attrs={'placeholder': 'Name or Value'}) + ) + record_type = forms.ChoiceField( + choices=[('', '--Type--'), ("A", "A"), ("AAAA", "AAAA")], + required=False, + label="" + ) + enabled = forms.ChoiceField( + choices=[('', '--Enabled--'), ('yes', '✓ Enabled'), ('no', '✗ Disabled')], + required=False, + label="" + ) + import_source = forms.ModelChoiceField( + queryset=HostsFileImportSource.objects.all(), + required=False, + label="", + empty_label="--Import Source--" + ) diff --git a/mallarddns/frontend/templates/frontend/hosts_list.html b/mallarddns/frontend/templates/frontend/hosts_list.html index 55f97c5..8bf3d00 100644 --- a/mallarddns/frontend/templates/frontend/hosts_list.html +++ b/mallarddns/frontend/templates/frontend/hosts_list.html @@ -1,7 +1,23 @@ {% extends "frontend/base.html" %} +{% load pagination_tags %} {% block content %}

Host Override Records

Add Host Record +
+
{{ filter_form.search }}
+
{{ filter_form.record_type }}
+
{{ filter_form.enabled }}
+
{{ filter_form.import_source }}
+ + {% if request.GET %} + Reset + {% endif %} +
@@ -41,4 +57,57 @@ {% endfor %}
+ {% if is_paginated and paginator.num_pages > 1 %} + +{% endif %} {% endblock content %} diff --git a/mallarddns/frontend/templatetags/__init__.py b/mallarddns/frontend/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mallarddns/frontend/templatetags/pagination_tags.py b/mallarddns/frontend/templatetags/pagination_tags.py new file mode 100644 index 0000000..9a96aea --- /dev/null +++ b/mallarddns/frontend/templatetags/pagination_tags.py @@ -0,0 +1,32 @@ +from django import template + +register = template.Library() + +@register.simple_tag +def smart_pager(page, num_pages, window=2): + """ + Smart pagination: always show 1, N, and window around current page. + Returns list, e.g. [1, None, 4, 5, 6, None, 19], None means '…' + """ + page = int(page) + num_pages = int(num_pages) + window = int(window) + result = [] + + left = max(1, page - window) + right = min(num_pages, page + window) + + # Always show 1 + result.append(1) + # Left gap + if left > 2: + result.append(None) + for p in range(max(2, left), min(right+1, num_pages)): + result.append(p) + # Right gap + if right < num_pages - 1: + result.append(None) + # Always show last + if num_pages > 1: + result.append(num_pages) + return result \ No newline at end of file diff --git a/mallarddns/frontend/views.py b/mallarddns/frontend/views.py index b99fb22..edfa1b1 100644 --- a/mallarddns/frontend/views.py +++ b/mallarddns/frontend/views.py @@ -11,6 +11,8 @@ from django.shortcuts import get_object_or_404 from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib import messages from django.http import HttpResponseRedirect +from django.db import models +from django.core.paginator import Paginator from mallarddns.records.models import ( HostsOverrideRecord, @@ -19,7 +21,7 @@ from mallarddns.records.models import ( Record, ) from mallarddns.records.hosts_import import import_hosts_from_url -from .forms import HostsOverrideForm, HostsImportForm, ZoneForm, RecordForm +from .forms import HostsOverrideForm, HostsImportForm, ZoneForm, RecordForm, HostsOverrideRecordFilterForm # Dashboard @@ -36,6 +38,36 @@ class HostsOverrideListView(LoginRequiredMixin, ListView): model = HostsOverrideRecord context_object_name = "records" ordering = ["-enabled", "name"] + paginate_by = 100 # Show 100 records per page + + def get_filter_form(self): + data = self.request.GET if self.request.GET else None + return HostsOverrideRecordFilterForm(data) + + def get_queryset(self): + qs = HostsOverrideRecord.objects.all() + form = self.get_filter_form() + if form.is_valid(): + cd = form.cleaned_data + if cd.get("search"): + # Filter by name or value substring (case-insensitive) + qs = qs.filter( + models.Q(name__icontains=cd["search"]) | models.Q(value__icontains=cd["search"]) + ) + if cd.get("record_type"): + qs = qs.filter(record_type=cd["record_type"]) + if cd.get("enabled") == "yes": + qs = qs.filter(enabled=True) + elif cd.get("enabled") == "no": + qs = qs.filter(enabled=False) + if cd.get("import_source"): + qs = qs.filter(import_source=cd["import_source"]) + return qs.order_by(*self.ordering) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["filter_form"] = self.get_filter_form() + return context class HostsOverrideCreateView(LoginRequiredMixin, CreateView): diff --git a/mallarddns/records/models.py b/mallarddns/records/models.py index bcbabb5..635c4f0 100644 --- a/mallarddns/records/models.py +++ b/mallarddns/records/models.py @@ -94,7 +94,7 @@ class HostsFileImportSource(models.Model): last_updated = models.DateTimeField(auto_now=True) def __str__(self): - return self.url + return self.description if self.description else self.url class RootServer(models.Model):