move mesh forms from site to mesh module… wtf
This commit is contained in:
parent
df7ae47a9a
commit
375aa9f12c
3 changed files with 105 additions and 95 deletions
|
@ -1,14 +1,22 @@
|
||||||
import time
|
import time
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from dataclasses import replace as dataclass_replace
|
||||||
|
from functools import cached_property
|
||||||
|
from itertools import chain
|
||||||
|
from typing import Sequence, Any
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
from asgiref.sync import async_to_sync
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import transaction
|
||||||
|
from django.forms import Form, ChoiceField, BooleanField
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from c3nav.mesh.dataformats import BoardConfig, BoardType, LedType, SerialLedType
|
from c3nav.mesh.dataformats import BoardConfig, BoardType, LedType, SerialLedType
|
||||||
from c3nav.mesh.messages import MESH_BROADCAST_ADDRESS, MESH_ROOT_ADDRESS, MeshMessage, MeshMessageType
|
from c3nav.mesh.messages import MESH_BROADCAST_ADDRESS, MESH_ROOT_ADDRESS, MeshMessage, MeshMessageType
|
||||||
from c3nav.mesh.models import MeshNode
|
from c3nav.mesh.models import MeshNode, HardwareDescription, FirmwareBuild, OTAUpdateRecipient, OTARecipientStatus, \
|
||||||
|
OTAUpdate
|
||||||
|
|
||||||
|
|
||||||
class MeshMessageForm(forms.Form):
|
class MeshMessageForm(forms.Form):
|
||||||
|
@ -284,3 +292,98 @@ class RangingForm(forms.Form):
|
||||||
node_choices = tuple((node.address, str(node)) for node in MeshNode.objects.all())
|
node_choices = tuple((node.address, str(node)) for node in MeshNode.objects.all())
|
||||||
self.fields['range_from'].choices = node_choices
|
self.fields['range_from'].choices = node_choices
|
||||||
self.fields['range_to'].choices = node_choices
|
self.fields['range_to'].choices = node_choices
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class OTAFormGroup:
|
||||||
|
hardware: HardwareDescription
|
||||||
|
builds: Sequence[FirmwareBuild]
|
||||||
|
fields: dict[str, tuple[MeshNode, Any]]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def builds_by_id(self) -> dict[int, FirmwareBuild]:
|
||||||
|
return {build.pk: build for build in self.builds}
|
||||||
|
|
||||||
|
|
||||||
|
class OTACreateForm(Form):
|
||||||
|
def __init__(self, builds: Sequence[FirmwareBuild], *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
nodes: Sequence[MeshNode] = MeshNode.objects.prefetch_last_messages(
|
||||||
|
MeshMessageType.CONFIG_BOARD
|
||||||
|
).prefetch_firmwares().prefetch_ota()
|
||||||
|
|
||||||
|
builds_by_hardware = {}
|
||||||
|
for build in builds:
|
||||||
|
for hardware_desc in build.hardware_descriptions:
|
||||||
|
builds_by_hardware.setdefault(hardware_desc, []).append(build)
|
||||||
|
|
||||||
|
nodes_by_hardware = {}
|
||||||
|
for node in nodes:
|
||||||
|
nodes_by_hardware.setdefault(node.hardware_description, []).append(node)
|
||||||
|
|
||||||
|
self._groups: list[OTAFormGroup] = []
|
||||||
|
for hardware, hw_nodes in sorted(nodes_by_hardware.items(), key=lambda k: len(k[1]), reverse=True):
|
||||||
|
try:
|
||||||
|
hw_builds = builds_by_hardware[hardware]
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
choices = [
|
||||||
|
('', '---'),
|
||||||
|
*((build.pk, build.variant) for build in hw_builds)
|
||||||
|
]
|
||||||
|
|
||||||
|
group = OTAFormGroup(
|
||||||
|
hardware=hardware,
|
||||||
|
builds=hw_builds,
|
||||||
|
fields={
|
||||||
|
f'build_{node.pk}': (node, (
|
||||||
|
ChoiceField(choices=choices, required=False)
|
||||||
|
if len(hw_builds) > 1
|
||||||
|
else BooleanField(required=False)
|
||||||
|
)) for node in hw_nodes
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for name, (node, hw_field) in group.fields.items():
|
||||||
|
self.fields[name] = hw_field
|
||||||
|
self._groups.append(group)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def groups(self) -> list[OTAFormGroup]:
|
||||||
|
return [
|
||||||
|
dataclass_replace(group, fields={
|
||||||
|
name: (node, self[name])
|
||||||
|
for name, (node, hw_field) in group.fields.items()
|
||||||
|
})
|
||||||
|
for group in self._groups
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def selected_builds(self):
|
||||||
|
build_nodes = {}
|
||||||
|
for group in self._groups:
|
||||||
|
for name, (node, hw_field) in group.fields.items():
|
||||||
|
value = self.cleaned_data.get(name, None)
|
||||||
|
if not value:
|
||||||
|
continue
|
||||||
|
if len(group.builds) == 1:
|
||||||
|
build_nodes.setdefault(group.builds[0], []).append(node)
|
||||||
|
else:
|
||||||
|
build_nodes.setdefault(group.builds[0], []).append(group.builds_by_id[int(value)])
|
||||||
|
return build_nodes
|
||||||
|
|
||||||
|
def save(self) -> list[OTAUpdate]:
|
||||||
|
updates = []
|
||||||
|
with transaction.atomic():
|
||||||
|
replaced_recipients = OTAUpdateRecipient.objects.filter(
|
||||||
|
node__in=chain(*self.selected_builds.values()),
|
||||||
|
status=OTARecipientStatus.RUNNING,
|
||||||
|
).select_for_update()
|
||||||
|
replaced_recipients.update(status=OTARecipientStatus.REPLACED)
|
||||||
|
for build, nodes in self.selected_builds.items():
|
||||||
|
update = OTAUpdate.objects.create(build=build)
|
||||||
|
for node in nodes:
|
||||||
|
update.recipients.create(node=node)
|
||||||
|
updates.append(update)
|
||||||
|
return updates
|
||||||
|
|
|
@ -2,9 +2,9 @@ from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.views.generic import DetailView, ListView, TemplateView
|
from django.views.generic import DetailView, ListView, TemplateView
|
||||||
from django.views.generic.edit import FormMixin
|
from django.views.generic.edit import FormMixin
|
||||||
|
|
||||||
|
from c3nav.mesh.forms import OTACreateForm
|
||||||
from c3nav.mesh.models import FirmwareBuild, FirmwareVersion, MeshNode, OTARecipientStatus, OTAUpdate
|
from c3nav.mesh.models import FirmwareBuild, FirmwareVersion, MeshNode, OTARecipientStatus, OTAUpdate
|
||||||
from c3nav.mesh.views.base import MeshControlMixin
|
from c3nav.mesh.views.base import MeshControlMixin
|
||||||
from c3nav.site.forms import OTACreateForm
|
|
||||||
|
|
||||||
|
|
||||||
class FirmwaresListView(MeshControlMixin, ListView):
|
class FirmwaresListView(MeshControlMixin, ListView):
|
||||||
|
|
|
@ -74,96 +74,3 @@ class PositionSetForm(Form):
|
||||||
self.fields['position'].queryset = Position.objects.filter(owner=request.user)
|
self.fields['position'].queryset = Position.objects.filter(owner=request.user)
|
||||||
self.fields['position'].label_from_instance = attrgetter('name')
|
self.fields['position'].label_from_instance = attrgetter('name')
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class OTAFormGroup:
|
|
||||||
hardware: HardwareDescription
|
|
||||||
builds: Sequence[FirmwareBuild]
|
|
||||||
fields: dict[str, tuple[MeshNode, Any]]
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def builds_by_id(self) -> dict[int, FirmwareBuild]:
|
|
||||||
return {build.pk: build for build in self.builds}
|
|
||||||
|
|
||||||
|
|
||||||
class OTACreateForm(Form):
|
|
||||||
def __init__(self, builds: Sequence[FirmwareBuild], *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
nodes: Sequence[MeshNode] = MeshNode.objects.prefetch_last_messages(
|
|
||||||
MeshMessageType.CONFIG_BOARD
|
|
||||||
).prefetch_firmwares().prefetch_ota()
|
|
||||||
|
|
||||||
builds_by_hardware = {}
|
|
||||||
for build in builds:
|
|
||||||
for hardware_desc in build.hardware_descriptions:
|
|
||||||
builds_by_hardware.setdefault(hardware_desc, []).append(build)
|
|
||||||
|
|
||||||
nodes_by_hardware = {}
|
|
||||||
for node in nodes:
|
|
||||||
nodes_by_hardware.setdefault(node.hardware_description, []).append(node)
|
|
||||||
|
|
||||||
self._groups: list[OTAFormGroup] = []
|
|
||||||
for hardware, hw_nodes in sorted(nodes_by_hardware.items(), key=lambda k: len(k[1]), reverse=True):
|
|
||||||
try:
|
|
||||||
hw_builds = builds_by_hardware[hardware]
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
choices = [
|
|
||||||
('', '---'),
|
|
||||||
*((build.pk, build.variant) for build in hw_builds)
|
|
||||||
]
|
|
||||||
|
|
||||||
group = OTAFormGroup(
|
|
||||||
hardware=hardware,
|
|
||||||
builds=hw_builds,
|
|
||||||
fields={
|
|
||||||
f'build_{node.pk}': (node, (
|
|
||||||
ChoiceField(choices=choices, required=False)
|
|
||||||
if len(hw_builds) > 1
|
|
||||||
else BooleanField(required=False)
|
|
||||||
)) for node in hw_nodes
|
|
||||||
}
|
|
||||||
)
|
|
||||||
for name, (node, hw_field) in group.fields.items():
|
|
||||||
self.fields[name] = hw_field
|
|
||||||
self._groups.append(group)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def groups(self) -> list[OTAFormGroup]:
|
|
||||||
return [
|
|
||||||
dataclass_replace(group, fields={
|
|
||||||
name: (node, self[name])
|
|
||||||
for name, (node, hw_field) in group.fields.items()
|
|
||||||
})
|
|
||||||
for group in self._groups
|
|
||||||
]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def selected_builds(self):
|
|
||||||
build_nodes = {}
|
|
||||||
for group in self._groups:
|
|
||||||
for name, (node, hw_field) in group.fields.items():
|
|
||||||
value = self.cleaned_data.get(name, None)
|
|
||||||
if not value:
|
|
||||||
continue
|
|
||||||
if len(group.builds) == 1:
|
|
||||||
build_nodes.setdefault(group.builds[0], []).append(node)
|
|
||||||
else:
|
|
||||||
build_nodes.setdefault(group.builds[0], []).append(group.builds_by_id[int(value)])
|
|
||||||
return build_nodes
|
|
||||||
|
|
||||||
def save(self) -> list[OTAUpdate]:
|
|
||||||
updates = []
|
|
||||||
with transaction.atomic():
|
|
||||||
replaced_recipients = OTAUpdateRecipient.objects.filter(
|
|
||||||
node__in=chain(*self.selected_builds.values()),
|
|
||||||
status=OTARecipientStatus.RUNNING,
|
|
||||||
).select_for_update()
|
|
||||||
replaced_recipients.update(status=OTARecipientStatus.REPLACED)
|
|
||||||
for build, nodes in self.selected_builds.items():
|
|
||||||
update = OTAUpdate.objects.create(build=build)
|
|
||||||
for node in nodes:
|
|
||||||
update.recipients.create(node=node)
|
|
||||||
updates.append(update)
|
|
||||||
return updates
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue