Merge branch 'release'

This commit is contained in:
Cayo Puigdefabregas 2024-01-22 18:13:45 +01:00
commit 8c5d77acf6
31 changed files with 1172 additions and 392 deletions

View file

@ -0,0 +1,52 @@
name: Django CI Pipeline
on:
push:
branches:
- main
- release
- testing-pipeline
jobs:
test:
runs-on: ubuntu-22.04
env:
SECRET_KEY: "t3st-dummy-s3cr3t-k3y"
STATIC_ROOT: "tmp/static/"
MEDIA_ROOT: "tmp/media/"
steps:
- uses: actions/checkout@v4
- name: Install python3-venv
id: install
run: |
apt-get update
apt-get install python3 python3-venv python3-pip -y
- name: Check Python version
run: |
python3 --version
echo "Python version: $(python3 --version)"
- name: Create virtual environment
run: |
python3 -m venv venv
source venv/bin/activate
echo "Virtual environment created successfully"
# https://docs.github.com/en/actions/learn-github-actions/contexts#steps-context
if: steps.install.outcome == 'success'
- name: Install dependencies
run: |
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
run: |
source venv/bin/activate
python manage.py test

View file

@ -74,7 +74,7 @@ class ImportForm(forms.Form):
(x.did, x.label) for x in dids.filter(eidas1=False)
]
self.fields['schema'].choices = [
(x.id, x.name()) for x in Schemas.objects.filter()
(x.id, x.name) for x in Schemas.objects.filter()
]
if dids.filter(eidas1=True).exists():
choices = [("", "")]

View file

@ -1,25 +1,255 @@
import django_tables2 as tables
from django.utils.translation import gettext_lazy as _
from idhub.models import Rol, Event
from django.utils.html import format_html
from idhub.models import (
Rol,
Event,
Service,
VerificableCredential,
DID,
File_datas,
Schemas
)
from idhub_auth.models import User
class ButtonColumn(tables.Column):
attrs = {
"a": {
"type": "button",
"class": "text-primary",
}
}
# it makes no sense to order a column of buttons
orderable = False
# django_tables will only call the render function if it doesn't find
# any empty values in the data, so we stop it from matching the data
# to any value considered empty
empty_values = ()
def render(self):
return format_html('<i class="bi bi-eye"></i>')
class UserTable(tables.Table):
view_user = ButtonColumn(
linkify={
"viewname": "idhub:admin_people",
"args": [tables.A("pk")]
},
orderable=False
)
membership = tables.Column(empty_values=())
role = tables.Column(empty_values=())
def render_view_user(self):
return format_html('<i class="bi bi-eye"></i>')
def render_membership(self, record):
return record.get_memberships()
def order_membership(self, queryset, is_descending):
# TODO: Test that this doesn't return more rows than it should
queryset = queryset.order_by(
("-" if is_descending else "") + "memberships__type"
)
return (queryset, True)
def render_role(self, record):
return record.get_roles()
def order_role(self, queryset, is_descending):
queryset = queryset.order_by(
("-" if is_descending else "") + "roles"
)
return (queryset, True)
class Meta:
model = User
template_name = "idhub/custom_table.html"
fields = ("first_name", "last_name", "email", "is_active", "is_admin")
fields = ("last_name", "first_name", "email", "membership", "role",
"view_user")
class RolesTable(tables.Table):
view_role = ButtonColumn(
linkify={
"viewname": "idhub:admin_rol_edit",
"args": [tables.A("pk")]
},
orderable=False
)
delete_role = ButtonColumn(
linkify={
"viewname": "idhub:admin_rol_del",
"args": [tables.A("pk")]
},
orderable=False
)
def render_view_role(self):
return format_html('<i class="bi bi-pencil-square"></i>')
def render_delete_role(self):
return format_html('<i class="bi bi-trash">')
class Meta:
model = Rol
template_name = "idhub/custom_table.html"
fields = ("name", "description")
class ServicesTable(tables.Table):
domain = tables.Column(verbose_name="Service")
role = tables.Column(empty_values=())
edit_service = ButtonColumn(
linkify={
"viewname": "idhub:admin_service_edit",
"args": [tables.A("pk")]
},
orderable=False
)
delete_service = ButtonColumn(
linkify={
"viewname": "idhub:admin_service_del",
"args": [tables.A("pk")]
},
orderable=False
)
def render_role(self, record):
return record.get_roles()
def render_edit_service(self):
return format_html('<i class="bi bi-pencil-square"></i>')
def render_delete_service(self):
return format_html('<i class="bi bi-trash">')
def order_role(self, queryset, is_descending):
queryset = queryset.order_by(
("-" if is_descending else "") + "rol"
)
return (queryset, True)
class Meta:
model = Service
template_name = "idhub/custom_table.html"
fields = ("domain", "description", "role",
"edit_service", "delete_service")
class DashboardTable(tables.Table):
class Meta:
model = Event
template_name = "idhub/custom_table.html"
fields = ("type", "message", "created")
class CredentialTable(tables.Table):
type = tables.Column(empty_values=())
# Pending VerificableCredential description fix
details = tables.Column(empty_values=(), orderable=False)
issued_on = tables.Column(verbose_name="Issued")
view_credential = ButtonColumn(
linkify={
"viewname": "idhub:admin_credential",
"args": [tables.A("pk")]
},
orderable=False
)
def render_type(self, record):
return record.type()
def render_details(self, record):
return record.description()
def render_view_credential(self):
return format_html('<i class="bi bi-eye"></i>')
def order_type(self, queryset, is_descending):
queryset = queryset.order_by(
("-" if is_descending else "") + "schema__type"
)
return (queryset, True)
class Meta:
model = VerificableCredential
template_name = "idhub/custom_table.html"
fields = ("type", "details", "issued_on", "status", "user")
class DIDTable(tables.Table):
created_at = tables.Column(verbose_name="Date")
did = tables.Column(verbose_name="ID")
edit_did = ButtonColumn(
linkify={
"viewname": "idhub:admin_dids_edit",
"args": [tables.A("pk")]
},
orderable=False,
verbose_name="Edit DID"
)
delete_template_code = """<a class="text-danger"
href="javascript:void()"
data-bs-toggle="modal"
data-bs-target="#confirm-delete-{{ record.id }}"
title="Remove"
><i class="bi bi-trash"></i></a>"""
delete_did = tables.TemplateColumn(template_code=delete_template_code,
orderable=False,
verbose_name="Delete DID")
def render_edit_did(self):
return format_html('<i class="bi bi-pencil-square"></i>')
class Meta:
model = DID
template_name = "idhub/custom_table.html"
fields = ("created_at", "label", "did", "edit_did", "delete_did")
class DataTable(tables.Table):
created_at = tables.Column(verbose_name="Date")
file_name = tables.Column(verbose_name="File")
class Meta:
model = File_datas
template_name = "idhub/custom_table.html"
fields = ("created_at", "file_name", "success")
class TemplateTable(tables.Table):
view_schema = ButtonColumn(
linkify={
"viewname": "idhub:admin_schemas_download",
"args": [tables.A("pk")]
},
orderable=False
)
delete_template_code = """<a class="text-danger"
href="javascript:void()"
data-bs-toggle="modal"
data-bs-target="#confirm-delete-{{ record.id }}"
title="Remove"
><i class="bi bi-trash"></i></a>"""
delete_schema = tables.TemplateColumn(template_code=delete_template_code,
orderable=False,
verbose_name="Delete schema")
_name = tables.Column(verbose_name="Name")
_description = tables.Column(verbose_name="Description")
class Meta:
model = Schemas
template_name = "idhub/custom_table.html"
fields = ("created_at", "file_schema", "_name", "_description",
"view_schema", "delete_schema")

