Merge pull request 'admin-users' (#7) from admin-users into main

Reviewed-on: #7
This commit is contained in:
cayop 2024-10-07 14:59:24 +00:00
commit 355355dcfc
30 changed files with 348 additions and 81 deletions

0
admin/__init__.py Normal file
View file

3
admin/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
admin/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class AdminConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "admin"

View file

3
admin/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View file

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col">
<h3>{{ subtitle }}</h3>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col">
<h3>{{ subtitle }}</h3>
</div>
<div class="col-2">
<a href="{% url 'admin:new_user' %}" class="btn btn-green-admin">{% translate "Add new user" %}</a>
</div>
</div>
<div class="row">
<div class="col">
<table class="table">
<thead>
<tr>
<th scope="col">Email</th>
<th>is Admin</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
{% for u in users %}
<td>{{ u.email }}</td>
<td>{{ u.is_admin }}</td>
<td><a href="{% url 'admin:edit_user' u.pk %}"><i class="bi bi-eye"></i></td>
<td><a href="{% url 'admin:delete_user' u.pk %}" class="text-danger" title="Remove"><i class="bi bi-trash"></i></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,38 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col">
<h3>{{ subtitle }}</h3>
</div>
</div>
{% load django_bootstrap5 %}
<div class="row mb-3">
<div class="col">
Are you sure than want remove the lot {{ object.name }} with {{ object.devices.count }} devices.
</div>
</div>
<form role="form" method="post">
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
<div class="message">
{% for field, error in form.errors.items %}
{{ error }}<br />
{% endfor %}
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
</div>
</div>
{% endif %}
{% bootstrap_form form %}
<div class="form-actions-no-box">
<a class="btn btn-grey" href="{% url 'admin:users' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Delete' %}" />
</div>
</form>
{% endblock %}

32
admin/templates/user.html Normal file
View file

@ -0,0 +1,32 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col">
<h3>{{ subtitle }}</h3>
</div>
</div>
{% load django_bootstrap5 %}
<form role="form" method="post">
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
<div class="message">
{% for field, error in form.errors.items %}
{{ error }}<br />
{% endfor %}
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
</div>
</div>
{% endif %}
{% bootstrap_form form %}
<div class="form-actions-no-box">
<a class="btn btn-grey" href="{% url 'admin:users' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div>
</form>
{% endblock %}

3
admin/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

12
admin/urls.py Normal file
View file

@ -0,0 +1,12 @@
from django.urls import path
from admin import views
app_name = 'admin'
urlpatterns = [
path("panel/", views.PanelView.as_view(), name="panel"),
path("users/", views.UsersView.as_view(), name="users"),
path("users/new", views.CreateUserView.as_view(), name="new_user"),
path("users/edit/<int:pk>", views.EditUserView.as_view(), name="edit_user"),
path("users/delete/<int:pk>", views.DeleteUserView.as_view(), name="delete_user"),
]

89
admin/views.py Normal file
View file

@ -0,0 +1,89 @@
from django.urls import reverse_lazy
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from django.views.generic.base import TemplateView
from django.views.generic.edit import (
CreateView,
UpdateView,
DeleteView,
)
from dashboard.mixins import DashboardView
from user.models import User
class PanelView(DashboardView, TemplateView):
template_name = "admin_panel.html"
title = _("Admin")
breadcrumb = _("admin") + " /"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return context
class UsersView(DashboardView, TemplateView):
template_name = "admin_users.html"
title = _("Users")
breadcrumb = _("admin / Users") + " /"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
"users": User.objects.filter()
})
return context
class CreateUserView(DashboardView, CreateView):
template_name = "user.html"
title = _("User")
breadcrumb = _("admin / User") + " /"
success_url = reverse_lazy('admin:users')
model = User
fields = (
"email",
"password",
"is_admin",
)
def form_valid(self, form):
form.instance.institution = self.request.user.institution
form.instance.set_password(form.instance.password)
response = super().form_valid(form)
return response
class DeleteUserView(DashboardView, DeleteView):
template_name = "delete_user.html"
title = _("Delete user")
breadcrumb = "admin / Delete user"
success_url = reverse_lazy('admin:users')
model = User
fields = (
"email",
"password",
"is_admin",
)
def form_valid(self, form):
response = super().form_valid(form)
return response
class EditUserView(DashboardView, UpdateView):
template_name = "user.html"
title = _("Edit user")
breadcrumb = "admin / Edit user"
success_url = reverse_lazy('admin:users')
model = User
fields = (
"email",
"is_admin",
)
def get_form_kwargs(self):
pk = self.kwargs.get('pk')
self.object = get_object_or_404(self.model, pk=pk)
#self.object.set_password(self.object.password)
kwargs = super().get_form_kwargs()
return kwargs

