From 4e5d1766bb723ac927af7e6bb54062daac6b82b4 Mon Sep 17 00:00:00 2001 From: Maximilien Cuony Date: Sat, 18 Jan 2014 21:40:02 +0100 Subject: [PATCH] Export --- .../0004_auto__add_field_config_group.py | 85 +++++++++++ server/configs/models.py | 5 +- .../templates/configs/configs/show.html | 7 + server/configs/views.py | 6 +- server/data/pip-reqs.txt | 1 + server/export/forms.py | 15 ++ server/export/templates/export/summary.html | 47 +++++++ .../export/templates/export/summary_pdf.html | 99 +++++++++++++ server/export/urls.py | 2 + server/export/views.py | 133 ++++++++++++++++-- server/templates/base.html | 1 + 11 files changed, 388 insertions(+), 13 deletions(-) create mode 100644 server/configs/migrations/0004_auto__add_field_config_group.py create mode 100644 server/export/templates/export/summary.html create mode 100644 server/export/templates/export/summary_pdf.html diff --git a/server/configs/migrations/0004_auto__add_field_config_group.py b/server/configs/migrations/0004_auto__add_field_config_group.py new file mode 100644 index 0000000..7cf50e8 --- /dev/null +++ b/server/configs/migrations/0004_auto__add_field_config_group.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Config.group' + db.add_column(u'configs_config', 'group', + self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Config.group' + db.delete_column(u'configs_config', 'group') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'configs.config': { + 'Meta': {'object_name': 'Config'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'admin_enable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'allowed_users': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'}), + 'group': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key_api': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'key_ipn': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'key_request': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'test_mode': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'url_back_err': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'url_back_ok': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'url_ipn': ('django.db.models.fields.URLField', [], {'max_length': '200'}) + }, + u'configs.configlogs': { + 'Meta': {'object_name': 'ConfigLogs'}, + 'config': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['configs.Config']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'when': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['configs'] \ No newline at end of file diff --git a/server/configs/models.py b/server/configs/models.py index e31959c..5443e77 100644 --- a/server/configs/models.py +++ b/server/configs/models.py @@ -29,6 +29,8 @@ class Config(models.Model): allowed_users = models.ManyToManyField(User, blank=True) + group = models.CharField(max_length=255, help_text=_('To group configs in summary'), blank=True, null=True) + def __unicode__(self): bonus = u'' @@ -51,7 +53,7 @@ def generate_diff(self, other): """Generate diff from this objet an another one (for logs)""" retour = u'\n\n' - for (prop, prop_name) in (('name', 'Name'), ('active', 'Active'), ('admin_enable', 'Admin enable'), ('test_mode', 'Test mode'), ('url_ipn', 'URL Ipn'), ('url_back_ok', 'Return URL for success'), ('url_back_err', 'Return URL for error')): + for (prop, prop_name) in (('name', 'Name'), ('active', 'Active'), ('admin_enable', 'Admin enable'), ('test_mode', 'Test mode'), ('url_ipn', 'URL Ipn'), ('url_back_ok', 'Return URL for success'), ('url_back_err', 'Return URL for error'), ('group', 'Group')): if not other.pk or getattr(self, prop) != getattr(other, prop): retour += unicode(prop_name) + u'=' + unicode(getattr(self, prop)) @@ -78,7 +80,6 @@ def gen_key(self): h = hashlib.sha512() - for i in range(2): h.update(str(os.random())) h.update(str(uuid.uuid4())) diff --git a/server/configs/templates/configs/configs/show.html b/server/configs/templates/configs/configs/show.html index c038753..ff02de1 100644 --- a/server/configs/templates/configs/configs/show.html +++ b/server/configs/templates/configs/configs/show.html @@ -46,6 +46,13 @@

{% trans "Details of a config" %}

{{object.name}}

+ {% if object.group %} +
+ +

{{object.group}}

+
+ {% endif %} +

