from __future__ import unicode_literals import csv from io import StringIO import re from django import forms from django.conf import settings from django.urls import reverse_lazy from mptt.forms import TreeNodeMultipleChoiceField 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+)\]' 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 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