From b5b9838913a45f3dd411b96e522e306b75331a1c Mon Sep 17 00:00:00 2001 From: LeandroRoberto Date: Thu, 19 Nov 2015 17:22:58 -0200 Subject: [PATCH] Diversas funcionalidades MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - implementação dos botões excluir na edição de dispositivos - implementação de perfis estruturais de textos normativos - refatoração do algoritmo de seleção de provaveis inserções - refatoração do algoritimo de inserção - parte da implementação do parser básico para import de arquivos odf --- compilacao/file2dispositivo.py | 327 ++++++++++++++++++ .../migrations/0015_auto_20151115_2310.py | 24 ++ .../migrations/0016_auto_20151119_0950.py | 48 +++ .../migrations/0017_auto_20151119_1035.py | 28 ++ .../migrations/0018_auto_20151119_1052.py | 23 ++ .../migrations/0019_auto_20151119_1120.py | 20 ++ .../migrations/0020_auto_20151119_1126.py | 20 ++ .../migrations/0021_auto_20151119_1617.py | 23 ++ compilacao/models.py | 98 ++++-- compilacao/templatetags/compilacao_filters.py | 18 +- compilacao/urls.py | 8 +- compilacao/views.py | 274 ++++++++++++--- requirements/requirements.txt | 1 + static/js/compilacao.js | 39 ++- static/styles/compilacao.scss | 118 +++++-- templates/compilacao/edit.html | 31 ++ templates/compilacao/edit_bloco.html | 46 +-- templates/compilacao/index.html | 5 +- templates/sistema.html | 3 +- 19 files changed, 1002 insertions(+), 152 deletions(-) create mode 100644 compilacao/file2dispositivo.py create mode 100644 compilacao/migrations/0015_auto_20151115_2310.py create mode 100644 compilacao/migrations/0016_auto_20151119_0950.py create mode 100644 compilacao/migrations/0017_auto_20151119_1035.py create mode 100644 compilacao/migrations/0018_auto_20151119_1052.py create mode 100644 compilacao/migrations/0019_auto_20151119_1120.py create mode 100644 compilacao/migrations/0020_auto_20151119_1126.py create mode 100644 compilacao/migrations/0021_auto_20151119_1617.py diff --git a/compilacao/file2dispositivo.py b/compilacao/file2dispositivo.py new file mode 100644 index 000000000..e6bbb6ae8 --- /dev/null +++ b/compilacao/file2dispositivo.py @@ -0,0 +1,327 @@ +import re + +from odf.element import Node, Text +from odf.opendocument import load +from odf.table import Table, TableCell, TableRow +from odf.text import (List, ListHeader, ListItem, ListLevelStyleBullet, + ListLevelStyleNumber, ListStyle, Note) + +from sapl import utils + + +class Parser(object): + + parser_list = [] + + def parser(self, _filepath): + + self.filepath = _filepath + + return self.re_parser() + + def re_parser(self): + + self.parser_list = [] + + # odt identificado pela extensão ou teste caso o arquivo sem extensão + if self.filepath.endswith('.odt') or\ + not re.search(r"(\w+)\.(\w+)", self.filepath): + + try: + odtparser = OdtParser() + + self.parser_list = odtparser.parser(self.filepath) + + return self.parser_list + except Exception as e: + print(e) + # TODO: Continue para outros formatos + pass + + # doc identificado pela extensão ou teste caso o arquivo sem extensão + if self.filepath.endswith(('.doc', 'docx')) or\ + not re.search(r"(\w+)\.(\w+)", self.filepath): + + try: + # TODO + return [] + except Exception as e: + # TODO: Continue para outros formatos + pass + + return [] + + def _reduce_terms(self, _nodes=None, level=0): + print(level) + if not _nodes: + nodes = self.parser_list + else: + nodes = _nodes + + fstr = True + i = -1 + for nd in nodes: + i += 1 + # print(nd) + + if not _nodes: + fstr = False + if nd[0] == 'table:table': + continue + + if isinstance(nd, list): + fstr = False + nodes[i] = self._reduce_terms(nd, level=level + 1) + + if fstr: + return ' '.join(nodes) + return nodes + + +class OdtParser(Parser): + FNC1 = '1' + FNCI = 'I' + FNCi = 'i' + FNCA = 'A' + FNCa = 'a' + FNC8 = '*' + FNCN = 'N' + + def re_parser(self): + + self.textdoc = load(self.filepath) + self.level_list = 0 + self.control_list = {} + + # mm = ODF2MoinMoin(self.filepath) + # self.parser_list = [mm.toString(), ] + + self.parser_list = self._import_itens(self.textdoc.text, level=0) + + # self._reduce_terms() + + return self.parser_list + + def _import_itens(self, element, level=0): + try: + result = [] + for el in element.childNodes: + print(level, el.tagName) + _r = '' + if el.tagName == 'Text': + _r = str(el) + else: + if el.isInstanceOf(Note): + continue + elif el.isInstanceOf(Table): + _r = self._import_table(el, level=level + 1) + elif el.isInstanceOf(List): + _r = self._import_list(el, level=level + 1) + # elif el.isInstanceOf(P): + # _r = [self.extractText(el),] + elif el.hasChildNodes(): + _r = self._import_itens(el, level=level + 1) + else: + _r = str(el) + + if _r: + if isinstance(_r, str): + result += [_r, ] + else: + result += _r + + return result + except Exception as e: + print(e) + + def _import_table(self, element, level=0): + result = '' + print(level) + try: + if element.isInstanceOf(Table): + result += '' + + for el in element.childNodes: + _r = '' + if isinstance(el, Text): + _r = str(el) + else: + if el.isInstanceOf(TableRow): + _r = self._import_table(el, level=level + 1) + _r = '%s' % (''.join(_r)) + result += ''.join(_r) + elif el.isInstanceOf(TableCell): + _r = self._import_table(el, level=level + 1) + if el.getAttribute('numberrowsspanned'): + _r = '' % ( + el.getAttribute('numberrowsspanned'), + ''.join(_r)) + elif el.getAttribute('numbercolumnsspanned'): + _r = '' % ( + el.getAttribute('numbercolumnsspanned'), + ''.join(_r)) + else: + _r = '' % (''.join(_r)) + + result += ''.join(_r) + else: + _r = self.extractText(el) + # _r = self._reduce_terms(_r) + if isinstance(_r, list): + result += '
'.join(_r) + else: + if _r: + result += _r + '
' + + if element.isInstanceOf(Table): + result += '
%s%s%s
' + + return [result, ] + except Exception as e: + print(e) + + def _import_list(self, element, level=0): + self.level_list += 1 + result = [] + print(level) + + numsufixo = '' + numformat = '' + startvalue = '' + + count_list_item = 0 + + try: + if element.isInstanceOf(List): + _stylename = element.getAttribute('stylename') + + if _stylename: + self.stylename = _stylename + + liststyles = self.textdoc.getElementsByType(ListStyle) + + for liststyle in liststyles: + if liststyle.getAttribute('name') == self.stylename: + break + + stylesnumbers = liststyle.getElementsByType( + ListLevelStyleNumber) + + for item in stylesnumbers: + if item.getAttribute('level') == str(self.level_list): + numsufixo = item.getAttribute('numsuffix') or '' + numformat = item.getAttribute('numformat') or '' + startvalue = item.getAttribute('startvalue') or '' + break + + if not numformat: + stylesbullets = liststyle.getElementsByType( + ListLevelStyleBullet) + for item in stylesbullets: + if item.getAttribute('level') == str(self.level_list): + numformat = '*' + break + + _id = element.getAttribute('id') + if _id: + self.id_last_list = _id + + if self.id_last_list not in self.control_list: + self.control_list[self.id_last_list] = [0, ] * 10 + + if _id: + if not element.getAttribute('continuelist') and\ + self.level_list == 1: + self.control_list[self.id_last_list] = [0, ] * 10 + + except Exception as e: + print(e) + + try: + flag_first = True + for el in element.childNodes: + prefixo = '' + if isinstance(el, Text): + _r = [str(el), ] + else: + if el.isInstanceOf(ListHeader) or\ + el.isInstanceOf(ListItem): + + if startvalue and flag_first: + self.control_list[self.id_last_list][ + self.level_list - 1] = int(startvalue) - 1 + flag_first = False + + self.control_list[self.id_last_list][ + self.level_list - 1] += 1 + count_list_item = self.control_list[self.id_last_list][ + self.level_list - 1] + + if numformat == OdtParser.FNC1: + prefixo = str(count_list_item) + elif numformat == OdtParser.FNCI: + prefixo = utils.int_to_roman(count_list_item) + elif numformat == OdtParser.FNCi: + prefixo = utils.int_to_roman( + count_list_item).lower() + elif numformat == OdtParser.FNCA: + prefixo = utils.int_to_letter(count_list_item) + elif numformat == OdtParser.FNCa: + prefixo = utils.int_to_letter( + count_list_item).lower() + elif numformat == OdtParser.FNC8: + prefixo = '*' + else: + prefixo = str(count_list_item) + + prefixo += numsufixo + + _r = self._import_itens(el, level=level + 1) + + if _r: + if prefixo: + _r[0] = '%s %s' % (prefixo, _r[0]) + result += _r + else: + result += _r + + self.level_list -= 1 + return result + + except Exception as e: + print(e) + + def extractText(self, odfElement): + """ Extract text content from an Element, with whitespace represented + properly. Returns the text, with tabs, spaces, and newlines + correctly evaluated. This method recursively descends through the + children of the given element, accumulating text and "unwrapping" + , , and elements along the way. + """ + result = [] + + if len(odfElement.childNodes) != 0: + for child in odfElement.childNodes: + if child.nodeType == Node.TEXT_NODE: + result.append(child.data) + elif child.nodeType == Node.ELEMENT_NODE: + subElement = child + tagName = subElement.qname + if tagName == (u"urn:oasis:names:tc:opendocument:xmlns:" + + "text:1.0", u"line-break"): + result.append("\n") + elif tagName == (u"urn:oasis:names:tc:opendocument:" + + "xmlns:text:1.0", u"tab"): + result.append("\t") + elif tagName == (u"urn:oasis:names:tc:opendocument:" + + "xmlns:text:1.0", u"s"): + c = subElement.getAttribute('c') + if c: + spaceCount = int(c) + else: + spaceCount = 1 + + result.append(" " * spaceCount) + else: + result.append(self.extractText(subElement)) + return ''.join(result) diff --git a/compilacao/migrations/0015_auto_20151115_2310.py b/compilacao/migrations/0015_auto_20151115_2310.py new file mode 100644 index 000000000..e0ec8b9e6 --- /dev/null +++ b/compilacao/migrations/0015_auto_20151115_2310.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0014_auto_20151107_1836'), + ] + + operations = [ + migrations.AddField( + model_name='tipodispositivo', + name='dispositivo_de_alteracao', + field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Dispositivo de Alteração'), + ), + migrations.AlterField( + model_name='tipodispositivorelationship', + name='filho_permitido', + field=models.ForeignKey(related_name='possiveis_pais', to='compilacao.TipoDispositivo'), + ), + ] diff --git a/compilacao/migrations/0016_auto_20151119_0950.py b/compilacao/migrations/0016_auto_20151119_0950.py new file mode 100644 index 000000000..3f417e105 --- /dev/null +++ b/compilacao/migrations/0016_auto_20151119_0950.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import datetime +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0015_auto_20151115_2310'), + ] + + operations = [ + migrations.CreateModel( + name='PerfilEstruturalTextosNormativos', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('nome', models.CharField(max_length=50, verbose_name='Nome')), + ], + options={ + 'verbose_name_plural': 'Perfis Estruturais de Textos Normativos', + 'verbose_name': 'Perfil Estrutural de Textos Normativos', + }, + ), + migrations.RemoveField( + model_name='dispositivo', + name='timestamp', + ), + migrations.AddField( + model_name='dispositivo', + name='created', + field=models.DateTimeField(default=datetime.datetime(2015, 11, 19, 11, 49, 55, 455058, tzinfo=utc), auto_now_add=True, verbose_name='created'), + preserve_default=False, + ), + migrations.AddField( + model_name='dispositivo', + name='modified', + field=models.DateTimeField(auto_now=True, default=datetime.datetime(2015, 11, 19, 11, 50, 5, 86839, tzinfo=utc), verbose_name='modified'), + preserve_default=False, + ), + migrations.AddField( + model_name='tipodispositivorelationship', + name='perfil', + field=models.ForeignKey(blank=True, related_name='+', null=True, default=None, to='compilacao.PerfilEstruturalTextosNormativos'), + ), + ] diff --git a/compilacao/migrations/0017_auto_20151119_1035.py b/compilacao/migrations/0017_auto_20151119_1035.py new file mode 100644 index 000000000..8a902b838 --- /dev/null +++ b/compilacao/migrations/0017_auto_20151119_1035.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0016_auto_20151119_0950'), + ] + + operations = [ + migrations.AddField( + model_name='perfilestruturaltextosnormativos', + name='padrao', + field=models.BooleanField(verbose_name='Padrão', choices=[(True, 'Sim'), (False, 'Não')], default=False), + ), + migrations.AlterField( + model_name='tipodispositivorelationship', + name='perfil', + field=models.ForeignKey(to='compilacao.PerfilEstruturalTextosNormativos'), + ), + migrations.AlterUniqueTogether( + name='tipodispositivorelationship', + unique_together=set([('pai', 'filho_permitido', 'perfil')]), + ), + ] diff --git a/compilacao/migrations/0018_auto_20151119_1052.py b/compilacao/migrations/0018_auto_20151119_1052.py new file mode 100644 index 000000000..00e78daef --- /dev/null +++ b/compilacao/migrations/0018_auto_20151119_1052.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0017_auto_20151119_1035'), + ] + + operations = [ + migrations.RemoveField( + model_name='tipodispositivo', + name='quantidade_permitida', + ), + migrations.AddField( + model_name='tipodispositivorelationship', + name='quantidade_permitida', + field=models.IntegerField(default=-1, verbose_name='Quantidade permitida nesta relação'), + ), + ] diff --git a/compilacao/migrations/0019_auto_20151119_1120.py b/compilacao/migrations/0019_auto_20151119_1120.py new file mode 100644 index 000000000..f5a461faf --- /dev/null +++ b/compilacao/migrations/0019_auto_20151119_1120.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0018_auto_20151119_1052'), + ] + + operations = [ + migrations.AddField( + model_name='perfilestruturaltextosnormativos', + name='sigla', + field=models.CharField(max_length=10, verbose_name='Sigla', default='LC95'), + preserve_default=False, + ), + ] diff --git a/compilacao/migrations/0020_auto_20151119_1126.py b/compilacao/migrations/0020_auto_20151119_1126.py new file mode 100644 index 000000000..d63e4c8d2 --- /dev/null +++ b/compilacao/migrations/0020_auto_20151119_1126.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0019_auto_20151119_1120'), + ] + + operations = [ + migrations.AlterField( + model_name='perfilestruturaltextosnormativos', + name='sigla', + field=models.CharField( + max_length=10, verbose_name='Sigla', unique=True), + ), + ] diff --git a/compilacao/migrations/0021_auto_20151119_1617.py b/compilacao/migrations/0021_auto_20151119_1617.py new file mode 100644 index 000000000..e378f4a60 --- /dev/null +++ b/compilacao/migrations/0021_auto_20151119_1617.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0020_auto_20151119_1126'), + ] + + operations = [ + migrations.AlterModelOptions( + name='perfilestruturaltextosnormativos', + options={'verbose_name': 'Perfil Estrutural de Textos Normativos', 'verbose_name_plural': 'Perfis Estruturais de Textos Normativos', 'ordering': ['-padrao', 'sigla']}, + ), + migrations.AddField( + model_name='tipodispositivorelationship', + name='permitir_variacao', + field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], verbose_name='Permitir Variação Numérica', default=True), + ), + ] diff --git a/compilacao/models.py b/compilacao/models.py index 529c40718..b2d646eff 100644 --- a/compilacao/models.py +++ b/compilacao/models.py @@ -11,6 +11,17 @@ from sapl import utils from sapl.utils import YES_NO_CHOICES +class TimestampedMixin(models.Model): + created = models.DateTimeField( + verbose_name=_('created'), + editable=False, blank=True, auto_now_add=True) + modified = models.DateTimeField( + verbose_name=_('modified'), editable=False, blank=True, auto_now=True) + + class Meta: + abstract = True + + class BaseModel(models.Model): class Meta: @@ -77,12 +88,6 @@ class TipoVide(models.Model): class TipoDispositivo(BaseModel): """ - - para class_css articulacao, omissis, ementa, - bloco_alteracao, artigo, caput e paragrafo - são palavras chaves usadas no código e de existência obrigatória. - - - apenas articulacao recebe nivel zero - - no attributo rotulo_prefixo_texto, caso haja um ';' (ponto e vírgula), e só pode haver 1 ';', o método [def rotulo_padrao] considerará que o rótulo do dispositivo deverá ser escrito com o contéudo após o ';' @@ -200,6 +205,10 @@ class TipoDispositivo(BaseModel): choices=YES_NO_CHOICES, default=False, verbose_name=_('Dispositivo de Articulação (Sem Texto)')) + dispositivo_de_alteracao = models.BooleanField( + choices=YES_NO_CHOICES, + default=False, + verbose_name=_('Dispositivo de Alteração')) formato_variacao0 = models.CharField( max_length=1, choices=FORMATO_NUMERACAO_CHOICES, @@ -238,10 +247,6 @@ class TipoDispositivo(BaseModel): symmetrical=False, related_name='+') - quantidade_permitida = models.IntegerField( - default=-1, - verbose_name=_('Quantidade permitida dentro de uma Norma')) - class Meta: verbose_name = _('Tipo de Dispositivo') verbose_name_plural = _('Tipos de Dispositivo') @@ -250,32 +255,86 @@ class TipoDispositivo(BaseModel): def __str__(self): return self.nome - def permitido_inserir_in(self, base, excluir_autos=False): - pp = self.possiveis_pais.filter(pai=base) + def permitido_inserir_in( + self, base, include_relative_autos=True, perfil_pk=None): + + if not perfil_pk: + perfis = PerfilEstruturalTextosNormativos.objects.filter( + padrao=True)[:1] + + if not perfis.exists(): + return False + + perfil_pk = perfis[0].pk + + pp = self.possiveis_pais.filter(pai=base, perfil_id=perfil_pk) if pp.exists(): - if excluir_autos: + if not include_relative_autos: if pp[0].filho_de_insercao_automatica: return False return True return False + def permitido_variacao( + self, base, perfil_pk=None): + + if not perfil_pk: + perfis = PerfilEstruturalTextosNormativos.objects.filter( + padrao=True)[:1] + + if not perfis.exists(): + return False + + perfil_pk = perfis[0].pk + + pp = self.possiveis_pais.filter(pai=base, perfil_id=perfil_pk) + if pp.exists(): + if pp[0].permitir_variacao: + return True + return False + + +class PerfilEstruturalTextosNormativos(BaseModel): + sigla = models.CharField( + max_length=10, unique=True, verbose_name=_('Sigla')) + nome = models.CharField(max_length=50, verbose_name=_('Nome')) + padrao = models.BooleanField( + default=False, + choices=YES_NO_CHOICES, verbose_name=_('Padrão')) + + class Meta: + verbose_name = _('Perfil Estrutural de Textos Normativos') + verbose_name_plural = _('Perfis Estruturais de Textos Normativos') + + ordering = ['-padrao', 'sigla'] + + def __str__(self): + return self.nome + class TipoDispositivoRelationship(BaseModel): pai = models.ForeignKey(TipoDispositivo, related_name='filhos_permitidos') filho_permitido = models.ForeignKey( TipoDispositivo, - blank=True, null=True, default=None, related_name='possiveis_pais') + perfil = models.ForeignKey(PerfilEstruturalTextosNormativos) filho_de_insercao_automatica = models.BooleanField( default=False, choices=YES_NO_CHOICES, verbose_name=_('Filho de Inserção Automática')) + permitir_variacao = models.BooleanField( + default=True, + choices=YES_NO_CHOICES, verbose_name=_('Permitir Variação Numérica')) + + quantidade_permitida = models.IntegerField( + default=-1, + verbose_name=_('Quantidade permitida nesta relação')) class Meta: verbose_name = _('Relação Direta Permitida') verbose_name_plural = _('Relaçõe Diretas Permitidas') ordering = ['pai', 'filho_permitido'] unique_together = ( - ('pai', 'filho_permitido',),) + ('pai', 'filho_permitido', 'perfil'),) def __str__(self): return '%s - %s' % ( @@ -331,7 +390,7 @@ class Publicacao(models.Model): return '%s: %s' % (self.veiculo_publicacao, self.publicacao) -class Dispositivo(BaseModel): +class Dispositivo(BaseModel, TimestampedMixin): TEXTO_PADRAO_DISPOSITIVO_REVOGADO = _('(Revogado)') INTERVALO_ORDEM = 1000 ordem = models.PositiveIntegerField( @@ -411,8 +470,6 @@ class Dispositivo(BaseModel): choices=YES_NO_CHOICES, verbose_name=_('Visibilidade na Norma Publicada')) - timestamp = models.DateTimeField() - tipo_dispositivo = models.ForeignKey( TipoDispositivo, related_name='dispositivos_do_tipo_set', @@ -894,11 +951,12 @@ class Dispositivo(BaseModel): return proxima_articulacao[0] - def is_relative_auto_insert(self): + def is_relative_auto_insert(self, perfil_pk): if self.dispositivo_pai is not None: # pp possiveis_pais pp = self.tipo_dispositivo.possiveis_pais.filter( - pai=self.dispositivo_pai.tipo_dispositivo) + pai=self.dispositivo_pai.tipo_dispositivo, + perfil_id=perfil_pk) if pp.exists(): if pp[0].filho_de_insercao_automatica: diff --git a/compilacao/templatetags/compilacao_filters.py b/compilacao/templatetags/compilacao_filters.py index d1c1f75da..65b8abab1 100644 --- a/compilacao/templatetags/compilacao_filters.py +++ b/compilacao/templatetags/compilacao_filters.py @@ -46,6 +46,8 @@ def nota_automatica(dispositivo): d = dispositivo.dispositivo_atualizador.dispositivo_pai if dispositivo.texto == Dispositivo.TEXTO_PADRAO_DISPOSITIVO_REVOGADO: return 'Revogado pelo %s.' % d + elif not dispositivo.dispositivo_substituido: + return 'Inclusão feita pelo %s.' % d else: return 'Alteração feita pelo %s.' % d return '' @@ -69,6 +71,16 @@ def get_sign_vigencia(value): return signer.sign(str(string)) +@register.filter +def select_provaveis_inserts(view, request): + return view.select_provaveis_inserts(request) + + +@register.filter +def is_relative_auto_insert(dpt, request): + return dpt.is_relative_auto_insert(request.session.perfil_estrutural) + + @register.filter def isinst(value, class_str): classe = value.__class__.__name__ @@ -82,11 +94,11 @@ def render_actions_head(view, d_atual): return False # Menu - if view.pk_view == view.pk_add and d_atual.pk == view.pk_view: + if view.pk_view == view.pk_edit and d_atual.pk == view.pk_view: return True # conteudo e menu no filho - if view.pk_view != view.pk_add and d_atual.pk == view.pk_add: + if view.pk_view != view.pk_edit and d_atual.pk == view.pk_edit: return True return False @@ -95,7 +107,7 @@ def render_actions_head(view, d_atual): @register.filter def short_string(str, length): if len(str) > length: - return str[:length]+'...' + return str[:length] + '...' else: return str diff --git a/compilacao/urls.py b/compilacao/urls.py index b3c74e3d1..d59058411 100644 --- a/compilacao/urls.py +++ b/compilacao/urls.py @@ -1,9 +1,9 @@ from django.conf.urls import include, url from compilacao import views -from compilacao.views import (tipo_dispositivo_crud, tipo_nota_crud, - tipo_publicacao_crud, tipo_vide_crud, - veiculo_publicacao_crud) +from compilacao.views import (perfil_estr_txt_norm, tipo_dispositivo_crud, + tipo_nota_crud, tipo_publicacao_crud, + tipo_vide_crud, veiculo_publicacao_crud) urlpatterns_compilacao = [ url(r'^(?P[0-9]+)/compilacao$', @@ -37,4 +37,6 @@ urlpatterns = [ include(tipo_dispositivo_crud.urls)), url(r'^sistema/compilacao/veiculo-publicacao/', include(veiculo_publicacao_crud.urls)), + url(r'^sistema/compilacao/perfil-estrutural-textos-normativos/', + include(perfil_estr_txt_norm.urls)), ] diff --git a/compilacao/views.py b/compilacao/views.py index 3075bf275..b5972d3e7 100644 --- a/compilacao/views.py +++ b/compilacao/views.py @@ -2,17 +2,21 @@ from collections import OrderedDict from datetime import datetime, timedelta from os.path import sys +from django import forms from django.core.signing import Signer from django.db.models import Q from django.http.response import JsonResponse +from django.shortcuts import render from django.utils.dateparse import parse_date from django.utils.translation import ugettext_lazy as _ from django.views.generic.base import TemplateView from django.views.generic.edit import FormMixin from django.views.generic.list import ListView -from compilacao.models import (Dispositivo, TipoDispositivo, TipoNota, - TipoPublicacao, TipoVide, VeiculoPublicacao) +from compilacao.file2dispositivo import Parser +from compilacao.models import (Dispositivo, PerfilEstruturalTextosNormativos, + TipoDispositivo, TipoNota, TipoPublicacao, + TipoVide, VeiculoPublicacao) from norma.models import NormaJuridica from sapl.crud import build_crud @@ -49,6 +53,13 @@ tipo_publicacao_crud = build_crud( [('sigla', 2), ('nome', 10)]], ]) +perfil_estr_txt_norm = build_crud( + PerfilEstruturalTextosNormativos, 'perfil_estrutural', [ + + [_('Perfil Estrutural de Textos Normativos'), + [('sigla', 2), ('nome', 10)]], + ]) + veiculo_publicacao_crud = build_crud( VeiculoPublicacao, 'veiculo_publicacao', [ @@ -97,10 +108,6 @@ tipo_dispositivo_crud = build_crud( ], - [_('Outras Configurações'), - [('quantidade_permitida', 12), - ], - ], ]) @@ -246,7 +253,20 @@ class DispositivoView(CompilacaoView): return itens -class CompilacaoEditView(CompilacaoView): +class UpLoadImportFileForm(forms.Form): + import_file = forms.FileField( + required=True, + label=_('Arquivo formato ODF para Importanção')) + + +def handle_uploaded_file(f, outfilepath): + with open(outfilepath, 'wb+') as destination: + for chunk in f.chunks(): + destination.write(chunk) + + +class CompilacaoEditView(CompilacaoView, FormMixin): + template_name = 'compilacao/edit.html' flag_alteradora = -1 @@ -254,11 +274,60 @@ class CompilacaoEditView(CompilacaoView): flag_nivel_ini = 0 flag_nivel_old = -1 - pk_add = 0 + pk_edit = 0 pk_view = 0 + def post(self, request, *args, **kwargs): + form = UpLoadImportFileForm(request.POST, request.FILES) + message = "Arquivo Submetido com sucesso" + + self.object_list = self.get_queryset() + + if form.is_valid(): + try: + f = request.FILES['import_file'] + outfilepath = '/tmp/' + f.name + handle_uploaded_file(f, outfilepath) + + p = Parser() + p.parser(outfilepath) + + except Exception as e: + print(e) + + context = self.get_context_data( + object_list=self.object_list, + form=form, + message=message, + view=self, + parser_list=p.parser_list) + return render(request, self.template_name, context) + else: + context = self.get_context_data( + object_list=self.object_list, + form=form, + message=form.errors, + view=self) + return self.form_invalid(context) + + return self.render_to_response({'form': form}) + + def form_invalid(self, context): + return self.render_to_response(context) + + def get(self, request, *args, **kwargs): + + self.object_list = self.get_queryset() + form_class = UpLoadImportFileForm + self.form = self.get_form(form_class) + context = self.get_context_data( + object_list=self.object_list, + form=self.form) + + return self.render_to_response(context) + def get_queryset(self): - self.pk_add = 0 + self.pk_edit = 0 self.pk_view = 0 self.flag_alteradora = -1 @@ -315,8 +384,25 @@ class CompilacaoEditView(CompilacaoView): return result + def set_perfil_in_session(self, request=None, perfil_id=0): + if not request: + return None + + if perfil_id: + perfil = PerfilEstruturalTextosNormativos.objects.get( + pk=perfil_id) + request.session['perfil_estrutural'] = perfil.pk + else: + perfis = PerfilEstruturalTextosNormativos.objects.filter( + padrao=True)[:1] + + if not perfis.exists(): + request.session.pop('perfil_estrutural') + else: + request.session['perfil_estrutural'] = perfis[0].pk -class DispositivoEditView(CompilacaoEditView, FormMixin): + +class DispositivoEditView(CompilacaoEditView): template_name = 'compilacao/edit_bloco.html' def post(self, request, *args, **kwargs): @@ -357,19 +443,45 @@ class DispositivoEditView(CompilacaoEditView, FormMixin): return JsonResponse(data, safe=False) + def get_queryset_perfil_estrutural(self): + perfis = PerfilEstruturalTextosNormativos.objects.all() + return perfis + + def get(self, request, *args, **kwargs): + + try: + if 'perfil_pk' in request.GET: + self.set_perfil_in_session( + request, request.GET['perfil_pk']) + elif 'perfil_estrutural' not in request.session: + self.set_perfil_in_session(request=request) + + self.object_list = self.get_queryset() + + self.perfil_estrutural_list = self.get_queryset_perfil_estrutural() + + context = self.get_context_data( + object_list=self.object_list, + perfil_estrutural_list=self.perfil_estrutural_list + ) + except Exception as e: + print(e) + + return self.render_to_response(context) + def get_queryset(self): self.flag_alteradora = -1 self.flag_nivel_ini = 0 self.flag_nivel_old = -1 try: - self.pk_add = int(self.request.GET['pkadd']) + self.pk_edit = int(self.request.GET['edit']) except: - self.pk_add = 0 + self.pk_edit = 0 self.pk_view = int(self.kwargs['dispositivo_id']) try: - if self.pk_add == self.pk_view: + if self.pk_edit == self.pk_view: bloco = Dispositivo.objects.get( pk=self.kwargs['dispositivo_id']) else: @@ -381,7 +493,7 @@ class DispositivoEditView(CompilacaoEditView, FormMixin): self.flag_nivel_old = bloco.nivel - 1 self.flag_nivel_ini = bloco.nivel - if self.pk_add == self.pk_view: + if self.pk_edit == self.pk_view: return [bloco, ] proximo_bloco = Dispositivo.objects.filter( @@ -402,14 +514,20 @@ class DispositivoEditView(CompilacaoEditView, FormMixin): ).select_related(*DISPOSITIVO_SELECT_RELATED) return itens - def select_provaveis_inserts(self): + def select_provaveis_inserts(self, request=None): try: + + if request and 'perfil_estrutural' not in request.session: + self.set_perfil_in_session(request) + + perfil_pk = request.session['perfil_estrutural'] + # Não salvar d_base - if self.pk_add == 0: + if self.pk_edit == 0: base = Dispositivo.objects.get(pk=self.pk_view) else: - base = Dispositivo.objects.get(pk=self.pk_add) + base = Dispositivo.objects.get(pk=self.pk_edit) prox_possivel = Dispositivo.objects.filter( ordem__gt=base.ordem, @@ -444,14 +562,15 @@ class DispositivoEditView(CompilacaoEditView, FormMixin): if dp.nivel >= nivel: continue - if dp.is_relative_auto_insert(): + if dp.is_relative_auto_insert(perfil_pk): continue if prox_possivel and \ dp.tipo_dispositivo != base.tipo_dispositivo and\ dp.nivel < prox_possivel.nivel and\ not prox_possivel.tipo_dispositivo.permitido_inserir_in( - dp.tipo_dispositivo): + dp.tipo_dispositivo, + perfil_pk=perfil_pk): if dp.tipo_dispositivo != prox_possivel.tipo_dispositivo: continue @@ -469,6 +588,13 @@ class DispositivoEditView(CompilacaoEditView, FormMixin): dp.tipo_dispositivo.nome,), 'dispositivo_base': base.pk}) + if dp.dispositivo_pai: + flag_pv = dp.tipo_dispositivo.permitido_variacao( + dp.dispositivo_pai.tipo_dispositivo, + perfil_pk=perfil_pk) + else: + flag_pv = False + r = [] flag_direcao = 1 flag_variacao = 0 @@ -491,8 +617,12 @@ class DispositivoEditView(CompilacaoEditView, FormMixin): 'dispositivo_base': base.pk}) flag_direcao = -1 + r.reverse() + if not flag_pv: + r = [r[0], ] + if len(r) > 0 and dp.tipo_dispositivo.formato_variacao0 == \ TipoDispositivo.FNCN: r = [r[0], ] @@ -530,7 +660,9 @@ class DispositivoEditView(CompilacaoEditView, FormMixin): for td in otds: if paradentro and not td.permitido_inserir_in( - tipb, excluir_autos=True): + tipb, + include_relative_autos=False, + perfil_pk=perfil_pk): continue base.tipo_dispositivo = td @@ -540,29 +672,32 @@ class DispositivoEditView(CompilacaoEditView, FormMixin): flag_insercao = False for possivelpai in parents: if td.permitido_inserir_in( - possivelpai.tipo_dispositivo, - excluir_autos=True): + possivelpai.tipo_dispositivo, + include_relative_autos=False, + perfil_pk=perfil_pk): flag_insercao = True break if not flag_insercao: continue - if possivelpai.is_relative_auto_insert(): + if possivelpai.is_relative_auto_insert(perfil_pk): continue if prox_possivel: if prox_possivel.nivel == base.nivel: if prox_possivel.tipo_dispositivo != td and\ not prox_possivel.tipo_dispositivo.\ - permitido_inserir_in(td): + permitido_inserir_in( + td, perfil_pk=perfil_pk): continue else: if possivelpai.tipo_dispositivo != \ prox_possivel.tipo_dispositivo and\ not prox_possivel.tipo_dispositivo.\ permitido_inserir_in( - possivelpai.tipo_dispositivo) and \ + possivelpai.tipo_dispositivo, + perfil_pk=perfil_pk) and \ possivelpai.nivel < \ prox_possivel.nivel: continue @@ -610,17 +745,33 @@ class ActionsEditMixin(object): def render_to_json_response(self, context, **response_kwargs): - if context['action'] == 'add_next': - return JsonResponse(self.add_next(context), safe=False) - elif context['action'] == 'add_in': - return JsonResponse(self.add_in(context), safe=False) - elif context['action'] == 'add_prior': - return JsonResponse(self.add_prior(context), safe=False) + test = getattr(self, context['action']) + return JsonResponse(test(context), safe=False) + + def delete_item_dispositivo(self, context): + return self.delete_bloco_dispositivo(context) + + def delete_bloco_dispositivo(self, context): + base = Dispositivo.objects.get(pk=context['dispositivo_id']) + + base_anterior = Dispositivo.objects.order_by('-ordem').filter( + norma_id=base.norma_id, + ordem__lt=base.ordem + )[:1] + base.delete() + + if base_anterior.exists(): + if base_anterior[0].dispositivo_pai_id: + data = {'pk': base_anterior[0].pk, 'pai': [ + base_anterior[0].dispositivo_pai_id, ]} + else: + data = {'pk': base_anterior[0].pk, 'pai': [-1, ]} + return data else: - return JsonResponse({}, safe=False) + return {} def add_prior(self, context): - pass + return {} def add_in(self, context): return self.add_next(context, local_add='add_in') @@ -637,7 +788,7 @@ class ActionsEditMixin(object): count_auto_insert = 0 for tipoauto in tipos_dp_auto_insert: - qtdp = tipoauto.filho_permitido.quantidade_permitida + qtdp = tipoauto.quantidade_permitida if qtdp >= 0: qtdp -= Dispositivo.objects.filter( norma_id=base.norma_id, @@ -657,7 +808,9 @@ class ActionsEditMixin(object): if dp.tipo_dispositivo == tipo: dp_irmao = dp break - if tipo.permitido_inserir_in(dp.tipo_dispositivo): + if tipo.permitido_inserir_in( + dp.tipo_dispositivo, + perfil_pk=context['perfil_pk']): dp_pai = dp break dp_pai = dp @@ -723,30 +876,33 @@ class ActionsEditMixin(object): antes da inserção atual e que são inferiores a dp, redirecionando para o novo pai''' - possiveis_filhos = Dispositivo.objects.filter( - ordem__gt=dp.ordem, - norma_id=dp.norma_id) - nivel = sys.maxsize flag_niveis = False - for filho in possiveis_filhos: - if filho.nivel > nivel: - continue + if not dp.tipo_dispositivo.dispositivo_de_alteracao: + possiveis_filhos = Dispositivo.objects.filter( + ordem__gt=dp.ordem, + norma_id=dp.norma_id) - if filho.dispositivo_pai.ordem >= dp.ordem: - continue + for filho in possiveis_filhos: - nivel = filho.nivel + if filho.nivel > nivel: + continue - if not filho.tipo_dispositivo.permitido_inserir_in( - dp.tipo_dispositivo): - continue + if filho.dispositivo_pai.ordem >= dp.ordem: + continue - filho.dispositivo_pai = dp - filho.clean() - filho.save() - flag_niveis = True + nivel = filho.nivel + + if not filho.tipo_dispositivo.permitido_inserir_in( + dp.tipo_dispositivo, + perfil_pk=context['perfil_pk']): + continue + + filho.dispositivo_pai = dp + filho.clean() + filho.save() + flag_niveis = True if flag_niveis: dp.organizar_niveis() @@ -795,7 +951,7 @@ class ActionsEditMixin(object): contagem continua, caso a inserção seja uma articulação''' numtipos = {} - if tipo.class_css == 'articulacao': + if dp.nivel == 0: proxima_articulacao = Dispositivo.objects.filter( ordem__gt=dp.ordem, @@ -894,6 +1050,14 @@ class ActionsEditView(ActionsEditMixin, TemplateView): def render_to_response(self, context, **response_kwargs): context['action'] = self.request.GET['action'] - context['tipo_pk'] = self.request.GET['tipo_pk'] - context['variacao'] = self.request.GET['variacao'] + + if 'tipo_pk' in self.request.GET: + context['tipo_pk'] = self.request.GET['tipo_pk'] + + if 'variacao' in self.request.GET: + context['variacao'] = self.request.GET['variacao'] + + if 'perfil_estrutural' in self.request.session: + context['perfil_pk'] = self.request.session['perfil_estrutural'] + return self.render_to_json_response(context, **response_kwargs) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 80547dc4c..7d7abaf98 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -9,6 +9,7 @@ django-crispy-forms django-extra-views django-vanilla-views git+git://github.com/interlegis/django-sass-processor.git +git+git://github.com/LeandroRoberto/odfpy.git libsass psycopg2 pytz diff --git a/static/js/compilacao.js b/static/js/compilacao.js index f5ff95468..30e8551a0 100644 --- a/static/js/compilacao.js +++ b/static/js/compilacao.js @@ -63,12 +63,14 @@ var clickUpdateDispositivo = function(event, __pk_refresh, __pk_edit, __action, var _action = __action; var _variacao = ''; var _tipo_pk = ''; + var _perfil_pk = ''; if (event != null) { pk_refresh = event.currentTarget.getAttribute('pk'); _action = $(this).attr('action'); _variacao = $(this).attr('variacao'); _tipo_pk = $(this).attr('tipo_pk'); + _perfil_pk = $(this).attr('perfil_pk'); } if (pk_edit == null) @@ -78,20 +80,22 @@ var clickUpdateDispositivo = function(event, __pk_refresh, __pk_edit, __action, if (_action == '') return; else if ( _action == null) { - url = pk_refresh+'/refresh?pkadd='+pk_edit; + url = pk_refresh+'/refresh?edit='+pk_edit; } else if (_action.startsWith('refresh')) { - var str = _action.split(':'); if (str.length > 1) { - editortype = str[1]; - SetCookie("editortype", editortype, 30) - + if(_action.endsWith('perfil')) { + url = '&perfil_pk='+_perfil_pk; + } + else { + editortype = str[1]; + SetCookie("editortype", editortype, 30) + } } - url = pk_refresh+'/refresh?pkadd='+pk_edit+url; - $("#message_block").css("display", "block"); + url = pk_refresh+'/refresh?edit='+pk_edit+url; } - else { + else if (_action.startsWith('add_')) { url = pk_refresh+'/actions?action='+_action; url += '&tipo_pk='+_tipo_pk; @@ -100,11 +104,14 @@ var clickUpdateDispositivo = function(event, __pk_refresh, __pk_edit, __action, $("#message_block").css("display", "block"); } + else if (_action.startsWith('delete_')) { + url = pk_refresh+'/actions?action='+_action; + $("#message_block").css("display", "block"); + } $.get(url).done(function( data ) { if ( _action == null || _action.startsWith('refresh')) { - if (flag_refresh_all) { if (flag_actions_vibible) clearEditSelected(); @@ -165,6 +172,16 @@ var clickUpdateDispositivo = function(event, __pk_refresh, __pk_edit, __action, alert('Erro na inserção!'); } } + else if (_action.startsWith('delete_')) { + $("#message_block").css("display", "block"); + clearEditSelected(); + if (data.pk != null) { + refreshScreenFocusPk(data); + } + else { + alert('Erro exclusão!'); + } + } else { clearEditSelected(); reloadFunctionClicks(); @@ -196,11 +213,11 @@ function clearEditSelected() { } function reloadFunctionClicks() { - $('.dpt .de, .btn-action, .btn-inserts, .btn-edit').off(); + $('.dpt .de, .btn-action, .btn-edit').off(); $('.dpt .de, .btn-edit').on('click', clickEditDispositivo); - $('.btn-action, .btn-inserts').on('click', clickUpdateDispositivo); + $('.btn-action').on('click', clickUpdateDispositivo); $('#editdi_texto').focus(); } diff --git a/static/styles/compilacao.scss b/static/styles/compilacao.scss index ddd0c06f6..c1ca8d044 100644 --- a/static/styles/compilacao.scss +++ b/static/styles/compilacao.scss @@ -17,6 +17,7 @@ $color_actions_border: #CCC; border-radius: $radius; } + @mixin li_flutuante() { & > ul { @@ -34,19 +35,26 @@ $color_actions_border: #CCC; a { border-right: 0px !important; } - } - &::before { - border-width: 0.375rem; - border-style: inset inset solid; - content: ""; - display: block; - height: 0px; - width: 0px; - border-color: transparent transparent #3385CA; - position: absolute; - top: -0.75rem; - left: 0.9375rem; + + &:first-child { + + &::before { + border-width: 0.375rem; + border-style: inset inset solid; + content: ""; + display: block; + height: 0px; + width: 0px; + border-color: transparent transparent #3385CA; + position: absolute; + top: -0.75rem; + left: 0.9375rem; + } + &:hover::before { + border-color: transparent transparent #0A5EA4; + } + } } // This bridges the gap between the top bar and a dropdown. &::after { @@ -74,7 +82,9 @@ $color_actions_border: #CCC; } } - +.test_import:nth-child(even) { + background-color: #ccc; +} #message_block { display: block; position: fixed; @@ -267,7 +277,31 @@ $color_actions_border: #CCC; } .fixed{ + z-index:98; + opacity: 0.2; + transition: all 2s ease-in-out; + -webkit-transition-delay: 3s; /* Safari */ + transition-delay: 3s; + + &:hover { + -webkit-transition-delay: 0s; /* Safari */ + transition-delay: 0s; + transition: all 0.3s ease-in-out; + opacity: 0.9; + + &::-webkit-scrollbar { + width: 10px; + height: 10px; + } + + + &::-webkit-scrollbar-thumb:vertical { + height: 30px; + background-color: rgba(0, 0, 0, 0.1); + } + + } } } /* end cp */ @@ -297,7 +331,7 @@ $color_actions_border: #CCC; & > .actions_left { color: #fff; position: absolute; - left: -2em; + left: -2.6em; opacity: 0; transition: all 0.4s ease-in-out; a { @@ -360,13 +394,10 @@ $color_actions_border: #CCC; @extend .articulacao; margin: 0; padding-top: 3em; + padding-left: 0em; background: #ddd; &::before { content: "Bloco de Alteração"; - position:absolute; - left: 0; - right: 0; - top: 0; display: block; } @@ -407,11 +438,6 @@ $color_actions_border: #CCC; } - .bloco_alteracao { - &::before { - display: none; - } - } & > .bloco { padding: 1em 0; @@ -466,7 +492,6 @@ $color_actions_border: #CCC; & > a { text-shadow: 0 0 5px #777; color: #ff0; - font-weight: bold; } } } @@ -661,14 +686,16 @@ $color_actions_border: #CCC; } &.add_prior { @include li_flutuante(); - display: block; + table-layout: fixed; } &.opc_excluir { @include li_flutuante(); - display: block; + table-layout: fixed; + position: static; & > ul { + right: 0.5em; li { a { background-color: #A70808; @@ -676,10 +703,16 @@ $color_actions_border: #CCC; background: #c70808; } } - } - - &::before { - border-color: transparent transparent #A70808; + &:first-child { + &::before { + border-color: transparent transparent #A70808; + right: 10%; + left: auto; + } + &:hover::before { + border-color: transparent transparent #c70808; + } + } } } @@ -692,6 +725,21 @@ $color_actions_border: #CCC; .menu_flutuante { & > li { @include li_flutuante(); + + + &.opc_excluir { + & > ul { + li { + &:first-child { + &::before { + right: auto; + left: 0.9375rem; + } + } + } + } + + } } } @@ -803,7 +851,7 @@ $color_actions_border: #CCC; left: 1em !important; right: 1em !important; margin-left: 0; - &::before { + li:first-child::before { left: 37%; } } @@ -816,18 +864,18 @@ $color_actions_border: #CCC; right: 0 !important; margin-left: 0; margin-right: 0.5em; - &::before { + li:first-child::before { right: 42%; left: auto; } } &.opc_excluir > ul { - left: 30% !important; + left: 10% !important; right: 0 !important; margin-left: 0; margin-right: 0.5em; - &::before { - right: 35%; + li:first-child::before { + right: 27%; left: auto; } } diff --git a/templates/compilacao/edit.html b/templates/compilacao/edit.html index acc76a8ad..5781ca373 100644 --- a/templates/compilacao/edit.html +++ b/templates/compilacao/edit.html @@ -3,6 +3,8 @@ {% load compilacao_filters %} {% load staticfiles %} {% load sass_tags %} +{% load crispy_forms_tags %} + {% block head_content %}{{block.super}} @@ -22,4 +24,33 @@ {% include 'compilacao/edit_bloco.html'%} + +{% if user.is_authenticated and False %} +
+ {% csrf_token %} + + {% if message %} +
+ {{message}} + × +
+ {% endif %} + + +
+ {% trans 'Parser ODF' %} + {{ form.as_p }} + +
+
+ +{% for parser in parser_list %} +
+ {{ parser|safe}} +
+{% endfor %} +{% endif%} + + + {% endblock base_content %} diff --git a/templates/compilacao/edit_bloco.html b/templates/compilacao/edit_bloco.html index e0aa78341..5febac519 100644 --- a/templates/compilacao/edit_bloco.html +++ b/templates/compilacao/edit_bloco.html @@ -35,40 +35,40 @@ - -
Módulo Sessão Plenária