Merge branch 'develop' into feature-infoadmin

This commit is contained in:
Juhana Luomanen
2017-06-01 18:04:26 +03:00
23 changed files with 619 additions and 213 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ test:
pep8:
stage: lint
script:
- pip install -r requirements.txt
- pip install pep8
- pep8 --config=setup.cfg --count .
eslint:
+14
View File
@@ -0,0 +1,14 @@
html {
width: 100%;
height: 100%;
font-size: 16px;
}
body {
background-color: #fcfcfc;
max-width: 750px;
margin: auto;
text-align: center;
color: #100;
font-family: sans-serif;
}
+1 -1
View File
@@ -163,7 +163,7 @@
</select>
</div>
<div class="col-md-3">
<input type="submit" class="btn btn-success">
<input type="submit" class="btn btn-success" value="{% trans "Submit" %}">
</div>
</form>
</div>
Binary file not shown.
+88 -38
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-05-30 20:32+0300\n"
"POT-Creation-Date: 2017-05-29 22:48+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"
@@ -50,7 +50,7 @@ msgid "External image"
msgstr "External image"
#: infoscreen/templates/infoscreen_admin.html:24
#: members/templates/members_base.html:69
#: members/templates/members_base.html:79
msgid "Log out"
msgstr "Log out"
@@ -259,7 +259,7 @@ msgstr "Error"
msgid "Back"
msgstr "Back"
#: members/templates/member_add.html:8 members/templates/members_base.html:45
#: members/templates/member_add.html:8 members/templates/members_base.html:55
msgid "Add member"
msgstr "Add member"
@@ -309,6 +309,31 @@ 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"
@@ -317,51 +342,58 @@ msgstr "Edit member"
msgid "Member register"
msgstr "Member register"
#: members/templates/member_list.html:23
#: 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:30
msgid "Download CSV"
msgstr "Download CSV"
#: members/templates/members_base.html:33
#: members/templates/members_base.html:42
msgid "Member register of SIK ry"
msgstr "Member register of SIK ry"
#: members/templates/members_base.html:42 webapp/templates/main_index.html:7
#: members/templates/members_base.html:52 webapp/templates/main_index.html:7
msgid "Members"
msgstr "Members"
#: members/templates/members_base.html:44
#: members/templates/members_base.html:54
msgid "List members"
msgstr "List members"
#: members/templates/members_base.html:46
#: members/templates/members_base.html:56
msgid "Add multiple"
msgstr "Add multiple"
#: members/templates/members_base.html:50
#: members/templates/members_base.html:60
msgid "Payments"
msgstr "Payments"
#: members/templates/members_base.html:52
#: members/templates/members_base.html:62
msgid "List payments"
msgstr "List payments"
#: members/templates/members_base.html:53 members/templates/payment_add.html:8
#: members/templates/members_base.html:63 members/templates/payment_add.html:8
msgid "Add payment"
msgstr "Add payment"
#: members/templates/members_base.html:57
#: members/templates/members_base.html:67
msgid "Applications"
msgstr "Applications"
#: members/templates/members_base.html:59
#: members/templates/members_base.html:69
msgid "List applications"
msgstr "List applications"
#: members/templates/members_base.html:60
#: members/templates/members_base.html:70
msgid "Application form"
msgstr "Application form"
#: members/templates/members_base.html:65 members/templates/settings.html:11
#: members/templates/members_base.html:75 members/templates/settings.html:11
msgid "Settings"
msgstr "Settings"
@@ -381,96 +413,102 @@ msgstr "Payment events"
msgid "Language"
msgstr "Language"
#: members/templates/settings.html:20 sikweb/settings-sample.py:178
#: sikweb/settings.py:178
#: members/templates/settings.html:20 sikweb/settings-sample.py:179
#: sikweb/settings.py:177
msgid "Finnish"
msgstr "Finnish"
#: members/templates/settings.html:21 sikweb/settings-sample.py:177
#: sikweb/settings.py:177
#: members/templates/settings.html:21 sikweb/settings-sample.py:178
#: sikweb/settings.py:176
msgid "English"
msgstr "English"
#: members/views.py:121 members/views.py:178 members/views.py:197
#: members/views.py:128 members/views.py:185 members/views.py:204
msgid "No member id specified"
msgstr "No member id specified"
#: members/views.py:143
#: members/views.py:150
msgid "Successfully added member"
msgstr "Successfully added member"
#: members/views.py:164
#: members/views.py:171
msgid "Successfully updated member"
msgstr "Successfully updated member"
#: members/views.py:168
#: members/views.py:175
msgid "Could not update member object"
msgstr "Could not update member object"
#: members/views.py:182
#: members/views.py:189
msgid "Successfully deleted member"
msgstr "Successfully deleted member"
#: members/views.py:188
#: members/views.py:195
msgid "Could not delete member object"
msgstr "Could not delete member object"
#: members/views.py:231 members/views.py:265 members/views.py:283
#: members/views.py:238 members/views.py:272 members/views.py:290
msgid "No application id specified"
msgstr "No application id specified"
#: members/views.py:252
#: members/views.py:259
msgid "Successfully accepted application"
msgstr "Successfully accepted application"
#: members/views.py:255
#: members/views.py:262
msgid "Could not accept application object"
msgstr "Could not accept application object"
#: members/views.py:269
#: members/views.py:276
msgid "Successfully deleted application"
msgstr "Successfully deleted application"
#: members/views.py:274
#: members/views.py:281
msgid "Could not delete application object"
msgstr "Could not delete application object"
#: members/views.py:338
#: members/views.py:345
msgid "Successfully added payment for member"
msgstr "Successfully added payment for member"
#: members/views.py:351 members/views.py:364 members/views.py:378
#: members/views.py:358 members/views.py:371 members/views.py:385
msgid "No payment id specified"
msgstr "No payment id specified"
#: members/views.py:382
#: members/views.py:389
msgid "Successfully deleted payment"
msgstr "Successfully deleted payment"
#: members/views.py:387
#: members/views.py:394
msgid "Could not delete payment object"
msgstr "Could not delete payment object"
#: members/views.py:402
#: members/views.py:409
msgid "Successfully updated payment"
msgstr "Successfully updated payment"
#: members/views.py:405
#: members/views.py:412
msgid "Could not update payment object"
msgstr "Could not update payment object"
#: members/views.py:422
#: members/views.py:429
msgid "Missing \"textfield\" POST request field"
msgstr "Missing \"textfield\" POST request field"
#: members/views.py:427
#: members/views.py:434
msgid "Successfully imported multiple members"
msgstr "Successfully imported multiple members"
#: members/views.py:430
#: members/views.py:437
msgid "Failed to import members"
msgstr "Failed to import members"
#: members/views.py:497
#, fuzzy
#| msgid "Successfully deleted member"
msgid "Successfully resolved all member conflicts."
msgstr "Successfully deleted member"
#: templates/footer.html:7
msgid "Copyright Aalto-yliopiston Sähköinsinöörikilta ry"
msgstr "Copyright Aalto-yliopiston Sähköinsinöörikilta ry"
@@ -483,6 +521,18 @@ msgstr "SIK Admin"
msgid "Aalto-yliopiston Sähköinsinöörikilta ry"
msgstr "Aalto-yliopiston Sähköinsinöörikilta ry"
#: webapp/templates/login.html:25 webapp/templates/login.html:27
msgid "Username"
msgstr "Username"
#: webapp/templates/login.html:31 webapp/templates/login.html:33
msgid "Password"
msgstr "Password"
#: webapp/templates/login.html:43
msgid "Log in"
msgstr "Log in"
#: webapp/templates/main_index.html:8
msgid "Infoscreen"
msgstr "Infoscreen"
Binary file not shown.
+89 -38
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-05-30 20:32+0300\n"
"POT-Creation-Date: 2017-05-29 22:48+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"
@@ -51,7 +51,7 @@ msgid "External image"
msgstr "Ulkoinen kuva"
#: infoscreen/templates/infoscreen_admin.html:24
#: members/templates/members_base.html:69
#: members/templates/members_base.html:79
msgid "Log out"
msgstr "Kirjaudu ulos"
@@ -258,7 +258,7 @@ msgstr "Virhe"
msgid "Back"
msgstr "Takaisin"
#: members/templates/member_add.html:8 members/templates/members_base.html:45
#: members/templates/member_add.html:8 members/templates/members_base.html:55
msgid "Add member"
msgstr "Lisää jäsen"
@@ -311,6 +311,31 @@ 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ä"
@@ -319,51 +344,59 @@ msgstr "Muokkaa jäsentä"
msgid "Member register"
msgstr "Jäsenrekisteri"
#: members/templates/member_list.html:23
#: 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:30
msgid "Download CSV"
msgstr "Lataa CSV"
#: members/templates/members_base.html:33
#: members/templates/members_base.html:42
msgid "Member register of SIK ry"
msgstr "Aalto-yliopiston Sähköinsinöörikilta ry:n jäsenrekisteri"
#: members/templates/members_base.html:42 webapp/templates/main_index.html:7
#: members/templates/members_base.html:52 webapp/templates/main_index.html:7
msgid "Members"
msgstr "Jäsenet"
#: members/templates/members_base.html:44
#: members/templates/members_base.html:54
msgid "List members"
msgstr "Jäsenlistaus"
#: members/templates/members_base.html:46
#: members/templates/members_base.html:56
msgid "Add multiple"
msgstr "Lisää useita"
#: members/templates/members_base.html:50
#: members/templates/members_base.html:60
msgid "Payments"
msgstr "Maksutapahtumat"
#: members/templates/members_base.html:52
#: members/templates/members_base.html:62
msgid "List payments"
msgstr "Maksulistaus"
#: members/templates/members_base.html:53 members/templates/payment_add.html:8
#: members/templates/members_base.html:63 members/templates/payment_add.html:8
msgid "Add payment"
msgstr "Lisää maksu"
#: members/templates/members_base.html:57
#: members/templates/members_base.html:67
msgid "Applications"
msgstr "Jäsenhakemukset"
#: members/templates/members_base.html:59
#: members/templates/members_base.html:69
msgid "List applications"
msgstr "Hakemuslistaus"
#: members/templates/members_base.html:60
#: members/templates/members_base.html:70
msgid "Application form"
msgstr "Jäsenhakemuslomake"
#: members/templates/members_base.html:65 members/templates/settings.html:11
#: members/templates/members_base.html:75 members/templates/settings.html:11
msgid "Settings"
msgstr "Asetukset"
@@ -383,96 +416,102 @@ msgstr "Maksutapahtumat"
msgid "Language"
msgstr "Kieli"
#: members/templates/settings.html:20 sikweb/settings-sample.py:178
#: sikweb/settings.py:178
#: members/templates/settings.html:20 sikweb/settings-sample.py:179
#: sikweb/settings.py:177
msgid "Finnish"
msgstr "suomi"
#: members/templates/settings.html:21 sikweb/settings-sample.py:177
#: sikweb/settings.py:177
#: members/templates/settings.html:21 sikweb/settings-sample.py:178
#: sikweb/settings.py:176
msgid "English"
msgstr "englanti"
#: members/views.py:121 members/views.py:178 members/views.py:197
#: members/views.py:128 members/views.py:185 members/views.py:204
msgid "No member id specified"
msgstr "Jäsenen ID ei määritelty"
#: members/views.py:143
#: members/views.py:150
msgid "Successfully added member"
msgstr "Onnistuneesti lisättiin jäsen"
#: members/views.py:164
#: members/views.py:171
msgid "Successfully updated member"
msgstr "Onnistuneesti päivitettiin jäsen"
#: members/views.py:168
#: members/views.py:175
msgid "Could not update member object"
msgstr "Jäsenobjektia ei voitu päivittää"
#: members/views.py:182
#: members/views.py:189
msgid "Successfully deleted member"
msgstr "Onnistuneesti poistettiin jäsen"
#: members/views.py:188
#: members/views.py:195
msgid "Could not delete member object"
msgstr "Jäsenobjektia ei voitu poistaa"
#: members/views.py:231 members/views.py:265 members/views.py:283
#: members/views.py:238 members/views.py:272 members/views.py:290
msgid "No application id specified"
msgstr "Hakemuksen ID ei määritelty"
#: members/views.py:252
#: members/views.py:259
msgid "Successfully accepted application"
msgstr "Onnistuneesti hyväksyttiin hakemus"
#: members/views.py:255
#: members/views.py:262
msgid "Could not accept application object"
msgstr "Hakemusobjektia ei voitu hyväksyä"
#: members/views.py:269
#: members/views.py:276
msgid "Successfully deleted application"
msgstr "Onnistuneesti poistettiin hakemus"
#: members/views.py:274
#: members/views.py:281
msgid "Could not delete application object"
msgstr "Hakemusobjektia ei voitu poistaa"
#: members/views.py:338
#: members/views.py:345
msgid "Successfully added payment for member"
msgstr "Onnistuneesti lisättiin maksutapahtuma jäsenelle"
#: members/views.py:351 members/views.py:364 members/views.py:378
#: members/views.py:358 members/views.py:371 members/views.py:385
msgid "No payment id specified"
msgstr "Maksutapahtuman ID ei määritelty"
#: members/views.py:382
#: members/views.py:389
msgid "Successfully deleted payment"
msgstr "Onnistuneesti poistettiin maksutapahtuma"
#: members/views.py:387
#: members/views.py:394
msgid "Could not delete payment object"
msgstr "Maksutapahtumaobjektia ei voitu poistaa"
#: members/views.py:402
#: members/views.py:409
msgid "Successfully updated payment"
msgstr "Onnistuneesti päivitettiin maksutapahtuma"
#: members/views.py:405
#: members/views.py:412
msgid "Could not update payment object"
msgstr "Maksutapahtumaobjektia ei voitu päivittää"
#: members/views.py:422
#: members/views.py:429
msgid "Missing \"textfield\" POST request field"
msgstr "Puuttuva \"textfield\" POST-kenttä"
#: members/views.py:427
#: members/views.py:434
msgid "Successfully imported multiple members"
msgstr "Onnistuneesti tuotu useita jäseniä"
#: members/views.py:430
#: members/views.py:437
msgid "Failed to import members"
msgstr "Jäsenten tuonti epäonnistui"
#: members/views.py:497
#, fuzzy
#| msgid "Successfully deleted member"
msgid "Successfully resolved all member conflicts."
msgstr "Onnistuneesti poistettiin jäsen"
#: templates/footer.html:7
msgid "Copyright Aalto-yliopiston Sähköinsinöörikilta ry"
msgstr "Copyright Aalto-yliopiston Sähköinsinöörikilta ry"
@@ -485,6 +524,18 @@ msgstr "SIK Hallintapaneeli"
msgid "Aalto-yliopiston Sähköinsinöörikilta ry"
msgstr "Aalto-yliopiston Sähköinsinöörikilta ry"
#: webapp/templates/login.html:25 webapp/templates/login.html:27
msgid "Username"
msgstr "Käyttäjänimi"
#: webapp/templates/login.html:31 webapp/templates/login.html:33
msgid "Password"
msgstr "Salasana"
#: webapp/templates/login.html:43
msgid "Log in"
msgstr "Kirjaudu sisään"
#: webapp/templates/main_index.html:8
msgid "Infoscreen"
msgstr "Infonäyttö"
+2 -1
View File
@@ -1,7 +1,8 @@
from django.contrib import admin
from members.models import Member, Request, Payment
from members.models import Member, Request, Payment, MemberConflict
# Register your models here.
admin.site.register(Member)
admin.site.register(Request)
admin.site.register(Payment)
admin.site.register(MemberConflict)
+24
View File
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-05-28 20:32
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('members', '0011_auto_20170526_2013'),
]
operations = [
migrations.CreateModel(
name='MemberConflict',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='memberconflict_first_member', to='members.Member')),
('second_member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='memberconflict_second_member', to='members.Member')),
],
),
]
+19 -1
View File
@@ -103,7 +103,7 @@ class Member(BaseMember):
latest = payments.latest('date')
date = latest.date
return date
except DoesNotExist:
except Payment.DoesNotExist:
return None
@staticmethod
@@ -119,3 +119,21 @@ class Member(BaseMember):
AYY=bool(array[4]),
jas=bool(array[5]),
)
class MemberConflict(models.Model):
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):
return MemberForm(instance=self.first_member)
@property
def second_member_form(self):
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
+10
View File
@@ -0,0 +1,10 @@
from rest_framework import serializers
from members.models import Member
class MemberSerializer(serializers.ModelSerializer):
paid = serializers.DateTimeField(source='last_paid')
class Meta:
model = Member
fields = ('id', 'first_name', 'last_name', 'email', 'POR', 'AYY', 'jas', 'created', 'paid')
+146 -95
View File
@@ -1,86 +1,115 @@
html, body {
font-size: 14px;
width: 100%;
font-size: 14px;
width: 100%;
}
div {
padding: 0.5rem;
padding: 0.5rem;
}
input {
padding: 0.5rem;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
padding: 0.5rem;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
#header {
display: block;
}
#wrapper {
padding-right: 0;
padding-top: 0;
padding-bottom: 0;
padding-right: 0;
padding-top: 0;
padding-bottom: 0;
}
/* fixes for the sidebar layout */
#sidebar-wrapper {
background: #202020;
top: 0;
padding: 0;
background: #202020;
top: 0;
padding: 0;
-webkit-transition: width 0.01s height 0.01s linear;
-moz-transition: width 0.01s height 0.01s linear;
-o-transition: width 0.01s height 0.01s linear;
transition: width 0.01s, height 0.01s, transform 0.01s;
}
.sidebar-nav {
width: initial;
left: 0;
width: 100%;
padding-top: 0.5rem;
height: 100%;
position: relative;
width: initial;
left: 0;
width: 100%;
padding-top: 0.5rem;
height: 100%;
position: relative;
}
.sidebar-nav li ul {
padding-left: 0px;
padding-left: 0px;
}
.sidebar-nav li span {
user-select: none;
cursor: default;
user-select: none;
cursor: default;
}
/* Override bootstrap default margin-bottom */
.navbar {
margin-bottom: 0;
}
@media (max-width: 767px) {
table {
table-layout: fixed;
}
table {
table-layout: fixed;
}
#sidebar-wrapper {
position: initial;
width: 100%;
margin-left: 0;
text-align: center;
}
#sidebar-wrapper {
position: initial;
width: 100%;
margin-left: 0;
text-align: center;
}
#sidebar-wrapper ul li {
text-align: center;
}
#sidebar-wrapper ul li {
text-align: center;
}
#header {
display: none;
}
.logout-container {
position: initial;
margin-bottom: 1rem;
}
.sidebar-nav {
display: none;
}
#wrapper {
width: 100%;
}
#sidebar-collapse {
display: block;
}
/* Force table to not be like tables anymore */
.logout-container {
position: initial;
margin-bottom: 1rem;
}
#wrapper {
width: 100%;
}
.page-content-container {
padding: 0;
}
/* Force table to not be like tables anymore */
table, thead, tbody, th, td, tr {
display: block;
}
thead tr {
background-color: #CCCCCC;
background-color: #CCCCCC;
}
tr { border: 1px solid #ccc; }
tr {
border: 1px solid #ccc;
}
td {
/* Behave like a "row" */
@@ -103,113 +132,135 @@ input {
}
@media (min-width: 769px) {
.logout-container {
bottom: 1rem;
position: absolute;
width: 100%;
}
.logout-container {
bottom: 1rem;
position: absolute;
width: 100%;
}
.logout-container input {
width: 80%;
}
.logout-container input {
width: 80%;
}
#settings-button {
position: absolute;
bottom: 0;
}
#settings-button {
position: absolute;
bottom: 0;
}
#sidebar-collapse {
display: none;
}
}
#download-csv {
margin-left: 20px;
margin-left: 20px;
}
#header h1 {
margin-bottom: 5rem;
margin-top: 2rem;
text-align: center;
margin-bottom: 5rem;
margin-top: 2rem;
text-align: center;
}
.table-button {
margin-left: 5px;
margin-right: 5px;
margin-bottom: 5px;
margin-left: 5px;
margin-right: 5px;
margin-bottom: 5px;
}
.table-button-column {
text-align: right;
text-align: right;
}
.pagination li {
padding: 0.5rem;
margin: 0.5rem;
padding: 0.5rem;
margin: 0.5rem;
}
#filter-collapser {
float: right;
margin-left: 1rem;
float: right;
margin-left: 1rem;
}
.filter-form {
}
.filter-row {
float: right;
display: table;
background-color: rgba(122, 164, 232, 0.5);
float: right;
display: table;
background-color: rgba(122, 164, 232, 0.5);
}
.filter-group {
display: inline-block;
float: left;
height: 100%;
display: inline-block;
float: left;
height: 100%;
}
.filter-field {
display: table;
clear: both;
width: 100%;
margin-left: 0;
margin-right: 0;
display: table;
clear: both;
width: 100%;
margin-left: 0;
margin-right: 0;
}
.filter-field h5 {
display: inline;
display: inline;
}
.filter-field input[type="datetime-local"] {
display: inline;
width: 50%;
float: right;
display: inline;
width: 50%;
float: right;
}
.filter-button {
margin: 0.5rem;
display: block;
width: 100%;
margin: 0.5rem;
display: block;
width: 100%;
}
.filter-field #search-filter {
width: 100%;
width: 100%;
}
.inline-title {
display: inline;
display: inline;
}
.ellipsis-menu {
height: 2rem;
height: 2rem;
}
.data-table-button {
width: 100%;
width: 100%;
}
.readonly {
pointer-events: none;
pointer-events: none;
}
.large-textarea {
width: 100%;
max-width: 100%;
height: 10rem !important;
}
width: 100%;
max-width: 100%;
height: 10rem !important;
}
.conflict-row {
overflow: auto;
margin-bottom: 2rem;
border: 1px dotted black;
}
.table-conflict {
}
.conflict-row>div>table>tbody>tr>th,
.conflict-row>div>table>tbody>tr>td {
border-top: none;
padding: 1rem;
}
.conflict-row>div {
}
+40
View File
@@ -0,0 +1,40 @@
{% extends "members_base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block content %}
<div>
<div>
<h3>{% trans "Conflicting member entries" %}</h3>
</div>
<div>
<p>{% blocktrans %}Found conflicting member entries. Choose how to handle the problematic data.{% endblocktrans %}</p>
{% for conflict in conflicts %}
<div class="conflict-row">
<div class="col-md-6">
<table class="table readonly table-conflict bg-info" >
{{ conflict.first_member_form }}
</table>
</div>
<div class="col-md-6">
<table class="table readonly table-conflict bg-info" >
{{ conflict.second_member_form }}
</table>
</div>
<div>
<form action="/members/resolve_conflict" method="POST">{% csrf_token %}
<p>{% blocktrans %}Which one has the correct information for this member?{% endblocktrans %}</p>
<input type="hidden" name="id" value="{{ conflict.id }}">
<button type="submit" name="action" value="first" class="btn btn-primary">{% trans "Accept first and remove second" %}</button>
<button type="submit" name="action" value="second" class="btn btn-primary">{% trans "Accept second and remove first" %}</button>
<button type="submit" name="action" value="both" class="btn btn-primary">{% trans "Accept both as two members" %}</button>
</form>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock content %}
+7
View File
@@ -11,6 +11,13 @@
<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 }}
+10
View File
@@ -37,6 +37,16 @@
<!-- Sidebar -->
<div id="sidebar-wrapper">
<nav class="navbar navbar-inverse" id="sidebar-collapse">
<div class="navbar-header">
<span class="navbar-brand hidden-lg hidden-md">{% trans "Member register of SIK ry" %}</span>
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".sidebar-nav">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
</nav>
<ul class="sidebar-nav">
<li>
<span class="text-primary">{% trans "Members" %}</span>
+1 -1
View File
@@ -20,7 +20,7 @@
<option value="fi" {% if LANGUAGE_CODE == "fi" %} selected {% endif %}>{% trans "Finnish" %}</option>
<option value="en" {% if LANGUAGE_CODE == "en" %} selected {% endif %}>{% trans "English" %}</option>
</select>
<input type="submit" class="btn btn-success">
<input type="submit" class="btn btn-success" value="{% trans "Submit" %}">
</form>
</div>
</div>
+9
View File
@@ -0,0 +1,9 @@
from rest_framework.throttling import UserRateThrottle
class BurstRateThrottle(UserRateThrottle):
scope = 'burst'
class SustainedRateThrottle(UserRateThrottle):
scope = 'sustained'
+13 -4
View File
@@ -16,6 +16,11 @@ from members.views import member_update
from members.views import member_delete_confirm
from members.views import member_delete
from members.views import payment_list
from members.views import member_duplicates
from members.views import resolve_conflict
# rest api
from members.views import MemberDetail
# application
from members.views import application_form
@@ -96,9 +101,13 @@ urlpatterns = [
# favourite icon
url(r'^favicon\.ico$', favicon_view),
# email validation
# url(r'^validate/(?P<uidb64>[0-9A-Za-z_\-\']+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', validateEmail, name='user-activation-link'),
# url(r'^validate/success/$', validate_success),
# url(r'^validate/failure/$', validate_fail),
# rest api url
url(r'^api/members/(?P<pk>\d+)$', MemberDetail.as_view()),
# member duplicate resolution view
url(r'^duplicates$', member_duplicates),
# post target for resolving a conflict
url(r'^resolve_conflict$', resolve_conflict)
]
+105 -18
View File
@@ -7,20 +7,27 @@ from django.core.mail import send_mail
from django.conf import settings
from django.utils.translation import ugettext as _
'''Email validation'''
# Email validation
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes
from django.core.mail import send_mail
# REST framework
from members.serializers import MemberSerializer
from rest_framework import generics
from rest_framework import permissions
from rest_framework.throttling import UserRateThrottle, AnonRateThrottle
import json
import requests
import logging
import html
import csv
from smtplib import SMTPAuthenticationError
from members.models import Member, Request, Payment
from members.models import Member, Request, Payment, MemberConflict
from members.forms import MemberForm, PaymentForm, ApplicationForm
# Logger function, you can use the same idea when implementing other loggers to other apps
@@ -99,7 +106,8 @@ def member_list(request, *args, **kwargs):
context = {
'table': table_html,
'member_count': len(members),
'notification': request.GET.get('notification', None)
'notification': request.GET.get('notification', None),
'is_member_conflict': MemberConflict.objects.exists()
}
return render(request, 'member_list.html', context)
@@ -449,27 +457,106 @@ def export_csv(request, *args, **kwargs):
return response
@ensure_csrf_cookie
@require_http_methods(["GET"])
@permission_required('members.change_member', login_url='/login')
def member_duplicates(request, *args, **kwargs):
conflicts = MemberConflict.objects.all()
context = {
'conflicts': conflicts
}
return render(request, 'member_duplicates.html', context)
@ensure_csrf_cookie
@require_http_methods(["POST"])
@permission_required('members.change_member', login_url='/login')
def resolve_conflict(request, *args, **kwargs):
action = request.POST.get('action', None)
if action not in ['first', 'second', 'both']:
return render(request, 'error.html', {'error': '{}: {}'.format(('Incorrect action value'), action)})
id = request.POST.get('id', None)
if id is None:
return render(request, 'error.html', {'error': '{}: {}'.format(('Incorrect id value'), id)})
conflict = MemberConflict.objects.get(id=id)
first_member = conflict.first_member
second_member = conflict.second_member
if action == 'first':
second_member.delete()
elif action == 'second':
first_member.delete()
conflict.delete()
if MemberConflict.objects.exists():
return HttpResponseRedirect('/members/duplicates')
else:
notification = _('Successfully resolved all member conflicts.')
return HttpResponseRedirect('/members/list?notification={}'.format(html.escape(notification)))
def send_mail_wrapper(subject, message, email_to):
send_mail(
subject,
message,
settings.DEFAULT_EMAIL_FROM,
[email_to],
fail_silently=False
)
send_mail(subject,
message,
settings.DEFAULT_EMAIL_FROM,
[email_to],
fail_silently=False)
@receiver(post_save, sender=Request)
def email_on_request(sender, instance, created, **kwargs):
if created:
subject = 'Test1'
message = 'Please validate your email address\r\n'
send_mail_wrapper(subject, message, instance.email)
if not settings.ENABLE_AUTOMATIC_EMAILS:
return
try:
if created:
subject = 'Test1'
message = 'Please validate your email address\r\n'
send_mail_wrapper(subject, message, instance.email)
except SMTPAuthenticationError:
memberlogger.error('Failed to send email to accepted request!')
@receiver(post_save, sender=Member)
def email_on_accept(sender, instance, created, **kwargs):
if created:
subject = 'Test2'
message = 'Jäsenhakemuksesi on hyväksytty!!!\r\n'
send_mail_wrapper(subject, message, instance.email)
if not settings.ENABLE_AUTOMATIC_EMAILS:
return
try:
if created:
subject = 'Test2'
message = 'Jäsenhakemuksesi on hyväksytty!!!\r\n'
send_mail_wrapper(subject, message, instance.email)
except SMTPAuthenticationError:
memberlogger.error('Failed to send email to accepted member!')
def check_for_duplicates(instance):
name_candidates = Member.objects.filter(first_name=instance.first_name,
last_name=instance.last_name)
email_candidates = Member.objects.filter(email=instance.email)
candidates = name_candidates | email_candidates
duplicates = candidates.exclude(id=instance.id)
if len(duplicates) > 0:
conflict = MemberConflict(first_member=instance,
second_member=duplicates[0])
conflict.save()
@receiver(post_save, sender=Member)
def duplicate_receiver(sender, instance, created, **kwargs):
check_for_duplicates(instance)
# Can be used to retrieve single member information via REST API
class MemberDetail(generics.RetrieveAPIView):
queryset = Member.objects.all()
serializer_class = MemberSerializer
permission_classes = (permissions.IsAdminUser, )
throttle_classes = (UserRateThrottle, AnonRateThrottle, )
+2 -1
View File
@@ -138,6 +138,7 @@ REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
'rest_framework.permissions.DjangoModelPermissions',
'rest_framework.permissions.IsAdminUser',
),
'DEFAULT_THROTTLE_CLASSES': (
'members.throttles.BurstRateThrottle',
@@ -157,7 +158,7 @@ EMAIL_PORT = 587
EMAIL_HOST_USER = '<gmailtunnarisi>@gmail.com'
EMAIL_HOST_PASSWORD = '<gmail_passu>'
DEFAULT_EMAIL_FROM = 'SIK Viestintä <sikviestinta@gmail.com>'
ENABLE_AUTOMATIC_EMAILS = False
# ReCaptcha
# http://www.yaconiello.com/blog/integrating-google-recaptcha-to-django/
+1 -4
View File
@@ -3,13 +3,10 @@
{% load static %}
<html>
<head>
<link rel="stylesheet" href="{% static "css/base.css" %}">
<link rel="stylesheet" href="{% static "css/about.css" %}">
</head>
<body>
<h1>SIKWEB 2.0</h1>
<h2>Git revision</h2>
{{ REVISION }}
<h2>Git tag</h2>
{{ TAG }}
</body>
</html>
+30 -3
View File
@@ -1,7 +1,34 @@
html {
width: 100%;
height: 100%;
}
@media (max-width: 760px) {
html {
font-size: 24px;
}
}
@media (min-width: 760px) {
html {
font-size: 16px;
}
}
body {
max-width: 760px;
margin: auto;
font-size: 1rem;
}
h1 {
padding-bottom: 3rem;
padding-top: 3rem;
font-size: 3rem;
}
#content-body {
width: 50vw;
max-width: 800px;
margin: 40px auto;
width: 100%;
}
#login-button {
+7 -7
View File
@@ -5,8 +5,8 @@
<html>
<head>
<meta charset="utf-8">
<title>SIK - Login</title>
<meta name="viewport" charset="utf-8" content="width=device-width" />
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="{% static "js/lib/jquery-3.1.0.min.js" %}"></script>
@@ -19,18 +19,18 @@
</head>
<body>
<div id="content-body" class="container">
<h1 id="site-title">SIK Admin</h1>
<h1>SIK Admin</h1>
<form method="POST" class="form-horizontal" action=""> {% csrf_token %}
<div class="form-group row">
<label for="input-username" class="col-sm-2 col-form-label">Käyttäjätunnus</label>
<label for="input-username" class="col-sm-2 col-form-label">{% trans "Username" %}</label>
<div class="col-sm-10">
<input type="text" name="username" id="input-username" class="form-control" placeholder="Käyttäjätunnus"></input>
<input type="text" name="username" id="input-username" class="form-control" placeholder="{% trans "Username" %}"></input>
</div>
</div>
<div class="form-group row">
<label for="input-password" class="col-sm-2 col-form-label">Salasana</label>
<label for="input-password" class="col-sm-2 col-form-label">{% trans "Password" %}</label>
<div class="col-sm-10">
<input type="password" name="passwd" id="input-passwd" class="form-control" placeholder="Salasana"></input>
<input type="password" name="passwd" id="input-passwd" class="form-control" placeholder="{% trans "Password" %}"></input>
</div>
</div>
<div class="form-group row">
@@ -40,7 +40,7 @@
</div>
<div class="form-group row" id="login-button">
<div class="col-sm-2">
<button type="submit" class="btn btn-default">Kirjaudu</button>
<button type="submit" class="btn btn-primary">{% trans "Log in" %}</button>
</div>
</div>
</form>