View file

@ -47,7 +47,9 @@ class DashboardView(LoginRequiredMixin):
dev_ids = self.request.session.pop("devices", [])
self._devices = []
for x in Annotation.objects.filter(value__in=dev_ids).filter(owner=self.request.user).distinct():
for x in Annotation.objects.filter(value__in=dev_ids).filter(
owner=self.request.user.institution
).distinct():
self._devices.append(Device(id=x.value))
return self._devices

View file

@ -79,6 +79,26 @@
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="position-sticky pt-5">
<ul class="nav flex-column">
{% if user.is_admin %}
<li class="nav-item">
<a class="admin {% if path in 'panel users' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_admin" aria-expanded="false" aria-controls="ul_admin" href="javascript:void()">
<i class="bi bi-person-fill-gear icon_sidebar"></i>
{% trans 'Admin' %}
</a>
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path in 'panel users' %}expanded{% else %}collapse{% endif %}" id="ul_admin" data-bs-parent="#sidebarMenu">
<li class="nav-item">
<a class="nav-link{% if path == 'panel' %} active2{% endif %}" href="{% url 'admin:panel' %}">
{% trans 'Panel' %}
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if path == 'users' %} active2{% endif %}" href="{% url 'admin:users' %}">
{% trans 'Users' %}
</a>
</li>
</ul>
</li>
{% endif %}
<li class="nav-item">
<a class="admin {% if path == 'unassigned_devices' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_devices" aria-expanded="false" aria-controls="ul_devices" href="javascript:void()">
<i class="bi bi-laptop icon_sidebar"></i>

View file

@ -13,7 +13,7 @@ class UnassignedDevicesView(InventaryMixin):
breadcrumb = "Devices / Unassigned Devices"
def get_devices(self, user, offset, limit):
return Device.get_unassigned(self.request.user, offset, limit)
return Device.get_unassigned(self.request.user.institution, offset, limit)
class LotDashboardView(InventaryMixin, DetailsMixin):

View file

@ -56,7 +56,7 @@ class BaseDeviceFormSet(forms.BaseFormSet):
if not commit:
return doc
create_index(doc)
create_index(doc, self.user)
create_annotation(doc, user, commit=commit)
return doc

View file

