Repository: interlegis/sapl

Issue:
Erro ao deletar uma norma com texto articulado
Traceback:

Environment:


Request Method: POST
Request URL: http://192.168.31.77/norma/49/delete

Django Version: 1.9.13
Python Version: 3.5.2
Installed Applications:
('django_admin_bootstrapped',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django_extensions',
 'djangobower',
 'bootstrap3',
 'crispy_forms',
 'easy_thumbnails',
 'image_cropping',
 'floppyforms',
 'haystack',
 'sass_processor',
 'rest_framework',
 'reversion',
 'reversion_compare',
 'whoosh',
 'speedinfo',
 'sapl.audiencia',
 'sapl.base',
 'sapl.crud',
 'sapl.parlamentares',
 'sapl.comissoes',
 'sapl.materia',
 'sapl.norma',
 'sapl.sessao',
 'sapl.lexml',
 'sapl.painel',
 'sapl.protocoloadm',
 'sapl.redireciona_urls',
 'sapl.compilacao',
 'sapl.api',
 'sapl.rules',
 'debug_toolbar',
 'rest_framework_docs')
Installed Middleware:
('debug_toolbar.middleware.DebugToolbarMiddleware',
 'reversion.middleware.RevisionMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.locale.LocaleMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'django.middleware.security.SecurityMiddleware')



Traceback:

File "/var/interlegis/.virtualenvs/sapl/lib/python3.5/site-packages/django/core/handlers/base.py" in get_response
  149.                     response = self.process_exception_by_middleware(e, request)

File "/var/interlegis/.virtualenvs/sapl/lib/python3.5/site-packages/django/core/handlers/base.py" in get_response
  147.                     response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/var/interlegis/.virtualenvs/sapl/lib/python3.5/site-packages/django/views/generic/base.py" in view
  68.             return self.dispatch(request, *args, **kwargs)

File "/var/interlegis/sapl/sapl/crud/base.py" in dispatch
  229.             request, *args, **kwargs)

File "/var/interlegis/.virtualenvs/sapl/lib/python3.5/site-packages/django/views/generic/base.py" in dispatch
  88.         return handler(request, *args, **kwargs)

File "/var/interlegis/.virtualenvs/sapl/lib/python3.5/site-packages/django/views/generic/edit.py" in post
  308.         return self.delete(request, *args, **kwargs)

File "/var/interlegis/sapl/sapl/crud/base.py" in delete
  848.             return super(CrudDeleteView, self).delete(request, args, kwargs)

File "/var/interlegis/.virtualenvs/sapl/lib/python3.5/site-packages/braces/views/_forms.py" in delete
  134.         response = super(FormValidMessageMixin, self).delete(*args, **kwargs)

File "/var/interlegis/.virtualenvs/sapl/lib/python3.5/site-packages/django/views/generic/edit.py" in delete
  303.         self.object.delete()

File "/var/interlegis/sapl/sapl/norma/models.py" in delete
  155.             self, using=using, keep_parents=keep_parents)

File "/var/interlegis/.virtualenvs/sapl/lib/python3.5/site-packages/django/db/models/base.py" in delete
  870.         return collector.delete()

File "/var/interlegis/.virtualenvs/sapl/lib/python3.5/site-packages/django/db/models/deletion.py" in delete
  287.                         sender=model, instance=obj, using=self.using

File "/var/interlegis/.virtualenvs/sapl/lib/python3.5/site-packages/django/dispatch/dispatcher.py" in send
  192.             response = receiver(signal=self, sender=sender, **named)

File "/var/interlegis/sapl/sapl/rules/apps.py" in revision_pre_delete_signal
  242.         kwargs['instance'].save()

File "/var/interlegis/sapl/sapl/compilacao/models.py" in save
  1096.             update_fields=update_fields, clean=clean)

File "/var/interlegis/sapl/sapl/compilacao/models.py" in save
  73.             self.clean()

