From 80313a776539506cabf26a0eb41b1f2e59b8db04 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Thu, 18 May 2017 13:28:30 +0300 Subject: [PATCH] Fix "add many" and csv import and export --- members/forms.py | 8 ++- members/models.py | 61 +++++++++++++++- members/static/css/members.css | 6 ++ members/templates/application_edit.html | 48 +++++-------- members/templates/member_add_many.html | 47 +++++++------ members/templates/member_list.html | 44 +----------- members/templates/payment_list.html | 46 ------------- members/urls.py | 14 +++- members/views.py | 92 +++++++++++++++++++------ 9 files changed, 200 insertions(+), 166 deletions(-) diff --git a/members/forms.py b/members/forms.py index efe6825..7eb19b8 100644 --- a/members/forms.py +++ b/members/forms.py @@ -1,6 +1,6 @@ from django import forms -from members.models import Member, Payment +from members.models import Member, Payment, Request class MemberForm(forms.ModelForm): @@ -15,3 +15,9 @@ class PaymentForm(forms.ModelForm): class Meta: model = Payment fields = ['date', 'source', 'member'] + +class ApplicationForm(forms.ModelForm): + + class Meta: + model = Request + fields = ['first_name', 'last_name', 'email', 'AYY', 'jas', 'POR'] \ No newline at end of file diff --git a/members/models.py b/members/models.py index 2fc005a..b15e318 100644 --- a/members/models.py +++ b/members/models.py @@ -3,6 +3,11 @@ from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from datetime import datetime +import csv +import logging + +memberlogger = logging.getLogger(__name__) + class BaseMember(models.Model): ''' @@ -21,6 +26,41 @@ class BaseMember(models.Model): def __str__(self): return "{} {}, {}".format(self.last_name, self.first_name, self.email) + @staticmethod + def from_csv(data): + print("Imported CSV data: {}".format(data)) + clean_data = data.strip().split('\n') + csv_reader = csv.reader(clean_data) + + members = [] + for line in csv_reader: + try: + line = list(map(lambda x: x.strip(), line)) + print(line) + + member = Member.from_array([ + line[0], line[1], line[2], line[3], + bool(int(line[4])), bool(int(line[5])) + ]) + members.append(member) + except: + return False + + for member in members: + member.save() + + return True + + def as_array(self): + return [ + self.first_name, + self.last_name, + self.email, + self.POR, + int(self.AYY), + int(self.jas) + ] + class Request(BaseMember): ''' @@ -28,6 +68,11 @@ class Request(BaseMember): ''' submitted = models.DateTimeField(default=timezone.now) + def to_member(self): + member = Member.from_array(self.as_array()) + + return member + class Payment(models.Model): ''' @@ -50,4 +95,18 @@ class Member(BaseMember): ''' Member model represets one member on the registry. ''' - created = models.DateTimeField(default=timezone.now) \ No newline at end of file + created = models.DateTimeField(default=timezone.now) + + @staticmethod + def from_array(array): + if len(array) != 6: + raise Exception("Invalid array length for member instantiation") + + return Member.objects.create( + first_name=array[0], + last_name=array[1], + email=array[2], + POR=array[3], + AYY=bool(array[4]), + jas=bool(array[5]), + ) \ No newline at end of file diff --git a/members/static/css/members.css b/members/static/css/members.css index 0d1ad4b..5f560f5 100644 --- a/members/static/css/members.css +++ b/members/static/css/members.css @@ -206,4 +206,10 @@ input { .readonly { pointer-events: none; +} + +.large-textarea { + width: 100%; + max-width: 100%; + height: 10rem !important; } \ No newline at end of file diff --git a/members/templates/application_edit.html b/members/templates/application_edit.html index 14a5dda..1728d8e 100644 --- a/members/templates/application_edit.html +++ b/members/templates/application_edit.html @@ -1,40 +1,24 @@ {% extends "members_base.html" %} +{% load i18n %} +{% load bootstrap3 %} + {% block content %} - -
-

Muokkaa hakemuksen jäsentietoja

+
+
+

{% trans "Edit application" %}

+
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- - + {% csrf_token %} + + {% bootstrap_form form %} + {% buttons %} + + {% trans "Decline" %} + {% endbuttons %}
diff --git a/members/templates/member_add_many.html b/members/templates/member_add_many.html index 72449ec..fc083eb 100644 --- a/members/templates/member_add_many.html +++ b/members/templates/member_add_many.html @@ -1,31 +1,36 @@ {% extends "members_base.html" %} +{% load i18n %} + {% block content %} -
-
+
+

Lisää useampi jäsen

-
- Syötä jäsentiedot pilkuilla erotettuina formaatissa Etunimi, Sukunimi, Sähköposti, Asuinkunta, AYY-jäsen(0 tai 1), JAS-listaan(0 tai 1) -
-
- Erota jäsenet rivinvaihdoilla toisistaan. -
-
-
-
-
- -
-
-
-
- -
-
-
{% endblock content %} diff --git a/members/templates/member_list.html b/members/templates/member_list.html index 084a9a7..221764f 100644 --- a/members/templates/member_list.html +++ b/members/templates/member_list.html @@ -20,49 +20,7 @@ {{ table|safe }} - -
-
-
-
- -
-
-
-
-
{% trans "Added after" %}
- -
-
-
{% trans "Added before" %}
- -
-
-
-
-
{% trans "Paid after" %}
- -
-
-
{% trans "Paid before" %}
- -
-
-
-
- - -
-
-
-
- -
{% endblock content %} diff --git a/members/templates/payment_list.html b/members/templates/payment_list.html index ddb12f9..780e0cb 100644 --- a/members/templates/payment_list.html +++ b/members/templates/payment_list.html @@ -15,51 +15,5 @@ {% endif %} {{ table|safe }} - -
- {% trans "Download CSV" %} -
- -
-
-
-
- -
-
-
-
-
{% trans "Added after" %}
- -
-
-
{% trans "Added before" %}
- -
-
-
-
-
{% trans "Paid after" %}
- -
-
-
{% trans "Paid before" %}
- -
-
-
-
- - -
-
-
-
- -
- - {% trans "Show filters" %} - -
{% endblock content %} diff --git a/members/urls.py b/members/urls.py index f8a1388..b6fcbb5 100644 --- a/members/urls.py +++ b/members/urls.py @@ -2,7 +2,8 @@ from django.conf.urls import url from django.views.generic.base import RedirectView # members -from members.views import member_list, payment_add, payment_submit +from members.views import member_list, payment_add, payment_submit, application_delete_confirm, application_delete, \ + application_accept, import_csv, export_csv from members.views import settings_page, payment_edit from members.views import payment_delete_confirm from members.views import payment_delete, payment_update @@ -54,6 +55,8 @@ urlpatterns = [ url(r'^submit_payment$', payment_submit), url(r'^update_payment$', payment_update), url(r'^delete_payment$', payment_delete), + url(r'^accept_application$', application_accept), + url(r'^delete_application$', application_delete), # the actual member application form url(r'^application/$', application_form), @@ -61,6 +64,9 @@ urlpatterns = [ # success page for the application url(r'^application/success$', application_form_success), + # delete confirmation view for applications + url(r'^delete_application_confirm/(?P\d+)$', application_delete_confirm), + # list all payment events url(r'^payments$', payment_list), @@ -76,6 +82,12 @@ urlpatterns = [ # settings page url(r'^settings$', settings_page), + # send CSV member data by POST + url(r'^import_csv', import_csv), + + # download CSV member data + url(r'^export_csv', export_csv), + # favourite icon url(r'^favicon\.ico$', favicon_view), ] diff --git a/members/views.py b/members/views.py index 21870ea..5aa00bc 100644 --- a/members/views.py +++ b/members/views.py @@ -11,9 +11,10 @@ import json import requests import logging import html +import csv from members.models import Member, Request, Payment -from members.forms import MemberForm, PaymentForm +from members.forms import MemberForm, PaymentForm, ApplicationForm # Logger function, you can use the same idea when implementing other loggers to other apps from members.tables import MemberTable, PaymentTable, RequestTable @@ -169,7 +170,7 @@ def member_delete(request, *args, **kwargs): try: id = request.POST['id'] except KeyError: - return HttpResponse(401) + return render(request, 'error.html', {'error': _('No member id specified')}) try: member = Member.objects.get(id=id) @@ -224,7 +225,61 @@ def application_edit(request, *args, **kwargs): if i is None: return render(request, 'error.html', {'error': _('No application id specified')}) else: - return render(request, 'application_edit.html', {'member_id': i}) + application = Request.objects.get(id=i) + form = ApplicationForm(instance=application) + return render(request, 'application_edit.html', {'application_id': i, 'form': form}) + + +@ensure_csrf_cookie +@require_http_methods(["POST"]) +@permission_required('members.change_member', login_url='/login') +def application_accept(request, *args, **kwargs): + form = ApplicationForm(request.POST) + if form.is_valid(): + id = request.POST['id'] + application = Request.objects.get(id=id) + + member = application.to_member() + member.save() + application.delete() + + memberlogger.info("Accepted application in member register with the following info: {}".format(form)) + notification = "{} {}.".format(_("Successfully accepted application"), str(application)) + return HttpResponseRedirect('/members/list?notification={}'.format(html.escape(notification))) + else: + return render(request, 'error.html', {'error': _('Could not accept application object')}) + + +@ensure_csrf_cookie +@require_http_methods(["POST"]) +@permission_required('members.change_member', login_url='/login') +def application_delete(request, *args, **kwargs): + try: + id = request.POST['id'] + except KeyError: + return render(request, 'error.html', {'error': _('No application id specified')}) + + try: + application = Request.objects.get(id=id) + notification = "{} {}.".format(_("Successfully deleted application"), str(application)) + application.delete() + memberlogger.info("Delete application in member register with the following id: {}".format(id)) + return HttpResponseRedirect('/members/applications?notification={}'.format(html.escape(notification))) + except: + return render(request, 'error.html', {'error': _('Could not delete application object')}) + + +@ensure_csrf_cookie +@require_http_methods(["GET"]) +@permission_required('members.change_member', login_url='/login') +def application_delete_confirm(request, *args, **kwargs): + i = kwargs.pop('index', None) + if i is None: + return render(request, 'error.html', {'error': _('No application id specified')}) + else: + application = Request.objects.get(id=i) + form = ApplicationForm(instance=application) + return render(request, 'application_delete_confirm.html', {'application_id': i, 'form': form}) @ensure_csrf_cookie @@ -315,7 +370,7 @@ def payment_delete(request, *args, **kwargs): try: id = request.POST['id'] except KeyError: - return HttpResponse(401) + return render(request, 'error.html', {'error': _('No payment id specified')}) try: payment = Payment.objects.get(id=id) @@ -355,29 +410,25 @@ def settings_page(request, *args, **kwargs): @ensure_csrf_cookie @require_http_methods(["POST"]) @permission_required('members.change_member', login_url='/login') -def csv_import(request, *args, **kwargs): - data = request.body.decode("utf-8") - +def import_csv(request, *args, **kwargs): try: - payload = json.loads(data) + data = request.POST['textfield'] except: - return HttpResponse(json.dumps({'error': 'Malformed request'}), 400) + return render(request, 'error.html', {'error': _('Missing "textfield" POST request field')}) - resp_data = Member.import_csv(payload['csv']) - resp = HttpResponse(json.dumps(resp_data)) - if resp_data['status'] == 'failure': - resp.status_code = 400 - memberlogger.warning('POST request failed with status code {}'.format(resp.status_code)) - - return resp + success = Member.from_csv(data) + if success: + memberlogger.info('Imported CSV data:\n'.format(data)) + notification = "{}.".format(_("Successfully imported multiple members")) + return HttpResponseRedirect('/members/list?notification={}'.format(html.escape(notification))) + else: + return render(request, 'error.html', {'error': _('Failed to import members')}) @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def export_csv(request, *args, **kwargs): - import csv - response = HttpResponse() response['Content-type'] = 'text/csv' response['Accept'] = 'text/csv' @@ -385,9 +436,8 @@ def export_csv(request, *args, **kwargs): writer = csv.writer(response, csv.excel) response.write(u'\ufeff'.encode('utf8')) # BOM (optional...Excel needs it to open UTF-8 file properly) for obj in Member.objects.all(): - data = obj.get_dict() - field_list = map(lambda s: str(data[s]), - ['id', 'first_name', 'last_name', 'email', 'POR', 'AYY', 'jas', 'created', 'paid']) + data = obj.as_array() + field_list = map(lambda d: str(d), data) writer.writerow(field_list)