from __future__ import unicode_literals import csv import itertools import re from mptt.forms import TreeNodeMultipleChoiceField from django import forms from django.conf import settings from django.urls import reverse_lazy from .validators import EnhancedURLValidator COLOR_CHOICES = ( ('aa1409', 'Dark red'), ('f44336', 'Red'), ('e91e63', 'Pink'), ('ff66ff', 'Fuschia'), ('9c27b0', 'Purple'), ('673ab7', 'Dark purple'), ('3f51b5', 'Indigo'), ('2196f3', 'Blue'), ('03a9f4', 'Light blue'), ('00bcd4', 'Cyan'), ('009688', 'Teal'), ('2f6a31', 'Dark green'), ('4caf50', 'Green'), ('8bc34a', 'Light green'), ('cddc39', 'Lime'), ('ffeb3b', 'Yellow'), ('ffc107', 'Amber'), ('ff9800', 'Orange'), ('ff5722', 'Dark orange'), ('795548', 'Brown'), ('c0c0c0', 'Light grey'), ('9e9e9e', 'Grey'), ('607d8b', 'Dark grey'), ('111111', 'Black'), ) NUMERIC_EXPANSION_PATTERN = '\[((?:\d+[?:,-])+\d+)\]' ALPHANUMERIC_EXPANSION_PATTERN = '\[((?:[a-zA-Z0-9]+[?:,-])+[a-zA-Z0-9]+)\]' IP4_EXPANSION_PATTERN = '\[((?:[0-9]{1,3}[?:,-])+[0-9]{1,3})\]' IP6_EXPANSION_PATTERN = '\[((?:[0-9a-f]{1,4}[?:,-])+[0-9a-f]{1,4})\]' def parse_numeric_range(string, base=10): """ Expand a numeric range (continuous or not) into a decimal or hexadecimal list, as specified by the base parameter '0-3,5' => [0, 1, 2, 3, 5] '2,8-b,d,f' => [2, 8, 9, a, b, d, f] """ values = list() for dash_range in string.split(','): try: begin, end = dash_range.split('-') except ValueError: begin, end = dash_range, dash_range begin, end = int(begin.strip(), base=base), int(end.strip(), base=base) + 1 values.extend(range(begin, end)) return list(set(values)) def expand_numeric_pattern(string): """ Expand a numeric pattern into a list of strings. Examples: 'ge-0/0/[0-3,5]' => ['ge-0/0/0', 'ge-0/0/1', 'ge-0/0/2', 'ge-0/0/3', 'ge-0/0/5'] 'xe-0/[0,2-3]/[0-7]' => ['xe-0/0/0', 'xe-0/0/1', 'xe-0/0/2', ... 'xe-0/3/5', 'xe-0/3/6', 'xe-0/3/7'] """ lead, pattern, remnant = re.split(NUMERIC_EXPANSION_PATTERN, string, maxsplit=1) parsed_range = parse_numeric_range(pattern) for i in parsed_range: if re.search(NUMERIC_EXPANSION_PATTERN, remnant): for string in expand_numeric_pattern(remnant): yield "{}{}{}".format(lead, i, string) else: yield "{}{}{}".format(lead, i, remnant) def parse_alphanumeric_range(string): """ Expand an alphanumeric range (continuous or not) into a list. 'a-d,f' => [a, b, c, d, f] '0-3,a-d' => [0, 1, 2, 3, a, b, c, d] """ values = [] for dash_range in string.split(','): try: begin, end = dash_range.split('-') vals = begin + end # Break out of loop if there's an invalid pattern to return an error if (not (vals.isdigit() or vals.isalpha())) or (vals.isalpha() and not (vals.isupper() or vals.islower())): return [] except ValueError: begin, end = dash_range, dash_range if begin.isdigit() and end.isdigit(): for n in list(range(int(begin), int(end) + 1)): values.append(n) else: for n in list(range(ord(begin), ord(end) + 1)): values.append(chr(n)) return values def expand_alphanumeric_pattern(string): """ Expand an alphabetic pattern into a list of strings. """ lead, pattern, remnant = re.split(ALPHANUMERIC_EXPANSION_PATTERN, string, maxsplit=1) parsed_range = parse_alphanumeric_range(pattern) for i in parsed_range: if re.search(ALPHANUMERIC_EXPANSION_PATTERN, remnant): for string in expand_alphanumeric_pattern(remnant): yield "{}{}{}".format(lead, i, string) else: yield "{}{}{}".format(lead, i, remnant) def expand_ipaddress_pattern(string, family): """ Expand an IP address pattern into a list of strings. Examples: '192.0.2.[1,2,100-250,254]/24' => ['192.0.2.1/24', '192.0.2.2/24', '192.0.2.100/24' ... '192.0.2.250/24', '192.0.2.254/24'] '2001:db8:0:[0,fd-ff]::/64' => ['2001:db8:0:0::/64', '2001:db8:0:fd::/64', ... '2001:db8:0:ff::/64'] """ if family not in [4, 6]: raise Exception("Invalid IP address family: {}".format(family)) if family == 4: regex = IP4_EXPANSION_PATTERN base = 10 else: regex = IP6_EXPANSION_PATTERN base = 16 lead, pattern, remnant = re.split(regex, string, maxsplit=1) parsed_range = parse_numeric_range(pattern, base) for i in parsed_range: if re.search(regex, remnant): for string in expand_ipaddress_pattern(remnant, family): yield ''.join([lead, format(i, 'x' if family == 6 else 'd'), string]) else: yield ''.join([lead, format(i, 'x' if family == 6 else 'd'), remnant]) def add_blank_choice(choices): """ Add a blank choice to the beginning of a choices list. """ return ((None, '---------'),) + tuple(choices) # # Widgets # class SmallTextarea(forms.Textarea): pass class ColorSelect(forms.Select): """ Extends the built-in Select widget to colorize each