diff --git a/painel/migrations/0001_initial.py b/painel/migrations/0001_initial.py new file mode 100644 index 000000000..ee7ee4836 --- /dev/null +++ b/painel/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Cronometro', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), + ('start', models.PositiveIntegerField(verbose_name='Iniciar cronômetro')), + ('reset', models.PositiveIntegerField(verbose_name='Reiniciar cronômetro')), + ('stop', models.PositiveIntegerField(verbose_name='Parar cronômetro')), + ('data_painel', models.DateField(verbose_name='Data cronômetro')), + ('tipo', models.CharField(max_length=1, choices=[('A', 'Aparte'), ('D', 'Discurso'), ('O', 'Ordem do dia')], verbose_name='Tipo Cronômetro')), + ], + ), + migrations.CreateModel( + name='Painel', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), + ('abrir', models.PositiveIntegerField(verbose_name='Abrir painel', default=0)), + ('fechar', models.PositiveIntegerField(verbose_name='Fechar painel', default=1)), + ('data_painel', models.DateField(verbose_name='Data painel')), + ('mostrar', models.CharField(max_length=1, choices=[('C', 'Completo'), ('P', 'Parlamentares'), ('V', 'Votação'), ('M', 'Mensagem')], default='C')), + ], + ), + ] diff --git a/painel/migrations/0002_auto_20150908_1818.py b/painel/migrations/0002_auto_20150908_1818.py new file mode 100644 index 000000000..9c4f07f65 --- /dev/null +++ b/painel/migrations/0002_auto_20150908_1818.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('painel', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='painel', + name='abrir', + ), + migrations.RemoveField( + model_name='painel', + name='fechar', + ), + migrations.AddField( + model_name='painel', + name='aberto', + field=models.BooleanField(verbose_name='Abrir painel', default=False), + ), + ] diff --git a/painel/migrations/0003_cronometro_counter.py b/painel/migrations/0003_cronometro_counter.py new file mode 100644 index 000000000..a59968b8e --- /dev/null +++ b/painel/migrations/0003_cronometro_counter.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('painel', '0002_auto_20150908_1818'), + ] + + operations = [ + migrations.AddField( + model_name='cronometro', + name='counter', + field=models.PositiveIntegerField(default=0), + ), + ] diff --git a/painel/migrations/0004_auto_20150908_1858.py b/painel/migrations/0004_auto_20150908_1858.py new file mode 100644 index 000000000..388278b30 --- /dev/null +++ b/painel/migrations/0004_auto_20150908_1858.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('painel', '0003_cronometro_counter'), + ] + + operations = [ + migrations.RemoveField( + model_name='cronometro', + name='counter', + ), + migrations.RemoveField( + model_name='cronometro', + name='reset', + ), + migrations.RemoveField( + model_name='cronometro', + name='start', + ), + migrations.RemoveField( + model_name='cronometro', + name='stop', + ), + migrations.AddField( + model_name='cronometro', + name='status', + field=models.CharField(max_length=1, verbose_name='Status do cronômetro', choices=[('I', 'Start'), ('R', 'Reset'), ('S', 'Stop')], default='S'), + ), + migrations.AddField( + model_name='cronometro', + name='time', + field=models.FloatField(verbose_name='Start time', default=0), + ), + migrations.AlterField( + model_name='cronometro', + name='data_painel', + field=models.DateField(verbose_name='Data do cronômetro'), + ), + ] diff --git a/painel/migrations/0005_auto_20150909_1414.py b/painel/migrations/0005_auto_20150909_1414.py new file mode 100644 index 000000000..4e1254ec1 --- /dev/null +++ b/painel/migrations/0005_auto_20150909_1414.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('painel', '0004_auto_20150908_1858'), + ] + + operations = [ + migrations.RenameField( + model_name='cronometro', + old_name='data_painel', + new_name='data_cronometro', + ), + migrations.RemoveField( + model_name='cronometro', + name='time', + ), + ] diff --git a/painel/models.py b/painel/models.py index 0b4331b36..9f23dd2ed 100644 --- a/painel/models.py +++ b/painel/models.py @@ -1,3 +1,38 @@ -# from django.db import models +from django.db import models -# Create your models here. + +class Painel(models.Model): + PAINEL_TYPES = ( + ('C', 'Completo'), + ('P', 'Parlamentares'), + ('V', 'Votação'), + ('M', 'Mensagem'), + ) + + aberto = models.BooleanField(verbose_name='Abrir painel', default=False) + data_painel = models.DateField(verbose_name='Data painel') + mostrar = models.CharField(max_length=1, + choices=PAINEL_TYPES, default='C') + + +class Cronometro(models.Model): + CRONOMETRO_TYPES = ( + ('A', 'Aparte'), + ('D', 'Discurso'), + ('O', 'Ordem do dia') + ) + + CRONOMETRO_STATUS = ( + ('I', 'Start'), + ('R', 'Reset'), + ('S', 'Stop'), + ) + + status = models.CharField( + max_length=1, + verbose_name='Status do cronômetro', + choices=CRONOMETRO_STATUS, + default='S') + data_cronometro = models.DateField(verbose_name='Data do cronômetro') + tipo = models.CharField( + max_length=1, choices=CRONOMETRO_TYPES, verbose_name='Tipo Cronômetro') diff --git a/painel/urls.py b/painel/urls.py index 88ec93ba4..77e9b7596 100644 --- a/painel/urls.py +++ b/painel/urls.py @@ -1,12 +1,17 @@ -from django.conf.urls import url +from django.conf.urls import include, url -from .views import (json_presenca, json_votacao, painel_parlamentares_view, - painel_view, painel_votacao_view) +from .views import (controlador_painel, cronometro_painel_crud, json_presenca, + json_votacao, painel_parlamentares_view, painel_view, + painel_votacao_view) urlpatterns = [ url(r'^sistema/painel$', painel_view), + url(r'^sistema/painel/controlador', + controlador_painel, name='controlador_painel'), url(r'^sistema/painel/parlamentares', painel_parlamentares_view), url(r'^sistema/painel/votacao', painel_votacao_view), url(r'^sistema/painel/json_presenca', json_presenca, name='json_presenca'), url(r'^sistema/painel/json_votacao', json_votacao, name='json_votacao'), + url(r'^sistema/painel/cronometro', + include(cronometro_painel_crud.urls)), ] diff --git a/painel/views.py b/painel/views.py index 763d56cca..a715565f1 100644 --- a/painel/views.py +++ b/painel/views.py @@ -1,16 +1,72 @@ +from datetime import date from django.core import serializers from django.http import HttpResponse, JsonResponse from django.shortcuts import render +from django.utils.translation import ugettext_lazy as _ +from painel.models import Painel from parlamentares.models import Filiacao +from sapl.crud import build_crud from sessao.models import (OrdemDia, PresencaOrdemDia, RegistroVotacao, SessaoPlenaria, SessaoPlenariaPresenca, VotoParlamentar) +from .models import Cronometro + +cronometro_painel_crud = build_crud( + Cronometro, '', [ + + [_('Cronometro'), + [('status', 3), ('data_cronometro', 6), + ('tipo', 3)]], + ]) + +# REST WS + + +def controlador_painel(request): + + painel_created = Painel.objects.get_or_create(data_painel=date.today()) + painel = painel_created[0] + + if request.method == 'POST': + if 'start-painel' in request.POST: + painel.aberto = True + painel.save() + elif 'stop-painel' in request.POST: + painel.aberto = False + painel.save() + elif 'save-painel' in request.POST: + painel.mostrar = request.POST['tipo_painel'] + painel.save() + + context = {'painel': painel, 'PAINEL_TYPES': Painel.PAINEL_TYPES} + return render(request, 'painel/controller.html', context) + + +def cronometro_painel(request): + print(request.POST) + + return HttpResponse({}) + + +def painel_view(request): + context = {'head_title': 'Painel Plenário', + 'title': '3a. Sessao Ordinária do Município XYZ'} + return render(request, 'painel/index.html', {'context': context}) + + +def painel_parlamentares_view(request): + return render(request, 'painel/parlamentares.html') + + +def painel_votacao_view(request): + return render(request, 'painel/votacao.html') # REST web services + def json_presenca(request): presencas = PresencaOrdemDia.objects.filter(sessao_plenaria_id=50) parlamentares = [] @@ -18,7 +74,7 @@ def json_presenca(request): parlamentares.append(p.parlamentar) # parlamentares = serializers.serialize('json', Parlamentar.objects.all()) parlamentares = serializers.serialize('json', parlamentares) - return HttpResponse(parlamentares, content_type='application/json') + return HttpResponse(parlamentares, content_type='application/json') # return JsonResponse(data) # work with python dict @@ -61,7 +117,7 @@ def json_votacao(request): nome_parlamentar = p.parlamentar.nome_parlamentar presentes_ordem_dia.append( {'nome': nome_parlamentar, - 'partido': parlamentar_partido[nome_parlamentar], + 'partido': parlamentar_partido[nome_parlamentar], 'voto': votos.get(nome_parlamentar, '-')}) total_votos = votacao.numero_votos_sim + \ @@ -93,19 +149,3 @@ def json_votacao(request): "presentes_sessao_plenaria": presentes_sessao_plenaria, } return JsonResponse(votacao_json) - - -# UI views - -def painel_view(request): - context = {'head_title': 'Painel Plenário', - 'title': '3a. Sessao Ordinária do Município XYZ'} - return render(request, 'painel/index.html', {'context': context}) - - -def painel_parlamentares_view(request): - return render(request, 'painel/parlamentares.html') - - -def painel_votacao_view(request): - return render(request, 'painel/votacao.html') diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index d532fbf88..330ef2854 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -5,5 +5,6 @@ django-extensions flake8 ipdb isort +autopep8 pygraphviz==1.3rc2 pytest-ipdb==0.1-prerelease2 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 5c41a2f45..6132a663b 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -12,4 +12,5 @@ libsass psycopg2 pytz PyYAML +trml2pdf>=0.3 Unipath diff --git a/scripts/voto_parlamentar.sql b/scripts/voto_parlamentar.sql new file mode 100644 index 000000000..88b15f954 --- /dev/null +++ b/scripts/voto_parlamentar.sql @@ -0,0 +1,13 @@ + +INSERT INTO sessao_votoparlamentar (id, parlamentar_id, voto, votacao_id) values (1, 21, 'S', 83); +INSERT INTO sessao_votoparlamentar (id, parlamentar_id, voto, votacao_id) values (2, 23, 'S', 83); +INSERT INTO sessao_votoparlamentar (id, parlamentar_id, voto, votacao_id) values (3, 33, 'S', 83); +INSERT INTO sessao_votoparlamentar (id, parlamentar_id, voto, votacao_id) values (4, 112, 'S', 83); +INSERT INTO sessao_votoparlamentar (id, parlamentar_id, voto, votacao_id) values (5, 117, 'S', 83); + +INSERT INTO sessao_votoparlamentar (id, parlamentar_id, voto, votacao_id) values (9, 116, 'N', 83); +INSERT INTO sessao_votoparlamentar (id, parlamentar_id, voto, votacao_id) values (6, 120, 'N', 83); +INSERT INTO sessao_votoparlamentar (id, parlamentar_id, voto, votacao_id) values (7, 121, 'N', 83); +INSERT INTO sessao_votoparlamentar (id, parlamentar_id, voto, votacao_id) values (8, 122, 'N', 83); + +SELECT * FROM sessao_votoparlamentar; \ No newline at end of file diff --git a/sessao/urls.py b/sessao/urls.py index e0a30c41d..ce5f391c4 100644 --- a/sessao/urls.py +++ b/sessao/urls.py @@ -1,8 +1,9 @@ from django.conf.urls import include, url - -from sessao.views import (ExpedienteView, PainelView, PresencaOrdemDiaView, - PresencaView, sessao_crud, tipo_expediente_crud, - tipo_resultado_votacao_crud, tipo_sessao_crud) +from sessao.views import (ExpedienteView, OradorExpedienteDelete, + OradorExpedienteView, PainelView, + PresencaOrdemDiaView, PresencaView, sessao_crud, + tipo_expediente_crud, tipo_resultado_votacao_crud, + tipo_sessao_crud) urlpatterns_sessao = sessao_crud.urlpatterns + [ url(r'^(?P\d+)/expediente$', @@ -14,6 +15,11 @@ urlpatterns_sessao = sessao_crud.urlpatterns + [ url(r'^(?P\d+)/presencaordemdia$', PresencaOrdemDiaView.as_view(), name='presencaordemdia'), + url(r'^(?P\d+)/oradorexpediente$', + OradorExpedienteView.as_view(), name='oradorexpediente'), + url(r'^(?P\d+)/oradorexpediente/excluir/(?P\d+)$', + OradorExpedienteDelete.as_view(), name='oradorexcluir'), + ] sessao_urls = urlpatterns_sessao, sessao_crud.namespace, sessao_crud.namespace diff --git a/sessao/views.py b/sessao/views.py index 0d7c67f68..025f3687f 100644 --- a/sessao/views.py +++ b/sessao/views.py @@ -2,11 +2,13 @@ from django import forms from django.utils.translation import ugettext_lazy as _ from django.views.generic.edit import FormMixin from extra_views import InlineFormSetView + from parlamentares.models import Parlamentar from sapl.crud import build_crud -from .models import (ExpedienteMateria, ExpedienteSessao, OrdemDia, - PresencaOrdemDia, RegistroVotacao, SessaoPlenaria, - SessaoPlenariaPresenca, TipoExpediente, + +from .models import (ExpedienteMateria, ExpedienteSessao, OradorExpediente, + OrdemDia, PresencaOrdemDia, RegistroVotacao, + SessaoPlenaria, SessaoPlenariaPresenca, TipoExpediente, TipoResultadoVotacao, TipoSessaoPlenaria) tipo_sessao_crud = build_crud( @@ -211,3 +213,97 @@ class PresencaOrdemDiaView(FormMixin, sessao_crud.CrudDetailView): yield (parlamentar, False) else: yield (parlamentar, True) + + +class OradorForm(forms.Form): + numero_ordem = forms.IntegerField(required=True) + parlamentar = forms.CharField(required=True, max_length=20) + url_discurso = forms.CharField(required=False, max_length=100) + + +class OradorDeleteForm(forms.Form): + pass + + +class OradorExpedienteDelete(FormMixin, sessao_crud.CrudDetailView): + template_name = 'sessao/delete_orador.html' + form_class = OradorDeleteForm + + def post(self, request, *args, **kwargs): + self.object = self.get_object() + current_url = request.get_full_path() + words = current_url.split('/') + form = OradorDeleteForm(request.POST) + + if form.is_valid(): + orador = OradorExpediente.objects.get( + sessao_plenaria_id=self.object.id, + parlamentar_id=words[-1]) + orador.delete() + return self.form_valid(form) + else: + return self.form_invalid(form) + + def get_success_url(self): + return self.detail_url + + +class OradorExpedienteView(FormMixin, sessao_crud.CrudDetailView): + template_name = 'sessao/oradorExpediente.html' + form_class = OradorForm + + def get(self, request, *args, **kwargs): + self.object = self.get_object() + context = self.get_context_data(object=self.object) + return self.render_to_response(context) + + def get_candidatos_orador(self): + self.object = self.get_object() + lista_parlamentares = [] + lista_oradores = [] + + for parlamentar in Parlamentar.objects.all(): + if parlamentar.ativo: + lista_parlamentares.append(parlamentar) + + for orador in OradorExpediente.objects.filter( + sessao_plenaria_id=self.object.id): + parlamentar = Parlamentar.objects.get( + id=orador.parlamentar_id) + lista_oradores.append(parlamentar) + + lista = list(set(lista_parlamentares) - set(lista_oradores)) + lista.sort(key=lambda x: x.nome_parlamentar) + return lista + + def get_oradores(self): + self.object = self.get_object() + + for orador in OradorExpediente.objects.filter( + sessao_plenaria_id=self.object.id): + numero_ordem = orador.numero_ordem + url_discurso = orador.url_discurso + parlamentar = Parlamentar.objects.get( + id=orador.parlamentar_id) + yield(numero_ordem, url_discurso, parlamentar) + + def post(self, request, *args, **kwargs): + self.object = self.get_object() + print(request.POST['numero_ordem'], request.POST['parlamentar']) + form = OradorForm(request.POST) + + if form.is_valid(): + orador = OradorExpediente() + orador.sessao_plenaria_id = self.object.id + orador.numero_ordem = request.POST['numero_ordem'] + orador.parlamentar = Parlamentar.objects.get( + id=request.POST['parlamentar']) + orador.url_discurso = request.POST['url_discurso'] + orador.save() + + return self.form_valid(form) + else: + return self.form_invalid(form) + + def get_success_url(self): + return self.detail_url diff --git a/templates/painel/controlador.html b/templates/painel/controlador.html deleted file mode 100644 index 672c86320..000000000 --- a/templates/painel/controlador.html +++ /dev/null @@ -1,45 +0,0 @@ -{% load i18n %} -{% load staticfiles %} - - - - - - - - - - {% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %} - - - - - - - - STATUS SESSÃO:
- - - -
- - CRONÔMETROS:
- - - -
- - - -
- - - -
- - - diff --git a/templates/painel/controller.html b/templates/painel/controller.html new file mode 100644 index 000000000..7675303c1 --- /dev/null +++ b/templates/painel/controller.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block base_content %} + +STATUS: +{% if painel.aberto %} +ABERTO +{% else %} +FECHADO +{% endif %} +
+
+ {% csrf_token %} + Tipo de painel: {{ painel.get_mostrar_display }}
+ {% for id, value in PAINEL_TYPES %} + +
+ {% endfor %} +
+ + + +
+{% endblock %} diff --git a/templates/sessao/delete_orador.html b/templates/sessao/delete_orador.html new file mode 100644 index 000000000..f91af9767 --- /dev/null +++ b/templates/sessao/delete_orador.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block base_content %} +
{% csrf_token %} +
+ +

