Compare commits

..

7 commits

31 changed files with 286 additions and 558 deletions

View file

@ -16,13 +16,8 @@
<div class="col"> <div class="col">
{% if lot_tags_edit %} {% if lot_tags_edit %}
<table class="table table-hover table-bordered align-middle"> <table class="table table-hover table-bordered align-middle">
<caption class="text-muted small">
{% trans 'Inbox order CANNOT be changed' %}
</caption>
<thead class="table-light"> <thead class="table-light">
<tr> <tr>
<th scope="col" class="text-center" width="5%"> #
</th>
<th scope="col">{% trans "Lot Group Name" %} <th scope="col">{% trans "Lot Group Name" %}
</th> </th>
<th scope="col" width="15%" class="text-center">{% trans "Actions" %} <th scope="col" width="15%" class="text-center">{% trans "Actions" %}
@ -31,15 +26,7 @@
</thead> </thead>
<tbody id="sortable_list"> <tbody id="sortable_list">
{% for tag in lot_tags_edit %} {% for tag in lot_tags_edit %}
<tr {% if tag.id == 1 %} class="bg-light no-sort"{% endif %} <tr>
data-lookup="{{ tag.id }}"
style="cursor: grab;" >
<td class="">
<i class="bi bi-grip-vertical" aria-hidden="true" >
<strong class="ps-2">{{ tag.order }}</strong>
</i>
</td>
<td class="font-monospace"> <td class="font-monospace">
{{ tag.name }} {{ tag.name }}
</td> </td>
@ -57,8 +44,7 @@
<button <button
type="button" class="btn btn-sm btn-outline-danger d-flex align-items-center" type="button" class="btn btn-sm btn-outline-danger d-flex align-items-center"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#deleteLotTagModal{{ tag.id }}" data-bs-target="#deleteLotTagModal{{ tag.id }}" >
{% if tag.id == 1 %} disabled {% endif %}>
<i class="bi bi-trash me-1"></i> <i class="bi bi-trash me-1"></i>
{% trans 'Delete' %} {% trans 'Delete' %}
</button> </button>
@ -69,11 +55,6 @@
</tbody> </tbody>
</table> </table>
<form id="orderingForm" method="post" action="{% url 'admin:update_lot_tag_order' %}">
{% csrf_token %}
<input type="hidden" id="orderingInput" name="ordering">
<button id="saveOrderBtn" class="btn btn-success mt-5 float-start collapse" >{% trans "Update Order" %}</button>
</form>
{% else %} {% else %}
<div class="alert alert-primary text-center mt-5" role="alert"> <div class="alert alert-primary text-center mt-5" role="alert">
@ -129,9 +110,6 @@
<label for="editLotTagInput{{ tag.id }}" class="form-label">{% trans "Tag" %}</label> <label for="editLotTagInput{{ tag.id }}" class="form-label">{% trans "Tag" %}</label>
<input type="text" class="form-control" id="editLotTagInput{{ tag.id }}" name="name" maxlength="50" value="{{ tag.name }}" required> <input type="text" class="form-control" id="editLotTagInput{{ tag.id }}" name="name" maxlength="50" value="{{ tag.name }}" required>
<div class="form-text">{% trans "Maximum 50 characters." %}</div> <div class="form-text">{% trans "Maximum 50 characters." %}</div>
{% if tag.id == 1 %}
<p class="text-muted text-end mt-3">{% trans "INBOX can only be edited, not deleted." %}</p>
{% endif %}
</div> </div>
</div> </div>
@ -158,28 +136,16 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans 'Close' %}"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans 'Close' %}"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
{% if tag.lot_set.first %}
{% if tag.lot_set.first %}
<div class="alert alert-warning text-center" role="alert"> <div class="alert alert-warning text-center" role="alert">
<strong class="text-bold mb-0"> {% trans "This lot group has" %} {{tag.lot_set.count}} {% trans "lot/s." %}</strong> {% trans "Failed to remove Lot Group, it is not empty" %}
</div> </div>
{% else %} {% endif %}
<p class="mb-0 text-muted mt-2">{% trans "Are you sure you want to delete this lot group?" %}</p>
{% endif %}
<div class="d-flex align-items-center border rounded p-3 mt-3"> <div class="d-flex align-items-center border rounded p-3 mt-3">
<div> <div>
<p class="mb-0 fw-bold">{{ tag.name }}</p> <p class="mb-0 fw-bold">{{ tag.name }}</p>
</div> </div>
</div> </div>
{% if tag.lot_set.first %}
<p class="mb-0 text-muted text-end mt-3">
{% trans "This lot group is not empty and therefore cannot be deleted." %}
</p>
{% endif %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@ -189,7 +155,7 @@
{% trans "Cancel" %} {% trans "Cancel" %}
</button> </button>
{% if tag.lot_set.first %} {% if tag.lot_set.first %}
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal" disabled> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
{% trans "Delete" %} {% trans "Delete" %}
</button> </button>
{% else %} {% else %}
@ -204,42 +170,4 @@
</div> </div>
{% endfor %} {% endfor %}
<script>
//following https://dev.to/nemecek_f/django-how-to-let-user-re-order-sort-table-of-content-with-drag-and-drop-3nlp
const saveOrderingButton = document.getElementById('saveOrderBtn');
const orderingForm = document.getElementById('orderingForm');
const formInput = orderingForm.querySelector('#orderingInput');
const sortable_table = document.getElementById('sortable_list');
const inbox_row = document.getElementById('inbox');
const sortable = new Sortable(sortable_table, {
animation: 150,
swapThreshold: 0.10,
filter: '.no-sort',
onChange: () => {
//TODO: change hide/show animation to a nicer one
const collapse = new bootstrap.Collapse(saveOrderingButton, {
toggle: false
});
collapse.show();
}
});
function saveOrdering() {
const rows = sortable_table.querySelectorAll('tr');
let ids = [];
for (let row of rows) {
ids.push(row.dataset.lookup);
}
formInput.value = ids.join(',');
orderingForm.submit();
}
saveOrderingButton.addEventListener('click', saveOrdering);
</script>
{% endblock %} {% endblock %}

View file

@ -19,5 +19,4 @@ urlpatterns = [
path("lot/add", views.AddLotTagView.as_view(), name="add_lot_tag"), path("lot/add", views.AddLotTagView.as_view(), name="add_lot_tag"),
path("lot/delete/<int:pk>", views.DeleteLotTagView.as_view(), name='delete_lot_tag'), path("lot/delete/<int:pk>", views.DeleteLotTagView.as_view(), name='delete_lot_tag'),
path("lot/edit/<int:pk>/", views.UpdateLotTagView.as_view(), name='edit_lot_tag'), path("lot/edit/<int:pk>/", views.UpdateLotTagView.as_view(), name='edit_lot_tag'),
path("lot/update_order/", views.UpdateLotTagOrderView.as_view(), name='update_lot_tag_order'),
] ]

View file

@ -206,30 +206,6 @@ class UpdateLotTagView(AdminView, UpdateView):
return response return response
class UpdateLotTagOrderView(AdminView, TemplateView):
success_url = reverse_lazy('admin:tag_panel')
def post(self, request, *args, **kwargs):
form = OrderingStateForm(request.POST)
if form.is_valid():
ordered_ids = form.cleaned_data["ordering"].split(',')
with transaction.atomic():
#TODO: log on institution wide logging - if implemented -
current_order = 2
for lookup_id in ordered_ids:
lot_tag = LotTag.objects.get(id=lookup_id)
lot_tag.order = current_order
lot_tag.save()
current_order += 1
messages.success(self.request, _("Order changed succesfuly."))
return redirect(self.success_url)
else:
return Http404
class InstitutionView(AdminView, UpdateView): class InstitutionView(AdminView, UpdateView):
template_name = "institution.html" template_name = "institution.html"
title = _("Edit institution") title = _("Edit institution")

View file

@ -35,7 +35,7 @@ class DashboardView(LoginRequiredMixin):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
lot_tags = LotTag.objects.filter( lot_tags = LotTag.objects.filter(
owner=self.request.user.institution, owner=self.request.user.institution,
).order_by('order') )
context.update({ context.update({
"commit_id": settings.COMMIT, "commit_id": settings.COMMIT,
'title': self.title, 'title': self.title,

View file

@ -1,4 +1,4 @@
{% load i18n static startswith %} {% load i18n static %}
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
@ -80,122 +80,97 @@
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse"> <nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="position-sticky pt-5"> <div class="position-sticky pt-5">
<ul class="nav flex-column"> <ul class="nav flex-column">
{% if user.is_admin %}
<!-- Admin submenu-->
{% if user.is_admin %}
{% with is_path=request.path|startswith:'/admin' %}
<li class="nav-item"> <li class="nav-item">
<a class="admin {% if is_path %}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()"> <a class="admin {% if path in 'panel users states_panel tag_panel edit_user delete_user new_user institution' %}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> <i class="bi bi-person-fill-gear icon_sidebar"></i>
{% trans 'Admin' %} {% trans 'Admin' %}
</a> </a>
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path in 'panel users tag_panel states_panel' %}expanded{% else %}collapse{% endif %}" id="ul_admin" data-bs-parent="#sidebarMenu"> <ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path in 'panel users tag_panel states_panel' %}expanded{% else %}collapse{% endif %}" id="ul_admin" data-bs-parent="#sidebarMenu">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if path in 'panel institution' and is_path %} active2{% endif %}" href="{% url 'admin:panel' %}"> <a class="nav-link{% if path in 'panel institution' %} active2{% endif %}" href="{% url 'admin:panel' %}">
{% trans 'Panel' %} {% trans 'Panel' %}
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if path in 'users edit_user new_user delete_user' and is_path %} active2{% endif %}" href="{% url 'admin:users' %}"> <a class="nav-link{% if path in 'users edit_user new_user delete_user' %} active2{% endif %}" href="{% url 'admin:users' %}">
{% trans 'Users' %} {% trans 'Users' %}
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if path == 'states_panel' and is_path %} active2{% endif %}" href="{% url 'admin:states_panel' %}"> <a class="nav-link{% if path == 'states_panel' %} active2{% endif %}" href="{% url 'admin:states_panel' %}">
{% trans 'States' %} {% trans 'States' %}
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if path == 'tag_panel' and is_path %} active2{% endif %}" href="{% url 'admin:tag_panel' %}"> <a class="nav-link{% if path == 'tag_panel' %} active2{% endif %}" href="{% url 'admin:tag_panel' %}">
{% trans 'Lot Groups' %} {% trans 'Lot Groups' %}
</a> </a>
</li> </li>
</ul> </ul>
</li> </li>
{% endwith %} {% endif %}
{% endif %}
<!-- Device submenu-->
{% with is_path=request.path|startswith:'/dashboard' %}
<li class="nav-item"> <li class="nav-item">
<a class="admin {% if is_path %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_device" aria-expanded="false" aria-controls="ul_lots" href="javascript:void()"> <a class="admin {% if path in 'all_device' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_device" aria-expanded="false" aria-controls="ul_lots" href="javascript:void()">
<i class="bi bi-laptop icon_sidebar"></i> <i class="bi bi-laptop icon_sidebar"></i>
{% trans 'Device' %} {% trans 'Device' %}
</a> </a>
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path in 'all_device' %}expanded{% else %}collapse{% endif %}" id="ul_device" data-bs-parent="#sidebarMenu"> <ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path in 'all_device' %}expanded{% else %}collapse{% endif %}" id="ul_device" data-bs-parent="#sidebarMenu">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if path == 'all_device' and is_path %} active2{% endif %}" href="{% url 'dashboard:all_device' %}"> <a class="nav-link{% if path == 'all_device' %} active2{% endif %}" href="{% url 'dashboard:all_device' %}">
{% trans 'All' %} {% trans 'All' %}
</a> </a>
</li> </li>
</ul> </ul>
</li> </li>
{% endwith %}
<!-- Lot submenu-->
{% with is_path=request.path|startswith:'/lot' %}
<li class="nav-item">
<a class="admin {% if is_path %}active{% endif %} nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_lots" aria-expanded="false" aria-controls="ul_lots" href="javascript:void()">
<i class="bi bi-database icon_sidebar"></i>
{% trans 'Lots' %}
</a>
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if is_path %}expanded{% else %}collapse{% endif %}" id="ul_lots" data-bs-parent="#sidebarMenu">
<!-- If current path is lot, then add a link so user knows where it is situated on the site -->
{% if path == "lot" and lot %}
<li class="nav-item">
<a class="nav-link active2" href="{% url 'lot:lot' lot.id %}">
{% trans "Lot" %} {{ lot.name }}
</a>
</li>
{% endif %}
{% for tag in lot_tags %}
<li class="nav-item">
{% if tag.inbox %}
<a class="nav-link{% if path == 'unassigned' %} active2{% endif %}" href="{% url 'lot:unassigned' %}">
{% else %}
<a class="nav-link{% if path == 'tags' %} active2{% endif %}" href="{% url 'lot:tags' tag.id %}">
{% endif %}
{{ tag.name|truncatechars:17 }}
</a>
</li>
{% endfor %}
</ul>
</li>
{% endwith %}
<!--Evidences submenu -->
{% with is_path=request.path|startswith:'/evidence,/device/add' %}
<li class="nav-item"> <li class="nav-item">
<a class="admin {% if is_path %} active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_evidences" aria-expanded="false" aria-controls="ul_evidences" href="javascript:void()"> <a class="admin {% if path == 'tags' or path == 'lot' or path in 'unassigned dashboard' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_lots" aria-expanded="false" aria-controls="ul_lots" href="javascript:void()">
<i class="bi bi-database icon_sidebar"></i>
{% trans 'Lots' %}
</a>
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path == 'tags' or path == 'lot' or path in 'unassigned dashboard' %}expanded{% else %}collapse{% endif %}" id="ul_lots" data-bs-parent="#sidebarMenu">
{% for tag in lot_tags %}
<li class="nav-items">
{% if tag.inbox %}
<a class="nav-link{% if path == 'unassigned' %} active2{% endif %}" href="{% url 'dashboard:unassigned' %}">
{% else %}
<a class="nav-link{% if path == 'tags' %} active2{% endif %}" href="{% url 'lot:tags' tag.id %}">
{% endif %}
{{ tag.name }}
</a>
</li>
{% endfor %}
</ul>
</li>
<li class="nav-item">
<a class="admin {% if path in 'upload list import add' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_evidences" aria-expanded="false" aria-controls="ul_evidences" href="javascript:void()">
<i class="bi bi-usb-drive icon_sidebar"></i> <i class="bi bi-usb-drive icon_sidebar"></i>
{% trans 'Evidences' %} {% trans 'Evidences' %}
</a> </a>
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if is_path %}expanded{% else %}collapse{% endif %}" id="ul_evidences" data-bs-parent="#sidebarMenu"> <ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path in 'upload list import add' %}expanded{% else %}collapse{% endif %}" id="ul_evidences" data-bs-parent="#sidebarMenu">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if path == 'list' and is_path %} active2{% endif %}" href="{% url 'evidence:list' %}"> <a class="nav-link{% if path == 'list' %} active2{% endif %}" href="{% url 'evidence:list' %}">
{% trans 'List of evidences' %} {% trans 'List of evidences' %}
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if path == 'upload' and is_path %} active2{% endif %}" href="{% url 'evidence:upload' %}"> <a class="nav-link{% if path == 'upload' %} active2{% endif %}" href="{% url 'evidence:upload' %}">
{% trans 'Upload with JSON file' %} {% trans 'Upload with JSON file' %}
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if path == 'import' and is_path%} active2{% endif %}" href="{% url 'evidence:import' %}"> <a class="nav-link{% if path == 'import' %} active2{% endif %}" href="{% url 'evidence:import' %}">
{% trans 'Upload with Spreadsheet' %} {% trans 'Upload with Spreadsheet' %}
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if path == 'add' and is_path %} active2{% endif %}" href="{% url 'device:add' %}"> <a class="nav-link{% if path == 'add' %} active2{% endif %}" href="{% url 'device:add' %}">
{% trans 'Upload with Web Form' %} {% trans 'Upload with Web Form' %}
</a> </a>
</li> </li>
</ul> </ul>
</li> </li>
{% endwith %}
</ul> </ul>
</div> </div>
</nav> </nav>
@ -219,10 +194,10 @@
{% endif %} {% endif %}
</h1> </h1>
<form method="post" action="{% url 'dashboard:search' %}"> <form method="get" action="{% url 'dashboard:search' %}">
{% csrf_token %} {% csrf_token %}
<div class="input-group rounded"> <div class="input-group rounded">
<input type="search" name="search" class="form-control rounded" placeholder="Search your device..." aria-label="Search" aria-describedby="search-addon" /> <input type="search" name="search" class="form-control rounded" {% if search %}value="{{ search }}" {% endif %}placeholder="Search your device..." aria-label="Search" aria-describedby="search-addon" />
<span class="input-group-text border-0" id="search-addon"> <span class="input-group-text border-0" id="search-addon">
<i class="fas fa-search"></i> <i class="fas fa-search"></i>
</span> </span>
@ -239,14 +214,8 @@
</div> </div>
</div> </div>
<div class="d-flex flex-wrap gap-2 justify-content-end m-2 mb-4"> {% block content %}
{% block actions %} {% endblock content %}
{% endblock %}
</div>
<div class= "mx-2">
{% block content %}
{% endblock content %}
</div>
</main> </main>
</div> </div>

View file

@ -4,12 +4,12 @@
<ul class="pagination"> <ul class="pagination">
{% if page_number > 1 %} {% if page_number > 1 %}
<li class="previous"> <li class="previous">
<a type="button" class="btn btn-grey border border-dark" href="?page=1&limit={{ limit }}"> <a type="button" class="btn btn-grey border border-dark" href="?page=1&limit={{ limit }}{% if search %}&search={{ search }}{% endif %}">
&laquo; &laquo;
</a> </a>
</li> </li>
<li class="previous"> <li class="previous">
<a type="button" class="btn btn-grey border border-dark" href="?page={{ page_number|add:-1 }}&limit={{ limit }}"> <a type="button" class="btn btn-grey border border-dark" href="?page={{ page_number|add:-1 }}&limit={{ limit }}{% if search %}&search={{ search }}{% endif %}">
{% trans 'Previous' %} {% trans 'Previous' %}
</a> </a>
</li> </li>
@ -24,7 +24,7 @@
{% if p == page_number or p == "..." %} {% if p == page_number or p == "..." %}
href="#"> href="#">
{% else %} {% else %}
href="?page={{ p }}&limit={{ limit }}"> href="?page={{ p }}&limit={{ limit }}{% if search %}&search={{ search }}{% endif %}">
{% endif %} {% endif %}
{{ p }} {{ p }}
</a> </a>
@ -34,12 +34,12 @@
{% if page_number < total_pages %} {% if page_number < total_pages %}
<li class="previous"> <li class="previous">
<a type="button" class="btn btn-grey border border-dark" href="?page={{ page_number|add:+1 }}&limit={{ limit }}"> <a type="button" class="btn btn-grey border border-dark" href="?page={{ page_number|add:+1 }}&limit={{ limit }}{% if search %}&search={{ search }}{% endif %}">
{% trans 'Next' %} {% trans 'Next' %}
</a> </a>
</li> </li>
<li class="previous"> <li class="previous">
<a type="button" class="btn btn-grey border border-dark" href="?page={{ total_pages }}&limit={{ limit }}"> <a type="button" class="btn btn-grey border border-dark" href="?page={{ total_pages }}&limit={{ limit }}{% if search %}&search={{ search }}{% endif %}">
&raquo; &raquo;
</a> </a>
</li> </li>

View file

@ -86,7 +86,7 @@
</div> </div>
<div class="row mt-3"> <div class="row mt-3">
<div class="col"> <div class="col">
{% render_pagination page total_pages limit %} {% render_pagination page total_pages limit search %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -3,7 +3,7 @@ from django import template
register = template.Library() register = template.Library()
@register.inclusion_tag('pagination.html') @register.inclusion_tag('pagination.html')
def render_pagination(page_number, total_pages, limit=10): def render_pagination(page_number, total_pages, limit=10, search=None):
""" """
Template tag for render pagination Template tag for render pagination
@ -16,5 +16,6 @@ def render_pagination(page_number, total_pages, limit=10):
return { return {
'page_number': page_number, 'page_number': page_number,
'total_pages': total_pages, 'total_pages': total_pages,
'limit': limit 'limit': limit,
"search": search,
} }

View file

@ -1,9 +0,0 @@
#https://medium.com/@malvin.lok/add-a-custom-function-startwith-on-the-django-template-f11e1916f0d1
from django import template
register = template.Library()
@register.filter('startswith')
def startswith(value, prefixes):
return any(value.startswith(prefix) for prefix in prefixes.split(','))

View file

@ -4,6 +4,8 @@ from dashboard import views
app_name = 'dashboard' app_name = 'dashboard'
urlpatterns = [ urlpatterns = [
path("", views.UnassignedDevicesView.as_view(), name="unassigned"),
path("all", views.AllDevicesView.as_view(), name="all_device"), path("all", views.AllDevicesView.as_view(), name="all_device"),
path("<int:pk>/", views.LotDashboardView.as_view(), name="lot"),
path("search", views.SearchView.as_view(), name="search"), path("search", views.SearchView.as_view(), name="search"),
] ]

View file

@ -12,6 +12,16 @@ from device.models import Device
from lot.models import Lot from lot.models import Lot
class UnassignedDevicesView(InventaryMixin):
template_name = "unassigned_devices.html"
section = "Unassigned"
title = _("Unassigned Devices")
breadcrumb = "Devices / Unassigned Devices"
def get_devices(self, user, offset, limit):
return Device.get_unassigned(self.request.user.institution, offset, limit)
class AllDevicesView(InventaryMixin): class AllDevicesView(InventaryMixin):
template_name = "unassigned_devices.html" template_name = "unassigned_devices.html"
section = "All" section = "All"
@ -22,15 +32,60 @@ class AllDevicesView(InventaryMixin):
return Device.get_all(self.request.user.institution, offset, limit) return Device.get_all(self.request.user.institution, offset, limit)
class LotDashboardView(InventaryMixin, DetailsMixin):
template_name = "unassigned_devices.html"
section = "dashboard_lot"
title = _("Lot Devices")
breadcrumb = "Lot / Devices"
model = Lot
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
lot = context.get('object')
context.update({
'lot': lot,
})
return context
def get_devices(self, user, offset, limit):
chids = self.object.devicelot_set.all().values_list(
"device_id", flat=True
).distinct()
props = SystemProperty.objects.filter(
owner=self.request.user.institution,
value__in=chids
).order_by("-created")
chids_ordered = []
for x in props:
if x.value not in chids_ordered:
chids_ordered.append(x.value)
chids_page = chids_ordered[offset:offset+limit]
return [Device(id=x) for x in chids_page], len(chids_ordered)
class SearchView(InventaryMixin): class SearchView(InventaryMixin):
template_name = "unassigned_devices.html" template_name = "unassigned_devices.html"
section = "Search" section = "Search"
title = _("Search Devices") title = _("Search Devices")
breadcrumb = "Devices / Search Devices" breadcrumb = "Devices / Search Devices"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
search_params = self.request.GET.urlencode(),
search = self.request.GET.get("search")
if search:
context.update({
'search_params': search_params,
'search': search
})
return context
def get_devices(self, user, offset, limit): def get_devices(self, user, offset, limit):
post = dict(self.request.POST) query = dict(self.request.GET).get("search")
query = post.get("search")
if not query: if not query:
return [], 0 return [], 0
@ -41,6 +96,12 @@ class SearchView(InventaryMixin):
offset, offset,
limit limit
) )
count = search(
self.request.user.institution,
query[0],
0,
9999
).size()
if not matches or not matches.size(): if not matches or not matches.size():
return self.search_hids(query, offset, limit) return self.search_hids(query, offset, limit)
@ -55,7 +116,6 @@ class SearchView(InventaryMixin):
devices.append(dev) devices.append(dev)
dev_id.append(dev.id) dev_id.append(dev.id)
count = matches.size()
# TODO fix of pagination, the count is not correct # TODO fix of pagination, the count is not correct
return devices, count return devices, count

View file

@ -336,7 +336,7 @@ class Device:
FROM FROM
RankedProperties RankedProperties
WHERE WHERE
row_num = 1; row_num = 1
ORDER BY created DESC ORDER BY created DESC
""".format( """.format(
uuid=uuid.replace("-", ""), uuid=uuid.replace("-", ""),

View file

@ -78,7 +78,7 @@
{% endfor %} {% endfor %}
</div> </div>
<div class="container"> <div class="container">
<a class="btn btn-grey" href="{% url 'lot:unassigned' %}">{% translate "Cancel" %}</a> <a class="btn btn-grey" href="{% url 'dashboard:unassigned' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" /> <input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div> </div>

View file

@ -9,7 +9,7 @@
{% if lot.type == tag %} {% if lot.type == tag %}
<div class="row mb-3"> <div class="row mb-3">
<div class="col"> <div class="col">
<a href="{% url 'lot:lot' lot.id %}">{{ lot.name }} <a href="{% url 'dashboard:lot' lot.id %}">{{ lot.name }}
</a> </a>
</div> </div>
</div> </div>

View file

@ -40,7 +40,7 @@ class NewDeviceView(DashboardView, FormView):
template_name = "new_device.html" template_name = "new_device.html"
title = _("New Device") title = _("New Device")
breadcrumb = "Device / New Device" breadcrumb = "Device / New Device"
success_url = reverse_lazy('lot:unassigned') success_url = reverse_lazy('dashboard:unassigned')
form_class = DeviceFormSet form_class = DeviceFormSet
def form_valid(self, form): def form_valid(self, form):
@ -57,7 +57,7 @@ class EditDeviceView(DashboardView, UpdateView):
template_name = "new_device.html" template_name = "new_device.html"
title = _("Update Device") title = _("Update Device")
breadcrumb = "Device / Update Device" breadcrumb = "Device / Update Device"
success_url = reverse_lazy('lot:unassigned_devices') success_url = reverse_lazy('dashboard:unassigned_devices')
model = SystemProperty model = SystemProperty
def get_form_kwargs(self): def get_form_kwargs(self):
@ -74,6 +74,7 @@ class EditDeviceView(DashboardView, UpdateView):
class DetailsView(DashboardView, TemplateView): class DetailsView(DashboardView, TemplateView):
template_name = "details.html" template_name = "details.html"
title = _("Device")
breadcrumb = "Device / Details" breadcrumb = "Device / Details"
model = SystemProperty model = SystemProperty
@ -112,7 +113,6 @@ class DetailsView(DashboardView, TemplateView):
device_notes = Note.objects.filter(snapshot_uuid__in=uuids).order_by('-date') device_notes = Note.objects.filter(snapshot_uuid__in=uuids).order_by('-date')
context.update({ context.update({
'object': self.object, 'object': self.object,
'title': _("Device {}".format(self.object.shortid)),
'snapshot': last_evidence, 'snapshot': last_evidence,
'lot_tags': lot_tags, 'lot_tags': lot_tags,
'dpps': dpps, 'dpps': dpps,

View file

@ -147,6 +147,7 @@ run_demo() {
'example/demo-snapshots-vc/snapshot_pre-verifiable-credential.json' \ 'example/demo-snapshots-vc/snapshot_pre-verifiable-credential.json' \
> 'example/snapshots/snapshot_workbench-script_verifiable-credential.json' > 'example/snapshots/snapshot_workbench-script_verifiable-credential.json'
fi fi
./manage.py create_default_states "${INIT_ORG}"
/usr/bin/time ./manage.py up_snapshots example/snapshots/ "${INIT_USER}" /usr/bin/time ./manage.py up_snapshots example/snapshots/ "${INIT_USER}"
} }

View file

@ -19,6 +19,11 @@ class BuildMix:
self.mac = "" self.mac = ""
self.type = "" self.type = ""
self.version = "" self.version = ""
self.default = ""
self.algorithms = {}
if not self.uuid:
logger.error("snapshot without UUID. Software {}".format(self.json.get("software")))
return
self.get_details() self.get_details()
self.generate_chids() self.generate_chids()

View file

@ -55,6 +55,9 @@ class Build:
if check: if check:
return return
if not self.build.uuid:
return
self.index() self.index()
self.create_annotations() self.create_annotations()
if settings.DPP: if settings.DPP:

View file

@ -22,7 +22,7 @@
{% bootstrap_form form alert_error_type="none" error_css_class="alert alert-danger alert-icon alert-icon-border" %} {% bootstrap_form form alert_error_type="none" error_css_class="alert alert-danger alert-icon alert-icon-border" %}
<div class="form-actions-no-box"> <div class="form-actions-no-box">
<a class="btn btn-grey" href="{% url 'lot:unassigned' %}">{% translate "Cancel" %}</a> <a class="btn btn-grey" href="{% url 'dashboard:unassigned' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" /> <input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div> </div>

View file

@ -17,17 +17,17 @@ class LoginView(auth_views.LoginView):
template_name = 'login.html' template_name = 'login.html'
extra_context = { extra_context = {
'title': _('Login'), 'title': _('Login'),
'success_url': reverse_lazy('lot:unassigned'), 'success_url': reverse_lazy('dashboard:unassigned'),
'commit_id': settings.COMMIT, 'commit_id': settings.COMMIT,
} }
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.extra_context['success_url'] = request.GET.get( self.extra_context['success_url'] = request.GET.get(
'next', 'next',
reverse_lazy('lot:unassigned') reverse_lazy('dashboard:unassigned')
) )
if not self.request.user.is_anonymous: if not self.request.user.is_anonymous:
return redirect(reverse_lazy('lot:unassigned')) return redirect(reverse_lazy('dashboard:unassigned'))
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)

View file

@ -1,22 +0,0 @@
# Generated by Django 5.0.6 on 2025-02-21 20:58
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("lot", "0007_lottag_inbox"),
("user", "0002_institution_algorithm"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddConstraint(
model_name="lot",
constraint=models.UniqueConstraint(
fields=("owner", "name"), name="unique_institution_and_name"
),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2025-02-25 12:32
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('lot', '0007_lottag_inbox'),
]
operations = [
migrations.RenameField(
model_name='lot',
old_name='closed',
new_name='archived',
),
]

View file

@ -1,31 +0,0 @@
# Generated by Django 5.0.6 on 2025-02-28 16:57
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("lot", "0008_lot_unique_institution_and_name"),
("user", "0002_institution_algorithm"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveConstraint(
model_name="lot",
name="unique_institution_and_name",
),
migrations.AddField(
model_name="lottag",
name="order",
field=models.PositiveIntegerField(default=0),
),
migrations.AddConstraint(
model_name="lot",
constraint=models.UniqueConstraint(
fields=("owner", "name", "type"), name="unique_institution_and_name"
),
),
]

View file

@ -1,5 +1,4 @@
from django.db import models from django.db import models
from django.db.models import Max
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from utils.constants import ( from utils.constants import (
STR_SM_SIZE, STR_SM_SIZE,
@ -17,28 +16,11 @@ class LotTag(models.Model):
owner = models.ForeignKey(Institution, 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) user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
inbox = models.BooleanField(default=False) inbox = models.BooleanField(default=False)
order = models.PositiveIntegerField(default=0)
def __str__(self): def __str__(self):
return self.name return self.name
def save(self, *args, **kwargs):
if not self.pk:
# set the order to be last
max_order = LotTag.objects.filter(owner=self.owner).aggregate(Max('order'))['order__max']
self.order = (max_order or 0) + 1
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
institution = self.owner
order = self.order
super().delete(*args, **kwargs)
# Adjust the order of other instances
LotTag.objects.filter(owner=institution, order__gt=order).update(order=models.F('order') - 1)
class DeviceLot(models.Model): class DeviceLot(models.Model):
lot = models.ForeignKey("Lot", on_delete=models.CASCADE) lot = models.ForeignKey("Lot", on_delete=models.CASCADE)
device_id = models.CharField(max_length=STR_EXTEND_SIZE, blank=False, null=False) device_id = models.CharField(max_length=STR_EXTEND_SIZE, blank=False, null=False)
@ -50,17 +32,11 @@ class Lot(models.Model):
name = models.CharField(max_length=STR_SIZE, blank=True, null=True) name = models.CharField(max_length=STR_SIZE, blank=True, null=True)
code = models.CharField(max_length=STR_SIZE, blank=True, null=True) code = models.CharField(max_length=STR_SIZE, blank=True, null=True)
description = models.CharField(max_length=STR_SIZE, blank=True, null=True) description = models.CharField(max_length=STR_SIZE, blank=True, null=True)
closed = models.BooleanField(default=False) archived = models.BooleanField(default=False)
owner = models.ForeignKey(Institution, 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) user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
type = models.ForeignKey(LotTag, on_delete=models.CASCADE) type = models.ForeignKey(LotTag, on_delete=models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(fields=['owner', 'name', 'type'], name='unique_institution_and_name')
]
def add(self, v): def add(self, v):
if DeviceLot.objects.filter(lot=self, device_id=v).exists(): if DeviceLot.objects.filter(lot=self, device_id=v).exists():
return return
@ -70,11 +46,6 @@ class Lot(models.Model):
for d in DeviceLot.objects.filter(lot=self, device_id=v): for d in DeviceLot.objects.filter(lot=self, device_id=v):
d.delete() d.delete()
@property
def devices(self):
return DeviceLot.objects.filter(lot=self)
class LotProperty(Property): class LotProperty(Property):
lot = models.ForeignKey(Lot, on_delete=models.CASCADE) lot = models.ForeignKey(Lot, on_delete=models.CASCADE)

View file

@ -1,6 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n %} {% load i18n %}
{% load django_bootstrap5 %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
@ -9,34 +8,13 @@
</div> </div>
</div> </div>
{% load django_bootstrap5 %}
<div class="row mb-3"> <div class="row mb-3">
<div class="col"> <div class="col">
<div class="card border{% if object.devices.count > 0 %}border-danger{% endif %}"> Are you sure than want remove the lot {{ object.name }} with {{ object.devices.count }} devices.
<div class="card-body">
<h5 class="card-title">{% trans "Delete Lot" %}</h5>
<div class="card-text mt-4">
{% blocktranslate with name=object.name count devices=object.devices.count %}
Are you sure you want to remove the lot "{{ name }}" with {{ devices }} device?
{% plural %}
Are you sure you want to remove the lot "{{ name }}" with {{ devices }} devices?
{% endblocktranslate %}
</div>
{% if object.devices.count > 0 %}
<div class="mt-3 text-danger">
<i class="bi bi-exclamation-circle-fill"></i>
{% trans "All associated devices will be deassigned." %}
</div>
{% else %}
<div class="mt-3 text-muted">
<i class="bi bi-info-circle-fill"></i>
{% trans "No devices are associated with this lot." %}
</div>
{% endif %}
</div>
</div>
</div> </div>
</div> </div>
<form role="form" method="post"> <form role="form" method="post">
{% csrf_token %} {% csrf_token %}
{% if form.errors %} {% if form.errors %}
@ -52,8 +30,8 @@
{% endif %} {% endif %}
{% bootstrap_form form %} {% bootstrap_form form %}
<div class="form-actions-no-box"> <div class="form-actions-no-box">
<a class="btn btn-grey" href="{{ request.META.HTTP_REFERER }}">{% translate "Cancel" %}</a> <a class="btn btn-grey" href="{% url 'dashboard:unassigned' %}">{% translate "Cancel" %}</a>
<input class="btn btn-danger" type="submit" name="submit" value="{% translate 'Delete' %}"/> <input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Delete' %}" />
</div> </div>
</form> </form>

View file

@ -1,10 +0,0 @@
{% load i18n %}
<a href="{% url 'lot:edit' record.id %}" class="btn btn-sm btn-outline-primary me-2">
<i class="bi bi-pen"></i>
{% trans 'Edit' %}
</a>
<a href="{% url 'lot:delete' record.id %}" class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i>
{% trans 'Delete' %}
</a>

View file

@ -1,72 +1,43 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n paginacion %} {% load i18n %}
{% load render_table from django_tables2 %}
{% block content %} {% block content %}
<div class="row mb-3">
<a href="{% url 'lot:add' %}" type="button" class="btn btn-green-admin mb-3"> <div class="col">
<i class="bi bi-plus"></i> <h3>{{ subtitle }}</h3>
{% trans 'Add new lot' %}
</a>
<h3>{{ subtitle }}</h3>
<!-- Search and Filter Section -->
<div class="row mb-4">
<!-- Search Input -->
<div class="col-md-6 mb-3">
<form method="get" class="d-flex">
<input
type="text"
name="q"
class="form-control me-2"
placeholder="{% trans 'Search by name or description...' %}"
value="{{ search_query }}">
<button type="submit" class="btn btn-outline-secondary">
<i class="bi bi-search"></i>
</button>
</form>
</div> </div>
<div class="col text-center">
{% if show_archived %}
<a href="?show_archived=false" class="btn btn-green-admin">
{% trans 'Show active lots' %}
</a>
{% else %}
<a href="?show_archived=true" class="btn btn-green-admin">
{% trans 'Show archived lots' %}
</a>
{% endif %}
<div class="col-md-6 text-md-end"> <a href="{% url 'lot:add' %}" type="button" class="btn btn-green-admin">
<div class="dropdown"> <i class="bi bi-plus"></i>
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="filterDropdown" data-bs-toggle="dropdown" aria-expanded="false"> {% trans 'Add new lot' %}
<i class="bi bi-filter me-2"></i> </a>
{% trans 'Filter' %}
</button>
<ul class="dropdown-menu" aria-labelledby="filterDropdown">
<li>
<a class="dropdown-item" href="?{% if search_query %}q={{ search_query }}&{% endif %}show_closed=false">
{% trans 'Open Lots' %}
{% if show_closed == 'false' %}<i class="bi bi-check"></i>{% endif %}
</a>
</li>
<li>
<a class="dropdown-item" href="?{% if search_query %}q={{ search_query }}&{% endif %}show_closed=true">
{% trans 'Closed Lots' %}
{% if show_closed == 'true' %}<i class="bi bi-check"></i>{% endif %}
</a>
</li>
<li>
<a class="dropdown-item" href="?{% if search_query %}q={{ search_query }}&{% endif %}show_closed=both">
{% trans 'All Lots' %}
{% if show_closed == 'both' %}<i class="bi bi-check"></i>{% endif %}
</a>
</li>
</ul>
</div> </div>
</div> </div>
<!-- Lots list --> <div class="row">
{% render_table table %} <table class= "table table-striped table-sm">
<div class="mt-5 d-flex align-items-center justify-content-center"> {% for lot in lots %}
{% if table.page and table.paginator.num_pages > 1 %} <tr>
{% include "django_tables2/pagination.html" %} <td><a href="{% url 'dashboard:lot' lot.id %}">{{ lot.name }}</a></td>
{% endif %} <td>
<a href="{% url 'lot:edit' lot.id %}"><i class="bi bi-pen"></i></a>
</td>
<td>
<a href="{% url 'lot:delete' lot.id %}"><i class="bi bi-trash text-danger"></i></a>
</td>
</tr>
{% endfor %}
</table>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -24,8 +24,7 @@
{% endif %} {% endif %}
{% bootstrap_form form %} {% bootstrap_form form %}
<div class="form-actions-no-box"> <div class="form-actions-no-box">
<a class="btn btn-grey" href="{{ request.META.HTTP_REFERER }}">{% translate "Cancel" %}</a> <a class="btn btn-grey" href="{% url 'dashboard:unassigned' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" /> <input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div> </div>

View file

@ -4,8 +4,6 @@ from lot import views
app_name = 'lot' app_name = 'lot'
urlpatterns = [ urlpatterns = [
path("unasigned", views.UnassignedDevicesView.as_view(), name="unassigned"),
path("<int:pk>/", views.LotView.as_view(), name="lot"),
path("add/", views.NewLotView.as_view(), name="add"), path("add/", views.NewLotView.as_view(), name="add"),
path("delete/<int:pk>/", views.DeleteLotView.as_view(), name="delete"), path("delete/<int:pk>/", views.DeleteLotView.as_view(), name="delete"),
path("edit/<int:pk>/", views.EditLotView.as_view(), name="edit"), path("edit/<int:pk>/", views.EditLotView.as_view(), name="edit"),

View file

@ -2,52 +2,30 @@ from django.db import IntegrityError
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.shortcuts import get_object_or_404, redirect, Http404 from django.shortcuts import get_object_or_404, redirect, Http404
from django.contrib import messages from django.contrib import messages
from dashboard.mixins import InventaryMixin, DetailsMixin
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.utils.safestring import mark_safe
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from django.db.models import Q
from django.views.generic.edit import ( from django.views.generic.edit import (
CreateView, CreateView,
DeleteView, DeleteView,
UpdateView, UpdateView,
FormView, FormView,
) )
import django_tables2 as tables
from dashboard.mixins import DashboardView from dashboard.mixins import DashboardView
from evidence.models import SystemProperty
from device.models import Device
from lot.models import Lot, LotTag, LotProperty from lot.models import Lot, LotTag, LotProperty
from lot.forms import LotsForm from lot.forms import LotsForm
class NewLotView(DashboardView, CreateView):
class LotSuccessUrlMixin():
success_url = reverse_lazy('lot:unassigned') #default_url
def get_success_url(self):
lot_group_id = LotTag.objects.only('id').get(
owner=self.object.owner,
name=self.object.type
).id
#null checking just in case
if not lot_group_id:
return self.success_url
return reverse_lazy('lot:tags', args=[lot_group_id])
class NewLotView(LotSuccessUrlMixin ,DashboardView, CreateView):
template_name = "new_lot.html" template_name = "new_lot.html"
title = _("New lot") title = _("New lot")
breadcrumb = "lot / New lot" breadcrumb = "lot / New lot"
success_url = reverse_lazy('dashboard:unassigned')
model = Lot model = Lot
fields = ( fields = (
"type", "type",
"name", "name",
"code", "code",
"description", "description",
"closed", "archived",
) )
def get_form(self): def get_form(self):
@ -59,56 +37,43 @@ class NewLotView(LotSuccessUrlMixin ,DashboardView, CreateView):
return form return form
def form_valid(self, form): def form_valid(self, form):
try: form.instance.owner = self.request.user.institution
form.instance.owner = self.request.user.institution form.instance.user = self.request.user
form.instance.user = self.request.user response = super().form_valid(form)
response = super().form_valid(form)
messages.success(self.request, _("Lot created successfully."))
return response
except IntegrityError:
messages.error(self.request, _("Lot name is already defined."))
return self.form_invalid(form)
return response return response
class DeleteLotView(LotSuccessUrlMixin, DashboardView, DeleteView):
class DeleteLotView(DashboardView, DeleteView):
template_name = "delete_lot.html" template_name = "delete_lot.html"
title = _("Delete lot") title = _("Delete lot")
breadcrumb = "lot / Delete lot" breadcrumb = "lot / Delete lot"
success_url = reverse_lazy('dashboard:unassigned')
model = Lot model = Lot
fields = ( fields = (
"type", "type",
"name", "name",
"code", "code",
"description", "description",
"closed", "archived",
) )
def form_valid(self, form): def form_valid(self, form):
response = super().form_valid(form) response = super().form_valid(form)
messages.warning(self.request, _("Lot '{}' was successfully deleted.").format(self.object.name))
return response
def form_invalid(self, form):
response = super().form_invalid(form)
messages.error(self.request, _("Error deleting the lot."))
return response return response
class EditLotView(LotSuccessUrlMixin, DashboardView, UpdateView): class EditLotView(DashboardView, UpdateView):
template_name = "new_lot.html" template_name = "new_lot.html"
title = _("Edit lot") title = _("Edit lot")
breadcrumb = "Lot / Edit lot" breadcrumb = "Lot / Edit lot"
success_url = reverse_lazy('dashboard:unassigned')
model = Lot model = Lot
fields = ( fields = (
"type", "type",
"name", "name",
"code", "code",
"description", "description",
"closed", "archived",
) )
def get_form_kwargs(self): def get_form_kwargs(self):
@ -130,22 +95,12 @@ class EditLotView(LotSuccessUrlMixin, DashboardView, UpdateView):
) )
return form return form
def form_valid(self, form):
response = super().form_valid(form)
messages.warning(self.request, _("Lot '{}' was successfully edited.").format(self.object.name))
return response
def form_invalid(self, form):
response = super().form_invalid(form)
messages.error(self.request, _("Error editing the lot."))
return response
class AddToLotView(DashboardView, FormView): class AddToLotView(DashboardView, FormView):
template_name = "list_lots.html" template_name = "list_lots.html"
title = _("Add to lots") title = _("Add to lots")
breadcrumb = "lot / add to lots" breadcrumb = "lot / add to lots"
success_url = reverse_lazy('lot:unassigned') success_url = reverse_lazy('dashboard:unassigned')
form_class = LotsForm form_class = LotsForm
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -182,79 +137,31 @@ class DelToLotView(AddToLotView):
return response return response
class LotTable(tables.Table): class LotsTagsView(DashboardView, TemplateView):
name = tables.Column(linkify=("lot:lot", {"pk": tables.A("id")}), verbose_name=_("Lot Name"), attrs={"td": {"class": "fw-bold"}})
description = tables.Column(verbose_name=_("Description"), default=_("No description"),attrs={"td": {"class": "text-muted"}} )
closed = tables.Column(verbose_name=_("Status"))
created = tables.DateColumn(format="Y-m-d", verbose_name=_("Created On"))
user = tables.Column(verbose_name=("Created By"), default=_("Unknown"), attrs={"td": {"class": "text-muted"}} )
actions = tables.TemplateColumn(
template_name="lot_actions.html",
verbose_name=_(""),
attrs={"td": {"class": "text-end"}}
)
def render_closed(self, value):
if value:
return mark_safe('<span class="badge bg-danger">Closed</span>')
return mark_safe('<span class="badge bg-success">Open</span>')
class Meta:
model = Lot
fields = ("closed", "name", "description", "created", "user", "actions")
attrs = {
"class": "table table-hover align-middle",
"thead": {"class": "table-light"}
}
order_by = ("-created",)
class LotsTagsView(DashboardView, tables.SingleTableView):
template_name = "lots.html" template_name = "lots.html"
title = _("Lot group") title = _("lots")
breadcrumb = _("lots") + " /" breadcrumb = _("lots") + " /"
success_url = reverse_lazy('lot:unassigned') success_url = reverse_lazy('dashboard:unassigned')
model = Lot
table_class = LotTable
def get_queryset(self):
self.pk = self.kwargs.get('pk')
self.tag = get_object_or_404(LotTag, owner=self.request.user.institution, id=self.pk)
self.show_open = self.request.GET.get('show_open', 'false') == 'true'
self.show_closed = self.request.GET.get('show_closed', 'false')
self.search_query = self.request.GET.get('q', '').strip()
queryset = Lot.objects.filter(owner=self.request.user.institution, type=self.tag)
if self.show_closed == 'true':
queryset = queryset.filter(closed=True)
elif self.show_closed == 'false':
queryset = queryset.filter(closed=False)
if self.search_query:
queryset = queryset.filter(
Q(name__icontains=self.search_query) |
Q(description__icontains=self.search_query) |
Q(code__icontains=self.search_query)
)
sort = self.request.GET.get('sort')
if sort:
queryset = queryset.order_by(sort)
return queryset
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
self.pk = kwargs.get('pk')
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
tag = get_object_or_404(LotTag, owner=self.request.user.institution, id=self.pk)
self.title += " {}".format(tag.name)
self.breadcrumb += " {}".format(tag.name)
show_archived = self.request.GET.get('show_archived', 'false') == 'true'
lots = Lot.objects.filter(owner=self.request.user.institution).filter(
type=tag, archived=show_archived
)
context.update({ context.update({
'title': self.title + " " + self.tag.name, 'lots': lots,
'breadcrumb': self.breadcrumb + " " + self.tag.name, 'title': self.title,
'show_closed': self.show_closed, 'breadcrumb': self.breadcrumb,
'search_query': self.search_query, 'show_archived': show_archived
}) })
return context return context
class LotPropertiesView(DashboardView, TemplateView): class LotPropertiesView(DashboardView, TemplateView):
template_name = "properties.html" template_name = "properties.html"
title = _("New Lot Property") title = _("New Lot Property")
@ -282,7 +189,7 @@ class AddLotPropertyView(DashboardView, CreateView):
template_name = "new_property.html" template_name = "new_property.html"
title = _("New Lot Property") title = _("New Lot Property")
breadcrumb = "Device / New property" breadcrumb = "Device / New property"
success_url = reverse_lazy('lot:unassigned_devices') success_url = reverse_lazy('dashboard:unassigned_devices')
model = LotProperty model = LotProperty
fields = ("key", "value") fields = ("key", "value")
@ -367,46 +274,3 @@ class DeleteLotPropertyView(DashboardView, DeleteView):
# Redirect back to the original URL # Redirect back to the original URL
return redirect(self.success_url) return redirect(self.success_url)
class UnassignedDevicesView(InventaryMixin):
template_name = "unassigned_devices.html"
section = "Unassigned"
title = _("Unassigned Devices")
breadcrumb = "Devices / Unassigned Devices"
def get_devices(self, user, offset, limit):
return Device.get_unassigned(self.request.user.institution, offset, limit)
class LotView(InventaryMixin, DetailsMixin):
template_name = "unassigned_devices.html"
section = "dashboard_lot"
breadcrumb = "Lot / Devices"
model = Lot
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
lot = context.get('object')
context.update({
'lot': lot,
'title': _("Lot {}".format(lot.name))
})
return context
def get_devices(self, user, offset, limit):
chids = self.object.devicelot_set.all().values_list(
"device_id", flat=True
).distinct()
props = SystemProperty.objects.filter(
owner=self.request.user.institution,
value__in=chids
).order_by("-created")
chids_ordered = []
for x in props:
if x.value not in chids_ordered:
chids_ordered.append(x.value)
chids_page = chids_ordered[offset:offset+limit]
return [Device(id=x) for x in chids_page], len(chids_ordered)

View file

@ -1,6 +1,6 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from user.models import Institution from user.models import Institution
from lot.models import LotTag from lot.models import LotTag, Lot
class Command(BaseCommand): class Command(BaseCommand):
@ -12,6 +12,7 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
self.institution = Institution.objects.create(name=kwargs['name']) self.institution = Institution.objects.create(name=kwargs['name'])
self.create_lot_tags() self.create_lot_tags()
self.create_lots()
def create_lot_tags(self): def create_lot_tags(self):
LotTag.objects.create( LotTag.objects.create(
@ -29,3 +30,59 @@ class Command(BaseCommand):
name=tag, name=tag,
owner=self.institution owner=self.institution
) )
def create_lots(self):
for g in LotTag.objects.all():
if g.name == "Entrada":
Lot.objects.create(
name="donante-orgA",
owner=self.institution,
archived=True,
type=g
)
Lot.objects.create(
name="donante-orgB",
owner=self.institution,
type=g
)
Lot.objects.create(
name="donante-orgC",
owner=self.institution,
type=g
)
if g.name == "Salida":
Lot.objects.create(
name="beneficiario-org1",
owner=self.institution,
type=g
)
Lot.objects.create(
name="beneficiario-org2",
owner=self.institution,
archived=True,
type=g
)
Lot.objects.create(
name="beneficiario-org3",
owner=self.institution,
type=g
)
if g.name == "Temporal":
Lot.objects.create(
name="palet1",
owner=self.institution,
type=g
)
Lot.objects.create(
name="palet2",
owner=self.institution,
type=g
)
Lot.objects.create(
name="palet3",
owner=self.institution,
archived=True,
type=g
)