Browse Source

Cria app Espacos para reserva e alocação de espaços gerenciados pelo ILB

pull/169/head 3.0.59
Sesóstris Vieira 1 year ago
parent
commit
a456fa8eef
  1. 0
      sigi/apps/espacos/__init__.py
  2. 158
      sigi/apps/espacos/admin.py
  3. 6
      sigi/apps/espacos/admin_urls.py
  4. 9
      sigi/apps/espacos/apps.py
  5. 213
      sigi/apps/espacos/migrations/0001_initial.py
  6. 52
      sigi/apps/espacos/migrations/0002_converte_eventos.py
  7. 0
      sigi/apps/espacos/migrations/__init__.py
  8. 150
      sigi/apps/espacos/models.py
  9. 15
      sigi/apps/espacos/templates/admin/espacos/reserva/change_form.html
  10. 44
      sigi/apps/espacos/templates/espacos/agenda.html
  11. 108
      sigi/apps/espacos/templates/espacos/agenda_pdf.html
  12. 55
      sigi/apps/espacos/templates/espacos/snippets/agenda_cal.html
  13. 3
      sigi/apps/espacos/tests.py
  14. 126
      sigi/apps/espacos/views.py
  15. 8
      sigi/apps/utils/templatetags/action_icons.py
  16. 38
      sigi/apps/utils/templatetags/sigi_tags.py
  17. 12
      sigi/menu_conf.yaml
  18. 3
      sigi/settings.py
  19. 1
      sigi/urls.py

0
sigi/apps/espacos/__init__.py

158
sigi/apps/espacos/admin.py

@ -0,0 +1,158 @@
from django.contrib import admin, messages
from django.urls import path, reverse
from django.utils.translation import gettext as _, ngettext
from django.shortcuts import get_object_or_404, redirect
from import_export.fields import Field
from sigi.apps.espacos.models import (
Espaco,
Recurso,
Reserva,
RecursoSolicitado,
)
from sigi.apps.utils.mixins import CartExportMixin, LabeledResourse
class ReservaResource(LabeledResourse):
recursos_solicitados = Field(column_name="recursos solicitados")
class Meta:
model = Reserva
fields = (
"status",
"espaco__sigla",
"espaco__nome",
"proposito",
"inicio",
"termino",
"informacoes",
"solicitante",
"contato",
"telefone_contato",
)
export_order = fields
def dehydrate_status(self, obj):
return obj.get_status_display()
def dehydrate_recursos_solicitados(self, obj):
return ", ".join(
[
_(f"{r.quantidade} {r.recurso.nome}")
for r in obj.recursosolicitado_set.all()
]
)
class RecursoSolicitadoInline(admin.TabularInline):
model = RecursoSolicitado
autocomplete_fields = [
"recurso",
]
@admin.register(Espaco)
class EspacoAdmin(admin.ModelAdmin):
list_display = ["sigla", "nome"]
search_fields = ["sigla", "nome"]
@admin.register(Recurso)
class RecursoAdmin(admin.ModelAdmin):
list_display = ["sigla", "nome"]
search_fields = ["sigla", "nome"]
@admin.register(Reserva)
class ReservaAdmin(CartExportMixin, admin.ModelAdmin):
resource_classes = [ReservaResource]
list_display = [
"get_status",
"proposito",
"get_espaco",
"inicio",
"termino",
"solicitante",
"contato",
"telefone_contato",
]
list_display_links = ["get_status", "proposito"]
list_filter = ["status", "espaco"]
search_fields = ["proposito", "espaco__nome", "espaco__sigla"]
date_hierarchy = "inicio"
actions = ["cancelar_action", "reativar_action"]
fieldsets = [
(None, {"fields": ("status",)}),
(
_("Solicitação"),
{
"fields": (
"espaco",
"proposito",
"inicio",
"termino",
"informacoes",
)
},
),
(
_("Contato"),
{"fields": ("solicitante", "contato", "telefone_contato")},
),
]
autocomplete_fields = ["espaco"]
readonly_fields = ("status",)
inlines = [RecursoSolicitadoInline]
def get_urls(self):
urls = super().get_urls()
model_info = self.get_model_info()
my_urls = [
path(
"<path:object_id>/cancel/",
self.admin_site.admin_view(self.cancelar_reserva),
name="%s_%s_cancel" % model_info,
)
]
return my_urls + urls
@admin.display(description=_("Status"), ordering="status", boolean=True)
def get_status(self, obj):
return obj.status == Reserva.STATUS_ATIVO
@admin.display(description=_("Espaço"), ordering="espaco")
def get_espaco(self, obj):
return obj.espaco.sigla
def cancelar_reserva(self, request, object_id):
reserva = get_object_or_404(Reserva, id=object_id)
reserva.status = Reserva.STATUS_CANCELADO
reserva.save()
return redirect(
reverse(
"admin:%s_%s_change" % self.get_model_info(), args=[object_id]
)
+ "?"
+ self.get_preserved_filters(request)
)
@admin.action(description=_("Cancelar as reservas selecionadas"))
def cancelar_action(self, request, queryset):
count = queryset.update(status=Reserva.STATUS_CANCELADO)
self.message_user(
request,
ngettext(
"Uma reserva cancelada", f"{count} reservas canceladas", count
),
messages.SUCCESS,
)
@admin.action(description=_("Reativar as reservas selecionadas"))
def reativar_action(self, request, queryset):
count = queryset.update(status=Reserva.STATUS_ATIVO)
self.message_user(
request,
ngettext(
"Uma reserva reativada", f"{count} reservas reativadas", count
),
messages.SUCCESS,
)

