Compare commits
17 commits
main
...
rework/lot
|
@ -16,8 +16,13 @@
|
|||
<div class="col">
|
||||
{% if lot_tags_edit %}
|
||||
<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">
|
||||
<tr>
|
||||
<th scope="col" class="text-center" width="5%"> #
|
||||
</th>
|
||||
<th scope="col">{% trans "Lot Group Name" %}
|
||||
</th>
|
||||
<th scope="col" width="15%" class="text-center">{% trans "Actions" %}
|
||||
|
@ -26,7 +31,15 @@
|
|||
</thead>
|
||||
<tbody id="sortable_list">
|
||||
{% for tag in lot_tags_edit %}
|
||||
<tr>
|
||||
<tr {% if tag.id == 1 %} class="bg-light no-sort"{% endif %}
|
||||
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">
|
||||
{{ tag.name }}
|
||||
</td>
|
||||
|
@ -44,7 +57,8 @@
|
|||
<button
|
||||
type="button" class="btn btn-sm btn-outline-danger d-flex align-items-center"
|
||||
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>
|
||||
{% trans 'Delete' %}
|
||||
</button>
|
||||
|
@ -55,6 +69,11 @@
|
|||
</tbody>
|
||||
</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 %}
|
||||
<div class="alert alert-primary text-center mt-5" role="alert">
|
||||
|
@ -110,6 +129,9 @@
|
|||
<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>
|
||||
<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>
|
||||
|
@ -136,16 +158,28 @@
|
|||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans 'Close' %}"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
{% if tag.lot_set.first %}
|
||||
<div class="alert alert-warning text-center" role="alert">
|
||||
{% trans "Failed to remove Lot Group, it is not empty" %}
|
||||
<strong class="text-bold mb-0"> {% trans "This lot group has" %} {{tag.lot_set.count}} {% trans "lot/s." %}</strong>
|
||||
|
||||
</div>
|
||||
{% else %}
|
||||
<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>
|
||||
<p class="mb-0 fw-bold">{{ tag.name }}</p>
|
||||
</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 class="modal-footer">
|
||||
|
@ -155,7 +189,7 @@
|
|||
{% trans "Cancel" %}
|
||||
</button>
|
||||
{% if tag.lot_set.first %}
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal" disabled>
|
||||
{% trans "Delete" %}
|
||||
</button>
|
||||
{% else %}
|
||||
|
@ -170,4 +204,42 @@
|
|||
</div>
|
||||
|
||||
{% 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 %}
|
||||
|
|
|
@ -19,4 +19,5 @@ urlpatterns = [
|
|||
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/edit/<int:pk>/", views.UpdateLotTagView.as_view(), name='edit_lot_tag'),
|
||||
path("lot/update_order/", views.UpdateLotTagOrderView.as_view(), name='update_lot_tag_order'),
|
||||
]
|
||||
|
|
|
@ -206,6 +206,30 @@ class UpdateLotTagView(AdminView, UpdateView):
|
|||
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):
|
||||
template_name = "institution.html"
|
||||
title = _("Edit institution")
|
||||
|
|
|
@ -35,7 +35,7 @@ class DashboardView(LoginRequiredMixin):
|
|||
context = super().get_context_data(**kwargs)
|
||||
lot_tags = LotTag.objects.filter(
|
||||
owner=self.request.user.institution,
|
||||
)
|
||||
).order_by('order')
|
||||
context.update({
|
||||
"commit_id": settings.COMMIT,
|
||||
'title': self.title,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% load i18n static %}
|
||||
{% load i18n static startswith %}
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
@ -80,97 +80,122 @@
|
|||
<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">
|
||||
|
||||
<!-- Admin submenu-->
|
||||
{% if user.is_admin %}
|
||||
{% with is_path=request.path|startswith:'/admin' %}
|
||||
<li class="nav-item">
|
||||
<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()">
|
||||
<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()">
|
||||
<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 tag_panel states_panel' %}expanded{% else %}collapse{% endif %}" id="ul_admin" data-bs-parent="#sidebarMenu">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if path in 'panel institution' %} active2{% endif %}" href="{% url 'admin:panel' %}">
|
||||
<a class="nav-link{% if path in 'panel institution' and is_path %} active2{% endif %}" href="{% url 'admin:panel' %}">
|
||||
{% trans 'Panel' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if path in 'users edit_user new_user delete_user' %} active2{% endif %}" href="{% url 'admin:users' %}">
|
||||
<a class="nav-link{% if path in 'users edit_user new_user delete_user' and is_path %} active2{% endif %}" href="{% url 'admin:users' %}">
|
||||
{% trans 'Users' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if path == 'states_panel' %} active2{% endif %}" href="{% url 'admin:states_panel' %}">
|
||||
<a class="nav-link{% if path == 'states_panel' and is_path %} active2{% endif %}" href="{% url 'admin:states_panel' %}">
|
||||
{% trans 'States' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if path == 'tag_panel' %} active2{% endif %}" href="{% url 'admin:tag_panel' %}">
|
||||
<a class="nav-link{% if path == 'tag_panel' and is_path %} active2{% endif %}" href="{% url 'admin:tag_panel' %}">
|
||||
{% trans 'Lot Groups' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Device submenu-->
|
||||
{% with is_path=request.path|startswith:'/dashboard' %}
|
||||
<li class="nav-item">
|
||||
<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()">
|
||||
<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()">
|
||||
<i class="bi bi-laptop icon_sidebar"></i>
|
||||
{% trans 'Device' %}
|
||||
</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">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if path == 'all_device' %} active2{% endif %}" href="{% url 'dashboard:all_device' %}">
|
||||
<a class="nav-link{% if path == 'all_device' and is_path %} active2{% endif %}" href="{% url 'dashboard:all_device' %}">
|
||||
{% trans 'All' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endwith %}
|
||||
|
||||
<!-- Lot submenu-->
|
||||
{% with is_path=request.path|startswith:'/lot' %}
|
||||
<li class="nav-item">
|
||||
<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()">
|
||||
<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 path == 'tags' or path == 'lot' or path in 'unassigned dashboard' %}expanded{% else %}collapse{% endif %}" id="ul_lots" data-bs-parent="#sidebarMenu">
|
||||
<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-items">
|
||||
<li class="nav-item">
|
||||
{% if tag.inbox %}
|
||||
<a class="nav-link{% if path == 'unassigned' %} active2{% endif %}" href="{% url 'dashboard:unassigned' %}">
|
||||
<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 }}
|
||||
{{ 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">
|
||||
<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()">
|
||||
<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()">
|
||||
<i class="bi bi-usb-drive icon_sidebar"></i>
|
||||
{% trans 'Evidences' %}
|
||||
</a>
|
||||
<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">
|
||||
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if is_path %}expanded{% else %}collapse{% endif %}" id="ul_evidences" data-bs-parent="#sidebarMenu">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if path == 'list' %} active2{% endif %}" href="{% url 'evidence:list' %}">
|
||||
<a class="nav-link{% if path == 'list' and is_path %} active2{% endif %}" href="{% url 'evidence:list' %}">
|
||||
{% trans 'List of evidences' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if path == 'upload' %} active2{% endif %}" href="{% url 'evidence:upload' %}">
|
||||
<a class="nav-link{% if path == 'upload' and is_path %} active2{% endif %}" href="{% url 'evidence:upload' %}">
|
||||
{% trans 'Upload with JSON file' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if path == 'import' %} active2{% endif %}" href="{% url 'evidence:import' %}">
|
||||
<a class="nav-link{% if path == 'import' and is_path%} active2{% endif %}" href="{% url 'evidence:import' %}">
|
||||
{% trans 'Upload with Spreadsheet' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if path == 'add' %} active2{% endif %}" href="{% url 'device:add' %}">
|
||||
<a class="nav-link{% if path == 'add' and is_path %} active2{% endif %}" href="{% url 'device:add' %}">
|
||||
{% trans 'Upload with Web Form' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endwith %}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -194,10 +219,10 @@
|
|||
{% endif %}
|
||||
</h1>
|
||||
|
||||
<form method="get" action="{% url 'dashboard:search' %}">
|
||||
<form method="post" action="{% url 'dashboard:search' %}">
|
||||
{% csrf_token %}
|
||||
<div class="input-group rounded">
|
||||
<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" />
|
||||
<input type="search" name="search" class="form-control rounded" placeholder="Search your device..." aria-label="Search" aria-describedby="search-addon" />
|
||||
<span class="input-group-text border-0" id="search-addon">
|
||||
<i class="fas fa-search"></i>
|
||||
</span>
|
||||
|
@ -214,8 +239,14 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 justify-content-end m-2 mb-4">
|
||||
{% block actions %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div class= "mx-2">
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
<ul class="pagination">
|
||||
{% if page_number > 1 %}
|
||||
<li class="previous">
|
||||
<a type="button" class="btn btn-grey border border-dark" href="?page=1&limit={{ limit }}{% if search %}&search={{ search }}{% endif %}">
|
||||
<a type="button" class="btn btn-grey border border-dark" href="?page=1&limit={{ limit }}">
|
||||
«
|
||||
</a>
|
||||
</li>
|
||||
<li class="previous">
|
||||
<a type="button" class="btn btn-grey border border-dark" href="?page={{ page_number|add:-1 }}&limit={{ limit }}{% if search %}&search={{ search }}{% endif %}">
|
||||
<a type="button" class="btn btn-grey border border-dark" href="?page={{ page_number|add:-1 }}&limit={{ limit }}">
|
||||
{% trans 'Previous' %}
|
||||
</a>
|
||||
</li>
|
||||
|
@ -24,7 +24,7 @@
|
|||
{% if p == page_number or p == "..." %}
|
||||
href="#">
|
||||
{% else %}
|
||||
href="?page={{ p }}&limit={{ limit }}{% if search %}&search={{ search }}{% endif %}">
|
||||
href="?page={{ p }}&limit={{ limit }}">
|
||||
{% endif %}
|
||||
{{ p }}
|
||||
</a>
|
||||
|
@ -34,12 +34,12 @@
|
|||
|
||||
{% if page_number < total_pages %}
|
||||
<li class="previous">
|
||||
<a type="button" class="btn btn-grey border border-dark" href="?page={{ page_number|add:+1 }}&limit={{ limit }}{% if search %}&search={{ search }}{% endif %}">
|
||||
<a type="button" class="btn btn-grey border border-dark" href="?page={{ page_number|add:+1 }}&limit={{ limit }}">
|
||||
{% trans 'Next' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="previous">
|
||||
<a type="button" class="btn btn-grey border border-dark" href="?page={{ total_pages }}&limit={{ limit }}{% if search %}&search={{ search }}{% endif %}">
|
||||
<a type="button" class="btn btn-grey border border-dark" href="?page={{ total_pages }}&limit={{ limit }}">
|
||||
»
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col">
|
||||
{% render_pagination page total_pages limit search %}
|
||||
{% render_pagination page total_pages limit %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -3,7 +3,7 @@ from django import template
|
|||
register = template.Library()
|
||||
|
||||
@register.inclusion_tag('pagination.html')
|
||||
def render_pagination(page_number, total_pages, limit=10, search=None):
|
||||
def render_pagination(page_number, total_pages, limit=10):
|
||||
"""
|
||||
Template tag for render pagination
|
||||
|
||||
|
@ -16,6 +16,5 @@ def render_pagination(page_number, total_pages, limit=10, search=None):
|
|||
return {
|
||||
'page_number': page_number,
|
||||
'total_pages': total_pages,
|
||||
'limit': limit,
|
||||
"search": search,
|
||||
'limit': limit
|
||||
}
|
||||
|
|
9
dashboard/templatetags/startswith.py
Normal file
9
dashboard/templatetags/startswith.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
#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(','))
|
|
@ -4,8 +4,6 @@ from dashboard import views
|
|||
app_name = 'dashboard'
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.UnassignedDevicesView.as_view(), name="unassigned"),
|
||||
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"),
|
||||
]
|
||||
|
|
|
@ -12,16 +12,6 @@ from device.models import Device
|
|||
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):
|
||||
template_name = "unassigned_devices.html"
|
||||
section = "All"
|
||||
|
@ -32,60 +22,15 @@ class AllDevicesView(InventaryMixin):
|
|||
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):
|
||||
template_name = "unassigned_devices.html"
|
||||
section = "Search"
|
||||
title = _("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):
|
||||
query = dict(self.request.GET).get("search")
|
||||
post = dict(self.request.POST)
|
||||
query = post.get("search")
|
||||
|
||||
if not query:
|
||||
return [], 0
|
||||
|
@ -96,12 +41,6 @@ class SearchView(InventaryMixin):
|
|||
offset,
|
||||
limit
|
||||
)
|
||||
count = search(
|
||||
self.request.user.institution,
|
||||
query[0],
|
||||
0,
|
||||
9999
|
||||
).size()
|
||||
|
||||
if not matches or not matches.size():
|
||||
return self.search_hids(query, offset, limit)
|
||||
|
@ -116,6 +55,7 @@ class SearchView(InventaryMixin):
|
|||
devices.append(dev)
|
||||
dev_id.append(dev.id)
|
||||
|
||||
count = matches.size()
|
||||
# TODO fix of pagination, the count is not correct
|
||||
return devices, count
|
||||
|
||||
|
|
|
@ -336,7 +336,7 @@ class Device:
|
|||
FROM
|
||||
RankedProperties
|
||||
WHERE
|
||||
row_num = 1
|
||||
row_num = 1;
|
||||
ORDER BY created DESC
|
||||
""".format(
|
||||
uuid=uuid.replace("-", ""),
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
<div class="container">
|
||||
<a class="btn btn-grey" href="{% url 'dashboard:unassigned' %}">{% translate "Cancel" %}</a>
|
||||
<a class="btn btn-grey" href="{% url 'lot:unassigned' %}">{% translate "Cancel" %}</a>
|
||||
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||
</div>
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{% if lot.type == tag %}
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<a href="{% url 'dashboard:lot' lot.id %}">{{ lot.name }}
|
||||
<a href="{% url 'lot:lot' lot.id %}">{{ lot.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -40,7 +40,7 @@ class NewDeviceView(DashboardView, FormView):
|
|||
template_name = "new_device.html"
|
||||
title = _("New Device")
|
||||
breadcrumb = "Device / New Device"
|
||||
success_url = reverse_lazy('dashboard:unassigned')
|
||||
success_url = reverse_lazy('lot:unassigned')
|
||||
form_class = DeviceFormSet
|
||||
|
||||
def form_valid(self, form):
|
||||
|
@ -57,7 +57,7 @@ class EditDeviceView(DashboardView, UpdateView):
|
|||
template_name = "new_device.html"
|
||||
title = _("Update Device")
|
||||
breadcrumb = "Device / Update Device"
|
||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||
success_url = reverse_lazy('lot:unassigned_devices')
|
||||
model = SystemProperty
|
||||
|
||||
def get_form_kwargs(self):
|
||||
|
@ -74,7 +74,6 @@ class EditDeviceView(DashboardView, UpdateView):
|
|||
|
||||
class DetailsView(DashboardView, TemplateView):
|
||||
template_name = "details.html"
|
||||
title = _("Device")
|
||||
breadcrumb = "Device / Details"
|
||||
model = SystemProperty
|
||||
|
||||
|
@ -113,6 +112,7 @@ class DetailsView(DashboardView, TemplateView):
|
|||
device_notes = Note.objects.filter(snapshot_uuid__in=uuids).order_by('-date')
|
||||
context.update({
|
||||
'object': self.object,
|
||||
'title': _("Device {}".format(self.object.shortid)),
|
||||
'snapshot': last_evidence,
|
||||
'lot_tags': lot_tags,
|
||||
'dpps': dpps,
|
||||
|
|
|
@ -147,7 +147,6 @@ run_demo() {
|
|||
'example/demo-snapshots-vc/snapshot_pre-verifiable-credential.json' \
|
||||
> 'example/snapshots/snapshot_workbench-script_verifiable-credential.json'
|
||||
fi
|
||||
./manage.py create_default_states "${INIT_ORG}"
|
||||
/usr/bin/time ./manage.py up_snapshots example/snapshots/ "${INIT_USER}"
|
||||
}
|
||||
|
||||
|
|
|
@ -19,11 +19,6 @@ class BuildMix:
|
|||
self.mac = ""
|
||||
self.type = ""
|
||||
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.generate_chids()
|
||||
|
||||
|
|
|
@ -55,9 +55,6 @@ class Build:
|
|||
if check:
|
||||
return
|
||||
|
||||
if not self.build.uuid:
|
||||
return
|
||||
|
||||
self.index()
|
||||
self.create_annotations()
|
||||
if settings.DPP:
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
{% bootstrap_form form alert_error_type="none" error_css_class="alert alert-danger alert-icon alert-icon-border" %}
|
||||
<div class="form-actions-no-box">
|
||||
<a class="btn btn-grey" href="{% url 'dashboard:unassigned' %}">{% translate "Cancel" %}</a>
|
||||
<a class="btn btn-grey" href="{% url 'lot:unassigned' %}">{% translate "Cancel" %}</a>
|
||||
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||
</div>
|
||||
|
||||
|
|
|
@ -17,17 +17,17 @@ class LoginView(auth_views.LoginView):
|
|||
template_name = 'login.html'
|
||||
extra_context = {
|
||||
'title': _('Login'),
|
||||
'success_url': reverse_lazy('dashboard:unassigned'),
|
||||
'success_url': reverse_lazy('lot:unassigned'),
|
||||
'commit_id': settings.COMMIT,
|
||||
}
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.extra_context['success_url'] = request.GET.get(
|
||||
'next',
|
||||
reverse_lazy('dashboard:unassigned')
|
||||
reverse_lazy('lot:unassigned')
|
||||
)
|
||||
if not self.request.user.is_anonymous:
|
||||
return redirect(reverse_lazy('dashboard:unassigned'))
|
||||
return redirect(reverse_lazy('lot:unassigned'))
|
||||
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
|
22
lot/migrations/0008_lot_unique_institution_and_name.py
Normal file
22
lot/migrations/0008_lot_unique_institution_and_name.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
# 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"
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
# 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',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,31 @@
|
|||
# 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"
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,4 +1,5 @@
|
|||
from django.db import models
|
||||
from django.db.models import Max
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from utils.constants import (
|
||||
STR_SM_SIZE,
|
||||
|
@ -16,11 +17,28 @@ class LotTag(models.Model):
|
|||
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
inbox = models.BooleanField(default=False)
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
|
||||
def __str__(self):
|
||||
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):
|
||||
lot = models.ForeignKey("Lot", on_delete=models.CASCADE)
|
||||
device_id = models.CharField(max_length=STR_EXTEND_SIZE, blank=False, null=False)
|
||||
|
@ -32,11 +50,17 @@ class Lot(models.Model):
|
|||
name = 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)
|
||||
archived = models.BooleanField(default=False)
|
||||
closed = models.BooleanField(default=False)
|
||||
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
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):
|
||||
if DeviceLot.objects.filter(lot=self, device_id=v).exists():
|
||||
return
|
||||
|
@ -46,6 +70,11 @@ class Lot(models.Model):
|
|||
for d in DeviceLot.objects.filter(lot=self, device_id=v):
|
||||
d.delete()
|
||||
|
||||
@property
|
||||
def devices(self):
|
||||
return DeviceLot.objects.filter(lot=self)
|
||||
|
||||
|
||||
class LotProperty(Property):
|
||||
lot = models.ForeignKey(Lot, on_delete=models.CASCADE)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load django_bootstrap5 %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
|
@ -8,13 +9,34 @@
|
|||
</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 class="card border{% if object.devices.count > 0 %}border-danger{% endif %}">
|
||||
<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>
|
||||
|
||||
<form role="form" method="post">
|
||||
{% csrf_token %}
|
||||
{% if form.errors %}
|
||||
|
@ -30,8 +52,8 @@
|
|||
{% endif %}
|
||||
{% bootstrap_form form %}
|
||||
<div class="form-actions-no-box">
|
||||
<a class="btn btn-grey" href="{% url 'dashboard:unassigned' %}">{% translate "Cancel" %}</a>
|
||||
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Delete' %}" />
|
||||
<a class="btn btn-grey" href="{{ request.META.HTTP_REFERER }}">{% translate "Cancel" %}</a>
|
||||
<input class="btn btn-danger" type="submit" name="submit" value="{% translate 'Delete' %}"/>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
|
10
lot/templates/lot_actions.html
Normal file
10
lot/templates/lot_actions.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% 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>
|
|
@ -1,43 +1,72 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load i18n paginacion %}
|
||||
{% load render_table from django_tables2 %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<h3>{{ subtitle }}</h3>
|
||||
</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 %}
|
||||
|
||||
<a href="{% url 'lot:add' %}" type="button" class="btn btn-green-admin">
|
||||
<a href="{% url 'lot:add' %}" type="button" class="btn btn-green-admin mb-3">
|
||||
<i class="bi bi-plus"></i>
|
||||
{% 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 class="col-md-6 text-md-end">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="filterDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-filter me-2"></i>
|
||||
{% 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 class="row">
|
||||
<table class= "table table-striped table-sm">
|
||||
{% for lot in lots %}
|
||||
<tr>
|
||||
<td><a href="{% url 'dashboard:lot' lot.id %}">{{ lot.name }}</a></td>
|
||||
<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>
|
||||
<!-- Lots list -->
|
||||
{% render_table table %}
|
||||
<div class="mt-5 d-flex align-items-center justify-content-center">
|
||||
{% if table.page and table.paginator.num_pages > 1 %}
|
||||
{% include "django_tables2/pagination.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -24,7 +24,8 @@
|
|||
{% endif %}
|
||||
{% bootstrap_form form %}
|
||||
<div class="form-actions-no-box">
|
||||
<a class="btn btn-grey" href="{% url 'dashboard:unassigned' %}">{% translate "Cancel" %}</a>
|
||||
<a class="btn btn-grey" href="{{ request.META.HTTP_REFERER }}">{% translate "Cancel" %}</a>
|
||||
|
||||
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||
</div>
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ from lot import views
|
|||
app_name = 'lot'
|
||||
|
||||
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("delete/<int:pk>/", views.DeleteLotView.as_view(), name="delete"),
|
||||
path("edit/<int:pk>/", views.EditLotView.as_view(), name="edit"),
|
||||
|
|
190
lot/views.py
190
lot/views.py
|
@ -2,30 +2,52 @@ from django.db import IntegrityError
|
|||
from django.urls import reverse_lazy
|
||||
from django.shortcuts import get_object_or_404, redirect, Http404
|
||||
from django.contrib import messages
|
||||
from dashboard.mixins import InventaryMixin, DetailsMixin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.db.models import Q
|
||||
from django.views.generic.edit import (
|
||||
CreateView,
|
||||
DeleteView,
|
||||
UpdateView,
|
||||
FormView,
|
||||
)
|
||||
import django_tables2 as tables
|
||||
from dashboard.mixins import DashboardView
|
||||
from evidence.models import SystemProperty
|
||||
from device.models import Device
|
||||
from lot.models import Lot, LotTag, LotProperty
|
||||
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"
|
||||
title = _("New lot")
|
||||
breadcrumb = "lot / New lot"
|
||||
success_url = reverse_lazy('dashboard:unassigned')
|
||||
model = Lot
|
||||
fields = (
|
||||
"type",
|
||||
"name",
|
||||
"code",
|
||||
"description",
|
||||
"archived",
|
||||
"closed",
|
||||
)
|
||||
|
||||
def get_form(self):
|
||||
|
@ -37,43 +59,56 @@ class NewLotView(DashboardView, CreateView):
|
|||
return form
|
||||
|
||||
def form_valid(self, form):
|
||||
try:
|
||||
form.instance.owner = self.request.user.institution
|
||||
form.instance.user = self.request.user
|
||||
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)
|
||||
|
||||
class DeleteLotView(DashboardView, DeleteView):
|
||||
return response
|
||||
|
||||
class DeleteLotView(LotSuccessUrlMixin, DashboardView, DeleteView):
|
||||
template_name = "delete_lot.html"
|
||||
title = _("Delete lot")
|
||||
breadcrumb = "lot / Delete lot"
|
||||
success_url = reverse_lazy('dashboard:unassigned')
|
||||
model = Lot
|
||||
fields = (
|
||||
"type",
|
||||
"name",
|
||||
"code",
|
||||
"description",
|
||||
"archived",
|
||||
"closed",
|
||||
)
|
||||
|
||||
def form_valid(self, 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
|
||||
|
||||
|
||||
class EditLotView(DashboardView, UpdateView):
|
||||
class EditLotView(LotSuccessUrlMixin, DashboardView, UpdateView):
|
||||
template_name = "new_lot.html"
|
||||
title = _("Edit lot")
|
||||
breadcrumb = "Lot / Edit lot"
|
||||
success_url = reverse_lazy('dashboard:unassigned')
|
||||
|
||||
model = Lot
|
||||
fields = (
|
||||
"type",
|
||||
"name",
|
||||
"code",
|
||||
"description",
|
||||
"archived",
|
||||
"closed",
|
||||
)
|
||||
|
||||
def get_form_kwargs(self):
|
||||
|
@ -95,12 +130,22 @@ class EditLotView(DashboardView, UpdateView):
|
|||
)
|
||||
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):
|
||||
template_name = "list_lots.html"
|
||||
title = _("Add to lots")
|
||||
breadcrumb = "lot / add to lots"
|
||||
success_url = reverse_lazy('dashboard:unassigned')
|
||||
success_url = reverse_lazy('lot:unassigned')
|
||||
form_class = LotsForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
@ -137,31 +182,79 @@ class DelToLotView(AddToLotView):
|
|||
return response
|
||||
|
||||
|
||||
class LotsTagsView(DashboardView, TemplateView):
|
||||
class LotTable(tables.Table):
|
||||
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"
|
||||
title = _("lots")
|
||||
title = _("Lot group")
|
||||
breadcrumb = _("lots") + " /"
|
||||
success_url = reverse_lazy('dashboard:unassigned')
|
||||
success_url = reverse_lazy('lot: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):
|
||||
self.pk = kwargs.get('pk')
|
||||
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({
|
||||
'lots': lots,
|
||||
'title': self.title,
|
||||
'breadcrumb': self.breadcrumb,
|
||||
'show_archived': show_archived
|
||||
'title': self.title + " " + self.tag.name,
|
||||
'breadcrumb': self.breadcrumb + " " + self.tag.name,
|
||||
'show_closed': self.show_closed,
|
||||
'search_query': self.search_query,
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class LotPropertiesView(DashboardView, TemplateView):
|
||||
template_name = "properties.html"
|
||||
title = _("New Lot Property")
|
||||
|
@ -189,7 +282,7 @@ class AddLotPropertyView(DashboardView, CreateView):
|
|||
template_name = "new_property.html"
|
||||
title = _("New Lot Property")
|
||||
breadcrumb = "Device / New property"
|
||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||
success_url = reverse_lazy('lot:unassigned_devices')
|
||||
model = LotProperty
|
||||
fields = ("key", "value")
|
||||
|
||||
|
@ -274,3 +367,46 @@ class DeleteLotPropertyView(DashboardView, DeleteView):
|
|||
|
||||
# Redirect back to the original 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)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from user.models import Institution
|
||||
from lot.models import LotTag, Lot
|
||||
from lot.models import LotTag
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
@ -12,7 +12,6 @@ class Command(BaseCommand):
|
|||
def handle(self, *args, **kwargs):
|
||||
self.institution = Institution.objects.create(name=kwargs['name'])
|
||||
self.create_lot_tags()
|
||||
self.create_lots()
|
||||
|
||||
def create_lot_tags(self):
|
||||
LotTag.objects.create(
|
||||
|
@ -30,59 +29,3 @@ class Command(BaseCommand):
|
|||
name=tag,
|
||||
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
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue