2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 11:36:44 +00:00
Lukas aa7eaaab3a
Machine integration (#4824)
* Added initial draft for machines

* refactor: isPluginRegistryLoaded check into own ready function

* Added suggestions from codereview

* Refactor: base_drivers -> machine_types

* Use new BaseInvenTreeSetting unique interface

* Fix Django not ready error

* Added get_machines function to driver

- get_machines function on driver
- get_machine function on driver
- initialized attribute on machine

* Added error handeling for driver and machine type

* Extended get_machines functionality

* Export everything from plugin module

* Fix spelling mistakes

* Better states handeling, BaseMachineType is now used instead of Machine Model

* Use uuid as pk

* WIP: machine termination hook

* Remove termination hook as this does not work with gunicorn

* Remove machine from registry after delete

* Added ClassProviderMixin

* Check for slug dupplication

* Added config_type to MachineSettings to define machine/driver settings

* Refactor helper mixins into own file in InvenTree app

* Fixed typing and added required_attributes for BaseDriver

* fix: generic status import

* Added first draft for machine states

* Added convention for status codes

* Added update_machine hook

* Removed unnecessary _key suffix from machine config model

* Initil draft for machine API

* Refactored BaseInvenTreeSetting all_items and allValues method

* Added required to InvenTreeBaseSetting and check_settings method

* check if all required machine settings are defined and refactor: use getattr

* Fix: comment

* Fix initialize error and python 3.9 compability

* Make machine states available through the global states api

* Added basic PUI machine admin implementation that is still in dev

* Added basic machine setting UI to PUI

* Added machine detail view to PUI admin center

* Fix merge issues

* Fix style issues

* Added machine type,machine driver,error stack tables

* Fix style in machine/serializers.py

* Added pui link from machine to machine type/driver drawer

* Removed only partially working django admin in favor of the PUI admin center implementation

* Added required field to settings item

* Added machine restart function

* Added restart requird badge to machine table/drawer

* Added driver init function

* handle error functions for machines and registry

* Added driver errors

* Added machine table to driver drawer

* Added back button to detail drawer component

* Fix auto formatable pre-commit

* fix: style

* Fix deepsource

* Removed slug field from table, added more links between drawers, remove detail drawer blur

* Added initial docs

* Removed description from driver/machine type select and fixed disabled driver select if no machine type is selected

* Added basic label printing implementation

* Remove translated column names because they are now retrieved from the api

* Added printer location setting

* Save last 10 used printer machine per user and sort them in the printing dialog

* Added BasePrintingOptionsSerializer for common options

* Fix not printing_options are not properly casted to its internal value

* Fix type

* Improved machine docs

* Fix docs

* Added UNKNOWN status code to label printer status

* Skip machine loading when running migrations

* Fix testing?

* Fix: tests?

* Fix: tests?

* Disable docs check precommit

* Disable docs check precommit

* First draft for tests

* fix test

* Add type ignore

* Added API tests

* Test ci?

* Add more tests

* Added more tests

* Bump api version

* Changed driver/base driver naming schema

* Added more tests

* Fix tests

* Added setting choice with kwargs and get_machines with initialized=None

* Refetch table after deleting machine

* Fix test

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
2024-02-14 14:13:47 +00:00

252 lines
7.6 KiB
Python

"""JSON API for the machine app."""
from django.urls import include, path, re_path
from drf_spectacular.utils import extend_schema
from rest_framework import permissions
from rest_framework.exceptions import NotFound
from rest_framework.response import Response
from rest_framework.views import APIView
import machine.serializers as MachineSerializers
from InvenTree.filters import SEARCH_ORDER_FILTER
from InvenTree.mixins import ListCreateAPI, RetrieveUpdateAPI, RetrieveUpdateDestroyAPI
from machine import registry
from machine.models import MachineConfig, MachineSetting
class MachineList(ListCreateAPI):
"""API endpoint for list of Machine objects.
- GET: Return a list of all Machine objects
- POST: create a MachineConfig
"""
queryset = MachineConfig.objects.all()
serializer_class = MachineSerializers.MachineConfigSerializer
def get_serializer_class(self):
"""Allow driver, machine_type fields on creation."""
if self.request.method == 'POST':
return MachineSerializers.MachineConfigCreateSerializer
return super().get_serializer_class()
filter_backends = SEARCH_ORDER_FILTER
filterset_fields = ['machine_type', 'driver', 'active']
ordering_fields = ['name', 'machine_type', 'driver', 'active']
ordering = ['-active', 'machine_type']
search_fields = ['name']
class MachineDetail(RetrieveUpdateDestroyAPI):
"""API detail endpoint for MachineConfig object.
- GET: return a single MachineConfig
- PUT: update a MachineConfig
- PATCH: partial update a MachineConfig
- DELETE: delete a MachineConfig
"""
queryset = MachineConfig.objects.all()
serializer_class = MachineSerializers.MachineConfigSerializer
def get_machine(machine_pk):
"""Get machine by pk.
Raises:
NotFound: If machine is not found
Returns:
BaseMachineType: The machine instance in the registry
"""
machine = registry.get_machine(machine_pk)
if machine is None:
raise NotFound(detail=f"Machine '{machine_pk}' not found")
return machine
class MachineSettingList(APIView):
"""List endpoint for all machine related settings.
- GET: return all settings for a machine config
"""
permission_classes = [permissions.IsAuthenticated]
@extend_schema(
responses={200: MachineSerializers.MachineSettingSerializer(many=True)}
)
def get(self, request, pk):
"""Return all settings for a machine config."""
machine = get_machine(pk)
all_settings = []
for settings, config_type in machine.setting_types:
settings_dict = MachineSetting.all_settings(
settings_definition=settings,
machine_config=machine.machine_config,
config_type=config_type,
)
all_settings.extend(list(settings_dict.values()))
results = MachineSerializers.MachineSettingSerializer(
all_settings, many=True
).data
return Response(results)
class MachineSettingDetail(RetrieveUpdateAPI):
"""Detail endpoint for a machine-specific setting.
- GET: Get machine setting detail
- PUT: Update machine setting
- PATCH: Update machine setting
(Note that these cannot be created or deleted via API)
"""
lookup_field = 'key'
queryset = MachineSetting.objects.all()
serializer_class = MachineSerializers.MachineSettingSerializer
def get_object(self):
"""Lookup machine setting object, based on the URL."""
pk = self.kwargs['pk']
key = self.kwargs['key']
config_type = MachineSetting.get_config_type(self.kwargs['config_type'])
machine = get_machine(pk)
setting_map = {d: s for s, d in machine.setting_types}
if key.upper() not in setting_map[config_type]:
raise NotFound(
detail=f"Machine '{machine.name}' has no {config_type.name} setting matching '{key.upper()}'"
)
return MachineSetting.get_setting_object(
key, machine_config=machine.machine_config, config_type=config_type
)
class MachineRestart(APIView):
"""Endpoint for performing a machine restart.
- POST: restart machine by pk
"""
permission_classes = [permissions.IsAuthenticated]
@extend_schema(responses={200: MachineSerializers.MachineRestartSerializer()})
def post(self, request, pk):
"""Restart machine by pk."""
machine = get_machine(pk)
registry.restart_machine(machine)
result = MachineSerializers.MachineRestartSerializer({'ok': True}).data
return Response(result)
class MachineTypesList(APIView):
"""List API Endpoint for all discovered machine types.
- GET: List all machine types
"""
permission_classes = [permissions.IsAuthenticated]
@extend_schema(responses={200: MachineSerializers.MachineTypeSerializer(many=True)})
def get(self, request):
"""List all machine types."""
machine_types = list(registry.machine_types.values())
results = MachineSerializers.MachineTypeSerializer(
machine_types, many=True
).data
return Response(results)
class MachineDriverList(APIView):
"""List API Endpoint for all discovered machine drivers.
- GET: List all machine drivers
"""
permission_classes = [permissions.IsAuthenticated]
@extend_schema(
responses={200: MachineSerializers.MachineDriverSerializer(many=True)}
)
def get(self, request):
"""List all machine drivers."""
drivers = registry.drivers.values()
if machine_type := request.query_params.get('machine_type', None):
drivers = filter(lambda d: d.machine_type == machine_type, drivers)
results = MachineSerializers.MachineDriverSerializer(
list(drivers), many=True
).data
return Response(results)
class RegistryStatusView(APIView):
"""Status API endpoint for the machine registry.
- GET: Provide status data for the machine registry
"""
permission_classes = [permissions.IsAuthenticated]
serializer_class = MachineSerializers.MachineRegistryStatusSerializer
@extend_schema(
responses={200: MachineSerializers.MachineRegistryStatusSerializer()}
)
def get(self, request):
"""Provide status data for the machine registry."""
result = MachineSerializers.MachineRegistryStatusSerializer({
'registry_errors': [{'message': str(error)} for error in registry.errors]
}).data
return Response(result)
machine_api_urls = [
# machine types
path('types/', MachineTypesList.as_view(), name='api-machine-types'),
# machine drivers
path('drivers/', MachineDriverList.as_view(), name='api-machine-drivers'),
# registry status
path('status/', RegistryStatusView.as_view(), name='api-machine-registry-status'),
# detail views for a single Machine
path(
'<uuid:pk>/',
include([
# settings
path(
'settings/',
include([
re_path(
r'^(?P<config_type>M|D)/(?P<key>\w+)/',
MachineSettingDetail.as_view(),
name='api-machine-settings-detail',
),
path('', MachineSettingList.as_view(), name='api-machine-settings'),
]),
),
# restart
path('restart/', MachineRestart.as_view(), name='api-machine-restart'),
# detail
path('', MachineDetail.as_view(), name='api-machine-detail'),
]),
),
# machine list and create
path('', MachineList.as_view(), name='api-machine-list'),
]