{{object.url_ipn}}

diff --git a/server/configs/views.py b/server/configs/views.py index 7cb1627..3c82e2c 100644 --- a/server/configs/views.py +++ b/server/configs/views.py @@ -39,7 +39,7 @@ def edit(request, pk): """Edit or create a config""" try: - config = Config.configs.get(pk=pk) + config = Config.objects.get(pk=pk) create = False except: config = Config() @@ -56,14 +56,14 @@ def edit(request, pk): config = form.save(commit=False) if not create: - ConfigLogs(config=config, user=request.user, text=_('Config has been updated: ') + config.generate_diff(Config.configs.get(pk=pk))).save() + ConfigLogs(config=config, user=request.user, text=_('Config has been updated: ') + config.generate_diff(Config.objects.get(pk=pk))).save() config.save() # To use allowed_users config.allowed_users.clear() for u in request.POST.get('allowed_users', []): - config.allowed_users.add(User.configs.get(pk=u)) + config.allowed_users.add(User.objects.get(pk=u)) config.allowed_users.add(request.user) config.save() diff --git a/server/data/pip-reqs.txt b/server/data/pip-reqs.txt index 0a97a6b..0029a41 100644 --- a/server/data/pip-reqs.txt +++ b/server/data/pip-reqs.txt @@ -5,3 +5,4 @@ celery django-celery python-dateutil PicklingTools +reportlab diff --git a/server/export/forms.py b/server/export/forms.py index 867848c..424cb01 100644 --- a/server/export/forms.py +++ b/server/export/forms.py @@ -36,3 +36,18 @@ def __init__(self, user, *args, **kwargs): self.fields['config'].queryset = Config.objects.filter(allowed_users=user).order_by('name').all() else: self.fields['config'].queryset = Config.objects.order_by('name').all() + + +class SummaryForm(forms.Form): + + RANGE_CHOICES = [ + ('thismonth', _('This month')), + ('previousmonth', _('The previous month')), + ('sincemonth', _('Since a month')), + ('thisyear', _('This year')), + ('sinceyear', _('Since a year')), + ] + + include_test = forms.BooleanField(help_text=_('Export transactions from all config'), required=False) + + range = forms.ChoiceField(choices=RANGE_CHOICES) diff --git a/server/export/templates/export/summary.html b/server/export/templates/export/summary.html new file mode 100644 index 0000000..ae6bf28 --- /dev/null +++ b/server/export/templates/export/summary.html @@ -0,0 +1,47 @@ +{% extends "base.html" %} +{% load i18n %} +{% load bootstrap3 %} + +{% block title %}{{block.super}} :: {% trans "Summary" %}{% endblock %} + +{% block content %} + + + +

{% trans "Export of data" %}

+ + + +
+
+
+

{% trans "Summary parameters" %}

+
+
+ +
+ {% csrf_token %} + + {% bootstrap_form form %} + +
+ +
+ +
+
+
+ +
+ + + + +{% endblock %} \ No newline at end of file diff --git a/server/export/templates/export/summary_pdf.html b/server/export/templates/export/summary_pdf.html new file mode 100644 index 0000000..9aa4c14 --- /dev/null +++ b/server/export/templates/export/summary_pdf.html @@ -0,0 +1,99 @@ +{% load i18n %} + + + PolyBanking - {% blocktrans with start_date=start_date|date:"r" end_date=end_date|date:"r" %}Summary from {{start_date}} to {{end_date}}{% endblocktrans %} + + + +

PolyBanking - {% blocktrans with start_date=start_date|date:"r" end_date=end_date|date:"r" %}Summary from {{start_date}} to {{end_date}}{% endblocktrans %}

+
+ {% for group_name, group_data in data.items %} +

{{group_name}}