@ -9,9 +9,8 @@ from django.views.generic.edit import (
FormView,
)
from django.views.generic.base import TemplateView
from dashboard.mixins import DashboardView
from dashboard.mixins import DashboardView, Http403
from evidence.models import Annotation
from evidence.xapian import search
from lot.models import LotTag
from device.models import Device
from device.forms import DeviceFormSet
@ -72,7 +71,11 @@ class EditDeviceView(DashboardView, UpdateView):
def get_form_kwargs(self):
pk = self.kwargs.get('pk')
self.object = get_object_or_404(self.model, pk=pk)
self.object = get_object_or_404(
self.model,
pk=pk,
owner=self.request.user.institution
)
self.success_url = reverse_lazy('device:details', args=[pk])
kwargs = super().get_form_kwargs()
return kwargs
@ -87,6 +90,9 @@ class DetailsView(DashboardView, TemplateView):
def get(self, request, *args, **kwargs):
self.pk = kwargs['pk']
self.object = Device(id=self.pk)
if self.object.owner != self.request.user.institution:
raise Http403
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
@ -110,7 +116,9 @@ class AddAnnotationView(DashboardView, CreateView):
fields = ("key", "value")
def form_valid(self, form):
form.instance.owner = self.request.user
form.instance.owner = self.request.user.institution
form.instance.user = self.request.user
form.instance.uuid = self.annotation.uuid
form.instance.uuid = self.annotation.uuid
form.instance.type = Annotation.Type.USER
response = super().form_valid(form)
@ -119,10 +127,12 @@ class AddAnnotationView(DashboardView, CreateView):
def get_form_kwargs(self):
pk = self.kwargs.get('pk')
self.annotation = Annotation.objects.filter(
owner=self.request.user, value=pk, type=Annotation.Type.SYSTEM
owner=self.request.user.institution,
value=pk,
type=Annotation.Type.SYSTEM
).first()
if not self.annotation:
get_object_or_404(Annotation, pk=0, owner=self.request.user)
get_object_or_404(Annotation, pk=0, owner=self.request.user.institution)
self.success_url = reverse_lazy('device:details', args=[pk])
kwargs = super().get_form_kwargs()
return kwargs
@ -137,7 +147,8 @@ class AddDocumentView(DashboardView, CreateView):
fields = ("key", "value")
def form_valid(self, form):
form.instance.owner = self.request.user
form.instance.owner = self.request.user.institution
form.instance.user = self.request.user
form.instance.uuid = self.annotation.uuid
form.instance.type = Annotation.Type.DOCUMENT
response = super().form_valid(form)
@ -146,10 +157,10 @@ class AddDocumentView(DashboardView, CreateView):
def get_form_kwargs(self):
pk = self.kwargs.get('pk')
self.annotation = Annotation.objects.filter(
owner=self.request.user, value=pk, type=Annotation.Type.SYSTEM
owner=self.request.user.institution, value=pk, type=Annotation.Type.SYSTEM
).first()
if not self.annotation:
get_object_or_404(Annotation, pk=0, owner=self.request.user)
get_object_or_404(Annotation, pk=0, owner=self.request.user.institution)
self.success_url = reverse_lazy('device:details', args=[pk])
kwargs = super().get_form_kwargs()
return kwargs

View file

@ -35,7 +35,7 @@ ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
# "django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
@ -53,6 +53,7 @@ INSTALLED_APPS = [
"lot",
"documents",
"dashboard",
"admin",
]

View file

@ -23,5 +23,6 @@ urlpatterns = [
path("dashboard/", include("dashboard.urls")),
path("evidence/", include("evidence.urls")),
path("device/", include("device.urls")),
path("admin/", include("admin.urls")),
path("lot/", include("lot.urls")),
]

View file

@ -57,10 +57,12 @@ class UserTagForm(forms.Form):
def __init__(self, *args, **kwargs):
self.pk = None
self.uuid = kwargs.pop('uuid', None)
self.user = kwargs.pop('user')
instance = Annotation.objects.filter(
uuid=self.uuid,
type=Annotation.Type.SYSTEM,
key='CUSTOM_ID'
key='CUSTOM_ID',
owner=self.user.institution
).first()
if instance:
@ -77,7 +79,8 @@ class UserTagForm(forms.Form):
self.instance = Annotation.objects.filter(
uuid=self.uuid,
type=Annotation.Type.SYSTEM,
key='CUSTOM_ID'
key='CUSTOM_ID',
owner=self.user.institution
).first()
return True
@ -95,10 +98,11 @@ class UserTagForm(forms.Form):
Annotation.objects.create(
uuid=self.uuid,
owner=user,
type=Annotation.Type.SYSTEM,
key='CUSTOM_ID',
value=self.tag
value=self.tag,
owner=self.user.institution,
user=self.user
)
@ -148,7 +152,7 @@ class ImportForm(forms.Form):
if commit:
for doc, cred in table:
cred.save()
create_index(doc)
create_index(doc, self.user)
return table
return

View file

@ -1,4 +1,4 @@
# Generated by Django 5.0.6 on 2024-07-27 16:23
# Generated by Django 5.0.6 on 2024-10-07 11:38
import django.db.models.deletion
from django.conf import settings
@ -10,6 +10,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
("user", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
@ -30,7 +31,9 @@ class Migration(migrations.Migration):
("uuid", models.UUIDField()),
(
"type",
models.SmallIntegerField(choices=[(0, "System"), (1, "User")]),
models.SmallIntegerField(
choices=[(0, "System"), (1, "User"), (2, "Document")]
),
),
("key", models.CharField(max_length=256)),
("value", models.CharField(max_length=256)),
@ -38,6 +41,15 @@ class Migration(migrations.Migration):
"owner",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="user.institution",
),
),
(
"user",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to=settings.AUTH_USER_MODEL,
),
),

View file

@ -1,20 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-29 14:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("evidence", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="annotation",
name="type",
field=models.SmallIntegerField(
choices=[(0, "System"), (1, "User"), (2, "Document"), (3, "Action")]
),
),
]

View file

@ -1,20 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-29 15:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("evidence", "0002_alter_annotation_type"),
]
operations = [
migrations.AlterField(
model_name="annotation",
name="type",
field=models.SmallIntegerField(
choices=[(0, "System"), (1, "User"), (2, "Document")]
),
),
]

View file

@ -4,7 +4,7 @@ from django.db import models
from utils.constants import STR_SM_SIZE, STR_EXTEND_SIZE
from evidence.xapian import search
from user.models import User
from user.models import User, Institution
class Annotation(models.Model):
@ -15,7 +15,8 @@ class Annotation(models.Model):
created = models.DateTimeField(auto_now_add=True)
uuid = models.UUIDField()
owner = models.ForeignKey(User, on_delete=models.CASCADE)
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
type = models.SmallIntegerField(choices=Type)
key = models.CharField(max_length=STR_EXTEND_SIZE)
value = models.CharField(max_length=STR_EXTEND_SIZE)
@ -51,8 +52,10 @@ class Evidence:
def get_doc(self):
self.doc = {}
if not self.owner:
self.get_owner()
qry = 'uuid:"{}"'.format(self.uuid)
matches = search(qry, limit=1)
matches = search(self.owner, qry, limit=1)
if matches.size() < 0:
return
@ -73,6 +76,6 @@ class Evidence:
@classmethod
def get_all(cls, user):
return Annotation.objects.filter(
owner=user,
owner=user.institution,
type=Annotation.Type.SYSTEM,
).order_by("-created").values_list("uuid", flat=True).distinct()

View file

@ -4,7 +4,7 @@ import shutil
import hashlib
from datetime import datetime
from evidence.xapian import search, index
from evidence.xapian import index
from evidence.models import Evidence, Annotation
from utils.constants import ALGOS
@ -25,7 +25,7 @@ class Build:
def index(self):
snap = json.dumps(self.json)
index(self.uuid, snap)
index(self.user.institution, self.uuid, snap)
def generate_chids(self):
self.algorithms = {
@ -47,7 +47,8 @@ class Build:
for k, v in self.algorithms.items():
Annotation.objects.create(
uuid=self.uuid,
owner=self.user,
owner=self.user.institution,
user=self.user,
type=Annotation.Type.SYSTEM,
key=k,
value=v

View file

@ -92,7 +92,7 @@ class EvidenceView(DashboardView, FormView):
def get(self, request, *args, **kwargs):
self.pk = kwargs['pk']
self.object = Evidence(self.pk)
if self.object.owner != self.request.user:
if self.object.owner != self.request.user.institution:
raise Http403
self.object.get_annotations()
@ -109,6 +109,7 @@ class EvidenceView(DashboardView, FormView):
self.pk = self.kwargs.get('pk')
kwargs = super().get_form_kwargs()
kwargs['uuid'] = self.pk
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
@ -130,7 +131,7 @@ class DownloadEvidenceView(DashboardView, TemplateView):
def get(self, request, *args, **kwargs):
pk = kwargs['pk']
evidence = Evidence(pk)
if evidence.owner != self.request.user:
if evidence.owner != self.request.user.institution:
raise Http403()
evidence.get_doc()
@ -156,7 +157,11 @@ class AnnotationDeleteView(DashboardView, DeleteView):
# if is not possible resolve the reference path return 404
raise Http404
self.object = get_object_or_404(self.model, pk=self.pk, owner=self.request.user)
self.object = get_object_or_404(
self.model,
pk=self.pk,
owner=self.request.user.institution
)
self.object.delete()

View file

@ -10,7 +10,7 @@ import xapian
# indexer.set_stemmer(stemmer)
def search(qs, offset=0, limit=10):
def search(institution, qs, offset=0, limit=10):
database = xapian.Database("db")
qp = xapian.QueryParser()
@ -20,16 +20,20 @@ def search(qs, offset=0, limit=10):
qp.add_prefix("uuid", "uuid")
# qp.add_prefix("snapshot", "snapshot")
query = qp.parse_query(qs)
institution_term = "U{}".format(institution.id)
final_query = xapian.Query(
xapian.Query.OP_AND, query, xapian.Query(institution_term)
)
enquire = xapian.Enquire(database)
enquire.set_query(query)
enquire.set_query(final_query)
matches = enquire.get_mset(offset, limit)
return matches
def index(uuid, snap):
def index(institution, uuid, snap):
uuid = 'uuid:"{}"'.format(uuid)
try:
matches = search(uuid, limit=1)
matches = search(institution, uuid, limit=1)
if matches.size() > 0:
return
except (xapian.DatabaseNotFoundError, xapian.DatabaseOpeningError):
@ -47,5 +51,7 @@ def index(uuid, snap):
indexer.index_text(snap)
indexer.index_text(uuid, 10, "uuid")
# indexer.index_text(snap, 1, "snapshot")
institution_term = "U{}".format(institution.id)
doc.add_term(institution_term)
database.add_document(doc)

View file

@ -1,5 +1,5 @@
rm db/*
python3 manage.py migrate
python3 manage.py add_institution Pangea
python3 manage.py add_user Pangea user@example.org 1234
python3 manage.py add_user Pangea user@example.org 1234 True
python3 manage.py up_snapshots example/snapshots/ user@example.org

View file

@ -14,19 +14,22 @@ class Command(BaseCommand):
parser.add_argument('institution', type=str, help='institution')
parser.add_argument('email', type=str, help='email')
parser.add_argument('password', type=str, help='password')
parser.add_argument('is_admin', nargs='?', default=False, type=str, help='is admin')
def handle(self, *args, **kwargs):
email = kwargs['email']
password = kwargs['password']
is_admin = kwargs['is_admin']
institution = Institution.objects.get(name=kwargs['institution'])
self.create_user(institution, email, password)
self.create_user(institution, email, password, is_admin)
self.create_lot_tags()
def create_user(self, institution, email, password):
def create_user(self, institution, email, password, is_admin):
self.u = User.objects.create(
institution=institution,
email=email,
password=password
password=password,
is_admin=is_admin,
)
self.u.set_password(password)
self.u.save()

View file

@ -69,7 +69,8 @@ def create_annotation(doc, user, commit=False):
data = {
'uuid': doc['uuid'],
'owner': user,
'owner': user.institution,
'user': user,
'type': Annotation.Type.SYSTEM,
'key': 'CUSTOMER_ID',
'value': doc['CUSTOMER_ID'],
@ -80,10 +81,10 @@ def create_annotation(doc, user, commit=False):
return Annotation(**data)
def create_index(doc):
def create_index(doc, user):
if not doc or not doc.get('uuid'):
return []
_uuid = doc['uuid']
ev = json.dumps(doc)
index(_uuid, ev)
index(user, _uuid, ev)