...
 
Commits (59)
.env
.sass-cache
*.pyc
*.sqlite3*
*.sw[po]
......@@ -8,8 +6,8 @@
celerybeat-schedule
celerybeat.pid
contrib/
media/
public/
radarlegislativo/static/
secrets/
venv/
media/
......@@ -4,6 +4,7 @@ services:
build: .
ports:
- 127.0.0.1:8000:8000
- 127.0.0.1:9200:9200
env_file:
- .env
environment:
......
......@@ -20,6 +20,7 @@ services:
- memcache
volumes:
- ./secrets:/srv/secrets
- ./media:/srv/radarlegislativo/media/
restart: always
worker:
......
{
"categoria": ["palavras_chave", "00000", "00.000", "Pena"],
"categoria 2": ["outras palavras chave", "9999", "9.999", "processo"]
}
[
{
"name": "Penal Econômico",
"keywords": ["corrupção", "lavagem de dinheiro", "7.492", "7492", "9613", "9.613", "12846", "12.846", "8429", "8.429", "4.737", "4737", " 4.737", "anticorrupção", "improbidade", "contra administração pública", "contra o sistema financeiro", "contra o sistema tributário", "crime financeiro", "lavagem de dinheiro", "lavagem de capitais", "Corrupção eleitoral"],
"exclude": ["Corrupção de menores"]
},
{
"name": "Crianças e Adolescentes",
"keywords": ["Redução da maioridade", "maioridade penal", "período de internação", "Penalmente inimputável", " Penalmente inimputáveis", "socioeducativo", "8069", "8.069", "12594", "12.594", "228", "corrupção de menores", "Art. 228 da Constituição Federal", "Art. 227 da Constituição Federal", "sinase"]
},
{
"name": "Internet",
"keywords": ["Fakenews", "Misinformation", "Marco Civil", "invasão de computadores", "12737", "12.737", "cyberbullying", "direito ao esquecimento", "delitos informáticos", "crimes digitais", "crime digital", "12965", "12.965", "remoção de conteúdo", "retirada de conteúdo"],
"combined_keywords": [["rede social", "crime"], ["rede social", "pena"], ["internet", "crime"], ["internet", "pena"]]
}
]
......@@ -18,7 +18,7 @@
from django import forms
from django.conf import settings
from main.models import Projeto, Tag
from main.models import PalavraChave, Projeto, Tag
class TokenField(forms.CharField):
......@@ -30,25 +30,41 @@ class TokenField(forms.CharField):
class TagsField(forms.CharField):
@staticmethod
def _palavra_chave(nome):
nome = nome.strip()
if not nome:
return
obj, _ = PalavraChave.objects.get_or_create(
nome=nome,
defaults={'nome': nome}
)
return obj
def to_python(self, value):
"""Transforma as palavras-chave vindas do Raspador Legislativo em uma
sequência de `PalavraChave` e uma sequência de `Tag`"""
if not value:
raise forms.ValidationError("Palavras-chave são obrigatórias")
keywords = (tag.strip() for tag in value.split(','))
keywords = (self._palavra_chave(v) for v in value.split(','))
keywords = tuple(k for k in keywords if k)
tag_names = (
settings.PALAVRAS_CHAVE.get(keyword)
settings.PALAVRAS_CHAVE.get(keyword.nome)
for keyword in keywords
)
tag_names_cleaned = set(name for name in tag_names if name)
if not tag_names_cleaned:
return tuple()
return keywords, tuple()
qs = Tag.objects.filter(nome__in=tag_names_cleaned)
if not qs.exists():
return tuple()
return keywords, tuple()
return tuple(tag for tag in qs)
return keywords, tuple(tag for tag in qs)
class FormularioProjeto(forms.ModelForm):
......
......@@ -18,12 +18,13 @@
from django.test import TestCase, override_settings
from api.forms import FormularioProjeto
from main.models import Tag
from main.models import PalavraChave, Tag
PALAVRAS_CHAVE = {
"gdpr": "Privacidade",
"censura": u"Liberdade de expressão"
"censura": u"Liberdade de expressão",
"marco civil": "Privacidade"
}
......@@ -36,16 +37,23 @@ class TestCampoDeFormularioTags(TestCase):
data = {
"id_site": "42",
"origem": "SE",
"palavras_chave": "gdpr, censura",
"palavras_chave": "gdpr, marco civil, censura",
"token": "foobar"
}
form = FormularioProjeto(data)
nomes_tags = ("Privacidade", u"Liberdade de expressão")
tags = Tag.objects.filter(nome__in=nomes_tags)
form = FormularioProjeto(data)
self.assertTrue(form.is_valid())
self.assertEqual(2, len(form.cleaned_data['palavras_chave']))
self.assertIn(tags[0], form.cleaned_data['palavras_chave'])
self.assertIn(tags[1], form.cleaned_data['palavras_chave'])
keywords, tags = form.cleaned_data['palavras_chave']
self.assertEqual(2, len(tags))
self.assertEqual(3, len(keywords))
for t in Tag.objects.filter(nome__in=nomes_tags):
self.assertIn(t, tags)
for k in PalavraChave.objects.all():
self.assertIn(k, keywords)
@override_settings(PALAVRAS_CHAVE=PALAVRAS_CHAVE)
@override_settings(RASPADOR_API_TOKEN="foobar")
......
......@@ -24,7 +24,7 @@ from model_mommy import mommy
from main.models import Projeto, Tag
from main.tools.use_cases import DownloadProjects
from main.use_cases import FetchProjectsFromCamara
class TestRaspadorNovoProjeto(TestCase):
......@@ -45,14 +45,14 @@ class TestRaspadorNovoProjeto(TestCase):
@override_settings(RASPADOR_API_TOKEN=u"chave")
@override_settings(PALAVRAS_CHAVE={u"teste": u"Tag teste"})
@patch.object(DownloadProjects, 'execute')
@patch.object(FetchProjectsFromCamara, 'execute')
def test_novo_projeto(self, mock_execute):
response = self.client.post(self.url, data=self.data)
self.assertEqual(201, response.status_code)
mock_execute.assert_called_once_with(
source=Projeto.CAMARA,
external_id=31415,
tag_ids=(self.tag_teste.id,),
keywords_ids=(1,),
publish=False,
)
......
......@@ -20,7 +20,7 @@ from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from api.forms import FormularioProjeto
from main.tools.use_cases import DownloadProjects
from main.use_cases import FetchProjectsFromCamara, FetchProjectsFromSenado
@csrf_exempt
......@@ -30,14 +30,16 @@ def novo_projeto(request):
if not form.is_valid():
return JsonResponse(form.errors, status=400)
download = DownloadProjects()
switch = {'CA': FetchProjectsFromCamara(), 'SE': FetchProjectsFromSenado()}
keywords, tags = form.cleaned_data['palavras_chave']
download = switch.get(form.cleaned_data['origem'])
download.execute(
source=form.cleaned_data['origem'],
external_id=form.cleaned_data['id_site'],
tag_ids=tuple(tag.id for tag in form.cleaned_data['palavras_chave']),
keywords_ids=tuple(keyword.id for keyword in keywords),
tag_ids=tuple(tag.id for tag in tags),
publish=False
)
return JsonResponse(_serialize(form), status=201)
......
......@@ -17,7 +17,7 @@
from django.contrib import admin
from .models import Tag, Projeto, Tramitacao
from .models import Documento, PalavraChave, Projeto, Tag, Tramitacao
from .forms import ProjetoAddForm
......@@ -33,8 +33,8 @@ class ProjetoAdmin(admin.ModelAdmin):
search_fields = ("nome", "apelido", "autoria", "origem", "local")
initial_fields = ("origem", "id_site", "nome", "apelido", "publicado",
"tags", "autoria", "apresentacao", "ementa", "local",
"apensadas", "importante", "urgente",
"tags", "palavras_chave", "autoria", "apresentacao",
"ementa", "local", "apensadas", "importante", "urgente",
"arquivado", "impacto", "pontos_principais",
"informacoes_adicionais", "clippings",
"texto_acao", "promulgado", "nome_da_lei_promulgada",
......@@ -45,7 +45,7 @@ class ProjetoAdmin(admin.ModelAdmin):
readonly_fields = ("nome", "autoria", "apresentacao", "ementa", "local",
"apensadas", "ultima_atualizacao")
filter_horizontal = ("tags", )
filter_horizontal = ("tags", "palavras_chave")
actions = ('publicar', 'despublicar')
......@@ -118,6 +118,16 @@ class TramitacaoAdmin(admin.ModelAdmin):
return False
class DocumentoAdmin(admin.ModelAdmin):
filter_horizontal = ("tags", "projetos", "palavras_chave")
class PalavraChaveAdmin(admin.ModelAdmin):
pass
admin.site.register(Tag)
admin.site.register(Projeto, ProjetoAdmin)
admin.site.register(Tramitacao, TramitacaoAdmin)
admin.site.register(Documento, DocumentoAdmin)
admin.site.register(PalavraChave, PalavraChaveAdmin)
# -*- coding: utf-8 -*-
# Generated by Django 1.11.15 on 2018-08-21 11:50
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0024_auto_20180730_1132'),
]
operations = [
migrations.CreateModel(
name='Documento',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('titulo', models.CharField(max_length=255, verbose_name='T\xedtulo')),
('criado_em', models.DateTimeField(auto_now_add=True, db_index=True)),
('arquivo', models.FileField(blank=True, upload_to='documentos')),
('projetos', models.ManyToManyField(to='main.Projeto')),
('tags', models.ManyToManyField(to='main.Tag')),
],
options={
'ordering': ['-criado_em'],
},
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.15 on 2018-08-21 15:43
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0025_cria_modelo_documento'),
]
operations = [
migrations.AlterField(
model_name='documento',
name='projetos',
field=models.ManyToManyField(blank=True, to='main.Projeto'),
),
migrations.AlterField(
model_name='documento',
name='tags',
field=models.ManyToManyField(blank=True, to='main.Tag'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.15 on 2018-08-23 08:58
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0026_torna_tag_e_projeto_opcionais_para_documento'),
]
operations = [
migrations.AddField(
model_name='documento',
name='url',
field=models.URLField(blank=True, verbose_name='URL'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.15 on 2018-08-27 11:12
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0027_documento_url'),
]
operations = [
migrations.AlterField(
model_name='documento',
name='arquivo',
field=models.FileField(blank=True, null=True, upload_to='documentos'),
),
migrations.AlterField(
model_name='documento',
name='url',
field=models.URLField(blank=True, null=True, verbose_name='URL'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.15 on 2018-09-29 17:02
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0028_auto_20180827_1112'),
]
operations = [
migrations.CreateModel(
name='PalavraChave',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nome', models.CharField(max_length=256, unique=True, verbose_name='palavra-chave')),
],
),
migrations.AlterField(
model_name='tag',
name='icon',
field=models.FileField(blank=True, null=True, upload_to='tags-icons'),
),
migrations.AddField(
model_name='documento',
name='palavras_chave',
field=models.ManyToManyField(blank=True, to='main.PalavraChave'),
),
migrations.AddField(
model_name='projeto',
name='palavras_chave',
field=models.ManyToManyField(blank=True, to='main.PalavraChave'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.15 on 2018-09-29 17:51
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('main', '0029_auto_20180929_1702'),
]
operations = [
migrations.AlterModelOptions(
name='palavrachave',
options={'ordering': ('nome',), 'verbose_name': 'palavra-chave', 'verbose_name_plural': 'palavras-chave'},
),
]
......@@ -17,11 +17,14 @@
from __future__ import unicode_literals
import datetime
import os
from django.core.exceptions import ValidationError
from django.db import models
from django.dispatch import receiver
from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible
from django.utils.text import slugify
from django.urls import reverse
from markdownx.models import MarkdownxField
......@@ -35,6 +38,8 @@ URL_WEB_CAMARA = u'http://www.camara.gov.br/proposicoesWeb/fichadetramitacao?idP
@python_2_unicode_compatible
class Tag(models.Model):
"""Tags são categorias mais amplas do que palavras-chave, utilizadas para
organizar e filtrar os projetos de lei na interface"""
nome = models.CharField('tag', max_length=256)
slug = models.SlugField(max_length=20, unique=True)
icon = models.FileField(upload_to='tags-icons', null=True, blank=True)
......@@ -43,6 +48,25 @@ class Tag(models.Model):
return self.nome
@python_2_unicode_compatible
class PalavraChave(models.Model):
"""Palavras-chave são mais granulares do que as tags, normalmente vem como
parâmetro quando adiciona-se um projeto de lei automaticamente (por
exemplo, com o Raspador Legislativo). Armazena-se as palavras-chave para
fins de busca, de mapear o porquê de um projeto ter sido adicionado e para
podermos relacionar documentos a diversos projetos de lei de forma mais
granular do que via tags."""
nome = models.CharField('palavra-chave', max_length=256, unique=True)
def __str__(self):
return self.nome
class Meta:
verbose_name = 'palavra-chave'
verbose_name_plural = 'palavras-chave'
ordering = ('nome',)
class ProjetoManager(models.Manager):
def na_mesma_semana_que_o_dia(self, dia):
......@@ -70,6 +94,20 @@ class ProjetosNaoPublicadosManager(ProjetoManager):
return qs.filter(publicado=False)
class ProjetosPublicadosManager(ProjetoManager):
def get_queryset(self):
qs = super(ProjetosPublicadosManager, self).get_queryset()
return qs.filter(publicado=True)
class ProjetosNaoPublicadosManager(ProjetoManager):
def get_queryset(self):
qs = super(ProjetosNaoPublicadosManager, self).get_queryset()
return qs.filter(publicado=False)
@python_2_unicode_compatible
class Projeto(models.Model):
......@@ -99,6 +137,7 @@ class Projeto(models.Model):
id_site = models.IntegerField('id no site de origem')
nome = models.CharField(max_length=255)
tags = models.ManyToManyField(Tag)
palavras_chave = models.ManyToManyField(PalavraChave, blank=True)
apresentacao = models.DateField()
ementa = models.TextField()
......@@ -118,6 +157,7 @@ class Projeto(models.Model):
clippings = MarkdownxField(blank=True)
pontos_principais = MarkdownxField(blank=True)
texto_acao = models.TextField(blank=True)
publicado = models.BooleanField(db_index=True, default=True)
nome_da_lei_promulgada = models.CharField(max_length=255, blank=True,
help_text="Nome da lei caso o projeto tenha sido promulgado")
......@@ -171,6 +211,18 @@ class Projeto(models.Model):
elif self.origem == self.CAMARA:
return URL_WEB_CAMARA.format(self.id_site)
@property
def documentos(self):
if hasattr(self, '_documentos'):
return self._documentos
self._documentos = Documento.objects.filter(
models.Q(tags__in=self.tags.all()) |
models.Q(palavras_chave__in=self.palavras_chave.all()) |
models.Q(projetos=self)
)
return self._documentos
def link_para_edicao(self):
url_name = "admin:{}_{}_change".format(self._meta.app_label, self._meta.model_name)
return reverse(url_name, args=(self.id,))
......@@ -213,3 +265,40 @@ class Tramitacao(models.Model):
def __str__(self):
return u'{} - {} - {}'.format(self.projeto, self.data, self.local)
@python_2_unicode_compatible
class Documento(models.Model):
titulo = models.CharField('Título', max_length=255)
projetos = models.ManyToManyField(Projeto, blank=True)
tags = models.ManyToManyField(Tag, blank=True)
palavras_chave = models.ManyToManyField(PalavraChave, blank=True)
criado_em = models.DateTimeField(auto_now_add=True, db_index=True)
arquivo = models.FileField(upload_to='documentos', blank=True, null=True)
url = models.URLField('URL', blank=True, null=True)
# Forca um Documento a ter arquivo ou URL
def clean(self):
if not self.arquivo and not self.url:
raise ValidationError(
'Um documento deve ter Arquivo ou URL. Preencha um dos dois campos.'
)
if self.arquivo and self.url:
raise ValidationError(
'Um documento deve ter Arquivo ou URL. Apague um dos campos.'
)
def get_url(self):
return self.url if self.url else self.arquivo.url
def __str__(self):
return u'{} - {}'.format(self.titulo, self.arquivo)
class Meta:
ordering = ['-criado_em']
@receiver(models.signals.post_delete, sender=Documento)
def auto_delete_file_on_delete(sender, instance, **kwargs):
if instance.arquivo:
if os.path.isfile(instance.arquivo.path):
os.remove(instance.arquivo.path)
......@@ -17,11 +17,13 @@
import datetime
from django.core.exceptions import ValidationError
from django.test import TestCase
from model_mommy import mommy
from main.models import Projeto, Tramitacao
from main.models import Documento, PalavraChave, Projeto, Tag, Tramitacao
__all__ = ["TesteTramitacao"]
......@@ -50,3 +52,56 @@ class TesteTramitacao(TestCase):
tramitacao = mommy.make(Tramitacao,
descricao="descricao Inteiro teor")
self.assertEqual(tramitacao.descricao_limpa, "descricao ")
class TestProjetosListandoDocumentos(TestCase):
def setUp(self):
self.projetos = mommy.make(Projeto, _quantity=3)
self.tags = mommy.make(Tag, _quantity=3)
self.palavras_chave = mommy.make(PalavraChave, _quantity=3)
for proj, tag, palavra in zip(self.projetos, self.tags, self.palavras_chave):
proj.tags.add(tag)
proj.palavras_chave.add(palavra)
self.documento1 = mommy.make(Documento, tags=[self.tags[0]])
self.documento2 = mommy.make(Documento, projetos=[self.projetos[1]])
self.documento3 = mommy.make(Documento, palavras_chave=[self.palavras_chave[2]])
def test_documentos_por_tag(self):
projeto = Projeto.objects.get(pk=self.projetos[0].pk)
self.assertEqual(list(projeto.documentos), [self.documento1])
def test_documentos_por_projeto(self):
projeto = Projeto.objects.get(pk=self.projetos[1].pk)
self.assertEqual(list(projeto.documentos), [self.documento2])
def test_documentos_por_palavra_chave(self):
projeto = Projeto.objects.get(pk=self.projetos[2].pk)
self.assertEqual(list(projeto.documentos), [self.documento3])
class TestDocumentos(TestCase):
def test_documentos_com_arquivo(self):
mommy.make(Documento, arquivo=u'/srv/radarlegislativo/pip-selfcheck.json')
self.assertEqual(1, Documento.objects.count())
def test_documentos_com_url(self):
mommy.make(Documento, url=u'www.google.com')
self.assertEqual(1, Documento.objects.count())
def test_documentos_sem_url_ou_arquivo(self):
doc = Documento(
titulo='teste',
)
with self.assertRaises(ValidationError):
doc.clean()
def test_documentos_com_arquivo_E_url(self):
doc = Documento(
titulo='teste',
arquivo=u'/srv/radarlegislativo/pip-selfcheck.json',
url=u'www.google.com'
)
with self.assertRaises(ValidationError):
doc.clean()
......@@ -6,6 +6,8 @@ from mock import Mock, patch
from pytz import timezone
from unittest import TestCase
from django.test import tag
from main.models import Projeto, Tramitacao
from main.services import fetch_camara_project, fetch_senado_project
from main.use_cases import BaseFetchProjects, FetchProjectsFromCamara, FetchProjectsFromSenado
......@@ -54,13 +56,19 @@ class FetchProjectsFromCamaraTestCase(TestCase):
m_service.assert_called_once_with(1)
@tag('depends_on_order') # order das tramitações difere usando Docker
@vcr.use_cassette('main/tests/fixtures/vcr/project_camara_2150720.yaml')
def test_execute_creates_instance_not_previously_created(self):
camara_id = 2150720
tags = mommy.make('main.Tag', _quantity=2)
keywords = mommy.make('main.PalavraChave', _quantity=4)
assert 0 == Projeto.objects.filter(id_site=camara_id).count()
self.uc.execute(camara_id, tag_ids=[t.id for t in tags])
self.uc.execute(
camara_id,
tag_ids=[t.id for t in tags],
keywords_ids=[k.id for k in keywords]
)
instance = Projeto.objects.get(id_site=camara_id)
......@@ -75,6 +83,10 @@ class FetchProjectsFromCamaraTestCase(TestCase):
for t in tags:
assert t in instance.tags.all()
assert 4 == instance.palavras_chave.count()
for k in keywords:
assert k in instance.palavras_chave.all()
assert 7 == instance.tramitacao_set.count()
tramitacoes = instance.tramitacao_set.all()
......@@ -128,6 +140,7 @@ class FetchProjectsFromCamaraTestCase(TestCase):
assert 0 == instance.tags.count()
assert 0 == instance.tramitacao_set.count()
@tag('depends_on_order') # order das tramitações difere usando Docker
@vcr.use_cassette('main/tests/fixtures/vcr/project_camara_2150720.yaml')
def test_execute_creates_instance_without_tags_correctly(self):
camara_id = 2150720
......@@ -218,6 +231,7 @@ class FetchProjectsFromSenadoTestCase(TestCase):
m_service.assert_called_once_with(1)
@tag('depends_on_order') # order das tramitações difere usando Docker
@vcr.use_cassette('main/tests/fixtures/vcr/project_senado_2150720.yaml')
def test_execute_creates_instance_not_previously_created(self):
senado_id = 113947
......@@ -272,6 +286,7 @@ class FetchProjectsFromSenadoTestCase(TestCase):
assert 0 == instance.tags.count()
assert 0 == instance.tramitacao_set.count()
@tag('depends_on_order') # order das tramitações difere usando Docker
@vcr.use_cassette('main/tests/fixtures/vcr/project_senado_2150720.yaml')
def test_execute_creates_instance_without_tags_correctly(self):
senado_id = 113947
......
from main.models import Projeto, Tag, Tramitacao
from main.models import PalavraChave, Projeto, Tag, Tramitacao
from main.services import fetch_camara_project, fetch_senado_project
......@@ -10,13 +10,15 @@ class BaseFetchProjects:
def get_source(self):
raise NotImplementedError
def execute(self, external_id, tag_ids=None, previously_created=False):
def execute(self, external_id, tag_ids=None, keywords_ids=None, previously_created=False, publish=True):
service = self.get_service()
source = self.get_source()
tag_ids = tag_ids or []
keywords_ids = keywords_ids or []
kwargs = service(external_id)
tramitacoes = kwargs.pop('tramitacoes', [])
kwargs['publicado'] = publish
instance, created = Projeto.objects.update_or_create(
origem=source, id_site=external_id, defaults=kwargs)
......@@ -25,6 +27,9 @@ class BaseFetchProjects:
for tag in Tag.objects.filter(id__in=tag_ids):
instance.tags.add(tag)
for keyword in PalavraChave.objects.filter(id__in=keywords_ids):
instance.palavras_chave.add(keyword)
for tramitacao in tramitacoes:
tramitacao['projeto'] = instance
id_site = tramitacao.pop('id_site')
......
......@@ -147,7 +147,6 @@ USE_TZ = True
# https://docs.djangoproject.com/en/1.10/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
......@@ -158,6 +157,8 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
MEDIA_URL = '/media/'
# Cache
MEMCACHED_HOST = config("MEMCACHED_HOST", default=None)
MEMCACHED_PORT = config("MEMCACHED_PORT", default="11211")
......@@ -269,19 +270,16 @@ if CHECK_FOR_NEW_PROJECTS_EVERY:
# (palavras chaves para importação de projetos de lei pelo raspador)
def palavras_chave(arquivo):
if not arquivo:
return dict()
if not os.path.exists(arquivo):
if not arquivo or not os.path.exists(arquivo) or not os.path.isfile(arquivo):
return dict()
with open(arquivo) as objeto:
dados = json.load(objeto)
mapa = dict()
for tag, palavras_chave in dados.items():
for palavra_chave in palavras_chave:
mapa[palavra_chave.lower()] = tag
for tag in dados:
for palavra_chave in tag['keywords']:
mapa[palavra_chave.lower()] = tag['name']
return mapa
......
......@@ -17,6 +17,7 @@ from django.conf import settings
from django.conf.urls import url, include
from django.contrib import admin
from django.views.static import serve
from website.views import GrafoJson, TramitacoesFeed
......
......@@ -129,9 +129,36 @@
</div>
</div>
{% endfor %}
<br />
<div class="divider-horizontal"></div>
<br />
</div>
<br />
{% if projeto.documentos %}
<h2 class="section-title">documentos</h2>
<br />
<div class="documentos divisor-cruz">
<ul class="list-unstyled">
{% for documento in projeto.documentos %}
<li><a href="{{ documento.get_url }}">{{ documento.titulo }}</a></li>
{% endfor %}
</ul>
</div>
<br />
<div class="divider-horizontal"></div>
<br />
{% endif %}
{% if projeto.palavras_chave.all %}
<h2 class="section-title">palavras-chave</h2>
<p>
{% for palavra_chave in projeto.palavras_chave.all %}
{{ palavra_chave }}{% if not forloop.last %}, {% endif %}
{% endfor %}
</p>
<br />
{% endif %}
<div class="button-group">
{% if projeto.promulgado and projeto.link_da_lei_promulgada %}
......
......@@ -14,12 +14,15 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from main.models import Projeto
from django.urls import reverse
from model_mommy import mommy
from main.models import Documento, Projeto
__all__ = ["TestPaginaDeUmProjeto"]
__all__ = ["TestPaginaDeUmProjetoPublicado", "TestPaginaDeUmProjetoNaoPublicado"]
class TestPaginaDeUmProjetoPublicado(TestCase):
......@@ -28,6 +31,9 @@ class TestPaginaDeUmProjetoPublicado(TestCase):
def setUp(self):
self.projeto = Projeto.publicados.first()
def tearDown(self):
Documento.objects.all().delete() # triggers the signal to remove files
def test_pagina_de_um_projeto_retorna_200(self):
response = self.client.get(
reverse("website:projeto", args=[self.projeto.id]))
......@@ -43,12 +49,35 @@ class TestPaginaDeUmProjetoPublicado(TestCase):
reverse("website:projeto", args=[self.projeto.id]))
self.assertEqual(response.context["projeto"], self.projeto)
def test_documentos(self):
arquivo = SimpleUploadedFile(
name='documento.jpg',
content=b'',
content_type='image/jpeg'
)
documento1 = mommy.make(
Documento,
tags=[self.projeto.tags.first()],
arquivo=arquivo
)
documento2 = mommy.make(
Documento,
projetos=[self.projeto],
arquivo=arquivo
)
response = self.client.get(
reverse("website:projeto", args=[self.projeto.id]))
self.assertContains(response, documento1.arquivo.url)
self.assertContains(response, documento2.arquivo.url)
class TestPaginaDeUmProjetoNaoPublicado(TestCase):
fixtures = ["projetos", "tramitacoes", "tags"]
def setUp(self):
self.projeto = Projeto.nao_publicados.first()
self.projeto = Projeto.objects.first()
self.projeto.publicado = False
self.projeto.save()
def test_pagina_de_um_projeto_retorna_404(self):
response = self.client.get(
......