Merge branch 'develop' into 'master'
Search bar implementation See merge request !50
This commit is contained in:
+29
-2
@@ -1,3 +1,30 @@
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, Client
|
||||
from django.conf import settings
|
||||
|
||||
# Create your tests here.
|
||||
from coffee_scale.mqtt import on_message
|
||||
|
||||
HOST = settings.MQTT_SETTINGS['HOST']
|
||||
PORT = settings.MQTT_SETTINGS['PORT']
|
||||
TOPICS = settings.MQTT_SETTINGS['TOPICS']
|
||||
|
||||
|
||||
class MQTTTestCase(TestCase):
|
||||
"""Tests MQTT functionality"""
|
||||
|
||||
class MockMessage:
|
||||
def __init__(self, payload, topic):
|
||||
self.payload = payload
|
||||
self.topic = topic
|
||||
|
||||
def setUp(self):
|
||||
payload = '10'.encode('utf-8')
|
||||
topic = TOPICS['CUPS']
|
||||
msg = MQTTTestCase.MockMessage(payload, topic)
|
||||
|
||||
on_message(None, None, msg)
|
||||
self.c = Client()
|
||||
|
||||
def test_receive_cups(self):
|
||||
response = self.c.get('/coffee/cups')
|
||||
payload = response.json()
|
||||
self.assertEquals(payload['cups'], 10)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from django.shortcuts import render
|
||||
from django.http import JsonResponse
|
||||
|
||||
import datetime
|
||||
from django.utils import timezone
|
||||
|
||||
from .mqtt import get_latest
|
||||
import coffee_scale.mqtt # somehow this is needed
|
||||
|
||||
@@ -15,7 +16,7 @@ def coffee_view(request):
|
||||
|
||||
|
||||
def cups_view(request):
|
||||
now = datetime.datetime.now()
|
||||
now = timezone.now()
|
||||
latest = get_latest()
|
||||
data = {
|
||||
'date': now,
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
import urllib.request
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta, datetime
|
||||
from django.utils import timezone
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
@@ -20,7 +21,7 @@ class HSLFetcher:
|
||||
|
||||
def fetch_if_needed(self):
|
||||
"""Check if new fetch from HSL API is needed."""
|
||||
if (datetime.now() - HSLFetcher.last_fetched >
|
||||
if (timezone.now() - HSLFetcher.last_fetched >
|
||||
timedelta(minutes=HSLFetcher.INTERVAL)):
|
||||
self.fetch()
|
||||
|
||||
@@ -38,7 +39,7 @@ class HSLFetcher:
|
||||
|
||||
arr = []
|
||||
|
||||
time = (datetime.now() +
|
||||
time = (timezone.now() +
|
||||
timedelta(minutes=settings.HSL_DEPARTURE_THRESHOLD))
|
||||
time = "{0:02d}{0:02d}".format(time.hour, time.minute)
|
||||
for element in data:
|
||||
@@ -65,7 +66,7 @@ class HSLFetcher:
|
||||
obj = model_arr[count - 1]
|
||||
obj.data = json_dump
|
||||
obj.save()
|
||||
now = datetime.now()
|
||||
now = timezone.now()
|
||||
HSLFetcher.last_fetched = now
|
||||
|
||||
logging.info(
|
||||
|
||||
Binary file not shown.
+184
-139
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-06-07 18:22+0300\n"
|
||||
"POT-Creation-Date: 2017-09-25 21:32+0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -17,35 +17,35 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: infoscreen/models.py:77
|
||||
#: infoscreen/models.py:97
|
||||
msgid "ABB jobs"
|
||||
msgstr "ABB jobs"
|
||||
|
||||
#: infoscreen/models.py:88
|
||||
#: infoscreen/models.py:112
|
||||
msgid "APY Item"
|
||||
msgstr "ÄPY Item"
|
||||
|
||||
#: infoscreen/models.py:99
|
||||
#: infoscreen/models.py:127
|
||||
msgid "External website"
|
||||
msgstr "External website"
|
||||
|
||||
#: infoscreen/models.py:147
|
||||
#: infoscreen/models.py:184
|
||||
msgid "Sössö articles"
|
||||
msgstr "Sössö articles"
|
||||
|
||||
#: infoscreen/models.py:158
|
||||
#: infoscreen/models.py:199
|
||||
msgid "Events"
|
||||
msgstr "Events"
|
||||
|
||||
#: infoscreen/models.py:169
|
||||
#: infoscreen/models.py:214
|
||||
msgid "Image"
|
||||
msgstr "Image"
|
||||
|
||||
#: infoscreen/models.py:204
|
||||
#: infoscreen/models.py:260
|
||||
msgid "HSL timetables"
|
||||
msgstr "HSL timetables"
|
||||
|
||||
#: infoscreen/models.py:215
|
||||
#: infoscreen/models.py:275
|
||||
msgid "External image"
|
||||
msgstr "External image"
|
||||
|
||||
@@ -169,7 +169,8 @@ msgstr "Select rotation to edit"
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
|
||||
#: infoscreen/templates/infoscreen_admin.html:141 webapp/models.py:46
|
||||
#: infoscreen/templates/infoscreen_admin.html:141 webapp/models.py:60
|
||||
#: webapp/models.py:94 webapp/models.py:107
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
@@ -183,14 +184,12 @@ msgid "Language"
|
||||
msgstr "Language"
|
||||
|
||||
#: infoscreen/templates/infoscreen_admin.html:161
|
||||
#: members/templates/settings.html:20 sikweb/settings-sample.py:179
|
||||
#: sikweb/settings.py:178
|
||||
#: members/templates/settings.html:20 sikweb/base.py:216
|
||||
msgid "Finnish"
|
||||
msgstr "Finnish"
|
||||
|
||||
#: infoscreen/templates/infoscreen_admin.html:162
|
||||
#: members/templates/settings.html:21 sikweb/settings-sample.py:178
|
||||
#: sikweb/settings.py:177
|
||||
#: members/templates/settings.html:21 sikweb/base.py:217
|
||||
msgid "English"
|
||||
msgstr "English"
|
||||
|
||||
@@ -201,31 +200,32 @@ msgstr "English"
|
||||
msgid "Submit"
|
||||
msgstr "Submitted"
|
||||
|
||||
#: members/forms.py:20 members/tables.py:24
|
||||
#: members/forms.py:103 members/tables.py:32
|
||||
msgid "Member"
|
||||
msgstr "Member"
|
||||
|
||||
#: members/models.py:16
|
||||
#: members/models.py:13
|
||||
msgid "First name"
|
||||
msgstr "First name"
|
||||
|
||||
#: members/models.py:17
|
||||
#: members/models.py:14
|
||||
msgid "Last name"
|
||||
msgstr "Last name"
|
||||
|
||||
#: members/models.py:18
|
||||
#: members/models.py:15 webapp/models.py:95 webapp/models.py:108
|
||||
msgid "Email"
|
||||
msgstr "Email"
|
||||
|
||||
#: members/models.py:19
|
||||
#: members/models.py:16
|
||||
msgid "Place of residence"
|
||||
msgstr "Place of residence"
|
||||
|
||||
#: members/models.py:20 members/models.py:83
|
||||
#: members/models.py:18 members/models.py:83
|
||||
#: members/templates/member_add_many.html:35
|
||||
msgid "AYY"
|
||||
msgstr "AYY"
|
||||
|
||||
#: members/models.py:21
|
||||
#: members/models.py:19
|
||||
msgid "JAS"
|
||||
msgstr "JAS"
|
||||
|
||||
@@ -245,7 +245,7 @@ msgstr "Source"
|
||||
msgid "Cash"
|
||||
msgstr "Cash"
|
||||
|
||||
#: members/models.py:85
|
||||
#: members/models.py:85 members/templates/member_add_many.html:36
|
||||
msgid "Bank transfer"
|
||||
msgstr "Bank transfer"
|
||||
|
||||
@@ -253,15 +253,15 @@ msgstr "Bank transfer"
|
||||
msgid "Created"
|
||||
msgstr "Created"
|
||||
|
||||
#: members/tables.py:9
|
||||
#: members/tables.py:13
|
||||
msgid "Last paid"
|
||||
msgstr "Last paid"
|
||||
|
||||
#: members/tables.py:13 members/tables.py:28 members/tables.py:41
|
||||
#: members/tables.py:18 members/tables.py:37 members/tables.py:54
|
||||
msgid "Edit"
|
||||
msgstr "Edit"
|
||||
|
||||
#: members/tables.py:15 members/tables.py:30 members/tables.py:43
|
||||
#: members/tables.py:20 members/tables.py:39 members/tables.py:56
|
||||
msgid "Options"
|
||||
msgstr "Options"
|
||||
|
||||
@@ -294,10 +294,16 @@ msgid "Add member"
|
||||
msgstr "Add member"
|
||||
|
||||
#: members/templates/member_add.html:15 members/templates/member_edit.html:18
|
||||
#: members/templates/payment_add.html:15 members/templates/payment_edit.html:18
|
||||
#: members/templates/payment_add.html:20 members/templates/payment_edit.html:18
|
||||
msgid "Save"
|
||||
msgstr "Save"
|
||||
|
||||
#: members/templates/member_add_many.html:8
|
||||
#, fuzzy
|
||||
#| msgid "Add member"
|
||||
msgid "Add many members"
|
||||
msgstr "Add member"
|
||||
|
||||
#: members/templates/member_add_many.html:13
|
||||
msgid ""
|
||||
"\n"
|
||||
@@ -326,10 +332,41 @@ msgstr ""
|
||||
msgid "Syntax"
|
||||
msgstr "Syntax"
|
||||
|
||||
#: members/templates/member_add_many.html:32
|
||||
#: members/templates/member_add_many.html:29
|
||||
msgid "Data"
|
||||
msgstr ""
|
||||
|
||||
#: members/templates/member_add_many.html:33
|
||||
#, fuzzy
|
||||
#| msgid "Payments"
|
||||
msgid "Payment source"
|
||||
msgstr "Payments"
|
||||
|
||||
#: members/templates/member_add_many.html:37
|
||||
#, fuzzy
|
||||
#| msgid "List payments"
|
||||
msgid "Cash payment"
|
||||
msgstr "List payments"
|
||||
|
||||
#: members/templates/member_add_many.html:41
|
||||
#: members/templates/member_add_many_confirm.html:22
|
||||
msgid "Send"
|
||||
msgstr "Send"
|
||||
|
||||
#: members/templates/member_add_many_confirm.html:8
|
||||
msgid "Confirm adding these entries?"
|
||||
msgstr ""
|
||||
|
||||
#: members/templates/member_add_many_confirm.html:12
|
||||
#: members/templates/members_base.html:52 webapp/templates/main_index.html:7
|
||||
msgid "Members"
|
||||
msgstr "Members"
|
||||
|
||||
#: members/templates/member_add_many_confirm.html:16
|
||||
#: members/templates/members_base.html:60
|
||||
msgid "Payments"
|
||||
msgstr "Payments"
|
||||
|
||||
#: members/templates/member_delete_confirm.html:9
|
||||
msgid "Are you sure you want to delete this member?"
|
||||
msgstr "Are you sure you want to delete this member?"
|
||||
@@ -339,31 +376,6 @@ msgstr "Are you sure you want to delete this member?"
|
||||
msgid "Yes, I'm sure"
|
||||
msgstr "Yes, I'm sure"
|
||||
|
||||
#: members/templates/member_duplicates.html:9
|
||||
msgid "Conflicting member entries"
|
||||
msgstr ""
|
||||
|
||||
#: members/templates/member_duplicates.html:13
|
||||
msgid ""
|
||||
"Found conflicting member entries. Choose how to handle the problematic data."
|
||||
msgstr ""
|
||||
|
||||
#: members/templates/member_duplicates.html:29
|
||||
msgid "Which one has the correct information for this member?"
|
||||
msgstr ""
|
||||
|
||||
#: members/templates/member_duplicates.html:31
|
||||
msgid "Accept first and remove second"
|
||||
msgstr ""
|
||||
|
||||
#: members/templates/member_duplicates.html:32
|
||||
msgid "Accept second and remove first"
|
||||
msgstr ""
|
||||
|
||||
#: members/templates/member_duplicates.html:33
|
||||
msgid "Accept both as two members"
|
||||
msgstr ""
|
||||
|
||||
#: members/templates/member_edit.html:9
|
||||
msgid "Edit member"
|
||||
msgstr "Edit member"
|
||||
@@ -372,19 +384,21 @@ msgstr "Edit member"
|
||||
msgid "Member register"
|
||||
msgstr "Member register"
|
||||
|
||||
#: members/templates/member_list.html:16
|
||||
msgid ""
|
||||
"There are duplicate member entries in the register.\n"
|
||||
" Please visit <a href=\"/members/duplicates\">duplicate resolver</a>."
|
||||
msgstr ""
|
||||
|
||||
#: members/templates/member_list.html:28
|
||||
#: members/templates/member_list.html:21
|
||||
#, fuzzy
|
||||
#| msgid "Member register"
|
||||
msgid "Members in register:"
|
||||
msgstr "Member register"
|
||||
|
||||
#: members/templates/member_list.html:34
|
||||
#: members/templates/member_list.html:28 members/templates/payment_list.html:25
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: members/templates/member_list.html:36 members/templates/payment_list.html:33
|
||||
msgid "Showing results for"
|
||||
msgstr ""
|
||||
|
||||
#: members/templates/member_list.html:44
|
||||
msgid "Download CSV"
|
||||
msgstr "Download CSV"
|
||||
|
||||
@@ -393,10 +407,6 @@ msgstr "Download CSV"
|
||||
msgid "Member register of SIK ry"
|
||||
msgstr "Member register of SIK ry"
|
||||
|
||||
#: members/templates/members_base.html:52 webapp/templates/main_index.html:7
|
||||
msgid "Members"
|
||||
msgstr "Members"
|
||||
|
||||
#: members/templates/members_base.html:54
|
||||
msgid "List members"
|
||||
msgstr "List members"
|
||||
@@ -405,15 +415,11 @@ msgstr "List members"
|
||||
msgid "Add multiple"
|
||||
msgstr "Add multiple"
|
||||
|
||||
#: members/templates/members_base.html:60
|
||||
msgid "Payments"
|
||||
msgstr "Payments"
|
||||
|
||||
#: members/templates/members_base.html:62
|
||||
msgid "List payments"
|
||||
msgstr "List payments"
|
||||
|
||||
#: members/templates/members_base.html:63 members/templates/payment_add.html:8
|
||||
#: members/templates/members_base.html:63 members/templates/payment_add.html:13
|
||||
msgid "Add payment"
|
||||
msgstr "Add payment"
|
||||
|
||||
@@ -441,121 +447,136 @@ msgstr "Edit payment"
|
||||
msgid "Payment events"
|
||||
msgstr "Payment events"
|
||||
|
||||
#: members/views.py:129 members/views.py:186 members/views.py:205
|
||||
msgid "No member id specified"
|
||||
msgstr "No member id specified"
|
||||
#: members/templates/payment_list.html:18
|
||||
#, fuzzy
|
||||
#| msgid "Member register"
|
||||
msgid "Payments in register:"
|
||||
msgstr "Member register"
|
||||
|
||||
#: members/views.py:151
|
||||
msgid "Successfully added member"
|
||||
msgstr "Successfully added member"
|
||||
|
||||
#: members/views.py:172
|
||||
msgid "Successfully updated member"
|
||||
msgstr "Successfully updated member"
|
||||
|
||||
#: members/views.py:176
|
||||
msgid "Could not update member object"
|
||||
msgstr "Could not update member object"
|
||||
|
||||
#: members/views.py:190
|
||||
msgid "Successfully deleted member"
|
||||
msgstr "Successfully deleted member"
|
||||
|
||||
#: members/views.py:196
|
||||
msgid "Could not delete member object"
|
||||
msgstr "Could not delete member object"
|
||||
|
||||
#: members/views.py:239 members/views.py:273 members/views.py:291
|
||||
#: members/views/applications.py:49 members/views/applications.py:96
|
||||
#: members/views/applications.py:124
|
||||
msgid "No application id specified"
|
||||
msgstr "No application id specified"
|
||||
|
||||
#: members/views.py:260
|
||||
#: members/views/applications.py:77
|
||||
msgid "Successfully accepted application"
|
||||
msgstr "Successfully accepted application"
|
||||
|
||||
#: members/views.py:263
|
||||
#: members/views/applications.py:84
|
||||
msgid "Could not accept application object"
|
||||
msgstr "Could not accept application object"
|
||||
|
||||
#: members/views.py:277
|
||||
#: members/views/applications.py:100
|
||||
msgid "Successfully deleted application"
|
||||
msgstr "Successfully deleted application"
|
||||
|
||||
#: members/views.py:282
|
||||
#: members/views/applications.py:112
|
||||
msgid "Could not delete application object"
|
||||
msgstr "Could not delete application object"
|
||||
|
||||
#: members/views.py:346
|
||||
msgid "Successfully added payment for member"
|
||||
msgstr "Successfully added payment for member"
|
||||
#: members/views/members.py:70 members/views/members.py:163
|
||||
#: members/views/members.py:189
|
||||
msgid "No member id specified"
|
||||
msgstr "No member id specified"
|
||||
|
||||
#: members/views.py:359 members/views.py:372 members/views.py:386
|
||||
msgid "No payment id specified"
|
||||
msgstr "No payment id specified"
|
||||
|
||||
#: members/views.py:390
|
||||
msgid "Successfully deleted payment"
|
||||
msgstr "Successfully deleted payment"
|
||||
|
||||
#: members/views.py:395
|
||||
msgid "Could not delete payment object"
|
||||
msgstr "Could not delete payment object"
|
||||
|
||||
#: members/views.py:410
|
||||
msgid "Successfully updated payment"
|
||||
msgstr "Successfully updated payment"
|
||||
|
||||
#: members/views.py:413
|
||||
msgid "Could not update payment object"
|
||||
msgstr "Could not update payment object"
|
||||
|
||||
#: members/views.py:430
|
||||
msgid "Missing \"textfield\" POST request field"
|
||||
msgstr "Missing \"textfield\" POST request field"
|
||||
|
||||
#: members/views.py:435
|
||||
msgid "Successfully imported multiple members"
|
||||
msgstr "Successfully imported multiple members"
|
||||
|
||||
#: members/views.py:438
|
||||
#: members/views/members.py:105
|
||||
msgid "Failed to import members"
|
||||
msgstr "Failed to import members"
|
||||
|
||||
#: members/views.py:504
|
||||
#, fuzzy
|
||||
#| msgid "Successfully deleted member"
|
||||
msgid "Successfully resolved all member conflicts."
|
||||
#: members/views/members.py:118
|
||||
msgid "Successfully added member"
|
||||
msgstr "Successfully added member"
|
||||
|
||||
#: members/views/members.py:143
|
||||
msgid "Successfully updated member"
|
||||
msgstr "Successfully updated member"
|
||||
|
||||
#: members/views/members.py:151
|
||||
msgid "Could not update member object"
|
||||
msgstr "Could not update member object"
|
||||
|
||||
#: members/views/members.py:167
|
||||
msgid "Successfully deleted member"
|
||||
msgstr "Successfully deleted member"
|
||||
|
||||
#: members/views/members.py:178
|
||||
msgid "Could not delete member object"
|
||||
msgstr "Could not delete member object"
|
||||
|
||||
#: members/views/payments.py:69
|
||||
msgid "Successfully added payment for member"
|
||||
msgstr "Successfully added payment for member"
|
||||
|
||||
#: members/views/payments.py:87 members/views/payments.py:105
|
||||
#: members/views/payments.py:124
|
||||
msgid "No payment id specified"
|
||||
msgstr "No payment id specified"
|
||||
|
||||
#: members/views/payments.py:129
|
||||
msgid "Successfully deleted payment"
|
||||
msgstr "Successfully deleted payment"
|
||||
|
||||
#: members/views/payments.py:139
|
||||
msgid "Could not delete payment object"
|
||||
msgstr "Could not delete payment object"
|
||||
|
||||
#: members/views/payments.py:158
|
||||
msgid "Successfully updated payment"
|
||||
msgstr "Successfully updated payment"
|
||||
|
||||
#: members/views/payments.py:165
|
||||
msgid "Could not update payment object"
|
||||
msgstr "Could not update payment object"
|
||||
|
||||
#: members/views/utils.py:110
|
||||
msgid "Missing \"textfield\" POST request field"
|
||||
msgstr "Missing \"textfield\" POST request field"
|
||||
|
||||
#: templates/footer.html:7
|
||||
msgid "Copyright Aalto-yliopiston Sähköinsinöörikilta ry"
|
||||
msgstr "Copyright Aalto-yliopiston Sähköinsinöörikilta ry"
|
||||
|
||||
#: webapp/models.py:47
|
||||
#: webapp/models.py:61
|
||||
#, fuzzy
|
||||
#| msgid "Add member"
|
||||
msgid "Board member"
|
||||
msgstr "Add member"
|
||||
|
||||
#: webapp/models.py:54
|
||||
#: webapp/models.py:67
|
||||
#, fuzzy
|
||||
#| msgid "Duration"
|
||||
msgid "Description"
|
||||
msgstr "Duration"
|
||||
|
||||
#: webapp/models.py:55
|
||||
#: webapp/models.py:68
|
||||
msgid "Summary"
|
||||
msgstr ""
|
||||
|
||||
#: webapp/models.py:70
|
||||
#: webapp/models.py:96
|
||||
msgid "Message"
|
||||
msgstr ""
|
||||
|
||||
#: webapp/models.py:109
|
||||
msgid "Year"
|
||||
msgstr ""
|
||||
|
||||
#: webapp/models.py:123
|
||||
msgid "Role"
|
||||
msgstr ""
|
||||
|
||||
#: webapp/models.py:125
|
||||
msgid "Start date"
|
||||
msgstr ""
|
||||
|
||||
#: webapp/models.py:71
|
||||
#: webapp/models.py:126
|
||||
msgid "End date"
|
||||
msgstr ""
|
||||
|
||||
#: webapp/models.py:79
|
||||
#: webapp/models.py:136
|
||||
msgid "Official"
|
||||
msgstr ""
|
||||
|
||||
#: webapp/models.py:138
|
||||
msgid "Phone number"
|
||||
msgstr ""
|
||||
|
||||
@@ -603,5 +624,29 @@ msgstr "Sössö"
|
||||
msgid "Contact"
|
||||
msgstr "Contact"
|
||||
|
||||
#: webapp/templates/ohlhafv.html:8
|
||||
msgid "Ohlhafv"
|
||||
msgstr ""
|
||||
|
||||
#: webapp/templates/ohlhafv.html:15
|
||||
msgid "Challenge"
|
||||
msgstr ""
|
||||
|
||||
#: webapp/templates/ohlhafv_list.html:11
|
||||
msgid "All challenges"
|
||||
msgstr ""
|
||||
|
||||
#: webapp/templates/ohlhafv_list.html:15
|
||||
msgid "Total challenges:"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Successfully imported multiple members"
|
||||
#~ msgstr "Successfully imported multiple members"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Successfully deleted member"
|
||||
#~ msgid "Successfully resolved all member conflicts."
|
||||
#~ msgstr "Successfully deleted member"
|
||||
|
||||
#~ msgid "Select"
|
||||
#~ msgstr "Select"
|
||||
|
||||
Binary file not shown.
+169
-142
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-06-07 18:22+0300\n"
|
||||
"POT-Creation-Date: 2017-09-25 21:32+0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -18,35 +18,35 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: infoscreen/models.py:77
|
||||
#: infoscreen/models.py:97
|
||||
msgid "ABB jobs"
|
||||
msgstr "ABB-työpaikat"
|
||||
|
||||
#: infoscreen/models.py:88
|
||||
#: infoscreen/models.py:112
|
||||
msgid "APY Item"
|
||||
msgstr "ÄPY-tilastot"
|
||||
|
||||
#: infoscreen/models.py:99
|
||||
#: infoscreen/models.py:127
|
||||
msgid "External website"
|
||||
msgstr "Ulkoinen verkkosivu"
|
||||
|
||||
#: infoscreen/models.py:147
|
||||
#: infoscreen/models.py:184
|
||||
msgid "Sössö articles"
|
||||
msgstr "Sössön artikkelit"
|
||||
|
||||
#: infoscreen/models.py:158
|
||||
#: infoscreen/models.py:199
|
||||
msgid "Events"
|
||||
msgstr "Tapahtumat"
|
||||
|
||||
#: infoscreen/models.py:169
|
||||
#: infoscreen/models.py:214
|
||||
msgid "Image"
|
||||
msgstr "Kuva"
|
||||
|
||||
#: infoscreen/models.py:204
|
||||
#: infoscreen/models.py:260
|
||||
msgid "HSL timetables"
|
||||
msgstr "HSL-aikataulut"
|
||||
|
||||
#: infoscreen/models.py:215
|
||||
#: infoscreen/models.py:275
|
||||
msgid "External image"
|
||||
msgstr "Ulkoinen kuva"
|
||||
|
||||
@@ -168,7 +168,8 @@ msgstr "Valitse muokattava rotaatio"
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
|
||||
#: infoscreen/templates/infoscreen_admin.html:141 webapp/models.py:46
|
||||
#: infoscreen/templates/infoscreen_admin.html:141 webapp/models.py:60
|
||||
#: webapp/models.py:94 webapp/models.py:107
|
||||
msgid "Name"
|
||||
msgstr "Nimi"
|
||||
|
||||
@@ -182,14 +183,12 @@ msgid "Language"
|
||||
msgstr "Kieli"
|
||||
|
||||
#: infoscreen/templates/infoscreen_admin.html:161
|
||||
#: members/templates/settings.html:20 sikweb/settings-sample.py:179
|
||||
#: sikweb/settings.py:178
|
||||
#: members/templates/settings.html:20 sikweb/base.py:216
|
||||
msgid "Finnish"
|
||||
msgstr "suomi"
|
||||
|
||||
#: infoscreen/templates/infoscreen_admin.html:162
|
||||
#: members/templates/settings.html:21 sikweb/settings-sample.py:178
|
||||
#: sikweb/settings.py:177
|
||||
#: members/templates/settings.html:21 sikweb/base.py:217
|
||||
msgid "English"
|
||||
msgstr "englanti"
|
||||
|
||||
@@ -198,31 +197,32 @@ msgstr "englanti"
|
||||
msgid "Submit"
|
||||
msgstr "Lisää"
|
||||
|
||||
#: members/forms.py:20 members/tables.py:24
|
||||
#: members/forms.py:103 members/tables.py:32
|
||||
msgid "Member"
|
||||
msgstr "Jäsen"
|
||||
|
||||
#: members/models.py:16
|
||||
#: members/models.py:13
|
||||
msgid "First name"
|
||||
msgstr "Etunimi"
|
||||
|
||||
#: members/models.py:17
|
||||
#: members/models.py:14
|
||||
msgid "Last name"
|
||||
msgstr "Sukunimi"
|
||||
|
||||
#: members/models.py:18
|
||||
#: members/models.py:15 webapp/models.py:95 webapp/models.py:108
|
||||
msgid "Email"
|
||||
msgstr "Sähköposti"
|
||||
|
||||
#: members/models.py:19
|
||||
#: members/models.py:16
|
||||
msgid "Place of residence"
|
||||
msgstr "Asuinpaikka"
|
||||
|
||||
#: members/models.py:20 members/models.py:83
|
||||
#: members/models.py:18 members/models.py:83
|
||||
#: members/templates/member_add_many.html:35
|
||||
msgid "AYY"
|
||||
msgstr "AYY"
|
||||
|
||||
#: members/models.py:21
|
||||
#: members/models.py:19
|
||||
msgid "JAS"
|
||||
msgstr "JAS"
|
||||
|
||||
@@ -242,7 +242,7 @@ msgstr "Lähde"
|
||||
msgid "Cash"
|
||||
msgstr "Käteinen"
|
||||
|
||||
#: members/models.py:85
|
||||
#: members/models.py:85 members/templates/member_add_many.html:36
|
||||
msgid "Bank transfer"
|
||||
msgstr "Tilisiirto"
|
||||
|
||||
@@ -250,15 +250,15 @@ msgstr "Tilisiirto"
|
||||
msgid "Created"
|
||||
msgstr "Lisätty"
|
||||
|
||||
#: members/tables.py:9
|
||||
#: members/tables.py:13
|
||||
msgid "Last paid"
|
||||
msgstr "Viimeksi maksettu"
|
||||
|
||||
#: members/tables.py:13 members/tables.py:28 members/tables.py:41
|
||||
#: members/tables.py:18 members/tables.py:37 members/tables.py:54
|
||||
msgid "Edit"
|
||||
msgstr "Muokkaa"
|
||||
|
||||
#: members/tables.py:15 members/tables.py:30 members/tables.py:43
|
||||
#: members/tables.py:20 members/tables.py:39 members/tables.py:56
|
||||
msgid "Options"
|
||||
msgstr "Asetukset"
|
||||
|
||||
@@ -291,10 +291,14 @@ msgid "Add member"
|
||||
msgstr "Lisää jäsen"
|
||||
|
||||
#: members/templates/member_add.html:15 members/templates/member_edit.html:18
|
||||
#: members/templates/payment_add.html:15 members/templates/payment_edit.html:18
|
||||
#: members/templates/payment_add.html:20 members/templates/payment_edit.html:18
|
||||
msgid "Save"
|
||||
msgstr "Tallenna"
|
||||
|
||||
#: members/templates/member_add_many.html:8
|
||||
msgid "Add many members"
|
||||
msgstr "Lisää useita"
|
||||
|
||||
#: members/templates/member_add_many.html:13
|
||||
msgid ""
|
||||
"\n"
|
||||
@@ -326,10 +330,37 @@ msgstr ""
|
||||
msgid "Syntax"
|
||||
msgstr "Syntaksi"
|
||||
|
||||
#: members/templates/member_add_many.html:32
|
||||
#: members/templates/member_add_many.html:29
|
||||
msgid "Data"
|
||||
msgstr "Data"
|
||||
|
||||
#: members/templates/member_add_many.html:33
|
||||
msgid "Payment source"
|
||||
msgstr "Maksutapa"
|
||||
|
||||
#: members/templates/member_add_many.html:37
|
||||
msgid "Cash payment"
|
||||
msgstr "Käteismaksu"
|
||||
|
||||
#: members/templates/member_add_many.html:41
|
||||
#: members/templates/member_add_many_confirm.html:22
|
||||
msgid "Send"
|
||||
msgstr "Lähetä"
|
||||
|
||||
#: members/templates/member_add_many_confirm.html:8
|
||||
msgid "Confirm adding these entries?"
|
||||
msgstr "Vahvista muutokset?"
|
||||
|
||||
#: members/templates/member_add_many_confirm.html:12
|
||||
#: members/templates/members_base.html:52 webapp/templates/main_index.html:7
|
||||
msgid "Members"
|
||||
msgstr "Jäsenet"
|
||||
|
||||
#: members/templates/member_add_many_confirm.html:16
|
||||
#: members/templates/members_base.html:60
|
||||
msgid "Payments"
|
||||
msgstr "Maksutapahtumat"
|
||||
|
||||
#: members/templates/member_delete_confirm.html:9
|
||||
msgid "Are you sure you want to delete this member?"
|
||||
msgstr "Oletko varma, että haluat poistaa tämän jäsenen?"
|
||||
@@ -339,32 +370,6 @@ msgstr "Oletko varma, että haluat poistaa tämän jäsenen?"
|
||||
msgid "Yes, I'm sure"
|
||||
msgstr "Kyllä, olen varma"
|
||||
|
||||
#: members/templates/member_duplicates.html:9
|
||||
msgid "Conflicting member entries"
|
||||
msgstr "Ongelmalliset jäsentiedot"
|
||||
|
||||
#: members/templates/member_duplicates.html:13
|
||||
msgid ""
|
||||
"Found conflicting member entries. Choose how to handle the problematic data."
|
||||
msgstr ""
|
||||
"Ongelmallista jäsendataa havaittu. Valitse, miten ongelmat ratkaistaan."
|
||||
|
||||
#: members/templates/member_duplicates.html:29
|
||||
msgid "Which one has the correct information for this member?"
|
||||
msgstr "Kummassa on jäsenen oikeat tiedot?"
|
||||
|
||||
#: members/templates/member_duplicates.html:31
|
||||
msgid "Accept first and remove second"
|
||||
msgstr "Hyväksy ensimmäinen ja poista toinen"
|
||||
|
||||
#: members/templates/member_duplicates.html:32
|
||||
msgid "Accept second and remove first"
|
||||
msgstr "Hyväksy toinen ja poista ensimmäinen"
|
||||
|
||||
#: members/templates/member_duplicates.html:33
|
||||
msgid "Accept both as two members"
|
||||
msgstr "Hyväksy molemmat kahtena jäsenenä"
|
||||
|
||||
#: members/templates/member_edit.html:9
|
||||
msgid "Edit member"
|
||||
msgstr "Muokkaa jäsentä"
|
||||
@@ -373,20 +378,19 @@ msgstr "Muokkaa jäsentä"
|
||||
msgid "Member register"
|
||||
msgstr "Jäsenrekisteri"
|
||||
|
||||
#: members/templates/member_list.html:16
|
||||
msgid ""
|
||||
"There are duplicate member entries in the register.\n"
|
||||
" Please visit <a href=\"/members/duplicates\">duplicate resolver</a>."
|
||||
msgstr ""
|
||||
"Jäsenrekisterissä on duplikaattijäseniä.\n"
|
||||
" Käytä ongelman ratkaisuun <a href=\"/members/duplicates"
|
||||
"\">duplikaattityökalua</a>."
|
||||
|
||||
#: members/templates/member_list.html:28
|
||||
#: members/templates/member_list.html:21
|
||||
msgid "Members in register:"
|
||||
msgstr "Jäseniä:"
|
||||
|
||||
#: members/templates/member_list.html:34
|
||||
#: members/templates/member_list.html:28 members/templates/payment_list.html:25
|
||||
msgid "Search"
|
||||
msgstr "Hae"
|
||||
|
||||
#: members/templates/member_list.html:36 members/templates/payment_list.html:33
|
||||
msgid "Showing results for"
|
||||
msgstr "Näytetään tulokset haulle"
|
||||
|
||||
#: members/templates/member_list.html:44
|
||||
msgid "Download CSV"
|
||||
msgstr "Lataa CSV"
|
||||
|
||||
@@ -395,10 +399,6 @@ msgstr "Lataa CSV"
|
||||
msgid "Member register of SIK ry"
|
||||
msgstr "Aalto-yliopiston Sähköinsinöörikilta ry:n jäsenrekisteri"
|
||||
|
||||
#: members/templates/members_base.html:52 webapp/templates/main_index.html:7
|
||||
msgid "Members"
|
||||
msgstr "Jäsenet"
|
||||
|
||||
#: members/templates/members_base.html:54
|
||||
msgid "List members"
|
||||
msgstr "Jäsenlistaus"
|
||||
@@ -407,15 +407,11 @@ msgstr "Jäsenlistaus"
|
||||
msgid "Add multiple"
|
||||
msgstr "Lisää useita"
|
||||
|
||||
#: members/templates/members_base.html:60
|
||||
msgid "Payments"
|
||||
msgstr "Maksutapahtumat"
|
||||
|
||||
#: members/templates/members_base.html:62
|
||||
msgid "List payments"
|
||||
msgstr "Maksulistaus"
|
||||
|
||||
#: members/templates/members_base.html:63 members/templates/payment_add.html:8
|
||||
#: members/templates/members_base.html:63 members/templates/payment_add.html:13
|
||||
msgid "Add payment"
|
||||
msgstr "Lisää maksu"
|
||||
|
||||
@@ -443,115 +439,130 @@ msgstr "Muokkaa maksua"
|
||||
msgid "Payment events"
|
||||
msgstr "Maksutapahtumat"
|
||||
|
||||
#: members/views.py:129 members/views.py:186 members/views.py:205
|
||||
msgid "No member id specified"
|
||||
msgstr "Jäsenen ID ei määritelty"
|
||||
#: members/templates/payment_list.html:18
|
||||
msgid "Payments in register:"
|
||||
msgstr "Maksutapahtumia:"
|
||||
|
||||
#: members/views.py:151
|
||||
msgid "Successfully added member"
|
||||
msgstr "Onnistuneesti lisättiin jäsen"
|
||||
|
||||
#: members/views.py:172
|
||||
msgid "Successfully updated member"
|
||||
msgstr "Onnistuneesti päivitettiin jäsen"
|
||||
|
||||
#: members/views.py:176
|
||||
msgid "Could not update member object"
|
||||
msgstr "Jäsenobjektia ei voitu päivittää"
|
||||
|
||||
#: members/views.py:190
|
||||
msgid "Successfully deleted member"
|
||||
msgstr "Onnistuneesti poistettiin jäsen"
|
||||
|
||||
#: members/views.py:196
|
||||
msgid "Could not delete member object"
|
||||
msgstr "Jäsenobjektia ei voitu poistaa"
|
||||
|
||||
#: members/views.py:239 members/views.py:273 members/views.py:291
|
||||
#: members/views/applications.py:49 members/views/applications.py:96
|
||||
#: members/views/applications.py:124
|
||||
msgid "No application id specified"
|
||||
msgstr "Hakemuksen ID ei määritelty"
|
||||
|
||||
#: members/views.py:260
|
||||
#: members/views/applications.py:77
|
||||
msgid "Successfully accepted application"
|
||||
msgstr "Onnistuneesti hyväksyttiin hakemus"
|
||||
|
||||
#: members/views.py:263
|
||||
#: members/views/applications.py:84
|
||||
msgid "Could not accept application object"
|
||||
msgstr "Hakemusobjektia ei voitu hyväksyä"
|
||||
|
||||
#: members/views.py:277
|
||||
#: members/views/applications.py:100
|
||||
msgid "Successfully deleted application"
|
||||
msgstr "Onnistuneesti poistettiin hakemus"
|
||||
|
||||
#: members/views.py:282
|
||||
#: members/views/applications.py:112
|
||||
msgid "Could not delete application object"
|
||||
msgstr "Hakemusobjektia ei voitu poistaa"
|
||||
|
||||
#: members/views.py:346
|
||||
msgid "Successfully added payment for member"
|
||||
msgstr "Onnistuneesti lisättiin maksutapahtuma jäsenelle"
|
||||
#: members/views/members.py:70 members/views/members.py:163
|
||||
#: members/views/members.py:189
|
||||
msgid "No member id specified"
|
||||
msgstr "Jäsenen ID ei määritelty"
|
||||
|
||||
#: members/views.py:359 members/views.py:372 members/views.py:386
|
||||
msgid "No payment id specified"
|
||||
msgstr "Maksutapahtuman ID ei määritelty"
|
||||
|
||||
#: members/views.py:390
|
||||
msgid "Successfully deleted payment"
|
||||
msgstr "Onnistuneesti poistettiin maksutapahtuma"
|
||||
|
||||
#: members/views.py:395
|
||||
msgid "Could not delete payment object"
|
||||
msgstr "Maksutapahtumaobjektia ei voitu poistaa"
|
||||
|
||||
#: members/views.py:410
|
||||
msgid "Successfully updated payment"
|
||||
msgstr "Onnistuneesti päivitettiin maksutapahtuma"
|
||||
|
||||
#: members/views.py:413
|
||||
msgid "Could not update payment object"
|
||||
msgstr "Maksutapahtumaobjektia ei voitu päivittää"
|
||||
|
||||
#: members/views.py:430
|
||||
msgid "Missing \"textfield\" POST request field"
|
||||
msgstr "Puuttuva \"textfield\" POST-kenttä"
|
||||
|
||||
#: members/views.py:435
|
||||
msgid "Successfully imported multiple members"
|
||||
msgstr "Onnistuneesti tuotu useita jäseniä"
|
||||
|
||||
#: members/views.py:438
|
||||
#: members/views/members.py:105
|
||||
msgid "Failed to import members"
|
||||
msgstr "Jäsenten tuonti epäonnistui"
|
||||
|
||||
#: members/views.py:504
|
||||
msgid "Successfully resolved all member conflicts."
|
||||
msgstr "Kaikki jäsenkonfliktit ratkaistu onnistuneesti."
|
||||
#: members/views/members.py:118
|
||||
msgid "Successfully added member"
|
||||
msgstr "Onnistuneesti lisättiin jäsen"
|
||||
|
||||
#: members/views/members.py:143
|
||||
msgid "Successfully updated member"
|
||||
msgstr "Onnistuneesti päivitettiin jäsen"
|
||||
|
||||
#: members/views/members.py:151
|
||||
msgid "Could not update member object"
|
||||
msgstr "Jäsenobjektia ei voitu päivittää"
|
||||
|
||||
#: members/views/members.py:167
|
||||
msgid "Successfully deleted member"
|
||||
msgstr "Onnistuneesti poistettiin jäsen"
|
||||
|
||||
#: members/views/members.py:178
|
||||
msgid "Could not delete member object"
|
||||
msgstr "Jäsenobjektia ei voitu poistaa"
|
||||
|
||||
#: members/views/payments.py:69
|
||||
msgid "Successfully added payment for member"
|
||||
msgstr "Onnistuneesti lisättiin maksutapahtuma jäsenelle"
|
||||
|
||||
#: members/views/payments.py:87 members/views/payments.py:105
|
||||
#: members/views/payments.py:124
|
||||
msgid "No payment id specified"
|
||||
msgstr "Maksutapahtuman ID ei määritelty"
|
||||
|
||||
#: members/views/payments.py:129
|
||||
msgid "Successfully deleted payment"
|
||||
msgstr "Onnistuneesti poistettiin maksutapahtuma"
|
||||
|
||||
#: members/views/payments.py:139
|
||||
msgid "Could not delete payment object"
|
||||
msgstr "Maksutapahtumaobjektia ei voitu poistaa"
|
||||
|
||||
#: members/views/payments.py:158
|
||||
msgid "Successfully updated payment"
|
||||
msgstr "Onnistuneesti päivitettiin maksutapahtuma"
|
||||
|
||||
#: members/views/payments.py:165
|
||||
msgid "Could not update payment object"
|
||||
msgstr "Maksutapahtumaobjektia ei voitu päivittää"
|
||||
|
||||
#: members/views/utils.py:110
|
||||
msgid "Missing \"textfield\" POST request field"
|
||||
msgstr "Puuttuva \"textfield\" POST-kenttä"
|
||||
|
||||
#: templates/footer.html:7
|
||||
msgid "Copyright Aalto-yliopiston Sähköinsinöörikilta ry"
|
||||
msgstr "Copyright Aalto-yliopiston Sähköinsinöörikilta ry"
|
||||
|
||||
#: webapp/models.py:47
|
||||
#: webapp/models.py:61
|
||||
msgid "Board member"
|
||||
msgstr "Hallituksen jäsen"
|
||||
|
||||
#: webapp/models.py:54
|
||||
#: webapp/models.py:67
|
||||
msgid "Description"
|
||||
msgstr "Kuvaus"
|
||||
|
||||
#: webapp/models.py:55
|
||||
#: webapp/models.py:68
|
||||
msgid "Summary"
|
||||
msgstr "Tiivistelmä"
|
||||
|
||||
#: webapp/models.py:70
|
||||
#: webapp/models.py:96
|
||||
msgid "Message"
|
||||
msgstr "Viesti"
|
||||
|
||||
#: webapp/models.py:109
|
||||
msgid "Year"
|
||||
msgstr "Vuosi"
|
||||
|
||||
#: webapp/models.py:123
|
||||
msgid "Role"
|
||||
msgstr "Rooli"
|
||||
|
||||
#: webapp/models.py:125
|
||||
msgid "Start date"
|
||||
msgstr "Alkupäivämäärä"
|
||||
|
||||
#: webapp/models.py:71
|
||||
#: webapp/models.py:126
|
||||
msgid "End date"
|
||||
msgstr "Loppupäivämäärä"
|
||||
|
||||
#: webapp/models.py:79
|
||||
#: webapp/models.py:136
|
||||
msgid "Official"
|
||||
msgstr "Toimihenkilö"
|
||||
|
||||
#: webapp/models.py:138
|
||||
msgid "Phone number"
|
||||
msgstr "Puhelinnumero"
|
||||
|
||||
@@ -598,3 +609,19 @@ msgstr "Sössö"
|
||||
#: webapp/templates/navigation.html:32
|
||||
msgid "Contact"
|
||||
msgstr "Yhteystiedot"
|
||||
|
||||
#: webapp/templates/ohlhafv.html:8
|
||||
msgid "Ohlhafv"
|
||||
msgstr "Øhlhäfv"
|
||||
|
||||
#: webapp/templates/ohlhafv.html:15
|
||||
msgid "Challenge"
|
||||
msgstr "Haaste"
|
||||
|
||||
#: webapp/templates/ohlhafv_list.html:11
|
||||
msgid "All challenges"
|
||||
msgstr "Kaikki haasteet"
|
||||
|
||||
#: webapp/templates/ohlhafv_list.html:15
|
||||
msgid "Total challenges:"
|
||||
msgstr "Haasteita yhteensä:"
|
||||
|
||||
+1
-2
@@ -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'
|
||||
|
||||
+17
-4
@@ -1,13 +1,14 @@
|
||||
"""File containing member forms."""
|
||||
|
||||
from django import forms
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from members.models import Member, Payment, Request
|
||||
|
||||
import csv
|
||||
import datetime
|
||||
import logging
|
||||
from dal import autocomplete
|
||||
|
||||
|
||||
class CSVValidationError(Exception):
|
||||
@@ -37,11 +38,18 @@ class MemberForm(forms.ModelForm):
|
||||
|
||||
return email
|
||||
|
||||
def _clean_boolean_field(self, key):
|
||||
value = self.data.get(key, None)
|
||||
if value in ['1', '0']:
|
||||
return bool(int(value))
|
||||
else:
|
||||
return value == 'on'
|
||||
|
||||
def clean_jas(self):
|
||||
return bool(int(self.data['jas']))
|
||||
return self._clean_boolean_field('jas')
|
||||
|
||||
def clean_AYY(self):
|
||||
return bool(int(self.data['AYY']))
|
||||
return self._clean_boolean_field('AYY')
|
||||
|
||||
@staticmethod
|
||||
def csv_to_models(data, payment_source='AYY'):
|
||||
@@ -73,7 +81,7 @@ class MemberForm(forms.ModelForm):
|
||||
payment_data = {
|
||||
'source': payment_source,
|
||||
'member': member.id,
|
||||
'date': datetime.datetime.now(),
|
||||
'date': timezone.now(),
|
||||
}
|
||||
form = PaymentForm(payment_data)
|
||||
if not form.is_valid():
|
||||
@@ -88,6 +96,11 @@ class MemberForm(forms.ModelForm):
|
||||
class PaymentForm(forms.ModelForm):
|
||||
"""Payment model form."""
|
||||
|
||||
member = forms.ModelChoiceField(
|
||||
queryset=Member.objects.all(),
|
||||
widget=autocomplete.ModelSelect2(url='member-autocomplete')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
"""Meta for Payment model form."""
|
||||
|
||||
|
||||
@@ -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',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2017-09-25 16:24
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('members', '0015_auto_20170925_1917'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='member',
|
||||
name='created',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='payment',
|
||||
name='date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date'),
|
||||
),
|
||||
]
|
||||
+2
-22
@@ -4,7 +4,6 @@ from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from datetime import datetime
|
||||
import csv
|
||||
|
||||
|
||||
@@ -79,7 +78,7 @@ class Request(BaseMember):
|
||||
class Payment(models.Model):
|
||||
"""Payment model representing one payment event."""
|
||||
|
||||
date = models.DateTimeField(_('Date'), default=datetime.now)
|
||||
date = models.DateTimeField(_('Date'), default=timezone.now)
|
||||
source = models.CharField(_('Source'), choices=[
|
||||
('AYY', _('AYY')),
|
||||
('cash', _('Cash')),
|
||||
@@ -100,7 +99,7 @@ class Payment(models.Model):
|
||||
class Member(BaseMember):
|
||||
"""Member model represets one member on the registry."""
|
||||
|
||||
created = models.DateTimeField(_('Created'), default=datetime.now)
|
||||
created = models.DateTimeField(_('Created'), default=timezone.now)
|
||||
|
||||
def last_paid(self):
|
||||
"""Return member's last payment."""
|
||||
@@ -128,24 +127,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
|
||||
|
||||
@@ -11,13 +11,6 @@
|
||||
<h2>{% trans "Member register" %}</h2>
|
||||
</div>
|
||||
|
||||
{% if is_member_conflict %}
|
||||
<div class="alert alert-warning">
|
||||
{% blocktrans %}There are duplicate member entries in the register.
|
||||
Please visit <a href="/members/duplicates">duplicate resolver</a>.{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if notification %}
|
||||
<div class="alert alert-success">
|
||||
{{ notification }}
|
||||
@@ -28,6 +21,23 @@
|
||||
<span>{% trans "Members in register:" %} {{ member_count }}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<form class="input-group" method="GET" action="/members/list">
|
||||
<input class="form-control" type="text" name="q" placeholder="Teemu Teekkari" />
|
||||
<span class="input-group-btn">
|
||||
<input type="submit" class="btn" value="{% trans "Search" %}" />
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if request.GET.q %}
|
||||
<div>
|
||||
<div class="alert alert-info" role="alert">
|
||||
{% trans "Showing results for" %} "{{ request.GET.q }}"
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{{ table|safe }}
|
||||
|
||||
<div>
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<script
|
||||
src="https://code.jquery.com/jquery-3.2.1.min.js"
|
||||
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<div>
|
||||
<h3>{% trans "Add payment" %}</h3>
|
||||
|
||||
@@ -18,4 +23,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ form.media }}
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@@ -14,6 +14,27 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="member_count">
|
||||
<span>{% trans "Payments in register:" %} {{ payment_count }}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<form class="input-group" method="GET" action="/members/payments">
|
||||
<input class="form-control" type="text" name="q" placeholder="Teemu Teekkari" />
|
||||
<span class="input-group-btn">
|
||||
<input type="submit" class="btn" value="{% trans "Search" %}" />
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if request.GET.q %}
|
||||
<div>
|
||||
<div class="alert alert-info" role="alert">
|
||||
{% trans "Showing results for" %} "{{ request.GET.q }}"
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{{ table|safe }}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
+21
-5
@@ -12,9 +12,11 @@ class MemberRegisterTestCase(TestCase):
|
||||
def setUp(self):
|
||||
"""Setup testing environment by creating member and admin."""
|
||||
memb = Member.objects.create(first_name="Tidus", last_name="Tester")
|
||||
username, password = 'test_admin', 'password123'
|
||||
test_admin = User.objects.create_superuser(
|
||||
'test_admin', 'myemail@test.com', 'password123')
|
||||
username, 'myemail@test.com', password)
|
||||
self.c = Client()
|
||||
self.c.login(username=username, password=password)
|
||||
|
||||
def test_member_created(self):
|
||||
"""Test member creation."""
|
||||
@@ -24,13 +26,27 @@ class MemberRegisterTestCase(TestCase):
|
||||
def test_import_csv_single_line(self):
|
||||
"""Test csv import only with single line in csv file."""
|
||||
data = 'Teppo, Tulppu, teppo@tulppu.fi, Ankkalinna, 0, 0'
|
||||
response = self.c.post('/members/import_csv', {'textarea': data})
|
||||
self.assertIn(response.status_code, [200, 302])
|
||||
response = self.c.post('/members/import_csv', {'textarea': data}, follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_import_csv_multi_line(self):
|
||||
"""Test csv import with multilined csv."""
|
||||
data = ('Teppo, Tulppu, teppo@tulppu.fi, Ankkalinna, 0, 0\n'
|
||||
'Reiska, Remontti, remontti@reiska.fi, Värisilmä, 1, 1')
|
||||
|
||||
response = self.c.post('/members/import_csv', {'textarea': data})
|
||||
self.assertIn(response.status_code, [200, 302])
|
||||
response = self.c.post('/members/import_csv', {'textarea': data}, follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_autocomplete_search_found(self):
|
||||
"""Test member autocomplete search"""
|
||||
search_terms = 'Tidus'
|
||||
response = self.c.get('/members/member-autocomplete?q={}'.format(search_terms), follow=True)
|
||||
results = response.json()['results']
|
||||
self.assertEqual(len(results), 1)
|
||||
|
||||
def test_autocomplete_search_not_found(self):
|
||||
"""Test member autocomplete search"""
|
||||
search_terms = 'Notfound'
|
||||
response = self.c.get('/members/member-autocomplete?q={}'.format(search_terms), follow=True)
|
||||
results = response.json()['results']
|
||||
self.assertEqual(len(results), 0)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""File containing Member application URLs."""
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.views.generic.base import RedirectView
|
||||
|
||||
# members
|
||||
@@ -20,6 +21,9 @@ from members.views import member_delete
|
||||
from members.views import payment_list
|
||||
from members.views import add_many_confirm
|
||||
|
||||
# autocomplete view
|
||||
from members.views import MemberAutoComplete
|
||||
|
||||
# rest api
|
||||
from members.views import MemberDetail
|
||||
|
||||
@@ -36,6 +40,10 @@ from members.views import application_form_success
|
||||
favicon_view = RedirectView.as_view(
|
||||
url='static/img/favicon.ico', permanent=True)
|
||||
|
||||
member_autocomplete_view = login_required(
|
||||
permission_required('members.change_member', login_url='/login')(MemberAutoComplete.as_view())
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
# landing page
|
||||
@@ -110,4 +118,11 @@ urlpatterns = [
|
||||
# rest api url
|
||||
url(r'^api/members/(?P<pk>\d+)$', MemberDetail.as_view()),
|
||||
|
||||
# member select autocomplete view
|
||||
url(
|
||||
r'^member-autocomplete/$',
|
||||
member_autocomplete_view,
|
||||
name='member-autocomplete',
|
||||
),
|
||||
|
||||
]
|
||||
|
||||
+1
-594
@@ -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
|
||||
@@ -31,588 +25,11 @@ import csv
|
||||
import pickle
|
||||
from smtplib import SMTPAuthenticationError
|
||||
|
||||
from members.models import Member, Request, Payment, MemberConflict
|
||||
from members.models import Member, Request, Payment
|
||||
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."""
|
||||
@@ -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, )
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
from members.views.members import *
|
||||
from members.views.applications import *
|
||||
from members.views.payments import *
|
||||
from members.views.utils import *
|
||||
@@ -0,0 +1,142 @@
|
||||
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
|
||||
|
||||
import logging
|
||||
import html
|
||||
|
||||
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', {})
|
||||
@@ -0,0 +1,205 @@
|
||||
from django.shortcuts import render
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.utils.decorators import method_decorator
|
||||
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 dal import autocomplete
|
||||
|
||||
import logging
|
||||
import html
|
||||
|
||||
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."""
|
||||
search = request.GET.get('q', None)
|
||||
if search:
|
||||
firsts = Member.objects.filter(first_name__istartswith=search)
|
||||
lasts = Member.objects.filter(last_name__istartswith=search)
|
||||
members = firsts | lasts
|
||||
else:
|
||||
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.cleaned_data))
|
||||
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})
|
||||
|
||||
|
||||
class MemberAutoComplete(autocomplete.Select2QuerySetView):
|
||||
|
||||
def get_queryset(self):
|
||||
qs = Member.objects.all()
|
||||
|
||||
if self.q:
|
||||
firsts = qs.filter(first_name__istartswith=self.q)
|
||||
lasts = qs.filter(last_name__istartswith=self.q)
|
||||
qs = firsts | lasts
|
||||
|
||||
return qs
|
||||
@@ -0,0 +1,165 @@
|
||||
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
|
||||
|
||||
import logging
|
||||
import html
|
||||
|
||||
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."""
|
||||
search = request.GET.get('q', None)
|
||||
if search:
|
||||
firsts = Payment.objects.filter(member__first_name__istartswith=search)
|
||||
lasts = Payment.objects.filter(member__last_name__istartswith=search)
|
||||
payments = firsts | lasts
|
||||
else:
|
||||
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.cleaned_data))
|
||||
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')})
|
||||
@@ -0,0 +1,172 @@
|
||||
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
|
||||
|
||||
import logging
|
||||
import csv
|
||||
|
||||
# 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
|
||||
from members.forms import MemberForm, PaymentForm, ApplicationForm, CSVValidationError
|
||||
from members.tables import MemberTable, PaymentTable, RequestTable
|
||||
|
||||
|
||||
# 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)
|
||||
@@ -26,3 +26,5 @@ django-modeltranslation==0.12.1
|
||||
django-auditlog==0.4.3
|
||||
django-phonenumber-field==1.3.0
|
||||
paho-mqtt==1.3.0
|
||||
django-autocomplete-light==3.2.10
|
||||
six==1.10.0
|
||||
|
||||
@@ -64,6 +64,8 @@ LOGGING = {
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'modeltranslation', # has to be before admin for translation admin to work
|
||||
'dal',
|
||||
'dal_select2',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
|
||||
Reference in New Issue
Block a user