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 _ # 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 from smtplib import SMTPAuthenticationError from members.models import Member, Request, Payment, MemberConflict from members.forms import MemberForm, PaymentForm, ApplicationForm # Logger function, you can use the same idea when implementing other loggers to other apps from members.tables import MemberTable, PaymentTable, RequestTable memberlogger = logging.getLogger(__name__) logging.basicConfig(format='[%(levelname)s]%(asctime)s %(message)s', level=settings.LOGGERLEVEL, filename=settings.LOGPATH) 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) memberlogger.info('Recaptcha response: {}'.format(result)) return result["success"] except: return False def send_mail_wrapper(subject, message): send_mail(subject, message, 'no-reply@sahkoinsinoorikilta.fi', ['viestintamestari@sahkoinsinoorikilta.fi'], fail_silently=False) def convert_table_to_html(table, request): ''' 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): 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): 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): 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): 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): form = MemberForm(request.POST) if form.is_valid(): form.save() memberlogger.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): 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() memberlogger.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): 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() memberlogger.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): 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): 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): 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): 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 def application_form(request, *args, **kwargs): return render(request, 'application_index.html', {}) @ensure_csrf_cookie def application_form_success(request, *args, **kwargs): 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): 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): 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): form = PaymentForm(request.POST) if form.is_valid(): form.save() memberlogger.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): 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): 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): 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() memberlogger.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): 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() memberlogger.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): 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): try: data = request.POST['textfield'] except: return render(request, 'error.html', {'error': _('Missing "textfield" POST request field')}) success = Member.from_csv(data) if success: memberlogger.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')}) @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def export_csv(request, *args, **kwargs): response = HttpResponse() response['Content-type'] = 'text/csv' response['Accept'] = 'text/csv' response['Content-Disposition'] = 'filename; filename=members.csv' writer = csv.writer(response, csv.excel) response.write(u'\ufeff'.encode('utf8')) # BOM (optional...Excel needs it to open UTF-8 file properly) for obj in Member.objects.all(): data = obj.as_array() field_list = map(lambda d: str(d), data) writer.writerow(field_list) return response @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') def member_duplicates(request, *args, **kwargs): 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): 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': second_member.delete() elif action == 'second': 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(subject, message, settings.DEFAULT_EMAIL_FROM, [email_to], fail_silently=False) @receiver(post_save, sender=Request) def email_on_request(sender, instance, created, **kwargs): 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: memberlogger.error('Failed to send email to accepted request!') @receiver(post_save, sender=Member) def email_on_accept(sender, instance, created, **kwargs): 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: memberlogger.error('Failed to send email to accepted member!') def check_for_duplicates(instance): 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): check_for_duplicates(instance) # Can be used to retrieve single member information via REST API class MemberDetail(generics.RetrieveAPIView): queryset = Member.objects.all() serializer_class = MemberSerializer permission_classes = (permissions.IsAdminUser, ) throttle_classes = (UserRateThrottle, AnonRateThrottle, )