From e3b3727804af76062cefb036d54c131c362ffe0e Mon Sep 17 00:00:00 2001 From: Sesostris Vieira Date: Mon, 13 Jun 2016 15:23:01 -0300 Subject: [PATCH 01/63] Atualiza para Django 1.9.6 --- requirements/dev-requirements.txt | 8 +++--- requirements/requirements.txt | 26 +++++++++---------- requirements/test-requirements.txt | 16 ++++++------ sigi/apps/casas/admin.py | 4 +-- sigi/apps/casas/models.py | 8 +++--- sigi/apps/contatos/models.py | 10 +++---- sigi/apps/convenios/models.py | 8 +++--- sigi/apps/diagnosticos/forms.py | 2 +- sigi/apps/diagnosticos/models.py | 4 +-- .../templates/diagnosticos/mapa.html | 2 +- sigi/apps/diagnosticos/widgets.py | 6 ++--- sigi/apps/inventario/admin.py | 6 ++--- sigi/apps/inventario/models.py | 6 ++--- sigi/apps/metas/templates/metas/mapa.html | 2 +- sigi/apps/metas/views.py | 12 ++++----- sigi/apps/ocorrencias/forms.py | 4 +-- sigi/apps/ocorrencias/models.py | 2 +- .../templates/ocorrencias/painel.html | 2 +- sigi/apps/parlamentares/admin.py | 4 +-- sigi/apps/servicos/admin.py | 4 +-- sigi/apps/servicos/models.py | 2 +- .../servicos/templates/servicos/mapa.html | 2 +- sigi/apps/servidores/admin.py | 2 +- sigi/apps/servidores/models.py | 6 ++--- sigi/settings/base.py | 4 +-- templates/index.html | 4 +-- 26 files changed, 78 insertions(+), 78 deletions(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 547f372f..1a437764 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -1,5 +1,5 @@ -r test-requirements.txt -django-debug-toolbar==1.2.2 -ipdb==0.8 -ipython==2.3.1 -pygraphviz==1.2 +django-debug-toolbar==1.4 +ipdb==0.10.0 +ipython==4.2.0 +pygraphviz==1.3.1 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index abfd1d45..570c578c 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,23 +1,23 @@ -e git://github.com/marinho/geraldo.git@868ebdce67176d9b6205cddc92476f642c783fff#egg=geraldo -django-bootstrap3==6.2.2 -django-admin-bootstrapped==2.4.0 -django-auth-ldap==1.2.7 +django-bootstrap3==7.0.1 +django-admin-bootstrapped==2.5.7 +django-auth-ldap==1.2.8 django-autoslug==1.9.3 -django-extensions==1.5.7 -django-image-cropping==1.0.2 -django-localflavor==1.1 -Django==1.7.10 -easy-thumbnails==2.2 +django-extensions==1.6.7 +django-image-cropping==1.0.3 +django-localflavor==1.3 +Django==1.9.6 +easy-thumbnails==2.3 eav-django==1.4.7 -gunicorn==19.3.0 +gunicorn==19.6.0 html5lib==0.9999999 -Pillow==2.9.0 +Pillow==3.2.0 pisa==3.0.33 psycopg2==2.6.1 -python-memcached==1.53 +python-memcached==1.58 PyYAML==3.11 -reportlab==2.7 -requests==2.8.1 +reportlab==3.3.0 +requests==2.10.0 six==1.10.0 djangorestframework==2.4.8 django-ipware==1.1.6 diff --git a/requirements/test-requirements.txt b/requirements/test-requirements.txt index 6a2cb2fa..e3bbda13 100644 --- a/requirements/test-requirements.txt +++ b/requirements/test-requirements.txt @@ -1,10 +1,10 @@ -r requirements.txt -coverage==3.7.1 -django-dynamic-fixture==1.8.1 -django-webtest==1.7.7 +coverage==4.1 +django-dynamic-fixture==1.9.0 +django-webtest==1.7.9 pyPdf==1.13 -pyquery==1.2.9 -pytest-cov==1.8.1 -pytest-django==2.8.0 -pytest==2.6.4 -WebTest==2.0.17 +pyquery==1.2.13 +pytest-cov==2.2.1 +pytest-django==2.9.1 +pytest==2.9.2 +WebTest==2.0.21 diff --git a/sigi/apps/casas/admin.py b/sigi/apps/casas/admin.py index 392d752b..8f6b0b0c 100644 --- a/sigi/apps/casas/admin.py +++ b/sigi/apps/casas/admin.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from django.core.urlresolvers import reverse from django.contrib import admin -from django.contrib.contenttypes import generic +from django.contrib.contenttypes.admin import GenericTabularInline from django.http import HttpResponseRedirect from django.utils.translation import ugettext as _ from image_cropping import ImageCroppingMixin @@ -24,7 +24,7 @@ from sigi.apps.utils.base_admin import BaseModelAdmin -class TelefonesInline(generic.GenericTabularInline): +class TelefonesInline(GenericTabularInline): model = Telefone readonly_fields = ('ult_alteracao',) extra = 1 diff --git a/sigi/apps/casas/models.py b/sigi/apps/casas/models.py index b1995798..deb59551 100644 --- a/sigi/apps/casas/models.py +++ b/sigi/apps/casas/models.py @@ -4,7 +4,7 @@ from unicodedata import normalize from datetime import datetime -from django.contrib.contenttypes import generic +from django.contrib.contenttypes import fields from django.db import models from image_cropping import ImageRatioField @@ -85,7 +85,7 @@ class CasaLegislativa(models.Model): pesquisador = models.ForeignKey(Servidor, verbose_name=u"Pesquisador", null=True, blank=True) obs_pesquisa = models.TextField(u"Observações do pesquisador", blank=True) ult_alt_endereco = models.DateTimeField(u'Última alteração do endereço', null=True, blank=True, editable=True) - telefones = generic.GenericRelation('contatos.Telefone') + telefones = fields.GenericRelation('contatos.Telefone') foto = models.ImageField( upload_to='imagens/casas', @@ -293,8 +293,8 @@ class Funcionario(models.Model): sexo = models.CharField(max_length=1, choices=SEXO_CHOICES, default="M") nota = models.CharField(max_length=70, null=True, blank=True) email = models.CharField('e-mail', max_length=75, blank=True) - telefones = generic.GenericRelation('contatos.Telefone') - endereco = generic.GenericRelation('contatos.Endereco') + telefones = fields.GenericRelation('contatos.Telefone') + endereco = fields.GenericRelation('contatos.Endereco') cargo = models.CharField(max_length=100, null=True, blank=True) funcao = models.CharField(u'função', max_length=100, null=True, blank=True) setor = models.CharField(max_length=100, choices=SETOR_CHOICES, default="outros") diff --git a/sigi/apps/contatos/models.py b/sigi/apps/contatos/models.py index 9b3becf3..04cd2ae4 100644 --- a/sigi/apps/contatos/models.py +++ b/sigi/apps/contatos/models.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from django.contrib.contenttypes import generic +from django.contrib.contenttypes import fields from django.contrib.contenttypes.models import ContentType from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models @@ -176,7 +176,7 @@ class Telefone(models.Model): content_type = models.ForeignKey(ContentType) # identificador do registro na classe vinculado a esse registro object_id = models.PositiveIntegerField() - content_object = generic.GenericForeignKey('content_type', 'object_id') + content_object = fields.GenericForeignKey('content_type', 'object_id') class Meta: ordering = ('numero',) @@ -196,7 +196,7 @@ class Contato(models.Model): nota = models.CharField(max_length=70, blank=True) email = models.EmailField(_(u'e-mail'), blank=True) - telefones = generic.GenericRelation(Telefone) + telefones = fields.GenericRelation(Telefone) municipio = models.ForeignKey( Municipio, @@ -209,7 +209,7 @@ class Contato(models.Model): content_type = models.ForeignKey(ContentType) # identificador do registro na classe vinculado a esse registro object_id = models.PositiveIntegerField() - content_object = generic.GenericForeignKey('content_type', 'object_id') + content_object = fields.GenericForeignKey('content_type', 'object_id') class Meta: ordering = ('nome',) @@ -301,7 +301,7 @@ class Endereco(models.Model): content_type = models.ForeignKey(ContentType) # identificador do registro na classe vinculado a esse registro object_id = models.PositiveIntegerField() - content_object = generic.GenericForeignKey('content_type', 'object_id') + content_object = fields.GenericForeignKey('content_type', 'object_id') class Meta: ordering = ('logradouro', 'numero') diff --git a/sigi/apps/convenios/models.py b/sigi/apps/convenios/models.py index 07ddcd3e..7bf0f926 100644 --- a/sigi/apps/convenios/models.py +++ b/sigi/apps/convenios/models.py @@ -148,7 +148,7 @@ class Anexo(models.Model): convenio = models.ForeignKey(Convenio, verbose_name=_(u'convênio')) # caminho no sistema para o documento anexo arquivo = models.FileField(upload_to='apps/convenios/anexo/arquivo', max_length=500) - descricao = models.CharField(_(u'descrição'), max_length='70') + descricao = models.CharField(_(u'descrição'), max_length=70) data_pub = models.DateTimeField( _(u'data da publicação do anexo'), default=datetime.now @@ -167,8 +167,8 @@ class UnidadeAdministrativa(models.Model): que pode ser um servivo do próprio Interlegis, assim como uma unidade do Senado Federal """ - sigla = models.CharField(max_length='10') - nome = models.CharField(max_length='100') + sigla = models.CharField(max_length=10) + nome = models.CharField(max_length=100) def __unicode__(self): return unicode(self.sigla) @@ -184,7 +184,7 @@ class Tramitacao(models.Model): data = models.DateField() observacao = models.CharField( _(u'observação'), - max_length='512', + max_length=512, null=True, blank=True, ) diff --git a/sigi/apps/diagnosticos/forms.py b/sigi/apps/diagnosticos/forms.py index a271a1f5..e381cccb 100644 --- a/sigi/apps/diagnosticos/forms.py +++ b/sigi/apps/diagnosticos/forms.py @@ -2,7 +2,7 @@ from copy import deepcopy from django import forms -from django.contrib.contenttypes.generic import generic_inlineformset_factory +from django.contrib.contenttypes.forms import generic_inlineformset_factory from django.forms import (BooleanField, CharField, DateField, FloatField, ModelChoiceField, Textarea, ModelMultipleChoiceField) diff --git a/sigi/apps/diagnosticos/models.py b/sigi/apps/diagnosticos/models.py index 1b71459c..45030e20 100644 --- a/sigi/apps/diagnosticos/models.py +++ b/sigi/apps/diagnosticos/models.py @@ -198,7 +198,7 @@ class Escolha(BaseChoice): """ schema = models.ForeignKey(Pergunta, related_name='choices', verbose_name=_(u'pergunta')) - schema_to_open = models.ForeignKey(Pergunta, related_name='', + schema_to_open = models.ForeignKey(Pergunta, related_name='schema_to_open_related', verbose_name=_(u'pergunta para abrir'), blank=True, null=True) ordem = models.PositiveIntegerField(blank=True, null=True) @@ -242,7 +242,7 @@ class Anexo(models.Model): """ diagnostico = models.ForeignKey(Diagnostico, verbose_name=u'diagnóstico') arquivo = models.FileField(upload_to='apps/diagnostico/anexo/arquivo', max_length=500) - descricao = models.CharField(_(u'descrição'), max_length='70') + descricao = models.CharField(_(u'descrição'), max_length=70) data_pub = models.DateTimeField(_(u'data da publicação do anexo'), default=datetime.now) diff --git a/sigi/apps/diagnosticos/templates/diagnosticos/mapa.html b/sigi/apps/diagnosticos/templates/diagnosticos/mapa.html index 072201c9..16fe77f2 100644 --- a/sigi/apps/diagnosticos/templates/diagnosticos/mapa.html +++ b/sigi/apps/diagnosticos/templates/diagnosticos/mapa.html @@ -6,7 +6,7 @@ {{ block.super }} {% load static from staticfiles %} - + - + diff --git a/sigi/apps/metas/views.py b/sigi/apps/metas/views.py index b72b3cb8..a1f086d9 100644 --- a/sigi/apps/metas/views.py +++ b/sigi/apps/metas/views.py @@ -12,7 +12,7 @@ from django.http import HttpResponse from django.shortcuts import render, render_to_response from django.template import RequestContext -from django.utils.datastructures import SortedDict +from collections import OrderedDict from django.utils.translation import ugettext as _ from django.views.decorators.cache import cache_page from easy_thumbnails.templatetags.thumbnail import thumbnail_url @@ -36,8 +36,8 @@ def dashboard(request): raise PermissionDenied desembolsos_max = 0 - matriz = SortedDict() - dados = SortedDict() + matriz = OrderedDict() + dados = OrderedDict() projetos = Projeto.objects.all() meses = Desembolso.objects.dates('data', 'month', 'DESC')[:6] colors = ['ffff00', 'cc7900', 'ff0000', '92d050', '006600', '0097cc', '002776', 'ae78d6', 'ff00ff', '430080', @@ -137,9 +137,9 @@ def map_sum(request): casas = filtrar_casas(**param) # Montar registros de totalização - tot_servicos = SortedDict() - tot_projetos = SortedDict() - tot_diagnosticos = SortedDict() + tot_servicos = OrderedDict() + tot_projetos = OrderedDict() + tot_diagnosticos = OrderedDict() for ts in TipoServico.objects.all(): tot_servicos[ts.sigla] = 0 diff --git a/sigi/apps/ocorrencias/forms.py b/sigi/apps/ocorrencias/forms.py index bc37feab..45e01d92 100644 --- a/sigi/apps/ocorrencias/forms.py +++ b/sigi/apps/ocorrencias/forms.py @@ -68,7 +68,7 @@ class Meta: widgets = {'ocorrencia': HiddenInput()} class ComentarioForm(ModelForm): - encaminhar_setor = ModelChoiceField(queryset=Servico.objects.all(), cache_choices=True) + encaminhar_setor = ModelChoiceField(queryset=Servico.objects.all()) class Meta: model = Comentario @@ -80,4 +80,4 @@ class Meta: model = Ocorrencia fields = ['casa_legislativa', 'categoria', 'tipo_contato', 'assunto', 'prioridade', 'ticket', 'descricao', 'setor_responsavel',] - widgets = {'casa_legislativa': AjaxSelect(url=reverse_lazy('painel-buscacasa'), attrs={'size':100}), } \ No newline at end of file + widgets = {'casa_legislativa': AjaxSelect(url=reverse_lazy('painel-buscacasa'), attrs={'size':100}), } diff --git a/sigi/apps/ocorrencias/models.py b/sigi/apps/ocorrencias/models.py index 2bfe120c..125bd1ce 100644 --- a/sigi/apps/ocorrencias/models.py +++ b/sigi/apps/ocorrencias/models.py @@ -105,7 +105,7 @@ def save(self, *args, **kwargs): class Anexo(models.Model): ocorrencia = models.ForeignKey(Ocorrencia, verbose_name=_(u'ocorrência')) arquivo = models.FileField(_(u'Arquivo anexado'), upload_to='apps/ocorrencia/anexo/arquivo', max_length=500) - descricao = models.CharField(_(u'descrição do anexo'), max_length='70') + descricao = models.CharField(_(u'descrição do anexo'), max_length=70) data_pub = models.DateTimeField(_(u'data da publicação do anexo'), null=True, blank=True, auto_now_add=True) class Meta: diff --git a/sigi/apps/ocorrencias/templates/ocorrencias/painel.html b/sigi/apps/ocorrencias/templates/ocorrencias/painel.html index 4e964bfb..79391709 100644 --- a/sigi/apps/ocorrencias/templates/ocorrencias/painel.html +++ b/sigi/apps/ocorrencias/templates/ocorrencias/painel.html @@ -11,7 +11,7 @@ {% block extrahead %} {{ block.super }} - + {% endblock %} diff --git a/sigi/apps/parlamentares/admin.py b/sigi/apps/parlamentares/admin.py index 80c8ebcd..e42e8c33 100644 --- a/sigi/apps/parlamentares/admin.py +++ b/sigi/apps/parlamentares/admin.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from django.contrib import admin -from django.contrib.contenttypes import generic +from django.contrib.contenttypes.admin import GenericTabularInline from django.http import HttpResponseRedirect, HttpResponse from django.utils.html import escape, escapejs from django.utils.translation import ugettext as _ @@ -20,7 +20,7 @@ class MandatosInline(admin.TabularInline): raw_id_fields = ('legislatura', 'partido') -class TelefonesInline(generic.GenericTabularInline): +class TelefonesInline(GenericTabularInline): model = Telefone extra = 2 diff --git a/sigi/apps/servicos/admin.py b/sigi/apps/servicos/admin.py index cecc0a8c..7e7da331 100644 --- a/sigi/apps/servicos/admin.py +++ b/sigi/apps/servicos/admin.py @@ -109,11 +109,11 @@ def calcular_data_uso(self, request, queryset): calcular_data_uso.short_description = _(u"Atualizar a data do último uso do(s) serviço(s)") def get_actions(self, request): - from django.utils.datastructures import SortedDict + from collections import OrderedDict actions = [self.get_action(action) for action in self.actions] actions = filter(None, actions) actions.sort(lambda a, b: cmp(a[2].lower(), b[2].lower())) - actions = SortedDict([(name, (func, name, desc)) for func, name, desc in actions]) + actions = OrderedDict([(name, (func, name, desc)) for func, name, desc in actions]) return actions def lookup_allowed(self, lookup, value): diff --git a/sigi/apps/servicos/models.py b/sigi/apps/servicos/models.py index 6a9cde73..107bf1ba 100644 --- a/sigi/apps/servicos/models.py +++ b/sigi/apps/servicos/models.py @@ -15,7 +15,7 @@ class TipoServico(models.Model): {url} para incluir a URL do serviço,
{senha} para incluir a senha inicial do serviço''' nome = models.CharField(_(u'Nome'), max_length=60) - sigla = models.CharField(_(u'Sigla'), max_length='12') + sigla = models.CharField(_(u'Sigla'), max_length=12) modo = models.CharField(_(u'Modo de prestação do serviço'), max_length=1, choices=MODO_CHOICES) string_pesquisa = models.CharField(_(u'String de pesquisa'), blank=True, max_length=200, help_text=_(u'Sufixo para pesquisa RSS para averiguar a data da última atualização do serviço')) diff --git a/sigi/apps/servicos/templates/servicos/mapa.html b/sigi/apps/servicos/templates/servicos/mapa.html index 94508675..15025631 100644 --- a/sigi/apps/servicos/templates/servicos/mapa.html +++ b/sigi/apps/servicos/templates/servicos/mapa.html @@ -7,7 +7,7 @@ {{ block.super }} {% load static from staticfiles %} - + - + @@ -33,4 +33,4 @@

{% trans 'Dashboard' %}

{% include "snippets/modules/charts-convenios.html" %}
-{% endblock %} \ No newline at end of file +{% endblock %} From d0c4948f5df28836de2292c6bfd287a2019a2c50 Mon Sep 17 00:00:00 2001 From: Edward Ribeiro Date: Wed, 15 Jun 2016 14:25:17 -0300 Subject: [PATCH 02/63] =?UTF-8?q?ajusta=20concatena=C3=A7=C3=A3o=20de=20tu?= =?UTF-8?q?plas=20com=20tuplas=20(e=20n=C3=A3o=20listas)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sigi/settings/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sigi/settings/base.py b/sigi/settings/base.py index bbc1fdfb..dd011415 100644 --- a/sigi/settings/base.py +++ b/sigi/settings/base.py @@ -30,9 +30,9 @@ SITE_ID = 1 -TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + [ +TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + ( 'django.core.context_processors.request', -] +) # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = ('django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', From 93659fd7a15aca3ecc84fd32bf05c786bc5050a0 Mon Sep 17 00:00:00 2001 From: Edward Ribeiro Date: Tue, 21 Jun 2016 11:49:25 -0300 Subject: [PATCH 03/63] Tentativa de portar ATENDIMENTO pra Python 2.7, e dentro do SIGI --- .gitignore | 8 + .../migrations/0002_auto_20160616_1602.py | 38 ++ sigi/apps/crispy_layout_mixin.py | 175 +++++++ sigi/apps/crud/__init__.py | 0 sigi/apps/crud/base.py | 224 +++++++++ sigi/apps/crud/urls.py | 6 + sigi/apps/crud/utils.py | 65 +++ .../migrations/0002_auto_20160616_1602.py | 34 ++ .../migrations/0003_auto_20160616_1602.py | 20 + .../migrations/0003_auto_20160616_1602.py | 20 + sigi/apps/solicitacoes/__init__.py | 0 sigi/apps/solicitacoes/admin.py | 3 + sigi/apps/solicitacoes/apps.py | 10 + sigi/apps/solicitacoes/forms.py | 43 ++ sigi/apps/solicitacoes/layouts.yaml | 33 ++ .../solicitacoes/migrations/0001_initial.py | 53 +++ .../migrations/0002_auto_20160616_1602.py | 20 + sigi/apps/solicitacoes/migrations/__init__.py | 0 sigi/apps/solicitacoes/models.py | 54 +++ sigi/apps/solicitacoes/tests.py | 3 + sigi/apps/solicitacoes/urls.py | 13 + sigi/apps/solicitacoes/views.py | 56 +++ sigi/apps/templates/index.html | 12 + sigi/apps/usuarios/__init__.py | 0 sigi/apps/usuarios/admin.py | 3 + sigi/apps/usuarios/apps.py | 10 + sigi/apps/usuarios/forms.py | 450 ++++++++++++++++++ sigi/apps/usuarios/layouts.yaml | 46 ++ sigi/apps/usuarios/migrations/0001_initial.py | 93 ++++ .../migrations/0002_auto_20160616_1400.py | 35 ++ .../migrations/0003_auto_20160616_1602.py | 26 + sigi/apps/usuarios/migrations/__init__.py | 0 sigi/apps/usuarios/models.py | 148 ++++++ sigi/apps/usuarios/tests.py | 3 + sigi/apps/usuarios/urls.py | 61 +++ sigi/apps/usuarios/utils.py | 65 +++ sigi/apps/usuarios/views.py | 183 +++++++ sigi/settings/base.py | 51 +- sigi/urls.py | 5 + templates/base.html | 155 ++++++ templates/crud/confirm_delete.html | 18 + templates/crud/detail.html | 38 ++ templates/crud/form.html | 15 + templates/crud/list.html | 51 ++ templates/menus/subnav.html | 23 + templates/paginacao.html | 37 ++ templates/templatetags/__init__.py | 0 templates/templatetags/common_tags.py | 38 ++ templates/templatetags/menus.py | 51 ++ templates/usuarios/confirma_email.html | 7 + templates/usuarios/habilitar_detail.html | 41 ++ templates/usuarios/login.html | 34 ++ .../usuarios/recuperacao_senha_completo.html | 10 + .../usuarios/recuperacao_senha_form.html | 13 + templates/usuarios/recuperar_senha.html | 5 + templates/usuarios/recuperar_senha_email.html | 14 + .../usuarios/recuperar_senha_enviado.html | 9 + templates/usuarios/usuario_detail.html | 31 ++ templates/usuarios/usuario_list.html | 38 ++ 59 files changed, 2695 insertions(+), 2 deletions(-) create mode 100644 sigi/apps/convenios/migrations/0002_auto_20160616_1602.py create mode 100644 sigi/apps/crispy_layout_mixin.py create mode 100644 sigi/apps/crud/__init__.py create mode 100644 sigi/apps/crud/base.py create mode 100644 sigi/apps/crud/urls.py create mode 100644 sigi/apps/crud/utils.py create mode 100644 sigi/apps/diagnosticos/migrations/0002_auto_20160616_1602.py create mode 100644 sigi/apps/ocorrencias/migrations/0003_auto_20160616_1602.py create mode 100644 sigi/apps/servicos/migrations/0003_auto_20160616_1602.py create mode 100644 sigi/apps/solicitacoes/__init__.py create mode 100644 sigi/apps/solicitacoes/admin.py create mode 100644 sigi/apps/solicitacoes/apps.py create mode 100644 sigi/apps/solicitacoes/forms.py create mode 100644 sigi/apps/solicitacoes/layouts.yaml create mode 100644 sigi/apps/solicitacoes/migrations/0001_initial.py create mode 100644 sigi/apps/solicitacoes/migrations/0002_auto_20160616_1602.py create mode 100644 sigi/apps/solicitacoes/migrations/__init__.py create mode 100644 sigi/apps/solicitacoes/models.py create mode 100644 sigi/apps/solicitacoes/tests.py create mode 100644 sigi/apps/solicitacoes/urls.py create mode 100644 sigi/apps/solicitacoes/views.py create mode 100644 sigi/apps/templates/index.html create mode 100644 sigi/apps/usuarios/__init__.py create mode 100644 sigi/apps/usuarios/admin.py create mode 100644 sigi/apps/usuarios/apps.py create mode 100644 sigi/apps/usuarios/forms.py create mode 100644 sigi/apps/usuarios/layouts.yaml create mode 100644 sigi/apps/usuarios/migrations/0001_initial.py create mode 100644 sigi/apps/usuarios/migrations/0002_auto_20160616_1400.py create mode 100644 sigi/apps/usuarios/migrations/0003_auto_20160616_1602.py create mode 100644 sigi/apps/usuarios/migrations/__init__.py create mode 100644 sigi/apps/usuarios/models.py create mode 100644 sigi/apps/usuarios/tests.py create mode 100644 sigi/apps/usuarios/urls.py create mode 100644 sigi/apps/usuarios/utils.py create mode 100644 sigi/apps/usuarios/views.py create mode 100644 templates/base.html create mode 100644 templates/crud/confirm_delete.html create mode 100644 templates/crud/detail.html create mode 100644 templates/crud/form.html create mode 100644 templates/crud/list.html create mode 100644 templates/menus/subnav.html create mode 100644 templates/paginacao.html create mode 100644 templates/templatetags/__init__.py create mode 100644 templates/templatetags/common_tags.py create mode 100644 templates/templatetags/menus.py create mode 100644 templates/usuarios/confirma_email.html create mode 100644 templates/usuarios/habilitar_detail.html create mode 100644 templates/usuarios/login.html create mode 100644 templates/usuarios/recuperacao_senha_completo.html create mode 100644 templates/usuarios/recuperacao_senha_form.html create mode 100644 templates/usuarios/recuperar_senha.html create mode 100644 templates/usuarios/recuperar_senha_email.html create mode 100644 templates/usuarios/recuperar_senha_enviado.html create mode 100644 templates/usuarios/usuario_detail.html create mode 100644 templates/usuarios/usuario_list.html diff --git a/.gitignore b/.gitignore index 45262ed7..79b884be 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,11 @@ db.* .vagrant sigi/settings/prod.py +bower/ +collected_static/ +eav/ +geraldo/ + +*.bak +*~ + diff --git a/sigi/apps/convenios/migrations/0002_auto_20160616_1602.py b/sigi/apps/convenios/migrations/0002_auto_20160616_1602.py new file mode 100644 index 00000000..94c9ae39 --- /dev/null +++ b/sigi/apps/convenios/migrations/0002_auto_20160616_1602.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('convenios', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='anexo', + name='descricao', + field=models.CharField(max_length=70, verbose_name='descri\xe7\xe3o'), + preserve_default=True, + ), + migrations.AlterField( + model_name='tramitacao', + name='observacao', + field=models.CharField(max_length=512, null=True, verbose_name='observa\xe7\xe3o', blank=True), + preserve_default=True, + ), + migrations.AlterField( + model_name='unidadeadministrativa', + name='nome', + field=models.CharField(max_length=100), + preserve_default=True, + ), + migrations.AlterField( + model_name='unidadeadministrativa', + name='sigla', + field=models.CharField(max_length=10), + preserve_default=True, + ), + ] diff --git a/sigi/apps/crispy_layout_mixin.py b/sigi/apps/crispy_layout_mixin.py new file mode 100644 index 00000000..5f6e57b5 --- /dev/null +++ b/sigi/apps/crispy_layout_mixin.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- + +from math import ceil +from os.path import dirname, join + +import rtyaml +from crispy_forms.bootstrap import FormActions +from crispy_forms.helper import FormHelper +from crispy_forms.layout import HTML, Div, Fieldset, Layout, Submit +from django.utils import formats +from django.utils.translation import ugettext as _ + + +def heads_and_tails(list_of_lists): + for alist in list_of_lists: + yield alist[0], alist[1:] + + +def to_column(name_span): + fieldname, span = name_span + return Div(fieldname, css_class='col-md-%d' % span) + + +def to_row(names_spans): + return Div(*map(to_column, names_spans), css_class='row-fluid') + + +def to_fieldsets(fields): + for field in fields: + if isinstance(field, list): + legend, row_specs = field[0], field[1:] + rows = [to_row(name_span_list) for name_span_list in row_specs] + yield Fieldset(legend, *rows) + else: + yield field + + +def form_actions(more=[], save_label=_('Salvar')): + return FormActions( + Submit('salvar', save_label, css_class='pull-right'), *more) + + +class FormLayout(Layout): + + def __init__(self, label_cancel=_("Cancelar"), *fields): + buttons = form_actions(more=[ + HTML('%s' % label_cancel)]) + _fields = list(to_fieldsets(fields)) + [to_row([(buttons, 12)])] + super(FormLayout, self).__init__(*_fields) + + +def get_field_display(obj, fieldname): + field = obj._meta.get_field(fieldname) + verbose_name = str(field.verbose_name) + if field.choices: + value = getattr(obj, 'get_%s_display' % fieldname)() + else: + value = getattr(obj, fieldname) + + if value is None: + display = '' + elif 'date' in str(type(value)): + display = formats.date_format(value, "SHORT_DATE_FORMAT") + elif 'bool' in str(type(value)): + display = _('Sim') if value else _('Não') + elif 'ImageFieldFile' in str(type(value)): + if value: + display = ''.format(value.url) + else: + display = '' + elif 'FieldFile' in str(type(value)): + if value: + display = '{}'.format( + value.url, + value.name.split('/')[-1:][0]) + else: + display = '' + else: + display = str(value) + return verbose_name, display + + +class CrispyLayoutFormMixin(object): + + @property + def layout_key(self): + if hasattr(super(CrispyLayoutFormMixin, self), 'layout_key'): + return super(CrispyLayoutFormMixin, self).layout_key + else: + return self.model.__name__ + + def get_layout(self): + filename = join( + dirname(self.model._meta.app_config.models_module.__file__), + 'layouts.yaml') + return read_layout_from_yaml(filename, self.layout_key) + + @property + def fields(self): + if hasattr(self, 'form_class') and self.form_class: + return None + else: + '''Returns all fields in the layout''' + return [fieldname for legend_rows in self.get_layout() + for row in legend_rows[1:] + for fieldname, span in row] + + def get_form(self, form_class=None): + try: + form = super(CrispyLayoutFormMixin, self).get_form(form_class) + except AttributeError: + # simply return None if there is no get_form on super + pass + else: + form.helper = FormHelper() + form.helper.layout = FormLayout(*self.get_layout()) + return form + + @property + def list_field_names(self): + '''The list of field names to display on table + + This base implementation returns the field names + in the first fieldset of the layout. + ''' + rows = self.get_layout()[0][1:] + return [fieldname for row in rows for fieldname, __ in row] + + def get_column(self, fieldname, span): + obj = self.get_object() + verbose_name, text = get_field_display(obj, fieldname) + return { + 'id': fieldname, + 'span': span, + 'verbose_name': verbose_name, + 'text': text, + } + + @property + def layout_display(self): + + return [ + {'legend': legend, + 'rows': [[self.get_column(fieldname, span) + for fieldname, span in row] + for row in rows] + } for legend, rows in heads_and_tails(self.get_layout())] + + +def read_yaml_from_file(filename): + # TODO cache this at application level + with open(filename, 'r') as yamlfile: + return rtyaml.load(yamlfile) + + +def read_layout_from_yaml(filename, key): + # TODO cache this at application level + yaml = read_yaml_from_file(filename) + base = yaml[key] + + def line_to_namespans(line): + split = [cell.split(':') for cell in line.split()] + namespans = [[s[0], int(s[1]) if len(s) > 1 else 0] for s in split] + remaining = 12 - sum(s for n, s in namespans) + nondefined = [ns for ns in namespans if not ns[1]] + while nondefined: + span = ceil(remaining / len(nondefined)) + namespan = nondefined.pop(0) + namespan[1] = span + remaining = remaining - span + return list(map(tuple, namespans)) + + return [[legend] + [line_to_namespans(l) for l in lines] + for legend, lines in base.items()] diff --git a/sigi/apps/crud/__init__.py b/sigi/apps/crud/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sigi/apps/crud/base.py b/sigi/apps/crud/base.py new file mode 100644 index 00000000..1d5f22c7 --- /dev/null +++ b/sigi/apps/crud/base.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import +from braces.views import FormMessagesMixin +from django.conf.urls import url +from django.core.urlresolvers import reverse +from django.utils.decorators import classonlymethod +from django.utils.translation import ugettext_lazy as _ +from django.views.generic import (CreateView, DeleteView, DetailView, ListView, + UpdateView) + +from .utils import make_pagination +from sigi.apps.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display + +LIST, CREATE, DETAIL, UPDATE, DELETE = \ + u'list', u'create', u'detail', u'update', u'delete' + + +def _form_invalid_message(msg): + return u'%s %s' % (_(u'Formulário inválido.'), msg) + +FORM_MESSAGES = {CREATE: (_(u'Registro criado com sucesso!'), + _(u'O registro não foi criado.')), + UPDATE: (_(u'Registro alterado com sucesso!'), + _(u'Suas alterações não foram salvas.')), + DELETE: (_(u'Registro excluído com sucesso!'), + _(u'O registro não foi excluído.'))} +FORM_MESSAGES = dict((k, (a, _form_invalid_message(b))) + for k, (a, b) in FORM_MESSAGES.items()) + + +class CrudBaseMixin(CrispyLayoutFormMixin): + + @classmethod + def url_name(cls, suffix): + return u'%s_%s' % (cls.model._meta.model_name, suffix) + + def resolve_url(self, suffix, args=None): + namespace = self.model._meta.app_label + return reverse(u'%s:%s' % (namespace, self.url_name(suffix)), + args=args) + + @property + def list_url(self): + return self.resolve_url(LIST) + + @property + def create_url(self): + return self.resolve_url(CREATE) + + @property + def detail_url(self): + return self.resolve_url(DETAIL, args=(self.object.id,)) + + @property + def update_url(self): + return self.resolve_url(UPDATE, args=(self.object.id,)) + + @property + def delete_url(self): + return self.resolve_url(DELETE, args=(self.object.id,)) + + def get_template_names(self): + names = super(CrudBaseMixin, self).get_template_names() + names.append(u"crud/%s.html" % + self.template_name_suffix.lstrip(u'_')) + return names + + @property + def verbose_name(self): + return self.model._meta.verbose_name + + @property + def verbose_name_plural(self): + return self.model._meta.verbose_name_plural + + +class CrudListView(ListView): + + @classmethod + def get_url_regex(cls): + return ur'^$' + + paginate_by = 10 + no_entries_msg = _(u'Nenhum registro encontrado.') + + def get_rows(self, object_list): + return [self._as_row(obj) for obj in object_list] + + def get_headers(self): + return [self.model._meta.get_field(fieldname).verbose_name + for fieldname in self.list_field_names] + + def _as_row(self, obj): + return [ + (get_field_display(obj, name)[1], + self.resolve_url(DETAIL, args=(obj.id,)) if i == 0 else None) + for i, name in enumerate(self.list_field_names)] + + def get_context_data(self, **kwargs): + context = super(CrudListView, self).get_context_data(**kwargs) + context.setdefault(u'title', self.verbose_name_plural) + + # pagination + if self.paginate_by: + page_obj = context[u'page_obj'] + paginator = context[u'paginator'] + context[u'page_range'] = make_pagination( + page_obj.number, paginator.num_pages) + + # rows + object_list = context[u'object_list'] + context[u'headers'] = self.get_headers() + context[u'rows'] = self.get_rows(object_list) + + context[u'NO_ENTRIES_MSG'] = self.no_entries_msg + + return context + + +class CrudCreateView(FormMessagesMixin, CreateView): + + @classmethod + def get_url_regex(cls): + return ur'^create$' + + form_valid_message, form_invalid_message = FORM_MESSAGES[CREATE] + + @property + def cancel_url(self): + return self.list_url + + def get_success_url(self): + return self.detail_url + + def get_context_data(self, **kwargs): + kwargs.setdefault(u'title', _(u'Adicionar %(verbose_name)s') % { + u'verbose_name': self.verbose_name}) + return super(CrudCreateView, self).get_context_data(**kwargs) + + +class CrudDetailView(DetailView): + + @classmethod + def get_url_regex(cls): + return ur'^(?P\d+)$' + + +class CrudUpdateView(FormMessagesMixin, UpdateView): + + @classmethod + def get_url_regex(cls): + return ur'^(?P\d+)/edit$' + + form_valid_message, form_invalid_message = FORM_MESSAGES[UPDATE] + + @property + def cancel_url(self): + return self.detail_url + + def get_success_url(self): + return self.detail_url + + +class CrudDeleteView(FormMessagesMixin, DeleteView): + + @classmethod + def get_url_regex(cls): + return ur'^(?P\d+)/delete$' + + form_valid_message, form_invalid_message = FORM_MESSAGES[DELETE] + + @property + def cancel_url(self): + return self.detail_url + + def get_success_url(self): + return self.list_url + + +class Crud(object): + BaseMixin = CrudBaseMixin + ListView = CrudListView + CreateView = CrudCreateView + DetailView = CrudDetailView + UpdateView = CrudUpdateView + DeleteView = CrudDeleteView + help_path = u'' + + @classonlymethod + def get_urls(cls): + + def _add_base(view): + class CrudViewWithBase(cls.BaseMixin, view): + model = cls.model + help_path = cls.help_path + crud = cls + CrudViewWithBase.__name__ = view.__name__ + return CrudViewWithBase + + CrudListView = _add_base(cls.ListView) + CrudCreateView = _add_base(cls.CreateView) + CrudDetailView = _add_base(cls.DetailView) + CrudUpdateView = _add_base(cls.UpdateView) + CrudDeleteView = _add_base(cls.DeleteView) + + return [url(regex, view.as_view(), name=view.url_name(suffix)) + for regex, view, suffix in [ + (CrudListView.get_url_regex(), CrudListView, LIST), + (CrudCreateView.get_url_regex(), CrudCreateView, CREATE), + (CrudDetailView.get_url_regex(), CrudDetailView, DETAIL), + (CrudUpdateView.get_url_regex(), CrudUpdateView, UPDATE), + (CrudDeleteView.get_url_regex(), CrudDeleteView, DELETE), + ]] + + @classonlymethod + def build(cls, _model, _help_path): + + class ModelCrud(cls): + model = _model + help_path = _help_path + + ModelCrud.__name__ = u'%sCrud' % _model.__name__ + return ModelCrud diff --git a/sigi/apps/crud/urls.py b/sigi/apps/crud/urls.py new file mode 100644 index 00000000..b17b6df8 --- /dev/null +++ b/sigi/apps/crud/urls.py @@ -0,0 +1,6 @@ +from __future__ import absolute_import +from django.conf.urls import include, url + +urlpatterns = [ + url(ur'', include(u'stub_app.urls')), +] diff --git a/sigi/apps/crud/utils.py b/sigi/apps/crud/utils.py new file mode 100644 index 00000000..53bf2b2d --- /dev/null +++ b/sigi/apps/crud/utils.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +from django.utils.translation import ugettext_lazy as _ + +UF = [ + (u'AC', u'Acre'), + (u'AL', u'Alagoas'), + (u'AP', u'Amapá'), + (u'AM', u'Amazonas'), + (u'BA', u'Bahia'), + (u'CE', u'Ceará'), + (u'DF', u'Distrito Federal'), + (u'ES', u'Espírito Santo'), + (u'GO', u'Goiás'), + (u'MA', u'Maranhão'), + (u'MT', u'Mato Grosso'), + (u'MS', u'Mato Grosso do Sul'), + (u'MG', u'Minas Gerais'), + (u'PR', u'Paraná'), + (u'PB', u'Paraíba'), + (u'PA', u'Pará'), + (u'PE', u'Pernambuco'), + (u'PI', u'Piauí'), + (u'RJ', u'Rio de Janeiro'), + (u'RN', u'Rio Grande do Norte'), + (u'RS', u'Rio Grande do Sul'), + (u'RO', u'Rondônia'), + (u'RR', u'Roraima'), + (u'SC', u'Santa Catarina'), + (u'SE', u'Sergipe'), + (u'SP', u'São Paulo'), + (u'TO', u'Tocantins'), + (u'EX', u'Exterior'), +] + +YES_NO_CHOICES = [(None, _(u'----')), (False, _(u'Não')), (True, _(u'Sim'))] + + +def str2bool(v): + return v in (u'Sim', u'True') + + +SEXO_CHOICES = [(u'M', _(u'Masculino')), (u'F', _(u'Feminino'))] + + +def from_to(start, end): + return range(start, end + 1) + + +def make_pagination(index, num_pages): + PAGINATION_LENGTH = 10 + if num_pages <= PAGINATION_LENGTH: + return from_to(1, num_pages) + else: + if index - 1 <= 5: + tail = [num_pages - 1, num_pages] + head = from_to(1, PAGINATION_LENGTH - 3) + else: + if index + 1 >= num_pages - 3: + tail = from_to(index - 1, num_pages) + else: + tail = [index - 1, index, index + 1, + None, num_pages - 1, num_pages] + head = from_to(1, PAGINATION_LENGTH - len(tail) - 1) + return head + [None] + tail diff --git a/sigi/apps/diagnosticos/migrations/0002_auto_20160616_1602.py b/sigi/apps/diagnosticos/migrations/0002_auto_20160616_1602.py new file mode 100644 index 00000000..b83a708a --- /dev/null +++ b/sigi/apps/diagnosticos/migrations/0002_auto_20160616_1602.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import autoslug.fields +import eav.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('diagnosticos', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='anexo', + name='descricao', + field=models.CharField(max_length=70, verbose_name='descri\xe7\xe3o'), + preserve_default=True, + ), + migrations.AlterField( + model_name='escolha', + name='schema_to_open', + field=models.ForeignKey(related_name='schema_to_open_related', verbose_name='pergunta para abrir', blank=True, to='diagnosticos.Pergunta', null=True), + preserve_default=True, + ), + migrations.AlterField( + model_name='pergunta', + name='name', + field=autoslug.fields.AutoSlugField(populate_from=b'title', editable=True, max_length=250, blank=True, verbose_name='name', slugify=eav.models.slugify_attr_name), + preserve_default=True, + ), + ] diff --git a/sigi/apps/ocorrencias/migrations/0003_auto_20160616_1602.py b/sigi/apps/ocorrencias/migrations/0003_auto_20160616_1602.py new file mode 100644 index 00000000..77a9884f --- /dev/null +++ b/sigi/apps/ocorrencias/migrations/0003_auto_20160616_1602.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ocorrencias', '0002_auto_20160308_0828'), + ] + + operations = [ + migrations.AlterField( + model_name='anexo', + name='descricao', + field=models.CharField(max_length=70, verbose_name='descri\xe7\xe3o do anexo'), + preserve_default=True, + ), + ] diff --git a/sigi/apps/servicos/migrations/0003_auto_20160616_1602.py b/sigi/apps/servicos/migrations/0003_auto_20160616_1602.py new file mode 100644 index 00000000..ef24d8ad --- /dev/null +++ b/sigi/apps/servicos/migrations/0003_auto_20160616_1602.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('servicos', '0002_tiposervico_modo'), + ] + + operations = [ + migrations.AlterField( + model_name='tiposervico', + name='sigla', + field=models.CharField(max_length=12, verbose_name='Sigla'), + preserve_default=True, + ), + ] diff --git a/sigi/apps/solicitacoes/__init__.py b/sigi/apps/solicitacoes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sigi/apps/solicitacoes/admin.py b/sigi/apps/solicitacoes/admin.py new file mode 100644 index 00000000..4185d360 --- /dev/null +++ b/sigi/apps/solicitacoes/admin.py @@ -0,0 +1,3 @@ +# from django.contrib import admin + +# Register your models here. diff --git a/sigi/apps/solicitacoes/apps.py b/sigi/apps/solicitacoes/apps.py new file mode 100644 index 00000000..9567689e --- /dev/null +++ b/sigi/apps/solicitacoes/apps.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import +from django import apps +from django.utils.translation import ugettext_lazy as _ + + +class AppConfig(apps.AppConfig): + name = u'solicitacoes' + verbose_name = _(u'Solicitações') diff --git a/sigi/apps/solicitacoes/forms.py b/sigi/apps/solicitacoes/forms.py new file mode 100644 index 00000000..5db2086c --- /dev/null +++ b/sigi/apps/solicitacoes/forms.py @@ -0,0 +1,43 @@ +from __future__ import absolute_import +from django import forms +from django.forms import ModelForm + +from .models import Sistema, Solicitacao + + +class SolicitacaoForm(ModelForm): + + resumo = forms.CharField( + label=u'Resumo', + max_length=500, + widget=forms.Textarea) + + class Meta(object): + model = Solicitacao + fields = [u'codigo', u'usuario', u'sistema', + u'email_contato', u'telefone_contato', + u'casa_legislativa', u'titulo', u'resumo'] + widgets = {u'codigo': forms.HiddenInput(), + u'usuario': forms.HiddenInput()} + + +class SolicitacaoEditForm(ModelForm): + + resumo = forms.CharField( + label=u'Resumo', + max_length=500, + widget=forms.Textarea) + + class Meta(object): + model = Solicitacao + fields = [u'codigo', u'usuario', u'sistema', + u'casa_legislativa', u'titulo', u'resumo'] + widgets = {u'codigo': forms.TextInput(attrs={u'readonly': u'readonly'}), + u'usuario': forms.HiddenInput()} + + +class SistemaForm(ModelForm): + + class Meta(object): + model = Sistema + fields = [u'sigla', u'nome'] diff --git a/sigi/apps/solicitacoes/layouts.yaml b/sigi/apps/solicitacoes/layouts.yaml new file mode 100644 index 00000000..2e9804a4 --- /dev/null +++ b/sigi/apps/solicitacoes/layouts.yaml @@ -0,0 +1,33 @@ + +Sistema: + Sistema: + - sigla + - nome + +Solicitacao: + Solicitação: + - codigo + - usuario + - sistema + - casa_legislativa + - email_contato telefone_contato + - titulo + - resumo + +SolicitacaoEdit: + Solicitação: + - codigo + - usuario + - sistema + - titulo + - resumo + +SolicitacaoList: + Solicitação: + - codigo + - usuario + - sistema + - casa_legislativa + - email_contato telefone_contato + - titulo + - data_criacao diff --git a/sigi/apps/solicitacoes/migrations/0001_initial.py b/sigi/apps/solicitacoes/migrations/0001_initial.py new file mode 100644 index 00000000..b1947164 --- /dev/null +++ b/sigi/apps/solicitacoes/migrations/0001_initial.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.5 on 2016-06-16 16:34 +from __future__ import absolute_import +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + (u'usuarios', u'__first__'), + ] + + operations = [ + migrations.CreateModel( + name=u'Sistema', + fields=[ + (u'id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name=u'ID')), + (u'sigla', models.CharField(max_length=10, verbose_name=u'Sigla')), + (u'nome', models.CharField(max_length=100, verbose_name=u'Nome Sistema')), + (u'descricao', models.TextField(blank=True, null=True, verbose_name=u'Descrição')), + ], + options={ + u'verbose_name_plural': u'Sistemas', + u'verbose_name': u'Sistema', + }, + ), + migrations.CreateModel( + name=u'Solicitacao', + fields=[ + (u'id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name=u'ID')), + (u'codigo', models.PositiveIntegerField(unique=True)), + (u'titulo', models.CharField(max_length=100, verbose_name=u'Título')), + (u'resumo', models.CharField(max_length=50, verbose_name=u'Resumo')), + (u'casa_legislativa', models.CharField(max_length=200, verbose_name=u'Casa Legislativa')), + (u'email_contato', models.EmailField(blank=True, max_length=254, null=True, verbose_name=u'Email de contato')), + (u'telefone_contato', models.CharField(blank=True, max_length=15, null=True, verbose_name=u'Telefone de contato')), + (u'data_criacao', models.DateTimeField(auto_now_add=True, verbose_name=u'Data de criação')), + (u'descricao', models.TextField(blank=True, null=True, verbose_name=u'Descrição')), + (u'sistema', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=u'solicitacoes.Sistema')), + (u'usuario', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=u'usuarios.Usuario')), + ], + options={ + u'ordering': [u'data_criacao'], + u'verbose_name_plural': u'Solicitações de Novos Serviços', + u'verbose_name': u'Solicitação de Novo Serviço', + }, + ), + ] diff --git a/sigi/apps/solicitacoes/migrations/0002_auto_20160616_1602.py b/sigi/apps/solicitacoes/migrations/0002_auto_20160616_1602.py new file mode 100644 index 00000000..8712b333 --- /dev/null +++ b/sigi/apps/solicitacoes/migrations/0002_auto_20160616_1602.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('solicitacoes', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='solicitacao', + name='email_contato', + field=models.EmailField(max_length=75, null=True, verbose_name='Email de contato', blank=True), + preserve_default=True, + ), + ] diff --git a/sigi/apps/solicitacoes/migrations/__init__.py b/sigi/apps/solicitacoes/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sigi/apps/solicitacoes/models.py b/sigi/apps/solicitacoes/models.py new file mode 100644 index 00000000..f6d025f7 --- /dev/null +++ b/sigi/apps/solicitacoes/models.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from sigi.apps.usuarios.models import Usuario + + +class Sistema(models.Model): + sigla = models.CharField(verbose_name=_(u'Sigla'), max_length=10) + nome = models.CharField(verbose_name=_(u'Nome Sistema'), + max_length=100) + descricao = models.TextField(null=True, + blank=True, + verbose_name=_(u'Descrição')) + + class Meta(object): + verbose_name = _(u'Sistema') + verbose_name_plural = _(u'Sistemas') + + def __str__(self): + return u"%s - %s" % (self.sigla, self.nome) + + +class Solicitacao(models.Model): + codigo = models.PositiveIntegerField(unique=True) + usuario = models.ForeignKey(Usuario) + sistema = models.ForeignKey(Sistema) + titulo = models.CharField(verbose_name=_(u'Título'), max_length=100) + resumo = models.CharField(verbose_name=_(u'Resumo'), max_length=50) + casa_legislativa = models.CharField(verbose_name=_(u'Casa Legislativa'), + max_length=200) + email_contato = models.EmailField(blank=True, + null=True, + verbose_name=_(u'Email de contato')) + # Substituir por usuarios.models.Telefone? + telefone_contato = models.CharField(max_length=15, + null=True, + blank=True, + verbose_name=_(u'Telefone de contato')) + + data_criacao = models.DateTimeField(auto_now_add=True, + verbose_name=_(u'Data de criação')) + descricao = models.TextField(blank=True, + null=True, + verbose_name=_(u'Descrição')) + + class Meta(object): + verbose_name = _(u'Solicitação de Novo Serviço') + verbose_name_plural = _(u'Solicitações de Novos Serviços') + ordering = [u'data_criacao'] + + def __str__(self): + return u"%s - %s" % (self.codigo, self.resumo) diff --git a/sigi/apps/solicitacoes/tests.py b/sigi/apps/solicitacoes/tests.py new file mode 100644 index 00000000..a79ca8be --- /dev/null +++ b/sigi/apps/solicitacoes/tests.py @@ -0,0 +1,3 @@ +# from django.test import TestCase + +# Create your tests here. diff --git a/sigi/apps/solicitacoes/urls.py b/sigi/apps/solicitacoes/urls.py new file mode 100644 index 00000000..83f13dde --- /dev/null +++ b/sigi/apps/solicitacoes/urls.py @@ -0,0 +1,13 @@ +from __future__ import absolute_import +from django.conf.urls import include, url + +from sigi.apps.solicitacoes.views import SistemaCrud, SolicitacaoCrud + +from .apps import AppConfig + +app_name = AppConfig.name + +urlpatterns = [ + url(ur'sistema/', include(SistemaCrud.get_urls())), + url(ur'solicitacao/', include(SolicitacaoCrud.get_urls())), +] diff --git a/sigi/apps/solicitacoes/views.py b/sigi/apps/solicitacoes/views.py new file mode 100644 index 00000000..6c8198d1 --- /dev/null +++ b/sigi/apps/solicitacoes/views.py @@ -0,0 +1,56 @@ +from __future__ import absolute_import +import random + +from django.contrib.auth.mixins import LoginRequiredMixin + +import sigi.apps.crud.base +from sigi.apps.crud.base import Crud, CrudCreateView, CrudListView, CrudCreateView, CrudUpdateView +from sigi.apps.usuarios.models import Usuario + +from .forms import SistemaForm, SolicitacaoEditForm, SolicitacaoForm +from .models import Sistema, Solicitacao + + +class SolicitacaoCrud(LoginRequiredMixin, Crud): + model = Solicitacao + help_path = u'' + + class CreateView(LoginRequiredMixin, CrudCreateView): + form_class = SolicitacaoForm + + def get_initial(self): + try: + usuario = Usuario.objects.get(user=self.request.user) + self.initial[u'usuario'] = usuario + self.initial[u'codigo'] = random.randint(0, 65500) + self.initial[u'email_contato'] = usuario.email + self.initial[u'telefone_contato'] = usuario.primeiro_telefone + except Usuario.DoesNotExist: + pass + return self.initial.copy() # TODO: por que? + + class UpdateView(LoginRequiredMixin, CrudUpdateView): + form_class = SolicitacaoEditForm + + @property + def layout_key(self): + return u'SolicitacaoEdit' + + class ListView(LoginRequiredMixin, CrudListView): + @property + def layout_key(self): + return u'SolicitacaoList' + + +class SistemaCrud(Crud): + model = Sistema + help_path = u'' + + class CreateView(LoginRequiredMixin, CrudCreateView): + form_class = SistemaForm + + class UpdateView(LoginRequiredMixin, CrudUpdateView): + form_class = SistemaForm + + class ListView(LoginRequiredMixin, CrudListView): + pass diff --git a/sigi/apps/templates/index.html b/sigi/apps/templates/index.html new file mode 100644 index 00000000..fb3a5487 --- /dev/null +++ b/sigi/apps/templates/index.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block title %} +

Bem-vindo ao Centro de Suporte

+{% endblock %} + +{% block base_content %} + Através deste sistema, você poderá solicitar novos serviços a serem + hospedados em nosso Data Center e acompanhar as solicitações já em aberto. + Cadastre-se para continuar. +{% endblock %} diff --git a/sigi/apps/usuarios/__init__.py b/sigi/apps/usuarios/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sigi/apps/usuarios/admin.py b/sigi/apps/usuarios/admin.py new file mode 100644 index 00000000..4185d360 --- /dev/null +++ b/sigi/apps/usuarios/admin.py @@ -0,0 +1,3 @@ +# from django.contrib import admin + +# Register your models here. diff --git a/sigi/apps/usuarios/apps.py b/sigi/apps/usuarios/apps.py new file mode 100644 index 00000000..d1851f69 --- /dev/null +++ b/sigi/apps/usuarios/apps.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import +from django import apps +from django.utils.translation import ugettext_lazy as _ + + +class AppConfig(apps.AppConfig): + name = u'usuarios' + verbose_name = _(u'Usuários') diff --git a/sigi/apps/usuarios/forms.py b/sigi/apps/usuarios/forms.py new file mode 100644 index 00000000..44ecba2e --- /dev/null +++ b/sigi/apps/usuarios/forms.py @@ -0,0 +1,450 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +from datetime import datetime + +from captcha.fields import CaptchaField +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Fieldset, Layout, Submit +from django import forms +from django.conf import settings +from django.contrib.auth.forms import (AuthenticationForm, PasswordResetForm, + SetPasswordForm) +from django.contrib.auth.models import User +from django.contrib.auth.password_validation import validate_password +from django.contrib.auth.tokens import default_token_generator +from django.core.exceptions import ValidationError +from django.core.mail import send_mail +from django.core.urlresolvers import reverse +from django.db import transaction +from django.forms import ModelForm +from django.utils.encoding import force_bytes +from django.utils.http import urlsafe_base64_encode +from django.utils.translation import ugettext_lazy as _ + + +import sigi.apps.crispy_layout_mixin +from sigi.apps.crud.utils import YES_NO_CHOICES +from sigi.apps.crispy_layout_mixin import form_actions + +from .models import Telefone, Usuario, ConfirmaEmail + + +class LoginForm(AuthenticationForm): + username = forms.CharField( + label=u"Username", max_length=30, + widget=forms.TextInput( + attrs={u'class': u'form-control', u'name': u'username'})) + + password = forms.CharField( + label=u"Password", max_length=30, + widget=forms.PasswordInput( + attrs={u'class': u'form-control', u'name': u'password'})) + + +class UsuarioForm(ModelForm): + # Telefone + TIPO_TELEFONE = [(u'FIXO', u'FIXO'), (u'CELULAR', u'CELULAR')] + + # Primeiro Telefone + primeiro_tipo = forms.ChoiceField( + widget=forms.Select(), + choices=TIPO_TELEFONE, + label=_(u'Tipo Telefone')) + primeiro_ddd = forms.CharField(max_length=2, label=_(u'DDD')) + primeiro_numero = forms.CharField(max_length=10, label=_(u'Número')) + primeiro_principal = forms.TypedChoiceField( + widget=forms.Select(), + label=_(u'Telefone Principal?'), + choices=YES_NO_CHOICES) + + # Primeiro Telefone + segundo_tipo = forms.ChoiceField( + required=False, + widget=forms.Select(), + choices=TIPO_TELEFONE, + label=_(u'Tipo Telefone')) + segundo_ddd = forms.CharField(required=False, max_length=2, label=_(u'DDD')) + segundo_numero = forms.CharField( + required=False, max_length=10, label=_(u'Número')) + segundo_principal = forms.ChoiceField( + required=False, + widget=forms.Select(), + label=_(u'Telefone Principal?'), + choices=YES_NO_CHOICES) + + # Usuário + password = forms.CharField( + max_length=20, + label=_(u'Senha'), + widget=forms.PasswordInput()) + + password_confirm = forms.CharField( + max_length=20, + label=_(u'Confirmar Senha'), + widget=forms.PasswordInput()) + + email_confirm = forms.EmailField( + required=True, + widget=forms.TextInput(attrs={u'style': u'text-transform:lowercase;'}), + label=_(u'Confirmar Email')) + + captcha = CaptchaField() + + class Meta(object): + model = Usuario + fields = [u'username', u'email', u'nome_completo', u'password', u'vinculo', + u'password_confirm', u'email_confirm', u'captcha', u'cpf', u'rg', + u'cargo', u'casa_legislativa'] + + widgets = {u'email': forms.TextInput( + attrs={u'style': u'text-transform:lowercase;'}),} + + def __init__(self, *args, **kwargs): + super(UsuarioForm, self).__init__(*args, **kwargs) + self.fields[u'rg'].widget.attrs[u'class'] = u'rg' + self.fields[u'cpf'].widget.attrs[u'class'] = u'cpf' + self.fields[u'primeiro_numero'].widget.attrs[u'class'] = u'telefone' + self.fields[u'primeiro_ddd'].widget.attrs[u'class'] = u'ddd' + self.fields[u'segundo_numero'].widget.attrs[u'class'] = u'telefone' + self.fields[u'segundo_ddd'].widget.attrs[u'class'] = u'ddd' + + def valida_igualdade(self, texto1, texto2, msg): + if texto1 != texto2: + raise ValidationError(msg) + return True + + def clean_primeiro_numero(self): + cleaned_data = self.cleaned_data + + telefone = Telefone() + telefone.tipo = self.data[u'primeiro_tipo'] + telefone.ddd = self.data[u'primeiro_ddd'] + telefone.numero = self.data[u'primeiro_numero'] + telefone.principal = self.data[u'primeiro_principal'] + + cleaned_data[u'primeiro_telefone'] = telefone + return cleaned_data + + def clean_segundo_numero(self): + cleaned_data = self.cleaned_data + + telefone = Telefone() + telefone.tipo = self.data[u'segundo_tipo'] + telefone.ddd = self.data[u'segundo_ddd'] + telefone.numero = self.data[u'segundo_numero'] + telefone.principal = self.data[u'segundo_principal'] + + cleaned_data[u'segundo_telefone'] = telefone + return cleaned_data + + def valida_email_existente(self): + return Usuario.objects.filter( + email=self.cleaned_data[u'email']).exists() + + def clean(self): + + if (u'password' not in self.cleaned_data or + u'password_confirm' not in self.cleaned_data): + raise ValidationError(_(u'Favor informar senhas atuais ou novas')) + + msg = _(u'As senhas não conferem.') + self.valida_igualdade( + self.cleaned_data[u'password'], + self.cleaned_data[u'password_confirm'], + msg) + + if (u'email' not in self.cleaned_data or + u'email_confirm' not in self.cleaned_data): + raise ValidationError(_(u'Favor informar endereços de email')) + + msg = _(u'Os emails não conferem.') + self.valida_igualdade( + self.cleaned_data[u'email'], + self.cleaned_data[u'email_confirm'], + msg) + + email_existente = self.valida_email_existente() + + if email_existente: + msg = _(u'Esse email já foi cadastrado.') + raise ValidationError(msg) + + try: + validate_password(self.cleaned_data[u'password']) + except ValidationError, error: + raise ValidationError(error) + + return self.cleaned_data + + @transaction.atomic + def save(self, commit=False): + usuario = super(UsuarioForm, self).save(commit) + + # Cria telefones + tel = Telefone.objects.create( + tipo=self.data[u'primeiro_tipo'], + ddd=self.data[u'primeiro_ddd'], + numero=self.data[u'primeiro_numero'], + principal=self.data[u'primeiro_principal'] + ) + usuario.primeiro_telefone = tel + + tel = self.cleaned_data[u'segundo_telefone'] + if (tel.tipo and tel.ddd and tel.numero and tel.principal): + tel = Telefone.objects.create( + tipo=self.data[u'segundo_tipo'], + ddd=self.data[u'segundo_ddd'], + numero=self.data[u'segundo_numero'], + principal=self.data[u'segundo_principal'] + ) + usuario.segundo_telefone = tel + + # Cria User + u = User.objects.create(username=usuario.username, email=usuario.email) + u.set_password(self.cleaned_data[u'password']) + u.is_active = False + + u.save() + usuario.user = u + usuario.save() + + +class UsuarioEditForm(UsuarioForm): + + class Meta(object): + model = Usuario + fields = [u'username', u'email', u'nome_completo', u'vinculo', + u'email_confirm', u'captcha', u'cpf', u'rg', + u'cargo', u'casa_legislativa'] + widgets = {u'username': forms.TextInput(attrs={u'readonly': u'readonly'}), + u'email': forms.TextInput( + attrs={u'style': u'text-transform:lowercase;'}), + } + + def __init__(self, *args, **kwargs): + super(UsuarioEditForm, self).__init__(*args, **kwargs) + self.fields[u'email_confirm'].initial = self.instance.email + self.fields.pop(u'password') + self.fields.pop(u'password_confirm') + + def valida_email_existente(self): + u'''Não permite atualizar emails para + emails existentes de outro usuário + ''' + return Usuario.objects.filter( + email=self.cleaned_data[u'email']).exclude( + user__username=self.cleaned_data[u'username']).exists() + + def clean(self): + + if (u'email' not in self.cleaned_data or + u'email_confirm' not in self.cleaned_data): + raise ValidationError(_(u'Favor informar endereços de email')) + + msg = _(u'Os emails não conferem.') + self.valida_igualdade( + self.cleaned_data[u'email'], + self.cleaned_data[u'email_confirm'], + msg) + + email_existente = self.valida_email_existente() + + if email_existente: + msg = _(u'Esse email já foi cadastrado.') + raise ValidationError(msg) + + return self.cleaned_data + + @transaction.atomic + def save(self, commit=False): + + usuario = super(UsuarioForm, self).save(commit) + + # Primeiro telefone + tel = usuario.primeiro_telefone + + tel.tipo = self.data[u'primeiro_tipo'] + tel.ddd = self.data[u'primeiro_ddd'] + tel.numero = self.data[u'primeiro_numero'] + tel.principal = self.data[u'primeiro_principal'] + tel.save() + + usuario.primeiro_telefone = tel + + # Segundo telefone + tel = usuario.segundo_telefone + + if tel: + tel.tipo = self.data[u'segundo_tipo'] + tel.ddd = self.data[u'segundo_ddd'] + tel.numero = self.data[u'segundo_numero'] + tel.principal = self.data[u'segundo_principal'] + tel.save() + usuario.segundo_telefone = tel + + tel = self.cleaned_data[u'segundo_telefone'] + if (tel.tipo and tel.ddd and tel.numero and tel.principal): + tel = Telefone.objects.create( + tipo=self.data[u'segundo_tipo'], + ddd=self.data[u'segundo_ddd'], + numero=self.data[u'segundo_numero'], + principal=self.data[u'segundo_principal'] + ) + usuario.segundo_telefone = tel + + # User + u = usuario.user + u.email = usuario.email + u.save() + + usuario.data_ultima_atualizacao = datetime.now() + usuario.save() + return usuario + + +class HabilitarEditForm(ModelForm): + habilitado = forms.ChoiceField( + widget=forms.Select(), + required=True, + choices=YES_NO_CHOICES) + + class Meta(object): + model = Usuario + fields = [u'cpf', u'nome_completo', u'email', u'habilitado'] + widgets = { + u'cpf': forms.TextInput(attrs={u'readonly': u'readonly'}), + u'nome_completo': forms.TextInput(attrs={u'readonly': u'readonly'}), + u'email': forms.TextInput(attrs={u'readonly': u'readonly'}) + } + + def __init__(self, *args, **kwargs): + super(HabilitarEditForm, self).__init__(*args, **kwargs) + row1 = crispy_layout_mixin.to_row( + [(u'nome_completo', 4), + (u'cpf', 4), + (u'email', 4)]) + row2 = crispy_layout_mixin.to_row([(u'habilitado', 12)]) + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset( + _(u'Editar usuário'), row1, row2, form_actions( + more=[ + Submit( + u'Cancelar', + u'Cancelar', + style=u'background-color:black; color:white;')]) + ) + ) + + +class MudarSenhaForm(ModelForm): + + password = forms.CharField( + max_length=20, + label=_(u'Nova Senha'), + widget=forms.PasswordInput()) + + password_confirm = forms.CharField( + max_length=20, + label=_(u'Confirmar Nova Senha'), + widget=forms.PasswordInput()) + + captcha = CaptchaField() + + def valida_igualdade(self, texto1, texto2, msg): + if texto1 != texto2: + raise ValidationError(msg) + return True + + def clean(self): + if (u'password' not in self.cleaned_data or + u'password_confirm' not in self.cleaned_data): + raise ValidationError(_(u'Favor informar senhas atuais \ + ou novas')) + + msg = _(u'As senhas não conferem.') + self.valida_igualdade( + self.cleaned_data[u'password'], + self.cleaned_data[u'password_confirm'], + msg) + + try: + validate_password(self.cleaned_data[u'password']) + except ValidationError, error: + raise ValidationError(error) + + class Meta(object): + model = Usuario + fields = [u'password', u'password_confirm', u'captcha'] + + def __init__(self, *args, **kwargs): + super(MudarSenhaForm, self).__init__(*args, **kwargs) + row1 = crispy_layout_mixin.to_row( + [(u'password', 6), + (u'password_confirm', 6)]) + row2 = crispy_layout_mixin.to_row([(u'captcha', 12)]) + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset( + _(u'Mudar Senha'), row1, row2, + form_actions( + more=[ + Submit( + u'Cancelar', + u'Cancelar', + style=u'background-color:black; color:white;')]) + ) + ) + + +class RecuperarSenhaEmailForm(PasswordResetForm): + + def __init__(self, *args, **kwargs): + super(RecuperarSenhaEmailForm, self).__init__(*args, **kwargs) + row1 = crispy_layout_mixin.to_row( + [(u'email', 6)]) + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset(_(u'Recuperar Senha'), + row1, + form_actions( + more=[ + Submit( + u'Cancelar', + u'Cancelar', + style=u'background-color:black; color:white;')]) + ) + ) + + def clean(self): + email_existente_usuario = Usuario.objects.filter( + email=self.cleaned_data[u'email']) + email_existente_user = User.objects.filter( + email=self.cleaned_data[u'email']) + + if not email_existente_usuario and not email_existente_user: + msg = _(u'Não existe nenhum usuário cadastrado com este e-mail.') + raise ValidationError(msg) + + return self.cleaned_data + + +class RecuperacaoMudarSenhaForm(SetPasswordForm): + def __init__(self, *args, **kwargs): + super(RecuperacaoMudarSenhaForm, self).__init__(*args, **kwargs) + self.fields[u'new_password1'].help_text = u'' + row1 = crispy_layout_mixin.to_row( + [(u'new_password1', 6), + (u'new_password2', 6)]) + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset(_(u''), + row1, + form_actions( + more=[ + Submit( + u'Cancelar', + u'Cancelar', + style=u'background-color:black; color:white;')]) + ) + ) diff --git a/sigi/apps/usuarios/layouts.yaml b/sigi/apps/usuarios/layouts.yaml new file mode 100644 index 00000000..7cd74757 --- /dev/null +++ b/sigi/apps/usuarios/layouts.yaml @@ -0,0 +1,46 @@ + +CasaLegislativa: + Casa Legislativa: + - sigla:4 nome + - endereco:8 cep + - municipio:8 uf + - email + - telefone + - endereco_web + +Subsecretaria: + Subsecretaria: + - nome:8 sigla + +Usuario: + Usuário: + - username nome_completo:9 + - password password_confirm + - email email_confirm + - cpf rg casa_legislativa + - cargo vinculo + Primeiro Telefone: + - primeiro_tipo primeiro_ddd:2 primeiro_numero:6 primeiro_principal + Segundo Telefone: + - segundo_tipo segundo_ddd:2 segundo_numero:6 segundo_principal + Prove que você é um humano: + - captcha + +UsuarioEdit: + Usuário: + - username nome_completo:9 + - email email_confirm + - cpf rg casa_legislativa + - cargo vinculo + Primeiro Telefone: + - primeiro_tipo primeiro_ddd:2 primeiro_numero:6 primeiro_principal + Segundo Telefone: + - segundo_tipo segundo_ddd:2 segundo_numero:6 segundo_principal + Prove que você é um humano: + - captcha + +UsuarioDetail: + Perfil: + - nome_completo email + - cargo vinculo + - cpf rg casa_legislativa diff --git a/sigi/apps/usuarios/migrations/0001_initial.py b/sigi/apps/usuarios/migrations/0001_initial.py new file mode 100644 index 00000000..bad7bb64 --- /dev/null +++ b/sigi/apps/usuarios/migrations/0001_initial.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.5 on 2016-06-16 16:34 +from __future__ import absolute_import +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name=u'CasaLegislativa', + fields=[ + (u'id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name=u'ID')), + (u'nome', models.CharField(max_length=100, verbose_name=u'Nome')), + (u'sigla', models.CharField(max_length=100, verbose_name=u'Sigla')), + (u'endereco', models.CharField(max_length=100, verbose_name=u'Endereço')), + (u'cep', models.CharField(max_length=100, verbose_name=u'CEP')), + (u'municipio', models.CharField(max_length=100, verbose_name=u'Município')), + (u'uf', models.CharField(choices=[(u'AC', u'Acre'), (u'AL', u'Alagoas'), (u'AP', u'Amapá'), (u'AM', u'Amazonas'), (u'BA', u'Bahia'), (u'CE', u'Ceará'), (u'DF', u'Distrito Federal'), (u'ES', u'Espírito Santo'), (u'GO', u'Goiás'), (u'MA', u'Maranhão'), (u'MT', u'Mato Grosso'), (u'MS', u'Mato Grosso do Sul'), (u'MG', u'Minas Gerais'), (u'PR', u'Paraná'), (u'PB', u'Paraíba'), (u'PA', u'Pará'), (u'PE', u'Pernambuco'), (u'PI', u'Piauí'), (u'RJ', u'Rio de Janeiro'), (u'RN', u'Rio Grande do Norte'), (u'RS', u'Rio Grande do Sul'), (u'RO', u'Rondônia'), (u'RR', u'Roraima'), (u'SC', u'Santa Catarina'), (u'SE', u'Sergipe'), (u'SP', u'São Paulo'), (u'TO', u'Tocantins'), (u'EX', u'Exterior')], max_length=100, verbose_name=u'UF')), + (u'telefone', models.CharField(blank=True, max_length=100, verbose_name=u'Telefone')), + (u'endereco_web', models.URLField(blank=True, max_length=100, verbose_name=u'HomePage')), + (u'email', models.EmailField(blank=True, max_length=100, verbose_name=u'E-mail')), + ], + options={ + u'verbose_name': u'Casa Legislativa', + u'verbose_name_plural': u'Casas Legislativas', + }, + ), + migrations.CreateModel( + name=u'Subsecretaria', + fields=[ + (u'id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name=u'ID')), + (u'nome', models.CharField(max_length=100, null=True, verbose_name=u'Nome')), + (u'sigla', models.CharField(max_length=10, null=True, verbose_name=u'Sigla')), + ], + options={ + u'verbose_name': u'Subsecretaria', + u'ordering': (u'nome', u'sigla'), + u'verbose_name_plural': u'Subsecretarias', + }, + ), + migrations.CreateModel( + name=u'Telefone', + fields=[ + (u'id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name=u'ID')), + (u'tipo', models.CharField(choices=[(u'FIXO', u'FIXO'), (u'CELULAR', u'CELULAR')], max_length=7, verbose_name=u'Tipo Telefone')), + (u'ddd', models.CharField(max_length=2, verbose_name=u'DDD')), + (u'numero', models.CharField(max_length=10, verbose_name=u'Número')), + (u'principal', models.CharField(choices=[(None, u'----'), (False, u'Não'), (True, u'Sim')], max_length=10, verbose_name=u'Telefone Principal?')), + ], + options={ + u'verbose_name': u'Telefone', + u'verbose_name_plural': u'Telefones', + }, + ), + migrations.CreateModel( + name=u'Usuario', + fields=[ + (u'id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name=u'ID')), + (u'username', models.CharField(max_length=50, unique=True, verbose_name=u'Nome de Usuário')), + (u'nome_completo', models.CharField(max_length=128, verbose_name=u'Nome Completo')), + (u'data_criacao', models.DateTimeField(default=django.utils.timezone.now, verbose_name=u'Data Criação')), + (u'data_ultima_atualizacao', models.DateTimeField(default=django.utils.timezone.now, verbose_name=u'Última atualização')), + (u'email', models.EmailField(max_length=254, unique=True, verbose_name=u'Email')), + (u'habilitado', models.BooleanField(default=False, verbose_name=u'Habilitado?')), + (u'conveniado', models.BooleanField(default=False)), + (u'responsavel', models.BooleanField(default=False)), + (u'rg', models.CharField(max_length=9, null=True, verbose_name=u'RG')), + (u'cpf', models.CharField(default=u'00000000000', max_length=11, verbose_name=u'CPF')), + (u'cargo', models.CharField(default=u'--------', max_length=30, verbose_name=u'Cargo')), + (u'vinculo', models.CharField(choices=[(u'Tercerizado', u'Tercerizado'), (u'Efetivo', u'Efetivo'), (u'Contratado', u'Contratado')], default=u'--------', max_length=30, verbose_name=u'Vinculo')), + (u'casa_legislativa', models.CharField(default=u'--------', max_length=30, verbose_name=u'Casa Legislativa')), + (u'primeiro_telefone', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name=u'primeiro_telefone', to=u'usuarios.Telefone')), + (u'segundo_telefone', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name=u'segundo_telefone', to=u'usuarios.Telefone')), + (u'user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + u'verbose_name': u'Usuário', + u'verbose_name_plural': u'Usuários', + }, + ), + ] diff --git a/sigi/apps/usuarios/migrations/0002_auto_20160616_1400.py b/sigi/apps/usuarios/migrations/0002_auto_20160616_1400.py new file mode 100644 index 00000000..7b3f83a5 --- /dev/null +++ b/sigi/apps/usuarios/migrations/0002_auto_20160616_1400.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.5 on 2016-06-16 17:00 +from __future__ import absolute_import +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + (u'usuarios', u'0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name=u'ConfirmaEmail', + fields=[ + (u'id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name=u'ID')), + (u'email', models.EmailField(max_length=254, unique=True, verbose_name=u'Email')), + (u'confirmado', models.BooleanField(default=False)), + (u'token', models.CharField(max_length=50, verbose_name=u'Hash do Email')), + (u'user_id', models.TextField(blank=True, verbose_name=u'ID do Usuário')), + ], + options={ + u'verbose_name': u'Email', + u'verbose_name_plural': u'Emails', + }, + ), + migrations.AddField( + model_name=u'usuario', + name=u'email_confirmado', + field=models.BooleanField(default=False, verbose_name=u'Email confirmado?'), + ), + ] diff --git a/sigi/apps/usuarios/migrations/0003_auto_20160616_1602.py b/sigi/apps/usuarios/migrations/0003_auto_20160616_1602.py new file mode 100644 index 00000000..c45f0e6b --- /dev/null +++ b/sigi/apps/usuarios/migrations/0003_auto_20160616_1602.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('usuarios', '0002_auto_20160616_1400'), + ] + + operations = [ + migrations.AlterField( + model_name='confirmaemail', + name='email', + field=models.EmailField(unique=True, max_length=75, verbose_name='Email'), + preserve_default=True, + ), + migrations.AlterField( + model_name='usuario', + name='email', + field=models.EmailField(unique=True, max_length=75, verbose_name='Email'), + preserve_default=True, + ), + ] diff --git a/sigi/apps/usuarios/migrations/__init__.py b/sigi/apps/usuarios/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sigi/apps/usuarios/models.py b/sigi/apps/usuarios/models.py new file mode 100644 index 00000000..fc31e816 --- /dev/null +++ b/sigi/apps/usuarios/models.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +import base64 +from django.contrib.auth.models import User +from django.db import models +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ + +from sigi.apps.crud.utils import UF, YES_NO_CHOICES + + +class CasaLegislativa(models.Model): + nome = models.CharField(max_length=100, verbose_name=_(u'Nome')) + sigla = models.CharField(max_length=100, verbose_name=_(u'Sigla')) + endereco = models.CharField(max_length=100, verbose_name=_(u'Endereço')) + cep = models.CharField(max_length=100, verbose_name=_(u'CEP')) + municipio = models.CharField(max_length=100, verbose_name=_(u'Município')) + uf = models.CharField(max_length=100, + choices=UF, + verbose_name=_(u'UF')) + telefone = models.CharField( + max_length=100, blank=True, verbose_name=_(u'Telefone')) + endereco_web = models.URLField( + max_length=100, blank=True, verbose_name=_(u'HomePage')) + email = models.EmailField( + max_length=100, blank=True, verbose_name=_(u'E-mail')) + + class Meta(object): + verbose_name = _(u'Casa Legislativa') + verbose_name_plural = _(u'Casas Legislativas') + + def __str__(self): + return u'[%s] %s' % (self.sigla, self.nome) + + +class Subsecretaria(models.Model): + + nome = models.CharField(verbose_name=_(u'Nome'), max_length=100, null=True) + sigla = models.CharField(verbose_name=_(u'Sigla'), max_length=10, null=True) + + class Meta(object): + ordering = (u'nome', u'sigla') + verbose_name = _(u'Subsecretaria') + verbose_name_plural = _(u'Subsecretarias') + + def __str__(self): + return u'[%s] %s' % (self.sigla, self.nome) + + +class Telefone(models.Model): + TIPO_TELEFONE = [(u'FIXO', u'FIXO'), (u'CELULAR', u'CELULAR')] + + tipo = models.CharField( + max_length=7, + choices=TIPO_TELEFONE, + verbose_name=_(u'Tipo Telefone'),) + ddd = models.CharField(max_length=2, verbose_name=_(u'DDD')) + numero = models.CharField(max_length=10, verbose_name=_(u'Número')) + principal = models.CharField( + max_length=10, + verbose_name=_(u'Telefone Principal?'), + choices=YES_NO_CHOICES) + + class Meta(object): + verbose_name = _(u'Telefone') + verbose_name_plural = _(u'Telefones') + + def __str__(self): + return u'(%s) %s' % (self.ddd, self.numero) + + +class ConfirmaEmail(models.Model): + u""" + Classe de email + """ + email = models.EmailField(unique=True, verbose_name=_(u'Email')) + confirmado = models.BooleanField(default=False) + token = models.CharField( + max_length=50, verbose_name=_(u'Hash do Email')) + user_id = models.TextField(blank=True, verbose_name=_(u'ID do Usuário')) + + class Meta(object): + verbose_name = _(u'Email') + verbose_name_plural = _(u'Emails') + + +class Usuario(models.Model): + u''' + Usuário cadastrado via web + ''' + + TIPO_VINCULO = [(u'Tercerizado', u'Tercerizado'), + (u'Efetivo', u'Efetivo'), + (u'Contratado', u'Contratado')] + + user = models.ForeignKey(User) + username = models.CharField( + verbose_name=_(u'Nome de Usuário'), + unique=True, + max_length=50) + nome_completo = models.CharField( + verbose_name=_(u'Nome Completo'), + max_length=128) + data_criacao = models.DateTimeField( + _(u'Data Criação'), + default=timezone.now) + data_ultima_atualizacao = models.DateTimeField( + default=timezone.now, verbose_name=_(u'Última atualização')) + email = email = models.EmailField(unique=True, verbose_name=_(u'Email')) + email_confirmado = models.BooleanField( + default=False, verbose_name=_(u'Email confirmado?')) + habilitado = models.BooleanField( + default=False, + verbose_name=_(u'Habilitado?')) + conveniado = models.BooleanField(default=False) + responsavel = models.BooleanField(default=False) + rg = models.CharField( + max_length=9, + null=True, + verbose_name=_(u'RG')) + cpf = models.CharField( + max_length=11, + verbose_name=_(u'CPF'), + default=u'00000000000') + cargo = models.CharField( + max_length=30, + verbose_name=_(u'Cargo'), + default=u'--------') + vinculo = models.CharField( + max_length=30, + verbose_name=_(u'Vinculo'), + choices=TIPO_VINCULO, + default=u'--------') + casa_legislativa = models.CharField( + max_length=30, + verbose_name=_(u'Casa Legislativa'), + default=u'--------') + primeiro_telefone = models.ForeignKey( + Telefone, null=True, related_name=u'primeiro_telefone') + segundo_telefone = models.ForeignKey( + Telefone, null=True, related_name=u'segundo_telefone') + + class Meta(object): + verbose_name = _(u'Usuário') + verbose_name_plural = _(u'Usuários') + + def __str__(self): + return self.username diff --git a/sigi/apps/usuarios/tests.py b/sigi/apps/usuarios/tests.py new file mode 100644 index 00000000..a79ca8be --- /dev/null +++ b/sigi/apps/usuarios/tests.py @@ -0,0 +1,3 @@ +# from django.test import TestCase + +# Create your tests here. diff --git a/sigi/apps/usuarios/urls.py b/sigi/apps/usuarios/urls.py new file mode 100644 index 00000000..fdcd9a23 --- /dev/null +++ b/sigi/apps/usuarios/urls.py @@ -0,0 +1,61 @@ +from __future__ import absolute_import +from django.conf.urls import include, url +from django.contrib.auth.views import (login, logout, password_reset, + password_reset_done, + password_reset_confirm, + password_reset_complete) +#from atendimento.settings import EMAIL_SEND_USER +from sigi.apps.usuarios.forms import (LoginForm, RecuperarSenhaEmailForm, + RecuperacaoMudarSenhaForm) +from sigi.apps.usuarios.views import (HabilitarDetailView, HabilitarEditView, + MudarSenhaView, UsuarioCrud, ConfirmarEmailView) + +from .apps import AppConfig + +app_name = AppConfig.name + +EMAIL_SEND_USER='atendimento@interlegis.leg.br' + +recuperar_email = [ + url(ur'^recuperar/recuperar_senha/$', + password_reset, + {u'template_name': u'usuarios/recuperar_senha.html', + u'password_reset_form': RecuperarSenhaEmailForm, + u'post_reset_redirect': u'usuarios:recuperar_senha_finalizado', + u'email_template_name': u'usuarios/recuperar_senha_email.html', + u'from_email': EMAIL_SEND_USER, + u'html_email_template_name': u'usuarios/recuperar_senha_email.html'}, + name=u'recuperar_senha'), + url(ur'^recuperar/recuperar_recuperar/finalizado/$', + password_reset_done, + {u'template_name': u'usuarios/recuperar_senha_enviado.html'}, + name=u'recuperar_senha_finalizado'), + url(ur'^recuperar/(?P[0-9A-Za-z_\-]+)/(?P.+)/$', + password_reset_confirm, + {u'post_reset_redirect': u'usuarios:recuperar_senha_completo', + u'template_name': u'usuarios/recuperacao_senha_form.html', + u'set_password_form': RecuperacaoMudarSenhaForm}, + name=u'recuperar_senha_confirma'), + url(ur'^recuperar/completo/$', + password_reset_complete, + {u'template_name': u'usuarios/recuperacao_senha_completo.html'}, + name=u'recuperar_senha_completo'), +] + +urlpatterns = recuperar_email + [ + url(ur'^login/$', login, { + u'template_name': u'usuarios/login.html', + u'authentication_form': LoginForm}, + name=u'login'), + url(ur'^logout/$', logout, {u'next_page': u'/login'}, name=u'logout'), + url(ur'^usuario/', include(UsuarioCrud.get_urls())), + + url(ur'^habilitar/(?P\d+)$', + HabilitarDetailView.as_view(), name=u'habilitar_detail'), + url(ur'^habilitar/(?P\d+)/edit$', + HabilitarEditView.as_view(), name=u'habilitar_edit'), + url(ur'^usuario/(?P\d+)/mudar_senha$', + MudarSenhaView.as_view(), name=u'mudar_senha'), + url(ur'^usuario/confirmar/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})$', + ConfirmarEmailView.as_view(), name=u'confirmar_email'), +] diff --git a/sigi/apps/usuarios/utils.py b/sigi/apps/usuarios/utils.py new file mode 100644 index 00000000..53bf2b2d --- /dev/null +++ b/sigi/apps/usuarios/utils.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +from django.utils.translation import ugettext_lazy as _ + +UF = [ + (u'AC', u'Acre'), + (u'AL', u'Alagoas'), + (u'AP', u'Amapá'), + (u'AM', u'Amazonas'), + (u'BA', u'Bahia'), + (u'CE', u'Ceará'), + (u'DF', u'Distrito Federal'), + (u'ES', u'Espírito Santo'), + (u'GO', u'Goiás'), + (u'MA', u'Maranhão'), + (u'MT', u'Mato Grosso'), + (u'MS', u'Mato Grosso do Sul'), + (u'MG', u'Minas Gerais'), + (u'PR', u'Paraná'), + (u'PB', u'Paraíba'), + (u'PA', u'Pará'), + (u'PE', u'Pernambuco'), + (u'PI', u'Piauí'), + (u'RJ', u'Rio de Janeiro'), + (u'RN', u'Rio Grande do Norte'), + (u'RS', u'Rio Grande do Sul'), + (u'RO', u'Rondônia'), + (u'RR', u'Roraima'), + (u'SC', u'Santa Catarina'), + (u'SE', u'Sergipe'), + (u'SP', u'São Paulo'), + (u'TO', u'Tocantins'), + (u'EX', u'Exterior'), +] + +YES_NO_CHOICES = [(None, _(u'----')), (False, _(u'Não')), (True, _(u'Sim'))] + + +def str2bool(v): + return v in (u'Sim', u'True') + + +SEXO_CHOICES = [(u'M', _(u'Masculino')), (u'F', _(u'Feminino'))] + + +def from_to(start, end): + return range(start, end + 1) + + +def make_pagination(index, num_pages): + PAGINATION_LENGTH = 10 + if num_pages <= PAGINATION_LENGTH: + return from_to(1, num_pages) + else: + if index - 1 <= 5: + tail = [num_pages - 1, num_pages] + head = from_to(1, PAGINATION_LENGTH - 3) + else: + if index + 1 >= num_pages - 3: + tail = from_to(index - 1, num_pages) + else: + tail = [index - 1, index, index + 1, + None, num_pages - 1, num_pages] + head = from_to(1, PAGINATION_LENGTH - len(tail) - 1) + return head + [None] + tail diff --git a/sigi/apps/usuarios/views.py b/sigi/apps/usuarios/views.py new file mode 100644 index 00000000..bfbbe039 --- /dev/null +++ b/sigi/apps/usuarios/views.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import +from braces.views import FormValidMessageMixin +from django.conf import settings +from django.contrib.auth.mixins import LoginRequiredMixin +from django.core.urlresolvers import reverse +from django.utils import timezone +from django.views.generic import DetailView, FormView, TemplateView +import sigi.apps.crud.base +from django.core.mail import send_mail +from sigi.apps.crud.utils import str2bool +from django.contrib.auth.tokens import default_token_generator +from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode +from django.utils.encoding import force_bytes +from sigi.apps.crud.base import Crud, CrudBaseMixin, CrudCreateView, CrudListView, CrudUpdateView, CrudDetailView + +from .forms import (HabilitarEditForm, MudarSenhaForm, UsuarioEditForm, + UsuarioForm) +from .models import Usuario, ConfirmaEmail, User + + +class UsuarioCrud(Crud): + model = Usuario + help_path = u'' + + class CreateView(CrudCreateView): + form_class = UsuarioForm + form_valid_message = u'Cadastro realizado com sucesso. Aguarde a \ + validação do seu perfil.' + + def get_success_url(self): + kwargs = {} + user = User.objects.get(email=self.request.POST.get(u'email')) + confirmar_email = ConfirmaEmail( + email=user.email, + token=default_token_generator.make_token(user), + user_id=urlsafe_base64_encode(force_bytes(user.pk))) + confirmar_email.save() + + kwargs[u'token'] = confirmar_email.token + kwargs[u'uidb64'] = confirmar_email.user_id + assunto = u"Cadastro no Sistema de Atendimento ao Usuário" + full_url = self.request.get_raw_uri(), + url_base = full_url[0][:full_url[0].find(u'usuario') - 1], + mensagem = (u"Este e-mail foi utilizado para fazer cadastro no " + + u"Sistema de Atendimento ao Usuário do Interlegis.\n" + + u"Caso você não tenha feito este cadastro, por favor " + + u"ignore esta mensagem.\n" + url_base[0] + + reverse(u'usuarios:confirmar_email', kwargs=kwargs)) + remetente = settings.EMAIL_HOST_USER + destinatario = [confirmar_email.email, + settings.EMAIL_HOST_USER] + send_mail(assunto, mensagem, remetente, destinatario, + fail_silently=False) + return reverse(u'home') + + class ListView(LoginRequiredMixin, CrudListView): + pass + + class UpdateView(LoginRequiredMixin, CrudUpdateView): + form_class = UsuarioEditForm + + def get_initial(self): + if self.get_object(): + + tel1 = self.get_object().primeiro_telefone + self.initial[u'primeiro_tipo'] = tel1.tipo + self.initial[u'primeiro_ddd'] = tel1.ddd + self.initial[u'primeiro_numero'] = tel1.numero + self.initial[u'primeiro_principal'] = tel1.principal + + tel2 = self.get_object().segundo_telefone + if tel2: + self.initial[u'segundo_tipo'] = tel2.tipo + self.initial[u'segundo_ddd'] = tel2.ddd + self.initial[u'segundo_numero'] = tel2.numero + self.initial[u'segundo_principal'] = tel2.principal + + return self.initial.copy() + + @property + def layout_key(self): + return u'UsuarioEdit' + + class DetailView(LoginRequiredMixin, CrudDetailView): + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + + tel1 = context[u'object'].primeiro_telefone + tel1 = [(u'Primeiro Telefone'), + (u'[%s] - %s' % (tel1.ddd, tel1.numero))] + + tel2 = context[u'object'].segundo_telefone or u'' + if tel2: + tel2 = [(u'Segundo Telefone'), + (u'[%s] - %s' % (tel2.ddd, tel2.numero))] + + context[u'telefones'] = [tel1, tel2] + return context + + @property + def layout_key(self): + return u'UsuarioDetail' + + class BaseMixin(CrudBaseMixin): + list_field_names = [u'username', u'nome_completo', + u'data_criacao', u'habilitado', + u'data_ultima_atualizacao'] + + +class HabilitarDetailView(CrudDetailView): + template_name = u"usuarios/habilitar_detail.html" + + def get(self, request, *args, **kwargs): + context = {} + context[u'pk'] = self.kwargs[u'pk'] + context[u'usuario'] = Usuario.objects.get(pk=self.kwargs[u'pk']) + return self.render_to_response(context) + + +class HabilitarEditView(FormView): + template_name = u"crud/form.html" + + def get(self, request, *args, **kwargs): + context = {} + + usuario = Usuario.objects.get(pk=self.kwargs[u'pk']) + form = HabilitarEditForm(instance=usuario) + + context[u'pk'] = self.kwargs[u'pk'] + context[u'form'] = form + return self.render_to_response(context) + + def post(self, request, *args, **kwargs): + form = HabilitarEditForm(request.POST) + usuario = Usuario.objects.get(pk=self.kwargs[u'pk']) + usuario.habilitado = str2bool(form.data[u'habilitado']) + usuario.data_ultima_atualizacao = timezone.now() + + usuario.save() + return self.form_valid(form) + + def get_success_url(self): + return reverse(u'usuarios:usuario_list') + + +class MudarSenhaView(FormValidMessageMixin, FormView): + template_name = u"crud/form.html" + form_class = MudarSenhaForm + form_valid_message = u'Senha alterada com sucesso. É necessário fazer \ + login novamente.' + + def get(self, request, *args, **kwargs): + context = {} + usuario = Usuario.objects.get(pk=self.kwargs[u'pk']) + form = MudarSenhaForm(instance=usuario) + context[u'pk'] = self.kwargs[u'pk'] + context[u'form'] = self.get_form() + return self.render_to_response(context) + + def form_valid(self, form): + usuario = Usuario.objects.get(pk=self.kwargs[u'pk']) + u = usuario.user + u.set_password(form.cleaned_data[u'password']) + u.save() + return super(MudarSenhaView, self).form_valid(form) + + def get_success_url(self): + return reverse(u'home') + + +class ConfirmarEmailView(TemplateView): + template_name = u"usuarios/confirma_email.html" + + def get(self, request, *args, **kwargs): + uid = urlsafe_base64_decode(self.kwargs[u'uidb64']) + user = User.objects.get(id=uid) + user.is_active = True + user.save() + context = self.get_context_data(**kwargs) + return self.render_to_response(context) diff --git a/sigi/settings/base.py b/sigi/settings/base.py index dd011415..245efff6 100644 --- a/sigi/settings/base.py +++ b/sigi/settings/base.py @@ -30,9 +30,10 @@ SITE_ID = 1 -TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + ( +TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + [ 'django.core.context_processors.request', -) +] + # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = ('django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', @@ -70,6 +71,10 @@ 'sigi.apps.eventos', 'sigi.apps.whois', + 'sigi.apps.crud', + 'sigi.apps.usuarios', + 'sigi.apps.solicitacoes', + # Integração com Saberes (moodle) 'sigi.apps.mdl', 'sigi.apps.saberes', @@ -82,13 +87,21 @@ 'image_cropping', 'rest_framework', + 'captcha', + 'crispy_forms', + 'djangobower', + 'floppyforms', + 'sass_processor', + ) MIDDLEWARE_CLASSES = ( + 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', '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', ) @@ -181,6 +194,40 @@ }, } +STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(BASE_DIR, "collected_static") +STATICFILES_DIRS = (os.path.join(BASE_DIR, ("static")),) +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + 'djangobower.finders.BowerFinder', + 'sass_processor.finders.CssFinder', +) + + +CRISPY_TEMPLATE_PACK = 'bootstrap3' +CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap3' +CRISPY_FAIL_SILENTLY = False + +BOWER_COMPONENTS_ROOT = os.path.join(BASE_DIR, 'bower') +BOWER_INSTALLED_APPS = ( + 'bootstrap-sass#3.3.6', + 'components-font-awesome#4.5.0', + 'tinymce#4.3.3', + 'jquery-ui#1.11.4', + 'jquery-runner#2.3.3', + 'jQuery-Mask-Plugin#1.13.4', + 'jsdiff#2.2.1', +) + +# Additional search paths for SASS files when using the @import statement +SASS_PROCESSOR_INCLUDE_DIRS = ( + os.path.join(BOWER_COMPONENTS_ROOT, 'bower_components'), + os.path.join(BOWER_COMPONENTS_ROOT, 'bootstrap-sass'), + os.path.join(BOWER_COMPONENTS_ROOT, 'assets'), + os.path.join(BOWER_COMPONENTS_ROOT, 'stylesheets'), +) + SABERES_REST_PATH = 'webservice/rest/server.php' OSTICKET_URL = 'https://suporte.interlegis.leg.br/scp/tickets.php?a=search&query=%s' diff --git a/sigi/urls.py b/sigi/urls.py index 7eae6fca..6e6392c4 100644 --- a/sigi/urls.py +++ b/sigi/urls.py @@ -26,6 +26,11 @@ url(r'^eventos/', include('sigi.apps.eventos.urls')), url(r'^whois/', include('sigi.apps.whois.urls')), url(r'^pentaho/(?P(plugin|api)/.*)$', pentaho_proxy), + + url(r'', include('sigi.apps.usuarios.urls')), + url(r'', include('sigi.apps.solicitacoes.urls')), + url(r'^captcha/', include('captcha.urls')), + url(r'^', include('sigi.apps.home.urls')), url(r'^', include(admin.site.urls)), diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 00000000..ac7b5209 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,155 @@ + +{% load staticfiles i18n sass_tags%} + + + + + Sistema de Atendimento + {% block head_content %} + + + + + + + {% endblock head_content %} + + + {% block navigation %} + + {% endblock navigation %} + + + {% block body_block %} + {# Header #} + {% block main_header %} + + {% endblock main_header %} + + {# Main content #} + {% block content_container %} +
+
+ {# Feedback messages #} + {% for message in messages %} + + {% endfor %} + + {# Content header #} + {% block base_header %} +
+
+ {% block title %} +

+ {% if title %} + {{ title|safe|linebreaksbr }} + {% elif object %} + {{ object|safe|linebreaksbr }} + {% endif %} +

+ {% endblock %} +
+
+ {% endblock base_header %} + + {# Content per se #} + {% block base_content %}{% endblock %} +
+
+ {% endblock content_container %} + {% endblock %} + {% block foot_js %} + + + + + + + {% block extra_js %}{% endblock extra_js %} + {% endblock foot_js %} + + + {% block footer %} +
+ + {% endblock footer %} + diff --git a/templates/crud/confirm_delete.html b/templates/crud/confirm_delete.html new file mode 100644 index 00000000..685c0d4a --- /dev/null +++ b/templates/crud/confirm_delete.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block base_content %} +
{% csrf_token %} +
+
+ {% blocktrans %} + Confirma exclusão de "{{ object }}"? + {% endblocktrans %} +
+ +
+
+{% endblock %} diff --git a/templates/crud/detail.html b/templates/crud/detail.html new file mode 100644 index 00000000..5ce33ced --- /dev/null +++ b/templates/crud/detail.html @@ -0,0 +1,38 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block base_content %} +
+ {% block actions %} + + {% endblock actions %} +
+ + {% block msg %} {% endblock msg %} + + {% block detail_content %} + {% for fieldset in view.layout_display %} +

{{ fieldset.legend }}

+ {% for row in fieldset.rows %} +
+ {% for column in row %} +
+
+

{{ column.verbose_name }}

+
+

{{ column.text|safe }}

+
+
+
+ {% endfor %} +
+ {% endfor %} + {% endfor %} + {% endblock detail_content %} + + {% block extrablock %}{% endblock %} + +{% endblock base_content %} diff --git a/templates/crud/form.html b/templates/crud/form.html new file mode 100644 index 00000000..9e357324 --- /dev/null +++ b/templates/crud/form.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% load i18n crispy_forms_tags %} +{% block base_content %} + {% crispy form %} +{% endblock %} + +{% block extra_js %} + +{% endblock %} diff --git a/templates/crud/list.html b/templates/crud/list.html new file mode 100644 index 00000000..20e9e916 --- /dev/null +++ b/templates/crud/list.html @@ -0,0 +1,51 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block base_content %} + + {% block buttons %} + + {% endblock buttons %} + + + {% block content %} + + {% block extra_content %} {% endblock extra_content %} + + {% if not rows %} +

{{ NO_ENTRIES_MSG }}

+ {% else %} + + + + {% for name in headers %} + + {% endfor %} + + + + {% for value_list in rows %} + + {% for value, href in value_list %} + + {% endfor %} + + {% endfor %} + +
{{ name }}
+ {% if href %} + {{ value }} + {% else %} + {{ value|safe }} + {% endif %} +
+ {% endif %} + + {% include "paginacao.html" %} + {% endblock content %} +{% endblock %} diff --git a/templates/menus/subnav.html b/templates/menus/subnav.html new file mode 100644 index 00000000..85fdecb8 --- /dev/null +++ b/templates/menus/subnav.html @@ -0,0 +1,23 @@ +{% load i18n %} + +{% if menu %} + +{% endif %} diff --git a/templates/paginacao.html b/templates/paginacao.html new file mode 100644 index 00000000..c2e871f7 --- /dev/null +++ b/templates/paginacao.html @@ -0,0 +1,37 @@ +{% if is_paginated %} + +{% endif %} diff --git a/templates/templatetags/__init__.py b/templates/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/templates/templatetags/common_tags.py b/templates/templatetags/common_tags.py new file mode 100644 index 00000000..dc38f101 --- /dev/null +++ b/templates/templatetags/common_tags.py @@ -0,0 +1,38 @@ +from compressor.utils import get_class +from django import template + +register = template.Library() + + +@register.simple_tag +def field_verbose_name(instance, field_name): + return instance._meta.get_field(field_name).verbose_name + + +@register.simple_tag +def fieldclass_verbose_name(class_name, field_name): + cls = get_class(class_name) + return cls._meta.get_field(field_name).verbose_name + + +@register.simple_tag +def model_verbose_name(class_name): + model = get_class(class_name) + return model._meta.verbose_name + + +@register.simple_tag +def model_verbose_name_plural(class_name): + model = get_class(class_name) + return model._meta.verbose_name_plural + + +@register.filter +def lookup(d, key): + return d[key] if key in d else [] + + +@register.filter +def isinst(value, class_str): + classe = value.__class__.__name__ + return classe == class_str diff --git a/templates/templatetags/menus.py b/templates/templatetags/menus.py new file mode 100644 index 00000000..6591823b --- /dev/null +++ b/templates/templatetags/menus.py @@ -0,0 +1,51 @@ +import os + +import yaml +from django import template +from django.core.urlresolvers import reverse + +from sapl.settings import BASE_DIR + +register = template.Library() +TEMPLATES_DIR = BASE_DIR.child("templates") + + +@register.inclusion_tag('menus/subnav.html', takes_context=True) +def subnav(context, path=None): + """Renders a subnavigation for views of a certain object. + + If not provided, path defaults to /subnav.yaml + """ + # TODO: 118n !!!!!!!!!!!!!! + # How to internationalize yaml files???? + menu = None + root_pk = context.get('root_pk', None) + if not root_pk: + obj = context.get('object', None) + if obj: + root_pk = obj.pk + if root_pk: + request = context['request'] + app = request.resolver_match.app_name + # Esse IF elimina o bug do subnav em Tabelas Auxiliares + # e também em proposições + if request.path.find(app) == -1: + return + default_path = '%s/subnav.yaml' % app + path = os.path.join(TEMPLATES_DIR, path or default_path) + if os.path.exists(path): + menu = yaml.load(open(path, 'r')) + resolve_urls_inplace(menu, root_pk, app) + return {'menu': menu} + + +def resolve_urls_inplace(menu, pk, app): + if isinstance(menu, list): + for item in menu: + resolve_urls_inplace(item, pk, app) + else: + if 'url' in menu: + menu['url'] = reverse('%s:%s' % (app, menu['url']), + kwargs={'pk': pk}) + if 'children' in menu: + resolve_urls_inplace(menu['children'], pk, app) diff --git a/templates/usuarios/confirma_email.html b/templates/usuarios/confirma_email.html new file mode 100644 index 00000000..fefbd27b --- /dev/null +++ b/templates/usuarios/confirma_email.html @@ -0,0 +1,7 @@ +{% extends "crud/detail.html" %} +{% load i18n %} + +{% block detail_content %} +Sua conta foi confirmada via e-mail. Clique aqui para fazer seu login. + +{% endblock %} \ No newline at end of file diff --git a/templates/usuarios/habilitar_detail.html b/templates/usuarios/habilitar_detail.html new file mode 100644 index 00000000..04667b2a --- /dev/null +++ b/templates/usuarios/habilitar_detail.html @@ -0,0 +1,41 @@ +{% extends "crud/detail.html" %} +{% load i18n %} + +{% block actions %} + +{% endblock actions %} + +{% block msg %} + {% if not usuario.habilitado %} +

Esse perfil ainda não está habilitado para uso das funcionalidades.

+ {% else %} +

Esse perfil já está habilitado para uso das funcionalidades.

+ {% endif %} +{% endblock msg %} + +{% block title %} +

Habilitar

+{% endblock %} + +{% block detail_content %} +
+
+
Nome completo:
+
      - {{ usuario.nome_completo }}
+
Email:
+
      - {{ usuario.email }}
+
CPF:
+
      - {{ usuario.cpf }}
+
Cargo:
+
      - {{ usuario.cargo }}
+
Vinculo:
+
      - {{ usuario.vinculo }}
+
Casa Legislativa:
+
      - {{ usuario.casa_legislativa }}
+
Telefones:
+
      - [{{ usuario.primeiro_telefone.ddd }}] - {{usuario.primeiro_telefone.numero}}
+
      - [{{ usuario.segundo_telefone.ddd }}] - {{usuario.segundo_telefone.numero}}
+
+{% endblock detail_content %} diff --git a/templates/usuarios/login.html b/templates/usuarios/login.html new file mode 100644 index 00000000..f6fd241e --- /dev/null +++ b/templates/usuarios/login.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block base_content %} +
+

Login

+
+
+ {% csrf_token %} +

+ + {% if form.errors %} +

Usuário e/ou Senha inválidos.

+ {% endif %} + + + + + + + + + +
Usuário{{ form.username }}
Senha{{ form.password }}
+

+

+ +

+
Esqueceu sua senha?
+ +
+
+
+{% endblock %} diff --git a/templates/usuarios/recuperacao_senha_completo.html b/templates/usuarios/recuperacao_senha_completo.html new file mode 100644 index 00000000..150a4ef2 --- /dev/null +++ b/templates/usuarios/recuperacao_senha_completo.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block base_content %} + +

{% trans "Sua senha foi alterada corretamente. Agora você pode fazer o login." %}

+ +

{% trans 'Entrar' %}

+ +{% endblock %} \ No newline at end of file diff --git a/templates/usuarios/recuperacao_senha_form.html b/templates/usuarios/recuperacao_senha_form.html new file mode 100644 index 00000000..30664fb7 --- /dev/null +++ b/templates/usuarios/recuperacao_senha_form.html @@ -0,0 +1,13 @@ +{% extends "crud/form.html" %} +{% load i18n crispy_forms_tags %} + +{% block base_content %} + {% if validlink %} +

{% trans "Por favor, insira duas vezes para verificarmos se está correta." %}

+ {% crispy form %} + {% else %} +

{% trans 'A recuperação de senha não obteve sucesso' %}

+

{% trans "O link é inválido, possivelmente este link já foi utilizado. Por favor, refaça o pedido de recuperação de senha." %}

+ {% endif %} + +{% endblock %} \ No newline at end of file diff --git a/templates/usuarios/recuperar_senha.html b/templates/usuarios/recuperar_senha.html new file mode 100644 index 00000000..477f74bd --- /dev/null +++ b/templates/usuarios/recuperar_senha.html @@ -0,0 +1,5 @@ +{% extends "crud/form.html" %} +{% load i18n crispy_forms_tags %} +{% block base_content %} + {% crispy form %} +{% endblock %} diff --git a/templates/usuarios/recuperar_senha_email.html b/templates/usuarios/recuperar_senha_email.html new file mode 100644 index 00000000..f061bd6f --- /dev/null +++ b/templates/usuarios/recuperar_senha_email.html @@ -0,0 +1,14 @@ +{% load i18n %}{% autoescape off %} +{% blocktrans %}Você está recebendo este e-mail porque fez a solicitação de recuperação de senha no seguinte website {{ site_name }}.
{% endblocktrans %} + +{% trans "Clique no link abaixo para redefinir sua senha:" %}
+{% block reset_link %} + Clique aqui
+{% endblock %} +{% trans "Seu nome de usuário, caso você tenha esquecido:" %} {{ user.get_username }}
+ +{% trans "Obrigado por acessar nosso site!" %}
+ +{% blocktrans %}Atenciosamente, equipe Atendimento {% endblocktrans %}
+ +{% endautoescape %} \ No newline at end of file diff --git a/templates/usuarios/recuperar_senha_enviado.html b/templates/usuarios/recuperar_senha_enviado.html new file mode 100644 index 00000000..bbb7ca71 --- /dev/null +++ b/templates/usuarios/recuperar_senha_enviado.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block base_content %} + +

{% trans "Foi enviado um e-mail com as instruções para recuperação de senha. Você deve recebê-lo em breve." %}

+

{% trans "Caso você não receba, verifique se você inseriu o e-mail em que você está cadastrado e verifique, também, sua caixa de SPAM." %}

+ +{% endblock %} \ No newline at end of file diff --git a/templates/usuarios/usuario_detail.html b/templates/usuarios/usuario_detail.html new file mode 100644 index 00000000..3e01626b --- /dev/null +++ b/templates/usuarios/usuario_detail.html @@ -0,0 +1,31 @@ +{% extends "crud/detail.html" %} +{% load i18n %} + +{% block actions %} + +{% endblock actions %} + +{% block msg %} + {% if not usuario.habilitado %} +

Esse perfil ainda não está habilitado para uso das funcionalidades.

+ {% else %} +

Esse perfil já está habilitado para uso das funcionalidades.

+ {% endif %} +{% endblock msg %} + +{% block extrablock %} + + {% for tel in telefones %} +
+
+

{{tel|first}}

+
+

{{ tel|last }}

+
+
+
+ {% endfor %} + +{% endblock %} diff --git a/templates/usuarios/usuario_list.html b/templates/usuarios/usuario_list.html new file mode 100644 index 00000000..416ed511 --- /dev/null +++ b/templates/usuarios/usuario_list.html @@ -0,0 +1,38 @@ +{% extends "crud/list.html" %} +{% load i18n %} + +{% block buttons %}{% endblock buttons %} + +{% block content %} + + {% if not rows %} +

{{ NO_ENTRIES_MSG }}

+ {% else %} + + + + {% for name in headers %} + + {% endfor %} + + + + {% for value_list in rows %} + + {% for value, href in value_list %} + + {% endfor %} + + {% endfor %} + +
{{ name }}
+ {% if href %} + {{ value }} + {% else %} + {{ value|safe }} + {% endif %} +
+ {% endif %} + + {% include "paginacao.html" %} +{% endblock content %} From 7617d1de7448624e52c19dc8f95e4349f6604995 Mon Sep 17 00:00:00 2001 From: Luciano Almeida Date: Tue, 21 Jun 2016 14:48:34 -0300 Subject: [PATCH 04/63] Corrige alguns erros de import e requirements Signed-off-by: Luciano Almeida --- requirements/requirements.txt | 21 +++++++++++++++++++-- sigi/apps/usuarios/forms.py | 20 +++++++------------- sigi/apps/usuarios/views.py | 5 ++--- sigi/settings/base.py | 17 ++++++----------- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 570c578c..694f67bd 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,12 +1,25 @@ -e git://github.com/marinho/geraldo.git@868ebdce67176d9b6205cddc92476f642c783fff#egg=geraldo -django-bootstrap3==7.0.1 django-admin-bootstrapped==2.5.7 django-auth-ldap==1.2.8 django-autoslug==1.9.3 +django-bootstrap3==7.0.1 +django-bower==5.1.0 +django-braces==1.8.1 +django-compressor==2.0 +django-crispy-forms==1.6.0 +dj-database-url==0.4.1 +django-dotenv==1.4.1 django-extensions==1.6.7 +django-extra-views==0.7.1 +django-filter==0.13.0 +django-floppyforms==1.6.1 django-image-cropping==1.0.3 django-localflavor==1.3 +django-model-utils==2.4 +django-sass-processor==0.3.4 +django-simple-captcha==0.5.1 Django==1.9.6 +libsass==0.11.0 easy-thumbnails==2.3 eav-django==1.4.7 gunicorn==19.6.0 @@ -14,10 +27,14 @@ html5lib==0.9999999 Pillow==3.2.0 pisa==3.0.33 psycopg2==2.6.1 +python-decouple==3.0 python-memcached==1.58 +pytz==2016.3 PyYAML==3.11 -reportlab==3.3.0 +reportlab==2.7 requests==2.10.0 +rtyaml==0.0.2 +unipath==1.1 six==1.10.0 djangorestframework==2.4.8 django-ipware==1.1.6 diff --git a/sigi/apps/usuarios/forms.py b/sigi/apps/usuarios/forms.py index 44ecba2e..fa97dbe4 100644 --- a/sigi/apps/usuarios/forms.py +++ b/sigi/apps/usuarios/forms.py @@ -6,19 +6,13 @@ from crispy_forms.helper import FormHelper from crispy_forms.layout import Fieldset, Layout, Submit from django import forms -from django.conf import settings from django.contrib.auth.forms import (AuthenticationForm, PasswordResetForm, SetPasswordForm) from django.contrib.auth.models import User from django.contrib.auth.password_validation import validate_password -from django.contrib.auth.tokens import default_token_generator from django.core.exceptions import ValidationError -from django.core.mail import send_mail -from django.core.urlresolvers import reverse from django.db import transaction from django.forms import ModelForm -from django.utils.encoding import force_bytes -from django.utils.http import urlsafe_base64_encode from django.utils.translation import ugettext_lazy as _ @@ -26,7 +20,7 @@ from sigi.apps.crud.utils import YES_NO_CHOICES from sigi.apps.crispy_layout_mixin import form_actions -from .models import Telefone, Usuario, ConfirmaEmail +from .models import Telefone, Usuario class LoginForm(AuthenticationForm): @@ -319,11 +313,11 @@ class Meta(object): def __init__(self, *args, **kwargs): super(HabilitarEditForm, self).__init__(*args, **kwargs) - row1 = crispy_layout_mixin.to_row( + row1 = sigi.apps.crispy_layout_mixin.to_row( [(u'nome_completo', 4), (u'cpf', 4), (u'email', 4)]) - row2 = crispy_layout_mixin.to_row([(u'habilitado', 12)]) + row2 = sigi.apps.crispy_layout_mixin.to_row([(u'habilitado', 12)]) self.helper = FormHelper() self.helper.layout = Layout( Fieldset( @@ -379,10 +373,10 @@ class Meta(object): def __init__(self, *args, **kwargs): super(MudarSenhaForm, self).__init__(*args, **kwargs) - row1 = crispy_layout_mixin.to_row( + row1 = sigi.apps.crispy_layout_mixin.to_row( [(u'password', 6), (u'password_confirm', 6)]) - row2 = crispy_layout_mixin.to_row([(u'captcha', 12)]) + row2 = sigi.apps.crispy_layout_mixin.to_row([(u'captcha', 12)]) self.helper = FormHelper() self.helper.layout = Layout( Fieldset( @@ -401,7 +395,7 @@ class RecuperarSenhaEmailForm(PasswordResetForm): def __init__(self, *args, **kwargs): super(RecuperarSenhaEmailForm, self).__init__(*args, **kwargs) - row1 = crispy_layout_mixin.to_row( + row1 = sigi.apps.crispy_layout_mixin.to_row( [(u'email', 6)]) self.helper = FormHelper() self.helper.layout = Layout( @@ -433,7 +427,7 @@ class RecuperacaoMudarSenhaForm(SetPasswordForm): def __init__(self, *args, **kwargs): super(RecuperacaoMudarSenhaForm, self).__init__(*args, **kwargs) self.fields[u'new_password1'].help_text = u'' - row1 = crispy_layout_mixin.to_row( + row1 = sigi.apps.crispy_layout_mixin.to_row( [(u'new_password1', 6), (u'new_password2', 6)]) self.helper = FormHelper() diff --git a/sigi/apps/usuarios/views.py b/sigi/apps/usuarios/views.py index bfbbe039..6b900252 100644 --- a/sigi/apps/usuarios/views.py +++ b/sigi/apps/usuarios/views.py @@ -7,7 +7,6 @@ from django.core.urlresolvers import reverse from django.utils import timezone from django.views.generic import DetailView, FormView, TemplateView -import sigi.apps.crud.base from django.core.mail import send_mail from sigi.apps.crud.utils import str2bool from django.contrib.auth.tokens import default_token_generator @@ -45,8 +44,8 @@ def get_success_url(self): url_base = full_url[0][:full_url[0].find(u'usuario') - 1], mensagem = (u"Este e-mail foi utilizado para fazer cadastro no " + u"Sistema de Atendimento ao Usuário do Interlegis.\n" + - u"Caso você não tenha feito este cadastro, por favor " + - u"ignore esta mensagem.\n" + url_base[0] + + u"Caso você não tenha feito este cadastro, por favor" + + u" ignore esta mensagem.\n" + url_base[0] + reverse(u'usuarios:confirmar_email', kwargs=kwargs)) remetente = settings.EMAIL_HOST_USER destinatario = [confirmar_email.email, diff --git a/sigi/settings/base.py b/sigi/settings/base.py index 245efff6..0759d58f 100644 --- a/sigi/settings/base.py +++ b/sigi/settings/base.py @@ -129,6 +129,12 @@ STATICFILES_DIRS = ( os.path.join(BASE_DIR, 'sigiStatic'), ) +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + 'djangobower.finders.BowerFinder', + 'sass_processor.finders.CssFinder', +) SERVER_EMAIL = 'sigi@interlegis.leg.br' DEFAULT_FROM_EMAIL = 'spdt@interlegis.leg.br' @@ -194,17 +200,6 @@ }, } -STATIC_URL = '/static/' -STATIC_ROOT = os.path.join(BASE_DIR, "collected_static") -STATICFILES_DIRS = (os.path.join(BASE_DIR, ("static")),) -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - 'djangobower.finders.BowerFinder', - 'sass_processor.finders.CssFinder', -) - - CRISPY_TEMPLATE_PACK = 'bootstrap3' CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap3' CRISPY_FAIL_SILENTLY = False From b260176457a3364fcc12c8ba55612d5a5862f4dc Mon Sep 17 00:00:00 2001 From: Eduardo Edson Batista Cordeiro Alves Date: Tue, 21 Jun 2016 16:56:40 -0300 Subject: [PATCH 05/63] =?UTF-8?q?mudan=C3=A7as=20css=20e=20base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sigi/settings/base.py | 17 ++-- sigiStatic/img/img_atendimento/favicon.ico | Bin 0 -> 975 bytes .../glyphicons-halflings-white.png | Bin 0 -> 8777 bytes .../img_atendimento/glyphicons-halflings.png | Bin 0 -> 12799 bytes sigiStatic/img/img_atendimento/logo.png | Bin 0 -> 3868 bytes sigiStatic/img/img_atendimento/logo_cc.jpg | Bin 0 -> 2602 bytes .../img/img_atendimento/logo_interlegis.jpg | Bin 0 -> 2063 bytes templates/base.html | 82 ++++++++---------- 8 files changed, 49 insertions(+), 50 deletions(-) create mode 100644 sigiStatic/img/img_atendimento/favicon.ico create mode 100644 sigiStatic/img/img_atendimento/glyphicons-halflings-white.png create mode 100644 sigiStatic/img/img_atendimento/glyphicons-halflings.png create mode 100644 sigiStatic/img/img_atendimento/logo.png create mode 100644 sigiStatic/img/img_atendimento/logo_cc.jpg create mode 100644 sigiStatic/img/img_atendimento/logo_interlegis.jpg diff --git a/sigi/settings/base.py b/sigi/settings/base.py index 0759d58f..74d3a699 100644 --- a/sigi/settings/base.py +++ b/sigi/settings/base.py @@ -129,12 +129,6 @@ STATICFILES_DIRS = ( os.path.join(BASE_DIR, 'sigiStatic'), ) -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - 'djangobower.finders.BowerFinder', - 'sass_processor.finders.CssFinder', -) SERVER_EMAIL = 'sigi@interlegis.leg.br' DEFAULT_FROM_EMAIL = 'spdt@interlegis.leg.br' @@ -200,6 +194,17 @@ }, } +STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(BASE_DIR, "collected_static") +STATICFILES_DIRS = (os.path.join(BASE_DIR, ("static")),) + +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + 'djangobower.finders.BowerFinder', + 'sass_processor.finders.CssFinder', +) + CRISPY_TEMPLATE_PACK = 'bootstrap3' CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap3' CRISPY_FAIL_SILENTLY = False diff --git a/sigiStatic/img/img_atendimento/favicon.ico b/sigiStatic/img/img_atendimento/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b81629808c3b0ef399c3cbb7008d6405543b2575 GIT binary patch literal 975 zcmV;=12FuFP)1v3X4hmB{Gs(HDz=28P!x?AtB4mWMG&pU3!!2`@uFU6MGA^w zX%MR=5yby11ut4CsEBxJ5Ha}2YP_+68bL`m#9Fj9>$>h{&)19mtUGfi$)5Ftg_)i2 zyzg_~**RxcR27$+{?(o(pjVv+11PPjWb>MsvjBw}@T#1Li{t=2_wiEg4i6%lt_T2A z8GtIi6#*DXR04ojnn1<3K+*ukAK^Li^VPLGfHlB)L~q6O-hS5g{%sd9)u>dQKx|Mh zm#DoY%Iac+EXc=necz>;g^6!7onZwp0d9|Q|8RK)1GgZ5x%T@K^I}bjdvYXV3KQMk zL5+3{0RSr_8=Qw=)9F89*ffzbKG{J6hP#WrDyZt!HmVbX95e#bytz2J;_54Mgd@K0FAP} zVK#sh*e_HkYzDfUm{`mDs zf`CE!F_mmSG#bt@z~phQAf2}6%%rXfnTg$za~9-+`piisn@@~3D6#!Lmu>Tx0A~?C zvq9IqoYpnA`-?jNeWDA|hobUOjh;{4ZD}9TF;M>l;jRu(1${d%0000YdQ@0+Q*UN; xcVTj6004N}D=#nC%goCzPEIUH)ypqR2LLwM23QbN%3J^d002ovPDHLkV1keM!8rf` literal 0 HcmV?d00001 diff --git a/sigiStatic/img/img_atendimento/glyphicons-halflings-white.png b/sigiStatic/img/img_atendimento/glyphicons-halflings-white.png new file mode 100644 index 0000000000000000000000000000000000000000..3bf6484a29d8da269f9bc874b25493a45fae3bae GIT binary patch literal 8777 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 B7G?kd literal 0 HcmV?d00001 diff --git a/sigiStatic/img/img_atendimento/glyphicons-halflings.png b/sigiStatic/img/img_atendimento/glyphicons-halflings.png new file mode 100644 index 0000000000000000000000000000000000000000..a9969993201f9cee63cf9f49217646347297b643 GIT binary patch literal 12799 zcma*OWmH^Ivn@*S;K3nSf_t!#;0f+&pm7Po8`nk}2q8f5;M%x$SdAkd9FAvlc$ zx660V9e3Ox@4WZ^?7jZ%QFGU-T~%||Ug4iK6bbQY@zBuF2$hxOw9wF=A)nUSxR_5@ zEX>HBryGrjyuOFFv$Y4<+|3H@gQfEqD<)+}a~mryD|1U9*I_FOG&F%+Ww{SJ-V2BR zjt<81Ek$}Yb*95D4RS0HCps|uLyovt;P05hchQb-u2bzLtmog&f2}1VlNhxXV);S9 zM2buBg~!q9PtF)&KGRgf3#z7B(hm5WlNClaCWFs!-P!4-u*u5+=+D|ZE9e`KvhTHT zJBnLwGM%!u&vlE%1ytJ=!xt~y_YkFLQb6bS!E+s8l7PiPGSt9xrmg?LV&&SL?J~cI zS(e9TF1?SGyh+M_p@o1dyWu7o7_6p;N6hO!;4~ z2B`I;y`;$ZdtBpvK5%oQ^p4eR2L)BH>B$FQeC*t)c`L71gXHPUa|vyu`Bnz)H$ZcXGve(}XvR!+*8a>BLV;+ryG1kt0=)ytl zNJxFUN{V7P?#|Cp85QTa@(*Q3%K-R(Pkv1N8YU*(d(Y}9?PQ(j;NzWoEVWRD-~H$=f>j9~PN^BM2okI(gY-&_&BCV6RP&I$FnSEM3d=0fCxbxA6~l>54-upTrw zYgX@%m>jsSGi`0cQt6b8cX~+02IghVlNblR7eI;0ps}mpWUcxty1yG56C5rh%ep(X z?)#2d?C<4t-KLc*EAn>>M8%HvC1TyBSoPNg(4id~H8JwO#I)Bf;N*y6ai6K9_bA`4 z_g9(-R;qyH&6I$`b42v|0V3Z8IXN*p*8g$gE98+JpXNY+jXxU0zsR^W$#V=KP z3AEFp@OL}WqwOfsV<)A^UTF4&HF1vQecz?LWE@p^Z2){=KEC_3Iopx_eS42>DeiDG zWMXGbYfG~W7C8s@@m<_?#Gqk;!&)_Key@^0xJxrJahv{B&{^!>TV7TEDZlP|$=ZCz zmX=ZWtt4QZKx**)lQQoW8y-XLiOQy#T`2t}p6l*S`68ojyH@UXJ-b~@tN`WpjF z%7%Yzv807gsO!v=!(2uR)16!&U5~VPrPHtGzUU?2w(b1Xchq}(5Ed^G|SD7IG+kvgyVksU) z(0R)SW1V(>&q2nM%Z!C9=;pTg!(8pPSc%H01urXmQI6Gi^dkYCYfu6b4^tW))b^U+ z$2K&iOgN_OU7n#GC2jgiXU{caO5hZt0(>k+c^(r><#m|#J^s?zA6pi;^#*rp&;aqL zRcZi0Q4HhVX3$ybclxo4FFJW*`IV`)Bj_L3rQe?5{wLJh168Ve1jZv+f1D}f0S$N= zm4i|9cEWz&C9~ZI3q*gwWH^<6sBWuphgy@S3Qy?MJiL>gwd|E<2h9-$3;gT9V~S6r z)cAcmE0KXOwDA5eJ02-75d~f?3;n7a9d_xPBJaO;Z)#@s7gk5$Qn(Fc^w@9c5W0zY z59is0?Mt^@Rolcn{4%)Ioat(kxQH6}hIykSA)zht=9F_W*D#<}N(k&&;k;&gKkWIL z0Of*sP=X(Uyu$Pw;?F@?j{}=>{aSHFcii#78FC^6JGrg-)!)MV4AKz>pXnhVgTgx8 z1&5Y=>|8RGA6++FrSy=__k_imx|z-EI@foKi>tK0Hq2LetjUotCgk2QFXaej!BWYL zJc{fv(&qA7UUJ|AXLc5z*_NW#yWzKtl(c8mEW{A>5Hj^gfZ^HC9lQNQ?RowXjmuCj4!!54Us1=hY z0{@-phvC}yls!PmA~_z>Y&n&IW9FQcj}9(OLO-t^NN$c0o}YksCUWt|DV(MJB%%Sr zdf}8!9ylU2TW!=T{?)g-ojAMKc>3pW;KiZ7f0;&g)k}K^#HBhE5ot)%oxq$*$W@b# zg4p<Ou`ME|Kd1WHK@8 zzLD+0(NHWa`B{em3Ye?@aVsEi>y#0XVZfaFuq#;X5C3{*ikRx7UY4FF{ZtNHNO?A_ z#Q?hwRv~D8fPEc%B5E-ZMI&TAmikl||EERumQCRh7p;)>fdZMxvKq;ky0}7IjhJph zW*uuu*(Y6)S;Od--8uR^R#sb$cmFCnPcj9PPCWhPN;n`i1Q#Qn>ii z{WR|0>8F`vf&#E(c2NsoH=I7Cd-FV|%(7a`i}gZw4N~QFFG2WtS^H%@c?%9UZ+kez z;PwGgg_r6V>Kn5n(nZ40P4qMyrCP3bDkJp@hp6&X3>gzC>=f@Hsen<%I~7W+x@}b> z0}Et*vx_50-q@PIV=(3&Tbm}}QRo*FP2@)A#XX-8jYspIhah`9ukPBr)$8>Tmtg&R z?JBoH17?+1@Y@r>anoKPQ}F8o9?vhcG79Cjv^V6ct709VOQwg{c0Q#rBSsSmK3Q;O zBpNihl3S0_IGVE)^`#94#j~$;7+u870yWiV$@={|GrBmuz4b)*bCOPkaN0{6$MvazOEBxFdKZDlbVvv{8_*kJ zfE6C`4&Kkz<5u%dEdStd85-5UHG5IOWbo8i9azgg#zw-(P1AA049hddAB*UdG3Vn0 zX`OgM+EM|<+KhJ<=k?z~WA5waVj?T9eBdfJGebVifBKS1u<$#vl^BvSg)xsnT5Aw_ZY#}v*LXO#htB>f}x3qDdDHoFeb zAq7;0CW;XJ`d&G*9V)@H&739DpfWYzdQt+Kx_E1K#Cg1EMtFa8eQRk_JuUdHD*2;W zR~XFnl!L2A?48O;_iqCVr1oxEXvOIiN_9CUVTZs3C~P+11}ebyTRLACiJuMIG#`xP zKlC|E(S@QvN+%pBc6vPiQS8KgQAUh75C0a2xcPQDD$}*bM&z~g8+=9ltmkT$;c;s z5_=8%i0H^fEAOQbHXf0;?DN5z-5+1 zDxj50yYkz4ox9p$HbZ|H?8ukAbLE^P$@h}L%i6QVcY>)i!w=hkv2zvrduut%!8>6b zcus3bh1w~L804EZ*s96?GB&F7c5?m?|t$-tp2rKMy>F*=4;w*jW}^;8v`st&8)c; z2Ct2{)?S(Z;@_mjAEjb8x=qAQvx=}S6l9?~H?PmP`-xu;ME*B8sm|!h@BX4>u(xg_ zIHmQzp4Tgf*J}Y=8STR5_s)GKcmgV!$JKTg@LO402{{Wrg>#D4-L%vjmtJ4r?p&$F!o-BOf7ej~ z6)BuK^^g1b#(E>$s`t3i13{6-mmSp7{;QkeG5v}GAN&lM2lQT$@(aQCcFP(%UyZbF z#$HLTqGT^@F#A29b0HqiJsRJAlh8kngU`BDI6 zJUE~&!cQ*&f95Ot$#mxU5+*^$qg_DWNdfu+1irglB7yDglzH()2!@#rpu)^3S8weW z_FE$=j^GTY*|5SH95O8o8W9FluYwB=2PwtbW|JG6kcV^dMVmX(wG+Otj;E$%gfu^K z!t~<3??8=()WQSycsBKy24>NjRtuZ>zxJIED;YXaUz$@0z4rl+TW zWxmvM$%4jYIpO>j5k1t1&}1VKM~s!eLsCVQ`TTjn3JRXZD~>GM z$-IT~(Y)flNqDkC%DfbxaV9?QuWCV&-U1yzrV@0jRhE;)ZO0=r-{s@W?HOFbRHDDV zq;eLo+wOW;nI|#mNf(J?RImB9{YSO2Y`9825Lz#u4(nk3)RGv3X8B(A$TsontJ8L! z9JP^eWxtKC?G8^xAZa1HECx*rp35s!^%;&@Jyk)NexVc)@U4$^X1Dag6`WKs|(HhZ#rzO2KEw3xh~-0<;|zcs0L>OcO#YYX{SN8m6`9pp+ zQG@q$I)T?aoe#AoR@%om_#z=c@ych!bj~lV13Qi-xg$i$hXEAB#l=t7QWENGbma4L zbBf*X*4oNYZUd_;1{Ln_ZeAwQv4z?n9$eoxJeI?lU9^!AB2Y~AwOSq67dT9ADZ)s@ zCRYS7W$Zpkdx$3T>7$I%3EI2ik~m!f7&$Djpt6kZqDWZJ-G{*_eXs*B8$1R4+I}Kf zqniwCI64r;>h2Lu{0c(#Atn)%E8&)=0S4BMhq9$`vu|Ct;^ur~gL`bD>J@l)P$q_A zO7b3HGOUG`vgH{}&&AgrFy%K^>? z>wf**coZ2vdSDcNYSm~dZ(vk6&m6bVKmVgrx-X<>{QzA!)2*L+HLTQz$e8UcB&Djq zl)-%s$ZtUN-R!4ZiG=L0#_P=BbUyH+YPmFl_ogkkQ$=s@T1v}rNnZ^eMaqJ|quc+6 z*ygceDOrldsL30w`H;rNu+IjlS+G~p&0SawXCA1+D zC%cZtjUkLNq%FadtHE?O(yQTP486A{1x<{krq#rpauNQaeyhM3*i0%tBpQHQo-u)x z{0{&KS`>}vf2_}b160XZO2$b)cyrHq7ZSeiSbRvaxnKUH{Q`-P(nL&^fcF2){vhN- zbX&WEjP7?b4A%0y6n_=m%l00uZ+}mCYO(!x?j$+O$*TqoD_Q5EoyDJ?w?^UIa491H zE}87(bR`X;@u#3Qy~9wWdWQIg1`cXrk$x9=ccR|RY1~%{fAJ@uq@J3e872x0v$hmv ze_KcL(wM|n0EOp;t{hKoohYyDmYO;!`7^Lx;0k=PWPGZpI>V5qYlzjSL_(%|mud50 z7#{p97s`U|Sn$WYF>-i{i4`kzlrV6a<}=72q2sAT7Zh{>P%*6B;Zl;~0xWymt10Mo zl5{bmR(wJefJpNGK=fSRP|mpCI-)Nf6?Pv==FcFmpSwF1%CTOucV{yqxSyx4Zws3O z8hr5Uyd%ezIO7?PnEO0T%af#KOiXD$e?V&OX-B|ZX-YsgSs%sv-6U+sLPuz{D4bq| zpd&|o5tNCmpT>(uIbRf?8c}d3IpOb3sn6>_dr*26R#ev<_~vi)wleW$PX|5)$_ z+_|=pi(0D(AB_sjQ;sQQSM&AWqzDO1@NHw;C9cPdXRKRI#@nUW)CgFxzQ1nyd!+h& zcjU!U=&u|>@}R(9D$%lu2TlV>@I2-n@fCr5PrZNVyKWR7hm zWjoy^p7v8m#$qN0K#8jT- zq`mSirDZDa1Jxm;Rg3rAPhC)LcI4@-RvKT+@9&KsR3b0_0zuM!Fg7u>oF>3bzOxZPU&$ab$Z9@ zY)f7pKh22I7ZykL{YsdjcqeN++=0a}elQM-4;Q)(`Ep3|VFHqnXOh14`!Bus& z9w%*EWK6AiAM{s$6~SEQS;A>ey$#`7)khZvamem{P?>k)5&7Sl&&NXKk}o!%vd;-! zpo2p-_h^b$DNBO>{h4JdGB=D>fvGIYN8v&XsfxU~VaefL?q} z3ekM?iOKkCzQHkBkhg=hD!@&(L}FcHKoa zbZ7)H1C|lHjwEb@tu=n^OvdHOo7o+W`0-y3KdP#bb~wM=Vr_gyoEq|#B?$&d$tals ziIs-&7isBpvS|CjC|7C&3I0SE?~`a%g~$PI%;au^cUp@ER3?mn-|vyu!$7MV6(uvt z+CcGuM(Ku2&G0tcRCo7#D$Dirfqef2qPOE5I)oCGzmR5G!o#Q~(k~)c=LpIfrhHQk zeAva6MilEifE7rgP1M7AyWmLOXK}i8?=z2;N=no)`IGm#y%aGE>-FN zyXCp0Sln{IsfOBuCdE*#@CQof%jzuU*jkR*Su3?5t}F(#g0BD0Zzu|1MDes8U7f9; z$JBg|mqTXt`muZ8=Z`3wx$uizZG_7>GI7tcfOHW`C2bKxNOR)XAwRkLOaHS4xwlH4 zDpU29#6wLXI;H?0Se`SRa&I_QmI{zo7p%uveBZ0KZKd9H6@U?YGArbfm)D*^5=&Rp z`k{35?Z5GbZnv>z@NmJ%+sx=1WanWg)8r}C_>EGR8mk(NR$pW<-l8OTU^_u3M@gwS z7}GGa1)`z5G|DZirw;FB@VhH7Dq*0qc=|9lLe{w2#`g+_nt>_%o<~9(VZe=zI*SSz4w43-_o>4E4`M@NPKTWZuQJs)?KXbWp1M zimd5F;?AP(LWcaI-^Sl{`~>tmxsQB9Y$Xi*{Zr#py_+I$vx7@NY`S?HFfS!hUiz$a z{>!&e1(16T!Om)m)&k1W#*d#GslD^4!TwiF2WjFBvi=Ms!ADT)ArEW6zfVuIXcXVk z>AHjPADW+mJzY`_Ieq(s?jbk4iD2Rb8*V3t6?I+E06(K8H!!xnDzO%GB;Z$N-{M|B zeT`jo%9)s%op*XZKDd6*)-^lWO{#RaIGFdBH+;XXjI(8RxpBc~azG1H^2v7c^bkFE zZCVPE+E*Q=FSe8Vm&6|^3ki{9~qafiMAf7i4APZg>b%&5>nT@pHH z%O*pOv(77?ZiT{W zBibx}Q12tRc7Py1NcZTp`Q4ey%T_nj@1WKg5Fz_Rjl4wlJQj)rtp8yL3r!Shy zvZvnmh!tH4T6Js-?vI0<-rzzl{mgT*S0d_7^AU_8gBg^03o-J=p(1o6kww2hx|!%T z-jqp}m^G*W?$!R#M%Ef?&2jYxmx+lXWZszpI4d$pUN`(S)|*c^CgdwY>Fa>> zgGBJhwe8y#Xd*q0=@SLEgPF>+Qe4?%E*v{a`||luZ~&dqMBrRfJ{SDMaJ!s_;cSJp zSqZHXIdc@@XteNySUZs^9SG7xK`8=NBNM)fRVOjw)D^)w%L2OPkTQ$Tel-J)GD3=YXy+F4in(ILy*A3m@3o73uv?JC}Q>f zrY&8SWmesiba0|3X-jmlMT3 z*ST|_U@O=i*sM_*48G)dgXqlwoFp5G6qSM3&%_f_*n!PiT>?cNI)fAUkA{qWnqdMi+aNK_yVQ&lx4UZknAc9FIzVk% zo6JmFH~c{_tK!gt4+o2>)zoP{sR}!!vfRjI=13!z5}ijMFQ4a4?QIg-BE4T6!#%?d&L;`j5=a`4is>U;%@Rd~ zXC~H7eGQhhYWhMPWf9znDbYIgwud(6$W3e>$W4$~d%qoJ z+JE`1g$qJ%>b|z*xCKenmpV$0pM=Gl-Y*LT8K+P)2X#;XYEFF4mRbc~jj?DM@(1e`nL=F4Syv)TKIePQUz)bZ?Bi3@G@HO$Aps1DvDGkYF50O$_welu^cL7;vPiMGho74$;4fDqKbE{U zd1h{;LfM#Fb|Z&uH~Rm_J)R~Vy4b;1?tW_A)Iz#S_=F|~pISaVkCnQ0&u%Yz%o#|! zS-TSg87LUfFSs{tTuM3$!06ZzH&MFtG)X-l7>3)V?Txuj2HyG*5u;EY2_5vU0ujA? zHXh5G%6e3y7v?AjhyX79pnRBVr}RmPmtrxoB7lkxEzChX^(vKd+sLh?SBic=Q)5nA zdz7Mw3_iA>;T^_Kl~?1|5t%GZ;ki_+i>Q~Q1EVdKZ)$Sh3LM@ea&D~{2HOG++7*wF zAC6jW4>fa~!Vp5+$Z{<)Qxb|{unMgCv2)@%3j=7)Zc%U<^i|SAF88s!A^+Xs!OASYT%7;Jx?olg_6NFP1475N z#0s<@E~FI}#LNQ{?B1;t+N$2k*`K$Hxb%#8tRQi*Z#No0J}Pl;HWb){l7{A8(pu#@ zfE-OTvEreoz1+p`9sUI%Y{e5L-oTP_^NkgpYhZjp&ykinnW;(fu1;ttpSsgYM8ABX4dHe_HxU+%M(D=~) zYM}XUJ5guZ;=_ZcOsC`_{CiU$zN3$+x&5C`vX-V3`8&RjlBs^rf00MNYZW+jCd~7N z%{jJuUUwY(M`8$`B>K&_48!Li682ZaRknMgQ3~dnlp8C?__!P2z@=Auv;T^$yrsNy zCARmaA@^Yo2sS%2$`031-+h9KMZsIHfB>s@}>Y(z988e!`%4=EDoAQ0kbk>+lCoK60Mx9P!~I zlq~wf7kcm_NFImt3ZYlE(b3O1K^QWiFb$V^a2Jlwvm(!XYx<`i@ZMS3UwFt{;x+-v zhx{m=m;4dgvkKp5{*lfSN3o^keSpp9{hlXj%=}e_7Ou{Yiw(J@NXuh*;pL6@$HsfB zh?v+r^cp@jQ4EspC#RqpwPY(}_SS$wZ{S959`C25777&sgtNh%XTCo9VHJC-G z;;wi9{-iv+ETiY;K9qvlEc04f;ZnUP>cUL_T*ms``EtGoP^B#Q>n2dSrbAg8a>*Lg zd0EJ^=tdW~7fbcLFsqryFEcy*-8!?;n%;F+8i{eZyCDaiYxghr z$8k>L|2&-!lhvuVdk!r-kpSFl`5F5d4DJr%M4-qOy3gdmQbqF1=aBtRM7)c_Ae?$b8 zQg4c8*KQ{XJmL)1c7#0Yn0#PTMEs4-IHPjkn0!=;JdhMXqzMLeh`yOylXROP- zl#z3+fwM9l3%VN(6R77ua*uI9%hO7l7{+Hcbr(peh;afUK?B4EC09J{-u{mv)+u#? zdKVBCPt`eU@IzL)OXA`Ebu`Xp?u0m%h&X41}FNfnJ*g1!1wcbbpo%F4x!-#R9ft!8{5`Ho}04?FI#Kg zL|k`tF1t_`ywdy8(wnTut>HND(qNnq%Sq=AvvZbXnLx|mJhi!*&lwG2g|edBdVgLy zjvVTKHAx(+&P;P#2Xobo7_RttUi)Nllc}}hX>|N?-u5g7VJ-NNdwYcaOG?NK=5)}` zMtOL;o|i0mSKm(UI_7BL_^6HnVOTkuPI6y@ZLR(H?c1cr-_ouSLp{5!bx^DiKd*Yb z{K78Ci&Twup zTKm)ioN|wcYy%Qnwb)IzbH>W!;Ah5Zdm_jRY`+VRJ2 zhkspZ9hbK3iQD91A$d!0*-1i#%x81|s+SPRmD}d~<1p6!A13(!vABP2kNgqEG z?AMgl^P+iRoIY(9@_I?n1829lGvAsRnHwS~|5vD2+Zi53j<5N4wNn0{q>>jF9*bI) zL$kMXM-awNOElF>{?Jr^tOz1glbwaD-M0OKOlTeW3C!1ZyxRbB>8JDof(O&R1bh%3x#>y2~<>OXO#IIedH0Q`(&&?eo-c~ z>*Ah#3~09unym~UC-UFqqI>{dmUD$Y4@evG#ORLI*{ZM)Jl=e1it!XzY($S3V zLG!Y6fCjE>x6r@5FG1n|8ompSZaJ>9)q6jqU;XxCQk9zV(?C9+i*>w z21+KYt1gXX&0`x3E)hS7I5}snbBzox9C@Xzcr|{B8Hw;SY1$}&BoYKXH^hpjW-RgJ z-Fb}tannKCv>y~^`r|(1Q9;+sZlYf3XPSX|^gR01UFtu$B*R;$sPZdIZShRr>|b@J z;#G{EdoY+O;REEjQ}X7_YzWLO+Ey3>a_KDe1CjSe| z6arqcEZ)CX!8r(si`dqbF$uu&pnf^Np{1f*TdJ`r2;@SaZ z#hb4xlaCA@Pwqj#LlUEe5L{I$k(Zj$d3(~)u(F%&xb8={N9hKxlZIO1ABsM{Mt|)2 zJ^t9Id;?%4PfR4&Ph9B9cFK~@tG3wlFW-0fXZS_L4U*EiAA%+`h%q2^6BCC;t0iO4V=s4Qug{M|iDV@s zC7|ef-dxiR7T&Mpre!%hiUhHM%3Qxi$Lzw6&(Tvlx9QA_7LhYq<(o~=Y>3ka-zrQa zhGpfFK@)#)rtfz61w35^sN1=IFw&Oc!Nah+8@qhJ0UEGr;JplaxOGI82OVqZHsqfX ze1}r{jy;G?&}Da}a7>SCDsFDuzuseeCKof|Dz2BPsP8? zY;a)Tkr2P~0^2BeO?wnzF_Ul-ekY=-w26VnU%U3f19Z-pj&2 z4J_a|o4Dci+MO)mPQIM>kdPG1xydiR9@#8m zh27D7GF{p|a{8({Q-Pr-;#jV{2zHR>lGoFtIfIpoMo?exuQyX_A;;l0AP4!)JEM$EwMInZkj+8*IHP4vKRd zKx_l-i*>A*C@{u%ct`y~s6MWAfO{@FPIX&sg8H{GMDc{4M3%$@c8&RAlw0-R<4DO3 trJqdc$mBpWeznn?E0M$F`|3v=`3%T2A17h;rxP7$%JLd=6(2u;`(N3pt&so# literal 0 HcmV?d00001 diff --git a/sigiStatic/img/img_atendimento/logo.png b/sigiStatic/img/img_atendimento/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..bc94190650a910dc07eed4f4e8f29230a147e987 GIT binary patch literal 3868 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 SIzzQf1GVRixtY4~^*;dzQG0v< literal 0 HcmV?d00001 diff --git a/sigiStatic/img/img_atendimento/logo_interlegis.jpg b/sigiStatic/img/img_atendimento/logo_interlegis.jpg new file mode 100644 index 0000000000000000000000000000000000000000..06c178f74f5be7866eab495c8770cf6ae1307c91 GIT binary patch literal 2063 zcmb78dpy$%AO6jl88%}vV>!s3A&z8@`=z<9+zxh9h*G)bvPk9@QJKSBBUyyBy$#K+ zt6M@URBmgf+Jq#sbX@l8ec#X9`Q!cPeZJqnp6~NK-$ytud;`e1I1!ux5J&`2-yT3X z1=s_kVDOI>1&fM_ii$yC;*f1fOTeHogtUwdLK*=_%Arw6Sp``*Vu$h$1w}FF(a|N9_LEFtQb1@?>9^t4&$LE!QtnQmp5Gk;{g4PQA+&8H_#SS?}=pXOlP z{F*|IhRQ+-c_Y&jq+T7~emkPiNsv|)u;)<-koUiQ$|lVD`ICCTc+j%K0xa0ori^ci z$09tm5!0F4tDESrM$komlKOk?D{NwCM*HjY-(gye!o|0}@$q2X*wF2cg3_X;DO`V| zO@1aqHeveR{nSgROW#(xoU%F}*16GG_|A8#%OPZ^i-r|FFWf-&AnGmFvlf1hpY=5M zE^-+~Bx!LT@zE5r)aMQJDxUGB>n(H1XO%}uLmzY{i|yK_vu3K3D;x97i=V;RmJK|= z>4hZlEv=x`J~IBfimJnE)7fd=6^Q$r&$=A<+9BiFHvzVBw>9wvycILn417RcDNz3O zHS&2A3S{zJ2PuHY7JT90cCWbZLLfW5p-c1k)~R4>g|gG28z*Uuyq_#FB&Wn(tscqP z98GNxZ~6!1d)8|)cRf=t{kr~0Q(q*GY{F#N>g?l?A_-vhqG=mt^sMm+`jx+e9*cOa zv;NE2`l+4%1`VwQk3oN|i`Vk^V9&SZcN3C?K%JZEUfPAW&sEbSo%WmgF=sYaxuwS_ zW5yJT4X|M*xAS_H5WrdI-Z9i3wMq#|BfxnBSEQMBVNU$bN)0#j^?iBkX-%v#ks7V~ zaVjc5K{dN8iZeVr-!}_$rM*0GRIj|WnR_<{-*DuWhTvH{DBP0<8_TbirT@v&V(x8-Tw!c7DqGI1 zMH^fVs|yl%HD_FG8FKm{2)}q^v4>569}V;M_N_H9I(JFXYpt8hj72zKCc#`0ol_Q5 ztY8F9EZ~iRO)|g-&#TBsDH36x?Q%c7ZZ&~j3bvBw>RTk|mQf#A^ znChABW4Aw1yz)}HAy;0mB2n_pLbz)L^hAnxWj{d&eo5&@#XhN27*^JCkNJi_IN4AG@p={v<|QHvFbDmZS{BO; z-zcxTqWW=>!fsDP5IMI_-uM)|#n69j1J{_Tm7%A@r@2skstBv4EXoi!*AdD)L3rgO zNIX~nYu;E`k*J*>2zo)SIqqueEoNxmbJEUWIo8BET3o?>+_>NPlt*AtnT1*qs{b4| z3zH}ByT4d5LVK;4UaMnM*p##Y^xeM0aCp(h45(!|cD3>Q3pL{yZIQwdzi&wL;gLO%a(<(>I_Rqn4;?V(LvcE+S z3xV#Otq^pazs>QHjz1LD6GC^h|4?bF$)~U$cv$Z6KSJW{Y3m#vDpA=b7Qob>o#}l% zY)*=hbfK-J>XjVzc)WzA5FQOCe@-wliyI<`&*^{KHKZc2s1*Y9s6*12Lh^NIAJgYj z2EPklEKfA#`3yKVYRBArf)@hqHkH}Av`JZu`ryMk{$^bd%V&*M3J&9Y7S=of+|Rr&Xiy!~MYjV%YJ;@Gyc&S~BD_cYIc7iiH7KOKIUik~?~ ztqM-kv3dz#D_7@_U&$rj&VT@)bLduQb7pJS{eB9h(BPlWp8})9sA;zt=%jp4H_X16O;dP%P0# ID#D3>0;Xk}o&W#< literal 0 HcmV?d00001 diff --git a/templates/base.html b/templates/base.html index ac7b5209..edabff44 100644 --- a/templates/base.html +++ b/templates/base.html @@ -6,54 +6,48 @@ Sistema de Atendimento {% block head_content %} - + - + - + {% endblock head_content %} {% block navigation %} -