6
sigi/apps/espacos/admin_urls.py

@ -0,0 +1,6 @@
from django.urls import path, include
from sigi.apps.espacos import views
urlpatterns = [
path("agenda/", views.Agenda.as_view(), name="espacos_agenda"),
]

9
sigi/apps/espacos/apps.py

@ -0,0 +1,9 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class EspacosConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "espacos"
name = "sigi.apps.espacos"
verbose_name = _("Agenda de espaços")

213
sigi/apps/espacos/migrations/0001_initial.py

@ -0,0 +1,213 @@
# Generated by Django 4.2.4 on 2023-11-08 14:57
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Espaco",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"nome",
models.CharField(max_length=100, verbose_name="nome"),
),
(
"sigla",
models.CharField(max_length=20, verbose_name="sigla"),
),
(
"descricao",
models.TextField(blank=True, verbose_name="descrição"),
),
(
"local",
models.CharField(
help_text="Indique o prédio/bloco/sala onde este espaço está localizado.",
max_length=100,
verbose_name="local",
),
),
],
options={
"verbose_name": "espaço",
"verbose_name_plural": "espaços",
"ordering": ("nome",),
},
),
migrations.CreateModel(
name="Recurso",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"nome",
models.CharField(max_length=100, verbose_name="nome"),
),
(
"sigla",
models.CharField(max_length=20, verbose_name="sigla"),
),
(
"descricao",
models.TextField(blank=True, verbose_name="descrição"),
),
],
options={
"verbose_name": "recurso",
"verbose_name_plural": "recursos",
"ordering": ("nome",),
},
),
migrations.CreateModel(
name="Reserva",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"status",
models.CharField(
choices=[("A", "Ativo"), ("C", "Cancelado")],
default="A",
editable=False,
max_length=1,
verbose_name="Status",
),
),
(
"proposito",
models.CharField(
help_text="Indique o propósito da reserva (nome do evento, indicativo da reunião, aula, apresentação, etc.)",
max_length=100,
verbose_name="propósito",
),
),
(
"inicio",
models.DateTimeField(verbose_name="Data/hora de início"),
),
(
"termino",
models.DateTimeField(verbose_name="Data/hora de término"),
),
(
"informacoes",
models.TextField(
blank=True,
help_text="Utilize para anotar informações adicionais e demais detalhes sobre a reserva",
verbose_name="informações adicionais",
),
),
(
"solicitante",
models.CharField(
help_text="indique o nome da pessoa ou setor solicitante da reserva",
max_length=100,
verbose_name="solicitante",
),
),
(
"contato",
models.CharField(
blank=True,
help_text="Indique o nome da(s) pessoa(s) de contato para tratar assuntos da reserva.",
max_length=100,
verbose_name="pessoa de contato",
),
),
(
"telefone_contato",
models.CharField(
blank=True,
help_text="Indique o telefone/ramal da pessoa responsável pela reserva.",
max_length=100,
verbose_name="telefone de contato",
),
),
(
"espaco",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="espacos.espaco",
verbose_name="espaço",
),
),
],
options={
"verbose_name": "reserva",
"verbose_name_plural": "reservas",
"ordering": ("inicio", "espaco", "proposito"),
},
),
migrations.CreateModel(
name="RecursoSolicitado",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"quantidade",
models.FloatField(default=0.0, verbose_name="quantidade"),
),
(
"observacoes",
models.TextField(blank=True, verbose_name="observações"),
),
(
"recurso",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="espacos.recurso",
verbose_name="recurso",
),
),
(
"reserva",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="espacos.reserva",
verbose_name="reserva",
),
),
],
options={
"verbose_name": "recurso solicitado",
"verbose_name_plural": "recursos solicitados",
"ordering": ("recurso",),
},
),
]

