Fix "add many" and csv import and export

This commit is contained in:
Jan Tuomi
2017-05-18 13:28:30 +03:00
parent da86c411a6
commit 80313a7765
9 changed files with 200 additions and 166 deletions
+7 -1
View File
@@ -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']
+60 -1
View File
@@ -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)
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]),
)
+6
View File
@@ -206,4 +206,10 @@ input {
.readonly {
pointer-events: none;
}
.large-textarea {
width: 100%;
max-width: 100%;
height: 10rem !important;
}
+16 -32
View File
@@ -1,40 +1,24 @@
{% extends "members_base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block content %}
<script>
var memberId = {{ member_id }};
</script>
<div ng-controller="applEditController">
<h1> Muokkaa hakemuksen jäsentietoja </h1>
<div>
<div>
<h3>{% trans "Edit application" %}</h3>
</div>
<div id="input_form">
<form name="applicationForm">
<div class="form-group">
<label>Etunimi: </label>
<input id="firstNameField" required type="text" placeholder="Sähkö" class="form-control" ng-model="member.first_name"></input>
</div>
<div class="form-group">
<label>Sukunimi: </label>
<input id="lastNameField" required type="text" placeholder="Insinööri" class="form-control" ng-model="member.last_name"></input>
</div>
<div class="form-group">
<label>Sähköposti: </label>
<input id="emailField" required type="text" placeholder="sahko.insinoori@aalto.fi" class="form-control" ng-model="member.email"></input>
</div>
<div class="form-group">
<label>AYY jäsen: </label>
<input type="checkbox" id="AYY" ng-model="member.AYY"></input>
</div>
<div class="form-group">
<label>JAS-listaan: </label>
<input type="checkbox" id="jas" ng-model="member.jas"></input>
</div>
<div class="form-group">
<label>Asuinkunta: </label>
<input id="PORField" required type="text" placeholder="Otaniemi" class="form-control" ng-model="member.POR"></input>
</div>
<button class="btn btn-success" ng-click="applicationForm.$valid && sendappl()" type="submit" id="sendmember">Tallenna</button>
<button class="btn btn-warning" ng-click="cancelappl()" type="submit" id="sendmember">Peruuta</button>
<form name="applicationForm" action="/members/accept_application" method="post" class="form">{% csrf_token %}
<input type="hidden" name="id" value="{{ application_id }}">
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">
{% trans "Accept" %}
</button>
<a href="/members/delete_application_confirm/{{ application_id }}" class="btn btn-danger">{% trans "Decline" %}</a>
{% endbuttons %}
</form>
</div>
</div>
+26 -21
View File
@@ -1,31 +1,36 @@
{% extends "members_base.html" %}
{% load i18n %}
{% block content %}
<div class="container" style="align:middle;" ng-controller="addManyController">
<div class="instructions">
<div>
<div>
<h3> Lisää useampi jäsen </h3>
<h5>
Syötä jäsentiedot pilkuilla erotettuina formaatissa <b>Etunimi, Sukunimi, Sähköposti, Asuinkunta, AYY-jäsen(0 tai 1), JAS-listaan(0 tai 1)</b>
<br>
<br>
Erota jäsenet rivinvaihdoilla toisistaan.
</h5>
<!--<img src="/static/img/members_example.png" alt="exampleImg" style="max-width:100%; height:auto; margin-bottom:10px; opacity:0.7;">-->
</div>
<form name="memberTextForm">
<div class="row">
<div class="col-sm-4">
<textarea ng-model="memberData" rows=5 cols=80 placeholder="Salli, Vahvonen, salli.vahvonen@notmail.dom, Kerava, 0, 0" style="border:solid 3px #c9c9c9; transition:box-shadow 0.3s, border 0.3s; align:middle;"></textarea>
</div>
</div>
<div class="row">
<div class="col-sm-4">
<button ng-click="sendCSV()" type="submit" class="btn btn-success" id="sendMembers">Lähetä</button>
</div>
</div>
<div id="footer-div"/>
<div>
<p>
{% blocktrans %}
Enter member information in CSV format, separate members on separate lines.
{% endblocktrans %}
</p>
<p>
{% blocktrans %}
first_name, last_name, email_address and place_of_origin should be given string values.
ayy_member and jas_recipient should be given the value 0 (off) or 1 (on).
{% endblocktrans %}
</p>
<h4>{% trans "Syntax" %}</h4>
<pre>first_name, last_name, email_address, place_of_origin, ayy_member, jas_recipient</pre>
</div>
<form name="memberTextForm" action="/members/import_csv" method="POST">{% csrf_token %}
<div>
<textarea name="textfield" class="form-control large-textarea" placeholder="Teemu, Teekkari, teemu.teekkari@notmail.dom, Otaniemi, 0, 0"></textarea>
</div>
<div>
<button type="submit" class="btn btn-primary">{% trans "Send" %}</button>
</div>
</form>
</div>
{% endblock content %}
+1 -43
View File
@@ -20,49 +20,7 @@
{{ table|safe }}
<div>
<a id="download-csv" class="btn btn-info">{% trans "Download CSV" %}</a>
</div>
<form action="/members/list" method="POST" id="collapse-filters" class="collapse filter-form">
<div class="filter-row">
<div class="filter-group">
<div class="filter-field">
<input class="form-control" type="text" id="search-filter" placeholder="{% trans "Search" %}" >
</div>
</div>
<div class="filter-group">
<div class="filter-field">
<h5>{% trans "Added after" %}</h5>
<input type="datetime-local" id="addedAfterDatePicker">
</div>
<div class="filter-field">
<h5>{% trans "Added before" %}</h5>
<input type="datetime-local" class="filter-field" id="addedBeforeDatePicker">
</div>
</div>
<div class="filter-group">
<div class="filter-field">
<h5>{% trans "Paid after" %}</h5>
<input type="datetime-local" class="filter-field" id="paidAfterDatePicker">
</div>
<div class="filter-field">
<h5>{% trans "Paid before" %}</h5>
<input type="datetime-local" class="filter-field" id="paidBeforeDatePicker">
</div>
</div>
<div class="filter-group">
<div class="filter-field">
<input type="button" value="{% trans "Filter" %}" class="filter-button btn btn-success">
<input type="button" value="{% trans "Reset" %}" class="filter-button btn btn-warning">
</div>
</div>
</div>
</form>
<div>
<a id="filter-collapser" href="#collapse-filters" data-toggle="collapse" class="btn btn-info">
{% trans "Show filters" %}
</a>
<a href="/members/export_csv" class="btn btn-info">{% trans "Download CSV" %}</a>
</div>
</div>
{% endblock content %}
-46
View File
@@ -15,51 +15,5 @@
{% endif %}
{{ table|safe }}
<div>
<a id="download-csv" class="btn btn-info">{% trans "Download CSV" %}</a>
</div>
<form action="/members/list" method="POST" id="collapse-filters" class="collapse filter-form">
<div class="filter-row">
<div class="filter-group">
<div class="filter-field">
<input class="form-control" type="text" id="search-filter" placeholder="{% trans "Search" %}" >
</div>
</div>
<div class="filter-group">
<div class="filter-field">
<h5>{% trans "Added after" %}</h5>
<input type="datetime-local" id="addedAfterDatePicker">
</div>
<div class="filter-field">
<h5>{% trans "Added before" %}</h5>
<input type="datetime-local" class="filter-field" id="addedBeforeDatePicker">
</div>
</div>
<div class="filter-group">
<div class="filter-field">
<h5>{% trans "Paid after" %}</h5>
<input type="datetime-local" class="filter-field" id="paidAfterDatePicker">
</div>
<div class="filter-field">
<h5>{% trans "Paid before" %}</h5>
<input type="datetime-local" class="filter-field" id="paidBeforeDatePicker">
</div>
</div>
<div class="filter-group">
<div class="filter-field">
<input type="button" value="{% trans "Filter" %}" class="filter-button btn btn-success">
<input type="button" value="{% trans "Reset" %}" class="filter-button btn btn-warning">
</div>
</div>
</div>
</form>
<div>
<a id="filter-collapser" href="#collapse-filters" data-toggle="collapse" class="btn btn-info">
{% trans "Show filters" %}
</a>
</div>
</div>
{% endblock content %}
+13 -1
View File
@@ -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<index>\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),
]
+71 -21
View File
@@ -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)