diff --git a/.travis.yml b/.travis.yml index eb6abe0c3..f54ee6d20 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: python python: - - 3.4.3 + - 3.5 services: - postgresql @@ -14,13 +14,13 @@ before_script: - cp sapl/.env_test sapl/.env - psql -c "CREATE USER sapl WITH PASSWORD 'sapl'" -U postgres; - psql -c "CREATE DATABASE sapl OWNER sapl;" -U postgres - - ./check_migrations.sh + - ./scripts/django/check_migrations.sh script: - ./manage.py migrate - ./manage.py bower install - py.test --create-db - # - ./test_and_check_qa.sh + # - ./scripts/django/test_and_check_qa.sh addons: hosts: diff --git a/docker-compose.yml b/docker-compose.yml index 55a6b2359..68ab20687 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ sapldb: ports: - "5432:5432" sapl: - image: interlegis/sapl:3.1.77 + image: interlegis/sapl:3.1.81 restart: always environment: ADMIN_PASSWORD: interlegis diff --git a/docs/instalacao31.rst b/docs/instalacao31.rst index 0b159b91d..e904f8d6c 100644 --- a/docs/instalacao31.rst +++ b/docs/instalacao31.rst @@ -28,10 +28,10 @@ Instalar as seguintes dependências do sistema:: pkg-config postgresql postgresql-contrib pgadmin3 python-psycopg2 \ software-properties-common build-essential libxml2-dev libjpeg-dev \ libmysqlclient-dev libssl-dev libffi-dev libxslt1-dev python3-setuptools \ - python3-pip curl poppler-utils antiword default-jre + python3-pip curl poppler-utils antiword default-jre python3-venv sudo -i - curl -sL https://deb.nodesource.com/setup_6.x | bash - + curl -sL https://deb.nodesource.com/setup_8.x | bash - exit sudo apt-get install nodejs @@ -184,6 +184,8 @@ Copie a chave que aparecerá, edite o arquivo .env e altere o valor do parâmetr * Instalar as dependências do ``bower``:: eval $(echo "sudo chown -R $USER:$USER /home/$USER/") + sudo chown -R $USER:$GROUP ~/.npm + sudo chown -R $USER:$GROUP ~/.config ./manage.py bower install * Atualizar e/ou criar as tabelas da base de dados para refletir o modelo da versão clonada:: diff --git a/release.sh b/release.sh index 437355e57..cc3a172b5 100755 --- a/release.sh +++ b/release.sh @@ -29,7 +29,7 @@ function commit_and_push { } case "$1" in - --dryrun) + --dry-run) echo "Dry run" bump_version echo "done." @@ -37,7 +37,7 @@ case "$1" in exit 0 ;; - --a) + --publish) echo "generating release" bump_version commit_and_push diff --git a/requirements/requirements.txt b/requirements/requirements.txt index fe2e139c8..4b80ad130 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -22,7 +22,7 @@ easy-thumbnails==2.3 django-image-cropping==1.1.0 git+git://github.com/interlegis/trml2pdf.git libsass==0.11.1 -psycopg2==2.7.3 +psycopg2-binary==2.7.4 python-decouple==3.0 pytz==2016.4 pyyaml==3.11 diff --git a/sapl/base/forms.py b/sapl/base/forms.py index 513c3a7ca..5410ee3fe 100644 --- a/sapl/base/forms.py +++ b/sapl/base/forms.py @@ -834,6 +834,7 @@ class ConfiguracoesAppForm(ModelForm): 'cronometro_discurso', 'cronometro_aparte', 'cronometro_ordem', + 'cronometro_consideracoes', 'mostrar_brasao_painel', 'receber_recibo_proposicao'] @@ -842,6 +843,8 @@ class ConfiguracoesAppForm(ModelForm): self.fields['cronometro_discurso'].widget.attrs['class'] = 'cronometro' self.fields['cronometro_aparte'].widget.attrs['class'] = 'cronometro' self.fields['cronometro_ordem'].widget.attrs['class'] = 'cronometro' + self.fields['cronometro_consideracoes'].widget.attrs['class'] = 'cronometro' + def clean_mostrar_brasao_painel(self): mostrar_brasao_painel = self.cleaned_data.get( diff --git a/sapl/base/migrations/0017_appconfig_cronometro_consideracoes.py b/sapl/base/migrations/0017_appconfig_cronometro_consideracoes.py new file mode 100644 index 000000000..057344d9c --- /dev/null +++ b/sapl/base/migrations/0017_appconfig_cronometro_consideracoes.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-05-23 17:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0016_auto_20180326_1840'), + ] + + operations = [ + migrations.AddField( + model_name='appconfig', + name='cronometro_consideracoes', + field=models.TimeField(blank=True, null=True, verbose_name='Cronômetro de Considerações Finais'), + ), + ] diff --git a/sapl/base/models.py b/sapl/base/models.py index d2a6035a8..bbc03b6c7 100644 --- a/sapl/base/models.py +++ b/sapl/base/models.py @@ -113,6 +113,11 @@ class AppConfig(models.Model): blank=True, null=True) + cronometro_consideracoes = models.TimeField( + verbose_name=_('Cronômetro de Considerações Finais'), + blank=True, + null=True) + mostrar_brasao_painel = models.BooleanField( default=False, verbose_name=_('Mostrar brasão da Casa no painel?')) diff --git a/sapl/comissoes/forms.py b/sapl/comissoes/forms.py index 3576873b7..05a0a97b9 100644 --- a/sapl/comissoes/forms.py +++ b/sapl/comissoes/forms.py @@ -241,6 +241,9 @@ class ComissaoForm(forms.ModelForm): if not self.is_valid(): return self.cleaned_data + if len(self.cleaned_data['nome']) > 50: + msg = _('Nome da Comissão deve ter no máximo 50 caracteres.') + raise ValidationError(msg) if self.cleaned_data['data_extincao']: if (self.cleaned_data['data_extincao'] < self.cleaned_data['data_criacao']): diff --git a/sapl/legacy/migracao.py b/sapl/legacy/migracao.py index 059e49164..ffd0d2a18 100644 --- a/sapl/legacy/migracao.py +++ b/sapl/legacy/migracao.py @@ -28,6 +28,7 @@ def migrar(interativo=False): migrar_usuarios(REPO.working_dir) migrar_documentos(REPO) gravar_marco() + compactar_media() def compactar_media(): diff --git a/sapl/legacy/migracao_dados.py b/sapl/legacy/migracao_dados.py index 0f1e4d6dc..88758c8b1 100644 --- a/sapl/legacy/migracao_dados.py +++ b/sapl/legacy/migracao_dados.py @@ -1210,10 +1210,9 @@ def adjust_autor(new, old): break if old.col_username: - user_model = get_user_model() - if not user_model.objects.filter(username=old.col_username).exists(): - # cria um novo ususaŕio para o autor - user = user_model(username=old.col_username) + user, created = get_user_model().objects.get_or_create( + username=old.col_username) + if created: # gera uma senha inutilizável, que precisará ser trocada user.set_password(None) with reversion.create_revision(): @@ -1221,8 +1220,9 @@ def adjust_autor(new, old): reversion.set_comment( 'Usuário criado pela migração para o autor {}'.format( old.cod_autor)) - grupo_autor = Group.objects.get(name="Autor") - user.groups.add(grupo_autor) + grupo_autor = Group.objects.get(name="Autor") + user.groups.add(grupo_autor) + new.user = user def adjust_comissao(new, old): diff --git a/sapl/legacy/scripts/exporta_zope/exporta_zope.py b/sapl/legacy/scripts/exporta_zope/exporta_zope.py index 63d0ed09e..20c7d21e7 100755 --- a/sapl/legacy/scripts/exporta_zope/exporta_zope.py +++ b/sapl/legacy/scripts/exporta_zope/exporta_zope.py @@ -151,13 +151,14 @@ enumerate_properties = partial(enumerate_by_key_list, def enumerate_btree(folder): contagem_esperada = folder['_count'].value tree = folder['_tree'] + contagem_real = 0 # para o caso em que não haja itens for contagem_real, (id, obj) in enumerate(tree.iteritems(), start=1): obj, meta_type = br(obj), type(obj).__name__ yield id, obj, meta_type # verificação de consistência if contagem_esperada != contagem_real: print('ATENÇÃO: contagens diferentes na btree: ' - '{} esperada: {} real: {}'.format(folder, + '{} esperada: {} real: {}'.format(folder['title'], contagem_esperada, contagem_real)) @@ -362,7 +363,7 @@ def dump_sapl(sigla): destino.mkdir(parents=True) repo = git.Repo.init(destino) if TAG_ZOPE in repo.tags: - print('A exportação de documentos já está feita -- abortando') + print('{}: A exportação de documentos já está feita -- abortando'.format(sigla)) return repo_execute(repo, 'git annex init') diff --git a/sapl/painel/migrations/0002_auto_20180523_1430.py b/sapl/painel/migrations/0002_auto_20180523_1430.py new file mode 100644 index 000000000..52074acd1 --- /dev/null +++ b/sapl/painel/migrations/0002_auto_20180523_1430.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-05-23 17:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('painel', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='cronometro', + name='tipo', + field=models.CharField(choices=[('A', 'Aparte'), ('D', 'Discurso'), ('O', 'Ordem do dia'), ('C', 'Considerações finais')], max_length=1, verbose_name='Tipo Cronômetro'), + ), + ] diff --git a/sapl/painel/models.py b/sapl/painel/models.py index 097981520..f999ca480 100644 --- a/sapl/painel/models.py +++ b/sapl/painel/models.py @@ -26,7 +26,8 @@ class Cronometro(models.Model): CRONOMETRO_TYPES = ( ('A', _('Aparte')), ('D', _('Discurso')), - ('O', _('Ordem do dia')) + ('O', _('Ordem do dia')), + ('C', _('Considerações finais')) ) CRONOMETRO_STATUS = ( diff --git a/sapl/painel/views.py b/sapl/painel/views.py index d3f7fc5cc..0cb151382 100644 --- a/sapl/painel/views.py +++ b/sapl/painel/views.py @@ -464,6 +464,7 @@ def get_dados_painel(request, pk): 'cronometro_aparte': get_cronometro_status(request, 'aparte'), 'cronometro_discurso': get_cronometro_status(request, 'discurso'), 'cronometro_ordem': get_cronometro_status(request, 'ordem'), + 'cronometro_consideracoes': get_cronometro_status(request, 'consideracoes'), 'status_painel': sessao.painel_aberto, 'brasao': brasao } diff --git a/sapl/parlamentares/forms.py b/sapl/parlamentares/forms.py index e5b8d7d58..a7e042037 100644 --- a/sapl/parlamentares/forms.py +++ b/sapl/parlamentares/forms.py @@ -321,7 +321,7 @@ class FrenteForm(ModelForm): frente = super(FrenteForm, self).save(commit) content_type = ContentType.objects.get_for_model(Frente) object_id = frente.pk - tipo = TipoAutor.objects.get(descricao='Frente Parlamentar') + tipo = TipoAutor.objects.get(descricao__icontains='Frente') Autor.objects.create( content_type=content_type, object_id=object_id, diff --git a/sapl/protocoloadm/forms.py b/sapl/protocoloadm/forms.py index e85e9ecf2..7d68ad9c3 100644 --- a/sapl/protocoloadm/forms.py +++ b/sapl/protocoloadm/forms.py @@ -757,3 +757,118 @@ class DocumentoAdministrativoForm(ModelForm): row6, row7)) super(DocumentoAdministrativoForm, self).__init__( *args, **kwargs) + + +class DesvincularDocumentoForm(ModelForm): + + numero = forms.CharField(required=True, + label=DocumentoAdministrativo._meta. + get_field('numero').verbose_name + ) + ano = forms.ChoiceField(required=True, + label=DocumentoAdministrativo._meta. + get_field('ano').verbose_name, + choices=RANGE_ANOS, + widget=forms.Select(attrs={'class': 'selector'})) + + def clean(self): + super(DesvincularDocumentoForm, self).clean() + + cleaned_data = self.cleaned_data + + if not self.is_valid(): + return cleaned_data + + numero = cleaned_data['numero'] + ano = cleaned_data['ano'] + tipo = cleaned_data['tipo'] + + try: + documento = DocumentoAdministrativo.objects.get(numero=numero, ano=ano, tipo=tipo) + if not documento.protocolo: + raise forms.ValidationError( + _("%s %s/%s não se encontra vinculado a nenhum protocolo" % (tipo, numero, ano))) + except ObjectDoesNotExist: + raise forms.ValidationError( + _("%s %s/%s não existe" % (tipo, numero, ano))) + + return cleaned_data + + class Meta: + model = DocumentoAdministrativo + fields = ['tipo', + 'numero', + 'ano', + ] + + def __init__(self, *args, **kwargs): + + row1 = to_row( + [('numero', 4), + ('ano', 4), + ('tipo', 4)]) + + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset(_('Identificação do Documento'), + row1, + HTML(" "), + form_actions(label='Desvincular') + ) + ) + super(DesvincularDocumentoForm, self).__init__( + *args, **kwargs) + + +class DesvincularMateriaForm(forms.Form): + + numero = forms.CharField(required=True, + label=_('Número da Matéria')) + ano = forms.ChoiceField(required=True, + label=_('Ano da Matéria'), + choices=RANGE_ANOS, + widget=forms.Select(attrs={'class': 'selector'})) + tipo = forms.ModelChoiceField(label=_('Tipo de Matéria'), + required=True, + queryset=TipoMateriaLegislativa.objects.all(), + empty_label='------') + + def clean(self): + super(DesvincularMateriaForm, self).clean() + + cleaned_data = self.cleaned_data + + if not self.is_valid(): + return cleaned_data + + numero = cleaned_data['numero'] + ano = cleaned_data['ano'] + tipo = cleaned_data['tipo'] + + try: + materia = MateriaLegislativa.objects.get(numero=numero, ano=ano, tipo=tipo) + if not materia.numero_protocolo: + raise forms.ValidationError( + _("%s %s/%s não se encontra vinculada a nenhum protocolo" % (tipo, numero, ano))) + except ObjectDoesNotExist: + raise forms.ValidationError( + _("%s %s/%s não existe" % (tipo, numero, ano))) + + return cleaned_data + + def __init__(self, *args, **kwargs): + super(DesvincularMateriaForm, self).__init__(*args, **kwargs) + + row1 = to_row( + [('numero', 4), + ('ano', 4), + ('tipo', 4)]) + + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset(_('Identificação da Matéria'), + row1, + HTML(" "), + form_actions(label='Desvincular') + ) + ) diff --git a/sapl/protocoloadm/urls.py b/sapl/protocoloadm/urls.py index 46fec9980..223c38014 100644 --- a/sapl/protocoloadm/urls.py +++ b/sapl/protocoloadm/urls.py @@ -15,7 +15,9 @@ from sapl.protocoloadm.views import (AnularProtocoloAdmView, TipoDocumentoAdministrativoCrud, TramitacaoAdmCrud, atualizar_numero_documento, - doc_texto_integral) + doc_texto_integral, + DesvincularDocumentoView, + DesvincularMateriaView) from .apps import AppConfig @@ -61,6 +63,10 @@ urlpatterns_protocolo = [ url(r'^protocoloadm/anular-protocolo', AnularProtocoloAdmView.as_view(), name='anular_protocolo'), + url(r'^protocoloadm/desvincular-documento', + DesvincularDocumentoView.as_view(), name='desvincular_documento'), + url(r'^protocoloadm/desvincular-materia', + DesvincularMateriaView.as_view(), name='desvincular_materia'), url(r'^protocoloadm/protocolar-mat', ProtocoloMateriaView.as_view(), name='protocolar_mat'), diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index d4f7e96d9..7dd7206c7 100644 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -13,6 +13,7 @@ from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from django.views.generic import CreateView, ListView from django.views.generic.base import RedirectView, TemplateView +from django.views.generic.edit import FormView from django_filters.views import FilterView import sapl @@ -30,7 +31,7 @@ from .forms import (AnularProcoloAdmForm, DocumentoAcessorioAdministrativoForm, DocumentoAdministrativoFilterSet, DocumentoAdministrativoForm, ProtocoloDocumentForm, ProtocoloFilterSet, ProtocoloMateriaForm, - TramitacaoAdmEditForm, TramitacaoAdmForm) + TramitacaoAdmEditForm, TramitacaoAdmForm, DesvincularDocumentoForm, DesvincularMateriaForm) from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo, StatusTramitacaoAdministrativo, TipoDocumentoAdministrativo, TramitacaoAdministrativo) @@ -451,10 +452,11 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView): if not protocolo.numero: protocolo.numero = (numero['numero__max'] + 1) if numero['numero__max'] else 1 - if protocolo.numero < (numero['numero__max'] + 1): - msg = _('Número de protocolo deve ser maior que {}').format(numero['numero__max']) - messages.add_message(self.request, messages.ERROR, msg) - return self.render_to_response(self.get_context_data()) + if numero['numero__max']: + if protocolo.numero < (numero['numero__max'] + 1): + msg = _('Número de protocolo deve ser maior que {}').format(numero['numero__max']) + messages.add_message(self.request, messages.ERROR, msg) + return self.render_to_response(self.get_context_data()) protocolo.ano = timezone.now().year protocolo.data = timezone.now().date() protocolo.hora = timezone.now().time() @@ -720,3 +722,39 @@ def atualizar_numero_documento(request): {'numero': 1, 'ano': ano}) return response + + +class DesvincularDocumentoView(PermissionRequiredMixin, CreateView): + template_name = 'protocoloadm/anular_protocoloadm.html' + form_class = DesvincularDocumentoForm + form_valid_message = _('Documento desvinculado com sucesso!') + permission_required = ('protocoloadm.action_anular_protocolo', ) + + def get_success_url(self): + return reverse('sapl.protocoloadm:protocolo') + + def form_valid(self, form): + documento = DocumentoAdministrativo.objects.get(numero=form.cleaned_data['numero'], + ano=form.cleaned_data['ano'], + tipo=form.cleaned_data['tipo']) + documento.protocolo = None + documento.save() + return redirect(self.get_success_url()) + + +class DesvincularMateriaView(PermissionRequiredMixin, FormView): + template_name = 'protocoloadm/anular_protocoloadm.html' + form_class = DesvincularMateriaForm + form_valid_message = _('Matéria desvinculado com sucesso!') + permission_required = ('protocoloadm.action_anular_protocolo', ) + + def get_success_url(self): + return reverse('sapl.protocoloadm:protocolo') + + def form_valid(self, form): + materia = MateriaLegislativa.objects.get(numero=form.cleaned_data['numero'], + ano=form.cleaned_data['ano'], + tipo=form.cleaned_data['tipo']) + materia.numero_protocolo = None + materia.save() + return redirect(self.get_success_url()) \ No newline at end of file diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py index 9f46450b8..0bad27889 100644 --- a/sapl/relatorios/views.py +++ b/sapl/relatorios/views.py @@ -795,7 +795,8 @@ def relatorio_sessao_plenaria(request, pk): for idx in range(len(lst_expedientes)): txt_expedientes = lst_expedientes[idx]['txt_expediente'] - txt_expedientes = TrocaTag(txt_expedientes, '
| Considerações Finais: | +
' in texto: + texto = texto.replace('
', '') + texto = texto.replace('
', '') while (i < len(texto)): shard = texto[i:i + sizeStart] if (shard == startTag): i = ExtraiTag(texto, i) - textoSaida += '