utils.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import re
  2. from django import forms
  3. from django.forms.models import fields_for_model
  4. from utilities.querysets import RestrictedQuerySet
  5. from .constants import *
  6. __all__ = (
  7. 'add_blank_choice',
  8. 'expand_alphanumeric_pattern',
  9. 'expand_ipaddress_pattern',
  10. 'form_from_model',
  11. 'parse_alphanumeric_range',
  12. 'parse_numeric_range',
  13. 'restrict_form_fields',
  14. )
  15. def parse_numeric_range(string, base=10):
  16. """
  17. Expand a numeric range (continuous or not) into a decimal or
  18. hexadecimal list, as specified by the base parameter
  19. '0-3,5' => [0, 1, 2, 3, 5]
  20. '2,8-b,d,f' => [2, 8, 9, a, b, d, f]
  21. """
  22. values = list()
  23. for dash_range in string.split(','):
  24. try:
  25. begin, end = dash_range.split('-')
  26. except ValueError:
  27. begin, end = dash_range, dash_range
  28. begin, end = int(begin.strip(), base=base), int(end.strip(), base=base) + 1
  29. values.extend(range(begin, end))
  30. return list(set(values))
  31. def parse_alphanumeric_range(string):
  32. """
  33. Expand an alphanumeric range (continuous or not) into a list.
  34. 'a-d,f' => [a, b, c, d, f]
  35. '0-3,a-d' => [0, 1, 2, 3, a, b, c, d]
  36. """
  37. values = []
  38. for dash_range in string.split(','):
  39. try:
  40. begin, end = dash_range.split('-')
  41. vals = begin + end
  42. # Break out of loop if there's an invalid pattern to return an error
  43. if (not (vals.isdigit() or vals.isalpha())) or (vals.isalpha() and not (vals.isupper() or vals.islower())):
  44. return []
  45. except ValueError:
  46. begin, end = dash_range, dash_range
  47. if begin.isdigit() and end.isdigit():
  48. for n in list(range(int(begin), int(end) + 1)):
  49. values.append(n)
  50. else:
  51. # Value-based
  52. if begin == end:
  53. values.append(begin)
  54. # Range-based
  55. else:
  56. # Not a valid range (more than a single character)
  57. if not len(begin) == len(end) == 1:
  58. raise forms.ValidationError('Range "{}" is invalid.'.format(dash_range))
  59. for n in list(range(ord(begin), ord(end) + 1)):
  60. values.append(chr(n))
  61. return values
  62. def expand_alphanumeric_pattern(string):
  63. """
  64. Expand an alphabetic pattern into a list of strings.
  65. """
  66. lead, pattern, remnant = re.split(ALPHANUMERIC_EXPANSION_PATTERN, string, maxsplit=1)
  67. parsed_range = parse_alphanumeric_range(pattern)
  68. for i in parsed_range:
  69. if re.search(ALPHANUMERIC_EXPANSION_PATTERN, remnant):
  70. for string in expand_alphanumeric_pattern(remnant):
  71. yield "{}{}{}".format(lead, i, string)
  72. else:
  73. yield "{}{}{}".format(lead, i, remnant)
  74. def expand_ipaddress_pattern(string, family):
  75. """
  76. Expand an IP address pattern into a list of strings. Examples:
  77. '192.0.2.[1,2,100-250]/24' => ['192.0.2.1/24', '192.0.2.2/24', '192.0.2.100/24' ... '192.0.2.250/24']
  78. '2001:db8:0:[0,fd-ff]::/64' => ['2001:db8:0:0::/64', '2001:db8:0:fd::/64', ... '2001:db8:0:ff::/64']
  79. """
  80. if family not in [4, 6]:
  81. raise Exception("Invalid IP address family: {}".format(family))
  82. if family == 4:
  83. regex = IP4_EXPANSION_PATTERN
  84. base = 10
  85. else:
  86. regex = IP6_EXPANSION_PATTERN
  87. base = 16
  88. lead, pattern, remnant = re.split(regex, string, maxsplit=1)
  89. parsed_range = parse_numeric_range(pattern, base)
  90. for i in parsed_range:
  91. if re.search(regex, remnant):
  92. for string in expand_ipaddress_pattern(remnant, family):
  93. yield ''.join([lead, format(i, 'x' if family == 6 else 'd'), string])
  94. else:
  95. yield ''.join([lead, format(i, 'x' if family == 6 else 'd'), remnant])
  96. def add_blank_choice(choices):
  97. """
  98. Add a blank choice to the beginning of a choices list.
  99. """
  100. return ((None, '---------'),) + tuple(choices)
  101. def form_from_model(model, fields):
  102. """
  103. Return a Form class with the specified fields derived from a model. This is useful when we need a form to be used
  104. for creating objects, but want to avoid the model's validation (e.g. for bulk create/edit functions). All fields
  105. are marked as not required.
  106. """
  107. form_fields = fields_for_model(model, fields=fields)
  108. for field in form_fields.values():
  109. field.required = False
  110. return type('FormFromModel', (forms.Form,), form_fields)
  111. def restrict_form_fields(form, user, action='view'):
  112. """
  113. Restrict all form fields which reference a RestrictedQuerySet. This ensures that users see only permitted objects
  114. as available choices.
  115. """
  116. for field in form.fields.values():
  117. if hasattr(field, 'queryset') and issubclass(field.queryset.__class__, RestrictedQuerySet):
  118. field.queryset = field.queryset.restrict(user, action)