Merge branch 'develop' into feature-pep8-fixes

This commit is contained in:
Jan Tuomi
2017-09-20 23:32:41 +03:00
16 changed files with 205 additions and 155 deletions
+2 -2
View File
@@ -48,10 +48,10 @@ def on_message(client, userdata, msg):
def on_disconnect(client, userdata, rc):
if rc != 0:
print("Unexpected disconnection.")
logging.warning("MQTT unexpectedly disconnected.")
else:
client.loop_stop(force=False)
print("Disconnected")
logging.warning("MQTT disconnected.")
def get_latest():
-2
View File
@@ -184,8 +184,6 @@ def create_image_item(request, *args, **kwargs):
def create_video_item(request, *args, **kwargs):
"""Create video Infoscreen item."""
form = UploadFileForm(request.POST, request.FILES)
print(form.errors)
print("hurdurr")
if not form.is_valid():
return HttpResponseBadRequest('{"status": "failure",'
'"error": "invalid data supplied"}')
+70 -1
View File
@@ -5,6 +5,15 @@ from django.utils.translation import ugettext_lazy as _
from members.models import Member, Payment, Request
import csv
import datetime
import logging
class CSVValidationError(Exception):
def __init__(self, form_errors):
self.form_errors = form_errors
class MemberForm(forms.ModelForm):
"""Member model form."""
@@ -13,7 +22,67 @@ class MemberForm(forms.ModelForm):
"""Meta for Member model form."""
model = Member
fields = ['first_name', 'last_name', 'email', 'AYY', 'jas', 'POR']
fields = ['first_name', 'last_name', 'email', 'POR', 'AYY', 'jas']
class ImportResult:
def __init__(self, members, payments):
self.members = members
self.payments = payments
def clean_email(self):
email = self.cleaned_data['email']
if Member.objects.filter(email=email).exists():
raise forms.ValidationError('Member with email "{}" already exists.'.format(email), code='exists')
return email
def clean_jas(self):
return bool(int(self.data['jas']))
def clean_AYY(self):
return bool(int(self.data['AYY']))
@staticmethod
def csv_to_models(data, payment_source='AYY'):
clean_data = data.strip().split('\n')
clean_data = [row.rstrip(',') for row in clean_data]
csv_reader = csv.DictReader(clean_data, fieldnames=MemberForm.Meta.fields)
members = []
payments = []
for line in csv_reader:
for key, value in line.items():
line[key] = value.strip()
email = line['email']
member_exists = False
if Member.objects.filter(email=email).exists():
member_exists = True
if not member_exists:
form = MemberForm(line)
if not form.is_valid():
raise CSVValidationError(form.errors)
model = form.save(commit=False)
members.append(model)
else:
member = Member.objects.get(email=email)
payment_data = {
'source': payment_source,
'member': member.id,
'date': datetime.datetime.now(),
}
form = PaymentForm(payment_data)
if not form.is_valid():
raise CSVValidationError(form.errors)
model = form.save(commit=False)
payments.append(model)
return MemberForm.ImportResult(members, payments)
class PaymentForm(forms.ModelForm):
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-09-20 11:57
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0013_auto_20170601_1822'),
]
operations = [
migrations.AlterField(
model_name='member',
name='email',
field=models.EmailField(max_length=254, unique=True, verbose_name='Email'),
),
migrations.AlterField(
model_name='request',
name='email',
field=models.EmailField(max_length=254, unique=True, verbose_name='Email'),
),
]
+1 -3
View File
@@ -13,7 +13,7 @@ class BaseMember(models.Model):
first_name = models.CharField(_("First name"), max_length=127)
last_name = models.CharField(_("Last name"), max_length=127)
email = models.EmailField(_("Email"))
email = models.EmailField(_("Email"), unique=True)
POR = models.CharField(_("Place of residence"),
max_length=255) # place of residence
AYY = models.BooleanField(_("AYY"), default=False)
@@ -30,8 +30,6 @@ class BaseMember(models.Model):
@staticmethod
def from_csv(data):
"""Construct member model from csv data."""
print("Imported CSV data: {}".format(data))
clean_data = data.strip().split('\n')
csv_reader = csv.reader(clean_data)
+1 -1
View File
@@ -9,7 +9,7 @@
</div>
<div class="alert alert-danger">
{{ error }}
{{ error|safe }}
</div>
<div>
<button onclick="window.history.back();" class="btn btn-primary">{% trans "Back" %}</button>
+11 -2
View File
@@ -5,7 +5,7 @@
{% block content %}
<div>
<div>
<h3> Lisää useampi jäsen </h3>
<h3>{% trans "Add many members" %}</h3>
</div>
<div>
@@ -25,9 +25,18 @@
</div>
<form name="memberTextForm" action="/members/import_csv" method="POST">{% csrf_token %}
<div>
<div class="form-group">
<label>{% trans "Data" %}</label>
<textarea name="textfield" class="form-control large-textarea" placeholder="Teemu, Teekkari, teemu.teekkari@notmail.dom, Otaniemi, 0, 0"></textarea>
</div>
<div class="form-group">
<label>{% trans "Payment source" %}</label>
<select name="payment_source" class="form-control">
<option value="AYY">{% trans "AYY" %}</option>
<option value="bank_transfer">{% trans "Bank transfer" %}</option>
<option value="cash">{% trans "Cash payment" %}</option>
</select>
</div>
<div>
<button type="submit" class="btn btn-primary">{% trans "Send" %}</button>
</div>
@@ -0,0 +1,26 @@
{% extends "members_base.html" %}
{% load i18n %}
{% block content %}
<div>
<div>
<h3>{% trans "Confirm adding these entries?" %}</h3>
</div>
<div class="form-group">
<label>{% trans "Members" %}</label>
{{ members|safe }}
</div>
<div class="form-group">
<label>{% trans "Payments" %}</label>
{{ payments|safe }}
</div>
</div>
<form name="memberTextForm" action="/members/add_many_confirm" method="POST">{% csrf_token %}
<div>
<button type="submit" class="btn btn-primary">{% trans "Send" %}</button>
</div>
</form>
</div>
{% endblock content %}
-40
View File
@@ -1,40 +0,0 @@
{% extends "members_base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block content %}
<div>
<div>
<h3>{% trans "Conflicting member entries" %}</h3>
</div>
<div>
<p>{% blocktrans %}Found conflicting member entries. Choose how to handle the problematic data.{% endblocktrans %}</p>
{% for conflict in conflicts %}
<div class="conflict-row">
<div class="col-md-6">
<table class="table readonly table-conflict bg-info" >
{{ conflict.first_member_form }}
</table>
</div>
<div class="col-md-6">
<table class="table readonly table-conflict bg-info" >
{{ conflict.second_member_form }}
</table>
</div>
<div>
<form action="/members/resolve_conflict" method="POST">{% csrf_token %}
<p>{% blocktrans %}Which one has the correct information for this member?{% endblocktrans %}</p>
<input type="hidden" name="id" value="{{ conflict.id }}">
<button type="submit" name="action" value="first" class="btn btn-primary">{% trans "Accept first and remove second" %}</button>
<button type="submit" name="action" value="second" class="btn btn-primary">{% trans "Accept second and remove first" %}</button>
<button type="submit" name="action" value="both" class="btn btn-primary">{% trans "Accept both as two members" %}</button>
</form>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock content %}
+4 -8
View File
@@ -18,8 +18,7 @@ from members.views import member_update
from members.views import member_delete_confirm
from members.views import member_delete
from members.views import payment_list
from members.views import member_duplicates
from members.views import resolve_conflict
from members.views import add_many_confirm
# rest api
from members.views import MemberDetail
@@ -93,6 +92,9 @@ urlpatterns = [
# delete confirmation view
url(r'^delete_payment_confirm/(?P<index>\d+)$', payment_delete_confirm),
# post endpoint for confirming multiple entries
url(r'^add_many_confirm$', add_many_confirm),
# settings page
url(r'^settings$', settings_page),
@@ -108,10 +110,4 @@ urlpatterns = [
# rest api url
url(r'^api/members/(?P<pk>\d+)$', MemberDetail.as_view()),
# member duplicate resolution view
url(r'^duplicates$', member_duplicates),
# post target for resolving a conflict
url(r'^resolve_conflict$', resolve_conflict)
]
+59 -90
View File
@@ -8,6 +8,7 @@ from django.http import HttpResponse, HttpResponseRedirect
from django.core.mail import send_mail
from django.conf import settings
from django.utils.translation import ugettext as _
from django.forms.models import model_to_dict
# Email validation
from django.db.models.signals import post_save
@@ -27,13 +28,18 @@ import requests
import logging
import html
import csv
import pickle
from smtplib import SMTPAuthenticationError
from members.models import Member, Request, Payment, MemberConflict
from members.forms import MemberForm, PaymentForm, ApplicationForm
from members.forms import MemberForm, PaymentForm, ApplicationForm, CSVValidationError
from members.tables import MemberTable, PaymentTable, RequestTable
def error_view(request, message):
return render(request, 'error.html', {'error': str(message)})
def validate_recaptcha(response):
"""
Recaptcha is used in member applications.
@@ -518,23 +524,63 @@ def import_csv(request, *args, **kwargs):
"""Get csv data imported to page and create members based on that."""
try:
data = request.POST['textfield']
payment_source = request.POST['payment_source']
except:
return render(request,
'error.html',
{'error': _('Missing "textfield" POST request field')})
success = Member.from_csv(data)
if success:
logging.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')})
try:
result = MemberForm.csv_to_models(data, payment_source=payment_source)
except CSVValidationError as ex:
logging.exception('Model validation error')
return error_view(request, ex.form_errors)
except Exception as ex:
logging.exception('Other error in CSV import')
return error_view(request, ex)
member_table = MemberTable(result.members,
request=request,
exclude=['id', 'options'],
attrs={'class': 'table table-bordered table-hover'})
member_table_html = convert_table_to_html(member_table, request)
payment_table = PaymentTable(result.payments,
request=request,
exclude=['id', 'options'],
attrs={'class': 'table table-bordered table-hover'})
payment_table_html = convert_table_to_html(payment_table, request)
request.session['models'] = result
context = {
'members': member_table_html,
'payments': payment_table_html
}
return render(request, 'member_add_many_confirm.html', context)
@ensure_csrf_cookie
@require_http_methods(["POST"])
@permission_required('members.change_member', login_url='/login')
def add_many_confirm(request, *args, **kwargs):
models = request.session['models']
try:
members, payments = models.members, models.payments
for member in members:
member.save()
for payment in payments:
payment.save()
msg = "Successfully imported {} members and {} payments."
notification = _(msg).format(len(members), len(payments))
return HttpResponseRedirect('/members/list?notification={}'.format(html.escape(notification)))
except Exception as ex:
logging.exception('Failed to save models after "add many."')
return error_view(request, _('Failed to import members'))
@ensure_csrf_cookie
@@ -558,62 +604,6 @@ def export_csv(request, *args, **kwargs):
return response
@ensure_csrf_cookie
@require_http_methods(["GET"])
@permission_required('members.change_member', login_url='/login')
def member_duplicates(request, *args, **kwargs):
"""Check for duplicate members."""
conflicts = MemberConflict.objects.all()
context = {
'conflicts': conflicts
}
return render(request, 'member_duplicates.html', context)
@ensure_csrf_cookie
@require_http_methods(["POST"])
@permission_required('members.change_member', login_url='/login')
def resolve_conflict(request, *args, **kwargs):
"""Resolve duplicate member conflict."""
action = request.POST.get('action', None)
if action not in ['first', 'second', 'both']:
return render(request,
'error.html',
{'error': '{}: {}'.format(('Incorrect action value'),
action)})
id = request.POST.get('id', None)
if id is None:
return render(request,
'error.html',
{'error': '{}: {}'.format(('Incorrect id value'), id)})
conflict = MemberConflict.objects.get(id=id)
first_member = conflict.first_member
second_member = conflict.second_member
if action == 'first':
for payment in second_member.payments.all():
payment.member = first_member
payment.save()
second_member.delete()
elif action == 'second':
for payment in first_member.payments.all():
payment.member = second_member
payment.save()
first_member.delete()
conflict.delete()
if MemberConflict.objects.exists():
return HttpResponseRedirect('/members/duplicates')
else:
notification = _('Successfully resolved all member conflicts.')
return HttpResponseRedirect(
'/members/list?notification={}'.format(html.escape(notification)))
def send_mail_wrapper(subject, message, email_to):
"""Send mail to default email."""
send_mail(subject,
@@ -653,27 +643,6 @@ def email_on_accept(sender, instance, created, **kwargs):
logging.error('Failed to send email to accepted member!')
def check_for_duplicates(instance):
"""Check for member duplicates."""
name_candidates = Member.objects.filter(first_name=instance.first_name,
last_name=instance.last_name)
email_candidates = Member.objects.filter(email=instance.email)
candidates = name_candidates | email_candidates
duplicates = candidates.exclude(id=instance.id)
if len(duplicates) > 0:
conflict = MemberConflict(first_member=instance,
second_member=duplicates[0])
conflict.save()
@receiver(post_save, sender=Member)
def duplicate_receiver(sender, instance, created, **kwargs):
"""Call check_for_duplicates function."""
check_for_duplicates(instance)
# Can be used to retrieve single member information via REST API
class MemberDetail(generics.RetrieveAPIView):
"""Member detail rest API view."""
+1 -1
View File
@@ -16,7 +16,7 @@ then
USE_NPM="false"
fi
$INTERACTIVE || echo "Running in non-interactive mode." && env
$INTERACTIVE || (echo "Running in non-interactive mode." && env)
$INTERACTIVE && read -p "Are these programs installed? [y/n]" -n 1 -r || REPLY="y"
echo ""