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): def on_disconnect(client, userdata, rc):
if rc != 0: if rc != 0:
print("Unexpected disconnection.") logging.warning("MQTT unexpectedly disconnected.")
else: else:
client.loop_stop(force=False) client.loop_stop(force=False)
print("Disconnected") logging.warning("MQTT disconnected.")
def get_latest(): def get_latest():
-2
View File
@@ -184,8 +184,6 @@ def create_image_item(request, *args, **kwargs):
def create_video_item(request, *args, **kwargs): def create_video_item(request, *args, **kwargs):
"""Create video Infoscreen item.""" """Create video Infoscreen item."""
form = UploadFileForm(request.POST, request.FILES) form = UploadFileForm(request.POST, request.FILES)
print(form.errors)
print("hurdurr")
if not form.is_valid(): if not form.is_valid():
return HttpResponseBadRequest('{"status": "failure",' return HttpResponseBadRequest('{"status": "failure",'
'"error": "invalid data supplied"}') '"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 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): class MemberForm(forms.ModelForm):
"""Member model form.""" """Member model form."""
@@ -13,7 +22,67 @@ class MemberForm(forms.ModelForm):
"""Meta for Member model form.""" """Meta for Member model form."""
model = Member 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): 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) first_name = models.CharField(_("First name"), max_length=127)
last_name = models.CharField(_("Last 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"), POR = models.CharField(_("Place of residence"),
max_length=255) # place of residence max_length=255) # place of residence
AYY = models.BooleanField(_("AYY"), default=False) AYY = models.BooleanField(_("AYY"), default=False)
@@ -30,8 +30,6 @@ class BaseMember(models.Model):
@staticmethod @staticmethod
def from_csv(data): def from_csv(data):
"""Construct member model from csv data."""
print("Imported CSV data: {}".format(data))
clean_data = data.strip().split('\n') clean_data = data.strip().split('\n')
csv_reader = csv.reader(clean_data) csv_reader = csv.reader(clean_data)
+1 -1
View File
@@ -9,7 +9,7 @@
</div> </div>
<div class="alert alert-danger"> <div class="alert alert-danger">
{{ error }} {{ error|safe }}
</div> </div>
<div> <div>
<button onclick="window.history.back();" class="btn btn-primary">{% trans "Back" %}</button> <button onclick="window.history.back();" class="btn btn-primary">{% trans "Back" %}</button>
+11 -2
View File
@@ -5,7 +5,7 @@
{% block content %} {% block content %}
<div> <div>
<div> <div>
<h3> Lisää useampi jäsen </h3> <h3>{% trans "Add many members" %}</h3>
</div> </div>
<div> <div>
@@ -25,9 +25,18 @@
</div> </div>
<form name="memberTextForm" action="/members/import_csv" method="POST">{% csrf_token %} <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> <textarea name="textfield" class="form-control large-textarea" placeholder="Teemu, Teekkari, teemu.teekkari@notmail.dom, Otaniemi, 0, 0"></textarea>
</div> </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> <div>
<button type="submit" class="btn btn-primary">{% trans "Send" %}</button> <button type="submit" class="btn btn-primary">{% trans "Send" %}</button>
</div> </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_confirm
from members.views import member_delete from members.views import member_delete
from members.views import payment_list from members.views import payment_list
from members.views import member_duplicates from members.views import add_many_confirm
from members.views import resolve_conflict
# rest api # rest api
from members.views import MemberDetail from members.views import MemberDetail
@@ -93,6 +92,9 @@ urlpatterns = [
# delete confirmation view # delete confirmation view
url(r'^delete_payment_confirm/(?P<index>\d+)$', payment_delete_confirm), 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 # settings page
url(r'^settings$', settings_page), url(r'^settings$', settings_page),
@@ -108,10 +110,4 @@ urlpatterns = [
# rest api url # rest api url
url(r'^api/members/(?P<pk>\d+)$', MemberDetail.as_view()), 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.core.mail import send_mail
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.forms.models import model_to_dict
# Email validation # Email validation
from django.db.models.signals import post_save from django.db.models.signals import post_save
@@ -27,13 +28,18 @@ import requests
import logging import logging
import html import html
import csv import csv
import pickle
from smtplib import SMTPAuthenticationError from smtplib import SMTPAuthenticationError
from members.models import Member, Request, Payment, MemberConflict 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 from members.tables import MemberTable, PaymentTable, RequestTable
def error_view(request, message):
return render(request, 'error.html', {'error': str(message)})
def validate_recaptcha(response): def validate_recaptcha(response):
""" """
Recaptcha is used in member applications. 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.""" """Get csv data imported to page and create members based on that."""
try: try:
data = request.POST['textfield'] data = request.POST['textfield']
payment_source = request.POST['payment_source']
except: except:
return render(request, return render(request,
'error.html', 'error.html',
{'error': _('Missing "textfield" POST request field')}) {'error': _('Missing "textfield" POST request field')})
success = Member.from_csv(data) try:
if success: result = MemberForm.csv_to_models(data, payment_source=payment_source)
logging.info('Imported CSV data:\n'.format(data)) except CSVValidationError as ex:
notification = "{}.".format( logging.exception('Model validation error')
_("Successfully imported multiple members")) return error_view(request, ex.form_errors)
return HttpResponseRedirect( except Exception as ex:
'/members/list?notification={}' logging.exception('Other error in CSV import')
.format(html.escape(notification))) return error_view(request, ex)
else:
return render(request, member_table = MemberTable(result.members,
'error.html', request=request,
{'error': _('Failed to import members')}) 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 @ensure_csrf_cookie
@@ -558,62 +604,6 @@ def export_csv(request, *args, **kwargs):
return response 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): def send_mail_wrapper(subject, message, email_to):
"""Send mail to default email.""" """Send mail to default email."""
send_mail(subject, send_mail(subject,
@@ -653,27 +643,6 @@ def email_on_accept(sender, instance, created, **kwargs):
logging.error('Failed to send email to accepted member!') 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 # Can be used to retrieve single member information via REST API
class MemberDetail(generics.RetrieveAPIView): class MemberDetail(generics.RetrieveAPIView):
"""Member detail rest API view.""" """Member detail rest API view."""
+1 -1
View File
@@ -16,7 +16,7 @@ then
USE_NPM="false" USE_NPM="false"
fi 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" $INTERACTIVE && read -p "Are these programs installed? [y/n]" -n 1 -r || REPLY="y"
echo "" echo ""
+1 -1
View File
@@ -8,4 +8,4 @@ class OhlhafvForm(forms.ModelForm):
class Meta: class Meta:
model = OhlhafvChallenge model = OhlhafvChallenge
fields = ['challenger', 'challenger_email', 'victim', 'victim_email', 'series', 'message'] fields = ['challenger', 'challenger_email', 'victim', 'victim_email', 'series', 'message']
+1 -1
View File
@@ -5,4 +5,4 @@ from webapp.models import OhlhafvChallenge
class OhlhafvTable(tables.Table): class OhlhafvTable(tables.Table):
class Meta: class Meta:
model = OhlhafvChallenge model = OhlhafvChallenge
+1 -1
View File
@@ -23,7 +23,7 @@ urlpatterns = [
# git revision # git revision
url(r'^about', about_view), url(r'^about', about_view),
#ohlhafv # ohlhafv
url(r'^ohlhafv$', ohlhafv_view), url(r'^ohlhafv$', ohlhafv_view),
url(r'^ohlhafv/submit', ohlhafv_submit), url(r'^ohlhafv/submit', ohlhafv_submit),
url(r'^ohlhafv/list', ohlhafv_list), url(r'^ohlhafv/list', ohlhafv_list),
+2 -2
View File
@@ -75,10 +75,10 @@ def ohlhafv_submit(request, *args, **kwargs):
form = OhlhafvForm(request.POST) form = OhlhafvForm(request.POST)
if form.is_valid(): if form.is_valid():
form.save() form.save()
#return HttpResponseRedirect('/list/') # return HttpResponseRedirect('/list/')
else: else:
pass pass
#return render(request, 'error.html', {'error': form.errors}) # return render(request, 'error.html', {'error': form.errors})
return HttpResponseRedirect('/ohlhafv/list/') return HttpResponseRedirect('/ohlhafv/list/')