Merge pull request 'Added pagination and column sorting to the admin dashboard table' (#80) from tables2-admin-dashboard into main
Reviewed-on: https://gitea.pangea.org/trustchain-oc1-orchestral/IdHub/pulls/80
This commit is contained in:
commit
5eb606c4a4
28
idhub/admin/tables.py
Normal file
28
idhub/admin/tables.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
import django_tables2 as tables
|
||||
from idhub.models import Rol, Event
|
||||
from idhub_auth.models import User
|
||||
|
||||
|
||||
class UserTable(tables.Table):
|
||||
class Meta:
|
||||
model = User
|
||||
template_name = "idhub/custom_table.html"
|
||||
fields = ("first_name", "last_name", "email", "is_active", "is_admin")
|
||||
|
||||
|
||||
class RolesTable(tables.Table):
|
||||
class Meta:
|
||||
model = Rol
|
||||
template_name = "idhub/custom_table.html"
|
||||
fields = ("name", "description")
|
||||
|
||||
|
||||
class DashboardTable(tables.Table):
|
||||
type = tables.Column(verbose_name="Event")
|
||||
message = tables.Column(verbose_name="Description")
|
||||
created = tables.Column(verbose_name="Date")
|
||||
|
||||
class Meta:
|
||||
model = Event
|
||||
template_name = "idhub/custom_table.html"
|
||||
fields = ("type", "message", "created")
|
|
@ -5,6 +5,7 @@ import pandas as pd
|
|||
from pathlib import Path
|
||||
from jsonschema import validate
|
||||
from smtplib import SMTPException
|
||||
from django_tables2 import SingleTableView
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
@ -30,6 +31,9 @@ from idhub.admin.forms import (
|
|||
SchemaForm,
|
||||
UserRolForm,
|
||||
)
|
||||
from idhub.admin.tables import (
|
||||
DashboardTable
|
||||
)
|
||||
from idhub.models import (
|
||||
DID,
|
||||
Event,
|
||||
|
@ -43,19 +47,15 @@ from idhub.models import (
|
|||
)
|
||||
|
||||
|
||||
class DashboardView(AdminView, TemplateView):
|
||||
class DashboardView(AdminView, SingleTableView):
|
||||
template_name = "idhub/admin/dashboard.html"
|
||||
table_class = DashboardTable
|
||||
title = _('Dashboard')
|
||||
subtitle = _('Events')
|
||||
icon = 'bi bi-bell'
|
||||
section = "Home"
|
||||
model = Event
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'events': Event.objects.filter(user=None),
|
||||
})
|
||||
return context
|
||||
|
||||
class People(AdminView):
|
||||
title = _("User management")
|
||||
|
|
|
@ -1,29 +1,11 @@
|
|||
{% extends "idhub/base_admin.html" %}
|
||||
{% load i18n %}
|
||||
{% load render_table from django_tables2 %}
|
||||
|
||||
{% block content %}
|
||||
<h3>
|
||||
<i class="{{ icon }}"></i>
|
||||
{{ subtitle }}
|
||||
</h3>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Event' %}</button></th>
|
||||
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Description' %}</button></th>
|
||||
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Date' %}</button></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ev in events %}
|
||||
<tr>
|
||||
<td>{{ ev.get_type_name }}</td>
|
||||
<td>{{ ev.message }}</td>
|
||||
<td>{{ ev.created }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% render_table table %}
|
||||
{% endblock %}
|
||||
|
|
98
idhub/templates/idhub/custom_table.html
Normal file
98
idhub/templates/idhub/custom_table.html
Normal file
|
@ -0,0 +1,98 @@
|
|||
{% load django_tables2 %}
|
||||
{% load i18n %}
|
||||
{% block table-wrapper %}
|
||||
<div class="table-container">
|
||||
{% block table %}
|
||||
<table class= "table table-striped table-sm">
|
||||
{% block table.thead %}
|
||||
{% if table.show_header %}
|
||||
<thead {{ table.attrs.thead.as_html }}>
|
||||
<tr>
|
||||
{% for column in table.columns %}
|
||||
<th scope="col" {{ column.attrs.th.as_html }}>
|
||||
{% if column.orderable %}
|
||||
<a type="button" class="btn btn-grey border border-dark"
|
||||
href="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header }}</a>
|
||||
{% else %}
|
||||
{{ column.header }}
|
||||
{% endif %}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
{% endif %}
|
||||
{% endblock table.thead %}
|
||||
{% block table.tbody %}
|
||||
<tbody {{ table.attrs.tbody.as_html }}>
|
||||
{% for row in table.paginated_rows %}
|
||||
{% block table.tbody.row %}
|
||||
<tr {{ row.attrs.as_html }}>
|
||||
{% for column, cell in row.items %}
|
||||
<td {{ column.attrs.td.as_html }}>{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endblock table.tbody.row %}
|
||||
{% empty %}
|
||||
{% if table.empty_text %}
|
||||
{% block table.tbody.empty_text %}
|
||||
<tr><td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>
|
||||
{% endblock table.tbody.empty_text %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endblock table.tbody %}
|
||||
{% block table.tfoot %}
|
||||
{% if table.has_footer %}
|
||||
<tfoot {{ table.attrs.tfoot.as_html }}>
|
||||
<tr>
|
||||
{% for column in table.columns %}
|
||||
<td {{ column.attrs.tf.as_html }}>{{ column.footer }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</tfoot>
|
||||
{% endif %}
|
||||
{% endblock table.tfoot %}
|
||||
</table>
|
||||
{% endblock table %}
|
||||
|
||||
{% block pagination %}
|
||||
{% if table.page and table.paginator.num_pages > 1 %}
|
||||
<ul class="pagination">
|
||||
{% if table.page.has_previous %}
|
||||
{% block pagination.previous %}
|
||||
<li class="previous">
|
||||
<a type="button" class="btn btn-grey border border-dark" href="{% querystring table.prefixed_page_field=table.page.previous_page_number %}">
|
||||
{% trans 'Previous' %}
|
||||
</a>
|
||||
</li>
|
||||
{% endblock pagination.previous %}
|
||||
{% endif %}
|
||||
{% if table.page.has_previous or table.page.has_next %}
|
||||
{% block pagination.range %}
|
||||
{% for p in table.page|table_page_range:table.paginator %}
|
||||
<li {% if p == table.page.number %}class="active"{% endif %}>
|
||||
{% if p == '...' %}
|
||||
<a type="button" class="btn btn-grey border border-dark" href="#">{{ p }}</a>
|
||||
{% else %}
|
||||
<a type="button" class="btn btn-grey border border-dark" href="{% querystring table.prefixed_page_field=p %}">
|
||||
{{ p }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endblock pagination.range %}
|
||||
{% endif %}
|
||||
{% if table.page.has_next %}
|
||||
{% block pagination.next %}
|
||||
<li class="next">
|
||||
<a type="button" class="btn btn-grey border border-dark" href="{% querystring table.prefixed_page_field=table.page.next_page_number %}">
|
||||
{% trans 'Next' %}
|
||||
</a>
|
||||
</li>
|
||||
{% endblock pagination.next %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endblock pagination %}
|
||||
</div>
|
||||
{% endblock table-wrapper %}
|
|
@ -1,3 +0,0 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
0
idhub/tests/__init__.py
Normal file
0
idhub/tests/__init__.py
Normal file
17
idhub/tests/test_models.py
Normal file
17
idhub/tests/test_models.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from django.test import TestCase
|
||||
from idhub.models import Event
|
||||
from idhub_auth.models import User
|
||||
|
||||
|
||||
class EventModelTest(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
user = User.objects.create(email='testuser@example.org')
|
||||
Event.objects.create(message='Test Event', type=1, user=user)
|
||||
|
||||
def test_event_creation(self):
|
||||
event = Event.objects.get(id=1)
|
||||
self.assertEqual(event.message, 'Test Event')
|
||||
self.assertEqual(event.get_type_name(), 'User registered')
|
||||
|
||||
# Add more tests for other model methods and properties
|
65
idhub/tests/test_tables.py
Normal file
65
idhub/tests/test_tables.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
from datetime import datetime
|
||||
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from idhub_auth.models import User
|
||||
from idhub.admin.tables import DashboardTable
|
||||
from idhub.models import Event
|
||||
|
||||
|
||||
class AdminDashboardTableTest(TestCase):
|
||||
def setUp(self):
|
||||
self.admin_user = User.objects.create_superuser(
|
||||
email='adminuser@example.org',
|
||||
password='adminpass12')
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
# Creating test events with different dates
|
||||
Event.objects.create(message='Event 1', type=1,
|
||||
created=datetime(2023, 1, 3))
|
||||
Event.objects.create(message='Event 2', type=2,
|
||||
created=datetime(2023, 1, 2))
|
||||
Event.objects.create(message='Event 3', type=3,
|
||||
created=datetime(2023, 1, 25))
|
||||
|
||||
def test_sorting_by_date(self):
|
||||
# Create the table
|
||||
table = DashboardTable(Event.objects.all())
|
||||
|
||||
# Apply sorting
|
||||
table.order_by = 'created'
|
||||
|
||||
# Fetch the sorted records
|
||||
sorted_records = list(table.rows)
|
||||
|
||||
# Verify the order is as expected
|
||||
self.assertTrue(sorted_records[0].record.created
|
||||
< sorted_records[1].record.created)
|
||||
self.assertTrue(sorted_records[1].record.created
|
||||
< sorted_records[2].record.created)
|
||||
|
||||
def test_table_in_template(self):
|
||||
self.client.login(email='adminuser@example.org', password='adminpass12')
|
||||
response = self.client.get(reverse('idhub:admin_dashboard'))
|
||||
|
||||
self.assertTemplateUsed(response, 'idhub/custom_table.html')
|
||||
|
||||
def test_table_data(self):
|
||||
Event.objects.create(message="test_event", type=2)
|
||||
Event.objects.create(message="test_event", type=9)
|
||||
|
||||
table = DashboardTable(Event.objects.all())
|
||||
self.assertTrue(isinstance(table, DashboardTable))
|
||||
self.assertEqual(len(table.rows), Event.objects.count())
|
||||
|
||||
def test_table_columns(self):
|
||||
table = DashboardTable(Event.objects.all())
|
||||
expected_columns = ['type', 'message', 'created']
|
||||
for column in expected_columns:
|
||||
self.assertIn(column, table.columns)
|
||||
|
||||
def test_pagination(self):
|
||||
# TODO
|
||||
pass
|
18
idhub/tests/test_templates.py
Normal file
18
idhub/tests/test_templates.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
from django.urls import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from idhub_auth.models import User
|
||||
|
||||
|
||||
class TemplateTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.admin_user = User.objects.create_superuser(
|
||||
email='adminuser@example.org',
|
||||
password='adminpass12')
|
||||
|
||||
def test_dashboard_template(self):
|
||||
self.client.login(email='adminuser@example.org', password='adminpass12')
|
||||
response = self.client.get(reverse('idhub:admin_dashboard'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'idhub/base_admin.html')
|
50
idhub/tests/test_views.py
Normal file
50
idhub/tests/test_views.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
from django.urls import reverse
|
||||
from django.test import TestCase
|
||||
|
||||
from idhub_auth.models import User
|
||||
|
||||
|
||||
class AdminDashboardViewTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(email='normaluser@example.org',
|
||||
password='testpass12')
|
||||
self.admin_user = User.objects.create_superuser(
|
||||
email='adminuser@example.org',
|
||||
password='adminpass12')
|
||||
|
||||
def test_view_url_exists_at_desired_location(self):
|
||||
response = self.client.get('/admin/dashboard/', follow=True)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_view_redirects_to_login_when_not_authenticated(self):
|
||||
response = self.client.get(reverse("idhub:admin_dashboard"),
|
||||
follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'auth/login.html')
|
||||
|
||||
def test_view_redirects_on_incorrect_login_attempt(self):
|
||||
self.client.login(email='adminuser@example.org', password='wrongpass')
|
||||
response = self.client.get(reverse('idhub:admin_dashboard'))
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_view_redirects_to_login_on_incorrect_login_attempt(self):
|
||||
self.client.login(email='adminuser@example.org', password='wrongpass')
|
||||
response = self.client.get(reverse('idhub:admin_dashboard'),
|
||||
follow=True)
|
||||
|
||||
self.assertTemplateUsed(response, 'auth/login.html')
|
||||
|
||||
def test_login_admin_user(self):
|
||||
self.client.login(email='adminuser@example.org', password='adminpass12')
|
||||
response = self.client.get(reverse('idhub:admin_dashboard'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_view_uses_correct_template(self):
|
||||
self.client.login(email='adminuser@example.org', password='adminpass12')
|
||||
response = self.client.get(reverse('idhub:admin_dashboard'))
|
||||
|
||||
self.assertTemplateUsed(response, 'idhub/admin/dashboard.html')
|
|
@ -1,6 +1,7 @@
|
|||
django==4.2.5
|
||||
django-bootstrap5==23.3
|
||||
django-extensions==3.2.3
|
||||
django-tables2==2.6.0
|
||||
black==23.9.1
|
||||
python-decouple==3.8
|
||||
jsonschema==4.19.1
|
||||
|
|
|
@ -70,6 +70,7 @@ INSTALLED_APPS = [
|
|||
'django.contrib.staticfiles',
|
||||
'django_extensions',
|
||||
'django_bootstrap5',
|
||||
'django_tables2',
|
||||
'idhub_auth',
|
||||
'idhub'
|
||||
]
|
||||
|
|
Loading…
Reference in a new issue