Merge branch 'develop' into feature-pep8-fixes
This commit is contained in:
@@ -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():
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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
@@ -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
@@ -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."""
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
+1
-1
@@ -8,4 +8,4 @@ class OhlhafvForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = OhlhafvChallenge
|
||||
fields = ['challenger', 'challenger_email', 'victim', 'victim_email', 'series', 'message']
|
||||
fields = ['challenger', 'challenger_email', 'victim', 'victim_email', 'series', 'message']
|
||||
|
||||
+1
-1
@@ -5,4 +5,4 @@ from webapp.models import OhlhafvChallenge
|
||||
|
||||
class OhlhafvTable(tables.Table):
|
||||
class Meta:
|
||||
model = OhlhafvChallenge
|
||||
model = OhlhafvChallenge
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ urlpatterns = [
|
||||
# git revision
|
||||
url(r'^about', about_view),
|
||||
|
||||
#ohlhafv
|
||||
# ohlhafv
|
||||
url(r'^ohlhafv$', ohlhafv_view),
|
||||
url(r'^ohlhafv/submit', ohlhafv_submit),
|
||||
url(r'^ohlhafv/list', ohlhafv_list),
|
||||
|
||||
+2
-2
@@ -75,10 +75,10 @@ def ohlhafv_submit(request, *args, **kwargs):
|
||||
form = OhlhafvForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
#return HttpResponseRedirect('/list/')
|
||||
# return HttpResponseRedirect('/list/')
|
||||
else:
|
||||
pass
|
||||
#return render(request, 'error.html', {'error': form.errors})
|
||||
# return render(request, 'error.html', {'error': form.errors})
|
||||
return HttpResponseRedirect('/ohlhafv/list/')
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user