+ {% blocktrans %} + Tem certeza que deseja apagar o orador? + {% endblocktrans %} +

+ + + +
+
+{% endblock %} diff --git a/templates/sessao/oradorExpediente.html b/templates/sessao/oradorExpediente.html new file mode 100644 index 000000000..b206847dc --- /dev/null +++ b/templates/sessao/oradorExpediente.html @@ -0,0 +1,56 @@ +{% extends "sessao/sessaoplenaria_detail.html" %} +{% load i18n %} +{% load crispy_forms_tags %} + +{% block detail_content %} + +
+ Oradores do Expediente +
    +
  • Ordem de pronunciamento
  • +
  • Parlamentar
  • +
  • URL Discurso
  • +
  • Excluir
  • +
+ +
+ {% csrf_token %} + {% for numero_ordem, url_discurso, parlamentar in view.get_oradores %} +
    +
  • +
  • {{parlamentar.nome_parlamentar }}
  • +
  • {% if not url_discurso %}Orador sem discurso cadastrado{% else %}{{url_discurso}}{% endif %}
  • +
  • Excluir
  • +
+ {% endfor %} + +
+ +
+
+ +
+ Adicionar Orador +
+ {% csrf_token %} + +
    +
  • Ordem de pronunciamento
  • +
  • Parlamentar
  • +
  • URL Discurso
  • +
+ +
    +
  • + +
  • +
  • +
+ +
+
+{% endblock detail_content %} diff --git a/templates/sessao/painel.html b/templates/sessao/painel.html index 17e7d0b3f..bc930d732 100644 --- a/templates/sessao/painel.html +++ b/templates/sessao/painel.html @@ -99,6 +99,9 @@ $(function() { $('#discursoStart').click(function() { if($('#discursoStart').text() == 'Iniciar'){ + + $.post('/sistema/painel/cronometro', { tipo: 'Discurso', action: 'Start' } ); + $('#discursoReset').hide(); $('#discurso').runner('start'); $('#discursoStart').text('Parar'); @@ -107,6 +110,9 @@ $(function() { $('#ordemStart').prop('disabled', true); $('#ordemReset').prop('disabled', true); }else{ + + $.post('/sistema/painel/cronometro', { tipo: 'Discurso', action: 'Stop' } ); + $('#discursoReset').show(); $('#discurso').runner('stop'); $('#discursoStart').text('Iniciar'); @@ -118,6 +124,9 @@ $(function() { }); $('#discursoReset').click(function() { + + $.post('/sistema/painel/cronometro', { tipo: 'Discurso', action: 'Reset' } ); + $('#discurso').runner('stop'); $('#discurso').runner('reset'); }); @@ -134,6 +143,9 @@ $(function() { $('#aparteStart').click(function(){ if($('#aparteStart').text() == 'Iniciar'){ + + $.post('/sistema/painel/cronometro', { tipo: 'Aparte', action: 'Start' } ); + $('#aparteReset').hide(); $('#aparte').runner('start'); $('#aparteStart').text('Parar'); @@ -142,6 +154,9 @@ $(function() { $('#ordemStart').prop('disabled', true); $('#ordemReset').prop('disabled', true); }else{ + + $.post('/sistema/painel/cronometro', { tipo: 'Aparte', action: 'Stop' } ); + $('#aparteReset').show(); $('#aparte').runner('stop'); $('#aparteStart').text('Iniciar'); @@ -153,6 +168,9 @@ $(function() { }); $('#aparteReset').click(function() { + + $.post('/sistema/painel/cronometro', { tipo: 'Aparte', action: 'Reset' } ); + $('#aparte').runner('stop'); $('#aparte').runner('reset'); }); @@ -169,6 +187,9 @@ $(function() { $('#ordemStart').click(function() { if($('#ordemStart').text() == 'Iniciar'){ + + $.post('/sistema/painel/cronometro', { tipo: 'Ordem', action: 'Start' } ); + $('#ordemReset').hide(); $('#ordem').runner('start'); $('#ordemStart').text('Parar'); @@ -177,6 +198,9 @@ $(function() { $('#aparteStart').prop('disabled', true); $('#aparteReset').prop('disabled', true); }else{ + + $.post('/sistema/painel/cronometro', { tipo: 'Ordem', action: 'Stop' } ); + $('#ordemReset').show(); $('#ordem').runner('stop'); $('#ordemStart').text('Iniciar'); @@ -188,6 +212,9 @@ $(function() { }); $('#ordemReset').click(function() { + + $.post('/sistema/painel/cronometro', { tipo: 'Ordem', action: 'Reset' } ); + $('#ordem').runner('stop'); $('#ordem').runner('reset'); }); diff --git a/templates/sessao/sessaoplenaria_detail.html b/templates/sessao/sessaoplenaria_detail.html index 74d0f6bb1..70b8f5bb5 100644 --- a/templates/sessao/sessaoplenaria_detail.html +++ b/templates/sessao/sessaoplenaria_detail.html @@ -7,6 +7,7 @@
{% trans 'Expediente' %}
{% trans 'Presença' %}
{% trans 'Presença Ordem do Dia' %}
+
{% trans 'Oradores do Expediente' %}
{% trans 'Painel Eletrônico' %}
{% endblock sections_nav %}