Files
Django-Proxmox-Mikrotik/lib/router_abstract.py
Holger Sielaff 90c0ff61ed initial
2025-08-27 09:55:55 +02:00

343 lines
11 KiB
Python

import json
import logging
from copy import copy
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.forms import model_to_dict
from lib import FactoryMixin
class RouterObjectCollection(set, FactoryMixin):
_objectclass = None
def _check_member_class(self, member):
if self._objectclass:
if not isinstance(member, self._objectclass):
raise ValueError(
f'Only {self._objectclass.__class__.__name__} can be added to {self.__class__.__name__}')
else:
if not isinstance(member, RouterObjectAbstract):
raise ValueError(
f'Only {RouterObjectAbstract.__class__.__name__} can be added to {self.__class__.__name__}')
self._objectclass = member.__class__
def add(self, *args, **kwargs):
"""Override add() to check if object is of correct type
we need a __hash__() function for this"""
self._check_member_class(args[0])
return super().add(*args, **kwargs)
def filter(self, *args, **kwargs):
"""This returns a new object filtered by some criteria
"""
_new = self.__class__()
for obj in self:
if obj.filtered(*args, **kwargs):
_new.add(obj)
return _new
def all(self):
"""This returns all objects - a copy"""
return self.copy()
def first(self, *args, **kwargs):
"""This returns the first object
from a copy of self"""
if args or kwargs:
objects = self.filter(*args, **kwargs)
else:
objects = self.all()
return objects[0] if objects else None
def remove(self, *args, **kwargs):
"""Override remove() to not throw KeyError (like discard())"""
try:
super().remove(*args, **kwargs)
except KeyError:
pass
return self
def values_list(self, keyset: list, flat=False):
if flat:
_ret = []
else:
_ret = self.__class__()
for obj in self:
new_obj = obj.__class__(**{k: v for k, v in copy(obj).items() if k in keyset})
if flat:
valuesdict = {}
for k, v in new_obj.items():
valuesdict.setdefault(k, []).append(v)
return list(valuesdict.items())
else:
_ret.add(new_obj)
return _ret
class RoutedModelAbstract(models.Model):
class Meta:
abstract = True
internal_id = models.BigAutoField(primary_key=True)
@property
def router_object(self):
return self._router_object
@router_object.setter
def router_object(self, value):
self._router_object = value
def __init__(self, *args, **kwargs):
self._router_object = kwargs.pop('router_object', None)
super().__init__(*args, **kwargs)
self._old_values = self.to_json
@property
def to_json(self):
return model_to_dict(self)
@property
def unique_on_router(self):
raise NotImplemented(f'{self.__class__.__name__} must implement unique_on_router')
class RouterObjectAbstract(dict, FactoryMixin):
_router_instance = None
_model_class = None
_model_instance = None
def __init__(self, router=None, model=None, **kwargs):
if router:
self.set_router(router)
if model:
self.set_model(model)
super().__init__(**kwargs)
class DoesNotExistsOnRouter(ObjectDoesNotExist):
pass
@property
def model_class(self):
return self._model_class
@property
def get_from_db(self):
raise NotImplemented(f"{self.__class__.__name__} must implement get_from_db")
@model_class.setter
def model_class(self, model):
if isinstance(model, RoutedModelAbstract):
self._model_class = model.__class__
elif issubclass(model, RoutedModelAbstract):
self._model_class = model
else:
raise ValueError(
f"model must be of type {RoutedModelAbstract.__class__.__name__}, not {model.__class__.__name__}")
@property
def model_instance(self):
return self._model_instance
@model_instance.setter
def model_instance(self, model):
if self._model_class and not issubclass(model.__class__, self._model_class):
raise ValueError(
f'Model {model.__class__.__name__} must be of type {self._model_class.__class__.__name__}')
if not isinstance(model, RoutedModelAbstract):
raise ValueError(
f"model must be of type {RoutedModelAbstract.__class__.__name__}, not {model.__class__.__name__}")
self._model_instance = model
self._model_class = model.__class_
model.router_object = self
def set_model(self, model):
if isinstance(model, RoutedModelAbstract):
self.model_instance = model
else:
self.model_class = model.__class__
if not self._model_instance:
logging.debug(f'Creating new unsaved {self._model_class.__name__} from {self}')
self._model_instance = self.model_class(**({'router_object': self} | self))
return self
def set_router(self, router):
assert isinstance(router,
RouterAbstract), f"router must be of type {RouterAbstract.__class__.__name__}, not {router.__class__.__name__}"
self._router_instance = router
return self
def to_db_object(self, raise_on_keyerror=False) -> models.Model:
"""This returns a dict representation of the object"""
if raise_on_keyerror:
_data = {k: self.get(k, '') for k in model_to_dict(self._model_class).keys()}
else:
_errors = []
_data = {}
for k in model_to_dict(self._model_class):
try:
_data[k] = self[k]
except KeyError as e:
_errors.append(str(e))
if _errors:
raise KeyError(f'Could not convert {self.__class__.__name__} to DB object - missing keys: {_errors}')
return self._model_class(**_data)
@classmethod
def from_db_object(cls, db_object):
return cls(**model_to_dict(db_object))
@property
def router(self):
return self._router_instance
def __hash__(self):
return len(self.to_json)
def _filter_or(self, **kwargs):
for k, v in kwargs.items():
if self.get(k) == v:
return True
return False
def _filter_and(self, **kwargs):
for k, v in kwargs.items():
if self.get(k) != v:
return False
return True
def filtered(self, mode='or', raise_on_failure=False, **kwargs):
"""This returns objects filtered by some criteria
Return self if criterias match, else None
"""
assert mode in ('or', 'and'), f"mode must be 'or' or 'and', not {mode}"
if getattr(self, f'_filter_{mode}')(**kwargs):
return self
if raise_on_failure:
raise self.DoesNotExists(f'Object {self} does not match criteria {kwargs}')
return None
def __getattr__(self, name):
"""This makes a 'magic' trick"""
if name in self:
return self[name]
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
def to_json(self, **dump_params):
"""This returns a dict representation of the object"""
if default_fun := dump_params.pop('default', None):
pass
else:
default_fun = lambda o: str(o)
return json.loads(json.dumps(self, default=default_fun, **dump_params))
@classmethod
def router_get(cls, *args, **kwargs):
"""This returns a RouterObjectCollection of objects"""
raise NotImplemented(f"{cls.__name__} must implement router_get()")
def router_post(self):
"""This adds an object to the router"""
raise NotImplemented(f"{self.__class__.__name__} must implement router_post()")
def router_put(self, **kwargs):
"""This removes an object from the router"""
raise NotImplemented(f"{self.__class__.__name__} must implement router_put()")
def router_delete(self):
"""This removes an object from the router"""
raise NotImplemented(f"{self.__class__.__name__} must implement router_delete()")
class DNSStaticAbstract(RoutedModelAbstract):
"""The DNSStatic Object"""
class Meta:
abstract = True
@property
def get_name(self):
raise NotImplemented(f'{self.__class__.__name__} must implement get_name')
@property
def get_regex(self):
raise NotImplemented(f'{self.__class__.__name__} must implement get_name')
@property
def get_ip4(self):
raise NotImplemented(f'{self.__class__.__name__} must implement get_ip4')
@property
def get_ip6(self):
raise NotImplemented(f'{self.__class__.__name__} must implement get_ip6')
class LeaseAbstract(RoutedModelAbstract):
"""The IP Lease Object"""
class Meta:
abstract = True
@property
def get_mac(self):
raise NotImplemented(f'{self.__class__.__name__} must implement get_mac')
@property
def get_ip4(self):
raise NotImplemented(f'{self.__class__.__name__} must implement get_ip4')
@property
def get_ip6(self):
raise NotImplemented(f'{self.__class__.__name__} must implement get_ip6')
@property
def get_status(self):
raise NotImplemented(f'{self.__class__.__name__} must implement get_status')
class IPAddressAbstract(RoutedModelAbstract):
"""The Address Pool"""
class Meta:
abstract = True
@property
def get_address(self):
raise NotImplemented(f'{self.__class__.__name__} must implement get_address')
@property
def get_network(self):
raise NotImplemented(f'{self.__class__.__name__} must implement get_network')
class RouterAbstract:
def __init__(self, *args, **kwargs):
self.initialize(*args, **kwargs)
@property
def api(self):
raise NotImplemented(f"{self.__class__.__name__} must implement api or init with api parameter")
def initialize(self, *args, **kwargs):
"""This initializes the connection to the router"""
raise NotImplemented(f"{self.__class__.__name__} must implement initialize()")
def get(self, **kwargs):
raise NotImplemented(f"{self.__class__.__name__} must implement get()")
def add(self, **kwargs):
raise NotImplemented(f"{self.__class__.__name__} must implement post()")
def set(self, **kwargs):
raise NotImplemented(f"{self.__class__.__name__} must implement put()")
def remove(self, **kwargs):
raise NotImplemented(f"{self.__class__.__name__} must implement delete()")