managers.py 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. from django.db.models import Manager, QuerySet
  2. from django.db.models.expressions import RawSQL
  3. from .constants import NONCONNECTABLE_IFACE_TYPES
  4. # Regular expressions for parsing Interface names
  5. TYPE_RE = r"SUBSTRING({} FROM '^([^0-9\.:]+)')"
  6. SLOT_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(\d{{1,9}})/') AS integer), NULL)"
  7. SUBSLOT_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9\.:]+)?\d{{1,9}}/(\d{{1,9}})') AS integer), NULL)"
  8. POSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:\d{{1,9}}/){{2}}(\d{{1,9}})') AS integer), NULL)"
  9. SUBPOSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:\d{{1,9}}/){{3}}(\d{{1,9}})') AS integer), NULL)"
  10. ID_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9\.:]+)?(\d{{1,9}})([^/]|$)') AS integer)"
  11. CHANNEL_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^.*:(\d{{1,9}})(\.\d{{1,9}})?$') AS integer), 0)"
  12. VC_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^.*\.(\d{{1,9}})$') AS integer), 0)"
  13. class DeviceComponentManager(Manager):
  14. def get_queryset(self):
  15. queryset = super().get_queryset()
  16. table_name = self.model._meta.db_table
  17. sql = r"CONCAT(REGEXP_REPLACE({}.name, '\d+$', ''), LPAD(SUBSTRING({}.name FROM '\d+$'), 8, '0'))"
  18. # Pad any trailing digits to effect natural sorting
  19. return queryset.extra(
  20. select={
  21. 'name_padded': sql.format(table_name, table_name),
  22. }
  23. ).order_by('name_padded', 'pk')
  24. class InterfaceQuerySet(QuerySet):
  25. def connectable(self):
  26. """
  27. Return only physical interfaces which are capable of being connected to other interfaces (i.e. not virtual or
  28. wireless).
  29. """
  30. return self.exclude(form_factor__in=NONCONNECTABLE_IFACE_TYPES)
  31. class InterfaceManager(Manager):
  32. def get_queryset(self):
  33. """
  34. Naturally order interfaces by their type and numeric position. To order interfaces naturally, the `name` field
  35. is split into eight distinct components: leading text (type), slot, subslot, position, subposition, ID, channel,
  36. and virtual circuit:
  37. {type}{slot or ID}/{subslot}/{position}/{subposition}:{channel}.{vc}
  38. Components absent from the interface name are coalesced to zero or null. For example, an interface named
  39. GigabitEthernet1/2/3 would be parsed as follows:
  40. type = 'GigabitEthernet'
  41. slot = 1
  42. subslot = 2
  43. position = 3
  44. subposition = None
  45. id = None
  46. channel = 0
  47. vc = 0
  48. The original `name` field is considered in its entirety to serve as a fallback in the event interfaces do not
  49. match any of the prescribed fields.
  50. The `id` field is included to enforce deterministic ordering of interfaces in similar vein of other device
  51. components.
  52. """
  53. sql_col = '{}.name'.format(self.model._meta.db_table)
  54. ordering = [
  55. '_slot', '_subslot', '_position', '_subposition', '_type', '_id', '_channel', '_vc', 'name', 'pk'
  56. ]
  57. fields = {
  58. '_type': RawSQL(TYPE_RE.format(sql_col), []),
  59. '_id': RawSQL(ID_RE.format(sql_col), []),
  60. '_slot': RawSQL(SLOT_RE.format(sql_col), []),
  61. '_subslot': RawSQL(SUBSLOT_RE.format(sql_col), []),
  62. '_position': RawSQL(POSITION_RE.format(sql_col), []),
  63. '_subposition': RawSQL(SUBPOSITION_RE.format(sql_col), []),
  64. '_channel': RawSQL(CHANNEL_RE.format(sql_col), []),
  65. '_vc': RawSQL(VC_RE.format(sql_col), []),
  66. }
  67. return InterfaceQuerySet(self.model, using=self._db).annotate(**fields).order_by(*ordering)