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

(-)modules/ucsschool/importer/mass_import/mass_import.py (-1 / +5 lines)
 Lines 31-36    Link Here 
31
# /usr/share/common-licenses/AGPL-3; if not, see
31
# /usr/share/common-licenses/AGPL-3; if not, see
32
# <http://www.gnu.org/licenses/>.
32
# <http://www.gnu.org/licenses/>.
33
33
34
import sys
34
import datetime
35
import datetime
35
36
36
from ucsschool.importer.factory import Factory
37
from ucsschool.importer.factory import Factory
 Lines 37-42    Link Here 
37
from ucsschool.importer.configuration import Configuration
38
from ucsschool.importer.configuration import Configuration
38
from ucsschool.importer.utils.logging import get_logger
39
from ucsschool.importer.utils.logging import get_logger
39
from ucsschool.lib.models.utils import stopped_notifier
40
from ucsschool.lib.models.utils import stopped_notifier
41
from memory_profiler import profile, LogFile
40
42
41
43
42
class MassImport(object):
44
class MassImport(object):
 Lines 56-61    Link Here 
56
		self.result_exporter = self.factory.make_result_exporter()
58
		self.result_exporter = self.factory.make_result_exporter()
57
		self.password_exporter = self.factory.make_password_exporter()
59
		self.password_exporter = self.factory.make_password_exporter()
58
		self.errors = list()
60
		self.errors = list()
61
		sys.stdout = LogFile(self.logger.name, reportIncrementFlag=False)
59
62
60
	def mass_import(self):
63
	def mass_import(self):
61
		with stopped_notifier():
64
		with stopped_notifier():
 Lines 89-94    Link Here 
89
	def import_routers(self):
92
	def import_routers(self):
90
		pass
93
		pass
91
94
95
	@profile(precision=3)
92
	def import_users(self):
96
	def import_users(self):
93
		self.logger.info("------ Importing users... ------")
97
		self.logger.info("------ Importing users... ------")
94
		user_import = self.factory.make_user_importer(self.dry_run)
98
		user_import = self.factory.make_user_importer(self.dry_run)
 Lines 98-104    Link Here 
98
		user_import.create_and_modify_users(imported_users)
102
		user_import.create_and_modify_users(imported_users)
99
		self.errors.extend(user_import.errors)
103
		self.errors.extend(user_import.errors)
100
		user_import.log_stats()
104
		user_import.log_stats()
101
		if self.config["output"]["new_user_passwords"]:
105
		if False and self.config["output"]["new_user_passwords"]:  # TODO
102
			nup = datetime.datetime.now().strftime(self.config["output"]["new_user_passwords"])
106
			nup = datetime.datetime.now().strftime(self.config["output"]["new_user_passwords"])
103
			self.logger.info("------ Writing new users passwords to %s... ------", nup)
107
			self.logger.info("------ Writing new users passwords to %s... ------", nup)
104
			self.password_exporter.dump(user_import, nup)
108
			self.password_exporter.dump(user_import, nup)
(-)modules/ucsschool/importer/mass_import/user_import.py (-29 / +34 lines)
 Lines 43-48    Link Here 
43
from ucsschool.importer.configuration import Configuration
43
from ucsschool.importer.configuration import Configuration
44
from ucsschool.importer.utils.logging import get_logger
44
from ucsschool.importer.utils.logging import get_logger
45
from ucsschool.importer.utils.ldap_connection import get_admin_connection
45
from ucsschool.importer.utils.ldap_connection import get_admin_connection
46
from memory_profiler import profile, LogFile
46
47
47
48
48
class UserImport(object):
49
class UserImport(object):
 Lines 61-67    Link Here 
61
		self.connection, self.position = get_admin_connection()
62
		self.connection, self.position = get_admin_connection()
62
		self.factory = Factory()
63
		self.factory = Factory()
63
		self.reader = self.factory.make_reader()
64
		self.reader = self.factory.make_reader()
65
		sys.stdout = LogFile(self.logger.name, reportIncrementFlag=False)
64
66
67
	@profile(precision=3)
65
	def read_input(self):
68
	def read_input(self):
66
		"""
69
		"""