View file

@ -8,6 +8,7 @@ from smtplib import SMTPException
from django_tables2 import SingleTableView
from django.conf import settings
from django.template.loader import get_template
from django.utils.translation import gettext_lazy as _
from django.views.generic.base import TemplateView, View
from django.views.generic.edit import (
@ -35,7 +36,14 @@ from idhub.admin.forms import (
ImportCertificateForm,
)
from idhub.admin.tables import (
DashboardTable
DashboardTable,
UserTable,
RolesTable,
ServicesTable,
CredentialTable,
DIDTable,
DataTable,
TemplateTable
)
from idhub.models import (
DID,
@ -120,10 +128,12 @@ class ImportExport(AdminView):
section = "ImportExport"
class PeopleListView(People, TemplateView):
class PeopleListView(People, SingleTableView):
template_name = "idhub/admin/people.html"
subtitle = _('View users')
icon = 'bi bi-person'
table_class = UserTable
model = User
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -132,6 +142,11 @@ class PeopleListView(People, TemplateView):
})
return context
def get_queryset(self, **kwargs):
queryset = super().get_queryset(**kwargs)
return queryset
class PeopleView(People, TemplateView):
template_name = "idhub/admin/user.html"
@ -440,17 +455,20 @@ class PeopleRolDeleteView(PeopleView):
return redirect('idhub:admin_people_edit', user.id)
class RolesView(AccessControl):
class RolesView(AccessControl, SingleTableView):
template_name = "idhub/admin/roles.html"
subtitle = _('Manage roles')
table_class = RolesTable
icon = ''
model = Rol
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'roles': Rol.objects,
})
return context
queryset = kwargs.pop('object_list', None)
if queryset is None:
self.object_list = self.model.objects.all()
return super().get_context_data(**kwargs)
class RolRegisterView(AccessControl, CreateView):
template_name = "idhub/admin/rol_register.html"
@ -504,17 +522,20 @@ class RolDeleteView(AccessControl):
return redirect('idhub:admin_roles')
class ServicesView(AccessControl):
class ServicesView(AccessControl, SingleTableView):
template_name = "idhub/admin/services.html"
table_class = ServicesTable
subtitle = _('Manage services')
icon = ''
model = Service
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'services': Service.objects,
})
return context
queryset = kwargs.pop('object_list', None)
if queryset is None:
self.object_list = self.model.objects.all()
return super().get_context_data(**kwargs)
class ServiceRegisterView(AccessControl, CreateView):
template_name = "idhub/admin/service_register.html"
@ -578,17 +599,19 @@ class ServiceDeleteView(AccessControl):
return redirect('idhub:admin_services')
class CredentialsView(Credentials):
class CredentialsView(Credentials, SingleTableView):
template_name = "idhub/admin/credentials.html"
table_class = CredentialTable
subtitle = _('View credentials')
icon = ''
model = VerificableCredential
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'credentials': VerificableCredential.objects,
})
return context
queryset = kwargs.pop('object_list', None)
if queryset is None:
self.object_list = self.model.objects.all()
return super().get_context_data(**kwargs)
class CredentialView(Credentials):
@ -667,19 +690,26 @@ class DeleteCredentialsView(Credentials):
return redirect(self.success_url)
class DidsView(Credentials):
class DidsView(Credentials, SingleTableView):
template_name = "idhub/admin/dids.html"
table_class = DIDTable
subtitle = _('Manage identities (DID)')
icon = 'bi bi-patch-check-fill'
wallet = True
model = DID
def get_context_data(self, **kwargs):
queryset = kwargs.pop('object_list', None)
if queryset is None:
self.object_list = self.model.objects.all()
context = super().get_context_data(**kwargs)
context.update({
'dids': DID.objects.filter(user=self.request.user),
})
return context
class DidRegisterView(Credentials, CreateView):
template_name = "idhub/admin/did_register.html"
subtitle = _('Add a new organizational identity (DID)')
@ -699,7 +729,6 @@ class DidRegisterView(Credentials, CreateView):
return super().form_valid(form)
class DidEditView(Credentials, UpdateView):
template_name = "idhub/admin/did_register.html"
subtitle = _('Organization Identities (DID)')
@ -767,19 +796,21 @@ class WalletConfigIssuesView(Credentials, FormView):
return super().form_valid(form)
class SchemasView(SchemasMix):
class SchemasView(SchemasMix, SingleTableView):
template_name = "idhub/admin/schemas.html"
table_class = TemplateTable
subtitle = _('View credential templates')
icon = ''
model = Schemas
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'schemas': Schemas.objects,
})
return context
queryset = kwargs.pop('object_list', None)
if queryset is None:
self.object_list = self.model.objects.all()
return super().get_context_data(**kwargs)
class SchemasDeleteView(SchemasMix):
def get(self, request, *args, **kwargs):
@ -869,14 +900,14 @@ class SchemasImportView(SchemasMix):
if not Schemas.objects.filter(file_schema=x).exists()]
return schemas
class SchemasImportAddView(SchemasMix):
def get(self, request, *args, **kwargs):
self.check_valid_user()
file_name = kwargs['file_schema']
schemas_files = os.listdir(settings.SCHEMAS_DIR)
if not file_name in schemas_files:
if file_name not in schemas_files:
messages.error(self.request, f"The schema {file_name} not exist!")
return redirect('idhub:admin_schemas_import')
@ -897,7 +928,19 @@ class SchemasImportAddView(SchemasMix):
except Exception:
messages.error(self.request, _('This is not a valid schema!'))
return
schema = Schemas.objects.create(file_schema=file_name, data=data, type=title)
_name = json.dumps(ldata.get('name', ''))
_description = json.dumps(ldata.get('description', ''))
schema = Schemas.objects.create(
file_schema=file_name,
data=data,
type=title,
_name=_name,
_description=_description,
# template_description=_description
template_description=self.get_description()
)
schema.save()
return schema
@ -909,11 +952,27 @@ class SchemasImportAddView(SchemasMix):
return data
def get_template_description(self):
context = {}
template_name = 'credentials/{}'.format(
self.schema.file_schema
)
tmpl = get_template(template_name)
return tmpl.render(context)
class ImportView(ImportExport, TemplateView):
def get_description(self):
for des in json.loads(self.get_template_description()).get('description', []):
if settings.LANGUAGE_CODE == des.get('lang'):
return des.get('value', '')
return ''
class ImportView(ImportExport, SingleTableView):
template_name = "idhub/admin/import.html"
table_class = DataTable
subtitle = _('Import data')
icon = ''
model = File_datas
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -965,4 +1024,3 @@ class ImportAddView(NotifyActivateUserByEmail, ImportExport, FormView):
messages.error(self.request, e)
return super().form_valid(form)