File "/var/interlegis/sapl/sapl/compilacao/models.py" in clean
  1083.                     raise ValidationError(msg)

Exception Type: ValidationError at /norma/49/delete
Exception Value: ['Dispositivo com este Texto Articulado, Número do Dispositivo, Primeiro Nível de Variação, Segundo Nível de Variação, Terceiro Nível de Variação, Quarto Nível de Variação, Quinto Nível de Variação, Tipo do Dispositivo, Contagem contínua e Dispositivo Raiz já existe.']

Tentando deletar o texto articulado na norma:
![image](https://user-images.githubusercontent.com/1524415/43170911-f7f066dc-8f7d-11e8-8698-c63c197aeee1.png)


Tentando deletar a norma:

![image](https://user-images.githubusercontent.com/1524415/43170882-ccebf096-8f7d-11e8-9f9e-a1a8198aa45f.png)

![image](https://user-images.githubusercontent.com/1524415/43170888-d3e13bd6-8f7d-11e8-97eb-9db29437061d.png)









Discussion:
consegui solucionar o problema alterando todos os campos que está alegando no debug, porém não faço ideia como que aconteceu esse erro .... , estava sendo alimentado normalmente quando o problema aconteceu.
@LeandroRoberto Tem ideia do que pode ter sido?
@edwardoliveira, to voltando de férias nessa quarta-feira, dia 1/8/2018, verificarei!

Resolving patch:
diff --git a/sapl/compilacao/models.py b/sapl/compilacao/models.py
--- a/sapl/compilacao/models.py
+++ b/sapl/compilacao/models.py
@@ -69,8 +69,15 @@ def clean(self):
 
     def save(self, force_insert=False, force_update=False, using=None,
              update_fields=None, clean=True):
-        if clean:
+        # método clean não pode ser chamado no caso do save que está sendo
+        # executado é o save de revision_pre_delete_signal
+        import inspect
+        funcs = list(filter(lambda x: x == 'revision_pre_delete_signal',
+                            map(lambda x: x[3], inspect.stack())))
+
+        if clean and not funcs:
             self.clean()
+
         return models.Model.save(
             self,
             force_insert=force_insert,
diff --git a/sapl/compilacao/views.py b/sapl/compilacao/views.py
--- a/sapl/compilacao/views.py
+++ b/sapl/compilacao/views.py
@@ -15,7 +15,7 @@
 from django.db.models import Q
 from django.db.utils import IntegrityError
 from django.http.response import (HttpResponse, HttpResponseRedirect,
-                                  JsonResponse)
+                                  JsonResponse, Http404)
 from django.shortcuts import get_object_or_404, redirect
 from django.utils.dateparse import parse_date
 from django.utils.encoding import force_text
@@ -51,7 +51,6 @@
 from sapl.crud.base import Crud, CrudAux, CrudListView, make_pagination
 from sapl.settings import BASE_DIR
 
-
 TipoNotaCrud = CrudAux.build(TipoNota, 'tipo_nota')
 TipoVideCrud = CrudAux.build(TipoVide, 'tipo_vide')
 TipoPublicacaoCrud = CrudAux.build(TipoPublicacao, 'tipo_publicacao')
@@ -104,7 +103,7 @@ def get_redirect_deactivated(self):
               ) % self.model._meta.verbose_name_plural)
         return redirect('/')
 
-    def get(self, request,  *args, **kwargs):
+    def get(self, request, *args, **kwargs):
 
         try:
             if settings.DEBUG or not TipoDispositivo.objects.exists():
@@ -211,8 +210,12 @@ def has_permission(self):
 
     @property
     def ta(self):
-        ta = TextoArticulado.objects.get(
-            pk=self.kwargs.get('ta_id', self.kwargs.get('pk', 0)))
+        try:
+            ta = TextoArticulado.objects.get(
+                pk=self.kwargs.get('ta_id', self.kwargs.get('pk', 0)))
+        except TextoArticulado.DoesNotExist:
+            raise Http404()
+
         return ta
 
     def get_context_data(self, **kwargs):
@@ -1237,9 +1240,9 @@ def dispositivos_list(self):
                 'filhos': [],
                 'alts': [],
                 'pai': None,
-                'st': None,                        # dispositivo substituido
-                'sq': None,                        # dispositivo subsequente
-                'da': None,                        # dispositivo atualizador
+                'st': None,  # dispositivo substituido
+                'sq': None,  # dispositivo subsequente
+                'da': None,  # dispositivo atualizador
                 'td': tds[d.tipo_dispositivo_id],  # tipo do dispositivo
                 'na': self.nota_alteracao(d, lista_ta_publicado)\
                 if d.ta_id == ta_id else None
@@ -1480,30 +1483,36 @@ def json_delete_bloco_dispositivo(self, context, bloco=True):
         ).first()
 
         data = {}