67
		Read users from input data.
70
		Read users from input data.
 Lines 86-91    Link Here 
86
		self.logger.info("------ Read %d users from input data. ------", len(self.imported_users))
89
		self.logger.info("------ Read %d users from input data. ------", len(self.imported_users))
87
		return self.imported_users
90
		return self.imported_users
88
91
92
	@profile(precision=3)
89
	def create_and_modify_users(self, imported_users=None):
93
	def create_and_modify_users(self, imported_users=None):
90
		"""
94
		"""
91
		Create and modify users.
95
		Create and modify users.
 Lines 98-108    Link Here 
98
		:return tuple: (self.errors, self.added_users, self.modified_users)
102
		:return tuple: (self.errors, self.added_users, self.modified_users)
99
		"""
103
		"""
100
		self.logger.info("------ Creating / modifying users... ------")
104
		self.logger.info("------ Creating / modifying users... ------")
101
		for imported_user in imported_users:
105
		usernum = 0
106
		total = len(imported_users)
107
		while imported_users:
108
			imported_user = imported_users.pop(0)
109
			usernum += 1
102
			if imported_user.action == "D":
110
			if imported_user.action == "D":
103
				continue
111
				continue
104
			try:
112
			try:
105
				self.logger.debug("Creating / modifying user %s...", imported_user)
113
				self.logger.debug("Creating / modifying user %d/%d %s...", usernum, total, imported_user)
106
				user = self.determine_add_modify_action(imported_user)
114
				user = self.determine_add_modify_action(imported_user)
107
				cls_name = user.__class__.__name__
115
				cls_name = user.__class__.__name__
108
116
 Lines 146-159    Link Here 
146
						user.record_uid, exc), validation_error=exc, import_user=user), sys.exc_info()[2]
154
						user.record_uid, exc), validation_error=exc, import_user=user), sys.exc_info()[2]
147
155
148
				if success:
156
				if success:
149
					self.logger.info("Success %s %s (source_uid:%s record_uid: %s).", action_str.lower(), user,
157
					self.logger.info("Success %s %d/%d %s (source_uid:%s record_uid: %s).", action_str.lower(), usernum, 						total, user, user.source_uid, user.record_uid)
150
						user.source_uid, user.record_uid)
158
					store.append(user.to_dict_all())
151
					store.append(user)
152
				else:
159
				else:
153
					raise err("Error {} {} (source_uid:{} record_uid: {}), does probably {}exist.".format(
160
					raise err("Error {} {}/{} {} (source_uid:{} record_uid: {}), does probably {}exist.".format(
154
						action_str.lower(), user, user.source_uid, user.record_uid,
161
						action_str.lower(), usernum, len(imported_users), user, user.source_uid, user.record_uid,
155
						"not " if user.action == "M" else "already "), entry=user.entry_count, import_user=user)
162
						"not " if user.action == "M" else "already "), entry=user.entry_count, import_user=user)
156
157
			except (CreationError, ModificationError) as exc:
163
			except (CreationError, ModificationError) as exc:
158
				self.logger.error("Entry #%d: %s",  exc.entry, exc)  # traceback useless
164
				self.logger.error("Entry #%d: %s",  exc.entry, exc)  # traceback useless
159
				self._add_error(exc)
165
				self._add_error(exc)
 Lines 197-202    Link Here 
197
			user.action = "A"
203
			user.action = "A"
198
		return user
204
		return user
199
205
206
	@profile(precision=3)
200
	def detect_users_to_delete(self):
207
	def detect_users_to_delete(self):
201
		"""
208
		"""
202
		Find difference between source database and UCS user database.
209
		Find difference between source database and UCS user database.
 Lines 205-211    Link Here 
205
		"""
212
		"""
206
		self.logger.info("------ Detecting which users to delete... ------")
213
		self.logger.info("------ Detecting which users to delete... ------")
207
		users_to_delete = list()
214
		users_to_delete = list()
208
		a_user = self.factory.make_import_user([])
209
215
210
		if self.config["no_delete"]:
216
		if self.config["no_delete"]:
211
			self.logger.info("------ Looking only for users with action='D' (no_delete=%r) ------",
217
			self.logger.info("------ Looking only for users with action='D' (no_delete=%r) ------",
 Lines 213-221    Link Here 
213
			for user in self.imported_users:
219
			for user in self.imported_users:
214
				if user.action == "D":
220
				if user.action == "D":
215
					try:
221
					try:
216
						ldap_user = a_user.get_by_import_id(self.connection, user.source_uid, user.record_uid)
222
						users_to_delete.append((user.source_uid, user.record_uid))
217
						ldap_user.update(user)  # need user.input_data for hooks
218
						users_to_delete.append(ldap_user)
219
					except noObject:
223
					except noObject:
220
						msg = "User to delete not found in LDAP: {}.".format(user)
224
						msg = "User to delete not found in LDAP: {}.".format(user)
221
						self.logger.error(msg)
225
						self.logger.error(msg)
 Lines 235-253    Link Here 
235
		ucs_ldap_users = self.connection.search(filter_s, attr=attr)
239
		ucs_ldap_users = self.connection.search(filter_s, attr=attr)
236
		ucs_user_ids = set([(lu[1]["ucsschoolSourceUID"][0], lu[1]["ucsschoolRecordUID"][0]) for lu in ucs_ldap_users])
240
		ucs_user_ids = set([(lu[1]["ucsschoolSourceUID"][0], lu[1]["ucsschoolRecordUID"][0]) for lu in ucs_ldap_users])
237
241
238
		# collect ucschool objects for those users to delete in imported_users
242
		users_to_delete = list(ucs_user_ids - imported_user_ids)
239
		for ucs_id_not_in_import in (ucs_user_ids - imported_user_ids):
240
			try:
241
				ldap_user = a_user.get_by_import_id(self.connection, ucs_id_not_in_import[0], ucs_id_not_in_import[1])
242
				ldap_user.action = "D"  # mark for logging/csv-output purposes
243
				users_to_delete.append(ldap_user)
244
			except noObject as exc:
245
				self.logger.error("Cannot delete non existing user with source_uid=%r, record_uid=%r: %s",
246
					ucs_id_not_in_import[0], ucs_id_not_in_import[1], exc)
247
248
		self.logger.debug("users_to_delete=%r", users_to_delete)
243
		self.logger.debug("users_to_delete=%r", users_to_delete)
249
		return users_to_delete
244
		return users_to_delete
250
245
246
	@profile(precision=3)
251
	def delete_users(self, users=None):
247
	def delete_users(self, users=None):
252
		"""
248
		"""
253
		Delete users.
249
		Delete users.
 Lines 261-277    Link Here 
261
		:return: tuple: (self.errors, self.deleted_users)
257
		:return: tuple: (self.errors, self.deleted_users)
262
		"""
258
		"""
263
		self.logger.info("------ Deleting %d users... ------", len(users))
259
		self.logger.info("------ Deleting %d users... ------", len(users))
264
		for user in users:
260
		a_user = self.factory.make_import_user([])
261
		for num, ucs_id_not_in_import in enumerate(users):
265
			try:
262
			try:
263
				user = a_user.get_by_import_id(self.connection, ucs_id_not_in_import[0], ucs_id_not_in_import[1])
264
				user.action = "D"  # mark for logging/csv-output purposes
265
			except noObject as exc:
266
				self.logger.error("Cannot delete non existing user with source_uid=%r, record_uid=%r: %s",
267
								  ucs_id_not_in_import[0], ucs_id_not_in_import[1], exc)
268
				continue
269
			try:
266
				success = self.do_delete(user)
270
				success = self.do_delete(user)
267
				if success:
271
				if success:
268
					self.logger.info("Success deleting user %r (source_uid:%s record_uid: %s).", user.name,
272
					self.logger.info("Success deleting %d/%d %r (source_uid:%s record_uid: %s).", num, len(users),
269
						user.source_uid, user.record_uid)
273
						user.name, user.source_uid, user.record_uid)
270
				else:
274
				else:
271
					raise DeletionError("Error deleting user '{}' (source_uid:{} record_uid: {}), has probably already "
275
					raise DeletionError("Error deleting user '{}' (source_uid:{} record_uid: {}), has probably already "
272
						"been deleted.".format(user.name, user.source_uid, user.record_uid), entry=user.entry_count,
276
						"been deleted.".format(user.name, user.source_uid, user.record_uid), entry=user.entry_count,
273
						import_user=user)
277
						import_user=user)
274
				self.deleted_users[user.__class__.__name__].append(user)
278
				self.deleted_users[user.__class__.__name__].append(user.to_dict_all())
275
			except UcsSchoolImportError as exc:
279
			except UcsSchoolImportError as exc:
276
				self.logger.exception("Error in entry #%d: %s",  exc.entry, exc)
280
				self.logger.exception("Error in entry #%d: %s",  exc.entry, exc)
277
				self._add_error(exc)
281
				self._add_error(exc)
 Lines 352-357    Link Here 
352
			raise UnknownDeleteSetting("Don't know what to do with user_deletion=%r and expiration=%r.".format(
356
			raise UnknownDeleteSetting("Don't know what to do with user_deletion=%r and expiration=%r.".format(
353
				self.config["user_deletion"]["delete"], self.config["user_deletion"]["expiration"]),
357
				self.config["user_deletion"]["delete"], self.config["user_deletion"]["expiration"]),
354
				entry=user.entry_count, import_user=user)
358
				entry=user.entry_count, import_user=user)
359
		user.invalidate_all_caches()
355
		return success
360
		return success
356
361
357
	def log_stats(self):
362
	def log_stats(self):
 Lines 368-380    Link Here 
368
		for cls_name in sorted(cls_names):
373
		for cls_name in sorted(cls_names):
369
			self.logger.info("Created %s: %d", cls_name, len(self.added_users.get(cls_name, [])))
374
			self.logger.info("Created %s: %d", cls_name, len(self.added_users.get(cls_name, [])))
370
			for i in range(0, len(self.added_users[cls_name]), columns):
375
			for i in range(0, len(self.added_users[cls_name]), columns):
371
				self.logger.info("  %s", [iu.name for iu in self.added_users[cls_name][i:i+columns]])
376
				self.logger.info("  %s", [iu["name"] for iu in self.added_users[cls_name][i:i+columns]])
372
			self.logger.info("Modified %s: %d", cls_name, len(self.modified_users.get(cls_name, [])))
377
			self.logger.info("Modified %s: %d", cls_name, len(self.modified_users.get(cls_name, [])))
373
			for i in range(0, len(self.modified_users[cls_name]), columns):
378
			for i in range(0, len(self.modified_users[cls_name]), columns):
374
				self.logger.info("  %s", [iu.name for iu in self.modified_users[cls_name][i:i+columns]])
379
				self.logger.info("  %s", [iu["name"] for iu in self.modified_users[cls_name][i:i+columns]])
375
			self.logger.info("Deleted %s: %d", cls_name, len(self.deleted_users.get(cls_name, [])))
380
			self.logger.info("Deleted %s: %d", cls_name, len(self.deleted_users.get(cls_name, [])))
376
			for i in range(0, len(self.deleted_users[cls_name]), columns):
381
			for i in range(0, len(self.deleted_users[cls_name]), columns):
377
				self.logger.info("  %s", [iu.name for iu in self.deleted_users[cls_name][i:i+columns]])
382
				self.logger.info("  %s", [iu["name"] for iu in self.deleted_users[cls_name][i:i+columns]])
378
		self.logger.info("Errors: %d", len(self.errors))
383
		self.logger.info("Errors: %d", len(self.errors))
379
		if self.errors:
384
		if self.errors:
380
			self.logger.info("Entry #: Error description")
385
			self.logger.info("Entry #: Error description")
(-)modules/ucsschool/importer/models/import_user.py (-14 / +47 lines)
 Lines 80-85    Link Here 
80
	reader = None
80
	reader = None
81
	logger = None
81
	logger = None
82
	_pyhook_cache = None
82
	_pyhook_cache = None
83
	_additional_props = ("action", "entry_count", "udm_properties", "input_data", "old_user", "in_hook")
84
	prop = uadmin_property("_replace")
83
85
84
	def __init__(self, name=None, school=None, **kwargs):
86
	def __init__(self, name=None, school=None, **kwargs):
85
		self.action = None            # "A", "D" or "M"
87
		self.action = None            # "A", "D" or "M"
 Lines 88-100    Link Here 
88
		self.input_data = list()      # raw input data created by SomeReader.read()
90
		self.input_data = list()      # raw input data created by SomeReader.read()
89
		self.old_user = None          # user in LDAP, when modifying
91
		self.old_user = None          # user in LDAP, when modifying
90
		self.in_hook = False          # if a hook is currently running
92
		self.in_hook = False          # if a hook is currently running
93
94
		for attr in self._additional_props:
95
			try:
96
				val = kwargs.pop(attr)
97
				setattr(self, attr, val)
98
			except KeyError:
99
				pass
100
91
		if not self.factory:
101
		if not self.factory:
92
			self.factory = Factory()
102
			self.__class__.factory = Factory()
93
			self.ucr = self.factory.make_ucr()
103
			self.__class__.ucr = self.factory.make_ucr()
94
			self.config = Configuration()
104
			self.__class__.config = Configuration()
95
			self.reader = self.factory.make_reader()
105
			self.__class__.reader = self.factory.make_reader()
96
			self.logger = get_logger()
106
			self.__class__.logger = get_logger()
97
			self.username_max_length = 20 - len(self.ucr.get("ucsschool/ldap/default/userprefix/exam", "exam-"))
107
			self.__class__.username_max_length = 20 - len(self.ucr.get("ucsschool/ldap/default/userprefix/exam", "exam-"))
98
		self._lo = None
108
		self._lo = None
99
		self._userexpiry = None
109
		self._userexpiry = None
100
		super(ImportUser, self).__init__(name, school, **kwargs)
110
		super(ImportUser, self).__init__(name, school, **kwargs)
 Lines 186-191    Link Here 
186
		"""
196
		"""
187
		self._userexpiry = expiry
197
		self._userexpiry = expiry
188
198
199
	@classmethod
200
	def from_dict(cls, a_dict):
201
		if "Staff" in a_dict["type_name"]:
202
			if "Teacher" in a_dict["type_name"]:
203
				kls = ImportTeachersAndStaff
204
			else:
205
				kls = ImportStaff
206
		elif "Student" in a_dict["type_name"]:
207
			kls = ImportStudent
208
		elif "Teacher" in a_dict["type_name"]:
209
			kls = ImportTeacher
210
		else:
211
			kls = cls
212
		a_dict.pop("$dn$", None)
213
		a_dict.pop("type", None)
214
		a_dict.pop("type_name", None)
215
		return kls(**a_dict)
216
189
	def _alter_udm_obj(self, udm_obj):
217
	def _alter_udm_obj(self, udm_obj):
190
		super(ImportUser, self)._alter_udm_obj(udm_obj)
218
		super(ImportUser, self)._alter_udm_obj(udm_obj)
191
		if self._userexpiry is not None:
219
		if self._userexpiry is not None:
 Lines 479-485    Link Here 
479
			raise FormatError("No username was created from scheme '{}'.".format(
507
			raise FormatError("No username was created from scheme '{}'.".format(
480
				self.username_scheme), self.username_scheme, self.to_dict())
508
				self.username_scheme), self.username_scheme, self.to_dict())
481
		if not self.username_handler:
509
		if not self.username_handler:
482
			self.username_handler = self.factory.make_username_handler(self.username_max_length)
510
			self.__class__.username_handler = self.factory.make_username_handler(self.username_max_length)
483
		self.name = self.username_handler.format_username(self.name)
511
		self.name = self.username_handler.format_username(self.name)
484
512
485
	def modify(self, lo, validate=True, move_if_necessary=None):
513
	def modify(self, lo, validate=True, move_if_necessary=None):
 Lines 507-514    Link Here 
507
		self._lo = lo
535
		self._lo = lo
508
		return super(ImportUser, self).move(lo, udm_obj, force)
536
		return super(ImportUser, self).move(lo, udm_obj, force)
509
537
510
	@staticmethod
538
	@classmethod
511
	def normalize(s):
539
	def normalize(cls, s):
512
		"""
540
		"""
513
		Normalize string (german umlauts etc)
541
		Normalize string (german umlauts etc)
514
542
 Lines 516-523    Link Here 
516
		:return: str: normalized s
544
		:return: str: normalized s
517
		"""
545
		"""
518
		if isinstance(s, basestring):
546
		if isinstance(s, basestring):
519
			prop = uadmin_property("_replace")
547
			s = cls.prop._replace("<:umlauts>{}".format(s), {})
520
			s = prop._replace("<:umlauts>{}".format(s), {})
521
		return s
548
		return s
522
549
523
	def normalize_udm_properties(self):
550
	def normalize_udm_properties(self):
 Lines 665-676    Link Here 
665
			all_fields = self.reader.get_data_mapping(self.input_data)
692
			all_fields = self.reader.get_data_mapping(self.input_data)
666
		else:
693
		else:
667
			all_fields = dict()
694
			all_fields = dict()
668
		all_fields.update(self.to_dict().copy())
695
		all_fields.update(self.to_dict())
669
		all_fields.update(self.udm_properties)
696
		all_fields.update(self.udm_properties)
670
		all_fields.update(kwargs)
697
		all_fields.update(kwargs)
671
698
672
		prop = uadmin_property("_replace")
699
		res = self.prop._replace(scheme, all_fields)
673
		res = prop._replace(scheme, all_fields)
674
		if not res:
700
		if not res:
675
			self.logger.warn("Created empty '{prop_name}' from scheme '{scheme}' and input data {data}. ".format(
701
			self.logger.warn("Created empty '{prop_name}' from scheme '{scheme}' and input data {data}. ".format(
676
				prop_name=prop_name, scheme=scheme, data=all_fields))
702
				prop_name=prop_name, scheme=scheme, data=all_fields))
 Lines 755-760    Link Here 
755
			raise UnknownProperty("UDM properties could not be set. Unknown property: '{}'".format(exc),
781
			raise UnknownProperty("UDM properties could not be set. Unknown property: '{}'".format(exc),
756
				entry=self.entry_count, import_user = self)
782
				entry=self.entry_count, import_user = self)
757
783
784
	def to_dict_all(self):
785
		res = self.to_dict()
786
		for attr in self._additional_props:
787
			res[attr] = getattr(self, attr)
788
		self.logger.debug("res=%r", res)
789
		return res
790
758
	def update(self, other):
791
	def update(self, other):
759
		"""
792
		"""
760
		Copy attributes of other ImportUser into this one.
793
		Copy attributes of other ImportUser into this one.
(-)modules/ucsschool/importer/writer/user_import_csv_result_exporter.py (-1 / +4 lines)
 Lines 72-78    Link Here 
72
		for users in [user_import.added_users, user_import.modified_users, user_import.deleted_users]:
72
		for users in [user_import.added_users, user_import.modified_users, user_import.deleted_users]:
73
			tmp = list()
73
			tmp = list()
74
			map(tmp.extend, [u for u in users.values() if u])
74
			map(tmp.extend, [u for u in users.values() if u])
75
			li.extend(sorted(tmp, key=lambda x: x.entry_count))
75
			li.extend(tmp)
76
		return li
76
		return li
77
77
78
	def get_writer(self):
78
	def get_writer(self):
 Lines 95-100    Link Here 
95
			user = obj
95
			user = obj
96
		elif isinstance(obj, UcsSchoolImportError):
96
		elif isinstance(obj, UcsSchoolImportError):
97
			user = obj.import_user
97
			user = obj.import_user
98
		elif isinstance(obj, dict):
99
			user = self.factory.make_import_user([]).from_dict(obj)
100
			obj = user
98
		else:
101
		else:
99
			raise TypeError("Expected ImportUser or UcsSchoolImportError, got {}. Repr: {}".format(type(obj), repr(obj)))
102
			raise TypeError("Expected ImportUser or UcsSchoolImportError, got {}. Repr: {}".format(type(obj), repr(obj)))
100
		if not user:
103
		if not user:

Return to bug 41882