View file

@ -0,0 +1,293 @@
import random
from typing import Type, Callable, List
from django.core.management.base import BaseCommand
from django.db import models, IntegrityError
from idhub.models import Event, Rol, Service, UserRol
from idhub_auth.models import User
from faker import Faker
DEFAULT_OBJECTS_CREATED: int = 30
RANDOM_STRING_LENGTH: int = 30
EMAIL_RANDOM_STRING_LENGTH: int = 10
EXISTING_EVENTS: int = 30
fake = Faker()
class Command(BaseCommand):
"""
Populate the database with dummy values.
You can either specify which model to create objects for,
or create objects for all models.
If no data is specified it will create 30 events, users,
services, roles, and user roles.
"""
help = 'Populate the database with dummy values for testing.'
def __init__(self, *args, **kwargs):
"""
In the context of a Django management command,
initializing lists like created_users in the constructor ensures
that each run of the command has its own set of users, services,
roles, etc., and does not unintentionally share or retain state
across different invocations of the command.
"""
super().__init__(*args, **kwargs)
self.created_users = []
self.created_services = []
self.created_roles = []
def handle(self, *args, **options):
any_option_used = self.process_options(options)
if not any_option_used:
self.create_all()
def process_options(self, input_options):
option_methods = {
'events': self.create_events,
'users': self.create_users,
'superusers': self.create_superusers,
'services': self.create_services,
'roles': self.create_roles,
'userroles': self.create_user_roles,
'userrole': self.create_specific_user_role,
'register': self.register_user,
'register_superuser': self.register_superuser,
'amount': self.create_all
}
any_option_used = self.match_input_to_options(input_options,
option_methods)
return any_option_used
def match_input_to_options(self, input_options, option_methods):
any_option_used = False
for option, method in option_methods.items():
is_valid_option = option in input_options and input_options[
option] is not None
if is_valid_option:
self.call_selected_method(input_options, method, option)
any_option_used = True
return any_option_used
def call_selected_method(self, input_options, method, option):
args = self.create_argument_list(input_options, option)
method(*args)
def create_argument_list(self, input_options, option):
if isinstance(input_options[option], list):
args = input_options[option]
else:
args = [input_options[option]]
return args
def add_arguments(self, parser):
parser.add_argument(
'--amount', type=int, action='store',
help='Create N objects (includes events, users, services, roles, and user roles)'
)
parser.add_argument('--events', type=int,
help='Create the specified number of events')
parser.add_argument('--users', type=int,
help='Create the specified number of users')
parser.add_argument('--superusers', type=int,
help='Create the specified number of superusers')
parser.add_argument('--services', type=int,
help='Create the specified number of services')
parser.add_argument('--roles', type=int,
help='Create the specified number of roles')
parser.add_argument('--userroles', type=int,
help='Create the specified number of user roles')
parser.add_argument(
'--userrole', nargs=2, type=str,
help='Create a user role for user U and service S',
metavar=('U', 'S'),
)
parser.add_argument('--register', nargs=2, type=str,
help='Create a user with email EMAIL and password PW',
metavar=('EMAIL', 'PW'))
parser.add_argument('--register-superuser', nargs=2, type=str,
help='Create a superuser with email EMAIL and '
'password PW',
metavar=('EMAIL', 'PW'))
def register_user(self, email: str, password: str):
"""
Register a new user with the given email and password from the command line.
"""
try:
self.create_user(email, password)
self.stdout.write(f"Successfully registered user: {email}")
except IntegrityError:
self.stdout.write(f"Failed to register user: {email}")
def register_superuser(self, email: str, password: str):
"""
Register a new superuser with the given email and password from the command line.
"""
try:
self.create_superuser(email, password)
self.stdout.write(f"Successfully registered superuser: {email}")
except IntegrityError:
self.stdout.write(f"Failed to register superuser: {email}")
def create_all(self, amount=DEFAULT_OBJECTS_CREATED):
self.create_events(amount)
self.create_users(amount)
self.create_roles(amount)
self.create_services(amount)
self.create_user_roles(amount)
def create_objects(self, model: Type[models.Model],
data_generator: Callable, amount: int) -> List[
models.Model]:
"""
Generic method to create objects for a given model and keep track of them.
Args:
model: The Django model class for which objects are to be created.
data_generator: A function that returns a dictionary of attributes for creating a model instance.
amount: The number of objects to create.
Returns:
A list of successfully created model instances.
"""
created_objects = []
for _ in range(amount):
try:
model_instance = self.create_from_data(data_generator, model)
created_objects.append(model_instance)
except IntegrityError:
self.print_failure_message(model)
self.print_amount_created(created_objects, model)
return created_objects
def create_users(self, amount: int):
def user_data():
return {
'email': fake.email(),
'first_name': fake.first_name(),
'last_name': fake.last_name()
}
created_users = self.create_objects(User, user_data, amount)
self.created_users.extend(created_users)
def create_events(self, amount: int, user=None):
def event_data():
return {
'type': random.randint(1, EXISTING_EVENTS),
'message': fake.paragraph(nb_sentences=3),
'user': user
}
self.create_objects(Event, event_data, amount)
def create_superusers(self, amount=0):
"""Superusers can only be created from the specific command"""
created_superuser_amount = 0
for value in range(0, amount):
email = fake.email()
try:
User.objects.create_superuser(email)
created_superuser_amount += 1
except IntegrityError:
self.stdout.write("Couldn't create superuser " + email)
self.stdout.write(f"Created {created_superuser_amount} users")
def create_services(self, amount: int):
def service_data():
domain = fake.text(max_nb_chars=200)
description = fake.text(max_nb_chars=250)
return {
'domain': domain,
'description': description,
}
created_services = self.create_objects(Service, service_data, amount)
self.associate_random_roles(created_services)
self.created_services.extend(created_services)
def create_roles(self, amount: int):
def role_data():
name = fake.job()
description = fake.text(max_nb_chars=250)
return {'name': name, 'description': description}
created_roles = self.create_objects(Rol, role_data, amount)
self.created_roles.extend(created_roles)
def create_user_roles(self, amount: int, user_id=None, service_id=None):
def user_role_data():
user = self.get_or_create_user(user_id)
service = self.get_or_create_service(service_id)
return {"user": user, "service": service}
self.create_objects(UserRol, user_role_data, amount)
def create_specific_user_role(self, user, service):
self.create_user_roles(1, user, service)
def create_user(self, email: str, password: str):
User.objects.create_user(email, password)
def create_superuser(self, email: str, password: str):
User.objects.create_superuser(email, password)
def print_failure_message(self, model):
self.stdout.write(f"Couldn't create {model.__name__} object.")
def print_amount_created(self, created_objects, model):
self.stdout.write(f"Created {len(created_objects)} "
f"{model.__name__} objects")
def create_from_data(self, data_generator, model):
model_instance = model.objects.create(**data_generator())
return model_instance
def associate_random_roles(self, created_services):
for service in created_services:
self.associate_service_to_random_roles(service)
def associate_service_to_random_roles(self, service):
associated_roles = [self.get_or_create_role() for _ in range(
random.randint(0, 2))]
service.rol.set(associated_roles)
def get_or_create_user(self, user_id):
if user_id is not None:
user = User.objects.get(user_id=user_id)
elif len(self.created_users) != 0:
user = random.choice(self.created_users)
else:
self.create_users(1)
user = self.created_users[0]
return user
def get_or_create_service(self, service_id):
if service_id is not None:
service = Service.objects.get(service_id=service_id)
elif len(self.created_services) != 0:
service = random.choice(self.created_services)
else:
self.create_services(1)
service = self.created_services[0]
return service
def get_or_create_role(self):
if len(self.created_roles) != 0:
role = random.choice(self.created_roles)
else:
self.create_roles(1)
role = self.created_roles[0]
return role