52
sigi/apps/espacos/migrations/0002_converte_eventos.py

@ -0,0 +1,52 @@
# Generated by Django 4.2.4 on 2023-11-08 13:53
from django.db import migrations
def forwards(apps, schema_editor):
TipoEvento = apps.get_model("eventos", "TipoEvento")
Evento = apps.get_model("eventos", "Evento")
Espaco = apps.get_model("espacos", "Espaco")
Reserva = apps.get_model("espacos", "Reserva")
print("")
for tipo_evento in TipoEvento.objects.filter(categoria="C"):
print(f"\t Processando {tipo_evento.nome}...")
espaco = Espaco(
nome=tipo_evento.nome, sigla=tipo_evento.sigla, local="ILB"
)
espaco.save()
for evento in Evento.objects.filter(tipo_evento=tipo_evento):
print(f"\t\t Evento {evento.nome}... ", end="")
if evento.status == "C": # CANCELADO
status_reserva = "C"
else:
status_reserva = "A"
reserva = Reserva(
status=status_reserva,
espaco=espaco,
proposito=evento.nome,
inicio=evento.data_inicio,
termino=evento.data_termino,
informacoes=(
f"Processo SIGAD: {evento.num_processo}\n"
f"{evento.observacao}"
),
solicitante=evento.solicitante,
contato=evento.descricao[:100],
)
reserva.save()
print("reserva criada... ", end="")
evento.delete()
print("evento apagado!")
tipo_evento.delete()
class Migration(migrations.Migration):
dependencies = [
("espacos", "0001_initial"),
]
operations = [migrations.RunPython(forwards, migrations.RunPython.noop)]

0
sigi/apps/espacos/migrations/__init__.py

150
sigi/apps/espacos/models.py

