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 django import forms
from members.models import Member, Payment from members.models import Member, Payment, Request
class MemberForm(forms.ModelForm): class MemberForm(forms.ModelForm):
@@ -15,3 +15,9 @@ class PaymentForm(forms.ModelForm):
class Meta: class Meta:
model = Payment model = Payment
fields = ['date', 'source', 'member'] 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 django.utils.translation import ugettext_lazy as _
from datetime import datetime from datetime import datetime
import csv
import logging
memberlogger = logging.getLogger(__name__)
class BaseMember(models.Model): class BaseMember(models.Model):
''' '''
@@ -21,6 +26,41 @@ class BaseMember(models.Model):
def __str__(self): def __str__(self):
return "{} {}, {}".format(self.last_name, self.first_name, self.email) 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): class Request(BaseMember):
''' '''
@@ -28,6 +68,11 @@ class Request(BaseMember):
''' '''
submitted = models.DateTimeField(default=timezone.now) submitted = models.DateTimeField(default=timezone.now)
def to_member(self):
member = Member.from_array(self.as_array())
return member
class Payment(models.Model): class Payment(models.Model):
''' '''
@@ -50,4 +95,18 @@ class Member(BaseMember):
''' '''
Member model represets one member on the registry. 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 { .readonly {
pointer-events: none; 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" %} {% extends "members_base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block content %} {% block content %}
<script> <div>
var memberId = {{ member_id }}; <div>
</script> <h3>{% trans "Edit application" %}</h3>
<div ng-controller="applEditController"> </div>
<h1> Muokkaa hakemuksen jäsentietoja </h1>
<div id="input_form"> <div id="input_form">
<form name="applicationForm"> <form name="applicationForm" action="/members/accept_application" method="post" class="form">{% csrf_token %}
<div class="form-group"> <input type="hidden" name="id" value="{{ application_id }}">
<label>Etunimi: </label> {% bootstrap_form form %}
<input id="firstNameField" required type="text" placeholder="Sähkö" class="form-control" ng-model="member.first_name"></input> {% buttons %}
</div> <button type="submit" class="btn btn-primary">
<div class="form-group"> {% trans "Accept" %}
<label>Sukunimi: </label> </button>
<input id="lastNameField" required type="text" placeholder="Insinööri" class="form-control" ng-model="member.last_name"></input> <a href="/members/delete_application_confirm/{{ application_id }}" class="btn btn-danger">{% trans "Decline" %}</a>
</div> {% endbuttons %}
<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> </form>
</div> </div>
</div> </div>
+26 -21
View File
@@ -1,31 +1,36 @@
{% extends "members_base.html" %} {% extends "members_base.html" %}
{% load i18n %}
{% block content %} {% block content %}
<div class="container" style="align:middle;" ng-controller="addManyController"> <div>
<div class="instructions"> <div>
<h3> Lisää useampi jäsen </h3> <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> </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> </form>
</div> </div>
{% endblock content %} {% endblock content %}
+1 -43
View File
@@ -20,49 +20,7 @@
{{ table|safe }} {{ table|safe }}
<div> <div>
<a id="download-csv" class="btn btn-info">{% trans "Download CSV" %}</a> <a href="/members/export_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>
</div> </div>
{% endblock content %} {% endblock content %}
-46
View File
@@ -15,51 +15,5 @@
{% endif %} {% endif %}
{{ table|safe }} {{ 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> </div>
{% endblock content %} {% endblock content %}
+13 -1
View File
@@ -2,7 +2,8 @@ from django.conf.urls import url
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
# members # 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 settings_page, payment_edit
from members.views import payment_delete_confirm from members.views import payment_delete_confirm
from members.views import payment_delete, payment_update from members.views import payment_delete, payment_update
@@ -54,6 +55,8 @@ urlpatterns = [
url(r'^submit_payment$', payment_submit), url(r'^submit_payment$', payment_submit),
url(r'^update_payment$', payment_update), url(r'^update_payment$', payment_update),
url(r'^delete_payment$', payment_delete), url(r'^delete_payment$', payment_delete),
url(r'^accept_application$', application_accept),
url(r'^delete_application$', application_delete),
# the actual member application form # the actual member application form
url(r'^application/$', application_form), url(r'^application/$', application_form),
@@ -61,6 +64,9 @@ urlpatterns = [
# success page for the application # success page for the application
url(r'^application/success$', application_form_success), 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 # list all payment events
url(r'^payments$', payment_list), url(r'^payments$', payment_list),
@@ -76,6 +82,12 @@ urlpatterns = [
# settings page # settings page
url(r'^settings$', 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 # favourite icon
url(r'^favicon\.ico$', favicon_view), url(r'^favicon\.ico$', favicon_view),
] ]
+71 -21
View File
@@ -11,9 +11,10 @@ import json
import requests import requests
import logging import logging
import html import html
import csv
from members.models import Member, Request, Payment 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 # Logger function, you can use the same idea when implementing other loggers to other apps
from members.tables import MemberTable, PaymentTable, RequestTable from members.tables import MemberTable, PaymentTable, RequestTable
@@ -169,7 +170,7 @@ def member_delete(request, *args, **kwargs):
try: try:
id = request.POST['id'] id = request.POST['id']
except KeyError: except KeyError:
return HttpResponse(401) return render(request, 'error.html', {'error': _('No member id specified')})
try: try:
member = Member.objects.get(id=id) member = Member.objects.get(id=id)
@@ -224,7 +225,61 @@ def application_edit(request, *args, **kwargs):
if i is None: if i is None:
return render(request, 'error.html', {'error': _('No application id specified')}) return render(request, 'error.html', {'error': _('No application id specified')})
else: 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 @ensure_csrf_cookie
@@ -315,7 +370,7 @@ def payment_delete(request, *args, **kwargs):
try: try:
id = request.POST['id'] id = request.POST['id']
except KeyError: except KeyError:
return HttpResponse(401) return render(request, 'error.html', {'error': _('No payment id specified')})
try: try:
payment = Payment.objects.get(id=id) payment = Payment.objects.get(id=id)
@@ -355,29 +410,25 @@ def settings_page(request, *args, **kwargs):
@ensure_csrf_cookie @ensure_csrf_cookie
@require_http_methods(["POST"]) @require_http_methods(["POST"])
@permission_required('members.change_member', login_url='/login') @permission_required('members.change_member', login_url='/login')
def csv_import(request, *args, **kwargs): def import_csv(request, *args, **kwargs):
data = request.body.decode("utf-8")
try: try:
payload = json.loads(data) data = request.POST['textfield']
except: 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']) success = Member.from_csv(data)
resp = HttpResponse(json.dumps(resp_data)) if success:
if resp_data['status'] == 'failure': memberlogger.info('Imported CSV data:\n'.format(data))
resp.status_code = 400 notification = "{}.".format(_("Successfully imported multiple members"))
memberlogger.warning('POST request failed with status code {}'.format(resp.status_code)) return HttpResponseRedirect('/members/list?notification={}'.format(html.escape(notification)))
else:
return resp return render(request, 'error.html', {'error': _('Failed to import members')})
@ensure_csrf_cookie @ensure_csrf_cookie
@require_http_methods(["GET"]) @require_http_methods(["GET"])
@permission_required('members.change_member', login_url='/login') @permission_required('members.change_member', login_url='/login')
def export_csv(request, *args, **kwargs): def export_csv(request, *args, **kwargs):
import csv
response = HttpResponse() response = HttpResponse()
response['Content-type'] = 'text/csv' response['Content-type'] = 'text/csv'
response['Accept'] = 'text/csv' response['Accept'] = 'text/csv'
@@ -385,9 +436,8 @@ def export_csv(request, *args, **kwargs):
writer = csv.writer(response, csv.excel) writer = csv.writer(response, csv.excel)
response.write(u'\ufeff'.encode('utf8')) # BOM (optional...Excel needs it to open UTF-8 file properly) response.write(u'\ufeff'.encode('utf8')) # BOM (optional...Excel needs it to open UTF-8 file properly)
for obj in Member.objects.all(): for obj in Member.objects.all():
data = obj.get_dict() data = obj.as_array()
field_list = map(lambda s: str(data[s]), field_list = map(lambda d: str(d), data)
['id', 'first_name', 'last_name', 'email', 'POR', 'AYY', 'jas', 'created', 'paid'])
writer.writerow(field_list) writer.writerow(field_list)