View file

@ -95,16 +95,18 @@ class Command(BaseCommand):
assert title
except Exception:
title = ''
return
name = ''
try:
for x in dname:
if settings.LANGUAGE_CODE in x['lang']:
name = x.get('value', '')
except Exception:
return
_name = ''
Schemas.objects.create(file_schema=file_name, data=data, type=title)
_name = json.dumps(ldata.get('name', ''))
_description = json.dumps(ldata.get('description', ''))
Schemas.objects.create(
file_schema=file_name,
data=data,
type=title,
_name=_name,
_description=_description
)
def open_file(self, file_name):
data = ''

View file

@ -1,4 +1,4 @@
# Generated by Django 4.2.5 on 2024-01-20 14:29
# Generated by Django 4.2.5 on 2024-01-22 15:44
from django.conf import settings
from django.db import migrations, models
@ -102,6 +102,14 @@ class Migration(migrations.Migration):
('file_schema', models.CharField(max_length=250)),
('data', models.TextField()),
('created_at', models.DateTimeField(auto_now=True)),
('_name', models.TextField(db_column='name', null=True)),
(
'_description',
models.CharField(
db_column='description', max_length=250, null=True
),
),
('template_description', models.TextField(null=True)),
],
),
migrations.CreateModel(
@ -315,6 +323,7 @@ class Migration(migrations.Migration):
(28, 'Organisational DID deleted by admin'),
(29, 'User deactivated'),
(30, 'User activated'),
(31, 'User send Verificable Presentation'),
],
verbose_name='Event',
),

View file

@ -473,6 +473,9 @@ class Schemas(models.Model):
file_schema = models.CharField(max_length=250)
data = models.TextField()
created_at = models.DateTimeField(auto_now=True)
_name = models.TextField(null=True, db_column='name')
_description = models.CharField(max_length=250, null=True, db_column='description')
template_description = models.TextField(null=True)
@property
def get_schema(self):
@ -480,22 +483,62 @@ class Schemas(models.Model):
return {}
return json.loads(self.data)
def _update_and_get_field(self, field_attr, schema_key, is_json=False):
field_value = getattr(self, field_attr)
if not field_value:
field_value = self.get_schema.get(schema_key, [] if is_json else '')
self._update_model_field(field_attr, field_value)
try:
if is_json:
return json.loads(field_value)
except json.decoder.JSONDecodeError:
return field_value
return field_value
def _update_model_field(self, field_attr, field_value):
if field_value:
setattr(self, field_attr, field_value)
self.save(update_fields=[field_attr])
@property
def name(self, request=None):
names = {}
for name in self.get_schema.get('name', []):
lang = name.get('lang')
if 'ca' in lang:
lang = 'ca'
names[lang]= name.get('value')
names = self._update_and_get_field('_name', 'name',
is_json=True)
language_code = self._get_language_code(request)
name = self._get_name_by_language(names, language_code)
if request and request.LANGUAGE_CODE in names.keys():
return names[request.LANGUAGE_CODE]
return name
return names[settings.LANGUAGE_CODE]
def _get_language_code(self, request=None):
language_code = settings.LANGUAGE_CODE
if request:
language_code = request.LANGUAGE_CODE
if self._is_catalan_code(language_code):
language_code = 'ca'
return language_code
def _get_name_by_language(self, names, lang_code):
for name in names:
if name.get('lang') == lang_code:
return name.get('value')
return None
def _is_catalan_code(self, language_code):
return language_code == 'ca_ES'
@name.setter
def name(self, value):
self._name = json.dumps(value)
@property
def description(self):
return self.get_schema.get('description', '')
return self._update_and_get_field('_description', 'description')
@description.setter
def description(self, value):
self._description = value
class VerificableCredential(models.Model):
"""
@ -559,6 +602,9 @@ class VerificableCredential(models.Model):
def type(self):
return self.schema.type
def get_description(self):
return self.schema._description or ''
def description(self):
for des in json.loads(self.render("")).get('description', []):
if settings.LANGUAGE_CODE in des.get('lang'):
@ -652,7 +698,6 @@ class VerificableCredential(models.Model):
d_minimum = self.filter_dict(d_ordered)
return ujson.dumps(d_minimum)
def get_issued_on(self):
if self.issued_on:
return self.issued_on.strftime("%m/%d/%Y")
@ -732,7 +777,7 @@ class Service(models.Model):
def get_roles(self):
if self.rol.exists():
return ", ".join([x.name for x in self.rol.all()])
return ", ".join([x.name for x in self.rol.order_by("name")])
return _("None")
def __str__(self):

View file

@ -1,39 +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="row mt-5">
<div class="col">
<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 'Type' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Details' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Issued' %}</button></th>
<th scope="col" class="text-center"><button type="button" class="btn btn-grey border border-dark">{% trans 'Status' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'User' %}</button></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for f in credentials.all %}
<tr style="font-size:15px;">
<td>{{ f.get_type }}</td>
<td>{{ f.description }}</td>
<td>{{ f.get_issued_on }}</td>
<td class="text-center">{{ f.get_status }}</td>
<td>{{ f.user.email }}</td>
<td><a href="{% url 'idhub:admin_credential' f.id %}" class="text-primary" title="{% trans 'View' %}"><i class="bi bi-eye"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% render_table table %}
{% endblock %}

View file

@ -1,5 +1,6 @@
{% extends "idhub/base_admin.html" %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block content %}
<h3>
@ -8,6 +9,8 @@
</h3>
<div class="row mt-5">
<div class="col">
{% render_table table %}
{% comment %}
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
@ -31,6 +34,7 @@
{% endfor %}
</tbody>
</table>
{% endcomment %}
<div class="form-actions-no-box">
<a class="btn btn-green-admin" href="{% url 'idhub:admin_dids_new' %}">{% translate "Add identity" %} <i class="bi bi-plus"></i></a>
</div>

View file

@ -1,36 +1,14 @@
{% extends "idhub/base_admin.html" %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block content %}
<h3>
<i class="{{ icon }}"></i>
{{ subtitle }}
</h3>
<div class="row mt-5">
<div class="col">
<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 'Created' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'File' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Success' %}</button></th>
</tr>
</thead>
<tbody>
{% for f in dates.all %}
<tr style="font-size:15px;">
<td>{{ f.created_at }}</td>
<td>{{ f.file_name }}</td>
<td>{% if f.success %}<i class="bi bi-check-circle text-primary"></i>{% else %}<i class="bi bi-x-circle text-danger"></i>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="form-actions-no-box">
<a class="btn btn-green-admin" href="{% url 'idhub:admin_import_add' %}">{% translate "Import data" %} <i class="bi bi-plus"></i></a>
</div>
</div>
</div>
{% render_table table %}
<div class="form-actions-no-box">
<a class="btn btn-green-admin" href="{% url 'idhub:admin_import_add' %}">{% translate "Import data" %} <i class="bi bi-plus"></i></a>
</div>
{% endblock %}

View file

@ -1,39 +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-green-admin border border-dark">{% trans 'Last name' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'First name' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">Email</button></th>
<th scope="col" class="text-center"><button type="button" class="btn btn-grey border border-dark">{% trans 'Membership' %}</button></th>
<th scope="col" class="text-center"><button type="button" class="btn btn-grey border border-dark">{% trans 'Role' %}</button></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.last_name|default:'' }}</td>
<td>{{ user.first_name|default:'' }}</td>
<td>{{ user.email }}</td>
<td class="text-center">
{{ user.get_memberships }}
</td>
<td class="text-center">
{{ user.get_roles }}
</td>
<td><a type="button" class="text-primary" href="{% url 'idhub:admin_people' user.id %}" title="{% trans 'View' %}"><i class="bi bi-eye"></i></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% render_table table %}
{% endblock %}

View file

@ -1,5 +1,6 @@
{% extends "idhub/base_admin.html" %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block content %}
<h3>
@ -8,31 +9,10 @@
</h3>
<div class="row mt-5">
<div class="col">
<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 'Role' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Description' %}</button></th>
<th scope="col"></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for rol in roles.all %}
<tr>
<td>{{ rol.name }}</td>
<td>{{ rol.description|default:""}}</td>
<td><a href="{% url 'idhub:admin_rol_edit' rol.id %}" title="{% trans 'Edit' %}"><i class="bi bi-pencil-square"></i></a></td>
<td><a class="text-danger" href="{% url 'idhub:admin_rol_del' rol.id %}" title="{% trans 'Delete' %}"><i class="bi bi-trash"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% render_table table %}
<div class="form-actions-no-box">
<a class="btn btn-green-admin" href="{% url 'idhub:admin_rol_new' %}">{% translate "Add Role" %} <i class="bi bi-plus"></i></a>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,47 +1,19 @@
{% extends "idhub/base_admin.html" %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block content %}
<h3>
<i class="{{ icon }}"></i>
{{ subtitle }}
</h3>
<div class="row mt-5">
<div class="col">
<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 'Created' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Template file' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Name' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Description' %}</button></th>
<th scope="col"></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for schema in schemas.all %}
<tr style="font-size:15px;">
<td>{{ schema.created_at }}</td>
<td>{{ schema.file_schema }}</td>
<td>{{ schema.name }}</td>
<td>{{ schema.description }}</td>
<td><a class="text-primary" href="{% url 'idhub:admin_schemas_download' schema.id %}" target="_blank" title="{% trans 'View' %}"><i class="bi bi-eye"></i></a></td>
<td><a class="text-danger" href="jacascript:void()" data-bs-toggle="modal" data-bs-target="#confirm-delete-{{ schema.id }}" title="{% trans 'Remove' %}"><i class="bi bi-trash"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="form-actions-no-box">
<a class="btn btn-green-admin" href="{% url 'idhub:admin_schemas_import' %}">{% translate "Add template" %} <i class="bi bi-plus"></i></a>
</div>
</div>
</div>
{% render_table table %}
<div class="form-actions-no-box">
<a class="btn btn-green-admin" href="{% url 'idhub:admin_schemas_import' %}">{% translate "Add template" %} <i class="bi bi-plus"></i></a>
</div>
<!-- Modal -->
{% for schema in schemas.all %}
<div class="modal" id="confirm-delete-{{ schema.id}}" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
{% for schema in object_list %}
<div class="modal" id="confirm-delete-{{ schema.id }}" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">

View file

@ -1,5 +1,6 @@
{% extends "idhub/base_admin.html" %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block content %}
<h3>
@ -8,33 +9,10 @@
</h3>
<div class="row mt-5">
<div class="col">
<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 'Service' %}</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 'Role' %}</button></th>
<th scope="col"></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for service in services.all %}
<tr>
<td>{{ service.domain }}</td>
<td>{{ service.description }}</td>
<td>{{ service.get_roles }}</td>
<td><a href="{% url 'idhub:admin_service_edit' service.id %}" title="{% trans 'Edit' %}"><i class="bi bi-pencil-square"></i></a></td>
<td><a class="text-danger" href="{% url 'idhub:admin_service_del' service.id %}" title="{% trans 'Delete' %}"><i class="bi bi-trash"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% render_table table %}
<div class="form-actions-no-box">
<a class="btn btn-green-admin" href="{% url 'idhub:admin_service_new' %}">{% translate "Add service" %} <i class="bi bi-plus"></i></a>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,39 +1,11 @@
{% extends "idhub/base.html" %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block content %}
<h3>
<i class="{{ icon }}"></i>
{{ subtitle }}
</h3>
<div class="row mt-5">
<div class="col">
<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 'Type' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Details' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Issued' %}</button></th>
<th scope="col" class="text-center"><button type="button" class="btn btn-grey border border-dark">{% trans 'Status' %}</button></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for f in credentials.all %}
<tr style="font-size:15px;">
<td>{{ f.get_type }}</td>
<td>{{ f.description }}</td>
<td>{{ f.get_issued_on }}</td>
<td class="text-center">{{ f.get_status }}</td>
<td>
<a href="{% url 'idhub:user_credential' f.id %}" class="text-primary" title="{% trans 'View' %}"><i class="bi bi-eye"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% render_table table %}
{% endblock %}

View file

@ -1,29 +1,12 @@
{% extends "idhub/base.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 'Type' %}</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 user.events.all %}
<tr>
<td>{{ ev.get_type_name }}</td>
<td>{{ ev.message }}</td>
<td>{{ ev.created }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% render_table table %}
{% endblock %}

View file

@ -1,41 +1,15 @@
{% extends "idhub/base.html" %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block content %}
<h3>
<i class="{{ icon }}"></i>
{{ subtitle }}
</h3>
<div class="row mt-5">
<div class="col">
<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 'Date' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Label' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">ID</button></th>
<th scope="col"></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for d in dids.all %}
<tr style="font-size:15px;">
<td>{{ d.created_at }}</td>
<td>{{ d.label }}</td>
<td>{{ d.did }}</td>
<td><a class="text-primary" href="{% url 'idhub:user_dids_edit' d.id %}" title="{% trans 'Edit' %}"><i class="bi bi-pencil-square"></i></a></td>
<td><a class="text-danger" href="jacascript:void()" data-bs-toggle="modal" data-bs-target="#confirm-delete-{{ d.id }}" title="{% trans 'Remove' %}"><i class="bi bi-trash"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="form-actions-no-box">
<a class="btn btn-green-user" href="{% url 'idhub:user_dids_new' %}">{% translate "Add Identity" %} <i class="bi bi-plus"></i></a>
</div>
</div>
</div>
{% render_table table %}
<div class="form-actions-no-box">
<a class="btn btn-green-user" href="{% url 'idhub:user_dids_new' %}">{% translate "Add Identity" %} <i class="bi bi-plus"></i></a>
</div>
<!-- Modal -->
{% for d in dids.all %}

View file

@ -1,5 +1,6 @@
{% extends "idhub/base.html" %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block content %}
<div class="row">
@ -33,33 +34,6 @@
{% bootstrap_form form %}
</form>
<hr />
{% render_table table %}
<div class="row">
<div class="col">
<div class="table-responsive">
<table class="table table-striped table-sm text-center">
<thead>
<tr>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Membership' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'From' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'To' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Credentials' %}</button></th>
</tr>
</thead>
<tbody>
{% for membership in object.memberships.all %}
<tr>
<td>{{ membership.get_type }}</td>
<td>{{ membership.start_date|default:'' }}</td>
<td>{{ membership.end_date|default:'' }}</td>
<td>
<a href="javascript:void()" class="text-primary" title="{% trans 'View' %}"><i class="bi bi-eye"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,35 +1,12 @@
{% extends "idhub/base.html" %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block content %}
<h3>
<i class="{{ icon }}"></i>
{{ subtitle }}
</h3>
<div class="row mt-5">
<div class="col">
<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 'Role' %}</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 'Service' %}</button></th>
</tr>
</thead>
<tbody>
{% for rol in user.roles.all %}
<tr>
<td>{{ rol.service.get_roles }}</td>
<td>{{ rol.service.description }}</td>
<td>{{ rol.service.domain }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% render_table table %}
{% endblock %}

View file

@ -1,5 +1,5 @@
from django.test import TestCase
from idhub.models import Event
from idhub.models import Event, Membership, Rol, UserRol, Service
from idhub_auth.models import User
@ -14,4 +14,60 @@ class EventModelTest(TestCase):
self.assertEqual(event.message, 'Test Event')
self.assertEqual(event.get_type_name(), 'User registered')
# Add more tests for other model methods and properties
class UserTest(TestCase):
"""
Tests the very basic aspects of the User model,
like field properties and methods behaving as expected.
Further testing is recommended.
"""
def setUp(self):
self.user = User.objects.create(
email="test@example.com",
is_admin=True,
first_name="Dummy",
last_name="Dummyson"
)
def test_field_properties(self):
user = User.objects.get(email="test@example.com")
self.assertEqual(user._meta.get_field('email').max_length, 255)
self.assertTrue(user._meta.get_field('email').unique)
self.assertTrue(user._meta.get_field('is_active').default)
self.assertFalse(user._meta.get_field('is_admin').default)
def test_string_representation(self):
self.assertEqual(str(self.user), "test@example.com")
def test_has_perm(self):
self.assertTrue(self.user.has_perm(None))
def test_has_module_perms(self):
self.assertTrue(self.user.has_module_perms(None))
def test_is_staff_property(self):
self.assertTrue(self.user.is_staff)
def test_get_memberships(self):
Membership.objects.create(user=self.user,
type=Membership.Types.BENEFICIARY)
Membership.objects.create(user=self.user,
type=Membership.Types.EMPLOYEE)
# We test for the length because the order in which the string
# is given in get_memberships is non-deterministic
self.assertEqual(len(self.user.get_memberships()),
len("Beneficiary, Employee"))
def test_get_roles(self):
user = User.objects.get(email="test@example.com")
service = Service.objects.create(domain="Test Service")
role1 = Rol.objects.create(name="Role 1")
role2 = Rol.objects.create(name="Role 2")
service.rol.add(role1, role2)
UserRol.objects.create(user=user, service=service)
# We test for the length because the order in which the string
# is given in get_roles is non-deterministic
self.assertEqual(len(user.get_roles()), len("Role 1, Role 2"))

View file

@ -1,11 +1,13 @@
from datetime import datetime
from unittest.mock import MagicMock
from django.test import TestCase
from django.urls import reverse
from django.core.exceptions import FieldError
from idhub_auth.models import User
from idhub.admin.tables import DashboardTable
from idhub.models import Event
from idhub.admin.tables import DashboardTable, UserTable, TemplateTable
from idhub.models import Event, Membership, Rol, UserRol, Service, Schemas
class AdminDashboardTableTest(TestCase):
@ -63,3 +65,82 @@ class AdminDashboardTableTest(TestCase):
def test_pagination(self):
# TODO
pass
class UserTableTest(TestCase):
def setUp(self):
self.user1 = User.objects.create(email="user1@example.com")
self.user2 = User.objects.create(email="user2@example.com")
Membership.objects.create(user=self.user1,
type=Membership.Types.BENEFICIARY)
# Set up roles and services
service = Service.objects.create(domain="Test Service")
role = Rol.objects.create(name="Role 1")
service.rol.add(role)
UserRol.objects.create(user=self.user1, service=service)
self.table = UserTable(User.objects.all())
def test_membership_column_render(self):
# Get the user instance for the first row
user = self.table.rows[0].record
# Use the render_membership method of UserTable
rendered_column = self.table.columns['membership'].render(user)
self.assertIn("Beneficiary", str(rendered_column))
def test_role_column_render(self):
# Get the user instance for the first row
user = self.table.rows[0].record
# Use the render_role method of UserTable
rendered_column = self.table.columns['role'].render(user)
self.assertIn("Role 1", str(rendered_column))
class TemplateTableTest(TestCase):
def setUp(self):
self.table = TemplateTable(Schemas.objects.all())
self.create_schemas(amount=3)
def create_schemas(self, amount):
for i in range(amount):
self.create_schemas_object("testname" + str(i), "testdesc" + str(i))
def create_schemas_object(self, name, description):
data = self.format_data_for_json_reader(name, description)
Schemas.objects.create(
type="testy",
file_schema="filey",
data=data,
created_at=datetime.now()
)
def format_data_for_json_reader(self, name, description):
return '{"name": "'+name+'", "description": "'+description+'"}'
def test_order_table_by_name_throws_no_exception(self):
try:
# Apply sorting
self.table.order_by = 'name'
except FieldError:
self.fail("Ordering template table by name raised FieldError")
def test_order_table_by_name_correctly_orders(self):
table = TemplateTable(Schemas.objects.all(), order_by="name")
# Fetch the sorted records
sorted_records = list(table.rows)
# Verify the order is as expected
self.assertLess(sorted_records[0].record.name,
sorted_records[1].record.name)
self.assertLess(sorted_records[1].record.name,
sorted_records[2].record.name)
def test_order_table_by_description_works(self):
try:
# Apply sorting
self.table.order_by = 'description'
except FieldError:
self.fail("Ordering template table by description raised FieldError")

View file

@ -1,7 +1,8 @@
from django.urls import reverse
from django.test import TestCase
from django.test import TestCase, RequestFactory
from idhub_auth.models import User
from idhub.admin.views import PeopleListView
class AdminDashboardViewTest(TestCase):
@ -38,13 +39,55 @@ class AdminDashboardViewTest(TestCase):
self.assertTemplateUsed(response, 'auth/login.html')
def test_login_admin_user(self):
self.client.login(email='adminuser@example.org', password='adminpass12')
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')
self.client.login(email='adminuser@example.org',
password='adminpass12')
response = self.client.get(reverse('idhub:admin_dashboard'))
self.assertTemplateUsed(response, 'idhub/admin/dashboard.html')
class PeopleListViewTest(TestCase):
def setUp(self):
# Set up a RequestFactory to create mock requests
self.factory = RequestFactory()
# Create some user instances for testing
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')
# Create a request object for the view
self.request = self.factory.get(reverse('idhub:admin_people_list'))
self.request.user = self.admin_user
def test_template_used(self):
response = PeopleListView.as_view()(self.request)
self.assertEqual(response.template_name[0], "idhub/admin/people.html")
def test_context_data(self):
response = PeopleListView.as_view()(self.request)
self.assertIn('users', response.context_data)
# Assuming 2 users were created
self.assertEqual(len(response.context_data['users']), 2)
def test_get_queryset(self):
view = PeopleListView()
view.setup(self.request)
queryset = view.get_queryset()
# Assuming 2 users in the database
self.assertEqual(queryset.count(), 2)

View file

@ -1,11 +1,9 @@
import requests
from django import forms
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from idhub_auth.models import User
from idhub.models import DID, VerificableCredential
from oidc4vp.models import Organization
from idhub_auth.models import User
class ProfileForm(forms.ModelForm):

144
idhub/user/tables.py Normal file
View file

@ -0,0 +1,144 @@
from django.utils.html import format_html
import django_tables2 as tables
from idhub.models import Event, Membership, UserRol, DID, VerificableCredential
class ButtonColumn(tables.Column):
attrs = {
"a": {
"type": "button",
"class": "text-primary",
}
}
# it makes no sense to order a column of buttons
orderable = False
# django_tables will only call the render function if it doesn't find
# any empty values in the data, so we stop it from matching the data
# to any value considered empty
empty_values = ()
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")
class PersonalInfoTable(tables.Table):
type = tables.Column(verbose_name="Membership")
credential = ButtonColumn(
# TODO: See where this should actually link
linkify="idhub:user_credentials",
orderable=False
)
def render_credential(self):
return format_html('<i class="bi bi-eye"></i>')
class Meta:
model = Membership
template_name = "idhub/custom_table.html"
fields = ("type", "start_date", "end_date", "credential")
class RolesTable(tables.Table):
name = tables.Column(verbose_name="Role", empty_values=())
description = tables.Column(empty_values=())
service = tables.Column()
def render_name(self, record):
return record.service.get_roles()
def render_description(self, record):
return record.service.description
def render_service(self, record):
return record.service.domain
def order_name(self, queryset, is_descending):
queryset = queryset.order_by(
("-" if is_descending else "") + "service__rol__name"
)
return (queryset, True)
def order_description(self, queryset, is_descending):
queryset = queryset.order_by(
("-" if is_descending else "") + "service__description"
)
return (queryset, True)
def order_service(self, queryset, is_descending):
queryset = queryset.order_by(
("-" if is_descending else "") + "service__domain"
)
return (queryset, True)
class Meta:
model = UserRol
template_name = "idhub/custom_table.html"
fields = ("name", "description", "service")
class DIDTable(tables.Table):
created_at = tables.Column(verbose_name="Date")
did = tables.Column(verbose_name="ID")
edit = ButtonColumn(
linkify={
"viewname": "idhub:user_dids_edit",
"args": [tables.A("pk")]
},
orderable=False
)
delete_template_code = """<a class="text-danger"
href="javascript:void()"
data-bs-toggle="modal"
data-bs-target="#confirm-delete-{{ record.id }}"
title="Remove"
><i class="bi bi-trash"></i></a>"""
delete = tables.TemplateColumn(template_code=delete_template_code,
orderable=False)
def render_edit(self):
return format_html('<i class="bi bi-pencil-square"></i>')
class Meta:
model = DID
template_name = "idhub/custom_table.html"
fields = ("created_at", "label", "did", "edit", "delete")
class CredentialsTable(tables.Table):
description = tables.Column(verbose_name="Details", empty_values=())
def render_description(self, record):
return record.get_description()
def render_status(self, record):
return record.get_status()
def order_type(self, queryset, is_descending):
queryset = queryset.order_by(
("-" if is_descending else "") + "schema__type"
)
return (queryset, True)
def order_description(self, queryset, is_descending):
queryset = queryset.order_by(
("-" if is_descending else "") + "schema__template_description"
)
return (queryset, True)
class Meta:
model = VerificableCredential
template_name = "idhub/custom_table.html"
fields = ("type", "description", "issued_on", "status")

View file

@ -26,6 +26,14 @@ from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy
from django.http import HttpResponse
from django.contrib import messages
from django_tables2 import SingleTableView
from idhub.user.tables import (
DashboardTable,
PersonalInfoTable,
RolesTable,
DIDTable,
CredentialsTable
)
from django.core.cache import cache
from django.conf import settings
from idhub.user.forms import (
@ -36,7 +44,8 @@ from idhub.user.forms import (
)
from utils import certs
from idhub.mixins import UserView
from idhub.models import DID, VerificableCredential, Event
from idhub.models import DID, VerificableCredential, Event, Membership
from idhub_auth.models import User
class MyProfile(UserView):
@ -49,21 +58,35 @@ class MyWallet(UserView):
section = "MyWallet"
class DashboardView(UserView, TemplateView):
class DashboardView(UserView, SingleTableView):
template_name = "idhub/user/dashboard.html"
table_class = DashboardTable
title = _('Dashboard')
subtitle = _('Events')
icon = 'bi bi-bell'
section = "Home"
def get_queryset(self, **kwargs):
queryset = Event.objects.select_related('user').filter(
user=self.request.user)
class ProfileView(MyProfile, UpdateView):
return queryset
class ProfileView(MyProfile, UpdateView, SingleTableView):
template_name = "idhub/user/profile.html"
table_class = PersonalInfoTable
subtitle = _('My personal data')
icon = 'bi bi-person-gear'
from_class = ProfileForm
fields = ('first_name', 'last_name', 'email')
success_url = reverse_lazy('idhub:user_profile')
model = User
def get_queryset(self, **kwargs):
queryset = Membership.objects.select_related('user').filter(
user=self.request.user)
return queryset
def get_object(self):
return self.request.user
@ -79,11 +102,17 @@ class ProfileView(MyProfile, UpdateView):
return super().form_valid(form)
class RolesView(MyProfile, TemplateView):
class RolesView(MyProfile, SingleTableView):
template_name = "idhub/user/roles.html"
table_class = RolesTable
subtitle = _('My roles')
icon = 'fa-brands fa-critical-role'
def get_queryset(self, **kwargs):
queryset = self.request.user.roles.all()
return queryset
class GDPRView(MyProfile, TemplateView):
template_name = "idhub/user/gdpr.html"
@ -91,20 +120,17 @@ class GDPRView(MyProfile, TemplateView):
icon = 'bi bi-file-earmark-medical'
class CredentialsView(MyWallet, TemplateView):
class CredentialsView(MyWallet, SingleTableView):
template_name = "idhub/user/credentials.html"
table_class = CredentialsTable
subtitle = _('Credential management')
icon = 'bi bi-patch-check-fill'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
creds = VerificableCredential.objects.filter(
user=self.request.user
)
context.update({
'credentials': creds,
})
return context
def get_queryset(self):
queryset = VerificableCredential.objects.filter(
user=self.request.user)
return queryset
class TermsAndConditionsView(UserView, FormView):
@ -399,8 +425,9 @@ class DemandAuthorizationView(MyWallet, FormView):
return super().form_valid(form)
class DidsView(MyWallet, TemplateView):
class DidsView(MyWallet, SingleTableView):
template_name = "idhub/user/dids.html"
table_class = DIDTable
subtitle = _('Identities (DIDs)')
icon = 'bi bi-patch-check-fill'
@ -411,6 +438,11 @@ class DidsView(MyWallet, TemplateView):
})
return context
def get_queryset(self, **kwargs):
queryset = DID.objects.filter(user=self.request.user)
return queryset
class DidRegisterView(MyWallet, CreateView):
template_name = "idhub/user/did_register.html"

View file

@ -1,4 +1,4 @@
# Generated by Django 4.2.5 on 2024-01-20 14:29
# Generated by Django 4.2.5 on 2024-01-22 15:44
from django.db import migrations, models

View file

@ -1,4 +1,4 @@
# Generated by Django 4.2.5 on 2024-01-20 14:29
# Generated by Django 4.2.5 on 2024-01-22 15:44
from django.conf import settings
from django.db import migrations, models

View file

@ -1,4 +1,4 @@
# Generated by Django 4.2.5 on 2024-01-20 14:29
# Generated by Django 4.2.5 on 2024-01-22 15:44
from django.db import migrations, models
import django.db.models.deletion

View file

@ -15,6 +15,7 @@ pyld==2.0.3
pynacl==1.5.0
more-itertools==10.1.0
dj-database-url==2.1.0
faker==21.0.0
PyPDF2
svg2rlg
svglib

View file

@ -33,8 +33,8 @@ SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
DEVELOPMENT = config('DEVELOPMENT', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv())
CSRF_TRUSTED_ORIGINS = config('CSRF_TRUSTED_ORIGINS', default=[], cast=Csv())
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='', cast=Csv())
CSRF_TRUSTED_ORIGINS = config('CSRF_TRUSTED_ORIGINS', default='', cast=Csv())
DOMAIN = config("DOMAIN", "http://localhost")
@ -190,7 +190,7 @@ LOCALE_PATHS = [
]
# LANGUAGE_CODE="en"
# LANGUAGE_CODE="es"
LANGUAGE_CODE="ca"
LANGUAGE_CODE="en"
gettext = lambda s: s
LANGUAGES = (
('de', gettext('German')),