From c23371695c930a7983b547e022f39e05bcdc0f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ses=C3=B3stris=20Vieira?= Date: Sun, 9 May 2021 13:50:16 -0300 Subject: [PATCH] Exportar dados de oficinas para CSV --- sigi/apps/eventos/admin.py | 25 +- .../templates/admin/eventos/change_list.html | 1 + .../eventos/templates/eventos/carrinho.html | 63 +++++ sigi/apps/eventos/urls.py | 9 + sigi/apps/eventos/views.py | 246 +++++++++++++++--- 5 files changed, 307 insertions(+), 37 deletions(-) create mode 100644 sigi/apps/eventos/templates/admin/eventos/change_list.html create mode 100644 sigi/apps/eventos/templates/eventos/carrinho.html diff --git a/sigi/apps/eventos/admin.py b/sigi/apps/eventos/admin.py index 088d82c..e82717e 100644 --- a/sigi/apps/eventos/admin.py +++ b/sigi/apps/eventos/admin.py @@ -18,10 +18,12 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from django.contrib import admin from django import forms +from django.contrib import admin +from django.http import HttpResponseRedirect from django.utils.translation import ugettext as _ from sigi.apps.eventos.models import TipoEvento, Funcao, Evento, Equipe, Convite +from sigi.apps.eventos.views import adicionar_eventos_carrinho class EventoAdminForm(forms.ModelForm): class Meta: @@ -69,4 +71,23 @@ class EventoAdmin(admin.ModelAdmin): raw_id_fields = ('casa_anfitria', 'municipio',) search_fields = ('nome', 'tipo_evento__nome', 'casa_anfitria__search_text', 'municipio__search_text', 'solicitante') - inlines = (EquipeInline, ConviteInline) \ No newline at end of file + inlines = (EquipeInline, ConviteInline) + actions = ['adicionar_eventos', ] + + def adicionar_eventos(self, request, queryset): + if 'carrinho_eventos' in request.session: + q1 = len(request.session['carrinho_eventos']) + else: + q1 = 0 + response = adicionar_eventos_carrinho(request, queryset=queryset) + q2 = len(request.session['carrinho_eventos']) + quant = q2 - q1 + if quant: + self.message_user(request, str(q2 - q1) + " " + + _(u"Eventos adicionados no carrinho")) + else: + self.message_user(request, _(u"Os Eventos selecionados " + u"já foram adicionados anteriormente")) + return HttpResponseRedirect('.') + adicionar_eventos.short_description = _(u"Armazenar eventos no carrinho " + u"para exportar") diff --git a/sigi/apps/eventos/templates/admin/eventos/change_list.html b/sigi/apps/eventos/templates/admin/eventos/change_list.html new file mode 100644 index 0000000..83ebd55 --- /dev/null +++ b/sigi/apps/eventos/templates/admin/eventos/change_list.html @@ -0,0 +1 @@ +{% extends "change_list_with_cart.html" %} \ No newline at end of file diff --git a/sigi/apps/eventos/templates/eventos/carrinho.html b/sigi/apps/eventos/templates/eventos/carrinho.html new file mode 100644 index 0000000..554f8b4 --- /dev/null +++ b/sigi/apps/eventos/templates/eventos/carrinho.html @@ -0,0 +1,63 @@ +{% extends "admin/carrinho.html" %} +{% load admin_list i18n %} +{% block extrastyle %} + {{ block.super }} + {#% include "admin/tabs_style.html" %#} +{% endblock %} + +{% block title %}{% trans 'Eventos no Carrinho | SIGI' %}{% endblock %} +{% block content_title %}

{% trans 'Eventos no Carrinho' %}

{% endblock %} + +{% block mensagem%} + +{% endblock %} + +{% block action %}deleta_itens_carrinho{% endblock %} + +{% block tabela %} + + + + {%if not carIsEmpty%} + + {% endif %} + + + + + + + + + + + {% for evento in paginas.object_list %} + + {%if not carIsEmpty%} + + {% endif %} + + + + + + + + + {% endfor %} + +
+ {% trans 'Nome do evento' %}{% trans 'Tipo evento' %}{% trans 'Status' %}{% trans 'Data de início' %}{% trans 'Data de término' %}{% trans 'município' %}{% trans 'Solicitante' %}
+ {{evento.nome}}{{evento.tipo_evento}}{{evento.get_status_display}}{{evento.data_inicio}}{{evento.data_termino}}{{evento.municipio}}{{evento.solicitante}}
+{% endblock %} + +{% block botoes %} +{% trans "Exportar CVS" %} +{% endblock %} \ No newline at end of file diff --git a/sigi/apps/eventos/urls.py b/sigi/apps/eventos/urls.py index a648c7c..05af590 100644 --- a/sigi/apps/eventos/urls.py +++ b/sigi/apps/eventos/urls.py @@ -7,4 +7,13 @@ urlpatterns = patterns( # Painel de ocorrencias url(r'^calendario/$', 'calendario', name='eventos-calendario'), url(r'^alocacaoequipe/$', 'alocacao_equipe', name='eventos-alocacaoequipe'), + # Carrinho + url(r'^evento/carrinho/$', 'visualizar_carrinho', + name='visualizar-carrinho-evento'), + url(r'^evento/carrinho/excluir_carrinho/$', 'excluir_carrinho', + name='excluir-carrinho-evento'), # Error + url(r'^evento/carrinho/deleta_itens_carrinho$', 'deleta_itens_carrinho', + name='deleta-itens-carrinho-evento'), # Error + url(r'^evento/csv/$', 'export_csv', name='evento-export-csv'), # Error + ) diff --git a/sigi/apps/eventos/views.py b/sigi/apps/eventos/views.py index e6ab84b..164f644 100644 --- a/sigi/apps/eventos/views.py +++ b/sigi/apps/eventos/views.py @@ -3,17 +3,17 @@ # sigi.apps.eventos.views # # Copyright (C) 2015 Interlegis -# +# # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -21,11 +21,14 @@ import calendar import datetime import locale +from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden +from django.core.paginator import Paginator, InvalidPage, EmptyPage +from django.contrib import messages from django.contrib.auth.decorators import login_required from django.shortcuts import render from django.utils import translation from django.utils.translation import ungettext, ugettext as _ -from sigi.apps.eventos.models import Evento +from sigi.apps.eventos.models import Evento, Equipe, Convite from sigi.apps.servidores.models import Servidor from sigi.shortcuts import render_to_pdf import csv @@ -36,28 +39,28 @@ def calendario(request): mes_pesquisa = int(request.GET.get('mes', datetime.date.today().month)) ano_pesquisa = int(request.GET.get('ano', datetime.date.today().year)) formato = request.GET.get('fmt', 'html') - + dia1 = datetime.date(ano_pesquisa, mes_pesquisa, 1) mes_anterior = dia1 - datetime.timedelta(days=1) mes_seguinte = dia1.replace(day=28) + datetime.timedelta(days=4) # Ugly hack mes_seguinte = mes_seguinte.replace(day=1) - + data = {'mes_pesquisa': mes_pesquisa, 'ano_pesquisa': ano_pesquisa} - if Evento.objects.filter(data_inicio__year=mes_anterior.year, + if Evento.objects.filter(data_inicio__year=mes_anterior.year, data_inicio__month=mes_anterior.month).exists(): data['prev_button'] = {'mes': mes_anterior.month, 'ano': mes_anterior.year } - - if Evento.objects.filter(data_inicio__year=mes_seguinte.year, + + if Evento.objects.filter(data_inicio__year=mes_seguinte.year, data_inicio__month=mes_seguinte.month).exists(): data['next_button'] = {'mes': mes_seguinte.month, 'ano': mes_seguinte.year } - + c = calendar.Calendar(6) dates = reduce(lambda x,y: x+y, c.monthdatescalendar(ano_pesquisa, mes_pesquisa)) - + eventos = [] - - for evento in Evento.objects.filter(data_inicio__year=ano_pesquisa, + + for evento in Evento.objects.filter(data_inicio__year=ano_pesquisa, data_inicio__month=mes_pesquisa).order_by('data_inicio'): start = dates.index(evento.data_inicio) if not evento.data_termino in dates: @@ -75,13 +78,13 @@ def calendario(request): # Agrupa os eventos em linhas para melhorar a visualização linhas = [] - + for evento in eventos: encaixado = False for linha in linhas: sobrepoe = False for e in linha: - if (((evento['evento'].data_inicio >= e['evento'].data_inicio) and + if (((evento['evento'].data_inicio >= e['evento'].data_inicio) and (evento['evento'].data_inicio <= e['evento'].data_termino)) or ((evento['evento'].data_termino >= e['evento'].data_inicio) and (evento['evento'].data_termino <= e['evento'].data_termino))): @@ -96,7 +99,7 @@ def calendario(request): # Adiciona uma nova linha porque este evento não se encaixa em nenhuma existente linhas.append([evento]) - # Recalcula as distâncias dos eventos por linha para encaixar no calendário + # Recalcula as distâncias dos eventos por linha para encaixar no calendário for linha in linhas: anterior = None for evento in linha: @@ -110,27 +113,27 @@ def calendario(request): data['dates'] = dates data['eventos'] = eventos data['linhas'] = linhas - + if formato == 'pdf': return render_to_pdf('eventos/calendario_pdf.html', data ) - + return render(request, 'eventos/calendario.html', data) @login_required def alocacao_equipe(request): ano_pesquisa = int(request.GET.get('ano', datetime.date.today().year)) formato = request.GET.get('fmt', 'html') - + data = {'ano_pesquisa': ano_pesquisa} - + if Evento.objects.filter(data_inicio__year=ano_pesquisa-1).exists(): data['prev_button'] = {'ano': ano_pesquisa - 1 } - + if Evento.objects.filter(data_inicio__year=ano_pesquisa+1).exists(): data['next_button'] = {'ano': ano_pesquisa + 1 } - + dados = [] - + for evento in Evento.objects.filter(data_inicio__year=ano_pesquisa).exclude(status='C').prefetch_related('equipe_set'): for p in evento.equipe_set.all(): registro = None @@ -141,36 +144,36 @@ def alocacao_equipe(request): if not registro: registro = [p.membro.pk, p.membro.nome_completo, [{'dias': 0, 'eventos': 0} for x in range(1,13)]] dados.append(registro) - + registro[2][evento.data_inicio.month-1]['dias'] += (evento.data_termino - evento.data_inicio).days + 1 registro[2][evento.data_inicio.month-1]['eventos'] += 1 - + dados.sort(lambda x, y: cmp(x[1], y[1])) - + lang = (translation.to_locale(translation.get_language())+'.utf8').encode() locale.setlocale(locale.LC_ALL, lang) meses = [calendar.month_name[m] for m in range(1,13)] - + linhas = [[_(u"Servidor")] + meses + ['total']] - + for r in dados: r[2].append(reduce(lambda x,y:{'dias': x['dias'] + y['dias'], 'eventos': x['eventos'] + y['eventos']}, r[2])) - linhas.append([r[1]] + + linhas.append([r[1]] + [_(ungettext(u"%(dias)s dia", u"%(dias)s dias", d['dias']) + " em " + ungettext(u"%(eventos)s evento", u"%(eventos)s eventos", d['eventos']) ) % d if d['dias'] > 0 or d['eventos'] > 0 else '' for d in r[2]]) - + # for registro in Servidor.objects.filter(equipe_evento__evento__data_inicio__year=ano_pesquisa).exclude(equipe_evento__evento__status='C').distinct(): # dados = [{'dias': 0, 'eventos': 0} for x in range(1,13)] # for part in registro.equipe_evento.filter(evento__data_inicio__year=ano_pesquisa).exclude(evento__status='C'): -# dados[part.evento.data_inicio.month-1]['dias'] += (part.evento.data_termino - +# dados[part.evento.data_inicio.month-1]['dias'] += (part.evento.data_termino - # part.evento.data_inicio).days + 1 # dados[part.evento.data_inicio.month-1]['eventos'] += 1 # dados.append([registro.nome_completo] + [_(ungettext(u"%(dias)s dia", u"%(dias)s dias", d['dias']) + " em " + ungettext(u"%(eventos)s evento", u"%(eventos)s eventos", d['eventos'])) % d if d['dias'] > 0 or d['eventos'] > 0 else '' for d in dados]) - + data['linhas'] = linhas - + if formato == 'pdf': return render_to_pdf('eventos/alocacao_equipe_pdf.html', data) elif formato == 'csv': @@ -187,5 +190,178 @@ def alocacao_equipe(request): 'meses': {m[0]: m[1] for m in zip(meses+['total'], d[2])} } for d in dados]} return JsonResponse(result) - - return render(request, 'eventos/alocacao_equipe.html', data) \ No newline at end of file + + return render(request, 'eventos/alocacao_equipe.html', data) + +# Views e functions para carrinho de exportação + +def query_ordena(qs, o): + from sigi.apps.eventos.admin import EventoAdmin + list_display = EventoAdmin.list_display + order_fields = [] + + for order_number in o.split('.'): + order_number = int(order_number) + order = '' + if order_number != abs(order_number): + order_number = abs(order_number) + order = '-' + order_fields.append(order + list_display[order_number - 1]) + qs = qs.order_by(*order_fields) + return qs + +def get_for_qs(get, qs): + kwargs = {} + for k, v in get.iteritems(): + if str(k) not in ('page', 'pop', 'q', '_popup', 'o', 'ot'): + kwargs[str(k)] = v + qs = qs.filter(**kwargs) + if 'o' in get: + qs = query_ordena(qs, get['o']) + return qs + +def carrinhoOrGet_for_qs(request): + if 'carrinho_eventos' in request.session: + ids = request.session['carrinho_eventos'] + qs = Evento.objects.filter(pk__in=ids) + else: + qs = Evento.objects.all() + if request.GET: + qs = get_for_qs(request.GET, qs) + return qs + +def adicionar_eventos_carrinho(request, queryset=None, id=None): + if request.method == 'POST': + ids_selecionados = request.POST.getlist('_selected_action') + if 'carrinho_eventos' not in request.session: + request.session['carrinho_eventos'] = ids_selecionados + else: + lista = request.session['carrinho_eventos'] + # Verifica se id já não está adicionado + for id in ids_selecionados: + if id not in lista: + lista.append(id) + request.session['carrinho_eventos'] = lista + +@login_required +def visualizar_carrinho(request): + qs = carrinhoOrGet_for_qs(request) + paginator = Paginator(qs, 100) + + try: + page = int(request.GET.get('page', '1')) + except ValueError: + page = 1 + + try: + paginas = paginator.page(page) + except (EmptyPage, InvalidPage): + paginas = paginator.page(paginator.num_pages) + + carrinhoIsEmpty = not('carrinho_eventos' in request.session) + + return render( + request, + 'eventos/carrinho.html', + { + 'carIsEmpty': carrinhoIsEmpty, + 'paginas': paginas, + 'query_str': '?' + request.META['QUERY_STRING'] + } + ) + +@login_required +def excluir_carrinho(request): + if 'carrinho_eventos' in request.session: + del request.session['carrinho_eventos'] + messages.info(request, u'O carrinho foi esvaziado') + return HttpResponseRedirect('../../') + +@login_required +def deleta_itens_carrinho(request): + if request.method == 'POST': + ids_selecionados = request.POST.getlist('_selected_action') + removed = 0 + if 'carrinho_eventos' in request.session: + lista = request.session['carrinho_eventos'] + for item in ids_selecionados: + lista.remove(item) + removed += 1 + if lista: + request.session['carrinho_eventos'] = lista + else: + del lista + del request.session['carrinho_eventos'] + messages.info(request, u"{0} itens removidos do carrinho".format(removed)) + return HttpResponseRedirect('.') + +@login_required +def export_csv(request): + def serialize(r, field): + value = (getattr(r, 'get_{0}_display'.format(field.name), None) or + getattr(r, field.name, "")) + if callable(value): + value = value() + if value is None: + value = "" + return unicode(value).encode('utf8') + + eventos_fields = Evento._meta.fields + equipe_fields = Equipe._meta.fields + convite_fields = Convite._meta.fields + + eventos = carrinhoOrGet_for_qs(request) + eventos.select_related('equipe', 'convite') + + if not eventos: + messages.info(request, _(u"Nenhum evento a exportar")) + return HttpResponseRedirect('../') + + max_equipe = max([e.equipe_set.count() for e in eventos]) + max_convite = max([e.convite_set.count() for e in eventos]) + + head = [f.verbose_name.encode('utf8') for f in eventos_fields + if f.name != 'id'] + head.extend([f.verbose_name.encode('utf8')+"_{0}".format(i+1) + for i in range(max_equipe) for f in Equipe._meta.fields + if f.name not in ('id', 'evento')]) + head.extend([f.verbose_name.encode('utf8')+"_{0}".format(i+1) + for i in range(max_convite) for f in Convite._meta.fields + if f.name not in ('id', 'evento')]) + head.append('total_participantes') + + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename=eventos.csv' + + writer = csv.DictWriter(response, fieldnames=head) + writer.writeheader() + + for evento in eventos: + reg = {f.verbose_name.encode('utf8'): serialize(evento, f) + for f in Evento._meta.fields if f.name != 'id'} + reg['total_participantes'] = 0 + idx = 1 + for membro in evento.equipe_set.all(): + reg.update( + { + "{0}_{1}".format(f.verbose_name.encode('utf8'), idx): + serialize(membro, f) for f in Equipe._meta.fields + if f.name not in ('id', 'evento') + } + ) + idx += 1 + idx = 1 + for convite in evento.convite_set.all(): + reg.update( + { + "{0}_{1}".format(f.verbose_name.encode('utf8'), idx): + serialize(convite, f) for f in Convite._meta.fields + if f.name not in ('id', 'evento') + } + ) + reg['total_participantes'] += convite.qtde_participantes + idx += 1 + writer.writerow(reg) + + return response +