mirror of
https://git.private.coffee/PrivateCoffee/mallarddns.git
synced 2026-04-21 04:56:23 +05:30
feat: Pagination for hosts overrides
This commit is contained in:
parent
04f4aff400
commit
4e5a5c4b1b
6 changed files with 159 additions and 2 deletions
|
|
@ -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--"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,23 @@
|
|||
{% extends "frontend/base.html" %}
|
||||
{% load pagination_tags %}
|
||||
{% block content %}
|
||||
<h1>Host Override Records</h1>
|
||||
<a class="button" href="{% url 'hosts_add' %}">Add Host Record</a>
|
||||
<form method="get"
|
||||
style="margin-bottom:1.5em;
|
||||
display: flex;
|
||||
gap: 0.7em;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end">
|
||||
<div>{{ filter_form.search }}</div>
|
||||
<div>{{ filter_form.record_type }}</div>
|
||||
<div>{{ filter_form.enabled }}</div>
|
||||
<div>{{ filter_form.import_source }}</div>
|
||||
<button type="submit" class="button button-secondary">Filter</button>
|
||||
{% if request.GET %}
|
||||
<a class="button" style="margin-left:6px;" href="{% url 'hosts_list' %}">Reset</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -41,4 +57,57 @@
|
|||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if is_paginated and paginator.num_pages > 1 %}
|
||||
<div class="pagination"
|
||||
style="margin:1.5em 0;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap:0.7em;
|
||||
flex-wrap:wrap">
|
||||
{% with page=page_obj.number num_pages=paginator.num_pages %}
|
||||
<!-- Previous arrow -->
|
||||
{% if page_obj.has_previous %}
|
||||
<a class="button button-secondary"
|
||||
href="?{% for k,v in request.GET.items %}{% if k != 'page' %}{{ k }}={{ v|urlencode }}&{% endif %}{% endfor %}page={{ page_obj.previous_page_number }}">←</a>
|
||||
{% endif %}
|
||||
{% smart_pager page num_pages 2 as pager %}
|
||||
{% for p in pager %}
|
||||
{% if p %}
|
||||
{% if p == page %}
|
||||
<span style="font-weight:bold;
|
||||
background:#c1d1fa;
|
||||
padding:4px 12px;
|
||||
border-radius:5px">{{ p }}</span>
|
||||
{% else %}
|
||||
<a class="button button-secondary"
|
||||
href="?{% for k,v in request.GET.items %}{% if k != 'page' %}{{ k }}={{ v|urlencode }}&{% endif %}{% endfor %}page={{ p }}">{{ p }}</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span style="padding:0 5px;">…</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<!-- Next arrow -->
|
||||
{% if page_obj.has_next %}
|
||||
<a class="button button-secondary"
|
||||
href="?{% for k,v in request.GET.items %}{% if k != 'page' %}{{ k }}={{ v|urlencode }}&{% endif %}{% endfor %}page={{ page_obj.next_page_number }}">→</a>
|
||||
{% endif %}
|
||||
<!-- Jump to page -->
|
||||
<form method="get" style="display:inline; margin-left: 9px;">
|
||||
{% for k, v in request.GET.items %}
|
||||
{% if k != 'page' %}<input type="hidden" name="{{ k }}" value="{{ v|urlencode }}">{% endif %}
|
||||
{% endfor %}
|
||||
<input type="number"
|
||||
min="1"
|
||||
max="{{ num_pages }}"
|
||||
name="page"
|
||||
value="{{ page }}"
|
||||
style="width:3em">
|
||||
<button type="submit"
|
||||
class="button button-secondary"
|
||||
style="padding:2px 9px">Go</button>
|
||||
</form>
|
||||
<span style="margin-left:6px;">Page <b>{{ page }}</b> of <b>{{ num_pages }}</b></span>
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
|
|
|
|||
0
mallarddns/frontend/templatetags/__init__.py
Normal file
0
mallarddns/frontend/templatetags/__init__.py
Normal file
32
mallarddns/frontend/templatetags/pagination_tags.py
Normal file
32
mallarddns/frontend/templatetags/pagination_tags.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue