From 5ea174d553111444b7d12a44f7c4b669cbc56d2f Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Fri, 21 Feb 2025 14:40:05 -0300 Subject: [PATCH 01/17] search lots function and sortable table --- lot/templates/lots.html | 92 +++++++++++++++++++++++++---------------- lot/views.py | 70 ++++++++++++++++++++++++------- 2 files changed, 112 insertions(+), 50 deletions(-) diff --git a/lot/templates/lots.html b/lot/templates/lots.html index 2aa866d..47c9ce6 100644 --- a/lot/templates/lots.html +++ b/lot/templates/lots.html @@ -1,43 +1,63 @@ {% extends "base.html" %} -{% load i18n %} +{% load i18n paginacion %} +{% load render_table from django_tables2 %} + {% block content %} -
-
-

{{ subtitle }}

-
-
- {% if show_closed %} - - {% trans 'Hide closed lots' %} - - {% else %} - - {% trans 'Show closed lots' %} - - {% endif %} - - - {% trans 'Add new lot' %} - + + + {% trans 'Add new lot' %} + + +

{{ subtitle }}

+ +
+ +
+
+ + +
+
+ + +
-
- - {% for lot in lots %} - - - - - - {% endfor %} -
{{ lot.name }} - - - -
-
- -{% endblock %} + +{% render_table table %} +
+ {% if table.page and table.paginator.num_pages > 1 %} + {% include "django_tables2/pagination.html" %} + {% endif %} +
{% endblock %} diff --git a/lot/views.py b/lot/views.py index 341f15b..5c34539 100644 --- a/lot/views.py +++ b/lot/views.py @@ -4,12 +4,14 @@ from django.shortcuts import get_object_or_404, redirect, Http404 from django.contrib import messages from django.utils.translation import gettext_lazy as _ 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 lot.models import Lot, LotTag, LotProperty from lot.forms import LotsForm @@ -137,31 +139,71 @@ class DelToLotView(AddToLotView): return response -class LotsTagsView(DashboardView, TemplateView): +class LotTable(tables.Table): + name = tables.Column(linkify=("dashboard:lot", {"pk": tables.A("id")}), verbose_name=_("Lot Name")) + description = tables.Column(verbose_name=_("Description"), default="No description") + 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") + actions = tables.TemplateColumn( + template_name="lot_actions.html", + verbose_name=_("Actions"), + orderable=False, + attrs={"td": {"class": "text-end"}} + ) + + class Meta: + model = Lot + fields = ("name", "description", "closed", "created", "user", "actions") + attrs = { + "class": "table table-hover align-middle", + "thead": {"class": "table-light"} + } + + +class LotsTagsView(DashboardView, tables.SingleTableView): template_name = "lots.html" title = _("lots") breadcrumb = _("lots") + " /" 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_closed = self.request.GET.get('show_closed', 'false') == 'true' + self.search_query = self.request.GET.get('q', '').strip() + + queryset = Lot.objects.filter(owner=self.request.user.institution, type=self.tag) + + if not self.show_closed: + queryset = queryset.filter(closed=True) + + 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_closed = self.request.GET.get('show_closed', 'false') == 'true' - lots = Lot.objects.filter(owner=self.request.user.institution).filter( - type=tag, closed=show_closed - ) context.update({ - 'lots': lots, - 'title': self.title, - 'breadcrumb': self.breadcrumb, - 'show_closed': show_closed + 'title': self.title + " " + self.tag.name, + 'breadcrumb': self.breadcrumb + " " + self.tag.name, + 'show_closed': self.show_closed, + 'search_query': self.search_query, # Pass the search query to the template }) return context - class LotPropertiesView(DashboardView, TemplateView): template_name = "properties.html" title = _("New Lot Property") -- 2.30.2 From bf0ffc52f22888aea45e0ec86a80ed4b2b6f38da Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Fri, 21 Feb 2025 20:45:39 -0300 Subject: [PATCH 02/17] better filter dropdown --- lot/templates/lots.html | 50 +++++++++++++++++++++++------------------ lot/views.py | 9 +++++--- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/lot/templates/lots.html b/lot/templates/lots.html index 47c9ce6..5b4a6b6 100644 --- a/lot/templates/lots.html +++ b/lot/templates/lots.html @@ -28,33 +28,39 @@
- - - {% render_table table %}
{% if table.page and table.paginator.num_pages > 1 %} diff --git a/lot/views.py b/lot/views.py index 5c34539..324f09f 100644 --- a/lot/views.py +++ b/lot/views.py @@ -172,13 +172,16 @@ class LotsTagsView(DashboardView, tables.SingleTableView): 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_closed = self.request.GET.get('show_closed', 'false') == 'true' + 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 not self.show_closed: + 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( @@ -200,7 +203,7 @@ class LotsTagsView(DashboardView, tables.SingleTableView): 'title': self.title + " " + self.tag.name, 'breadcrumb': self.breadcrumb + " " + self.tag.name, 'show_closed': self.show_closed, - 'search_query': self.search_query, # Pass the search query to the template + 'search_query': self.search_query, }) return context -- 2.30.2 From 12b9be427673c81685c7981f8df0018dda369720 Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Mon, 24 Feb 2025 11:44:19 -0300 Subject: [PATCH 03/17] error on duplicated lot name --- .../0008_lot_unique_institution_and_name.py | 22 +++++++++++++++++++ lot/views.py | 13 ++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 lot/migrations/0008_lot_unique_institution_and_name.py diff --git a/lot/migrations/0008_lot_unique_institution_and_name.py b/lot/migrations/0008_lot_unique_institution_and_name.py new file mode 100644 index 0000000..e4ba18e --- /dev/null +++ b/lot/migrations/0008_lot_unique_institution_and_name.py @@ -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" + ), + ), + ] diff --git a/lot/views.py b/lot/views.py index 324f09f..cd9c1de 100644 --- a/lot/views.py +++ b/lot/views.py @@ -39,9 +39,16 @@ class NewLotView(DashboardView, CreateView): return form def form_valid(self, form): - form.instance.owner = self.request.user.institution - form.instance.user = self.request.user - response = super().form_valid(form) + try: + form.instance.owner = self.request.user.institution + form.instance.user = self.request.user + response = super().form_valid(form) + return response + + except IntegrityError: + messages.error(self.request, _("Lot name is already defined.")) + return self.form_invalid(form) + return response -- 2.30.2 From 81cd13dae4b3de4044aaa4cebe9fec8940864f8a Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Mon, 24 Feb 2025 14:01:26 -0300 Subject: [PATCH 04/17] better lot url redirect and error messages --- lot/models.py | 6 ++++++ lot/views.py | 47 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/lot/models.py b/lot/models.py index 7ab5ae5..90c1502 100644 --- a/lot/models.py +++ b/lot/models.py @@ -37,6 +37,12 @@ class Lot(models.Model): 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'], name='unique_institution_and_name') + ] + + def add(self, v): if DeviceLot.objects.filter(lot=self, device_id=v).exists(): return diff --git a/lot/views.py b/lot/views.py index cd9c1de..ea28426 100644 --- a/lot/views.py +++ b/lot/views.py @@ -16,11 +16,28 @@ from dashboard.mixins import DashboardView from lot.models import Lot, LotTag, LotProperty from lot.forms import LotsForm -class NewLotView(DashboardView, CreateView): + +class LotSuccessUrlMixin(): + + success_url = reverse_lazy('dashboard: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", @@ -43,6 +60,8 @@ class NewLotView(DashboardView, CreateView): 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: @@ -51,12 +70,10 @@ class NewLotView(DashboardView, CreateView): return response - -class DeleteLotView(DashboardView, DeleteView): +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", @@ -68,14 +85,20 @@ class DeleteLotView(DashboardView, DeleteView): 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", @@ -104,6 +127,16 @@ 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" -- 2.30.2 From e974a6744d5e65f39b231b7faff7eaab99d45c3f Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Mon, 24 Feb 2025 16:13:34 -0300 Subject: [PATCH 05/17] better lots group table --- lot/templates/lots.html | 11 +++++++---- lot/templates/new_lot.html | 3 ++- lot/views.py | 18 ++++++++++++------ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lot/templates/lots.html b/lot/templates/lots.html index 5b4a6b6..702c4cb 100644 --- a/lot/templates/lots.html +++ b/lot/templates/lots.html @@ -5,7 +5,7 @@ {% block content %} - + {% trans 'Add new lot' %} @@ -14,13 +14,13 @@
-
+
+ {% render_table table %}
{% if table.page and table.paginator.num_pages > 1 %} {% include "django_tables2/pagination.html" %} {% endif %} -
{% endblock %} +
+ +{% endblock %} diff --git a/lot/templates/new_lot.html b/lot/templates/new_lot.html index c4c2cc8..ba8abcb 100644 --- a/lot/templates/new_lot.html +++ b/lot/templates/new_lot.html @@ -24,7 +24,8 @@ {% endif %} {% bootstrap_form form %} diff --git a/lot/views.py b/lot/views.py index ea28426..a09aad2 100644 --- a/lot/views.py +++ b/lot/views.py @@ -3,6 +3,7 @@ from django.urls import reverse_lazy from django.shortcuts import get_object_or_404, redirect, Http404 from django.contrib import messages 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 ( @@ -180,25 +181,30 @@ class DelToLotView(AddToLotView): class LotTable(tables.Table): - name = tables.Column(linkify=("dashboard:lot", {"pk": tables.A("id")}), verbose_name=_("Lot Name")) - description = tables.Column(verbose_name=_("Description"), default="No description") + name = tables.Column(linkify=("dashboard: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") + user = tables.Column(verbose_name=("Created By"), default=_("Unknown"), attrs={"td": {"class": "text-muted"}} ) actions = tables.TemplateColumn( template_name="lot_actions.html", - verbose_name=_("Actions"), - orderable=False, + verbose_name=_(""), attrs={"td": {"class": "text-end"}} ) + def render_closed(self, value): + if value: + return mark_safe('Closed') + return mark_safe('Open') + class Meta: model = Lot - fields = ("name", "description", "closed", "created", "user", "actions") + 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): -- 2.30.2 From 0d256787e93f1f162e9be1b8a33eca93f3b33cb9 Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Mon, 24 Feb 2025 18:22:28 -0300 Subject: [PATCH 06/17] delete template now displays number of devices --- lot/templates/delete_lot.html | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/lot/templates/delete_lot.html b/lot/templates/delete_lot.html index babdb89..4756f6e 100644 --- a/lot/templates/delete_lot.html +++ b/lot/templates/delete_lot.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load i18n %} +{% load django_bootstrap5 %} {% block content %}
@@ -8,13 +9,34 @@
-{% load django_bootstrap5 %}
- Are you sure than want remove the lot {{ object.name }} with {{ object.devices.count }} devices. +
+
+
{% trans "Delete Lot" %}
+
+ {% 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 %} +
+ + {% if object.devices.count > 0 %} +
+ + {% trans "All associated devices will be deassigned." %} +
+ {% else %} +
+ + {% trans "No devices are associated with this lot." %} +
+ {% endif %} +
+
- {% csrf_token %} {% if form.errors %} @@ -30,8 +52,8 @@ {% endif %} {% bootstrap_form form %} -- 2.30.2 From 9c1aa20887a12821cb77630ca257e9fbb5ee68ed Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Mon, 24 Feb 2025 18:23:37 -0300 Subject: [PATCH 07/17] updated constraint and added devices fk's --- lot/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lot/models.py b/lot/models.py index 90c1502..d651689 100644 --- a/lot/models.py +++ b/lot/models.py @@ -39,7 +39,7 @@ class Lot(models.Model): class Meta: constraints = [ - models.UniqueConstraint(fields=['owner', 'name'], name='unique_institution_and_name') + models.UniqueConstraint(fields=['owner', 'name', 'type'], name='unique_institution_and_name') ] @@ -51,6 +51,10 @@ class Lot(models.Model): def remove(self, v): 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) -- 2.30.2 From b334c1652f8f5aefa4484f0a841820445552aa77 Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Mon, 24 Feb 2025 18:26:25 -0300 Subject: [PATCH 08/17] fix overflow issue 183 --- dashboard/templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashboard/templates/base.html b/dashboard/templates/base.html index abe23a0..1cd0932 100644 --- a/dashboard/templates/base.html +++ b/dashboard/templates/base.html @@ -137,7 +137,7 @@ {% else %} {% endif %} - {{ tag.name }} + {{ tag.name|truncatechars:17}} {% endfor %} -- 2.30.2 From 112dbf270d0ae6f68100cdccf47822be0eb86f99 Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Wed, 26 Feb 2025 23:43:54 -0300 Subject: [PATCH 09/17] changed edit and delete lotgroup modal --- admin/templates/lot_tag_panel.html | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/admin/templates/lot_tag_panel.html b/admin/templates/lot_tag_panel.html index 3a70a69..689aad0 100644 --- a/admin/templates/lot_tag_panel.html +++ b/admin/templates/lot_tag_panel.html @@ -26,7 +26,7 @@ {% for tag in lot_tags_edit %} - + {{ tag.name }} @@ -44,7 +44,8 @@ @@ -110,6 +111,9 @@
{% trans "Maximum 50 characters." %}
+ {% if tag.id == 1 %} +

{% trans "INBOX can only be edited, not deleted." %}

+ {% endif %}
@@ -136,16 +140,28 @@