"""File containing Members application views.""" from django.shortcuts import render from django.contrib.auth.decorators import permission_required from django.views.decorators.http import require_http_methods from django.views.decorators.csrf import ensure_csrf_cookie 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 from django.dispatch import receiver from django.utils.http import urlsafe_base64_encode from django.utils.encoding import force_bytes from django.core.mail import send_mail # REST framework from members.serializers import MemberSerializer from rest_framework import generics from rest_framework import permissions from rest_framework.throttling import UserRateThrottle, AnonRateThrottle import json 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, 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. :param response: :return: Boolean, success or not """ values = { 'secret': settings.GOOGLE_RECAPTCHA_SECRET_KEY, 'response': response, } url = "https://www.google.com/recaptcha/api/siteverify" headers = {'Content-type': 'application/x-www-form-urlencoded'} resp = requests.post(url, values, headers=headers) try: result = json.loads(resp.text) logging.info('Recaptcha response: {}'.format(result)) return result["success"] except: return False def send_mail_wrapper(subject, message): """Call send_mail function.""" send_mail(subject, message, 'no-reply@sahkoinsinoorikilta.fi', ['viestintamestari@sahkoinsinoorikilta.fi'], fail_silently=False) def convert_table_to_html(table, request): """ Convert table to html. This is a horrible hack for converting a table object to raw html. Even with extensive research I wasn't able to find a way to add a path prefix "e.g. /members/list" to the query strings "e.g. ?sort=foo", so I did it manually with string.replace. Note: When adding the html to a page, you need to run it through the "safe" filter. E.g. "{{ table|safe }}" :param table: Table object from members.tables :param request: HttpRequest :return: Raw html string """ table_as_html = table.as_html(request) path = request.path fixed = table_as_html.replace(r'href="?', r'href="{}?'.format(path)) return fixed @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def member_list(request, *args, **kwargs): """Render members list.""" members = Member.objects.all() table = MemberTable(members, request=request, exclude=['id'], attrs={'class': 'table table-bordered table-hover'}) table.paginate(page=request.GET.get('page', 1), per_page=25) table_html = convert_table_to_html(table, request) context = { 'table': table_html, 'member_count': len(members), 'notification': request.GET.get('notification', None), 'is_member_conflict': MemberConflict.objects.exists() } return render(request, 'member_list.html', context) @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def member_add(request, *args, **kwargs): """Render add member page.""" form = MemberForm() return render(request, 'member_add.html', {'form': form}) @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def member_delete_confirm(request, *args, **kwargs): """Render member deletion confirmation page.""" i = kwargs.pop('index', None) if i is None: return render(request, 'error.html', {'error': _('No member id specified')}) else: member = Member.objects.get(id=i) form = MemberForm(instance=member) return render(request, 'member_delete_confirm.html', {'member_id': i, 'form': form}) @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def member_add_many(request, *args, **kwargs): """Render add multiple members page.""" return render(request, 'member_add_many.html', {}) @ensure_csrf_cookie @require_http_methods(["POST"]) @permission_required('members.change_member', login_url='/login') def member_submit(request, *args, **kwargs): """Add member based on data gained from member form.""" form = MemberForm(request.POST) if form.is_valid(): form.save() logging.info("Saved new member to member register" "with the following info: {}".format(form)) notification = "{} {} {}.".format(_("Successfully added member"), form.cleaned_data['last_name'], form.cleaned_data['first_name']) return HttpResponseRedirect( '/members/list?notification={}'.format(html.escape(notification))) else: return render(request, 'error.html', {'error': form.errors}) @ensure_csrf_cookie @require_http_methods(["POST"]) @permission_required('members.change_member', login_url='/login') def member_update(request, *args, **kwargs): """Update member information.""" form = MemberForm(request.POST) if form.is_valid(): id = request.POST['id'] member = Member.objects.get(id=id) form = MemberForm(request.POST, instance=member) form.save() logging.info( "Updated member in member register with the following info: {}" .format(form)) notification = "{} {} {}.".format(_("Successfully updated member"), member.last_name, member.first_name) return HttpResponseRedirect( '/members/list?notification={}'.format(html.escape(notification))) else: return render( request, 'error.html', {'error': _('Could not update member object')}) @ensure_csrf_cookie @require_http_methods(["POST"]) @permission_required('members.change_member', login_url='/login') def member_delete(request, *args, **kwargs): """Delete member.""" try: id = request.POST['id'] except KeyError: return render(request, 'error.html', {'error': _('No member id specified')}) try: member = Member.objects.get(id=id) notification = "{} {} {}.".format(_("Successfully deleted member"), member.last_name, member.first_name) member.delete() logging.info( "Delete member in member register with the following id: {}" .format(id)) return HttpResponseRedirect( '/members/list?notification={}'.format(html.escape(notification))) except: return render(request, 'error.html', {'error': _('Could not delete member object')}) @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def member_edit(request, *args, **kwargs): """Edit member information.""" i = kwargs.pop('index', None) if i is None: return render( request, 'error.html', {'error': _('No member id specified')}) else: member = Member.objects.get(id=i) form = MemberForm(instance=member) return render( request, 'member_edit.html', {'member_id': i, 'form': form}) @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def application_list(request, *args, **kwargs): """List member applications not yet processed.""" applications = Request.objects.all() application_count = len(applications) table = RequestTable(applications, request=request, exclude=['id'], attrs={'class': 'table table-bordered table-hover'}) table.paginate(page=request.GET.get('page', 1), per_page=25) table_html = convert_table_to_html(table, request) context = { 'table': table_html, 'application_count': application_count, 'notification': request.GET.get('notification', None) } return render(request, 'application_list.html', context) @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def application_edit(request, *args, **kwargs): """Edit member request information.""" 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_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): """Accept application.""" 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() logging.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): """Delete member application.""" 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() logging.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): """Confirm application deletion.""" 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 def application_form(request, *args, **kwargs): """Render member application form.""" return render(request, 'application_index.html', {}) @ensure_csrf_cookie def application_form_success(request, *args, **kwargs): """Render application Successfully sent page.""" return render(request, 'application_success.html', {}) @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def payment_list(request, *args, **kwargs): """Render list of payments.""" payments = Payment.objects.all() table = PaymentTable(payments, request=request, exclude=['id'], attrs={'class': 'table table-bordered table-hover'}) table.paginate(page=request.GET.get('page', 1), per_page=25) table_html = convert_table_to_html(table, request) context = { 'table': table_html, 'payment_count': len(payments), 'notification': request.GET.get('notification', None) } return render(request, 'payment_list.html', context) @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def payment_add(request, *args, **kwargs): """Render add payment form.""" form = PaymentForm() return render(request, 'payment_add.html', {'form': form}) @ensure_csrf_cookie @require_http_methods(["POST"]) @permission_required('members.change_member', login_url='/login') def payment_submit(request, *args, **kwargs): """Submit payment.""" form = PaymentForm(request.POST) if form.is_valid(): form.save() logging.info( "Saved new payment to member register with the following info: {}" .format(form)) notification = "{} {}.".format( _("Successfully added payment for member"), form.cleaned_data['member']) return HttpResponseRedirect( '/members/payments?notification={}' .format(html.escape(notification))) else: return render(request, 'error.html', {'error': form.errors}) @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def payment_edit(request, *args, **kwargs): """Edit payment.""" i = kwargs.pop('index', None) if i is None: return render(request, 'error.html', {'error': _('No payment id specified')}) else: payment = Payment.objects.get(id=i) form = PaymentForm(instance=payment) return render(request, 'payment_edit.html', {'payment_id': i, 'form': form}) @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def payment_delete_confirm(request, *args, **kwargs): """Render payment delete confirmation page.""" i = kwargs.pop('index', None) if i is None: return render(request, 'error.html', {'error': _('No payment id specified')}) else: payment = Payment.objects.get(id=i) form = PaymentForm(instance=payment) return render(request, 'payment_delete_confirm.html', {'payment_id': i, 'form': form}) @ensure_csrf_cookie @require_http_methods(["POST"]) @permission_required('members.change_member', login_url='/login') def payment_delete(request, *args, **kwargs): """Delete payment.""" try: id = request.POST['id'] except KeyError: return render(request, 'error.html', {'error': _('No payment id specified')}) try: payment = Payment.objects.get(id=id) notification = "{} {}.".format( _("Successfully deleted payment"), str(payment)) payment.delete() logging.info( "Delete payment '{}' in member register".format(str(payment))) return HttpResponseRedirect( '/members/payments?notification={}' .format(html.escape(notification))) except: return render(request, 'error.html', {'error': _('Could not delete payment object')}) @ensure_csrf_cookie @require_http_methods(["POST"]) @permission_required('members.change_member', login_url='/login') def payment_update(request, *args, **kwargs): """Update payment information.""" form = PaymentForm(request.POST) if form.is_valid(): id = request.POST['id'] payment = Payment.objects.get(id=id) form = PaymentForm(request.POST, instance=payment) form.save() logging.info( "Updated member in member register with the following info: {}" .format(form)) notification = "{} {}.".format( _("Successfully updated payment"), str(payment)) return HttpResponseRedirect( '/members/payments?notification={}' .format(html.escape(notification))) else: return render(request, 'error.html', {'error': _('Could not update payment object')}) @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def settings_page(request, *args, **kwargs): """Render member app settings page.""" return render(request, 'settings.html', {}) @ensure_csrf_cookie @require_http_methods(["POST"]) @permission_required('members.change_member', login_url='/login') 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')}) 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 @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def export_csv(request, *args, **kwargs): """Export members as csv.""" response = HttpResponse() response['Content-type'] = 'text/csv' response['Accept'] = 'text/csv' response['Content-Disposition'] = 'filename; filename=members.csv' writer = csv.writer(response, csv.excel) # BOM (optional...Excel needs it to open UTF-8 file properly) response.write(u'\ufeff'.encode('utf8')) for obj in Member.objects.all(): data = obj.as_array() field_list = map(lambda d: str(d), data) writer.writerow(field_list) return response def send_mail_wrapper(subject, message, email_to): """Send mail to default email.""" send_mail(subject, message, settings.DEFAULT_EMAIL_FROM, [email_to], fail_silently=False) @receiver(post_save, sender=Request) def email_on_request(sender, instance, created, **kwargs): """Send email validation.""" if not settings.ENABLE_AUTOMATIC_EMAILS: return try: if created: subject = 'Test1' message = 'Please validate your email address\r\n' send_mail_wrapper(subject, message, instance.email) except SMTPAuthenticationError: logging.error('Failed to send email to accepted request!') @receiver(post_save, sender=Member) def email_on_accept(sender, instance, created, **kwargs): """Send email to accepted member.""" if not settings.ENABLE_AUTOMATIC_EMAILS: return try: if created: subject = 'Test2' message = 'Jäsenhakemuksesi on hyväksytty!!!\r\n' send_mail_wrapper(subject, message, instance.email) except SMTPAuthenticationError: logging.error('Failed to send email to accepted member!') # Can be used to retrieve single member information via REST API class MemberDetail(generics.RetrieveAPIView): """Member detail rest API view.""" queryset = Member.objects.all() serializer_class = MemberSerializer permission_classes = (permissions.IsAdminUser, ) throttle_classes = (UserRateThrottle, AnonRateThrottle, )