@ -0,0 +1,150 @@
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext as _
class Espaco(models.Model):
nome = models.CharField(_("nome"), max_length=100)
sigla = models.CharField(_("sigla"), max_length=20)
descricao = models.TextField(_("descrição"), blank=True)
local = models.CharField(
_("local"),
max_length=100,
help_text=_(
"Indique o prédio/bloco/sala onde este espaço está localizado."
),
)
class Meta:
verbose_name = _("espaço")
verbose_name_plural = _("espaços")
ordering = ("nome",)
def __str__(self):
return _(f"{self.sigla} - {self.nome}")
class Recurso(models.Model):
nome = models.CharField(_("nome"), max_length=100)
sigla = models.CharField(_("sigla"), max_length=20)
descricao = models.TextField(_("descrição"), blank=True)
class Meta:
verbose_name = _("recurso")
verbose_name_plural = _("recursos")
ordering = ("nome",)
def __str__(self):
return _(f"{self.sigla} - {self.nome}")
class Reserva(models.Model):
STATUS_ATIVO = "A"
STATUS_CANCELADO = "C"
STATUS_CHOICES = (
(STATUS_ATIVO, _("Ativo")),
(STATUS_CANCELADO, _("Cancelado")),
)
status = models.CharField(
_("Status"),
max_length=1,
choices=STATUS_CHOICES,
default=STATUS_ATIVO,
editable=False,
)
espaco = models.ForeignKey(
Espaco, verbose_name=_("espaço"), on_delete=models.PROTECT
)
proposito = models.CharField(
_("propósito"),
max_length=100,
help_text=_(
"Indique o propósito da reserva (nome do evento, indicativo da "
"reunião, aula, apresentação, etc.)"
),
)
inicio = models.DateTimeField(_("Data/hora de início"))
termino = models.DateTimeField(_("Data/hora de término"))
informacoes = models.TextField(
_("informações adicionais"),
blank=True,
help_text=_(
"Utilize para anotar informações adicionais e demais detalhes "
"sobre a reserva"
),
)
solicitante = models.CharField(
_("solicitante"),
max_length=100,
help_text=_(
"indique o nome da pessoa ou setor solicitante da reserva"
),
)
contato = models.CharField(
_("pessoa de contato"),
max_length=100,
blank=True,
help_text=_(
"Indique o nome da(s) pessoa(s) de contato para tratar "
"assuntos da reserva."
),
)
telefone_contato = models.CharField(
_("telefone de contato"),
max_length=100,
blank=True,
help_text=_(
"Indique o telefone/ramal da pessoa responsável pela reserva."
),
)
class Meta:
verbose_name = _("reserva")
verbose_name_plural = _("reservas")
ordering = ("inicio", "espaco", "proposito")
def __str__(self):
return _(f"{self.proposito} em {self.espaco.nome}")
def clean(self):
if self.inicio > self.termino:
raise ValidationError(
_("Data de início deve ser anterior à data de término")
)
if (
Reserva.objects.exclude(id=self.pk)
.filter(
espaco=self.espaco,
inicio__lte=self.termino,
termino__gte=self.inicio,
)
.exists()
):
raise ValidationError(
_(
"Já existe um evento neste mesmo espaço que conflita com "
"as datas solicitadas"
)
)
return super().clean()
class RecursoSolicitado(models.Model):
reserva = models.ForeignKey(
Reserva, verbose_name=_("reserva"), on_delete=models.CASCADE
)
recurso = models.ForeignKey(
Recurso, verbose_name=_("recurso"), on_delete=models.PROTECT
)
quantidade = models.FloatField(_("quantidade"), default=0.0)
observacoes = models.TextField(_("observações"), blank=True)
class Meta:
verbose_name = _("recurso solicitado")
verbose_name_plural = _("recursos solicitados")
ordering = ("recurso",)
def __str__(self):
return _(f"{self.recurso} para {self.reserva}")

15
sigi/apps/espacos/templates/admin/espacos/reserva/change_form.html

@ -0,0 +1,15 @@
{% extends "admin/change_form.html" %}
{% load i18n admin_urls %}
{% block object-tools-items %}
{% if object_id %}
<li>
{% url opts|admin_urlname:'cancel' object_id|admin_urlquote as tool_url %}
<a href="{% add_preserved_filters tool_url %}">
<i class="left material-icons" aria-hidden="true">cancel</i>
{% trans "Cancelar reserva" %}
</a>
</li>
{% endif %}
{{ block.super }}
{% endblock %}

44
sigi/apps/espacos/templates/espacos/agenda.html

@ -0,0 +1,44 @@
{% extends "eventos/calendario.html" %}
{% load i18n static sigi_tags %}
{% block extrastyle %}
{{ block.super }}
<style>
tr.linha-evento {
border-bottom: 1px solid var(--hairline-color);
}
tr td {
border-left: 1px solid var(--hairline-color);
}
</style>
{% endblock %}
{% block content %}
<div class="fixed-action-btn">
<a class="btn-floating">
<i class="large material-icons">mode_edit</i>
</a>
<ul>
<li>
<a class="btn-floating btn-small" href="?ano={{ ano_pesquisa|safe }}&mes={{ mes_pesquisa|safe }}&pdf=1" target="_blank"><i class="material-icons">picture_as_pdf</i></a>
</li>
</ul>
</div>
<div class="row">
<div class="col s12">
<ul class="tabs">
{% for ano in meses %}
<li class="tab col"><a {% if ano == ano_pesquisa %}class="active"{% endif %} href="#tab-{{ ano|safe }}">{{ ano| safe }}</a></li>
{% endfor %}
</ul>
</div>
{% for ano, lista in meses.items %}
<div id="tab-{{ ano|safe }}" class="col s12">
{% for mes, nome in lista.items %}
<a class="waves-effect waves-light btn-flat btn-small{% if ano == ano_pesquisa and mes == mes_pesquisa %} disabled{% endif %}" href="?ano={{ ano|safe }}&mes={{ mes|safe }}">{{ nome }}</a>
{% endfor %}
</div>
{% endfor %}
</div>
{% include "espacos/snippets/agenda_cal.html" %}
{% endblock %}

