View | Details | Raw Unified | Return to bug 42478
Collapse All | Expand All

(-)a/ucs-school-import/modules/ucsschool/importer/utils/username_handler.py (-57 / +90 lines)
 Lines 42-54   from ucsschool.importer.utils.logging import get_logger Link Here 
42
42
43
43
44
class UsernameHandler(object):
44
class UsernameHandler(object):
45
	replacement_variable_pattern = re.compile(r"\[.*?\]")
45
	"""
46
	>>> BAD_CHARS = ''.join(set(map(chr, range(128))) - set('.1032547698ACBEDGFIHKJMLONQPSRUTWVYXZacbedgfihkjmlonqpsrutwvyxz'))
47
	>>> UsernameHandler(20).format_username('Max.Mustermann')
48
	'Max.Mustermann'
49
	>>> UsernameHandler(20).format_username('Foo[COUNTER2][COUNTER2]')   # doctest: +IGNORE_EXCEPTION_DETAIL
50
	Traceback (most recent call last):
51
	...
52
	FormatError:
53
	>>> UsernameHandler(20).format_username('.')   # doctest: +IGNORE_EXCEPTION_DETAIL
54
	Traceback (most recent call last):
55
	...
56
	FormatError:
57
	>>> UsernameHandler(20).format_username('.Max.Mustermann.')
58
	'Max.Mustermann'
59
	>>> UsernameHandler(4).format_username('Max.Mustermann')
60
	'Max'
61
	>>> for c in BAD_CHARS:
62
	...  assert 'Max' == UsernameHandler(20).format_username('Ma%sx' % (c,))
63
	...
64
	>>> UsernameHandler(20).format_username('Max.Mustermann12.4567890')
65
	'Max.Mustermann12.456'
66
	>>> for c in '.1032547698ACBEDGFIHKJMLONQPSRUTWVYXZacbedgfihkjmlonqpsrutwvyxz':
67
	...  assert 'Ma%sx' % (c,) == UsernameHandler(20).format_username('Ma%sx' % (c,))
68
	...
69
	>>> UsernameHandler(20).format_username('Max[Muster]Mann')
70
	'MaxMusterMann'
71
	>>> UsernameHandler(20).format_username('Max[ALWAYSCOUNTER].Mustermann')
72
	'Max1.Mustermann'
73
	>>> UsernameHandler(20).format_username('Max[ALWAYSCOUNTER].Mustermann')
74
	'Max2.Mustermann'
75
	>>> UsernameHandler(20).format_username('Max[ALWAYSCOUNTER].Mustermann')
76
	'Max3.Mustermann'
77
	>>> UsernameHandler(20).format_username('Max[COUNTER2].Mustermann')
78
	'Max4.Mustermann'
79
	>>> UsernameHandler(20).format_username('Maria[ALWAYSCOUNTER].Musterfrau')
80
	'Maria1.Musterfrau'
81
	>>> UsernameHandler(20).format_username('Moritz[COUNTER2]')
82
	'Moritz'
83
	>>> UsernameHandler(20).format_username('Moritz[COUNTER2]')
84
	'Moritz2'
85
	>>> for i, c in enumerate(BAD_CHARS, 1):
86
	...  assert 'Foo%d' % (i) == UsernameHandler(20).format_username('Fo%so[ALWAYSCOUNTER]' % (c,))
87
	>>> UsernameHandler(8).format_username('aaaa[COUNTER2]bbbbcccc')
88
	'aaaab'
89
	>>> UsernameHandler(8).format_username('aaaa[COUNTER2]bbbbcccc')
90
	'aaaa2b'
91
	>>> UsernameHandler(8).format_username('bbbb[ALWAYSCOUNTER]ccccdddd')
92
	'bbbb1c'
93
	>>> UsernameHandler(8).format_username('bbbb[ALWAYSCOUNTER]ccccdddd')
94
	'bbbb2c'
95
	"""
96
46
	allowed_chars = string.ascii_letters + string.digits + "."
97
	allowed_chars = string.ascii_letters + string.digits + "."
47
98
48
	def __init__(self, username_max_length):
99
	def __init__(self, username_max_length):
49
		self.username_max_length = username_max_length
100
		self.username_max_length = username_max_length
50
		self.logger = get_logger()
101
		self.logger = get_logger()
51
		self.connection, self.position = get_admin_connection()
102
		self.connection, self.position = get_admin_connection()
103
		self.replacement_variable_pattern = re.compile(r'(%s)' % '|'.join(map(re.escape, self.counter_variable_to_function.keys())), flags=re.I)
52
104
53
	def add_to_ldap(self, username, first_number):
105
	def add_to_ldap(self, username, first_number):
54
		assert isinstance(username, basestring)
106
		assert isinstance(username, basestring)
 Lines 92-100   class UsernameHandler(object): Link Here 
92
		:param name: str: username to check
144
		:param name: str: username to check
93
		:return: str: copy of input, possibly modified
145
		:return: str: copy of input, possibly modified
94
		"""
146
		"""
95
		bad_chars = "".join(sorted(set(name).difference(set(self.allowed_chars))))
147
		bad_chars = ''.join(set(name).difference(set(self.allowed_chars)))
96
		if bad_chars:
148
		if bad_chars:
97
			self.logger.warn("Removing disallowed characters %r from username %r.", bad_chars, name)
149
			self.logger.warn("Removing disallowed characters %r from username %r.", ''.join(sorted(bad_chars)), name)
98
		if name.startswith(".") or name.endswith("."):
150
		if name.startswith(".") or name.endswith("."):
99
			self.logger.warn("Removing disallowed dot from start and end of username %r.", name)
151
			self.logger.warn("Removing disallowed dot from start and end of username %r.", name)
100
			name = name.strip(".")
152
			name = name.strip(".")
 Lines 116-177   class UsernameHandler(object): Link Here 
116
		:return: str: unique username
168
		:return: str: unique username
117
		"""
169
		"""
118
		assert isinstance(name, basestring)
170
		assert isinstance(name, basestring)
119
		ori_name = name
171
		PATTERN_FUNC_MAXLENGTH = 3  # maximum a counter function can produce is len('999')
120
		cut_pos = self.username_max_length - 3  # numbers >999 are not supported
172
		username = name
121
173
122
		match = self.replacement_variable_pattern.search(name)
174
		match = self.replacement_variable_pattern.search(name)
123
		if not match:
175
		if match:
124
			# no counter variable used, just check characters and length
176
			func = self.counter_variable_to_function[match.group().upper()]
125
			name = self.remove_bad_chars(name)
177
			cut_pos = self.username_max_length - PATTERN_FUNC_MAXLENGTH
126
			if len(name) > self.username_max_length:
178
127
				res = name[:self.username_max_length]
179
			# it's not allowed to have two [COUNTER] patterns
128
				self.logger.warn("Username %r too long, shortened to %r.", name, res)
180
			if len(self.replacement_variable_pattern.findall(name)) >= 2:
129
			else:
181
				raise FormatError("More than one counter variable found in username scheme '{}'.".format(name), name, name)
130
				res = name
182
131
			return res
183
			# the variable must no be the [COUNTER] pattern
132
184
			without_pattern = self.replacement_variable_pattern.sub('', name)
133
		if len(self.replacement_variable_pattern.split(name)) > 2:
185
134
			raise FormatError("More than one counter variable found in username scheme '{}'.".format(name), name, name)
186
			username = name
135
187
			if len(without_pattern) > cut_pos:
136
		# need username without counter variable to calculate length
188
				without_pattern = self.remove_bad_chars(without_pattern)[:cut_pos]
137
		_base_name = "".join(self.replacement_variable_pattern.split(name))
189
				start, end = without_pattern[:match.start()], without_pattern[match.start():]
138
		base_name = self.remove_bad_chars(_base_name)
190
				username = '%s[%s]%s' % (start, match.group(), end)
139
		if _base_name != base_name:
191
			username = self.replacement_variable_pattern.sub(func(without_pattern), username)
140
			# recalculate position of pattern
192
141
			name = "{}{}{}".format(base_name[:match.start()], match.group(), base_name[match.end():])
193
		username = self.remove_bad_chars(username)
142
			match = self.replacement_variable_pattern.search(name)
194
143
195
		if not match and len(name) > self.username_max_length:
144
		variable = match.group()
196
			username = username[:self.username_max_length]
145
		start = match.start()
197
			self.logger.warn("Username %r too long, shortened to %r.", name, username)
146
		end = match.end()
198
147
199
		username = username.strip('.')
148
		if start == 0 and end == len(name):
200
		if not username:
149
			raise FormatError("No username in '{}'.".format(name), ori_name, ori_name)
201
			raise FormatError("No username in '{}'.".format(name), name, name)
150
202
		return username
151
		# get counter function
152
		try:
153
			func = self.counter_variable_to_function[variable.upper()]
154
		except KeyError as exc:
155
			raise FormatError("Unknown variable name '{}' in username scheme '{}': '{}' not in known variables: '{}'".format(
156
				variable, ori_name, exc, self.counter_variable_to_function.keys()), variable, name)
157
		except AttributeError as exc:
158
			raise FormatError("No method '{}' can be found for variable name '{}' in username scheme '{}': {}".format(
159
				self.counter_variable_to_function[variable], variable, name, exc), variable, ori_name)
160
161
		if len(base_name) > cut_pos:
162
			# base name without variable to long, we have to shorten it
163
			# numbers will only be appended, no inserting possible anymore
164
			res = base_name[:cut_pos]
165
			insert_position = cut_pos
166
			self.logger.warn("Username %r too long, shortened to %r.", base_name, res)
167
			res = self.remove_bad_chars(res)  # dot from middle might be at end now
168
		else:
169
			insert_position = start
170
			res = u"{}{}".format(name[:start], name[end:])
171
172
		counter = func(res)  # get counter number to insert/append
173
		ret = "{}{}{}".format(res[:insert_position], counter, res[insert_position:])
174
		return ret
175
203
176
	@property
204
	@property
177
	def counter_variable_to_function(self):
205
	def counter_variable_to_function(self):
 Lines 217-219   class UsernameHandler(object): Link Here 
217
			num = first_time
245
			num = first_time
218
			self.add_to_ldap(name_base, "2")
246
			self.add_to_ldap(name_base, "2")
219
		return num
247
		return num
248
249
250
if __name__ == '__main__':
251
	import doctest
252
	doctest.testmod()

Return to bug 42478