diff --git a/members/admin.py b/members/admin.py index 6607a19..8124496 100644 --- a/members/admin.py +++ b/members/admin.py @@ -1,12 +1,11 @@ """Admin site registers for Members app.""" from django.contrib import admin -from members.models import Member, Request, Payment, MemberConflict +from members.models import Member, Request, Payment # Register your models here. admin.site.register(Member) admin.site.register(Request) admin.site.register(Payment) -admin.site.register(MemberConflict) admin.site.site_header = 'SIK Admin' diff --git a/members/migrations/0015_auto_20170925_1917.py b/members/migrations/0015_auto_20170925_1917.py new file mode 100644 index 0000000..3313ac2 --- /dev/null +++ b/members/migrations/0015_auto_20170925_1917.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-09-25 16:17 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('members', '0014_auto_20170920_1457'), + ] + + operations = [ + migrations.RemoveField( + model_name='memberconflict', + name='first_member', + ), + migrations.RemoveField( + model_name='memberconflict', + name='second_member', + ), + migrations.DeleteModel( + name='MemberConflict', + ), + ] diff --git a/members/models.py b/members/models.py index cbcb9d4..1a78afe 100644 --- a/members/models.py +++ b/members/models.py @@ -128,24 +128,5 @@ class Member(BaseMember): ) -class MemberConflict(models.Model): - """Model representing member conflict situation.""" - - first_member = models.ForeignKey( - 'Member', related_name='%(class)s_first_member') - second_member = models.ForeignKey( - 'Member', related_name='%(class)s_second_member') - - @property - def first_member_form(self): - """Get first member form.""" - return MemberForm(instance=self.first_member) - - @property - def second_member_form(self): - """Get second member form.""" - return MemberForm(instance=self.second_member) - - # To avoid problems with a cyclical import, this is at the bottom of the file from members.forms import MemberForm # nopep8 diff --git a/members/views.py b/members/views.py index 7d3f33c..24e5dc0 100644 --- a/members/views.py +++ b/members/views.py @@ -17,12 +17,6 @@ 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 @@ -36,583 +30,6 @@ from members.forms import MemberForm, PaymentForm, ApplicationForm, CSVValidatio 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.""" @@ -641,13 +58,3 @@ def email_on_accept(sender, instance, created, **kwargs): 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, ) diff --git a/members/views/__init__.py b/members/views/__init__.py new file mode 100644 index 0000000..a6e2691 --- /dev/null +++ b/members/views/__init__.py @@ -0,0 +1,4 @@ +from members.views.members import * +from members.views.applications import * +from members.views.payments import * +from members.views.utils import * diff --git a/members/views/applications.py b/members/views/applications.py new file mode 100644 index 0000000..030d0eb --- /dev/null +++ b/members/views/applications.py @@ -0,0 +1,138 @@ +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 + +from members.views.utils import * +from members.tables import RequestTable +from members.forms import ApplicationForm + +@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', {}) diff --git a/members/views/members.py b/members/views/members.py new file mode 100644 index 0000000..d65a5eb --- /dev/null +++ b/members/views/members.py @@ -0,0 +1,182 @@ +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 + +from members.models import Member, Request, Payment +from members.forms import MemberForm, CSVValidationError +from members.tables import MemberTable + +from members.views.utils import * + + +@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), + } + 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 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(["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}) diff --git a/members/views/payments.py b/members/views/payments.py new file mode 100644 index 0000000..9a0d0e0 --- /dev/null +++ b/members/views/payments.py @@ -0,0 +1,156 @@ +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 + +from members.views.utils import * +from members.tables import PaymentTable +from members.forms import PaymentForm + + +@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')}) diff --git a/members/views/utils.py b/members/views/utils.py new file mode 100644 index 0000000..35b7b84 --- /dev/null +++ b/members/views/utils.py @@ -0,0 +1,167 @@ +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 + +# REST framework +from members.serializers import MemberSerializer +from rest_framework import generics +from rest_framework import permissions +from rest_framework.throttling import UserRateThrottle, AnonRateThrottle + +from members.models import Member, Request, Payment + + +# 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, ) + + +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 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(["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)