From 64a95bf114ceb6b63993b0003745e45c0b365d03 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Mon, 10 Mar 2025 18:01:33 +0100 Subject: [PATCH] add endpoint for donor and page view --- dashboard/templates/unassigned_devices.html | 2 +- dashboard/views.py | 9 +- lot/forms.py | 17 ++- .../0009_donor_lotsubscription_and_more.py | 85 ++++++++++++ ...ription_lotsubscription_unique_lot_user.py | 28 ---- ...ubscription_is_circuit_manager_and_more.py | 28 ---- lot/models.py | 13 +- lot/templates/donor.html | 40 ++++++ lot/templates/donor_web.html | 129 ++++++++++++++++++ lot/urls.py | 1 + lot/views.py | 112 ++++++++++++--- 11 files changed, 379 insertions(+), 85 deletions(-) create mode 100644 lot/migrations/0009_donor_lotsubscription_and_more.py delete mode 100644 lot/migrations/0009_lotsubscription_lotsubscription_unique_lot_user.py delete mode 100644 lot/migrations/0010_lot_donor_lotsubscription_is_circuit_manager_and_more.py create mode 100644 lot/templates/donor.html create mode 100644 lot/templates/donor_web.html diff --git a/dashboard/templates/unassigned_devices.html b/dashboard/templates/unassigned_devices.html index f30b681..b98f787 100644 --- a/dashboard/templates/unassigned_devices.html +++ b/dashboard/templates/unassigned_devices.html @@ -28,7 +28,7 @@ {% trans 'Unsubscription' %} - {% if lot.donor %} + {% if donor %} {% trans 'Donor' %} diff --git a/dashboard/views.py b/dashboard/views.py index 01a8566..d03bbc9 100644 --- a/dashboard/views.py +++ b/dashboard/views.py @@ -9,7 +9,7 @@ from dashboard.mixins import InventaryMixin, DetailsMixin from evidence.models import SystemProperty from evidence.xapian import search from device.models import Device -from lot.models import Lot +from lot.models import Lot, Donor class UnassignedDevicesView(InventaryMixin): @@ -42,6 +42,13 @@ class LotDashboardView(InventaryMixin, DetailsMixin): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) lot = context.get('object') + donor = Donor.objects.filter( + lot=lot, + ).first() + + if donor: + context["donor"] = donor + context.update({ 'lot': lot, }) diff --git a/lot/forms.py b/lot/forms.py index fc81c5c..c662ae8 100644 --- a/lot/forms.py +++ b/lot/forms.py @@ -2,7 +2,7 @@ from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ValidationError from django import forms from user.models import User -from lot.models import Lot, LotSubscription +from lot.models import Lot, LotSubscription, Donor class LotsForm(forms.Form): @@ -132,6 +132,7 @@ class AddDonorForm(forms.Form): def __init__(self, *args, **kwargs): self.institution = kwargs.pop("institution") self.lot = kwargs.pop("lot") + self.donor = kwargs.pop("donor", None) super().__init__(*args, **kwargs) def clean(self): @@ -142,14 +143,20 @@ class AddDonorForm(forms.Form): if not commit: return - self.lot.donor = self._user - self.lot.save() + if self.donor: + self.donor.email = self._user + self.donor.save() + else: + self.donor = Donor.objects.create( + lot=self.lot, + email=self._user + ) # TODO # if self._user: # self.send_email() def remove(self): - self.lot.donor = "" - self.lot.save() + if self.donor: + self.donor.delete() return diff --git a/lot/migrations/0009_donor_lotsubscription_and_more.py b/lot/migrations/0009_donor_lotsubscription_and_more.py new file mode 100644 index 0000000..3a98ce9 --- /dev/null +++ b/lot/migrations/0009_donor_lotsubscription_and_more.py @@ -0,0 +1,85 @@ +# Generated by Django 5.0.6 on 2025-03-10 16:29 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("lot", "0008_rename_closed_lot_archived"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Donor", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "reconciliation", + models.BooleanField(default=False, verbose_name="Reconciliation"), + ), + ( + "email", + models.EmailField(max_length=255, verbose_name="Email address"), + ), + ( + "lot", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="lot.lot" + ), + ), + ], + ), + migrations.CreateModel( + name="LotSubscription", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "is_circuit_manager", + models.BooleanField( + default=False, verbose_name="is circuit manager" + ), + ), + ("is_shop", models.BooleanField(default=False, verbose_name="is shop")), + ( + "lot", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="lot.lot" + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.AddConstraint( + model_name="lotsubscription", + constraint=models.UniqueConstraint( + fields=("lot", "user"), name="unique_lot_user" + ), + ), + ] diff --git a/lot/migrations/0009_lotsubscription_lotsubscription_unique_lot_user.py b/lot/migrations/0009_lotsubscription_lotsubscription_unique_lot_user.py deleted file mode 100644 index 9bf64ff..0000000 --- a/lot/migrations/0009_lotsubscription_lotsubscription_unique_lot_user.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 5.0.6 on 2025-03-03 10:36 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('lot', '0008_rename_closed_lot_archived'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='LotSubscription', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('lot', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='lot.lot')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.AddConstraint( - model_name='lotsubscription', - constraint=models.UniqueConstraint(fields=('lot', 'user'), name='unique_lot_user'), - ), - ] diff --git a/lot/migrations/0010_lot_donor_lotsubscription_is_circuit_manager_and_more.py b/lot/migrations/0010_lot_donor_lotsubscription_is_circuit_manager_and_more.py deleted file mode 100644 index f491e17..0000000 --- a/lot/migrations/0010_lot_donor_lotsubscription_is_circuit_manager_and_more.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 5.0.6 on 2025-03-07 10:47 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("lot", "0009_lotsubscription_lotsubscription_unique_lot_user"), - ] - - operations = [ - migrations.AddField( - model_name="lot", - name="donor", - field=models.CharField(blank=True, max_length=64, null=True), - ), - migrations.AddField( - model_name="lotsubscription", - name="is_circuit_manager", - field=models.BooleanField(default=False, verbose_name="is circuit manager"), - ), - migrations.AddField( - model_name="lotsubscription", - name="is_shop", - field=models.BooleanField(default=False, verbose_name="is shop"), - ), - ] diff --git a/lot/models.py b/lot/models.py index 21a6bca..6aaaaba 100644 --- a/lot/models.py +++ b/lot/models.py @@ -1,3 +1,5 @@ +import uuid + from django.db import models from django.utils.translation import gettext_lazy as _ from utils.constants import ( @@ -35,7 +37,6 @@ class Lot(models.Model): 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) - donor = models.CharField(max_length=STR_SIZE, blank=True, null=True) def add(self, v): if DeviceLot.objects.filter(lot=self, device_id=v).exists(): @@ -73,3 +74,13 @@ class LotSubscription(models.Model): models.UniqueConstraint( fields=["lot", "user"], name="unique_lot_user") ] + + +class Donor(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + reconciliation = models.BooleanField(_("Reconciliation"), default=False) + lot = models.ForeignKey(Lot, on_delete=models.CASCADE) + email = models.EmailField( + _('Email address'), + max_length=255, + ) diff --git a/lot/templates/donor.html b/lot/templates/donor.html new file mode 100644 index 0000000..8d2654d --- /dev/null +++ b/lot/templates/donor.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block content %} +
+
+

