diff --git a/members/migrations/0006_auto_20170517_1309.py b/members/migrations/0006_auto_20170517_1309.py new file mode 100644 index 0000000..aebf2fe --- /dev/null +++ b/members/migrations/0006_auto_20170517_1309.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2017-05-17 10:09 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('members', '0005_auto_20170513_1029'), + ] + + operations = [ + migrations.AlterField( + model_name='payment', + name='date', + field=models.DateTimeField(default=datetime.datetime(2017, 5, 17, 13, 9, 21, 49238)), + ), + ] diff --git a/members/models.py b/members/models.py index d0e73cc..2fc005a 100644 --- a/members/models.py +++ b/members/models.py @@ -18,6 +18,9 @@ class BaseMember(models.Model): class Meta: abstract = True + def __str__(self): + return "{} {}, {}".format(self.last_name, self.first_name, self.email) + class Request(BaseMember): ''' @@ -39,12 +42,12 @@ class Payment(models.Model): member = models.ForeignKey('Member', on_delete=models.SET_NULL, blank=True, null=True) + def __str__(self): + return 'Payment no. {}, {}'.format(self.id, str(self.date)) + class Member(BaseMember): ''' Member model represets one member on the registry. ''' - created = models.DateTimeField(default=timezone.now) - - def __str__(self): - return "{} {}, {}".format(self.last_name, self.first_name, self.email) \ No newline at end of file + created = models.DateTimeField(default=timezone.now) \ No newline at end of file diff --git a/members/static/css/members.css b/members/static/css/members.css index 2677566..0d1ad4b 100644 --- a/members/static/css/members.css +++ b/members/static/css/members.css @@ -45,7 +45,7 @@ input { cursor: default; } -@media (max-width: 768px) { +@media (max-width: 767px) { table { table-layout: fixed; @@ -102,7 +102,7 @@ input { } } -@media (min-width: 768px) { +@media (min-width: 769px) { .logout-container { bottom: 1rem; position: absolute; @@ -195,3 +195,15 @@ input { .inline-title { display: inline; } + +.ellipsis-menu { + height: 2rem; +} + +.data-table-button { + width: 100%; +} + +.readonly { + pointer-events: none; +} \ No newline at end of file diff --git a/members/tables.py b/members/tables.py index 484c913..9b1820a 100644 --- a/members/tables.py +++ b/members/tables.py @@ -1,21 +1,40 @@ import django_tables2 as tables +from django.utils.translation import ugettext as _ from members.models import Member, Payment, Request class MemberTable(tables.Table): + options = tables.TemplateColumn( + '' + + _('Edit') + + '' + ) + class Meta: model = Member class PaymentTable(tables.Table): + options = tables.TemplateColumn( + '' + + _('Edit') + + '' + ) + class Meta: model = Payment class RequestTable(tables.Table): + options = tables.TemplateColumn( + '' + + _('Edit') + + '' + ) + class Meta: model = Request diff --git a/members/templates/member_delete_confirm.html b/members/templates/member_delete_confirm.html index 4a00313..9b82ca0 100644 --- a/members/templates/member_delete_confirm.html +++ b/members/templates/member_delete_confirm.html @@ -5,11 +5,13 @@ {% block content %}
-

{% trans "Are you sure you want to delete this member?" %}

+
+

{% trans "Are you sure you want to delete this member?" %}

+
-
- - {{ form.as_table }} +
+
+ {{ form }}
{% csrf_token %} diff --git a/members/templates/member_edit.html b/members/templates/member_edit.html index 5a15df9..4f2c7ea 100644 --- a/members/templates/member_edit.html +++ b/members/templates/member_edit.html @@ -5,7 +5,9 @@ {% block content %}
-

{% trans "Add member" %}

+
+

{% trans "Edit member" %}

+
{% csrf_token %} @@ -15,6 +17,7 @@ + {% trans "Delete" %} {% endbuttons %}
diff --git a/members/templates/members_base.html b/members/templates/members_base.html index 71ff9af..45885d2 100644 --- a/members/templates/members_base.html +++ b/members/templates/members_base.html @@ -56,7 +56,7 @@ {% trans "Payments" %}
  • diff --git a/members/templates/payment_delete_confirm.html b/members/templates/payment_delete_confirm.html new file mode 100644 index 0000000..fc13360 --- /dev/null +++ b/members/templates/payment_delete_confirm.html @@ -0,0 +1,24 @@ +{% extends "members_base.html" %} + +{% load i18n %} +{% load bootstrap3 %} + +{% block content %} +
    +
    +

    {% trans "Are you sure you want to delete this payment?" %}

    +
    + +
    + + {{ form }} +
    +
    {% csrf_token %} + + +
    +
    +
    +{% endblock content %} diff --git a/members/templates/payment_edit.html b/members/templates/payment_edit.html new file mode 100644 index 0000000..44ce144 --- /dev/null +++ b/members/templates/payment_edit.html @@ -0,0 +1,25 @@ +{% extends "members_base.html" %} + +{% load i18n %} +{% load bootstrap3 %} + +{% block content %} +
    +
    +

    {% trans "Edit payment" %}

    +
    + +
    +
    {% csrf_token %} + + {% bootstrap_form form %} + {% buttons %} + + {% trans "Delete" %} + {% endbuttons %} +
    +
    +
    +{% endblock content %} diff --git a/members/urls.py b/members/urls.py index 3333f77..f8a1388 100644 --- a/members/urls.py +++ b/members/urls.py @@ -2,7 +2,10 @@ from django.conf.urls import url from django.views.generic.base import RedirectView # members -from members.views import member_list, payment_add, payment_submit, settings_page +from members.views import member_list, payment_add, payment_submit +from members.views import settings_page, payment_edit +from members.views import payment_delete_confirm +from members.views import payment_delete, payment_update from members.views import member_add from members.views import member_add_many from members.views import member_edit @@ -12,7 +15,7 @@ from members.views import member_delete_confirm from members.views import member_delete from members.views import payment_list -#application +# application from members.views import application_form from members.views import application_list from members.views import application_edit @@ -49,6 +52,8 @@ urlpatterns = [ url(r'^update_member$', member_update), url(r'^delete_member$', member_delete), url(r'^submit_payment$', payment_submit), + url(r'^update_payment$', payment_update), + url(r'^delete_payment$', payment_delete), # the actual member application form url(r'^application/$', application_form), @@ -57,13 +62,19 @@ urlpatterns = [ url(r'^application/success$', application_form_success), # list all payment events - url(r'^payments', payment_list), + url(r'^payments$', payment_list), # add payment event - url(r'^payment_add', payment_add), + url(r'^add_payment$', payment_add), + + # edit payment event + url(r'^edit_payment/(?P\d+)$', payment_edit), + + # delete confirmation view + url(r'^delete_payment_confirm/(?P\d+)$', payment_delete_confirm), # settings page - url(r'^settings', settings_page), + url(r'^settings$', settings_page), # favourite icon url(r'^favicon\.ico$', favicon_view), diff --git a/members/views.py b/members/views.py index 6a827b8..21870ea 100644 --- a/members/views.py +++ b/members/views.py @@ -2,7 +2,7 @@ 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, HttpResponseBadRequest, HttpResponseRedirect +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 _ @@ -25,6 +25,9 @@ logging.basicConfig(format='[%(levelname)s]%(asctime)s %(message)s', level=setti def validate_recaptcha(response): ''' Recaptcha is used in member applications + + :param response: + :return: Boolean, success or not ''' values = { @@ -34,11 +37,12 @@ def validate_recaptcha(response): url = "https://www.google.com/recaptcha/api/siteverify" headers = {'Content-type': 'application/x-www-form-urlencoded'} resp = requests.post(url, values, headers=headers) - result = json.loads(resp.text) - memberlogger.info(result) - if not result["success"]: + try: + result = json.loads(resp.text) + memberlogger.info('Recaptcha response: {}'.format(result)) + return result["success"] + except: return False - return True def send_mail_wrapper(subject, message): @@ -71,6 +75,7 @@ def convert_table_to_html(table, request): 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') @@ -107,7 +112,7 @@ def member_add(request, *args, **kwargs): def member_delete_confirm(request, *args, **kwargs): i = kwargs.pop('index', None) if i is None: - return HttpResponse(status=500, error="{'error': 'No member id specified'}") + return render(request, 'error.html', {'error': _('No member id specified')}) else: member = Member.objects.get(id=i) form = MemberForm(instance=member) @@ -150,10 +155,11 @@ def member_update(request, *args, **kwargs): form.save() memberlogger.info("Updated member in member register with the following info: {}".format(form)) - return HttpResponseRedirect('/members') + notification = "{} {} {}.".format(_("Successfully updated member"), + member.last_name, member.first_name) + return HttpResponseRedirect('/members/list?notification={}'.format(html.escape(notification))) else: - print(form.errors) - return HttpResponse('oh shit') + return render(request, 'error.html', {'error': _('Could not update member object')}) @ensure_csrf_cookie @@ -167,12 +173,13 @@ def member_delete(request, *args, **kwargs): try: member = Member.objects.get(id=id) + notification = "{} {} {}.".format(_("Successfully deleted member"), + member.last_name, member.first_name) member.delete() - return HttpResponseRedirect('/members') + memberlogger.info("Delete member in member register with the following id: {}".format(id)) + return HttpResponseRedirect('/members/list?notification={}'.format(html.escape(notification))) except: - resp = HttpResponse('{"error" : "could not delete object"}') - resp.status_code = 500 - return resp + return render(request, 'error.html', {'error': _('Could not delete member object')}) @ensure_csrf_cookie @@ -181,7 +188,7 @@ def member_delete(request, *args, **kwargs): def member_edit(request, *args, **kwargs): i = kwargs.pop('index', None) if i is None: - return HttpResponse(status=500, error="{'error': 'No member id specified'}") + return render(request, 'error.html', {'error': _('No member id specified')}) else: member = Member.objects.get(id=i) form = MemberForm(instance=member) @@ -197,8 +204,7 @@ def application_list(request, *args, **kwargs): table = RequestTable(applications, request=request, exclude=['id'], - attrs={'class': 'table table-bordered table-hover'}, - ) + 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) @@ -216,9 +222,9 @@ def application_list(request, *args, **kwargs): def application_edit(request, *args, **kwargs): i = kwargs.pop('index', None) if i is None: - return HttpResponse(status=500, error="{'error': 'No member id specified'}") + return render(request, 'error.html', {'error': _('No application id specified')}) else: - return render(request, 'application_edit.html', {'member_id' : i}) + return render(request, 'application_edit.html', {'member_id': i}) @ensure_csrf_cookie @@ -270,17 +276,82 @@ def payment_submit(request, *args, **kwargs): 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']) + 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 HttpResponse(401) + + 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')