+ + + + + + + + + + + + + + {% for configs_data in group_data.0 %} + + {% for config_data in configs_data %} + + {% if forloop.first %} + + + + + + + {% else %} + + + {% if forloop.last %} + + + + + + + {% else %} + + + + + + + + + + {% endif %} + + + {% endif %} + + {% endfor %} + + + {% endfor %} + + + + + + + +
{% trans "Config" %}{% trans "From" %}{% trans "To" %}{% trans "Total" %}
{{config_data.3.name}} {% if config_data.3.test_mode %}{% trans "(Test mode !)" %}{% endif %}{{config_data.0|date:"r"}}{{config_data.1|date:"r"}}{{config_data.2|floatformat:"2"}} CHF
{{config_data|floatformat:"2"}} CHF
{{config_data.0|date:"r"}}{{config_data.1|date:"r"}}{{config_data.2|floatformat:"2"}} CHF
 
{% trans "Total" %}{{start_date|date:"r"}}{{end_date|date:"r"}}{{group_data.1|floatformat:"2"}} CHF
+ + {% endfor %} +
+
+ {%block page_foot%} + Page - PolyBanking - {% blocktrans with start_date=start_date|date:"r" end_date=end_date|date:"r" %}Summary from {{start_date}} to {{end_date}}{% endblocktrans %} + {%endblock%} +
+ + \ No newline at end of file diff --git a/server/export/urls.py b/server/export/urls.py index e5c3311..f8717b3 100644 --- a/server/export/urls.py +++ b/server/export/urls.py @@ -8,5 +8,7 @@ url(r'^$', 'home'), + url(r'^summary$', 'summary'), + ) diff --git a/server/export/views.py b/server/export/views.py index e40789f..df37518 100644 --- a/server/export/views.py +++ b/server/export/views.py @@ -23,13 +23,20 @@ import json import csv from huTools.structured import dict2xml +from django.db.models import Sum +import cStringIO as StringIO +import ho.pisa as pisa +from django.template.loader import get_template +from django.template import Context +from cgi import escape +from export.forms import ExportForm, SummaryForm -from export.forms import ExportForm - +from configs.models import Config from paiements.models import Transaction +@login_required def home(request): """Show the form to export data""" @@ -62,7 +69,6 @@ def home(request): file_name += u'ALL_' - if range == 'thismonth': start_date = now() + relativedelta(day=1, minute=0, hour=0, second=0, microsecond=0) end_date = now() + relativedelta(day=1, months=+1, seconds=-1, minute=0, hour=0, second=0, microsecond=0) @@ -83,7 +89,6 @@ def home(request): file_name += start_date.strftime('%Y-%m-%d_%H.%M.%S') + u' - ' + end_date.strftime('%Y-%m-%d_%H.%M.%S') - # Commong for json and xml data = [dict(tr.dump_api().items() + {'logs': [log.dump_api() for log in tr.transactionlog_set.order_by('when').all()]}.items()) for tr in transactions] @@ -97,7 +102,6 @@ def home(request): elif file_type == 'xml': - response = HttpResponse(dict2xml({'export': data}, pretty=True), content_type='text/xml') response['Content-Disposition'] = 'attachment; filename="%s.xml"' % (file_name, ) @@ -107,7 +111,7 @@ def home(request): elif file_type == 'csv': response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="%s.csv"' % (file_name, ) + response['Content-Disposition'] = 'attachment; filename="%s.csv"' % (file_name, ) writer = csv.writer(response) @@ -126,9 +130,122 @@ def home(request): return response - - else: form = ExportForm(request.user) return render_to_response('export/home.html', {'form': form}, context_instance=RequestContext(request)) + + +@login_required +@user_passes_test(lambda u: u.is_superuser) +def summary(request): + """Display a summary for configs, regrouped by groups""" + + if request.method == 'POST': # If the form has been submitted... + form = SummaryForm(request.POST) + + if form.is_valid(): # If the form is valid + + include_test = form.cleaned_data['include_test'] + range = form.cleaned_data['range'] + + if range == 'thismonth': + start_date = now() + relativedelta(day=1, minute=0, hour=0, second=0, microsecond=0) + end_date = now() + relativedelta(day=1, months=+1, seconds=-1, minute=0, hour=0, second=0, microsecond=0) + elif range == 'previousmonth': + start_date = now() + relativedelta(day=1, months=-1, minute=0, hour=0, second=0, microsecond=0) + end_date = now() + relativedelta(day=1, seconds=-1, minute=0, hour=0, second=0, microsecond=0) + elif range == 'sincemonth': + start_date = now() + relativedelta(months=-1) + end_date = now() + elif range == 'thisyear': + start_date = now() + relativedelta(day=1, month=1, minute=0, hour=0, second=0, microsecond=0) + end_date = now() + relativedelta(day=1, month=1, years=+1, seconds=-1, minute=0, hour=0, second=0, microsecond=0) + elif range == 'sinceyear': + start_date = now() + relativedelta(years=-1) + end_date = now() + + months = [] + + # Let's build the list of perids, by month, to sum transaction + current_date = start_date + relativedelta(minute=0, hour=0, second=0, microsecond=0) + + # While we don't have all dates + while current_date < end_date: + + # The end is the current date, + one month, minus one second, with the day forced to one + tmp_end_Date = current_date + relativedelta(months=+1, seconds=-1, day=1) + + # The next current date is +one month, at the start of the month + next_current = current_date + relativedelta(months=+1, day=1) + + # If we are going to far, stoè + if tmp_end_Date > end_date: + tmp_end_Date = end_date + + months.append((current_date, tmp_end_Date)) + + current_date = next_current + + #transactions = transactions.filter(creation_date__gte=start_date, creation_date__lt=end_date).all() + + data = {} + + base_config_filter = Config.objects.filter(admin_enable=True).exclude(group=None) + + if not include_test: + base_config_filter = base_config_filter.exclude(test_mode=True) + + for group in base_config_filter.order_by('group').values('group').distinct(): + + groupName = group['group'] + + data_group = [] + + group_total = 0 + + for config in base_config_filter.filter(group=groupName).all(): + + data_config = [] + total = 0 + + for (sub_start_date, sub_end_date) in months: + query_sum = config.transaction_set.filter(postfinance_status='9', creation_date__gte=sub_start_date, creation_date__lt=sub_end_date).all().aggregate(Sum('amount')) + + current_sum = query_sum['amount__sum'] + + if not current_sum: + current_sum = '0' + + total_month = int(current_sum) + + data_config.append((sub_start_date, sub_end_date, float(total_month) / 100.0, config)) + + total += total_month # Sum in and not floats ! + + data_config.append(float(total) / 100.0) + + data_group.append(data_config) + + group_total += total + + data[groupName] = (data_group, float(group_total) / 100.0) + + template = get_template('export/summary_pdf.html') + context = Context({'data': data, 'months': months, 'start_date': start_date, 'end_date': end_date}) + html = template.render(context) + result = StringIO.StringIO() + + pdf = pisa.pisaDocument(StringIO.StringIO(html.encode("ISO-8859-1")), result) + if not pdf.err: + response = HttpResponse(result.getvalue(), mimetype='application/pdf') + file_name = 'Summary_' + start_date.strftime('%Y-%m-%d_%H.%M.%S') + u' - ' + end_date.strftime('%Y-%m-%d_%H.%M.%S') + response['Content-Disposition'] = 'attachment; filename="%s.pdf"' % (file_name, ) + return response + + return HttpResponse('We had some errors
%s
' % escape(html)) + + else: + form = SummaryForm() + + return render_to_response('export/summary.html', {'form': form}, context_instance=RequestContext(request)) diff --git a/server/templates/base.html b/server/templates/base.html index e32aab4..73eb877 100644 --- a/server/templates/base.html +++ b/server/templates/base.html @@ -54,6 +54,7 @@ +