From ce924e68fd169dcb2672909dcdef8bc92d3ddd22 Mon Sep 17 00:00:00 2001 From: Leandro Roberto Date: Fri, 26 Feb 2021 21:31:46 -0300 Subject: [PATCH] Adiciona ImageField em compilacao.Dispositivo --- .../compilacao/js/old/compilacao_edit.js | 4 ++ .../compilacao/js/old/compilacao_view.js | 2 + .../__apps/compilacao/scss/compilacao.scss | 57 ++++++++++++------- .../js/image_cropping/image_cropping.js | 4 +- sapl/compilacao/forms.py | 56 +++++++++++++++--- .../migrations/0017_auto_20210225_1127.py | 25 ++++++++ sapl/compilacao/models.py | 21 ++++++- sapl/parlamentares/models.py | 1 + .../templates/compilacao/text_edit_bloco.html | 11 +++- .../templates/compilacao/text_list_bloco.html | 10 +++- .../compilacao/text_list_blocoalteracao.html | 10 +++- sapl/utils.py | 8 ++- 12 files changed, 166 insertions(+), 43 deletions(-) create mode 100644 sapl/compilacao/migrations/0017_auto_20210225_1127.py diff --git a/frontend/src/__apps/compilacao/js/old/compilacao_edit.js b/frontend/src/__apps/compilacao/js/old/compilacao_edit.js index 63135ab55..13c5aa331 100644 --- a/frontend/src/__apps/compilacao/js/old/compilacao_edit.js +++ b/frontend/src/__apps/compilacao/js/old/compilacao_edit.js @@ -228,6 +228,10 @@ window.DispositivoEdit = function () { dpt.find('.btn-group-inserts').removeClass('open show') }) + // Para ativar image_cropping dentro do editor dinâmico descolmentar linha abaixo + // e retirar restrição do backend que adiciona input image apenas o editor avançado + // window.image_cropping.init() + instance.gc() }) } diff --git a/frontend/src/__apps/compilacao/js/old/compilacao_view.js b/frontend/src/__apps/compilacao/js/old/compilacao_view.js index 4dc6a8e74..ef6d495b8 100644 --- a/frontend/src/__apps/compilacao/js/old/compilacao_view.js +++ b/frontend/src/__apps/compilacao/js/old/compilacao_view.js @@ -35,6 +35,7 @@ function textoMultiVigente (item, diff) { _$(item).addClass('active') _$('.dptt.desativado').removeClass('displaynone') _$('.desativado > .dn').removeClass('displaynone') + _$('.desativado > .dpt-img').removeClass('displaynone') _$('.dptt.revogado').removeClass('displaynone') _$('.dtxt').removeClass('displaynone') _$('.dtxt.diff').remove() @@ -133,6 +134,7 @@ function textoVigente (item, link) { _$(item).addClass('active') _$('.dptt.desativado').addClass('displaynone') _$('.desativado > .dn').addClass('displaynone') + _$('.desativado > .dpt-img').addClass('displaynone') _$('.nota-alteracao').removeClass('displaynone') _$('.dptt.revogado').removeClass('displaynone') if (!link) _$('.nota-alteracao').addClass('displaynone') diff --git a/frontend/src/__apps/compilacao/scss/compilacao.scss b/frontend/src/__apps/compilacao/scss/compilacao.scss index a6173399c..668de7383 100644 --- a/frontend/src/__apps/compilacao/scss/compilacao.scss +++ b/frontend/src/__apps/compilacao/scss/compilacao.scss @@ -200,7 +200,7 @@ a:link:after, a:visited:after { margin: -5px auto 0; border-radius: 50%; } - + & > a { position: absolute; white-space: nowrap; @@ -219,10 +219,10 @@ a:link:after, a:visited:after { margin-bottom: 5px; } } - + ul { z-index: 1; - position: absolute; + position: absolute; display: none; background: white; margin: 30px 0; @@ -252,7 +252,7 @@ a:link:after, a:visited:after { &:hover { background: #eee; } - } + } } &.active { @@ -329,6 +329,14 @@ a:link:after, a:visited:after { border: 1px dotted #ccc; } } + .dpt-img { + img { + filter: grayscale(100%) contrast(110%); + opacity: 0.5; + + + } + } } a { @@ -353,6 +361,12 @@ a:link:after, a:visited:after { &.indent { padding-left: 1em; } + .dpt-img { + text-align: center; + img { + max-width: 100%; + } + } .ementa { padding: 2em 0em 2em 35%; font-weight: bold; @@ -417,8 +431,8 @@ a:link:after, a:visited:after { margin-top: 0.3333em; font-size: 1.15em; } - - .texto_n_estruturado{ + + .texto_n_estruturado{ margin-top: 0.3333em; font-size: 1.15em; } @@ -443,16 +457,16 @@ a:link:after, a:visited:after { font-size: 1.0em; margin-top: 2px; } - + .assinatura { margin-top: 0.6em; font-size: 1.15em; } - + .fecho_lei { margin-top: 0.6em; font-size: 1.15em; - } + } .page-break { page-break-before: always; } .bloco_alteracao { @@ -714,6 +728,7 @@ a:link:after, a:visited:after { table, table td { border: 1px dotted #ccc; } + } a.nota-alteracao { @@ -1620,7 +1635,7 @@ a:link:after, a:visited:after { } } -.cp-nav-parents { +.cp-nav-parents { .dropdown-menu { left: 0; right: auto; @@ -1810,7 +1825,7 @@ a:link:after, a:visited:after { height: 8px; margin: -4px auto 0; } - + & > a { font-size: 0.75rem; } @@ -1824,22 +1839,22 @@ a:link:after, a:visited:after { margin-bottom: 4px; } } - + ul { a { line-height: 1.3rem; font-size: 0.7rem; background: #fff; - } + } } - + &.active { .circle { width: 14px; height: 14px; margin: -7px auto 0; } - + &:not(:last-child) { & > a { margin-bottom: 15px; @@ -1857,13 +1872,13 @@ a:link:after, a:visited:after { } @media print { - .cp .vigencias, - .toggle-topbar, - .menu-icon, - .button, - .tipo-vigencias, + .cp .vigencias, + .toggle-topbar, + .menu-icon, + .button, + .tipo-vigencias, .dne, - .cp-linha-vigencias, + .cp-linha-vigencias, .tipo-vigencias { display:none !important; } diff --git a/frontend/src/__global/js/image_cropping/image_cropping.js b/frontend/src/__global/js/image_cropping/image_cropping.js index eb30eb1c2..5f787ae5c 100644 --- a/frontend/src/__global/js/image_cropping/image_cropping.js +++ b/frontend/src/__global/js/image_cropping/image_cropping.js @@ -1,4 +1,4 @@ -/* eslint-disable */ +/* eslint-disable */ var image_cropping = (function ($) { var jcrop = {} function init() { @@ -184,7 +184,7 @@ var image_cropping = (function ($) { } })(jQuery) - +window.image_cropping = image_cropping jQuery(function() { /*var image_cropping_jquery_url = jQuery('.image-ratio:first').data('jquery-url') if (image_cropping_jquery_url == "None") { diff --git a/sapl/compilacao/forms.py b/sapl/compilacao/forms.py index 0a7542a46..8f504425b 100644 --- a/sapl/compilacao/forms.py +++ b/sapl/compilacao/forms.py @@ -13,7 +13,10 @@ from django.forms.forms import Form from django.forms.models import ModelForm from django.template import defaultfilters from django.utils.translation import ugettext_lazy as _ +from image_cropping.widgets import CropWidget, ImageCropWidget,\ + get_attrs from model_utils.choices import Choices +from prompt_toolkit.key_binding.bindings.named_commands import self_insert from sapl import utils from sapl.compilacao.models import (NOTAS_PUBLICIDADE_CHOICES, @@ -24,9 +27,20 @@ from sapl.compilacao.models import (NOTAS_PUBLICIDADE_CHOICES, VeiculoPublicacao, Vide) from sapl.compilacao.utils import DISPOSITIVO_SELECT_RELATED from sapl.crispy_layout_mixin import SaplFormHelper -from sapl.crispy_layout_mixin import SaplFormLayout, to_column, to_row,\ - form_actions -from sapl.utils import YES_NO_CHOICES +from sapl.crispy_layout_mixin import SaplFormLayout, to_column, to_row +from sapl.utils import YES_NO_CHOICES, FileFieldCheckMixin + + +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' + ) error_messages = { @@ -513,11 +527,16 @@ class DispositivoIntegerField(forms.IntegerField): min_value=0, *args, **kwargs) -class DispositivoEdicaoBasicaForm(ModelForm): +class DispositivoEdicaoBasicaForm(FileFieldCheckMixin, ModelForm): class Meta: model = Dispositivo - fields = [] + fields = ['imagem', 'imagem_cropping'] + + widgets = { + 'imagem': CustomImageCropWidget(), + 'imagem_cropping': CropWidget(), + } error_messages = { NON_FIELD_ERRORS: { @@ -670,13 +689,24 @@ class DispositivoEdicaoBasicaForm(ModelForm): DispositivoEdicaoBasicaForm.Meta.fields.remove( 'visibilidade') - fields = DispositivoEdicaoBasicaForm.Meta.fields - if fields: - self.base_fields.clear() - for f in fields: + if 'texto' in DispositivoEdicaoBasicaForm.Meta.fields and\ + not editor_type: + layout.append( + Fieldset('Anexar Imagem', + to_row([('imagem', 7), ('imagem_cropping', 5), ]), + css_class="col-md-12")) + DispositivoEdicaoBasicaForm.Meta.fields.append('imagem') + DispositivoEdicaoBasicaForm.Meta.fields.append('imagem_cropping') + + for f in DispositivoEdicaoBasicaForm.Meta.fields: + if hasattr(self, f): self.base_fields.update({f: getattr(self, f)}) + for f in set(self.base_fields.keys()) - set(DispositivoEdicaoBasicaForm.Meta.fields): + self.base_fields.pop(f) + self.helper = SaplFormHelper() + self.helper.include_media = False if not editor_type: cancel_label = _('Ir para o Editor Sequencial') @@ -689,6 +719,14 @@ class DispositivoEdicaoBasicaForm(ModelForm): super(DispositivoEdicaoBasicaForm, self).__init__(*args, **kwargs) + #imagem = self.fields['imagem'].widget + # imagem.attrs.update( + # get_attrs(self.instance.imagem, 'imagem') + #) + + # if 'class' in imagem.attrs: + # imagem.attrs.pop('class') + def actions_get_form_base(self, layout, inst, texto_articulado_do_editor=None): diff --git a/sapl/compilacao/migrations/0017_auto_20210225_1127.py b/sapl/compilacao/migrations/0017_auto_20210225_1127.py new file mode 100644 index 000000000..df4ccca5a --- /dev/null +++ b/sapl/compilacao/migrations/0017_auto_20210225_1127.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.13 on 2021-02-25 14:27 + +from django.db import migrations +import image_cropping.fields +import sapl.compilacao.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0016_auto_20201013_1159'), + ] + + operations = [ + migrations.AddField( + model_name='dispositivo', + name='imagem', + field=image_cropping.fields.ImageCropField(blank=True, null=True, upload_to=sapl.compilacao.models.imagem_upload_path, verbose_name='Imagem'), + ), + migrations.AddField( + model_name='dispositivo', + name='imagem_cropping', + field=image_cropping.fields.ImageRatioField('imagem', '0x0', adapt_rotation=False, allow_fullsize=False, free_crop=False, help_text='O recorte de imagem é possível após a atualização.', hide_image_field=False, size_warning=True, verbose_name='Recorte de Imagem'), + ), + ] diff --git a/sapl/compilacao/models.py b/sapl/compilacao/models.py index bde388b23..3c6fee330 100644 --- a/sapl/compilacao/models.py +++ b/sapl/compilacao/models.py @@ -1,5 +1,3 @@ - -from bs4 import BeautifulSoup from django.contrib import messages from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType @@ -13,11 +11,13 @@ from django.utils import timezone from django.utils.decorators import classonlymethod from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ +from image_cropping.fields import ImageCropField, ImageRatioField import reversion from sapl.compilacao.utils import (get_integrations_view_names, int_to_letter, int_to_roman) -from sapl.utils import YES_NO_CHOICES, get_settings_auth_user_model +from sapl.utils import YES_NO_CHOICES, get_settings_auth_user_model,\ + texto_upload_path, restringe_tipos_de_arquivo_img @reversion.register() @@ -965,6 +965,10 @@ class Publicacao(TimestampedMixin): self.ta) +def imagem_upload_path(instance, filename): + return texto_upload_path(instance, filename, subpath='') + + @reversion.register() class Dispositivo(BaseModel, TimestampedMixin): TEXTO_PADRAO_DISPOSITIVO_REVOGADO = force_text(_('(Revogado)')) @@ -1186,6 +1190,17 @@ class Dispositivo(BaseModel, TimestampedMixin): verbose_name=_('Contagem contínua') ) + imagem = ImageCropField( + verbose_name=_('Imagem'), + upload_to=imagem_upload_path, + validators=[restringe_tipos_de_arquivo_img], null=True, blank=True) + + imagem_cropping = ImageRatioField( + 'imagem', '100x100', verbose_name=_('Recorte de Imagem'), + free_crop=True, size_warning=True, + help_text=_('O recorte de imagem ' + 'é possível após a atualização.')) + class Meta: verbose_name = _('Dispositivo') verbose_name_plural = _('Dispositivos') diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index c02c4677a..f9ced6221 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -271,6 +271,7 @@ class Parlamentar(models.Model): verbose_name=_('Ativo na Casa?')) biografia = models.TextField( blank=True, verbose_name=_('Biografia')) + fotografia = ImageCropField( verbose_name=_('Fotografia'), upload_to=foto_upload_path, validators=[restringe_tipos_de_arquivo_img], null=True, blank=True) diff --git a/sapl/templates/compilacao/text_edit_bloco.html b/sapl/templates/compilacao/text_edit_bloco.html index a90c2ecd7..496122bbc 100644 --- a/sapl/templates/compilacao/text_edit_bloco.html +++ b/sapl/templates/compilacao/text_edit_bloco.html @@ -1,5 +1,5 @@ {% load i18n %} -{% load compilacao_filters %} +{% load compilacao_filters cropping%} {% load common_tags %} {% dispositivotree dispositivos_list %} @@ -53,11 +53,18 @@ {% endif %} + + {% if node.dpt.imagem %} +
+ Dispositivo possui imagem anexada.
+ A visualização junto ao texto é possível no PreView e/ou após publicado. Ou no Editor Avançado do dispositivo acima. +
+ {% endif %}
{% if node.alts or node.td.dispositivo_de_alteracao and node.td.dispositivo_de_articulacao %}
{{ alts }} - +
{% if node.td.dispositivo_de_alteracao %}
diff --git a/sapl/templates/compilacao/text_list_bloco.html b/sapl/templates/compilacao/text_list_bloco.html index e83b5faf7..d2108a0e5 100644 --- a/sapl/templates/compilacao/text_list_bloco.html +++ b/sapl/templates/compilacao/text_list_bloco.html @@ -1,6 +1,7 @@ {% load i18n %} {% load compilacao_filters %} {% load common_tags %} +{% load cropping %} {% for dpt in object_list %} {% if dpt.tipo_dispositivo.dispositivo_de_alteracao and not dpt.tipo_dispositivo.dispositivo_de_articulacao%} @@ -28,7 +29,7 @@ {{ dpt.tipo_dispositivo.rotulo_prefixo_html|safe }} {{ dpt.rotulo }}{{ dpt.tipo_dispositivo.rotulo_sufixo_html|safe }} {% endif %} - + {% endif %} {{ dpt.tipo_dispositivo.texto_prefixo_html|safe }}{%if dpt.texto %}{{ dpt.texto|safe }}{%else%}{%if not dpt.tipo_dispositivo.dispositivo_de_articulacao %} {% endif %}{% endif %} @@ -177,6 +178,13 @@
{% endif%} + + {% if dpt.imagem %} +
+ +
+ {% endif %} + {% if dpt.ta_publicado_id %}
diff --git a/sapl/templates/compilacao/text_list_blocoalteracao.html b/sapl/templates/compilacao/text_list_blocoalteracao.html index 8e17e3d01..452a49459 100644 --- a/sapl/templates/compilacao/text_list_blocoalteracao.html +++ b/sapl/templates/compilacao/text_list_blocoalteracao.html @@ -1,5 +1,4 @@ -{% load compilacao_filters %} -{% load common_tags %} +{% load compilacao_filters cropping common_tags %} {% for ch in dpt.pk|get_bloco_atualizador %} {% spaceless %} {% if ch.visibilidade %} @@ -30,6 +29,13 @@ {{ ch.tipo_dispositivo.rotulo_sufixo_html|safe }} {{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{% if ch.texto_atualizador %}{{ ch.texto_atualizador|safe }}{%else%}{{ ch.texto|safe }}{% endif %} {% endif %} + + {% if ch.imagem %} +
+ +
+ {% endif %} + {% endif %} diff --git a/sapl/utils.py b/sapl/utils.py index 7e57b027e..03e69d993 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -6,7 +6,6 @@ from operator import itemgetter import os import platform import re -import requests import tempfile from time import time from unicodedata import normalize as unicodedata_normalize @@ -35,6 +34,7 @@ import django_filters from easy_thumbnails import source_generators from floppyforms import ClearableFileInput import magic +import requests from reversion_compare.admin import CompareVersionAdmin from unipath.path import Path @@ -592,6 +592,7 @@ def fabrica_validador_de_tipos_de_arquivo(lista, nome): restringe_tipos_de_arquivo_txt = fabrica_validador_de_tipos_de_arquivo( TIPOS_TEXTO_PERMITIDOS, 'restringe_tipos_de_arquivo_txt') + restringe_tipos_de_arquivo_img = fabrica_validador_de_tipos_de_arquivo( TIPOS_IMG_PERMITIDOS, 'restringe_tipos_de_arquivo_img') @@ -1025,11 +1026,12 @@ def timing(f): ts = time() result = f(*args, **kw) te = time() - logger.info('funcao:%r args:[%r, %r] took: %2.4f sec' % \ - (f.__name__, args, kw, te-ts)) + logger.info('funcao:%r args:[%r, %r] took: %2.4f sec' % + (f.__name__, args, kw, te - ts)) return result return wrap + @timing def lista_anexados(principal): from sapl.materia.models import MateriaLegislativa