{{ subtitle }}

+
+
+ +{% load django_bootstrap5 %} +{% if donor %} +
+ +
+{% endif %} + +
+{% csrf_token %} +{% if form.errors %} + +{% endif %} +{% bootstrap_form form %} + + +
+{% endblock %} diff --git a/lot/templates/donor_web.html b/lot/templates/donor_web.html new file mode 100644 index 0000000..2cebd36 --- /dev/null +++ b/lot/templates/donor_web.html @@ -0,0 +1,129 @@ +{% load i18n %} + + + + + + + {{ donor.email }} + + + + + +
+

{{ donor.email }}

+ +
+
+ + + + + + + + + {% for dev in devices %} + + + + + + + {% endfor %} + +
ID{% trans "Manufacturer" %} + {% trans "Model" %} + {% trans "Serial Number" %} +
{{ dev.shortid }}{{ dev.manufacturer }}{{ dev.model }}{{ dev.serial_number }}
+
+
+
+ + + + + diff --git a/lot/urls.py b/lot/urls.py index c749645..c18501b 100644 --- a/lot/urls.py +++ b/lot/urls.py @@ -18,4 +18,5 @@ urlpatterns = [ path("/unsubscription/", views.UnsubscriptLotView.as_view(), name="unsubscription"), path("/donor/add", views.AddDonorView.as_view(), name="add_donor"), path("/donor/del", views.DelDonorView.as_view(), name="del_donor"), + path("/donor/", views.DonorView.as_view(), name="web_donor"), ] diff --git a/lot/views.py b/lot/views.py index b81d794..95b5edc 100644 --- a/lot/views.py +++ b/lot/views.py @@ -11,7 +11,9 @@ from django.views.generic.edit import ( FormView, ) from dashboard.mixins import DashboardView -from lot.models import Lot, LotTag, LotProperty, LotSubscription +from evidence.models import SystemProperty +from device.models import Device +from lot.models import Lot, LotTag, LotProperty, LotSubscription, Donor from lot.forms import LotsForm, LotSubscriptionForm, AddDonorForm class NewLotView(DashboardView, CreateView): @@ -333,39 +335,47 @@ class UnsubscriptLotView(SubscriptLotMixing): return response -class AddDonorView(DashboardView, FormView): - template_name = "subscription.html" - title = _("Add Donor") - breadcrumb = "Lot / {}".format(title) +class DonorMixing(DashboardView, FormView): + template_name = "donor.html" form_class = AddDonorForm lot = None + donor = None def get_context_data(self, **kwargs): self.pk = self.kwargs.get('pk') context = super().get_context_data(**kwargs) if not self.lot: self.get_lot() + if not self.donor: + self.get_donor() context.update({ 'lot': self.lot, - "action": _("Add") + 'donor': self.donor, }) return context - def form_valid(self, form): - form.save() - response = super().form_valid(form) - return response - def get_form_kwargs(self): + self.pk = self.kwargs.get('pk') self.success_url = reverse_lazy('dashboard:lot', args=[self.pk]) self.get_lot() + self.get_donor() + cmanager = LotSubscription.objects.filter( + lot=self.lot, + is_circuit_manager=True, + user=self.request.user + ).first() + + if not self.request.user.is_admin and not cmanager: + raise Http404 + kwargs = super().get_form_kwargs() kwargs["institution"] = self.request.user.institution kwargs["lot"] = self.lot - donor = self.lot.donor or "" - kwargs["initial"] = {"user": donor} + if self.donor: + kwargs["initial"] = {"user": self.donor.email} + kwargs["donor"] = self.donor return kwargs def get_lot(self): @@ -375,23 +385,83 @@ class AddDonorView(DashboardView, FormView): id=self.pk ) + def get_donor(self): + if not self.lot: + self.get_lot() -class DelDonorView(AddDonorView): + self.donor = Donor.objects.filter( + lot=self.lot, + ).first() + +class AddDonorView(DonorMixing): + title = _("Add Donor") + breadcrumb = "Lot / {}".format(title) + + def form_valid(self, form): + form.save() + response = super().form_valid(form) + return response + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["action"] = _("Add") + return context + + +class DelDonorView(DonorMixing): title = _("Remove Donor") breadcrumb = "Lot / {}".format(title) def form_valid(self, form): - response = super().form_valid(form) form.remove() + response = super().form_valid(form) return response - def get_form_kwargs(self): - kwargs = super().get_form_kwargs() - donor = kwargs["lot"].donor - kwargs["initial"] = {"user": donor} - return kwargs - def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["action"] = _("Remove") return context + + +class DonorView(TemplateView, UpdateView): + template_name = "donor_web.html" + model = Donor + fields = ("reconciliation",) + + def get_form_kwargs(self): + pk = self.kwargs.get('pk') + id = self.kwargs.get('id') + + self.object = get_object_or_404( + Donor, + id=id, + lot_id=pk + ) + + self.success_url = reverse_lazy('lot:web_donor', args=[pk, id]) + kwargs = super().get_form_kwargs() + kwargs['instance'] = self.object + return kwargs + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["donor"] = self.object + context["devices"] = self.get_devices() + return context + + def get_devices(self): + chids = self.object.lot.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(Device(id=x.value)) + + return chids_ordered