Merge branch 'develop' into 'master'

Search bar implementation

See merge request !50
This commit is contained in:
Jan Tuomi
2017-09-25 22:49:13 +03:00
25 changed files with 1237 additions and 923 deletions
+29 -2
View File
@@ -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)
+3 -2
View File
@@ -1,7 +1,8 @@
from django.shortcuts import render from django.shortcuts import render
from django.http import JsonResponse from django.http import JsonResponse
import datetime from django.utils import timezone
from .mqtt import get_latest from .mqtt import get_latest
import coffee_scale.mqtt # somehow this is needed import coffee_scale.mqtt # somehow this is needed
@@ -15,7 +16,7 @@ def coffee_view(request):
def cups_view(request): def cups_view(request):
now = datetime.datetime.now() now = timezone.now()
latest = get_latest() latest = get_latest()
data = { data = {
'date': now, 'date': now,
+5 -4
View File
@@ -3,7 +3,8 @@
import urllib.request import urllib.request
import json import json
import logging import logging
from datetime import datetime, timedelta from datetime import timedelta, datetime
from django.utils import timezone
from django.conf import settings from django.conf import settings
@@ -20,7 +21,7 @@ class HSLFetcher:
def fetch_if_needed(self): def fetch_if_needed(self):
"""Check if new fetch from HSL API is needed.""" """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)): timedelta(minutes=HSLFetcher.INTERVAL)):
self.fetch() self.fetch()
@@ -38,7 +39,7 @@ class HSLFetcher:
arr = [] arr = []
time = (datetime.now() + time = (timezone.now() +
timedelta(minutes=settings.HSL_DEPARTURE_THRESHOLD)) timedelta(minutes=settings.HSL_DEPARTURE_THRESHOLD))
time = "{0:02d}{0:02d}".format(time.hour, time.minute) time = "{0:02d}{0:02d}".format(time.hour, time.minute)
for element in data: for element in data:
@@ -65,7 +66,7 @@ class HSLFetcher:
obj = model_arr[count - 1] obj = model_arr[count - 1]
obj.data = json_dump obj.data = json_dump
obj.save() obj.save()
now = datetime.now() now = timezone.now()
HSLFetcher.last_fetched = now HSLFetcher.last_fetched = now
logging.info( logging.info(
Binary file not shown.
+184 -139
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,35 +17,35 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: infoscreen/models.py:77 #: infoscreen/models.py:97
msgid "ABB jobs" msgid "ABB jobs"
msgstr "ABB jobs" msgstr "ABB jobs"
#: infoscreen/models.py:88 #: infoscreen/models.py:112
msgid "APY Item" msgid "APY Item"
msgstr "ÄPY Item" msgstr "ÄPY Item"
#: infoscreen/models.py:99 #: infoscreen/models.py:127
msgid "External website" msgid "External website"
msgstr "External website" msgstr "External website"
#: infoscreen/models.py:147 #: infoscreen/models.py:184
msgid "Sössö articles" msgid "Sössö articles"
msgstr "Sössö articles" msgstr "Sössö articles"
#: infoscreen/models.py:158 #: infoscreen/models.py:199
msgid "Events" msgid "Events"
msgstr "Events" msgstr "Events"
#: infoscreen/models.py:169 #: infoscreen/models.py:214
msgid "Image" msgid "Image"
msgstr "Image" msgstr "Image"
#: infoscreen/models.py:204 #: infoscreen/models.py:260
msgid "HSL timetables" msgid "HSL timetables"
msgstr "HSL timetables" msgstr "HSL timetables"
#: infoscreen/models.py:215 #: infoscreen/models.py:275
msgid "External image" msgid "External image"
msgstr "External image" msgstr "External image"
@@ -169,7 +169,8 @@ msgstr "Select rotation to edit"
msgid "id" msgid "id"
msgstr "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" msgid "Name"
msgstr "Name" msgstr "Name"
@@ -183,14 +184,12 @@ msgid "Language"
msgstr "Language" msgstr "Language"
#: infoscreen/templates/infoscreen_admin.html:161 #: infoscreen/templates/infoscreen_admin.html:161
#: members/templates/settings.html:20 sikweb/settings-sample.py:179 #: members/templates/settings.html:20 sikweb/base.py:216
#: sikweb/settings.py:178
msgid "Finnish" msgid "Finnish"
msgstr "Finnish" msgstr "Finnish"
#: infoscreen/templates/infoscreen_admin.html:162 #: infoscreen/templates/infoscreen_admin.html:162
#: members/templates/settings.html:21 sikweb/settings-sample.py:178 #: members/templates/settings.html:21 sikweb/base.py:217
#: sikweb/settings.py:177
msgid "English" msgid "English"
msgstr "English" msgstr "English"
@@ -201,31 +200,32 @@ msgstr "English"
msgid "Submit" msgid "Submit"
msgstr "Submitted" msgstr "Submitted"
#: members/forms.py:20 members/tables.py:24 #: members/forms.py:103 members/tables.py:32
msgid "Member" msgid "Member"
msgstr "Member" msgstr "Member"
#: members/models.py:16 #: members/models.py:13
msgid "First name" msgid "First name"
msgstr "First name" msgstr "First name"
#: members/models.py:17 #: members/models.py:14
msgid "Last name" msgid "Last name"
msgstr "Last name" msgstr "Last name"
#: members/models.py:18 #: members/models.py:15 webapp/models.py:95 webapp/models.py:108
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
#: members/models.py:19 #: members/models.py:16
msgid "Place of residence" msgid "Place of residence"
msgstr "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" msgid "AYY"
msgstr "AYY" msgstr "AYY"
#: members/models.py:21 #: members/models.py:19
msgid "JAS" msgid "JAS"
msgstr "JAS" msgstr "JAS"
@@ -245,7 +245,7 @@ msgstr "Source"
msgid "Cash" msgid "Cash"
msgstr "Cash" msgstr "Cash"
#: members/models.py:85 #: members/models.py:85 members/templates/member_add_many.html:36
msgid "Bank transfer" msgid "Bank transfer"
msgstr "Bank transfer" msgstr "Bank transfer"
@@ -253,15 +253,15 @@ msgstr "Bank transfer"
msgid "Created" msgid "Created"
msgstr "Created" msgstr "Created"
#: members/tables.py:9 #: members/tables.py:13
msgid "Last paid" msgid "Last paid"
msgstr "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" msgid "Edit"
msgstr "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" msgid "Options"
msgstr "Options" msgstr "Options"
@@ -294,10 +294,16 @@ msgid "Add member"
msgstr "Add member" msgstr "Add member"
#: members/templates/member_add.html:15 members/templates/member_edit.html:18 #: 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" msgid "Save"
msgstr "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 #: members/templates/member_add_many.html:13
msgid "" msgid ""
"\n" "\n"
@@ -326,10 +332,41 @@ msgstr ""
msgid "Syntax" msgid "Syntax"
msgstr "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" msgid "Send"
msgstr "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 #: members/templates/member_delete_confirm.html:9
msgid "Are you sure you want to delete this member?" msgid "Are you sure you want to delete this member?"
msgstr "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" msgid "Yes, I'm sure"
msgstr "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 #: members/templates/member_edit.html:9
msgid "Edit member" msgid "Edit member"
msgstr "Edit member" msgstr "Edit member"
@@ -372,19 +384,21 @@ msgstr "Edit member"
msgid "Member register" msgid "Member register"
msgstr "Member register" msgstr "Member register"
#: members/templates/member_list.html:16 #: members/templates/member_list.html:21
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
#, fuzzy #, fuzzy
#| msgid "Member register" #| msgid "Member register"
msgid "Members in register:" msgid "Members in register:"
msgstr "Member 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" msgid "Download CSV"
msgstr "Download CSV" msgstr "Download CSV"
@@ -393,10 +407,6 @@ msgstr "Download CSV"
msgid "Member register of SIK ry" msgid "Member register of SIK ry"
msgstr "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 #: members/templates/members_base.html:54
msgid "List members" msgid "List members"
msgstr "List members" msgstr "List members"
@@ -405,15 +415,11 @@ msgstr "List members"
msgid "Add multiple" msgid "Add multiple"
msgstr "Add multiple" msgstr "Add multiple"
#: members/templates/members_base.html:60
msgid "Payments"
msgstr "Payments"
#: members/templates/members_base.html:62 #: members/templates/members_base.html:62
msgid "List payments" msgid "List payments"
msgstr "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" msgid "Add payment"
msgstr "Add payment" msgstr "Add payment"
@@ -441,121 +447,136 @@ msgstr "Edit payment"
msgid "Payment events" msgid "Payment events"
msgstr "Payment events" msgstr "Payment events"
#: members/views.py:129 members/views.py:186 members/views.py:205 #: members/templates/payment_list.html:18
msgid "No member id specified" #, fuzzy
msgstr "No member id specified" #| msgid "Member register"
msgid "Payments in register:"
msgstr "Member register"
#: members/views.py:151 #: members/views/applications.py:49 members/views/applications.py:96
msgid "Successfully added member" #: members/views/applications.py:124
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
msgid "No application id specified" msgid "No application id specified"
msgstr "No application id specified" msgstr "No application id specified"
#: members/views.py:260 #: members/views/applications.py:77
msgid "Successfully accepted application" msgid "Successfully accepted application"
msgstr "Successfully accepted application" msgstr "Successfully accepted application"
#: members/views.py:263 #: members/views/applications.py:84
msgid "Could not accept application object" msgid "Could not accept application object"
msgstr "Could not accept application object" msgstr "Could not accept application object"
#: members/views.py:277 #: members/views/applications.py:100
msgid "Successfully deleted application" msgid "Successfully deleted application"
msgstr "Successfully deleted application" msgstr "Successfully deleted application"
#: members/views.py:282 #: members/views/applications.py:112
msgid "Could not delete application object" msgid "Could not delete application object"
msgstr "Could not delete application object" msgstr "Could not delete application object"
#: members/views.py:346 #: members/views/members.py:70 members/views/members.py:163
msgid "Successfully added payment for member" #: members/views/members.py:189
msgstr "Successfully added payment for member" msgid "No member id specified"
msgstr "No member id specified"
#: members/views.py:359 members/views.py:372 members/views.py:386 #: members/views/members.py:105
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
msgid "Failed to import members" msgid "Failed to import members"
msgstr "Failed to import members" msgstr "Failed to import members"
#: members/views.py:504 #: members/views/members.py:118
#, fuzzy msgid "Successfully added member"
#| msgid "Successfully deleted member" msgstr "Successfully added member"
msgid "Successfully resolved all member conflicts."
#: 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" 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 #: templates/footer.html:7
msgid "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" msgid "Copyright Aalto-yliopiston Sähköinsinöörikilta ry"
msgstr "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 #, fuzzy
#| msgid "Add member" #| msgid "Add member"
msgid "Board member" msgid "Board member"
msgstr "Add member" msgstr "Add member"
#: webapp/models.py:54 #: webapp/models.py:67
#, fuzzy #, fuzzy
#| msgid "Duration" #| msgid "Duration"
msgid "Description" msgid "Description"
msgstr "Duration" msgstr "Duration"
#: webapp/models.py:55 #: webapp/models.py:68
msgid "Summary" msgid "Summary"
msgstr "" 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" msgid "Start date"
msgstr "" msgstr ""
#: webapp/models.py:71 #: webapp/models.py:126
msgid "End date" msgid "End date"
msgstr "" msgstr ""
#: webapp/models.py:79 #: webapp/models.py:136
msgid "Official"
msgstr ""
#: webapp/models.py:138
msgid "Phone number" msgid "Phone number"
msgstr "" msgstr ""
@@ -603,5 +624,29 @@ msgstr "Sössö"
msgid "Contact" msgid "Contact"
msgstr "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" #~ msgid "Select"
#~ msgstr "Select" #~ msgstr "Select"
Binary file not shown.
+169 -142
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,35 +18,35 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: infoscreen/models.py:77 #: infoscreen/models.py:97
msgid "ABB jobs" msgid "ABB jobs"
msgstr "ABB-työpaikat" msgstr "ABB-työpaikat"
#: infoscreen/models.py:88 #: infoscreen/models.py:112
msgid "APY Item" msgid "APY Item"
msgstr "ÄPY-tilastot" msgstr "ÄPY-tilastot"
#: infoscreen/models.py:99 #: infoscreen/models.py:127
msgid "External website" msgid "External website"
msgstr "Ulkoinen verkkosivu" msgstr "Ulkoinen verkkosivu"
#: infoscreen/models.py:147 #: infoscreen/models.py:184
msgid "Sössö articles" msgid "Sössö articles"
msgstr "Sössön artikkelit" msgstr "Sössön artikkelit"
#: infoscreen/models.py:158 #: infoscreen/models.py:199
msgid "Events" msgid "Events"
msgstr "Tapahtumat" msgstr "Tapahtumat"
#: infoscreen/models.py:169 #: infoscreen/models.py:214
msgid "Image" msgid "Image"
msgstr "Kuva" msgstr "Kuva"
#: infoscreen/models.py:204 #: infoscreen/models.py:260
msgid "HSL timetables" msgid "HSL timetables"
msgstr "HSL-aikataulut" msgstr "HSL-aikataulut"
#: infoscreen/models.py:215 #: infoscreen/models.py:275
msgid "External image" msgid "External image"
msgstr "Ulkoinen kuva" msgstr "Ulkoinen kuva"
@@ -168,7 +168,8 @@ msgstr "Valitse muokattava rotaatio"
msgid "id" msgid "id"
msgstr "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" msgid "Name"
msgstr "Nimi" msgstr "Nimi"
@@ -182,14 +183,12 @@ msgid "Language"
msgstr "Kieli" msgstr "Kieli"
#: infoscreen/templates/infoscreen_admin.html:161 #: infoscreen/templates/infoscreen_admin.html:161
#: members/templates/settings.html:20 sikweb/settings-sample.py:179 #: members/templates/settings.html:20 sikweb/base.py:216
#: sikweb/settings.py:178
msgid "Finnish" msgid "Finnish"
msgstr "suomi" msgstr "suomi"
#: infoscreen/templates/infoscreen_admin.html:162 #: infoscreen/templates/infoscreen_admin.html:162
#: members/templates/settings.html:21 sikweb/settings-sample.py:178 #: members/templates/settings.html:21 sikweb/base.py:217
#: sikweb/settings.py:177
msgid "English" msgid "English"
msgstr "englanti" msgstr "englanti"
@@ -198,31 +197,32 @@ msgstr "englanti"
msgid "Submit" msgid "Submit"
msgstr "Lisää" msgstr "Lisää"
#: members/forms.py:20 members/tables.py:24 #: members/forms.py:103 members/tables.py:32
msgid "Member" msgid "Member"
msgstr "Jäsen" msgstr "Jäsen"
#: members/models.py:16 #: members/models.py:13
msgid "First name" msgid "First name"
msgstr "Etunimi" msgstr "Etunimi"
#: members/models.py:17 #: members/models.py:14
msgid "Last name" msgid "Last name"
msgstr "Sukunimi" msgstr "Sukunimi"
#: members/models.py:18 #: members/models.py:15 webapp/models.py:95 webapp/models.py:108
msgid "Email" msgid "Email"
msgstr "Sähköposti" msgstr "Sähköposti"
#: members/models.py:19 #: members/models.py:16
msgid "Place of residence" msgid "Place of residence"
msgstr "Asuinpaikka" 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" msgid "AYY"
msgstr "AYY" msgstr "AYY"
#: members/models.py:21 #: members/models.py:19
msgid "JAS" msgid "JAS"
msgstr "JAS" msgstr "JAS"
@@ -242,7 +242,7 @@ msgstr "Lähde"
msgid "Cash" msgid "Cash"
msgstr "Käteinen" msgstr "Käteinen"
#: members/models.py:85 #: members/models.py:85 members/templates/member_add_many.html:36
msgid "Bank transfer" msgid "Bank transfer"
msgstr "Tilisiirto" msgstr "Tilisiirto"
@@ -250,15 +250,15 @@ msgstr "Tilisiirto"
msgid "Created" msgid "Created"
msgstr "Lisätty" msgstr "Lisätty"
#: members/tables.py:9 #: members/tables.py:13
msgid "Last paid" msgid "Last paid"
msgstr "Viimeksi maksettu" 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" msgid "Edit"
msgstr "Muokkaa" 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" msgid "Options"
msgstr "Asetukset" msgstr "Asetukset"
@@ -291,10 +291,14 @@ msgid "Add member"
msgstr "Lisää jäsen" msgstr "Lisää jäsen"
#: members/templates/member_add.html:15 members/templates/member_edit.html:18 #: 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" msgid "Save"
msgstr "Tallenna" msgstr "Tallenna"
#: members/templates/member_add_many.html:8
msgid "Add many members"
msgstr "Lisää useita"
#: members/templates/member_add_many.html:13 #: members/templates/member_add_many.html:13
msgid "" msgid ""
"\n" "\n"
@@ -326,10 +330,37 @@ msgstr ""
msgid "Syntax" msgid "Syntax"
msgstr "Syntaksi" 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" msgid "Send"
msgstr "Lähetä" 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 #: members/templates/member_delete_confirm.html:9
msgid "Are you sure you want to delete this member?" msgid "Are you sure you want to delete this member?"
msgstr "Oletko varma, että haluat poistaa tämän jäsenen?" 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" msgid "Yes, I'm sure"
msgstr "Kyllä, olen varma" 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 #: members/templates/member_edit.html:9
msgid "Edit member" msgid "Edit member"
msgstr "Muokkaa jäsentä" msgstr "Muokkaa jäsentä"
@@ -373,20 +378,19 @@ msgstr "Muokkaa jäsentä"
msgid "Member register" msgid "Member register"
msgstr "Jäsenrekisteri" msgstr "Jäsenrekisteri"
#: members/templates/member_list.html:16 #: members/templates/member_list.html:21
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
msgid "Members in register:" msgid "Members in register:"
msgstr "Jäseniä:" 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" msgid "Download CSV"
msgstr "Lataa CSV" msgstr "Lataa CSV"
@@ -395,10 +399,6 @@ msgstr "Lataa CSV"
msgid "Member register of SIK ry" msgid "Member register of SIK ry"
msgstr "Aalto-yliopiston Sähköinsinöörikilta ry:n jäsenrekisteri" 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 #: members/templates/members_base.html:54
msgid "List members" msgid "List members"
msgstr "Jäsenlistaus" msgstr "Jäsenlistaus"
@@ -407,15 +407,11 @@ msgstr "Jäsenlistaus"
msgid "Add multiple" msgid "Add multiple"
msgstr "Lisää useita" msgstr "Lisää useita"
#: members/templates/members_base.html:60
msgid "Payments"
msgstr "Maksutapahtumat"
#: members/templates/members_base.html:62 #: members/templates/members_base.html:62
msgid "List payments" msgid "List payments"
msgstr "Maksulistaus" 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" msgid "Add payment"
msgstr "Lisää maksu" msgstr "Lisää maksu"
@@ -443,115 +439,130 @@ msgstr "Muokkaa maksua"
msgid "Payment events" msgid "Payment events"
msgstr "Maksutapahtumat" msgstr "Maksutapahtumat"
#: members/views.py:129 members/views.py:186 members/views.py:205 #: members/templates/payment_list.html:18
msgid "No member id specified" msgid "Payments in register:"
msgstr "Jäsenen ID ei määritelty" msgstr "Maksutapahtumia:"
#: members/views.py:151 #: members/views/applications.py:49 members/views/applications.py:96
msgid "Successfully added member" #: members/views/applications.py:124
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
msgid "No application id specified" msgid "No application id specified"
msgstr "Hakemuksen ID ei määritelty" msgstr "Hakemuksen ID ei määritelty"
#: members/views.py:260 #: members/views/applications.py:77
msgid "Successfully accepted application" msgid "Successfully accepted application"
msgstr "Onnistuneesti hyväksyttiin hakemus" msgstr "Onnistuneesti hyväksyttiin hakemus"
#: members/views.py:263 #: members/views/applications.py:84
msgid "Could not accept application object" msgid "Could not accept application object"
msgstr "Hakemusobjektia ei voitu hyväksyä" msgstr "Hakemusobjektia ei voitu hyväksyä"
#: members/views.py:277 #: members/views/applications.py:100
msgid "Successfully deleted application" msgid "Successfully deleted application"
msgstr "Onnistuneesti poistettiin hakemus" msgstr "Onnistuneesti poistettiin hakemus"
#: members/views.py:282 #: members/views/applications.py:112
msgid "Could not delete application object" msgid "Could not delete application object"
msgstr "Hakemusobjektia ei voitu poistaa" msgstr "Hakemusobjektia ei voitu poistaa"
#: members/views.py:346 #: members/views/members.py:70 members/views/members.py:163
msgid "Successfully added payment for member" #: members/views/members.py:189
msgstr "Onnistuneesti lisättiin maksutapahtuma jäsenelle" msgid "No member id specified"
msgstr "Jäsenen ID ei määritelty"
#: members/views.py:359 members/views.py:372 members/views.py:386 #: members/views/members.py:105
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
msgid "Failed to import members" msgid "Failed to import members"
msgstr "Jäsenten tuonti epäonnistui" msgstr "Jäsenten tuonti epäonnistui"
#: members/views.py:504 #: members/views/members.py:118
msgid "Successfully resolved all member conflicts." msgid "Successfully added member"
msgstr "Kaikki jäsenkonfliktit ratkaistu onnistuneesti." 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 #: templates/footer.html:7
msgid "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" msgid "Copyright Aalto-yliopiston Sähköinsinöörikilta ry"
msgstr "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" msgid "Board member"
msgstr "Hallituksen jäsen" msgstr "Hallituksen jäsen"
#: webapp/models.py:54 #: webapp/models.py:67
msgid "Description" msgid "Description"
msgstr "Kuvaus" msgstr "Kuvaus"
#: webapp/models.py:55 #: webapp/models.py:68
msgid "Summary" msgid "Summary"
msgstr "Tiivistelmä" 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" msgid "Start date"
msgstr "Alkupäivämäärä" msgstr "Alkupäivämäärä"
#: webapp/models.py:71 #: webapp/models.py:126
msgid "End date" msgid "End date"
msgstr "Loppupäivämäärä" msgstr "Loppupäivämäärä"
#: webapp/models.py:79 #: webapp/models.py:136
msgid "Official"
msgstr "Toimihenkilö"
#: webapp/models.py:138
msgid "Phone number" msgid "Phone number"
msgstr "Puhelinnumero" msgstr "Puhelinnumero"
@@ -598,3 +609,19 @@ msgstr "Sössö"
#: webapp/templates/navigation.html:32 #: webapp/templates/navigation.html:32
msgid "Contact" msgid "Contact"
msgstr "Yhteystiedot" 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
View File
@@ -1,12 +1,11 @@
"""Admin site registers for Members app.""" """Admin site registers for Members app."""
from django.contrib import admin from django.contrib import admin
from members.models import Member, Request, Payment, MemberConflict from members.models import Member, Request, Payment
# Register your models here. # Register your models here.
admin.site.register(Member) admin.site.register(Member)
admin.site.register(Request) admin.site.register(Request)
admin.site.register(Payment) admin.site.register(Payment)
admin.site.register(MemberConflict)
admin.site.site_header = 'SIK Admin' admin.site.site_header = 'SIK Admin'
+17 -4
View File
@@ -1,13 +1,14 @@
"""File containing member forms.""" """File containing member forms."""
from django import forms from django import forms
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from members.models import Member, Payment, Request from members.models import Member, Payment, Request
import csv import csv
import datetime
import logging import logging
from dal import autocomplete
class CSVValidationError(Exception): class CSVValidationError(Exception):
@@ -37,11 +38,18 @@ class MemberForm(forms.ModelForm):
return email 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): def clean_jas(self):
return bool(int(self.data['jas'])) return self._clean_boolean_field('jas')
def clean_AYY(self): def clean_AYY(self):
return bool(int(self.data['AYY'])) return self._clean_boolean_field('AYY')
@staticmethod @staticmethod
def csv_to_models(data, payment_source='AYY'): def csv_to_models(data, payment_source='AYY'):
@@ -73,7 +81,7 @@ class MemberForm(forms.ModelForm):
payment_data = { payment_data = {
'source': payment_source, 'source': payment_source,
'member': member.id, 'member': member.id,
'date': datetime.datetime.now(), 'date': timezone.now(),
} }
form = PaymentForm(payment_data) form = PaymentForm(payment_data)
if not form.is_valid(): if not form.is_valid():
@@ -88,6 +96,11 @@ class MemberForm(forms.ModelForm):
class PaymentForm(forms.ModelForm): class PaymentForm(forms.ModelForm):
"""Payment model form.""" """Payment model form."""
member = forms.ModelChoiceField(
queryset=Member.objects.all(),
widget=autocomplete.ModelSelect2(url='member-autocomplete')
)
class Meta: class Meta:
"""Meta for Payment model form.""" """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
View File
@@ -4,7 +4,6 @@ from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from datetime import datetime
import csv import csv
@@ -79,7 +78,7 @@ class Request(BaseMember):
class Payment(models.Model): class Payment(models.Model):
"""Payment model representing one payment event.""" """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=[ source = models.CharField(_('Source'), choices=[
('AYY', _('AYY')), ('AYY', _('AYY')),
('cash', _('Cash')), ('cash', _('Cash')),
@@ -100,7 +99,7 @@ class Payment(models.Model):
class Member(BaseMember): class Member(BaseMember):
"""Member model represets one member on the registry.""" """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): def last_paid(self):
"""Return member's last payment.""" """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 # To avoid problems with a cyclical import, this is at the bottom of the file
from members.forms import MemberForm # nopep8 from members.forms import MemberForm # nopep8
+17 -7
View File
@@ -11,13 +11,6 @@
<h2>{% trans "Member register" %}</h2> <h2>{% trans "Member register" %}</h2>
</div> </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 %} {% if notification %}
<div class="alert alert-success"> <div class="alert alert-success">
{{ notification }} {{ notification }}
@@ -28,6 +21,23 @@
<span>{% trans "Members in register:" %} {{ member_count }}</span> <span>{% trans "Members in register:" %} {{ member_count }}</span>
</div> </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 }} {{ table|safe }}
<div> <div>
+8
View File
@@ -4,6 +4,11 @@
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
<script
src="https://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
crossorigin="anonymous"></script>
<div> <div>
<h3>{% trans "Add payment" %}</h3> <h3>{% trans "Add payment" %}</h3>
@@ -18,4 +23,7 @@
</form> </form>
</div> </div>
</div> </div>
{{ form.media }}
{% endblock content %} {% endblock content %}
+21
View File
@@ -14,6 +14,27 @@
</div> </div>
{% endif %} {% 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 }} {{ table|safe }}
</div> </div>
{% endblock content %} {% endblock content %}
+21 -5
View File
@@ -12,9 +12,11 @@ class MemberRegisterTestCase(TestCase):
def setUp(self): def setUp(self):
"""Setup testing environment by creating member and admin.""" """Setup testing environment by creating member and admin."""
memb = Member.objects.create(first_name="Tidus", last_name="Tester") memb = Member.objects.create(first_name="Tidus", last_name="Tester")
username, password = 'test_admin', 'password123'
test_admin = User.objects.create_superuser( test_admin = User.objects.create_superuser(
'test_admin', 'myemail@test.com', 'password123') username, 'myemail@test.com', password)
self.c = Client() self.c = Client()
self.c.login(username=username, password=password)
def test_member_created(self): def test_member_created(self):
"""Test member creation.""" """Test member creation."""
@@ -24,13 +26,27 @@ class MemberRegisterTestCase(TestCase):
def test_import_csv_single_line(self): def test_import_csv_single_line(self):
"""Test csv import only with single line in csv file.""" """Test csv import only with single line in csv file."""
data = 'Teppo, Tulppu, teppo@tulppu.fi, Ankkalinna, 0, 0' data = 'Teppo, Tulppu, teppo@tulppu.fi, Ankkalinna, 0, 0'
response = self.c.post('/members/import_csv', {'textarea': data}) response = self.c.post('/members/import_csv', {'textarea': data}, follow=True)
self.assertIn(response.status_code, [200, 302]) self.assertEqual(response.status_code, 200)
def test_import_csv_multi_line(self): def test_import_csv_multi_line(self):
"""Test csv import with multilined csv.""" """Test csv import with multilined csv."""
data = ('Teppo, Tulppu, teppo@tulppu.fi, Ankkalinna, 0, 0\n' data = ('Teppo, Tulppu, teppo@tulppu.fi, Ankkalinna, 0, 0\n'
'Reiska, Remontti, remontti@reiska.fi, Värisilmä, 1, 1') 'Reiska, Remontti, remontti@reiska.fi, Värisilmä, 1, 1')
response = self.c.post('/members/import_csv', {'textarea': data}) response = self.c.post('/members/import_csv', {'textarea': data}, follow=True)
self.assertIn(response.status_code, [200, 302]) 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)
+15
View File
@@ -1,6 +1,7 @@
"""File containing Member application URLs.""" """File containing Member application URLs."""
from django.conf.urls import url from django.conf.urls import url
from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
# members # members
@@ -20,6 +21,9 @@ from members.views import member_delete
from members.views import payment_list from members.views import payment_list
from members.views import add_many_confirm from members.views import add_many_confirm
# autocomplete view
from members.views import MemberAutoComplete
# rest api # rest api
from members.views import MemberDetail from members.views import MemberDetail
@@ -36,6 +40,10 @@ from members.views import application_form_success
favicon_view = RedirectView.as_view( favicon_view = RedirectView.as_view(
url='static/img/favicon.ico', permanent=True) url='static/img/favicon.ico', permanent=True)
member_autocomplete_view = login_required(
permission_required('members.change_member', login_url='/login')(MemberAutoComplete.as_view())
)
urlpatterns = [ urlpatterns = [
# landing page # landing page
@@ -110,4 +118,11 @@ urlpatterns = [
# rest api url # rest api url
url(r'^api/members/(?P<pk>\d+)$', MemberDetail.as_view()), 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
View File
@@ -17,12 +17,6 @@ from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.core.mail import send_mail 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 json
import requests import requests
import logging import logging
@@ -31,588 +25,11 @@ import csv
import pickle import pickle
from smtplib import SMTPAuthenticationError 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.forms import MemberForm, PaymentForm, ApplicationForm, CSVValidationError
from members.tables import MemberTable, PaymentTable, RequestTable 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) @receiver(post_save, sender=Request)
def email_on_request(sender, instance, created, **kwargs): def email_on_request(sender, instance, created, **kwargs):
"""Send email validation.""" """Send email validation."""
@@ -641,13 +58,3 @@ def email_on_accept(sender, instance, created, **kwargs):
send_mail_wrapper(subject, message, instance.email) send_mail_wrapper(subject, message, instance.email)
except SMTPAuthenticationError: except SMTPAuthenticationError:
logging.error('Failed to send email to accepted member!') 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, )
+4
View File
@@ -0,0 +1,4 @@
from members.views.members import *
from members.views.applications import *
from members.views.payments import *
from members.views.utils import *
+142
View File
@@ -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', {})
+205
View File
@@ -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
+165
View File
@@ -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')})
+172
View File
@@ -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)
+2
View File
@@ -26,3 +26,5 @@ django-modeltranslation==0.12.1
django-auditlog==0.4.3 django-auditlog==0.4.3
django-phonenumber-field==1.3.0 django-phonenumber-field==1.3.0
paho-mqtt==1.3.0 paho-mqtt==1.3.0
django-autocomplete-light==3.2.10
six==1.10.0
+2
View File
@@ -64,6 +64,8 @@ LOGGING = {
INSTALLED_APPS = [ INSTALLED_APPS = [
'modeltranslation', # has to be before admin for translation admin to work 'modeltranslation', # has to be before admin for translation admin to work
'dal',
'dal_select2',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',