-        if base_anterior:
-            data = self.get_json_for_refresh(base_anterior)
-        else:
+        if not base_anterior or base == base.get_raiz():
             base_anterior = base.get_nivel_zero_anterior()
-            data = self.get_json_for_refresh(base_anterior)
-
-        data['pai'] = [base.get_raiz().pk]
+            if not base_anterior:
+                base_anterior = base
+        data = self.get_json_for_refresh(base_anterior)
+
+        if base == base_anterior:
+            data['pk'] = base.pk
+            self.set_message(data, 'danger', _(
+                'Base Inicial não pode ser removida!'), modal=True)
+        else:
+            if base != base.get_raiz():
+                data['pai'] = [base.get_raiz().pk]
 
-        if ta_base.id != int(self.kwargs['ta_id']):
-            data['pai'] = [base.dispositivo_atualizador.pk]
-            data['pk'] = base.dispositivo_atualizador.pk
+            if ta_base.id != int(self.kwargs['ta_id']):
+                data['pai'] = [base.dispositivo_atualizador.pk]
+                data['pk'] = base.dispositivo_atualizador.pk
 
-        try:
-            with transaction.atomic():
-                message = str(self.remover_dispositivo(base, bloco))
-                if message:
-                    self.set_message(data, 'warning', message, modal=True)
-                else:
-                    self.set_message(data, 'success', _(
-                        'Exclusão efetuada com sucesso!'), modal=True)
-                ta_base.reagrupar_ordem_de_dispositivos()
-        except Exception as e:
-            data['pk'] = self.kwargs['dispositivo_id']
-            self.set_message(data, 'danger', str(e), modal=True)
+            try:
+                with transaction.atomic():
+                    message = str(self.remover_dispositivo(base, bloco))
+                    if message:
+                        self.set_message(data, 'warning', message, modal=True)
+                    else:
+                        self.set_message(data, 'success', _(
+                            'Exclusão efetuada com sucesso!'), modal=True)
+                    ta_base.reagrupar_ordem_de_dispositivos()
+            except Exception as e:
+                data['pk'] = self.kwargs['dispositivo_id']
+                self.set_message(data, 'danger', str(e), modal=True)
 
         return data
 
diff --git a/sapl/rules/apps.py b/sapl/rules/apps.py
--- a/sapl/rules/apps.py
+++ b/sapl/rules/apps.py
@@ -1,15 +1,15 @@
 from builtins import LookupError
 
 import django
-import reversion
 from django.apps import apps
 from django.contrib.auth import get_user_model
 from django.contrib.auth.management import _get_all_permissions
 from django.core import exceptions
 from django.db import models, router
 from django.db.utils import DEFAULT_DB_ALIAS
-from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import string_concat
+from django.utils.translation import ugettext_lazy as _
+import reversion
 
 from sapl.rules import (SAPL_GROUP_ADMINISTRATIVO, SAPL_GROUP_COMISSOES,
                         SAPL_GROUP_GERAL, SAPL_GROUP_MATERIA, SAPL_GROUP_NORMA,


Test patch: