diff --git a/sapl/compilacao/migrations/0005_auto_20180319_1041.py b/sapl/compilacao/migrations/0005_auto_20180319_1041.py new file mode 100644 index 000000000..fdd69bc8a --- /dev/null +++ b/sapl/compilacao/migrations/0005_auto_20180319_1041.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-19 13:41 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +def adjust_dispositivo_raiz(apps, schema_editor): + Dispositivo = apps.get_model('compilacao', 'Dispositivo') + + articulacoes = Dispositivo.objects.filter( + dispositivo_pai__isnull=True) + + def adicionar_raiz_aos_filhos(raiz, dispositivo): + for d in dispositivo.dispositivos_filhos_set.all(): + d.dispositivo_raiz = raiz + d.save() + adicionar_raiz_aos_filhos(raiz, d) + + for artic in articulacoes: + adicionar_raiz_aos_filhos(artic, artic) + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0004_auto_20171031_1327'), + ] + + operations = [ + migrations.AddField( + model_name='dispositivo', + name='dispositivo_raiz', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, + related_name='nodes', to='compilacao.Dispositivo', verbose_name='Dispositivo Raiz'), + ), + migrations.AlterUniqueTogether( + name='dispositivo', + unique_together=set([('ta', 'dispositivo0', 'dispositivo1', 'dispositivo2', 'dispositivo3', 'dispositivo4', 'dispositivo5', + 'tipo_dispositivo', 'dispositivo_raiz', 'dispositivo_pai', 'dispositivo_atualizador', 'ta_publicado', 'publicacao'), ('ta', 'ordem')]), + ), + migrations.RunPython(adjust_dispositivo_raiz), + ] diff --git a/sapl/compilacao/migrations/0006_auto_20180321_1054.py b/sapl/compilacao/migrations/0006_auto_20180321_1054.py new file mode 100644 index 000000000..12c6bec3b --- /dev/null +++ b/sapl/compilacao/migrations/0006_auto_20180321_1054.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-21 13:54 +from __future__ import unicode_literals + +from django.db import migrations, models + + +def adjust_contagem_continua(apps, schema_editor): + Dispositivo = apps.get_model('compilacao', 'Dispositivo') + + Dispositivo.objects.filter( + tipo_dispositivo__contagem_continua=True + ).update(contagem_continua=True) + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0005_auto_20180319_1041'), + ] + + operations = [ + migrations.AddField( + model_name='dispositivo', + name='contagem_continua', + field=models.BooleanField(choices=[( + True, 'Sim'), (False, 'Não')], default=False, verbose_name='Contagem contínua'), + ), + migrations.AlterUniqueTogether( + name='dispositivo', + unique_together=set([('ta', 'ordem'), ('ta', 'dispositivo0', 'dispositivo1', 'dispositivo2', 'dispositivo3', 'dispositivo4', 'dispositivo5', 'tipo_dispositivo', 'contagem_continua', 'dispositivo_raiz', 'dispositivo_atualizador', 'ta_publicado', + 'publicacao'), ('ta', 'dispositivo0', 'dispositivo1', 'dispositivo2', 'dispositivo3', 'dispositivo4', 'dispositivo5', 'tipo_dispositivo', 'dispositivo_raiz', 'dispositivo_pai', 'dispositivo_atualizador', 'ta_publicado', 'publicacao')]), + ), + migrations.RunPython(adjust_contagem_continua), + ] diff --git a/sapl/compilacao/models.py b/sapl/compilacao/models.py index ad30cc23c..ac1939f80 100644 --- a/sapl/compilacao/models.py +++ b/sapl/compilacao/models.py @@ -1,5 +1,4 @@ -import reversion from django.contrib import messages from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType @@ -13,6 +12,7 @@ from django.utils import timezone from django.utils.decorators import classonlymethod from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ +import reversion from sapl.compilacao.utils import (get_integrations_view_names, int_to_letter, int_to_roman) @@ -978,6 +978,11 @@ class Dispositivo(BaseModel, TimestampedMixin): blank=True, null=True, default=None, related_name='dispositivos_filhos_set', verbose_name=_('Dispositivo Pai')) + dispositivo_raiz = models.ForeignKey( + 'self', + blank=True, null=True, default=None, + related_name='nodes', + verbose_name=_('Dispositivo Raiz')) dispositivo_vigencia = models.ForeignKey( 'self', blank=True, null=True, default=None, @@ -990,6 +995,10 @@ class Dispositivo(BaseModel, TimestampedMixin): related_name='dispositivos_alterados_set', verbose_name=_('Dispositivo Atualizador')) + contagem_continua = models.BooleanField( + default=False, + choices=YES_NO_CHOICES, verbose_name=_('Contagem contínua')) + class Meta: verbose_name = _('Dispositivo') verbose_name_plural = _('Dispositivos') @@ -1004,10 +1013,24 @@ class Dispositivo(BaseModel, TimestampedMixin): 'dispositivo4', 'dispositivo5', 'tipo_dispositivo', + 'dispositivo_raiz', 'dispositivo_pai', 'dispositivo_atualizador', 'ta_publicado', 'publicacao',), + ('ta', + 'dispositivo0', + 'dispositivo1', + 'dispositivo2', + 'dispositivo3', + 'dispositivo4', + 'dispositivo5', + 'tipo_dispositivo', + 'contagem_continua', + 'dispositivo_raiz', + 'dispositivo_atualizador', + 'ta_publicado', + 'publicacao',), ) permissions = ( ('change_dispositivo_edicao_dinamica', _( @@ -1027,10 +1050,58 @@ class Dispositivo(BaseModel, TimestampedMixin): 'Permissão alteração global do dispositivo de vigência')), ) + def clean(self): + """ + Check for instances with null values in unique_together fields. + """ + from django.core.exceptions import ValidationError + + for field_tuple in self._meta.unique_together[:]: + unique_filter = {} + unique_fields = [] + null_found = False + for field_name in field_tuple: + field_value = getattr(self, field_name) + if getattr(self, field_name) is None: + unique_filter['%s__isnull' % field_name] = True + null_found = True + else: + unique_filter['%s' % field_name] = field_value + unique_fields.append(field_name) + if null_found: + unique_queryset = self.__class__.objects.filter( + **unique_filter) + if self.pk: + unique_queryset = unique_queryset.exclude(pk=self.pk) + if not self.contagem_continua and \ + 'contagem_continua' in field_tuple: + continue + + if unique_queryset.exists(): + msg = self.unique_error_message( + self.__class__, tuple(unique_fields)) + raise ValidationError(msg) + + def save(self, force_insert=False, force_update=False, using=None, + update_fields=None, clean=True): + + self.dispositivo_raiz = self.get_raiz() + self.contagem_continua = self.tipo_dispositivo.contagem_continua + + return super().save( + force_insert=force_insert, force_update=force_update, using=using, + update_fields=update_fields, clean=clean) + def __str__(self): return '%(rotulo)s' % { 'rotulo': (self.rotulo if self.rotulo else self.tipo_dispositivo)} + def get_raiz(self): + dp = self + while dp.dispositivo_pai is not None: + dp = dp.dispositivo_pai + return dp + def rotulo_padrao(self, local_insert=0, for_insert_in=0): """ 0 = Sem inserção - com nomeclatura padrao @@ -1210,7 +1281,7 @@ class Dispositivo(BaseModel, TimestampedMixin): if not numero[i]: continue - if i > profundidade: + if i < profundidade: continue numero[i] -= 1 @@ -1516,12 +1587,6 @@ class Dispositivo(BaseModel, TimestampedMixin): return True return False - def get_raiz(self): - dp = self - while dp.dispositivo_pai is not None: - dp = dp.dispositivo_pai - return dp - def history(self): ultimo = self while ultimo.dispositivo_subsequente: diff --git a/sapl/compilacao/views.py b/sapl/compilacao/views.py index f4b2b3e7a..2fc31ff7d 100644 --- a/sapl/compilacao/views.py +++ b/sapl/compilacao/views.py @@ -1,7 +1,7 @@ -import logging -import sys from collections import OrderedDict from datetime import timedelta +import logging +import sys from braces.views import FormMessagesMixin from django import forms @@ -19,8 +19,8 @@ from django.http.response import (HttpResponse, HttpResponseRedirect, from django.shortcuts import get_object_or_404, redirect from django.utils.dateparse import parse_date from django.utils.encoding import force_text -from django.utils.translation import ugettext_lazy as _ from django.utils.translation import string_concat +from django.utils.translation import ugettext_lazy as _ from django.views.generic.base import TemplateView from django.views.generic.detail import DetailView from django.views.generic.edit import (CreateView, DeleteView, FormView, @@ -51,6 +51,7 @@ from sapl.compilacao.utils import (DISPOSITIVO_SELECT_RELATED, 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') @@ -1661,10 +1662,13 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin): base.delete() for irmao in irmaos_posteriores: - irmao.transform_in_prior( - profundidade=profundidade_base) - irmao.rotulo = irmao.rotulo_padrao() - irmao.save() + try: + irmao.transform_in_prior( + profundidade=profundidade_base) + irmao.rotulo = irmao.rotulo_padrao() + irmao.save() + except: + break irmaos = pai_base.dispositivos_filhos_set.\ filter(tipo_dispositivo=base.tipo_dispositivo) @@ -1766,17 +1770,37 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin): dcc_a_religar = dcc_a_religar.exclude( ordem__gte=proxima_articulacao.ordem) - primeiro_a_religar = 0 + primeiro_a_religar = True + profundidade = d.get_profundidade() for dr in dcc_a_religar: - if not primeiro_a_religar: - primeiro_a_religar = dr.dispositivo0 - base.delete() - - dr.dispositivo0 = ( - dr.dispositivo0 - - primeiro_a_religar + d.dispositivo0) + if primeiro_a_religar: + primeiro_a_religar = False + d_pk = d.pk + d.delete() + if base.pk == d_pk: + base = d + + dr.transform_in_prior(profundidade=profundidade) dr.rotulo = dr.rotulo_padrao() - dr.save(clean=base != dr) + try: + dr.save(clean=base != dr) + except: + break + + # Pode não ser religavável + # Exemplo, numa sequencia com variáção: + # Art. 1º + # ... + # Art. 1º-A + # ... + # Art. 2º + # ... + # Ao tentar excluir o Art. 1º-A, o algoritmo + # de religação tentará reduzir Art. 2º para 1º + # e o método clean lançará um erro visto que + # já existe um, por outro lado, não é lógico + # reduzir Art 2º para Art. 1º-A, ou seja, + # em caso de variação não há o que reduzir if base.tipo_dispositivo.dispositivo_de_alteracao: dpts = base.dispositivos_alterados_set.all().order_by( @@ -1785,7 +1809,19 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin): self.remover_dispositivo(dpt, False) if base.pk: - base.delete() + """ + Um registro a ser excluido em bloco que não é um + dispositivo de contagem contínua, neste ponto, teve todos + os seus filhos excluídos mas ainda não foi e, tão pouco, + foi seus imãos (anterior e posterior) religados + numericamente. + A exclusão em bloco religa apenas dispositivos de contagem + continua internos extra bloco. + Depois do bloco limpo, a função é chamada novamente para + excluir realmente a escolha do usuário + e religar seus irmaos + """ + self.remover_dispositivo(base, False) return '' @@ -2198,6 +2234,11 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin): 'foi excedido.'), time=6000) return data + # FIXME - a criação de espaço não está considerando o local correto + # quando não existem irmãos ou pais possíveis e jogando o + # dispositivo a ser inserido para o final da articulação + # e não para onde o usuário decidiu, bem como para logo abaixo + # seus filhos serem associados a ele. ordem = base.criar_espaco( espaco_a_criar=1, local=local_add)