mirror of https://github.com/interlegis/sapl.git
Browse Source
* Ref Autor, TipoAutor, cria app api DRF - Autor e TipoAutor migrados para app base. - Foram refatorados para GR - Generic Relations - Em TipoAutor: passou se a apontar também para um ContentType que é usado para contextualização de dados da GR em Autor. - A captura da combo de ContentTypes é feita através do apontamento reverso nos models que se queira disponibilizar conceitualmente como Autor - Em Autor: neste commit, o form de create está em desenvolvimento, com o buscador de possiveis autores baseados na seleção do usuário de TipoAutor que, se não possui ContentType, abre o campo nome para insersão, se possui ContentType, abre caixa de busca com atualização jquery de radiobox's para o usuário selecionar um possível autor. - api rest: para a busca funcionar e como objetivo de futuras implementações em DRF, a app api foi criada, anotada nas configurações gerais de sapl.urls com o prefixo /api. - na api foi criada a uma ListAPIView para pesquisa de possiveis autores baseados no tipo autor enviado, url /api/autor/possiveis/?P<pk>[0-9]*)$ que sem pk devolve a lista de TipoAutor e, com pk, devolve a lista dos registros ligados ao ContentType, filtrados pelo parametro q * Ajusta front para busca por possiveis autores Ajusta front e implementa SaplGenericRelation, uma customização que adiciona o atributo fields_search que possibilita passar para qualquer implementação de busca quais são os campos de busca padrão do do GenericRelation * Conc refatoração no Cada de Autor e Tipos de Autor * Alt backend de perm e pag de drf e ref layout topo * Add procedimento na alteração de username de Autor Na edição de Autores foi adicionado o tratamento por opção do usuário do que deve ser feito com o usuário que está sendo desvinculado no caso de uma alteração do username de um Autor. Foram dadas três opções: 1) Apenas retirar Perfil de Autor do Usuário que está sendo desvinculado 2) Retirar Perfil de Autor e desativar Usuário que está sendo desvinculado 3) Excluir Usuário * Add field cargo em AutorForm para tipos sem CT Cadastro de Autores de Tipos sem ContentType podem adicionar nome, cargo e usuário. * Add Bloco, Bancada, Frente possíveis Tip de Autores * Corrige frase de mensagem no cadastro de Autor * ref buscador modal de Autores c pesq param reversa * Add documentação e faz modificações na api/autor * Ref Crud para Listar GenericRelations * Ref List da aba Proposições para parlamentares * Altera imports de teste na app materia * Corrige comentários da classe AutorListView * Customiza layout do drf docs. * Altera criação do grp Autor para inc list e detail * Remove customização do bootstrap Após fork e ajustes feitos no tema drunken-parrot-ui-flat, alterações feitas nos arquivos deste commit não são mais necessárias, passando a ser renderizado os arquivos que são padrão da biblioteca django-crispy-forms. * Adequa inserção dinamica de radio ao crispy-formspull/752/head
Leandro Roberto da Silva
8 years ago
committed by
Edward
59 changed files with 2042 additions and 443 deletions
@ -0,0 +1 @@ |
|||||
|
default_app_config = 'sapl.api.apps.AppConfig' |
@ -0,0 +1,8 @@ |
|||||
|
from django import apps |
||||
|
from django.utils.translation import ugettext_lazy as _ |
||||
|
|
||||
|
|
||||
|
class AppConfig(apps.AppConfig): |
||||
|
name = 'sapl.api' |
||||
|
label = 'api' |
||||
|
verbose_name = _('API Rest') |
@ -0,0 +1,57 @@ |
|||||
|
from django.contrib.contenttypes.fields import GenericRel |
||||
|
from django.db.models import Q |
||||
|
from django_filters.filters import MethodFilter, ModelChoiceFilter |
||||
|
from rest_framework.filters import FilterSet |
||||
|
|
||||
|
from sapl.base.forms import autores_models_generic_relations |
||||
|
from sapl.base.models import Autor, TipoAutor |
||||
|
from sapl.utils import SaplGenericRelation |
||||
|
|
||||
|
|
||||
|
class AutorChoiceFilterSet(FilterSet): |
||||
|
q = MethodFilter() |
||||
|
tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all()) |
||||
|
|
||||
|
class Meta: |
||||
|
model = Autor |
||||
|
fields = ['q', |
||||
|
'tipo', |
||||
|
'nome', ] |
||||
|
|
||||
|
def filter_q(self, queryset, value): |
||||
|
|
||||
|
query = value.split(' ') |
||||
|
if query: |
||||
|
q = Q() |
||||
|
for qtext in query: |
||||
|
if not qtext: |
||||
|
continue |
||||
|
q_fs = Q(nome__icontains=qtext) |
||||
|
|
||||
|
order_by = [] |
||||
|
|
||||
|
for gr in autores_models_generic_relations(): |
||||
|
model = gr[0] |
||||
|
sgr = gr[1] |
||||
|
for item in sgr: |
||||
|
if item.related_model != Autor: |
||||
|
continue |
||||
|
flag_order_by = True |
||||
|
for field in item.fields_search: |
||||
|
if flag_order_by: |
||||
|
flag_order_by = False |
||||
|
order_by.append('%s__%s' % ( |
||||
|
item.related_query_name(), |
||||
|
field[0]) |
||||
|
) |
||||
|
q_fs = q_fs | Q(**{'%s__%s%s' % ( |
||||
|
item.related_query_name(), |
||||
|
field[0], |
||||
|
field[1]): qtext}) |
||||
|
|
||||
|
q = q & q_fs |
||||
|
|
||||
|
if q: |
||||
|
queryset = queryset.filter(q).order_by(*order_by) |
||||
|
|
||||
|
return queryset |
@ -0,0 +1,34 @@ |
|||||
|
from django.core.paginator import EmptyPage |
||||
|
from django.utils.encoding import force_text |
||||
|
from rest_framework import pagination |
||||
|
from rest_framework.response import Response |
||||
|
|
||||
|
|
||||
|
class StandardPagination(pagination.PageNumberPagination): |
||||
|
page_size = 10 |
||||
|
page_size_query_param = 'page_size' |
||||
|
max_page_size = 50 |
||||
|
|
||||
|
def get_paginated_response(self, data): |
||||
|
try: |
||||
|
previous_page_number = self.page.previous_page_number() |
||||
|
except EmptyPage: |
||||
|
previous_page_number = None |
||||
|
|
||||
|
try: |
||||
|
next_page_number = self.page.next_page_number() |
||||
|
except EmptyPage: |
||||
|
next_page_number = None |
||||
|
|
||||
|
return Response({ |
||||
|
'pagination': { |
||||
|
'previous_page': previous_page_number, |
||||
|
'next_page': next_page_number, |
||||
|
'start_index': self.page.start_index(), |
||||
|
'end_index': self.page.end_index(), |
||||
|
'total_entries': self.page.paginator.count, |
||||
|
'total_pages': self.page.paginator.num_pages, |
||||
|
'page': self.page.number, |
||||
|
}, |
||||
|
'models': data, |
||||
|
}) |
@ -0,0 +1,17 @@ |
|||||
|
from rest_framework.permissions import DjangoModelPermissions |
||||
|
|
||||
|
|
||||
|
class DjangoModelPermissions(DjangoModelPermissions): |
||||
|
|
||||
|
perms_map = { |
||||
|
'GET': ['%(app_label)s.list_%(model_name)s', |
||||
|
'%(app_label)s.detail_%(model_name)s'], |
||||
|
'OPTIONS': ['%(app_label)s.list_%(model_name)s', |
||||
|
'%(app_label)s.detail_%(model_name)s'], |
||||
|
'HEAD': ['%(app_label)s.list_%(model_name)s', |
||||
|
'%(app_label)s.detail_%(model_name)s'], |
||||
|
'POST': ['%(app_label)s.list_%(model_name)s'], |
||||
|
'PUT': ['%(app_label)s.change_%(model_name)s'], |
||||
|
'PATCH': ['%(app_label)s.change_%(model_name)s'], |
||||
|
'DELETE': ['%(app_label)s.delete_%(model_name)s'], |
||||
|
} |
@ -0,0 +1,43 @@ |
|||||
|
from django.contrib.contenttypes.fields import GenericRel |
||||
|
from rest_framework import serializers |
||||
|
|
||||
|
from sapl.base.models import Autor |
||||
|
from sapl.utils import SaplGenericRelation |
||||
|
|
||||
|
|
||||
|
class ChoiceSerializer(serializers.Serializer): |
||||
|
value = serializers.SerializerMethodField() |
||||
|
text = serializers.SerializerMethodField() |
||||
|
|
||||
|
def get_text(self, obj): |
||||
|
return obj[1] |
||||
|
|
||||
|
def get_value(self, obj): |
||||
|
return obj[0] |
||||
|
|
||||
|
|
||||
|
class AutorChoiceSerializer(ChoiceSerializer): |
||||
|
|
||||
|
def get_text(self, obj): |
||||
|
return obj.nome |
||||
|
|
||||
|
def get_value(self, obj): |
||||
|
return obj.id |
||||
|
|
||||
|
class Meta: |
||||
|
model = Autor |
||||
|
fields = ['id', 'nome'] |
||||
|
|
||||
|
|
||||
|
class AutorObjectRelatedField(serializers.RelatedField): |
||||
|
|
||||
|
def to_representation(self, value): |
||||
|
return str(value) |
||||
|
|
||||
|
|
||||
|
class AutorSerializer(serializers.ModelSerializer): |
||||
|
autor_related = AutorObjectRelatedField(read_only=True) |
||||
|
|
||||
|
class Meta: |
||||
|
model = Autor |
||||
|
fields = ['id', 'tipo', 'nome', 'object_id', 'autor_related', 'user'] |
@ -0,0 +1,30 @@ |
|||||
|
from django.conf import settings |
||||
|
from django.conf.urls import url, include |
||||
|
|
||||
|
from sapl.api.views import AutorListView |
||||
|
|
||||
|
from .apps import AppConfig |
||||
|
|
||||
|
|
||||
|
app_name = AppConfig.name |
||||
|
|
||||
|
|
||||
|
# router = DefaultRouter() |
||||
|
|
||||
|
# urlpatterns += router.urls |
||||
|
|
||||
|
|
||||
|
urlpatterns_api = [ |
||||
|
# url(r'^$', api_root), |
||||
|
url(r'^autor', |
||||
|
AutorListView.as_view(), |
||||
|
name='autor_list'), |
||||
|
] |
||||
|
|
||||
|
if settings.DEBUG: |
||||
|
urlpatterns_api += [ |
||||
|
url(r'^docs', include('rest_framework_docs.urls')), ] |
||||
|
|
||||
|
urlpatterns = [ |
||||
|
url(r'^api/', include(urlpatterns_api)) |
||||
|
] |
@ -0,0 +1,169 @@ |
|||||
|
|
||||
|
from django.db.models import Q |
||||
|
from django.http import Http404 |
||||
|
from django.utils.translation import ugettext_lazy as _ |
||||
|
from rest_framework.filters import DjangoFilterBackend |
||||
|
from rest_framework.generics import ListAPIView |
||||
|
from rest_framework.permissions import IsAuthenticated, AllowAny |
||||
|
|
||||
|
from sapl.api.forms import AutorChoiceFilterSet |
||||
|
from sapl.api.serializers import ChoiceSerializer, AutorSerializer,\ |
||||
|
AutorChoiceSerializer |
||||
|
from sapl.base.models import Autor, TipoAutor |
||||
|
from sapl.utils import SaplGenericRelation, sapl_logger |
||||
|
|
||||
|
|
||||
|
class AutorListView(ListAPIView): |
||||
|
""" |
||||
|
Listagem de Autores com filtro para autores já cadastrados |
||||
|
e/ou possíveis autores. |
||||
|
|
||||
|
- tr - tipo do resultado |
||||
|
Prepera Lista de Autores para 3 cenários distintos |
||||
|
|
||||
|
- default = 1 |
||||
|
|
||||
|
= 1 -> para (value, text) usados geralmente |
||||
|
em combobox, radiobox, checkbox, etc com pesquisa básica |
||||
|
de Autores feita pelo django-filter |
||||
|
-> processo usado nas pesquisas, o mais usado. |
||||
|
|
||||
|
= 2 -> para (value, text) usados geralmente |
||||
|
em combobox, radiobox, checkbox, etc com pesquisa básica |
||||
|
de Autores mas feito para Possíveis Autores armazenados |
||||
|
segundo o ContentType associado ao Tipo de Autor via |
||||
|
relacionamento genérico. |
||||
|
Busca feita sem django-filter processada no get_queryset |
||||
|
-> processo no cadastro de autores para seleção e busca |
||||
|
dos possíveis autores |
||||
|
|
||||
|
= 3 -> Devolve instancias da classe Autor filtradas pelo |
||||
|
django-filter |
||||
|
|
||||
|
- tipo - chave primária do Tipo de Autor a ser filtrado |
||||
|
|
||||
|
- q - busca textual no nome do Autor ou em fields_search |
||||
|
declarados no field SaplGenericRelation das GenericFks |
||||
|
A busca textual acontece via django-filter com a |
||||
|
variável `tr` igual 1 ou 3. Em caso contrário, |
||||
|
o django-filter é desativado e a busca é feita |
||||
|
no model do ContentType associado ao tipo. |
||||
|
|
||||
|
Outros campos |
||||
|
""" |
||||
|
|
||||
|
TR_AUTOR_CHOICE_SERIALIZER = 1 |
||||
|
TR_CHOICE_SERIALIZER = 2 |
||||
|
TR_AUTOR_SERIALIZER = 3 |
||||
|
|
||||
|
# FIXME aplicar permissão correta de usuário |
||||
|
permission_classes = (AllowAny,) |
||||
|
serializer_class = AutorSerializer |
||||
|
queryset = Autor.objects.all() |
||||
|
model = Autor |
||||
|
|
||||
|
filter_class = AutorChoiceFilterSet |
||||
|
filter_backends = (DjangoFilterBackend, ) |
||||
|
serializer_class = AutorChoiceSerializer |
||||
|
|
||||
|
@property |
||||
|
def tr(self): |
||||
|
try: |
||||
|
tr = int(self.request.GET.get |
||||
|
('tr', AutorListView.TR_AUTOR_CHOICE_SERIALIZER)) |
||||
|
|
||||
|
assert tr in ( |
||||
|
AutorListView.TR_AUTOR_CHOICE_SERIALIZER, |
||||
|
AutorListView.TR_CHOICE_SERIALIZER, |
||||
|
AutorListView.TR_AUTOR_SERIALIZER), sapl_logger.info( |
||||
|
_("Tipo do Resultado a ser fornecido não existe!")) |
||||
|
except: |
||||
|
return AutorListView.TR_AUTOR_CHOICE_SERIALIZER |
||||
|
else: |
||||
|
return tr |
||||
|
|
||||
|
def get(self, request, *args, **kwargs): |
||||
|
""" |
||||
|
desativa o django-filter se a busca for por possiveis autores |
||||
|
parametro tr = TR_CHOICE_SERIALIZER |
||||
|
""" |
||||
|
|
||||
|
if self.tr == AutorListView.TR_CHOICE_SERIALIZER: |
||||
|
self.filter_class = None |
||||
|
self.filter_backends = [] |
||||
|
self.serializer_class = ChoiceSerializer |
||||
|
|
||||
|
elif self.tr == AutorListView.TR_AUTOR_SERIALIZER: |
||||
|
self.serializer_class = AutorSerializer |
||||
|
self.permission_classes = (IsAuthenticated,) |
||||
|
|
||||
|
return ListAPIView.get(self, request, *args, **kwargs) |
||||
|
|
||||
|
def get_queryset(self): |
||||
|
queryset = ListAPIView.get_queryset(self) |
||||
|
|
||||
|
if self.filter_backends: |
||||
|
return queryset |
||||
|
|
||||
|
params = {'content_type__isnull': False} |
||||
|
|
||||
|
tipo = '' |
||||
|
try: |
||||
|
tipo = int(self.request.GET.get('tipo', '')) |
||||
|
if tipo: |
||||
|
params['id'] = tipo |
||||
|
except: |
||||
|
pass |
||||
|
|
||||
|
tipos = TipoAutor.objects.filter(**params) |
||||
|
|
||||
|
if not tipos.exists() and tipo: |
||||
|
raise Http404() |
||||
|
|
||||
|
r = [] |
||||
|
for tipo in tipos: |
||||
|
q = self.request.GET.get('q', '').strip() |
||||
|
|
||||
|
model_class = tipo.content_type.model_class() |
||||
|
|
||||
|
fields = list(filter( |
||||
|
lambda field: isinstance(field, SaplGenericRelation) and |
||||
|
field.related_model == Autor, |
||||
|
model_class._meta.get_fields(include_hidden=True))) |
||||
|
|
||||
|
""" |
||||
|
fields - é um array de SaplGenericRelation que deve possuir o |
||||
|
atributo fields_search. Verifique na documentação da classe |
||||
|
a estrutura de fields_search. |
||||
|
""" |
||||
|
|
||||
|
assert len(fields) >= 1, (_( |
||||
|
'Não foi encontrado em %(model)s um atributo do tipo ' |
||||
|
'SaplGenericRelation que use o model %(model_autor)s') % { |
||||
|
'model': model_class._meta.verbose_name, |
||||
|
'model_autor': Autor._meta.verbose_name}) |
||||
|
|
||||
|
qs = model_class.objects.all() |
||||
|
|
||||
|
q_filter = Q() |
||||
|
if q: |
||||
|
for item in fields: |
||||
|
if item.related_model != Autor: |
||||
|
continue |
||||
|
q_fs = Q() |
||||
|
for field in item.fields_search: |
||||
|
q_fs = q_fs | Q(**{'%s%s' % ( |
||||
|
field[0], |
||||
|
field[1]): q}) |
||||
|
q_filter = q_filter & q_fs |
||||
|
|
||||
|
qs = qs.filter(q_filter).distinct( |
||||
|
fields[0].fields_search[0][0]) |
||||
|
|
||||
|
qs = qs.order_by(fields[0].fields_search[0][0]).values_list( |
||||
|
'id', fields[0].fields_search[0][0]) |
||||
|
r += list(qs) |
||||
|
|
||||
|
if tipos.count() > 1: |
||||
|
r.sort(key=lambda x: x[1].upper()) |
||||
|
return r |
@ -0,0 +1,55 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Generated by Django 1.9.7 on 2016-10-09 15:22 |
||||
|
from __future__ import unicode_literals |
||||
|
|
||||
|
from django.conf import settings |
||||
|
from django.db import migrations, models |
||||
|
import django.db.models.deletion |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
||||
|
('contenttypes', '0002_remove_content_type_name'), |
||||
|
('base', '0021_auto_20161006_1019'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.CreateModel( |
||||
|
name='Autor', |
||||
|
fields=[ |
||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
|
('object_id', models.PositiveIntegerField(blank=True, default=None, null=True)), |
||||
|
('nome', models.CharField(blank=True, max_length=50, verbose_name='Autor')), |
||||
|
('cargo', models.CharField(blank=True, max_length=50)), |
||||
|
('content_type', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Autor', |
||||
|
'verbose_name_plural': 'Autores', |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='TipoAutor', |
||||
|
fields=[ |
||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
|
('descricao', models.CharField(max_length=50, verbose_name='Descrição')), |
||||
|
('content_type', models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType', verbose_name='Modelo do Tipo de Autor')), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Tipo de Autor', |
||||
|
'verbose_name_plural': 'Tipos de Autor', |
||||
|
}, |
||||
|
), |
||||
|
migrations.AddField( |
||||
|
model_name='autor', |
||||
|
name='tipo', |
||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='base.TipoAutor', verbose_name='Tipo'), |
||||
|
), |
||||
|
migrations.AddField( |
||||
|
model_name='autor', |
||||
|
name='user', |
||||
|
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), |
||||
|
), |
||||
|
] |
@ -0,0 +1,25 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Generated by Django 1.9.7 on 2016-10-09 21:52 |
||||
|
from __future__ import unicode_literals |
||||
|
|
||||
|
from django.db import migrations, models |
||||
|
import django.db.models.deletion |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('base', '0022_auto_20161009_1222'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.AlterField( |
||||
|
model_name='tipoautor', |
||||
|
name='content_type', |
||||
|
field=models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType', verbose_name='Modelagem no SAPL'), |
||||
|
), |
||||
|
migrations.AlterUniqueTogether( |
||||
|
name='autor', |
||||
|
unique_together=set([('content_type', 'object_id')]), |
||||
|
), |
||||
|
] |
@ -0,0 +1,26 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Generated by Django 1.9.7 on 2016-10-10 13:02 |
||||
|
from __future__ import unicode_literals |
||||
|
|
||||
|
from django.db import migrations, models |
||||
|
import django.db.models.deletion |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('base', '0023_auto_20161009_1852'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.AlterField( |
||||
|
model_name='autor', |
||||
|
name='nome', |
||||
|
field=models.CharField(blank=True, max_length=50, verbose_name='Nome do Autor'), |
||||
|
), |
||||
|
migrations.AlterField( |
||||
|
model_name='autor', |
||||
|
name='tipo', |
||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='base.TipoAutor', verbose_name='Tipo do Autor'), |
||||
|
), |
||||
|
] |
@ -0,0 +1,20 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Generated by Django 1.9.7 on 2016-10-11 14:38 |
||||
|
from __future__ import unicode_literals |
||||
|
|
||||
|
from django.db import migrations, models |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('base', '0024_auto_20161010_1002'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.AddField( |
||||
|
model_name='tipoautor', |
||||
|
name='cria_usuario', |
||||
|
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, help_text='Criação de Usuários víncula e libera o acesso de Autores ao sistema. Vincular um Autor a um tipo que esta opção está marcada como "Não", o Autor não terá username associado.', verbose_name='Criação de Usuários'), |
||||
|
), |
||||
|
] |
@ -0,0 +1,19 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Generated by Django 1.9.7 on 2016-10-11 18:08 |
||||
|
from __future__ import unicode_literals |
||||
|
|
||||
|
from django.db import migrations |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('base', '0025_tipoautor_cria_usuario'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.RemoveField( |
||||
|
model_name='tipoautor', |
||||
|
name='cria_usuario', |
||||
|
), |
||||
|
] |
@ -0,0 +1,22 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Generated by Django 1.9.7 on 2016-10-11 19:24 |
||||
|
from __future__ import unicode_literals |
||||
|
|
||||
|
from django.conf import settings |
||||
|
from django.db import migrations, models |
||||
|
import django.db.models.deletion |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('base', '0026_remove_tipoautor_cria_usuario'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.AlterField( |
||||
|
model_name='autor', |
||||
|
name='user', |
||||
|
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), |
||||
|
), |
||||
|
] |
@ -0,0 +1,53 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Generated by Django 1.9.7 on 2016-10-09 15:22 |
||||
|
from __future__ import unicode_literals |
||||
|
|
||||
|
from django.db import migrations, models |
||||
|
import django.db.models.deletion |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('protocoloadm', '0003_auto_20161009_1222'), |
||||
|
('materia', '0053_auto_20161004_1854'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.RemoveField( |
||||
|
model_name='autor', |
||||
|
name='comissao', |
||||
|
), |
||||
|
migrations.RemoveField( |
||||
|
model_name='autor', |
||||
|
name='parlamentar', |
||||
|
), |
||||
|
migrations.RemoveField( |
||||
|
model_name='autor', |
||||
|
name='partido', |
||||
|
), |
||||
|
migrations.RemoveField( |
||||
|
model_name='autor', |
||||
|
name='tipo', |
||||
|
), |
||||
|
migrations.RemoveField( |
||||
|
model_name='autor', |
||||
|
name='user', |
||||
|
), |
||||
|
migrations.AlterField( |
||||
|
model_name='autoria', |
||||
|
name='autor', |
||||
|
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='base.Autor', verbose_name='Autor'), |
||||
|
), |
||||
|
migrations.AlterField( |
||||
|
model_name='proposicao', |
||||
|
name='autor', |
||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='base.Autor'), |
||||
|
), |
||||
|
migrations.DeleteModel( |
||||
|
name='Autor', |
||||
|
), |
||||
|
migrations.DeleteModel( |
||||
|
name='TipoAutor', |
||||
|
), |
||||
|
] |
@ -0,0 +1,32 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Generated by Django 1.9.7 on 2016-10-09 17:18 |
||||
|
from __future__ import unicode_literals |
||||
|
|
||||
|
from django.db import migrations, models |
||||
|
import django.db.models.deletion |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('base', '0022_auto_20161009_1222'), |
||||
|
('materia', '0054_auto_20161009_1222'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.AddField( |
||||
|
model_name='materialegislativa', |
||||
|
name='autores', |
||||
|
field=models.ManyToManyField(through='materia.Autoria', to='base.Autor'), |
||||
|
), |
||||
|
migrations.AlterField( |
||||
|
model_name='autoria', |
||||
|
name='autor', |
||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='base.Autor', verbose_name='Autor'), |
||||
|
), |
||||
|
migrations.AlterField( |
||||
|
model_name='autoria', |
||||
|
name='materia', |
||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='materia.MateriaLegislativa', verbose_name='Matéria Legislativa'), |
||||
|
), |
||||
|
] |
@ -0,0 +1,16 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Generated by Django 1.9.7 on 2016-10-11 19:45 |
||||
|
from __future__ import unicode_literals |
||||
|
|
||||
|
from django.db import migrations |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('materia', '0055_auto_20161009_1418'), |
||||
|
('materia', '0054_auto_20161011_0904'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
] |
@ -0,0 +1,26 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Generated by Django 1.9.7 on 2016-10-09 15:22 |
||||
|
from __future__ import unicode_literals |
||||
|
|
||||
|
from django.db import migrations, models |
||||
|
import django.db.models.deletion |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('protocoloadm', '0002_delete_tipoinstituicao'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.AlterField( |
||||
|
model_name='documentoadministrativo', |
||||
|
name='autor', |
||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='base.Autor'), |
||||
|
), |
||||
|
migrations.AlterField( |
||||
|
model_name='protocolo', |
||||
|
name='autor', |
||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='base.Autor'), |
||||
|
), |
||||
|
] |
@ -0,0 +1,146 @@ |
|||||
|
{% extends "crud/form.html" %} |
||||
|
{% load i18n %} |
||||
|
{% block extra_js %} |
||||
|
|
||||
|
<script type="text/javascript"> |
||||
|
|
||||
|
$(document).ready(function(){ |
||||
|
var flag_create = false; |
||||
|
if (location.href.indexOf('create') > 0) { |
||||
|
$('.radiogroup-status').remove(); |
||||
|
flag_create = true |
||||
|
} |
||||
|
var active = function(str, atualizar=true) { |
||||
|
if (str == 'nome') { |
||||
|
if (atualizar) |
||||
|
$('#id_nome, #id_q, #id_cargo').val(''); |
||||
|
$('.div_nome_cargo').removeClass('hidden'); |
||||
|
$("#div_id_autor_related .controls").html(''); |
||||
|
$("[data-application='AutorSearch'], .radiogroup-autor-related").addClass('hidden'); |
||||
|
} |
||||
|
else { |
||||
|
$('#id_nome, #id_cargo').val(''); |
||||
|
$('.div_nome_cargo').addClass('hidden'); |
||||
|
$("#div_id_autor_related .alert").remove(); |
||||
|
$("[data-application='AutorSearch'], .radiogroup-autor-related").removeClass('hidden'); |
||||
|
} |
||||
|
} |
||||
|
var update_search = function(pk, atualizar=true) { |
||||
|
var q = $('#id_q').val(); |
||||
|
|
||||
|
var url = '{% url 'sapl.api:autor_list'%}' |
||||
|
|
||||
|
var formData = { |
||||
|
'q' : q, |
||||
|
'tipo' : pk, |
||||
|
'tr' : '2' // tipo_resultado = 2 - api fornecerá possíveis Autores |
||||
|
} |
||||
|
$.get(url, formData).done(function(data) { |
||||
|
active('pesquisa'); |
||||
|
if (atualizar) { |
||||
|
var radios = $("#div_id_autor_related .controls").html(''); |
||||
|
data.models.forEach(function (val, index) { |
||||
|
var html_radio = '<div class="radio"><label><span class="icons"><span class="first-icon"></span><span class="second-icon"></span></span><input type="radio" name="autor_related" id="id_autor_related_'+index+'" value="'+val.value+'" style="display:none;">'+val.text+'</label></div>'; |
||||
|
radios.append(html_radio); |
||||
|
}); |
||||
|
|
||||
|
if (data.models.length > 1) { |
||||
|
$('input[name=autor_related]').change(function(event){ |
||||
|
if (this.checked) |
||||
|
$('#id_q').val(event.target.parentElement.textContent); |
||||
|
//$('input[name=autor_related]:not(:checked)').closest('.radio').remove(); |
||||
|
}); |
||||
|
} |
||||
|
else { |
||||
|
$('input[name=autor_related]').prop('checked', 'checked'); |
||||
|
$('input[name=autor_related]').closest('.radio').addClass('checked'); |
||||
|
} |
||||
|
|
||||
|
if (data.pagination.total_entries > 10) |
||||
|
radios.before('<div class="alert alert-info" role="alert"><strong>{% trans "Foram encontrados" %} '+data.pagination.total_entries+' {% trans "registros"%}</strong>'+' {% trans "mas será mostrado apenas os 10 primeiros resultados. Coloque mais informações no campo pesquisa para refinar sua busca."%}</div>'); |
||||
|
else if (data.pagination.total_entries == 0) |
||||
|
radios.before('<div class="alert alert-info" role="alert"><strong>{% trans "Não foram encontrados registros com os termos de pesquisa informados." %}</div>'); |
||||
|
} |
||||
|
else{ |
||||
|
$('#id_nome, #id_q').val(''); |
||||
|
} |
||||
|
}).fail(function(data) { |
||||
|
active('nome', atualizar); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
$('#id_tipo').change(function(event) { |
||||
|
if (event.target.selectedIndex == 0) { |
||||
|
$('#id_nome, #id_q').val(''); |
||||
|
active('nome'); |
||||
|
} |
||||
|
else { |
||||
|
var pk = this[event.target.selectedIndex].value; |
||||
|
$('input[name=autor_related]').closest('.radio').remove(); |
||||
|
update_search(pk, false) |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
$('.btn-filtrar-autor').click(function(event) { |
||||
|
var pk = $('#id_tipo').val(); |
||||
|
update_search(pk); |
||||
|
}); |
||||
|
|
||||
|
$('input[name=action_user]').change(function(event) { |
||||
|
if (!this.checked) |
||||
|
return; |
||||
|
|
||||
|
$('#div_id_username input').prop('readonly', ''); |
||||
|
|
||||
|
if (event.target.value == 'C') { |
||||
|
$('.new_user_fields, #div_id_username').removeClass('hidden'); |
||||
|
$('input[name=username]').val(''); |
||||
|
} |
||||
|
else if (event.target.value == 'N') { |
||||
|
$('.new_user_fields').addClass('hidden'); |
||||
|
if ($('input[name=username]').attr('data') != '') |
||||
|
$('.radiogroup-status').removeClass('hidden'); |
||||
|
|
||||
|
if (flag_create) { |
||||
|
$('#div_id_username').addClass('hidden'); |
||||
|
} |
||||
|
else { |
||||
|
$('#div_id_username input').prop('readonly', 'readonly'); |
||||
|
} |
||||
|
} |
||||
|
else { |
||||
|
$('.radiogroup-status').addClass('hidden'); |
||||
|
$('#div_id_username').removeClass('hidden'); |
||||
|
$('.new_user_fields').addClass('hidden'); |
||||
|
} |
||||
|
if (!flag_create) { |
||||
|
var username = $('input[name=username]'); |
||||
|
if (username.length == 1) { |
||||
|
if ((event.target.value == 'A' && username.attr('data') != '' && username[0].value != username.attr('data')) |
||||
|
|| (event.target.value == 'C' && username.attr('data') != '')) |
||||
|
$('.radiogroup-status').removeClass('hidden'); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
$('input[name=username]').keyup(function(event) { |
||||
|
if (!flag_create) |
||||
|
if (this.getAttribute('data') != '' && this.value != this.getAttribute('data')) |
||||
|
$('.radiogroup-status').removeClass('hidden'); |
||||
|
else |
||||
|
$('.radiogroup-status').addClass('hidden'); |
||||
|
}); |
||||
|
|
||||
|
$('input[name=action_user]:checked').trigger('change'); |
||||
|
$('input[name=autor_related]').closest('.radio').remove(); |
||||
|
|
||||
|
var pk = $('#id_tipo').val(); |
||||
|
if (pk) |
||||
|
update_search(pk, $('#id_q').val().length > 0) |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
{% endblock %} |
@ -0,0 +1,20 @@ |
|||||
|
{% extends "crud/form.html" %} |
||||
|
{% load i18n %} |
||||
|
{% block extra_js %} |
||||
|
|
||||
|
<script type="text/javascript"> |
||||
|
|
||||
|
$(document).ready(function(){ |
||||
|
|
||||
|
$('#id_content_type').change(function(event) { |
||||
|
if (event.target.selectedIndex == 0) |
||||
|
$('#id_descricao').val(''); |
||||
|
else |
||||
|
$('#id_descricao').val(this[event.target.selectedIndex].text); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
{% endblock %} |
@ -0,0 +1,81 @@ |
|||||
|
{% load static from staticfiles %} |
||||
|
|
||||
|
<!DOCTYPE html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<meta charset="utf-8"> |
||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
|
||||
|
<title>{% block title %}DRF Docs{% endblock %}</title> |
||||
|
|
||||
|
{% block style %} |
||||
|
<link rel="stylesheet" href="{% static "rest_framework_docs/css/style.css" %}"> |
||||
|
{% endblock %} |
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
{% block github_badge %} |
||||
|
<a href="https://github.com/ekonstantinidis/drf-docs/" class="github-corner" target="_blank"> |
||||
|
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:#18bc9c; color:#fff; position: absolute; top: 0; border: 0; right: 0;"> |
||||
|
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path> |
||||
|
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path> |
||||
|
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path> |
||||
|
</svg> |
||||
|
</a> |
||||
|
{% endblock %} |
||||
|
|
||||
|
<div class="container"> |
||||
|
|
||||
|
<nav class="navbar navbar-default"> |
||||
|
<div class="container-fluid"> |
||||
|
<!-- Brand and toggle get grouped for better mobile display --> |
||||
|
<div class="navbar-header"> |
||||
|
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#drfdoc-navbar" aria-expanded="false"> |
||||
|
<span class="sr-only">Toggle navigation</span> |
||||
|
<span class="icon-bar"></span> |
||||
|
<span class="icon-bar"></span> |
||||
|
<span class="icon-bar"></span> |
||||
|
</button> |
||||
|
{% block logo %}<a class="navbar-brand" href="http://www.drfdocs.com/">DRF Docs</a>{% endblock %} |
||||
|
</div> |
||||
|
|
||||
|
<!-- Collect the nav links, forms, and other content for toggling --> |
||||
|
<div class="collapse navbar-collapse" id="drfdoc-navbar"> |
||||
|
<form method="get" action="." class="navbar-form navbar-right" role="search"> |
||||
|
<div class="form-group"> |
||||
|
<input type="text" class="form-control" name="search" value="{{ query }}" placeholder="Search"> |
||||
|
</div> |
||||
|
</form> |
||||
|
<ul class="nav navbar-nav navbar-right"> |
||||
|
{% block apps_menu %}{% endblock %} |
||||
|
</ul> |
||||
|
</div><!-- /.navbar-collapse --> |
||||
|
</div><!-- /.container-fluid --> |
||||
|
</nav> |
||||
|
|
||||
|
{% block jumbotron %} |
||||
|
<div class="jumbotron"> |
||||
|
<h1>DRF Docs</h1> |
||||
|
<h3>Document Web APIs made with <a href="http://www.django-rest-framework.org/" target="_blank">Django REST Framework</a>.</h3> |
||||
|
</div> |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block content %}{% endblock %} |
||||
|
|
||||
|
{% block footer %} |
||||
|
<div class="footer"> |
||||
|
<div class="links"> |
||||
|
<a href="http://www.iamemmanouil.com"><i class="fa fa-link"></i></a> |
||||
|
<a href="http://www.github.com/ekonstantinidis"><i class="fa fa-github"></i></a> |
||||
|
<a href="http://www.twitter.com/iamemmanouil"><i class="fa fa-twitter"></i></a> |
||||
|
</div> |
||||
|
Copyright © 2016 Emmanouil Konstantinidis. |
||||
|
</div> |
||||
|
{% endblock %} |
||||
|
</div> |
||||
|
|
||||
|
<!-- Dist.js - Inlcuded Live API, jQuery, Bootstrap --> |
||||
|
<script type="text/javascript" src="{% static "rest_framework_docs/js/dist.min.js" %}"></script> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,123 @@ |
|||||
|
{% extends "rest_framework_docs/docs.html" %} |
||||
|
|
||||
|
{% block style %}{{block.super}} |
||||
|
<style media="screen"> |
||||
|
.lead { |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
</style> |
||||
|
{% endblock %} |
||||
|
{% block logo %} |
||||
|
<a class="navbar-brand" href="http://sapl31demo.interlegis.leg.br">DRF Docs - SAPL - Sistema de Apoio ao Processo Legislativo</a> |
||||
|
{% endblock %} |
||||
|
{% block title %}SAPL - Sistema de Apoio ao Processo Legislativo{% endblock %} |
||||
|
{% block jumbotron %} |
||||
|
<div class="jumbotron"> |
||||
|
<h1>SAPL </h1> |
||||
|
<h3>Sistema de Apoio ao Processo Legislativo.</h3> |
||||
|
</div> |
||||
|
{% endblock %} |
||||
|
|
||||
|
|
||||
|
{% block apps_menu %} |
||||
|
{% regroup endpoints by name_parent as endpoints_grouped %} |
||||
|
<li class="dropdown"> |
||||
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Jump To <span class="caret"></span></a> |
||||
|
<ul class="dropdown-menu"> |
||||
|
{% for group in endpoints_grouped %} |
||||
|
<li><a href="#{{ group.grouper|lower }}-group">{{ group.grouper }}</a></li> |
||||
|
{% endfor %} |
||||
|
</ul> |
||||
|
</li> |
||||
|
{% endblock %} |
||||
|
|
||||
|
|
||||
|
{% block content %} |
||||
|
|
||||
|
{% regroup endpoints by name_parent as endpoints_grouped %} |
||||
|
|
||||
|
{% if endpoints_grouped %} |
||||
|
{% for group in endpoints_grouped %} |
||||
|
|
||||
|
<h1 id="{{ group.grouper|lower }}-group">{{group.grouper}}</h1> |
||||
|
|
||||
|
<div class="panel-group" role="tablist"> |
||||
|
|
||||
|
{% for endpoint in group.list %} |
||||
|
|
||||
|
<div class="panel panel-default endpoint"> |
||||
|
|
||||
|
<div class="panel-heading" role="tab" data-toggle="collapse" data-target="#{{ endpoint.path|slugify }}"> |
||||
|
<div class="row"> |
||||
|
<div class="col-md-7"> |
||||
|
<h4 class="panel-title title"> |
||||
|
<i class="fa fa-link"></i> {{ endpoint.path }} |
||||
|
</h4> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-md-5"> |
||||
|
<ul class="list-inline methods"> |
||||
|
{% for method in endpoint.allowed_methods %} |
||||
|
<li class="method {{ method|lower }}">{{ method }}</li> |
||||
|
{% endfor %} |
||||
|
<li class="method plug" |
||||
|
data-toggle="modal" |
||||
|
data-path="{{ endpoint.path }}" |
||||
|
data-methods="{{ endpoint.allowed_methods }}" |
||||
|
data-permissions="{{ endpoint.permissions }}" |
||||
|
data-fields="{{ endpoint.fields_json }}"> |
||||
|
<i class="fa fa-plug"></i></li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="{{ endpoint.path|slugify }}" class="panel-collapse collapse" role="tabpanel"> |
||||
|
<div class="panel-body"> |
||||
|
{% if endpoint.docstring %} |
||||
|
<pre class="lead">{{ endpoint.docstring}}</pre> |
||||
|
{% endif %} |
||||
|
|
||||
|
{% if endpoint.errors %} |
||||
|
<div class="alert alert-danger" role="alert">Oops! There was something wrong with {{ endpoint.errors }}. Please check your code.</div> |
||||
|
{% endif %} |
||||
|
|
||||
|
{% if endpoint.fields %} |
||||
|
<p class="fields-desc">Fields:</p> |
||||
|
<ul class="list fields"> |
||||
|
{% for field in endpoint.fields %} |
||||
|
<li class="field">{{ field.name }}: {{ field.type }} {% if field.required %}<span class="label label-primary label-required" title="Required">R</span>{% endif %}</li> |
||||
|
{% endfor %} |
||||
|
</ul> |
||||
|
{% elif not endpoint.errors %} |
||||
|
<p>No fields.</p> |
||||
|
{% endif %} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
{% endfor %} |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
{% endfor %} |
||||
|
{% elif not query %} |
||||
|
<h2 class="text-center">There are currently no api endpoints to document.</h2> |
||||
|
{% else %} |
||||
|
<h2 class="text-center">No endpoints found for {{ query }}.</h2> |
||||
|
{% endif %} |
||||
|
|
||||
|
<!-- Modal --> |
||||
|
<div class="modal fade api-modal" id="liveAPIModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> |
||||
|
<div class="modal-dialog modal-lg" role="document"> |
||||
|
<div class="modal-content"> |
||||
|
<div class="modal-header"> |
||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> |
||||
|
<h4 class="modal-title">Live API Endpoints <span class="label label-info">Beta</span></h4> |
||||
|
</div> |
||||
|
|
||||
|
<div id="liveAPIEndpoints"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
{% endblock %} |
Loading…
Reference in new issue