def delete_node(self, obj): """ Удаление группы справочника. Нельзя удалять группу если у нее есть подгруппы, или если в ней есть элементы. Но даже после этого удалять группу можно только прямым запросом, т.к. мы не знаем заранее, кто на нее может ссылаться и кого зацепит каскадное удаление джанги. """ message = '' if obj is None: message = u'Группа не существует в базе данных.' elif self.tree_model.objects.filter(**{ self.tree_parent_field: obj }).exists(): message = u'Нельзя удалить группу содержащую в себе другие группы.' elif self.list_model and self.list_model.objects.filter( **{ self.list_parent_field: obj }).exists(): message = u'Нельзя удалить группу содержащую в себе элементы.' elif not safe_delete(obj): message = ( u'Не удалось удалить группу. Возможно на неё есть ссылки.') return OperationResult.by_message(message)
def run(self, request, context): try: if not context.id and self.parent.add_window: obj = utils.bind_request_form_to_object( request, self.parent.get_row, self.parent.add_window) else: obj = utils.bind_request_form_to_object( request, self.parent.get_row, self.parent.edit_window) except self.parent._nofound_exception: return OperationResult.by_message(MSG_DOESNOTEXISTS % context.id) # Проверка корректности полей сохраняемого объекта result = self.parent.validate_row(obj, request) if result: assert isinstance(result, ActionResult) return result result = self.parent.save_row(obj) if isinstance(result, OperationResult) and result.success is True: # узкое место. после того, как мы переделаем работу экшенов, # имя параметра с идентификатором запси может уже называться не # id if 'm3_audit' in settings.INSTALLED_APPS: AuditManager().write( 'dict-changes', user=request.user, model_object=obj, type='new' if not context.id else 'edit') context.id = obj.id return result
def run(self, request, context): try: if not context.id and self.parent.add_window: obj = utils.bind_request_form_to_object( request, self.parent.get_row, self.parent.add_window) else: obj = utils.bind_request_form_to_object( request, self.parent.get_row, self.parent.edit_window) except self.parent._nofound_exception: return OperationResult.by_message(MSG_DOESNOTEXISTS % context.id) # Проверка корректности полей сохраняемого объекта result = self.parent.validate_row(obj, request) if result: assert isinstance(result, ActionResult) return result result = self.parent.save_row(obj) if isinstance(result, OperationResult) and result.success is True: # узкое место. после того, как мы переделаем работу экшенов, # имя параметра с идентификатором запси может уже называться не # id if 'm3_audit' in settings.INSTALLED_APPS: AuditManager().write('dict-changes', user=request.user, model_object=obj, type='new' if not context.id else 'edit') context.id = obj.id return result
def run(self, request, context): try: result = self.parent.get_row(context.id) except self.parent._nofound_exception: return OperationResult.by_message(MSG_DOESNOTEXISTS % context.id) return PreJsonResult(result)
def delete_row(self, objs): # Такая реализация обусловлена тем, # что IntegrityError невозможно отловить # до завершения транзакции, и приходится оборачивать транзакцию. @transaction.commit_on_success def delete_row_in_transaction(self, objs): message = '' if len(objs) == 0: message = u'Элемент не существует в базе данных.' else: for obj in objs: if ( isinstance(obj, BaseObjectModel) or (hasattr(obj, 'safe_delete') and callable(obj.safe_delete)) ): try: obj.safe_delete() except RelatedError, e: message = e.args[0] else: if not safe_delete(obj): message = ( u'Не удалось удалить элемент %s. ' u'Возможно на него есть ссылки.' ) % obj.id break return OperationResult.by_message(message)
def run(self, request, context): base = self.parent is_get_data = context.isGetData # Получаем объект по id try: obj = base.get_row(context.id) except base._nofound_exception: return OperationResult.by_message(MSG_DOESNOTEXISTS % context.id) # Разница между новым и созданным объектов в том, # что у нового нет id или он пустой create_new = True if isinstance(obj, dict) and obj.get('id') is not None: create_new = False elif hasattr(obj, 'id') and getattr(obj, 'id') is not None: create_new = False if create_new and base.add_window: win = utils.bind_object_from_request_to_form( request, base.get_row, base.add_window) else: win = utils.bind_object_from_request_to_form( request, base.get_row, base.edit_window) if not win.title: win.title = base.title win.form.url = base.save_action.get_absolute_url() # укажем адрес для чтения данных win.data_url = base.edit_window_action.get_absolute_url() # проверим право редактирования if not self.parent.has_sub_permission(request.user, self.parent.PERM_EDIT, request): exclude_list = ['close_btn', 'cancel_btn'] win.make_read_only(True, exclude_list) # У окна может быть процедура доп. конфигурации # под конкретный справочник if (hasattr(win, 'configure_for_dictpack') and callable(win.configure_for_dictpack)): win.configure_for_dictpack(action=self, pack=self.parent, request=request, context=context) if not is_get_data: # если запрашивали не данные - вернем окно return ExtUIScriptResult(base.get_edit_window(win)) else: # если просили данные, то выжмем их из окна обратно в объект, # т.к. в окне могли быть и другие данных (не из этого объекта) data_object = {} # т.к. мы не знаем какие поля должны быть у объекта - создадим # все, которые есть на форме all_fields = win.form._get_all_fields(win) for field in all_fields: data_object[field.name] = None win.form.to_object(data_object) return PreJsonResult({'success': True, 'data': data_object})
def run(self, request, context): base = self.parent id = getattr(context, base.contextTreeIdName) try: obj = self.parent.get_node(id) except self.parent._group_nofound_exception: return OperationResult.by_message(MSG_DOESNOTEXISTS % id) return self.parent.delete_node(obj)
def run(self, request, context): base = self.parent is_get_data = context.isGetData # Получаем объект по id try: obj = base.get_row(context.id) except base._nofound_exception: return OperationResult.by_message(MSG_DOESNOTEXISTS % context.id) # Разница между новым и созданным объектов в том, # что у нового нет id или он пустой create_new = True if isinstance(obj, dict) and obj.get('id') is not None: create_new = False elif hasattr(obj, 'id') and getattr(obj, 'id') is not None: create_new = False if create_new and base.add_window: win = utils.bind_object_from_request_to_form( request, base.get_row, base.add_window) else: win = utils.bind_object_from_request_to_form( request, base.get_row, base.edit_window) if not win.title: win.title = base.title win.form.url = base.save_action.get_absolute_url() # укажем адрес для чтения данных win.data_url = base.edit_window_action.get_absolute_url() # проверим право редактирования if not self.parent.has_sub_permission( request.user, self.parent.PERM_EDIT, request): exclude_list = ['close_btn', 'cancel_btn'] win.make_read_only(True, exclude_list) # У окна может быть процедура доп. конфигурации # под конкретный справочник if (hasattr(win, 'configure_for_dictpack') and callable(win.configure_for_dictpack)): win.configure_for_dictpack(action=self, pack=self.parent, request=request, context=context) if not is_get_data: # если запрашивали не данные - вернем окно return ExtUIScriptResult(base.get_edit_window(win)) else: # если просили данные, то выжмем их из окна обратно в объект, # т.к. в окне могли быть и другие данных (не из этого объекта) data_object = {} # т.к. мы не знаем какие поля должны быть у объекта - создадим # все, которые есть на форме all_fields = win.form._get_all_fields(win) for field in all_fields: data_object[field.name] = None win.form.to_object(data_object) return PreJsonResult({'success': True, 'data': data_object})
def run(self, request, context): try: obj = utils.bind_request_form_to_object(request, self.parent.get_row, self.parent.edit_window) except self.parent._nofound_exception: return OperationResult.by_message(MSG_DOESNOTEXISTS % context.id) result = self.parent.validate_row(obj, request) if result: assert isinstance(result, ActionResult) return result return self.parent.save_row(obj)
def drag_item(self, ids, dest_id): # В корень нельзя кидать простые элементы if dest_id < 1: return OperationResult.by_message( u'Нельзя перемещать элементы в корень справочника!') # Из грида в дерево одновременно # могут быть перенесены несколько элементов # Их id разделены запятыми for id in ids: row = self.get_row(id) row.parent_id = dest_id row.save() return OperationResult()
def run(self, request, context): # Создаем форму для биндинга к ней win = self.parent.edit_node_window() win.form.bind_to_request(request) # Получаем наш объект по id try: obj = self.parent.get_node(context.id) except self.parent._group_nofound_exception: return OperationResult.by_message(MSG_DOESNOTEXISTS % context.id) # Биндим форму к объекту win.form.to_object(obj) result = self.parent.validate_node(obj, request) if result: assert isinstance(result, ActionResult) return result return self.parent.save_node(obj)
def run(self, request, context): """ Удаляться одновременно могут несколько объектов. Их ключи приходят разделенные запятыми. """ ids = utils.extract_int_list(request, 'id') try: objs = [self.parent.get_row(id) for id in ids] except self.parent._nofound_exception: return OperationResult.by_message(MSG_DOESNOTEXISTS % id) result = self.parent.delete_row(objs) if (isinstance(result, OperationResult) and result.success is True and 'm3_audit' in settings.INSTALLED_APPS): for obj in objs: AuditManager().write('dict-changes', user=request.user, model_object=obj, type='delete') return result
def delete_node(self, obj): """ Удаление группы справочника. Нельзя удалять группу если у нее есть подгруппы, или если в ней есть элементы. Но даже после этого удалять группу можно только прямым запросом, т.к. мы не знаем заранее, кто на нее может ссылаться и кого зацепит каскадное удаление джанги. """ message = '' if obj is None: message = u'Группа не существует в базе данных.' elif self.tree_model.objects.filter( **{self.tree_parent_field: obj}).exists(): message = u'Нельзя удалить группу содержащую в себе другие группы.' elif self.list_model and self.list_model.objects.filter( **{self.list_parent_field: obj}).exists(): message = u'Нельзя удалить группу содержащую в себе элементы.' elif not safe_delete(obj): message = ( u'Не удалось удалить группу. Возможно на неё есть ссылки.') return OperationResult.by_message(message)
def run(self, request, context): """ Удаляться одновременно могут несколько объектов. Их ключи приходят разделенные запятыми. """ ids = utils.extract_int_list(request, 'id') try: objs = [self.parent.get_row(id) for id in ids] except self.parent._nofound_exception: return OperationResult.by_message(MSG_DOESNOTEXISTS % id) result = self.parent.delete_row(objs) if (isinstance(result, OperationResult) and result.success is True and 'm3_audit' in settings.INSTALLED_APPS): for obj in objs: AuditManager().write( 'dict-changes', user=request.user, model_object=obj, type='delete') return result
def run(self, request, context): base = self.parent try: win = utils.bind_object_from_request_to_form(request, base.get_row, base.edit_window) except self.parent._nofound_exception: return OperationResult.by_message(MSG_DOESNOTEXISTS % context.id) if not win.title: win.title = base.title win.form.url = base.save_row_action.get_absolute_url() # проверим право редактирования if not self.parent.has_perm(request, self.parent.PERM_EDIT): win.make_read_only( access_off=True, exclude_list=['cancel_btn', 'close_btn']) # У окна может быть процедура доп. конфигурации под конкретный справочник if hasattr(win, 'configure_for_dictpack') and callable(win.configure_for_dictpack): win.configure_for_dictpack(action=self, pack=self.parent, request=request, context=context) return ExtUIScriptResult(base.get_edit_window(win))
class BaseTreeDictionaryModelActions(BaseTreeDictionaryActions): """ Класс реализует действия над иерархическим справочником, основанном на моделе """ # Признак возвращения всех узлов дерева ALL_ROWS = -1 # Поля для поиска по умолчанию. DEFAULT_FILTER_FIELDS = ['code', 'name'] # Настройки для модели дерева tree_model = None # Сама модель дерева tree_filter_fields = [] # Поля по которым производится поиск в дереве tree_columns = [] # Список из кортежей с параметрами выводимых в дерево колонок tree_parent_field = 'parent' # Имя поля ссылающегося на группу tree_readonly = False # Если истина, то адреса экшенов дереву не назначаются tree_order_field = '' tree_drag_and_drop = True # Разрешает перетаскивание внутри дерева # Настройки модели списка list_model = None # Не обязательная модель списка связанного с деревом list_columns = [] # Список из кортежей с параметрами выводимых в грид колонок filter_fields = [] # Поля по которым производится поиск в списке list_parent_field = 'parent' # Имя поля ссылающегося на группу list_readonly = False # Если истина, то адреса экшенов гриду не назначаются list_drag_and_drop = True # Разрешает перетаскивание элементов из грида в другие группы дерева list_order_field = '' list_paging = True # Порядок сортировки элементов списка. Работает следующим образом: # 1. Если в list_columns модели списка есть поле code, # то устанавливается сортировка по возрастанию этого поля; # 2. Если в list_columns модели списка нет поля code, но есть поле name, # то устанавливается сортировка по возрастанию поля name; # Пример list_sort_order = ['code', '-name'] list_sort_order = [] tree_sort_order = None def __init__(self): super(BaseTreeDictionaryModelActions, self).__init__() # Установка значений по умолчанию для поиска и сортировки if self.list_model: self.filter_fields = self._default_list_search_filter() self.list_sort_order = self._default_order() self._nofound_exception = self.list_model.DoesNotExist if self.tree_model: self.tree_filter_fields = self._default_tree_search_filter() self._group_nofound_exception = self.tree_model.DoesNotExist def get_nodes(self, parent_id, filter, branch_id = None): """ Метод получения списка узлов дерева, которые """ # parent_id - это элемент, который раскрывается, # поэтому для него фильтр ставить не надо, иначе фильтруем # branch_id - это элемент ограничивающий дерево, # т.е. должны возвращаться только дочерние ему элементы if filter and not parent_id: filter_dict = utils.create_search_filter( filter, self.tree_filter_fields) nodes = utils.fetch_search_tree( self.tree_model, filter_dict, branch_id) else: if branch_id and hasattr(self.tree_model, 'get_descendants'): branch_node = self.tree_model.objects.get(id=branch_id) if parent_id: query = branch_node.get_descendants().filter( parent=parent_id) else: query = branch_node.get_children() else: query = self.tree_model.objects.filter(parent=parent_id) query = utils.apply_sort_order( query, self.tree_columns, self.tree_sort_order) nodes = list(query) # Если имеем дело с листом, нужно передавать параметр leaf = true for node in nodes: if not self.tree_model.objects.filter(parent=node.id).exists(): node.leaf = 'true' # генерируем сигнал о том, что узлы дерева подготовлены nodes_prepared.send(sender=self.__class__, nodes=nodes) return nodes def get_rows(self, parent_id, offset, limit, filter): # если справочник состоит только из дерева и у него просят запись, # то надо брать из модели дерева # TODO: возможно это не надо было делать - раз не туда обратились, # значит сами виноваты if self.list_model: query = None if parent_id == BaseTreeDictionaryModelActions.ALL_ROWS: # отображаются все данные query = self.list_model.objects else: # отображаются данные с фильтрацией по значению parent_id query = self.list_model.objects.filter( **{self.list_parent_field: parent_id}) # Подтягиваем группу, т.к. при сериализации она требуется query = query.select_related(self.list_parent_field) query = utils.apply_sort_order( query, self.list_columns, self.list_sort_order) query = utils.apply_search_filter( query, filter, self.filter_fields) # Для работы пейджинга нужно передавать общее количество записей total = query.count() # Срез данных для страницы if limit > 0: query = query[offset: offset + limit] result = {'rows': list(query.all()), 'total': total} return result else: return self.get_nodes(parent_id, filter) def _get_model_fieldnames(self, model): """ Возвращает имена всех полей модели """ return [field.attname for field in model._meta.local_fields] def _default_order(self): """ Устанавливаем параметры сортировки по умолчанию 'code' и 'name' в случае, если у модели есть такие поля """ filter_order = self.list_sort_order if not filter_order: filter_order = [] all_fields = self._get_model_fieldnames(self.list_model) filter_order.extend([ field for field in ('code', 'name', 'id') if field in all_fields ]) return filter_order def _default_tree_search_filter(self): """ Если поля для поиска не заданы, то возвращает список из полей модели по которым будет производиться поиск. По умолчанию берутся код и наименование """ if not self.tree_filter_fields: assert self.tree_model, 'Tree model is not defined!' for field_name in self._get_model_fieldnames(self.tree_model): if field_name in self.DEFAULT_FILTER_FIELDS: self.tree_filter_fields.append(field_name) return self.tree_filter_fields def _default_list_search_filter(self): """ Если поля для поиска не заданы, то возвращает список из полей модели по которым будет производиться поиск. По умолчанию берутся код и наименование """ if not self.filter_fields: assert self.list_model, 'List model is not defined!' for field_name in self._get_model_fieldnames(self.list_model): if field_name in self.DEFAULT_FILTER_FIELDS: self.filter_fields.append(field_name) return self.filter_fields def get_nodes_like_rows(self, filter, branch_id=None): """ Возвращаются узлы дерева, предствленные в виде общего списка """ # branch_id - это элемент ограничивающий дерево, # т.е. должны возвращаться только дочерние ему элементы if filter: filter_dict = utils.create_search_filter( filter, self.tree_filter_fields) if branch_id and hasattr(self.tree_model, 'get_descendants'): branch_node = self.tree_model.objects.get(id=branch_id) nodes = branch_node.get_descendants().filter( filter_dict).select_related('parent') else: nodes = self.tree_model.objects.filter( filter_dict).select_related('parent') else: if branch_id and hasattr(self.tree_model, 'get_descendants'): branch_node = self.tree_model.objects.get(id=branch_id) nodes = branch_node.get_descendants() else: nodes = self.tree_model.objects.all() # Для работы пейджинга нужно передавать общее количество записей total = len(nodes) result = {'rows': list(nodes), 'total': total} return result def _get_obj(self, model, id): """ Возвращает запись заданной модели model по id Если id нет, значит нужно создать новый объект """ assert isinstance(id, int) if id == 0: obj = model() else: obj = model.objects.get(id=id) return obj def get_node(self, id=0): return self._get_obj(self.tree_model, id) def get_row(self, id=0): # если справочник состоит только из дерева и у него просят запись, # то надо брать из модели дерева # TODO: это надо было для элемента выбора из справочника # - он не знает откуда ему взять запись и всегда вызывает get_row. # может надо было как-то по-другому это решить if self.list_model: return self._get_obj(self.list_model, id) else: return self.get_node(id) def save_row(self, obj): obj.save() return OperationResult(success=True) def save_node(self, obj): obj.save() return OperationResult(success=True) def delete_row(self, objs): # Такая реализация обусловлена тем, # что IntegrityError невозможно отловить # до завершения транзакции, и приходится оборачивать транзакцию. @transaction.commit_on_success def delete_row_in_transaction(self, objs): message = '' if len(objs) == 0: message = u'Элемент не существует в базе данных.' else: for obj in objs: if ( isinstance(obj, BaseObjectModel) or (hasattr(obj, 'safe_delete') and callable(obj.safe_delete)) ): try: obj.safe_delete() except RelatedError, e: message = e.args[0] else: if not safe_delete(obj): message = ( u'Не удалось удалить элемент %s. ' u'Возможно на него есть ссылки.' ) % obj.id break return OperationResult.by_message(message) # Тут пытаемся поймать ошибку из транзакции. try: return delete_row_in_transaction(self, objs) except Exception, e: # Встроенный в Django IntegrityError не генерируется. # Кидаются исключения специфичные для каждого драйвера БД. # Но по спецификации PEP 249 все они называются IntegrityError if e.__class__.__name__ == 'IntegrityError': message = ( u'Не удалось удалить элемент. ' u'Возможно на него есть ссылки.' ) return OperationResult.by_message(message) else: # все левые ошибки выпускаем наверх raise
class BaseDictionaryModelActions(BaseDictionaryActions): """ Класс, который реализует действия со справочником, записи которого являются моделями. """ # Настройки вида справочника (задаются конечным разработчиком) model = None # Список полей модели по которым будет идти поиск filter_fields = [] # Порядок сортировки элементов списка. Работает следующим образом: # 1. Если в list_columns модели списка есть поле # code, то устанавливается сортировка по возрастанию этого поля; # 2. Если в list_columns модели списка нет поля code, но # есть поле name, то устанавливается сортировка по возрастанию поля name; # Пример list_sort_order = ['code', '-name'] list_sort_order = None def __init__(self): super(BaseDictionaryModelActions, self).__init__() if self.model: self._nofound_exception = self.model.DoesNotExist def get_rows_modified(self, offset, limit, filter, user_sort='', request=None, context=None): ''' Возвращает данные для грида справочника ''' sort_order = user_sort.split( ',') if user_sort else self.list_sort_order filter_fields = self._default_filter() query = self.model.objects.all() query = utils.apply_sort_order(query, self.list_columns, sort_order) query = utils.apply_search_filter(query, filter, filter_fields) if (hasattr(self, 'modify_rows_query') and callable(self.modify_rows_query)): query = self.modify_rows_query(query, request, context) total = query.count() if limit > 0: query = query[offset:offset + limit] result = {'rows': list(query), 'total': total} return result def get_rows(self, offset, limit, filter, user_sort=''): sort_order = user_sort.split( ',') if user_sort else self.list_sort_order filter_fields = self._default_filter() query = utils.apply_sort_order(self.model.objects, self.list_columns, sort_order) query = utils.apply_search_filter(query, filter, filter_fields) total = query.count() if limit > 0: query = query[offset:offset + limit] result = {'rows': list(query.all()), 'total': total} return result # def modify_rows_query(self, query, request, context): # ''' # Модифицирует запрос на получение данных. # Данный метод необходимо определить в # дочерних классах. # ''' # return query def get_row(self, id): assert isinstance(id, int) # Если id нет, значит нужно создать новый объект if id == 0: record = self.model() else: record = self.model.objects.get(id=id) return record @transaction.commit_on_success def save_row(self, obj): obj.save() return OperationResult(success=True) def delete_row(self, objs): # Такая реализация обусловлена тем, # что IntegrityError невозможно отловить # до завершения транзакции, и приходится оборачивать транзакцию. @transaction.commit_on_success def delete_row_in_transaction(self, objs): message = '' if len(objs) == 0: message = u'Элемент не существует в базе данных.' else: for obj in objs: if (isinstance(obj, BaseObjectModel) or (hasattr(obj, 'safe_delete') and callable(obj.safe_delete))): try: obj.safe_delete() except RelatedError, e: message = e.args[0] else: if not safe_delete(obj): message = (u'Не удалось удалить элемент %s. ' u'Возможно на него есть ссылки.' % obj.id) break return OperationResult.by_message(message) # Тут пытаемся поймать ошибку из транзакции. try: return delete_row_in_transaction(self, objs) except Exception, e: # Встроенный в Django IntegrityError # не генерируется. Кидаются исключения # специфичные для каждого драйвера БД. # Но по спецификации PEP 249 все они # называются IntegrityError if e.__class__.__name__ == 'IntegrityError': message = (u'Не удалось удалить элемент. ' u'Возможно на него есть ссылки.') return OperationResult.by_message(message) else: # все левые ошибки выпускаем наверх raise