108
sigi/apps/espacos/templates/espacos/agenda_pdf.html

@ -0,0 +1,108 @@
{% extends 'pdf/base_report.html' %}
{% load static i18n sigi_tags %}
{% block page_size %}A4 landscape{% endblock page_size %}
{% block extra_style %}
{{ block.super }}
blockquote {
margin: 20px 0;
padding-left: 1.5rem;
border-left: 5px solid #ee6e73;
font-size: 1.1 rem;
font-weight: bold;
}
a {
color: black;
text-decoration: none;
}
table {
table-layout: fixed;
}
.calendar-table {
border-collapse: collapse;
border-spacing: 0;
border: 1px solid #d2d2d2;
}
.calendar-table td+td {
border-left: 1px solid #d2d2d2 !important;
}
table td,
table td * {
vertical-align: top;
}
.calendar-table tr:nth-child(even) {
background-color: white !important;
}
tr.linha-dias {
background: #d2d2d2;
border-top: 1px solid #d2d2d2;
}
tr.linha-evento {
border-bottom: 1px solid #d2d2d2;
}
span.numero-dia {
font-size: 1em;
}
.card {
background-color: #fff;
padding: 15px;
margin: 10px 0;
}
.card .card-content .card-title {
display: block;
line-height: 32px;
margin-bottom: 8px;
font-weight: 300;
}
.card-title {
font-size: 20px !important;
margin-bottom: -6px !important;
}
.data-evento {
font-size: 1em;
display: block;
}
.tipo-evento {
font-size: 1em;
color: var(--body-quiet-color);
display: block;
margin-bottom: 8px;
}
.evento {
margin: 0;
padding: 5px 10px;
}
.cyan.lighten-4 { background-color: #b2ebf2!important; }
.red.lighten-4 { background-color: #ffcdd2!important; }
.purple.lighten-4 { background-color: #e1bee7!important; }
.blue.lighten-4 { background-color: #bbdefb!important; }
.orange.lighten-4 { background-color: #ffe0b2!important; }
.brown.lighten-4 { background-color: #d7ccc8 !important; }
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url('/static/material/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2') format('woff2');
}
i.tiny { font-size: 1rem; }
.material-icons {
font-family: "Material Icons";
font-weight: 400;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: "liga";
-webkit-font-smoothing: antialiased;
}
{% endblock %}
{% block main_content %}
{% include "espacos/snippets/agenda_cal.html" %}
{% endblock main_content %}

55
sigi/apps/espacos/templates/espacos/snippets/agenda_cal.html

@ -0,0 +1,55 @@
{% load i18n static sigi_tags %}
{% for semana in semanas %}
<div class="card-panel">
<blockquote>
{% blocktranslate with start=semana.datas|first|date:"SHORT_DATE_FORMAT" end=semana.datas|last|date:"SHORT_DATE_FORMAT" %}
Semana de {{ start }} a {{ end }}
{% endblocktranslate %}
</blockquote>
<table class="calendar-table">
<thead>
<tr>
<th rowspan="2">{% trans "Espaço" %}</th>
{% for name in day_names %}
<th>{{ name }}</th>
{% endfor %}
</tr>
<tr>
{% for dia in semana.datas %}
<th>{{ dia|date:"d/m"}}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for espaco, reservas in semana.reservas.items %}
<tr class="linha-evento">
<th>{{ espaco.sigla }}</th>
{% setvar 0 as last_pos %}
{% for reserva, tupla in reservas %}
{% for x in ""|ljust:tupla.0|make_list %}<td></td>{% endfor %}
<td colspan="{{ tupla.1 }}" class="blue lighten-4">
<p><strong>{{ reserva.proposito }}</strong></p>
<p>{{ reserva.inicio|interval:reserva.termino }}</p>
<p>
{% blocktranslate with solicitante=reserva.solicitante %}
solicitado por {{ solicitante }}
{% endblocktranslate %}
</p>
</td>
{% if forloop.last %}
{% for x in ""|ljust:tupla.2|make_list %}<td></td>{% endfor %}
{% endif %}
{% setvar last_pos|sum:tupla.1 as last_pos %}
{% empty %}
{% for x in "1234567"|make_list %}<td></td>{% endfor %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if pdf and not forloop.last %}
<div class="new-page"></div>
{% endif %}
{% endfor %}

3
sigi/apps/espacos/tests.py

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

126
sigi/apps/espacos/views.py

@ -0,0 +1,126 @@
import calendar
import locale
from typing import Any
from django import http
from django.db.models import Q
from django.template.response import TemplateResponse
from django.utils import timezone
from django.utils.translation import (
to_locale,
get_language,
ngettext,
gettext as _,
)
from django.views.generic.base import TemplateView
from django_weasyprint.views import WeasyTemplateResponse
from sigi.apps.espacos.models import Espaco, Reserva
class Agenda(TemplateView):
def _is_pdf(self):
return bool(self.request.GET.get("pdf", 0))
def get_template_names(self):
if self._is_pdf():
return ["espacos/agenda_pdf.html"]
else:
return ["espacos/agenda.html"]
def get_context_data(self, **kwargs):
mes_pesquisa = int(
self.request.GET.get("mes", timezone.localdate().month)
)
ano_pesquisa = int(
self.request.GET.get("ano", timezone.localdate().year)
)
sel_espacos = self.request.GET.getlist(
"espaco", list(Espaco.objects.values_list("id", flat=True))
)
meses = {}
lang = to_locale(get_language()) + ".UTF-8"
locale.setlocale(locale.LC_ALL, lang)
for ano, mes in (
Reserva.objects.values_list("inicio__year", "inicio__month")
.order_by("inicio__year", "inicio__month")
.distinct("inicio__year", "inicio__month")
):
if ano in meses:
meses[ano][mes] = calendar.month_name[mes]
else:
meses[ano] = {mes: calendar.month_name[mes]}
espacos = list(Espaco.objects.all())
semanas = [
{"datas": s, "reservas": {espaco: [] for espaco in espacos}}
for s in calendar.Calendar().monthdatescalendar(
ano_pesquisa, mes_pesquisa
)
]
primeiro_dia = timezone.make_aware(
timezone.datetime(*semanas[0]["datas"][0].timetuple()[:6])
)
ultimo_dia = timezone.make_aware(
timezone.datetime(*semanas[-1]["datas"][-1].timetuple()[:6])
)
for reserva in Reserva.objects.exclude(
status=Reserva.STATUS_CANCELADO
).filter(
Q(inicio__range=[primeiro_dia, ultimo_dia])
| Q(termino__range=[primeiro_dia, ultimo_dia])
):
for semana in semanas:
if not (
(reserva.termino.date() < semana["datas"][0])
or (reserva.inicio.date() > semana["datas"][-1])
):
start = max(semana["datas"][0], reserva.inicio.date())
end = min(semana["datas"][-1], reserva.termino.date())
semana["reservas"][reserva.espaco].append(
[
reserva,
[
start.weekday(),
end.weekday() - start.weekday() + 1,
6 - end.weekday(),
],
]
)
for semana in semanas:
for espaco, reservas in semana["reservas"].items():
last_pos = 0
for reserva in reservas:
if last_pos > 0:
reserva[1][0] -= last_pos
last_pos += reserva[1][0] + reserva[1][1]
context = super().get_context_data(**kwargs)
context["mes_pesquisa"] = mes_pesquisa
context["ano_pesquisa"] = ano_pesquisa
context["sel_espacos"] = sel_espacos
context["meses"] = meses
context["espacos"] = Espaco.objects.all()
context["semanas"] = semanas
context["day_names"] = calendar.day_abbr
if self._is_pdf():
context["pdf"] = True
context["title"] = _("Reserva de espaços do ILB")
return context
def render_to_response(self, context, **response_kwargs):
self.response_class = TemplateResponse
self.content_type = None
if self._is_pdf():
self.content_type = "application/pdf"
self.response_class = WeasyTemplateResponse
response_kwargs.setdefault(
"filename", f"agenda-{timezone.localdate()}.pdf"
)
return super().render_to_response(context, **response_kwargs)

8
sigi/apps/utils/templatetags/action_icons.py

@ -9,10 +9,18 @@ ACTION_LIST = {
"calcular_data_uso": "functions", "calcular_data_uso": "functions",
} }
ACTION_PREFIXES = {
"cancelar": "cancel",
"ativar": "check_circle",
}
@register.simple_tag @register.simple_tag
def action_icon(action_name): def action_icon(action_name):
if action_name in ACTION_LIST: if action_name in ACTION_LIST:
return ACTION_LIST[action_name] return ACTION_LIST[action_name]
else: else:
for prefix in ACTION_PREFIXES:
if prefix in action_name:
return ACTION_PREFIXES[prefix]
return "play_arrow" return "play_arrow"

38
sigi/apps/utils/templatetags/sigi_tags.py

@ -0,0 +1,38 @@
import datetime
from django import template
from django.conf import settings
from django.utils import timezone
from django.utils.translation import gettext as _
register = template.Library()
@register.filter
def interval(value, arg):
if not isinstance(value, datetime.datetime) or not isinstance(
arg, datetime.datetime
):
return ""
value = timezone.localtime(value)
arg = timezone.localtime(arg)
if value.year != arg.year:
format_mask = "%d/%m/%Y às %H:%M"
elif value.month != arg.month:
format_mask = "%d/%m às %H:%M"
elif value.day != arg.day:
format_mask = "dia %d às %H:%M"
else:
format_mask = "%H:%M"
return _(
f"de {value.strftime(format_mask)} " f"a {arg.strftime(format_mask)}"
)
@register.filter
def sum(value, arg):
return value + arg
@register.simple_tag
def setvar(val=None):
return val

12
sigi/menu_conf.yaml

@ -71,6 +71,14 @@ main_menu:
- title: Painel de ocorrências - title: Painel de ocorrências
view_name: ocorrencias_painel view_name: ocorrencias_painel
querystr: status=1&status=2 querystr: status=1&status=2
- title: Reserva de espaços
icon: event
children:
- title: Reservas
view_name: admin:espacos_reserva_changelist
querystr: status=A
- title: Agenda de reservas
view_name: espacos_agenda
- title: Eventos - title: Eventos
icon: school icon: school
children: children:
@ -128,6 +136,10 @@ main_menu:
view_name: admin:eventos_modelodeclaracao_changelist view_name: admin:eventos_modelodeclaracao_changelist
- title: Partidos políticos - title: Partidos políticos
view_name: admin:parlamentares_partido_changelist view_name: admin:parlamentares_partido_changelist
- title: Espaços para eventos
view_name: admin:espacos_espaco_changelist
- title: Recursos para eventos
view_name: admin:espacos_recurso_changelist
contato_menu: contato_menu:
- title: Casa legislativa - title: Casa legislativa

3
sigi/settings.py

@ -44,6 +44,7 @@ INSTALLED_APPS = [
"sigi.apps.casas", "sigi.apps.casas",
"sigi.apps.contatos", "sigi.apps.contatos",
"sigi.apps.convenios", "sigi.apps.convenios",
"sigi.apps.espacos",
"sigi.apps.eventos", "sigi.apps.eventos",
"sigi.apps.home", "sigi.apps.home",
"sigi.apps.inventario", "sigi.apps.inventario",
@ -132,7 +133,7 @@ FORM_RENDERER = "django.forms.renderers.TemplatesSetting"
WSGI_APPLICATION = "sigi.wsgi.application" WSGI_APPLICATION = "sigi.wsgi.application"
167808
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/ # https://docs.djangoproject.com/en/4.0/topics/i18n/

1
sigi/urls.py

@ -26,6 +26,7 @@ urlpatterns = [
path("parlamentares/", include("sigi.apps.parlamentares.urls")), path("parlamentares/", include("sigi.apps.parlamentares.urls")),
path("servicos/", include("sigi.apps.servicos.urls")), path("servicos/", include("sigi.apps.servicos.urls")),
path("admin/casas/", include("sigi.apps.casas.admin_urls")), path("admin/casas/", include("sigi.apps.casas.admin_urls")),
path("admin/espacos/", include("sigi.apps.espacos.admin_urls")),
path("admin/eventos/", include("sigi.apps.eventos.admin_urls")), path("admin/eventos/", include("sigi.apps.eventos.admin_urls")),
path("admin/convenios/", include("sigi.apps.convenios.urls")), path("admin/convenios/", include("sigi.apps.convenios.urls")),
path("admin/ocorrencias/", include("sigi.apps.ocorrencias.admin_urls")), path("admin/ocorrencias/", include("sigi.apps.ocorrencias.admin_urls")),

Loading…
Cancel
Save