diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 408cabd90..4e8c9e452 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -19,6 +19,7 @@ django-sass-processor==0.5.4 djangorestframework==3.4.0 drfdocs==0.0.11 easy-thumbnails==2.3 +django-image-cropping==1.1.0 git+git://github.com/interlegis/trml2pdf.git libsass==0.11.1 psycopg2==2.7.3 diff --git a/sapl/crud/base.py b/sapl/crud/base.py index d53125249..44852d7a0 100644 --- a/sapl/crud/base.py +++ b/sapl/crud/base.py @@ -17,8 +17,8 @@ from django.http.response import Http404 from django.shortcuts import redirect from django.utils.decorators import classonlymethod from django.utils.encoding import force_text -from django.utils.translation import ugettext_lazy as _ from django.utils.translation import string_concat +from django.utils.translation import ugettext_lazy as _ from django.views.generic import (CreateView, DeleteView, DetailView, ListView, UpdateView) from django.views.generic.base import ContextMixin @@ -30,6 +30,7 @@ from sapl.rules.map_rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL, from sapl.settings import BASE_DIR from sapl.utils import normalize + logger = logging.getLogger(BASE_DIR.name) ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \ @@ -39,6 +40,7 @@ ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \ def _form_invalid_message(msg): return '%s %s' % (_('Formulário inválido.'), msg) + FORM_MESSAGES = {ACTION_CREATE: (_('Registro criado com sucesso!'), _('O registro não foi criado.')), ACTION_UPDATE: (_('Registro alterado com sucesso!'), @@ -79,6 +81,7 @@ def make_pagination(index, num_pages): head = from_to(1, PAGINATION_LENGTH - len(tail) - 1) return head + [None] + tail + """ variáveis do crud: help_topic diff --git a/sapl/parlamentares/forms.py b/sapl/parlamentares/forms.py index 77be6ac80..32a1b67de 100644 --- a/sapl/parlamentares/forms.py +++ b/sapl/parlamentares/forms.py @@ -13,6 +13,7 @@ from django.forms import ModelForm from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from floppyforms.widgets import ClearableFileInput +from image_cropping.widgets import ImageCropWidget, CropWidget from sapl.crispy_layout_mixin import form_actions, to_row from sapl.rules import SAPL_GROUP_VOTANTE @@ -26,6 +27,18 @@ class ImageThumbnailFileInput(ClearableFileInput): template_name = 'floppyforms/image_thumbnail.html' +class CustomImageCropWidget(ImageCropWidget): + """ + Custom ImageCropWidget that doesn't show the initial value of the field. + We use this trick, and place it right under the CropWidget so that + it looks like the user is seeing the image and clearing the image. + """ + template_with_initial = ( + # '%(initial_text)s: %(initial)s ' + '%(clear_template)s
%(input_text)s: %(input)s' + ) + + def validar_datas_legislatura(eleicao, inicio, fim, pk=None): # Verifica se data de eleição < inicio < fim @@ -128,9 +141,12 @@ class ParlamentarForm(ModelForm): class Meta: model = Parlamentar exclude = [] - widgets = {'fotografia': ImageThumbnailFileInput, - 'biografia': forms.Textarea( - attrs={'id': 'texto-rico'})} + + widgets = { + 'fotografia': CustomImageCropWidget(), + 'cropping': CropWidget(), + 'biografia': forms.Textarea( + attrs={'id': 'texto-rico'})} class ParlamentarCreateForm(ParlamentarForm): diff --git a/sapl/parlamentares/migrations/0016_auto_20180202_1331.py b/sapl/parlamentares/migrations/0016_auto_20180202_1331.py new file mode 100644 index 000000000..693660a1a --- /dev/null +++ b/sapl/parlamentares/migrations/0016_auto_20180202_1331.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-02-02 15:31 +from __future__ import unicode_literals + +from django.db import migrations +import image_cropping.fields +import sapl.parlamentares.models +import sapl.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0015_auto_20180131_1629'), + ] + + operations = [ + migrations.AddField( + model_name='parlamentar', + name='cropping', + field=image_cropping.fields.ImageRatioField('fotografia', '128x128', adapt_rotation=False, allow_fullsize=False, free_crop=False, + help_text='A configuração do Avatar é possível após a atualização da fotografia.', hide_image_field=False, size_warning=False, verbose_name='Avatar'), + ), + migrations.AlterField( + model_name='parlamentar', + name='fotografia', + field=image_cropping.fields.ImageCropField(blank=True, null=True, upload_to=sapl.parlamentares.models.foto_upload_path, validators=[ + sapl.utils.restringe_tipos_de_arquivo_img], verbose_name='Fotografia'), + ), + ] diff --git a/sapl/parlamentares/migrations/0017_auto_20180202_1528.py b/sapl/parlamentares/migrations/0017_auto_20180202_1528.py new file mode 100644 index 000000000..89fc81314 --- /dev/null +++ b/sapl/parlamentares/migrations/0017_auto_20180202_1528.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-02-02 17:28 +from __future__ import unicode_literals + +from django.db import migrations +import image_cropping.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0016_auto_20180202_1331'), + ] + + operations = [ + migrations.AlterField( + model_name='parlamentar', + name='cropping', + field=image_cropping.fields.ImageRatioField('fotografia', '128x128', adapt_rotation=False, allow_fullsize=False, free_crop=False, help_text='A configuração do Avatar é possível após a atualização da fotografia.', hide_image_field=False, size_warning=True, verbose_name='Avatar'), + ), + ] diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index d58102e87..239e75cab 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -1,9 +1,10 @@ -import reversion from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ +from image_cropping.fields import ImageCropField, ImageRatioField from model_utils import Choices +import reversion from sapl.base.models import Autor from sapl.decorators import vigencia_atual @@ -262,12 +263,14 @@ class Parlamentar(models.Model): blank=True, verbose_name=_('Biografia')) # XXX Esse atribuito foi colocado aqui para não atrapalhar a migração - fotografia = models.ImageField( - blank=True, - null=True, - upload_to=foto_upload_path, - verbose_name=_('Fotografia'), - validators=[restringe_tipos_de_arquivo_img]) + fotografia = ImageCropField( + verbose_name=_('Fotografia'), upload_to=foto_upload_path, + validators=[restringe_tipos_de_arquivo_img], null=True, blank=True) + + cropping = ImageRatioField( + 'fotografia', '128x128', verbose_name=_('Avatar'), size_warning=True, + help_text=_('A configuração do Avatar ' + 'é possível após a atualização da fotografia.')) # campo conceitual de reversão genérica para o model Autor que dá a # o meio possível de localização de tipos de autores. diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index e708af3f5..a96873f18 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -1,5 +1,5 @@ -import json from datetime import datetime +import json from django.contrib import messages from django.contrib.contenttypes.models import ContentType @@ -33,6 +33,7 @@ from .models import (CargoMesa, Coligacao, ComposicaoColigacao, ComposicaoMesa, NivelInstrucao, Parlamentar, Partido, SessaoLegislativa, SituacaoMilitar, TipoAfastamento, TipoDependente, Votante) + CargoMesaCrud = CrudAux.build(CargoMesa, 'cargo_mesa') PartidoCrud = CrudAux.build(Partido, 'partidos') SessaoLegislativaCrud = CrudAux.build(SessaoLegislativa, 'sessao_legislativa') @@ -396,7 +397,6 @@ class ParlamentarCrud(Crud): class BaseMixin(Crud.BaseMixin): ordered_list = False list_field_names = [ - 'avatar_html', 'nome_parlamentar', 'filiacao_atual', 'ativo', @@ -421,6 +421,10 @@ class ParlamentarCrud(Crud): class UpdateView(Crud.UpdateView): form_class = ParlamentarForm + @property + def layout_key(self): + return 'ParlamentarUpdate' + class CreateView(Crud.CreateView): form_class = ParlamentarCreateForm @@ -477,8 +481,7 @@ class ParlamentarCrud(Crud): mandato_titular=F('mandato__titular')) def get_headers(self): - return ['', - _('Parlamentar'), _('Partido'), + return [_('Parlamentar'), _('Partido'), _('Ativo?'), _('Titular?')] def get_context_data(self, **kwargs): @@ -489,54 +492,44 @@ class ParlamentarCrud(Crud): context['legislaturas'] = legislaturas context['legislatura_id'] = self.take_legislatura_id() - # Tira Link do avatar_html e coloca no nome for row in context['rows']: - # preenche coluna foto, se vazia - if not row[0][0]: - img = "
" \ - % static('img/avatar.png') - row[0] = (img, row[0][1]) + # Pega o Parlamentar por meio da pk + parlamentar = Parlamentar.objects.get( + id=(row[0][1].split('/')[-1])) + + row[0] += (parlamentar, ) + + # Pega a Legislatura + legislatura = Legislatura.objects.get( + id=context['legislatura_id']) # Coloca a filiação atual ao invés da última - if row[0][1]: - # Pega o Parlamentar por meio da pk - parlamentar = Parlamentar.objects.get( - id=(row[0][1].split('/')[-1])) - - # Pega a Legislatura - legislatura = Legislatura.objects.get( - id=context['legislatura_id']) - - # As condições para mostrar a filiação são: - # A data de filiacao deve ser menor que a data de fim - # da legislatura e data de desfiliação deve nula, ou maior, - # ou igual a data de fim da legislatura - try: - filiacao = parlamentar.filiacao_set.get(Q( - data__lte=legislatura.data_fim, - data_desfiliacao__gte=legislatura.data_fim) | Q( - data__lte=legislatura.data_fim, - data_desfiliacao__isnull=True)) - - # Caso não exista filiação com essas condições - except ObjectDoesNotExist: - row[2] = ('Não possui filiação', None) - - # Caso exista mais de uma filiação nesse intervalo - # Entretanto, NÃO DEVE OCORRER - except MultipleObjectsReturned: - row[2] = ( - 'O Parlamentar possui duas filiações conflitantes', - None) - - # Caso encontre UMA filiação nessas condições - else: - row[2] = (filiacao.partido.sigla, None) - - row[1] = (row[1][0], row[0][1]) - row[0] = (row[0][0], None) + # As condições para mostrar a filiação são: + # A data de filiacao deve ser menor que a data de fim + # da legislatura e data de desfiliação deve nula, ou maior, + # ou igual a data de fim da legislatura + try: + filiacao = parlamentar.filiacao_set.get(Q( + data__lte=legislatura.data_fim, + data_desfiliacao__gte=legislatura.data_fim) | Q( + data__lte=legislatura.data_fim, + data_desfiliacao__isnull=True)) + + # Caso não exista filiação com essas condições + except ObjectDoesNotExist: + row[1] = ('Não possui filiação', None) + + # Caso exista mais de uma filiação nesse intervalo + # Entretanto, NÃO DEVE OCORRER + except MultipleObjectsReturned: + row[1] = ( + 'O Parlamentar possui duas filiações conflitantes', + None) + + # Caso encontre UMA filiação nessas condições + else: + row[1] = (filiacao.partido.sigla, None) return context diff --git a/sapl/settings.py b/sapl/settings.py index 5c933d57f..fcf6707f7 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -17,11 +17,13 @@ import logging from decouple import config from dj_database_url import parse as db_url +from easy_thumbnails.conf import Settings as thumbnail_settings from unipath import Path from .temp_suppress_crispy_form_warnings import \ SUPRESS_CRISPY_FORM_WARNINGS_LOGGING + BASE_DIR = Path(__file__).ancestor(1) PROJECT_DIR = Path(__file__).ancestor(2) @@ -79,6 +81,7 @@ INSTALLED_APPS = ( 'bootstrap3', # basically for django_admin_bootstrapped 'crispy_forms', 'easy_thumbnails', + 'image_cropping', 'floppyforms', 'haystack', 'sass_processor', @@ -181,6 +184,12 @@ DATABASES = { ) } +IMAGE_CROPPING_JQUERY_URL = None +THUMBNAIL_PROCESSORS = ( + 'image_cropping.thumbnail_processors.crop_corners', +) + thumbnail_settings.THUMBNAIL_PROCESSORS + + # troque no caso de reimplementação da classe User conforme # https://docs.djangoproject.com/en/1.9/topics/auth/customizing/#substituting-a-custom-user-model AUTH_USER_MODEL = 'auth.User' @@ -300,6 +309,7 @@ def excepthook(*args): # sys.excepthook = excepthook + PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.PBKDF2PasswordHasher', # default 'sapl.hashers.ZopeSHA1PasswordHasher', diff --git a/sapl/static/js/app.js b/sapl/static/js/app.js index 7b18b6aad..132d96e3b 100644 --- a/sapl/static/js/app.js +++ b/sapl/static/js/app.js @@ -1,3 +1,4 @@ + function initTinymce(elements, readonly=false) { removeTinymce(); var config_tinymce = { diff --git a/sapl/static/styles/app.scss b/sapl/static/styles/app.scss index 8d50f217d..2edac98fc 100644 --- a/sapl/static/styles/app.scss +++ b/sapl/static/styles/app.scss @@ -252,8 +252,8 @@ fieldset { } .avatar-parlamentar { - height: 106px; - width: 141px; + height: 128px; + width: 128px; margin: 0 auto; display: table; } diff --git a/sapl/templates/base.html b/sapl/templates/base.html index 042ac51a9..c6a5d223b 100644 --- a/sapl/templates/base.html +++ b/sapl/templates/base.html @@ -20,6 +20,7 @@ + {# Scripts #} {# modernizr must be in head (see http://modernizr.com/docs/#installing) #} @@ -224,7 +225,6 @@ {% block foot_js %} - diff --git a/sapl/templates/parlamentares/layouts.yaml b/sapl/templates/parlamentares/layouts.yaml index 49d769dd9..d31076100 100644 --- a/sapl/templates/parlamentares/layouts.yaml +++ b/sapl/templates/parlamentares/layouts.yaml @@ -45,6 +45,23 @@ Parlamentar: - fotografia - biografia +ParlamentarUpdate: + {% trans 'Cadastro do Parlamentar' %}: + - nome_parlamentar:8 ativo + - nome_completo + - nivel_instrucao sexo data_nascimento + - cpf rg titulo_eleitor + - situacao_militar profissao + - endereco_web + - email + - numero_gab_parlamentar telefone fax + - endereco_residencia cep_residencia + - municipio_residencia uf_residencia + - telefone_residencia fax_residencia + - locais_atuacao + - fotografia cropping + - biografia + ParlamentarCreate: {% trans 'Dados do Mandato' %}: - legislatura data_expedicao_diploma diff --git a/sapl/templates/parlamentares/parlamentares_list.html b/sapl/templates/parlamentares/parlamentares_list.html index a2139852f..0afaceae4 100644 --- a/sapl/templates/parlamentares/parlamentares_list.html +++ b/sapl/templates/parlamentares/parlamentares_list.html @@ -1,18 +1,63 @@ {% extends "crud/list.html" %} {% load i18n %} -{% load crispy_forms_tags %} +{% load crispy_forms_tags cropping%} {% block extra_content %} -
- Selecione o Período -
- -
-
-
+
+ Selecione o Período +
+ +
+
+
+{% endblock %} + +{% block container_table_list %} + {% if not rows %} +

{{ NO_ENTRIES_MSG }}

+ {% else %} +
+
{% blocktrans with verbose_name_plural=view.verbose_name_plural %}Total de {{ verbose_name_plural }}: {{count}}{% endblocktrans %}
+ + + + {% for name in headers %} + {% if forloop.first %} + + {% endfor %} + + + + {% for value_list in rows %} + + {% for value, href, obj in value_list %} + {% if forloop.first %} + + {% endif %} + + {% endfor %} + + {% endfor %} + + +
+ {% endif %} {% endblock %} diff --git a/sapl/templates/parlamentares/public_composicaomesa_form.html b/sapl/templates/parlamentares/public_composicaomesa_form.html index 48705f261..e50a0458e 100644 --- a/sapl/templates/parlamentares/public_composicaomesa_form.html +++ b/sapl/templates/parlamentares/public_composicaomesa_form.html @@ -1,5 +1,5 @@ {% extends "crud/detail.html" %} -{% load i18n %} +{% load i18n cropping%} {% block actions %} {% endblock %} {% block detail_content %} @@ -46,7 +46,7 @@ {% for p in composicao_mesa %} {% if p.parlamentar.fotografia %} - + {% else %} {% endif %}