소스 검색

Merge branch 'c3s-master'

부모
커밋
7de7442d08
No known key found for this signature in database

+ 14
- 0
CHANGES.rst 파일 보기

@@ -2,10 +2,24 @@ Next Release
2 2
 ============
3 3
 
4 4
 
5
+- Fix double entry when applicant edits details
6
+
7
+
8
+
9
+1.20.5
10
+======
11
+
12
+
5 13
 - Remove editing of number of shares hold by a member.
6 14
 
7 15
 - Remove old import and export functionality.
8 16
 
17
+- Show error message if applicant is younger than 18 years old.
18
+
19
+- Invitations for general assembly and bar camp 2018.
20
+
21
+- Hide invoice 2017 sending in membership list and toolbox.
22
+
9 23
 
10 24
 
11 25
 1.20.4

+ 1
- 1
VERSION 파일 보기

@@ -1 +1 @@
1
-1.20.4
1
+1.20.5

+ 27
- 0
alembic/versions/34c421bb0d0c_invitations_ga_bc_2018.py 파일 보기

@@ -0,0 +1,27 @@
1
+"""Invitations for general assembly and bar camp 2018
2
+
3
+Revision ID: 34c421bb0d0c
4
+Revises: 2fbe1bde5df8
5
+Create Date: 2018-04-23 20:03:17.014936
6
+
7
+"""
8
+
9
+# revision identifiers, used by Alembic.
10
+revision = '34c421bb0d0c'
11
+down_revision = '2fbe1bde5df8'
12
+
13
+from alembic import op
14
+import sqlalchemy as sa
15
+
16
+
17
+def upgrade():
18
+    op.add_column('members', sa.Column('email_invite_date_bcgv18', sa.DateTime(), nullable=True))
19
+    op.add_column('members', sa.Column('email_invite_flag_bcgv18', sa.Boolean(), nullable=True))
20
+    op.add_column('members', sa.Column('email_invite_token_bcgv18', sa.Unicode(length=255), nullable=True))
21
+
22
+
23
+def downgrade():
24
+    with op.batch_alter_table('members') as batch_op:
25
+        batch_op.drop_column('email_invite_token_bcgv18')
26
+        batch_op.drop_column('email_invite_flag_bcgv18')
27
+        batch_op.drop_column('email_invite_date_bcgv18')

+ 3
- 0
c3smembership/__init__.py 파일 보기

@@ -88,6 +88,9 @@ def main(global_config, **settings):
88 88
     share_acquisition = ShareAcquisition(ShareRepository)
89 89
     config.registry.share_acquisition = share_acquisition
90 90
 
91
+    from pyramid_mailer import get_mailer
92
+    config.registry.get_mailer = get_mailer
93
+
91 94
     # Membership application process
92 95
     # Step 1 (join.pt): home is /, the membership application form
93 96
     config.add_route('join', '/')

+ 1
- 1
c3smembership/accountants_views.py 파일 보기

@@ -383,7 +383,7 @@ def mail_payment_confirmation(request):
383 383
     if 'detail' in request.referrer:
384 384
         return HTTPFound(request.route_url(
385 385
             'detail',
386
-            memberid=request.matchdict['memberid']))
386
+            memberid=member.id))
387 387
     else:
388 388
         return get_dashboard_redirect(request, member.id)
389 389
 

+ 6
- 3
c3smembership/business/membership_application.py 파일 보기

@@ -13,13 +13,16 @@ from c3smembership.mail_utils import (
13 13
 from pyramid_mailer.message import Message
14 14
 
15 15
 
16
+_make_signature_confirmation_email = make_signature_confirmation_email
17
+_send_message = send_message
18
+
19
+
16 20
 class MembershipApplication(object):
17 21
     """
18 22
     Provides functionality for the membership application process.
19 23
     """
20 24
 
21 25
     datetime = datetime
22
-    # pylint: disable=invalid-name
23 26
 
24 27
     def __init__(self, member_repository):
25 28
         """
@@ -136,7 +139,7 @@ class MembershipApplication(object):
136 139
         # - Remove dependency to pyramid_mail and move to separate service.
137 140
         member = self.member_repository.get_member_by_id(member_id)
138 141
         # pylint: disable=too-many-function-args
139
-        email_subject, email_body = make_signature_confirmation_email(member)
142
+        email_subject, email_body = _make_signature_confirmation_email(member)
140 143
         message = Message(
141 144
             subject=email_subject,
142 145
             sender='yes@c3s.cc',
@@ -144,6 +147,6 @@ class MembershipApplication(object):
144 147
             body=email_body
145 148
         )
146 149
         # pylint: disable=too-many-function-args
147
-        send_message(request, message)
150
+        _send_message(request, message)
148 151
         member.signature_confirmed = True
149 152
         member.signature_confirmed_date = self.datetime.now()

+ 18
- 8
c3smembership/business/tests/test_membership_application.py 파일 보기

@@ -8,6 +8,8 @@ from datetime import (
8 8
 
9 9
 from unittest import TestCase
10 10
 
11
+import c3smembership.business.membership_application as \
12
+    membership_application_package
11 13
 from c3smembership.business.membership_application import (
12 14
     MembershipApplication,
13 15
 )
@@ -141,10 +143,16 @@ class MemberApplicationTest(TestCase):
141 143
             signature_confirmed=None)
142 144
         member_repository_mock.get_member_by_id.side_effect = [member_mock]
143 145
         membership_application = MembershipApplication(member_repository_mock)
144
-        membership_application.make_signature_confirmation_email = mock.Mock()
145
-        membership_application.make_signature_confirmation_email.side_effect = \
146
+
147
+        make_signature_confirmation_email = mock.Mock()
148
+        make_signature_confirmation_email.side_effect = \
146 149
             [('email subject', 'email body')]
147
-        membership_application.send_message = mock.Mock()
150
+        send_message = mock.Mock()
151
+
152
+        membership_application_package._make_signature_confirmation_email = \
153
+            make_signature_confirmation_email
154
+        membership_application_package._send_message = send_message
155
+
148 156
         membership_application.datetime = mock.Mock()
149 157
         membership_application.datetime.now.side_effect = ['now result']
150 158
 
@@ -152,21 +160,23 @@ class MemberApplicationTest(TestCase):
152 160
             'member id',
153 161
             'pyramid request')
154 162
 
163
+        make_signature_confirmation_email.assert_called_with(member_mock)
155 164
         member_repository_mock.get_member_by_id.assert_called_with('member id')
156 165
         self.assertEqual(
157
-            membership_application.send_message.call_args[0][0],
166
+            send_message.call_args[0][0],
158 167
             'pyramid request')
159 168
         self.assertEqual(
160
-            membership_application.send_message.call_args[0][1].subject,
169
+            send_message.call_args[0][1].subject,
161 170
             'email subject')
162 171
         self.assertEqual(
163
-            membership_application.send_message.call_args[0][1].sender,
172
+            send_message.call_args[0][1].sender,
164 173
             'yes@c3s.cc')
165 174
         self.assertEqual(
166
-            membership_application.send_message.call_args[0][1].recipients,
175
+            send_message.call_args[0][1].recipients,
167 176
             ['jane@example.com'])
168 177
         self.assertEqual(
169
-            membership_application.send_message.call_args[0][1].body,
178
+            send_message.call_args[0][1].body,
170 179
             'email body')
180
+
171 181
         self.assertTrue(member_mock.signature_confirmed)
172 182
         self.assertEqual(member_mock.signature_confirmed_date, 'now result')

+ 14
- 13
c3smembership/invite_members.py 파일 보기

@@ -16,6 +16,7 @@ It was then reused for:
16 16
 - BarCamp and General Assembly 2015
17 17
 - BarCamp and General Assembly 2016
18 18
 - BarCamp and General Assembly 2017
19
+- BarCamp and General Assembly 2018
19 20
 
20 21
 How it works
21 22
 ------------
@@ -43,7 +44,7 @@ from pyramid.httpexceptions import HTTPFound
43 44
 from pyramid.view import view_config
44 45
 from pyramid_mailer.message import Message
45 46
 
46
-from c3smembership.invite_members_texts import make_bcga17_invitation_email
47
+from c3smembership.invite_members_texts import make_bcga18_invitation_email
47 48
 from c3smembership.mail_utils import send_message
48 49
 from c3smembership.membership_certificate import make_random_token
49 50
 from c3smembership.models import C3sMember
@@ -82,16 +83,16 @@ def invite_member_bcgv(request):
82 83
         return get_memberhip_listing_redirect(request, member_id)
83 84
 
84 85
     # prepare a random token iff none is set
85
-    if member.email_invite_token_bcgv17 is None:
86
-        member.email_invite_token_bcgv17 = make_random_token()
86
+    if member.email_invite_token_bcgv18 is None:
87
+        member.email_invite_token_bcgv18 = make_random_token()
87 88
     url = URL_PATTERN.format(
88 89
         ticketing_url=request.registry.settings['ticketing.url'],
89
-        token=member.email_invite_token_bcgv17,
90
+        token=member.email_invite_token_bcgv18,
90 91
         email=member.email)
91 92
 
92 93
     LOG.info("mailing event invitation to to member id %s", member.id)
93 94
 
94
-    email_subject, email_body = make_bcga17_invitation_email(member, url)
95
+    email_subject, email_body = make_bcga18_invitation_email(member, url)
95 96
     message = Message(
96 97
         subject=email_subject,
97 98
         sender='yes@c3s.cc',
@@ -104,8 +105,8 @@ def invite_member_bcgv(request):
104 105
     send_message(request, message)
105 106
 
106 107
     # member._token = _looong_token
107
-    member.email_invite_flag_bcgv17 = True
108
-    member.email_invite_date_bcgv17 = datetime.now()
108
+    member.email_invite_flag_bcgv18 = True
109
+    member.email_invite_date_bcgv18 = datetime.now()
109 110
     return get_memberhip_listing_redirect(request, member.id)
110 111
 
111 112
 
@@ -146,16 +147,16 @@ def batch_invite(request):
146 147
 
147 148
     for member in invitees:
148 149
         # prepare a random token iff none is set
149
-        if member.email_invite_token_bcgv17 is None:
150
-            member.email_invite_token_bcgv17 = make_random_token()
150
+        if member.email_invite_token_bcgv18 is None:
151
+            member.email_invite_token_bcgv18 = make_random_token()
151 152
         url = URL_PATTERN.format(
152 153
             ticketing_url=request.registry.settings['ticketing.url'],
153
-            token=member.email_invite_token_bcgv17,
154
+            token=member.email_invite_token_bcgv18,
154 155
             email=member.email)
155 156
 
156 157
         LOG.info("mailing event invitation to to member id %s", member.id)
157 158
 
158
-        email_subject, email_body = make_bcga17_invitation_email(member, url)
159
+        email_subject, email_body = make_bcga18_invitation_email(member, url)
159 160
         message = Message(
160 161
             subject=email_subject,
161 162
             sender='yes@c3s.cc',
@@ -167,8 +168,8 @@ def batch_invite(request):
167 168
         )
168 169
         send_message(request, message)
169 170
 
170
-        member.email_invite_flag_bcgv17 = True
171
-        member.email_invite_date_bcgv17 = datetime.now()
171
+        member.email_invite_flag_bcgv18 = True
172
+        member.email_invite_date_bcgv18 = datetime.now()
172 173
         num_sent += 1
173 174
         ids_sent.append(member.id)
174 175
 

+ 5
- 5
c3smembership/invite_members_texts.py 파일 보기

@@ -11,7 +11,7 @@ from c3smembership.mail_utils import (
11 11
 DEBUG = False
12 12
 
13 13
 
14
-def make_bcga17_invitation_email(member, url):
14
+def make_bcga18_invitation_email(member, url):
15 15
     """
16 16
     Create email subject and body for an invitation email for members.
17 17
 
@@ -23,18 +23,18 @@ def make_bcga17_invitation_email(member, url):
23 23
         print(u"the member.locale: {}".format(member.locale))
24 24
         print(u"the url: {}".format(url))
25 25
         print(u"the subject: {}".format(
26
-            get_template_text('bcga2017_invite_subject', member.locale)))
26
+            get_template_text('bcga2018_invite_subject', member.locale)))
27 27
         print(u"the salutation: {}".format(get_salutation(member)))
28 28
         print(u"the footer: {}".format(get_email_footer(member.locale)))
29 29
         print(u"the body: {}".format(
30
-            get_template_text('bcga2017_invite_body', member.locale).format(
30
+            get_template_text('bcga2018_invite_body', member.locale).format(
31 31
                 salutation=get_salutation(member),
32 32
                 invitation_url=url,
33 33
                 footer=get_email_footer(member.locale))))
34 34
     return (
35
-        get_template_text('bcga2017_invite_subject', member.locale).rstrip(
35
+        get_template_text('bcga2018_invite_subject', member.locale).rstrip(
36 36
             '\n'),  # remove newline (\n) from mail subject
37
-        get_template_text('bcga2017_invite_body', member.locale).format(
37
+        get_template_text('bcga2018_invite_body', member.locale).format(
38 38
             salutation=get_salutation(member),
39 39
             invitation_url=url,
40 40
             footer=get_email_footer(member.locale)

+ 69
- 74
c3smembership/locale/en/LC_MESSAGES/c3smembership.po 파일 보기

@@ -3,21 +3,19 @@
3 3
 # This file is distributed under the same license as the c3smembership project.
4 4
 # FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
5 5
 #
6
+#, fuzzy
6 7
 msgid ""
7 8
 msgstr ""
8 9
 "Project-Id-Version: c3smembership 1.20.4\n"
9 10
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
10
-"POT-Creation-Date: 2018-04-17 16:37+0200\n"
11
-"PO-Revision-Date: 2018-04-17 16:41+0200\n"
12
-"Language-Team: \n"
11
+"POT-Creation-Date: 2018-04-01 16:28+0200\n"
12
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+"Language-Team: LANGUAGE <LL@li.org>\n"
13 15
 "MIME-Version: 1.0\n"
14
-"Content-Type: text/plain; charset=UTF-8\n"
16
+"Content-Type: text/plain; charset=utf-8\n"
15 17
 "Content-Transfer-Encoding: 8bit\n"
16 18
 "Generated-By: Babel 2.1.1\n"
17
-"X-Generator: Poedit 1.8.11\n"
18
-"Last-Translator: \n"
19
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
20
-"Language: en\n"
21 19
 
22 20
 #: c3smembership/accountants_views.py:79 c3smembership/administration.py:542
23 21
 #: c3smembership/annual_accounting.py:71 c3smembership/edit_member.py:498
@@ -27,33 +25,32 @@ msgstr ""
27 25
 
28 26
 #: c3smembership/accountants_views.py:80 c3smembership/administration.py:543
29 27
 #: c3smembership/edit_member.py:499 c3smembership/new_member.py:214
30
-#: c3smembership/views/afm.py:349
28
+#: c3smembership/views/afm.py:351
31 29
 msgid "Reset"
32 30
 msgstr ""
33 31
 
34 32
 #: c3smembership/accountants_views.py:91 c3smembership/administration.py:566
35 33
 #: c3smembership/annual_accounting.py:89 c3smembership/edit_member.py:529
36 34
 #: c3smembership/new_member.py:252 c3smembership/shares_views.py:105
37
-#: c3smembership/views/afm.py:372
35
+#: c3smembership/views/afm.py:374
38 36
 msgid "Please note: There were errors, please check the form below."
39 37
 msgstr ""
40 38
 
41 39
 #: c3smembership/administration.py:485 c3smembership/edit_member.py:322
42
-#: c3smembership/new_member.py:118 c3smembership/views/afm.py:186
40
+#: c3smembership/new_member.py:118 c3smembership/views/afm.py:188
43 41
 msgid "Yes"
44 42
 msgstr ""
45 43
 
46 44
 #: c3smembership/administration.py:486 c3smembership/edit_member.py:323
47
-#: c3smembership/new_member.py:119 c3smembership/views/afm.py:187
45
+#: c3smembership/new_member.py:119 c3smembership/views/afm.py:189
48 46
 msgid "No"
49 47
 msgstr ""
50 48
 
51
-#: c3smembership/administration.py:489 c3smembership/views/afm.py:190
52
-msgid ""
53
-"I want to become a ... (choose membership type, see C3S SCE statute sec. 4)"
54
-msgstr "I want to become a ... (choose membership type, see pEp SCE statute)"
49
+#: c3smembership/administration.py:489 c3smembership/views/afm.py:192
50
+msgid "I want to become a ... (choose membership type, see C3S SCE statute sec. 4)"
51
+msgstr ""
55 52
 
56
-#: c3smembership/administration.py:491 c3smembership/views/afm.py:192
53
+#: c3smembership/administration.py:491 c3smembership/views/afm.py:194
57 54
 msgid "choose the type of membership."
58 55
 msgstr ""
59 56
 
@@ -64,14 +61,14 @@ msgid ""
64 61
 "lyricists and remixers. They get a vote."
65 62
 msgstr ""
66 63
 
67
-#: c3smembership/administration.py:500 c3smembership/views/afm.py:204
64
+#: c3smembership/administration.py:500 c3smembership/views/afm.py:206
68 65
 msgid ""
69 66
 "INVESTING member. Investing members can be natural or legal entities or "
70 67
 "private companies that do not register works with C3S. They do not get a "
71 68
 "vote, but may counsel."
72 69
 msgstr ""
73 70
 
74
-#: c3smembership/administration.py:510 c3smembership/views/afm.py:214
71
+#: c3smembership/administration.py:510 c3smembership/views/afm.py:216
75 72
 msgid "Currently, I am a member of (at least) one other collecting society."
76 73
 msgstr ""
77 74
 
@@ -150,27 +147,27 @@ msgid "Addess Line 1"
150 147
 msgstr ""
151 148
 
152 149
 #: c3smembership/edit_member.py:175 c3smembership/templates/success.pt:65
153
-#: c3smembership/views/afm.py:137
150
+#: c3smembership/views/afm.py:128
154 151
 msgid "Address Line 2"
155 152
 msgstr ""
156 153
 
157 154
 #: c3smembership/edit_member.py:179 c3smembership/templates/success.pt:66
158
-#: c3smembership/views/afm.py:141
155
+#: c3smembership/views/afm.py:132
159 156
 msgid "Postal Code"
160 157
 msgstr ""
161 158
 
162 159
 #: c3smembership/edit_member.py:184 c3smembership/templates/success.pt:67
163
-#: c3smembership/views/afm.py:146
160
+#: c3smembership/views/afm.py:137
164 161
 msgid "City"
165 162
 msgstr ""
166 163
 
167 164
 #: c3smembership/edit_member.py:189 c3smembership/templates/success.pt:68
168
-#: c3smembership/views/afm.py:151
165
+#: c3smembership/views/afm.py:142
169 166
 msgid "Country"
170 167
 msgstr ""
171 168
 
172 169
 #: c3smembership/edit_member.py:197 c3smembership/templates/success.pt:69
173
-#: c3smembership/views/afm.py:159
170
+#: c3smembership/views/afm.py:150
174 171
 msgid "Date of Birth"
175 172
 msgstr ""
176 173
 
@@ -297,7 +294,7 @@ msgid "Resignations are only allowed to the 31st of December of a year."
297 294
 msgstr ""
298 295
 
299 296
 #: c3smembership/edit_member.py:428 c3smembership/new_member.py:197
300
-#: c3smembership/templates/success.pt:59 c3smembership/views/afm.py:330
297
+#: c3smembership/templates/success.pt:59 c3smembership/views/afm.py:332
301 298
 msgid "Personal Data"
302 299
 msgstr ""
303 300
 
@@ -322,7 +319,7 @@ msgid "E-Mail"
322 319
 msgstr ""
323 320
 
324 321
 #: c3smembership/new_member.py:205 c3smembership/templates/success.pt:79
325
-#: c3smembership/views/afm.py:336
322
+#: c3smembership/views/afm.py:338
326 323
 msgid "Shares"
327 324
 msgstr ""
328 325
 
@@ -499,8 +496,7 @@ msgstr ""
499 496
 
500 497
 #. Default: Membership Application for Cultural Commons Collecting Society (C3S
501 498
 #. SCE)
502
-#: c3smembership/templates/base.pt:7
503
-#: c3smembership/templates/base_bootstrap.pt:7
499
+#: c3smembership/templates/base.pt:7 c3smembership/templates/base_bootstrap.pt:7
504 500
 msgid "membership-form-title"
505 501
 msgstr ""
506 502
 
@@ -566,14 +562,13 @@ msgid "check-email-paragraph-check-email-hint"
566 562
 msgstr ""
567 563
 
568 564
 #. Default: C3S: confirm your email address and load your PDF
569
-#: c3smembership/templates/check-mail.pt:66 c3smembership/views/afm.py:541
565
+#: c3smembership/templates/check-mail.pt:66 c3smembership/views/afm.py:548
570 566
 msgid "check-email-paragraph-check-email-subject"
571 567
 msgstr ""
572 568
 
573 569
 #. Default: Join the Cultural Commons Collecting Society (C3S)
574 570
 #. Default: Join Cultural Commons Collecting Society (C3S)
575
-#: c3smembership/templates/join.pt:24
576
-#: c3smembership/templates/verify_password.pt:9
571
+#: c3smembership/templates/join.pt:24 c3smembership/templates/verify_password.pt:9
577 572
 msgid "join-form-title"
578 573
 msgstr ""
579 574
 
@@ -673,16 +668,16 @@ msgstr ""
673 668
 #: c3smembership/templates/mtype-form.pt:35
674 669
 msgid ""
675 670
 "as mentioned in our mail we would like to give you the opportunity to add "
676
-"some information to your C3S account. We need your help so we can tell if "
677
-"you are eligible to vote or not."
671
+"some information to your C3S account. We need your help so we can tell if you"
672
+" are eligible to vote or not."
678 673
 msgstr ""
679 674
 
680 675
 #: c3smembership/templates/mtype-form.pt:38
681 676
 msgid ""
682 677
 "In case you are unsure, please take a look at our FAQ (see link below) or "
683
-"just send a mail to info@c3s.cc. However, for those of you who are curious "
684
-"to know more about the details, there’s a link to our statutes (see below). "
685
-"Be prepared to enjoy some legal talk..."
678
+"just send a mail to info@c3s.cc. However, for those of you who are curious to"
679
+" know more about the details, there’s a link to our statutes (see below). Be "
680
+"prepared to enjoy some legal talk..."
686 681
 msgstr ""
687 682
 
688 683
 #: c3smembership/templates/mtype-form.pt:40
@@ -801,7 +796,7 @@ msgstr ""
801 796
 msgid "success-headline-data-received"
802 797
 msgstr ""
803 798
 
804
-#: c3smembership/templates/success.pt:64 c3smembership/views/afm.py:132
799
+#: c3smembership/templates/success.pt:64 c3smembership/views/afm.py:123
805 800
 msgid "Address Line 1"
806 801
 msgstr ""
807 802
 
@@ -932,124 +927,124 @@ msgstr ""
932 927
 msgid "load-pdf-notification"
933 928
 msgstr ""
934 929
 
935
-#: c3smembership/views/afm.py:125
936
-msgid "Password (to protect access to your data)"
930
+#: c3smembership/views/afm.py:161
931
+msgid "Sorry, but we do not believe that the birthday you entered is correct."
937 932
 msgstr ""
938 933
 
939
-#: c3smembership/views/afm.py:126
934
+#: c3smembership/views/afm.py:164
940 935
 msgid ""
941
-"We need a password to protect your data. After verifying your email you will "
942
-"have to enter it."
936
+"Unfortunately, the membership application of an underaged person is currently"
937
+" not possible via our web form. Please send an email to office@c3s.cc."
943 938
 msgstr ""
944 939
 
945
-#: c3smembership/views/afm.py:170
946
-msgid "Sorry, we do not believe that you are that old"
940
+#: c3smembership/views/afm.py:175
941
+msgid "Password (to protect access to your data)"
947 942
 msgstr ""
948 943
 
949
-#: c3smembership/views/afm.py:171
944
+#: c3smembership/views/afm.py:176
950 945
 msgid ""
951
-"Unfortunately, the membership application of an underaged person is "
952
-"currently not possible via our web form. Please send an email to office@c3s."
953
-"cc."
946
+"We need a password to protect your data. After verifying your email you will "
947
+"have to enter it."
954 948
 msgstr ""
955 949
 
956
-#: c3smembership/views/afm.py:197
950
+#: c3smembership/views/afm.py:199
957 951
 msgid ""
958 952
 "FULL member. Full members have to be natural persons who register at least "
959 953
 "three works they created themselves with C3S. This applies to composers, "
960 954
 "lyricists and remixers. They get a vote."
961 955
 msgstr ""
962 956
 
963
-#: c3smembership/views/afm.py:224
964
-msgid ""
965
-"If so, which one(s)? Please separate multiple collecting societies by comma."
957
+#: c3smembership/views/afm.py:226
958
+msgid "If so, which one(s)? Please separate multiple collecting societies by comma."
966 959
 msgstr ""
967 960
 
968
-#: c3smembership/views/afm.py:226
961
+#: c3smembership/views/afm.py:228
969 962
 msgid ""
970 963
 "Please tell us which collecting societies you are a member of. If more than "
971 964
 "one, please separate them by comma."
972 965
 msgstr ""
973 966
 
974
-#: c3smembership/views/afm.py:247
967
+#: c3smembership/views/afm.py:249
975 968
 msgid ""
976 969
 "I want to buy the following number of Shares (50€ each, up to 3000€, see C3S "
977 970
 "statute sec. 5)"
978 971
 msgstr ""
979 972
 
980
-#: c3smembership/views/afm.py:250
973
+#: c3smembership/views/afm.py:252
981 974
 msgid "You can choose any amount of shares between 1 and 60."
982 975
 msgstr ""
983 976
 
984
-#: c3smembership/views/afm.py:258
977
+#: c3smembership/views/afm.py:260
985 978
 msgid "You need at least one share of 50 €."
986 979
 msgstr ""
987 980
 
988
-#: c3smembership/views/afm.py:259
981
+#: c3smembership/views/afm.py:261
989 982
 msgid "You may choose 60 shares at most (3000 €)."
990 983
 msgstr ""
991 984
 
992
-#: c3smembership/views/afm.py:280
985
+#: c3smembership/views/afm.py:282
993 986
 msgid ""
994 987
 "I acknowledge that the statutes and membership dues regulations determine "
995 988
 "periodic contributions for full members."
996 989
 msgstr ""
997 990
 
998
-#: c3smembership/views/afm.py:284
991
+#: c3smembership/views/afm.py:286
999 992
 msgid ""
1000 993
 "An electronic copy of the statute of the C3S SCE has been made available to "
1001 994
 "me (see link below)."
1002 995
 msgstr ""
1003 996
 
1004
-#: c3smembership/views/afm.py:287
997
+#: c3smembership/views/afm.py:289
1005 998
 msgid "You must confirm to have access to the statute."
1006 999
 msgstr ""
1007 1000
 
1008
-#: c3smembership/views/afm.py:307
1001
+#: c3smembership/views/afm.py:309
1009 1002
 msgid ""
1010 1003
 "An electronic copy of the temporary membership dues regulations of the C3S "
1011 1004
 "SCE has been made available to me (see link below)."
1012 1005
 msgstr ""
1013 1006
 
1014
-#: c3smembership/views/afm.py:311
1015
-msgid ""
1016
-"You must confirm to have access to the temporary membership dues regulations."
1007
+#: c3smembership/views/afm.py:313
1008
+msgid "You must confirm to have access to the temporary membership dues regulations."
1017 1009
 msgstr ""
1018 1010
 
1019
-#: c3smembership/views/afm.py:333
1011
+#: c3smembership/views/afm.py:335
1020 1012
 msgid "Membership Data"
1021 1013
 msgstr ""
1022 1014
 
1023
-#: c3smembership/views/afm.py:339
1015
+#: c3smembership/views/afm.py:341
1024 1016
 msgid "Acknowledgement"
1025 1017
 msgstr ""
1026 1018
 
1027
-#: c3smembership/views/afm.py:348
1019
+#: c3smembership/views/afm.py:350
1028 1020
 msgid "Next"
1029 1021
 msgstr ""
1030 1022
 
1031
-#: c3smembership/views/afm.py:384
1032
-msgid "Please re-enter your password."
1023
+#: c3smembership/views/afm.py:386
1024
+msgid ""
1025
+"Please re-enter your password. For security reasons your password is not "
1026
+"cached and therefore needs to be re-entered in case of validation issues."
1033 1027
 msgstr ""
1034 1028
 
1035
-#: c3smembership/views/afm.py:631
1029
+#: c3smembership/views/afm.py:638
1036 1030
 msgid ""
1037 1031
 "Not found. Check verification URL. If all seems right, please use the form "
1038 1032
 "again."
1039 1033
 msgstr ""
1040 1034
 
1041
-#: c3smembership/views/afm.py:646
1035
+#: c3smembership/views/afm.py:653
1042 1036
 msgid "Wrong Password!"
1043 1037
 msgstr ""
1044 1038
 
1045
-#: c3smembership/views/afm.py:698
1039
+#: c3smembership/views/afm.py:705
1046 1040
 msgid "Success. Load your PDF!"
1047 1041
 msgstr ""
1048 1042
 
1049
-#: c3smembership/views/afm.py:703
1043
+#: c3smembership/views/afm.py:710
1050 1044
 msgid "Please enter your password."
1051 1045
 msgstr ""
1052 1046
 
1053
-#: c3smembership/views/afm.py:748
1047
+#: c3smembership/views/afm.py:755
1054 1048
 msgid "[yes][ALERT] check the logs!"
1055 1049
 msgstr ""
1050
+

+ 13
- 1
c3smembership/membership_list.py 파일 보기

@@ -407,9 +407,21 @@ def make_member_view(request):
407 407
             member.is_legalentity = False
408 408
         member.membership_number = C3sMember.get_next_free_membership_number()
409 409
 
410
+        # Currently, the inconsistent data model stores the amount of applied
411
+        # shares in member.num_shares which must be moved to a membership
412
+        # application process property. As the acquisition of shares increases
413
+        # the amount of shares and this is still read from member.num_shares,
414
+        # this value must first be reset to 0 so that it can be increased by
415
+        # the share acquisition. Once the new data model is complete the
416
+        # property num_shares will not exist anymore. Instead, a membership
417
+        # application process stores the number of applied shares and the
418
+        # shares store the number of actual shares.
419
+        num_shares = member.num_shares
420
+        member.num_shares = 0
421
+
410 422
         share_id = request.registry.share_acquisition.create(
411 423
             member.membership_number,
412
-            member.num_shares,
424
+            num_shares,
413 425
             member.membership_date)
414 426
         share_acquisition = request.registry.share_acquisition
415 427
         share_acquisition.set_signature_reception(

+ 43
- 8
c3smembership/models.py 파일 보기

@@ -55,6 +55,10 @@ from c3smembership.data.model.base import (
55 55
     DBSession,
56 56
 )
57 57
 
58
+
59
+# pylint: disable=no-member
60
+
61
+
58 62
 CRYPT = cryptacular.bcrypt.BCRYPTPasswordManager()
59 63
 
60 64
 
@@ -143,6 +147,7 @@ class Group(Base):
143 147
 
144 148
 
145 149
 # table for relation between staffers and groups
150
+# pylint: disable=invalid-name
146 151
 staff_groups = Table(
147 152
     'staff_groups', Base.metadata,
148 153
     Column(
@@ -336,6 +341,7 @@ class Shares(Base):
336 341
 
337 342
 
338 343
 # table for relation between membership and shares
344
+# pylint: disable=invalid-name
339 345
 members_shares = Table(
340 346
     'members_shares', Base.metadata,
341 347
     Column(
@@ -592,6 +598,9 @@ class C3sMember(Base):
592 598
     email_invite_flag_bcgv17 = Column(Boolean, default=False)
593 599
     email_invite_date_bcgv17 = Column(DateTime(), default=datetime(1970, 1, 1))
594 600
     email_invite_token_bcgv17 = Column(Unicode(255))
601
+    email_invite_flag_bcgv18 = Column(Boolean, default=False)
602
+    email_invite_date_bcgv18 = Column(DateTime(), default=datetime(1970, 1, 1))
603
+    email_invite_token_bcgv18 = Column(Unicode(255))
595 604
     # legal entities
596 605
     is_legalentity = Column(Boolean, default=False)
597 606
     court_of_law = Column(Unicode(255))
@@ -867,7 +876,7 @@ class C3sMember(Base):
867 876
             object: C3sMember object
868 877
         """
869 878
         return DBSession.query(cls).filter(
870
-            cls.email_invite_token_bcgv17 == token).first()
879
+            cls.email_invite_token_bcgv18 == token).first()
871 880
 
872 881
     @classmethod
873 882
     def check_for_existing_confirm_code(cls, email_confirm_code):
@@ -876,7 +885,7 @@ class C3sMember(Base):
876 885
         """
877 886
         check = DBSession.query(cls).filter(
878 887
             cls.email_confirm_code == email_confirm_code).first()
879
-        return (check is not None)
888
+        return check is not None
880 889
 
881 890
     @classmethod
882 891
     def get_by_id(cls, member_id):
@@ -935,10 +944,10 @@ class C3sMember(Base):
935 944
             and_(
936 945
                 cls.is_member_filter(),
937 946
                 or_(
938
-                    (cls.email_invite_flag_bcgv17 == 0),
939
-                    (cls.email_invite_flag_bcgv17 == ''),
947
+                    (cls.email_invite_flag_bcgv18 == 0),
948
+                    (cls.email_invite_flag_bcgv18 == ''),
940 949
                     # pylint: disable=singleton-comparison
941
-                    (cls.email_invite_flag_bcgv17 == None),
950
+                    (cls.email_invite_flag_bcgv18 == None),
942 951
                 )
943 952
             )
944 953
         ).slice(0, num).all()
@@ -1005,6 +1014,10 @@ class C3sMember(Base):
1005 1014
         Returns:
1006 1015
           a list of *n* member objects
1007 1016
         """
1017
+
1018
+        # In SqlAlchemy the True comparison must be done as "a == True" and not
1019
+        # in the python default way "a is True". Therefore:
1020
+        # pylint: disable=singleton-comparison
1008 1021
         return DBSession.query(cls).filter(
1009 1022
             and_(
1010 1023
                 cls.membership_accepted == True,
@@ -1353,6 +1366,10 @@ class C3sMember(Base):
1353 1366
 
1354 1367
     @classmethod
1355 1368
     def nonmember_listing_count(cls):
1369
+        """
1370
+        Gets the number of applicants which have not been accepted as members
1371
+        yet.
1372
+        """
1356 1373
         query = DBSession.query(cls).filter(
1357 1374
             or_(
1358 1375
                 cls.membership_accepted == 0,
@@ -1367,6 +1384,10 @@ class C3sMember(Base):
1367 1384
     # count for statistics
1368 1385
     @classmethod
1369 1386
     def afm_num_shares_unpaid(cls):
1387
+        """
1388
+        Gets the number of shares for which membership applicant has not yet
1389
+        paid the price.
1390
+        """
1370 1391
         rows = DBSession.query(cls).all()
1371 1392
         num_shares_unpaid = 0
1372 1393
         for row in rows:
@@ -1376,6 +1397,10 @@ class C3sMember(Base):
1376 1397
 
1377 1398
     @classmethod
1378 1399
     def afm_num_shares_paid(cls):
1400
+        """
1401
+        Gets the number of shares for which membership applicant has already
1402
+        paid the price.
1403
+        """
1379 1404
         rows = DBSession.query(cls).all()
1380 1405
         num_shares_paid = 0
1381 1406
         for row in rows:
@@ -1386,6 +1411,12 @@ class C3sMember(Base):
1386 1411
     # workflow: need approval by the board
1387 1412
     @classmethod
1388 1413
     def afms_ready_for_approval(cls):
1414
+        """
1415
+        Gets the list of membership applicants who can be granted membership by
1416
+        the board of directors because they have fulfilled their duty of
1417
+        sending in a signed membership application as well as paying the
1418
+        share's price.
1419
+        """
1389 1420
         return DBSession.query(cls).filter(
1390 1421
             and_(
1391 1422
                 (cls.membership_accepted == 0),
@@ -1653,6 +1684,10 @@ class C3sMember(Base):
1653 1684
             self.dues17_amount_reduced = Decimal('NaN')
1654 1685
 
1655 1686
     def get_url_safe_name(self):
1687
+        """
1688
+        Gets a url-safe version of the member's name in which all characters
1689
+        except 0-9, a-z and A-Z are replaced by a dash.
1690
+        """
1656 1691
         return re.sub(  # # replace characters
1657 1692
             '[^0-9a-zA-Z]',  # other than these
1658 1693
             '-',  # with a -
@@ -1873,7 +1908,7 @@ class Dues15Invoice(Base):
1873 1908
         """
1874 1909
         check = DBSession.query(cls).filter(
1875 1910
             cls.token == dues_token).first()
1876
-        return (check is not None)
1911
+        return check is not None
1877 1912
 
1878 1913
     @classmethod
1879 1914
     def get_monthly_stats(cls):
@@ -2073,7 +2108,7 @@ class Dues16Invoice(Base):
2073 2108
         """
2074 2109
         check = DBSession.query(cls).filter(
2075 2110
             cls.token == dues_token).first()
2076
-        return (check is not None)
2111
+        return check is not None
2077 2112
 
2078 2113
     @classmethod
2079 2114
     def get_monthly_stats(cls):
@@ -2273,7 +2308,7 @@ class Dues17Invoice(Base):
2273 2308
         """
2274 2309
         check = DBSession.query(cls).filter(
2275 2310
             cls.token == dues_token).first()
2276
-        return (check is not None)
2311
+        return check is not None
2277 2312
 
2278 2313
     @classmethod
2279 2314
     def get_monthly_stats(cls):

+ 7
- 7
c3smembership/presentation/templates/memberships_list_backend.pt 파일 보기

@@ -96,16 +96,16 @@
96 96
                  title="Sort by id: descending"
97 97
                  class="glyphicon glyphicon-chevron-down"></a>
98 98
             </th>
99
-            <!--!
100 99
             <th>
101 100
                 bc &amp; ga<br />
102 101
                 invitation
103 102
             </th>
104
-            -->
103
+            <!--!
105 104
             <th>
106 105
                 dues17<br />
107 106
                 invoice
108 107
             </th>
108
+            -->
109 109
             <th>
110 110
                 certificate
111 111
             </th>
@@ -158,20 +158,19 @@
158 158
                     </a>
159 159
                 </div>
160 160
             </td>
161
-            <!--!
162 161
             <td>
163 162
                 <div tal:condition="member.is_member()" tal:omit-tag="">
164
-                  <a tal:condition="member.email_invite_flag_bcgv17 is not True"
163
+                  <a tal:condition="member.email_invite_flag_bcgv18 is not True"
165 164
                       href="${request.route_url('invite_member', m_id=member.id)}"
166 165
                       title="invitation not sent yet. Click to send!"
167 166
                       class="btn btn-danger"></a>
168
-                  <a tal:condition="member.email_invite_flag_bcgv17 is True"
167
+                  <a tal:condition="member.email_invite_flag_bcgv18 is True"
169 168
                       href="${request.route_url('invite_member', m_id=member.id)}"
170
-                      title="gesendet ${member.email_invite_date_bcgv17.strftime('am %d.%m.%Y um %H:%M')}"
169
+                      title="gesendet ${member.email_invite_date_bcgv18.strftime('am %d.%m.%Y um %H:%M')}"
171 170
                       class="btn btn-success"></a>
172 171
                 </div>
173 172
             </td>
174
-            -->
173
+            <!--!
175 174
             <td>
176 175
                 <div tal:omit-tag="" tal:condition="not member.membership_date > date(2017,12,31) and (member.membership_loss_date is None or member.membership_loss_date >= date(2017,1,1))">
177 176
                     <a tal:condition="member.dues17_invoice is False"
@@ -184,6 +183,7 @@
184 183
                         class="btn btn-success"></a>
185 184
                 </div>
186 185
             </td>
186
+            -->
187 187
             <td>
188 188
                 <div tal:condition="member.is_member()" tal:omit-tag="">
189 189
                   <a tal:condition="not member.certificate_email"

+ 3
- 1
c3smembership/presentation/templates/toolbox.pt 파일 보기

@@ -88,6 +88,7 @@
88 88
       </form>
89 89
     </p>
90 90
     -->
91
+    <!--!
91 92
     <h3>Mail Invoices for Membership Dues 2017</h3>
92 93
     <p>
93 94
       <a href="${request.route_url('send_dues17_invoice_batch', number=5)}"
@@ -100,6 +101,7 @@
100 101
         <input type='submit' name='submit'></input>
101 102
       </form>
102 103
     </p>
104
+    -->
103 105
 
104 106
     <h2>Search</h2>
105 107
     <p>
@@ -149,7 +151,7 @@
149 151
       </div>
150 152
     </p>
151 153
 
152
-    <h4>Mail Invitations for GA &amp; BC 2017</h4>
154
+    <h4>Mail Invitations for GA &amp; BC 2018</h4>
153 155
     <p>
154 156
       <a href="${request.route_url('invite_batch', number=5)}"
155 157
          title="Note: change number in URL as appropriate! Default is 5."

+ 192
- 0
c3smembership/templates/mail/bcga2018_invite_body_de.txt 파일 보기

@@ -0,0 +1,192 @@
1
+
2
+Hallo {salutation},
3
+
4
+der Verwaltungsrat der
5
+
6
+Cultural Commons Collecting Society SCE
7
+mit beschränkter Haftung
8
+- C3S SCE -
9
+Rochusstr. 44
10
+40479 Düsseldorf
11
+
12
+lädt Dich ein
13
+
14
+* zur 5. ordentlichen Generalversammlung nach § 13 der Satzung der
15
+ C3S SCE [1] am 03.06.2018 und
16
+* zum C3S-Barcamp 2018 am Vortag der Generalversammlung
17
+
18
+Bitte lies den gesamten Einladungstext, er enthält wichtige Hinweise.
19
+
20
+***************************  W I C H T I G  ***************************
21
+Dies ist Dein individueller Link zur Anmeldung:
22
+
23
+  {invitation_url}
24
+
25
+Bitte teile uns dort frühzeitig mit, ob Du teilnimmst oder nicht oder
26
+ob du dich vertreten lassen möchtest.
27
+Auch Absagen sind erwünscht. Auf der verlinkten Seite kannst Du separat
28
+die Teilnahme für die Generalversammlung und das Barcamp bestätigen.
29
+***************************
30
+
31
+Die Generalversammlung
32
+======================
33
+
34
+Die Generalversammlung 2018 der C3S SCE findet in Düsseldorf im
35
+selben Gebäude statt, in dem wir auch unser Büro haben:
36
+
37
+03.06.2018: 5. ordentliche Generalversammlung der C3S SCE
38
+            Rochusstr. 44
39
+            40479 Düsseldorf [2]
40
+            13 - 17 Uhr
41
+            Akkreditierung ab 12 Uhr
42
+            Eintritt frei
43
+
44
+Bitte komme zeitig, um Verzögerungen zu vermeiden, da die Akkreditierung
45
+Zeit benötigt. Die Teilnahme an der GV ist selbstverständlich kostenlos.
46
+
47
+Das Barcamp
48
+===========
49
+
50
+02.06.2018: Barcamp [3]
51
+            Rochusstr. 44
52
+            40479 Düsseldorf [2]
53
+            13 - 19 Uhr
54
+            Eintritt frei
55
+
56
+Wir gehen davon aus, dass es zu einigen Themen mehr Informations-
57
+und Diskussionsbedarf gibt, als wir in der Generalversammlung selbst
58
+unterbringen können. Um die Generalversammlung vorzubereiten und
59
+Diskussionen vorzuverlagern, bieten wir am Vortag ein Barcamp [3] an,
60
+das einen halben Tag lang in mehreren parallelen Slots Zeit für
61
+Diskussion und Austausch bietet. Ziel ist es am Ende des Barcamps kurze 
62
+und verständliche Zusammenfassungen des Besprochenen zu erstellen.
63
+
64
+Dies ist der Link zu unserer wiki-Seite, auf der Du die Themen für das
65
+Barcamp ergänzen und einsehen kannst:
66
+
67
+    https://wiki.c3s.cc/index.php/Themenvorschl%C3%A4ge_BarCamp2018
68
+
69
+Zugangsdaten:
70
+Name: schwarm
71
+Kennwort: letmein
72
+
73
+Nach einem anstrengend-produktiven Barcamp braucht man auch wieder neue
74
+Energiezufuhr. Das wollen wir gerne in der gemeinsamen Runde
75
+bewerkstelligen. Deshalb würde es uns sehr freuen, wenn Du nach dem
76
+Barcamp mit uns gemeinsam Essen gehst -- wahrscheinlich in einem
77
+italienischen Restaurant. In jedem Fall wird es aber eine Auswahl
78
+an vegetarischen Speisen geben. Bitte vermerke das in der Anmeldung,
79
+damit wir entsprechend reservieren können.
80
+
81
+
82
+Agenda der Generalversammlung 2018 der C3S SCE
83
+==============================================
84
+
85
+Begrüßung der Anwesenden
86
+
87
+# 1 Wahl der Versammlungsleitung und de(s/r) Protokollführer(s/in)
88
+
89
+# 2 Genehmigung der Tagesordnung
90
+
91
+# 3 Wiederkehrende Tagesordnungspunkte
92
+
93
+## 3.1 Entgegennahme der Tätigkeitsberichte der geschäftsführenden
94
+       Direktoren und des Verwaltungsrates mit anschließender Aussprache
95
+## 3.2 Feststellung des Jahresabschlusses
96
+## 3.3 Entscheidung über die Verwendung des Jahresüberschusses und die
97
+       Verrechnung des Jahresfehlbetrages
98
+## 3.4 Entlastung der geschäftsführenden Direktoren und des
99
+       Verwaltungsrates
100
+
101
+# 4 Nachwahl von Mitgliedern des Verwaltungsrates
102
+
103
+# 5 Bericht zum Stand des Zulassungsverfahrens beim Deutschen Patent- 
104
+    und Markenamt (DPMA) 
105
+
106
+# 6 Bericht vom C3S-Barcamp 2018
107
+
108
+# 7 Berichte aus den Beratungskommissionen
109
+
110
+## 7.1 Kommission Tarife
111
+## 7.2 Kommission Verteilung und Beitragsordnung 
112
+## 7.3 Kommission Wahrnehmungsverträge
113
+
114
+# 8 Diskussion zu und ggfs. Beschlussfassung über Festlegung einer
115
+    vorläufigen Beitragsordnung
116
+    https://url.c3s.cc/ga5bv8de
117
+
118
+# 9 Diskussionen / Sonstiges
119
+
120
+Beschlussanträge und Anträge zur Änderung der Tagesordnung kannst Du bis
121
+zum 23.05.2018 (Ausschlussfrist 24 Uhr MESZ/CEST, d.h. UTC +2) in Textform
122
+unter agenda@c3s.cc einreichen.
123
+
124
+
125
+Organisatorisches
126
+=================
127
+
128
+Teilnahme
129
+---------
130
+Teilnahmeberechtigt an der Generalversammlung sind nur Mitglieder der
131
+C3S SCE oder Bevollmächtigte nicht anwesender Mitglieder.
132
+
133
+Stellvertretende Bevollmächtigte
134
+--------------------------------
135
+Solltest Du nicht teilnehmen können und stimmberechtigt sein,
136
+also nutzendes Mitglied, kannst Du eine Vollmacht erteilen. Weitere
137
+Informationen hierzu findest du bei der Anmeldung.
138
+
139
+Audio-Protokoll
140
+---------------
141
+Während der Generalversammlung wird ein Audio-Mitschnitt aufgezeichnet, um
142
+ein fehlerfreies Protokoll zu gewährleisten. Der Mitschnitt wird nicht
143
+veröffentlicht, aber intern als Anhang zum Protokoll archiviert.
144
+Wer nicht möchte, dass sein Redebeitrag aufgezeichnet wird, kann dem vor
145
+Beginn seines/ihres Beitrags widersprechen.
146
+
147
+
148
+Warum ist Deine Teilnahme wichtig?
149
+==================================
150
+
151
+Auf dem Barcamp kannst Du ausführlich zu den aktuellen Themen mitdiskutieren
152
+und Dich mit dem Kernteam der C3S austauschen. Die Generalversammlung ist das
153
+Organ, das die grundlegenden Entscheidungen der C3S SCE trifft.
154
+(Gemeinsam mit den anderen Mitgliedern bist Du die Generalversammlung.)
155
+
156
+Das war's! Versorge uns mit Themenvorschlägen, plane Deine Fahrt - dann sehen
157
+wir uns Anfang Juni in Düsseldorf! Bei Fragen kannst Du Dich, wie immer, an
158
+info@c3s.cc wenden.
159
+
160
+Wir freuen uns auf Dich & Deine Ideen!
161
+
162
+Der Verwaltungsrat der C3S SCE
163
+Veit Winkler - Vorsitzender
164
+
165
+
166
+====================
167
+Der Verwaltungsrat der C3S SCE setzt sich zusammen aus:
168
+
169
+Vorsitz:
170
+Veit Winkler, Vorsitzender des VR
171
+Danny Bruder, stellv. Vorsitzender des VR
172
+
173
+Weitere Mitglieder:
174
+* Johanna Breuckmann
175
+* m.eik michalke
176
+* Thomas Mielke 
177
+* Christoph Scheid
178
+* Elmar Schuck
179
+* Holger Schwetter
180
+
181
+====================
182
+
183
+Links:
184
+
185
+[1] Satzung der C3S SCE: https://url.c3s.cc/satzung
186
+[2] Karte Veranstaltungsort:
187
+https://www.openstreetmap.org/node/2679106873#map=19/51.23274/6.78834
188
+[3] Was ist ein Barcamp? https://url.c3s.cc/bcerklaerung
189
+
190
+{footer}
191
+
192
+

+ 191
- 0
c3smembership/templates/mail/bcga2018_invite_body_en.txt 파일 보기

@@ -0,0 +1,191 @@
1
+
2
+Hello {salutation},
3
+
4
+the administrative board of the
5
+
6
+CulturalCommons Collecting Society SCE
7
+mit beschränkter Haftung
8
+- C3S SCE -
9
+Rochusstr. 44
10
+40479 Düsseldorf
11
+
12
+invites you
13
+
14
+* to the 5th statutory general assembly, according to § 13 of the
15
+  articles of association of the C3S SCE [1] on 3rd June, 2018 and
16
+* to the C3S barcamp 2018 on the day preceding the general assembly.
17
+
18
+Please read the whole text of the invitation. It contains important
19
+information.
20
+
21
+*************************** Important ***************************
22
+This is your individual registration link:
23
+
24
+  {invitation_url}
25
+
26
+Please use it to inform us early on about whether or not you would
27
+like to participate or if you want to be represented by somebody.
28
+Cancellations are requested as well. You can separately confirm your
29
+participation in the general assembly and the barcamp.
30
+***************************
31
+
32
+
33
+The general assembly
34
+====================
35
+
36
+The general assembly 2018 of the C3S SCE will be held in Düsseldorf
37
+in the building where we also have our office.
38
+
39
+June 3rd, 2018:  5th statutory general assembly of the C3S SCE
40
+                 Rochusstr. 44
41
+                 40479 Düsseldorf [2]
42
+                 1.00 pm - 5.00 pm
43
+                 Accreditation from 12.00 pm
44
+                 Admission free
45
+
46
+Please be punctual in order to avoid delays, because the accreditation
47
+will take some time. Of course, participation is free.
48
+
49
+The Barcamp
50
+===========
51
+
52
+June 2nd, 2018:  Barcamp [3]
53
+                 Rochusstr. 44
54
+                 40479 Düsseldorf [2]
55
+                 1.00 pm - 7.00 pm
56
+                 Admission free
57
+
58
+
59
+Again, we are of the opinion that on some topics there will be more
60
+need for information and discussion than we can deal with in the general
61
+assembly. In order to prepare the general assembly and to move up
62
+debates, we are offering a barcamp [3], where half a day is devoted to
63
+discussions and idea exchange in parallel time slots.
64
+The goal is to produce short and comprehensible summaries of what
65
+has been discussed at the barcamp.
66
+
67
+On the following wiki page you can add to and view the topics for the
68
+barcamp: https://wiki.c3s.cc/index.php/Themenvorschl%C3%A4ge_BarCamp2018
69
+
70
+Login details:
71
+Name: schwarm
72
+Password: letmein
73
+
74
+At the end of an exhausting and productive barcamp, everyone needs new
75
+energy. Therefore we would be happy to have dinner with you after the
76
+barcamp – likely at an Italian restaurant. In any case there will be
77
+vegetarian dishes as well. Please note on registration wether you
78
+want to attend so that we can make reservations accordingly.
79
+
80
+
81
+Agenda of the general assembly 2018 of the C3S SCE
82
+==================================================
83
+
84
+Welcoming address
85
+
86
+# 1 Appointment of the chairperson of the assembly
87
+    and the minute taker
88
+
89
+# 2 Approval of the agenda
90
+
91
+# 3 Recurring items on the agenda
92
+
93
+## 3.1 Acceptance of the progress report of the executive directors and
94
+       the administrative board, followed by debate
95
+## 3.2 Approval of the annual financial statement
96
+## 3.3 Resolution on the appropriation
97
+       of the net income and the offsetting ofthe annual net loss
98
+## 3.4 Discharge of the executive directors and the members of the
99
+       adminstrative board
100
+
101
+# 4 By-election of members of the administrative board
102
+
103
+# 5 Status report regarding the approval procedure by the German Patent 
104
+    and Trade Mark Office
105
+
106
+# 6 Report from the C3S barcamp 2018
107
+
108
+# 7 Reports from the consulting committees
109
+
110
+## 7.1 Tariffs commission
111
+## 7.2 Distribution and contributing rules commission
112
+## 7.3 Collection agreements commission
113
+
114
+# 8 Discussion and, if applicable, resolution on the determination of
115
+    temporary contribution rules
116
+    https://url.c3s.cc/ga5bv8en
117
+
118
+# 9 Discussion / Other issues
119
+
120
+You may contribute written proposals for resolutions and for alterations
121
+of the agenda until 23rd Mai, 2018 (midnight MESZ/CEST, i.e. UTC +2)
122
+to agenda@c3s.cc.
123
+
124
+
125
+Organizational issues
126
+=====================
127
+
128
+Participation
129
+-------------
130
+Participation in the general assembly is limited to members of the C3S
131
+SCE or authorized representatives of absent members.
132
+
133
+Authorized representatives
134
+--------------------------------
135
+If you are an active member and therefore entitled to vote, but unable
136
+to participate, you may grant a power of attorney. Further information
137
+on that will be included in the registration e-mail.
138
+
139
+Audio recording
140
+---------------
141
+During the general assembly, an audio recording will be made in order to
142
+ensure error-free minutes. The recording will not be published but
143
+archived internally as an appendix to the minutes.
144
+
145
+Those who do not wish their speech contributions to be recorded, may
146
+veto before commencing to speak.
147
+
148
+
149
+Why is it important that you participate?
150
+=========================================
151
+
152
+At the barcamp, you will be able to make detailed contributions to the
153
+discussions of the current topics and talk to the core team of the C3S.
154
+The general assembly is the body that takes the fundamental decisions
155
+for the C3S. (You are the general assembly, together with the other
156
+members.)
157
+
158
+That's all! Please let us know your proposals for topics, plan your
159
+trip - and we'll meet in Düsseldorf in June! If you have questions,
160
+you can get in touch, as always, via info@c3s.cc.
161
+
162
+We look forward to you and your ideas!
163
+
164
+For and on behalf of the administrative board of the C3S SCE
165
+Veit Winkler - Chairperson
166
+
167
+===============
168
+The administrative board of the C3S SCE consists of:
169
+
170
+Chairpersons:
171
+Veit Winkler, chairperson of the administrative board
172
+Danny Bruder, deputy chairperson of the administrative board
173
+
174
+Other members:
175
+* Johanna Breuckmann
176
+* m.eik michalke
177
+* Thomas Mielke 
178
+* Christoph Scheid
179
+* Elmar Schuck
180
+* Holger Schwetter
181
+
182
+======================
183
+
184
+Links:
185
+
186
+[1] Articles of association of the C3S SCE: https://url.c3s.cc/statutes
187
+[2] Map of location:
188
+https://www.openstreetmap.org/node/2679106873#map=19/51.23274/6.78834
189
+[3] What is a barcamp? https://url.c3s.cc/bcexplanation
190
+
191
+{footer}

+ 1
- 0
c3smembership/templates/mail/bcga2018_invite_subject_de.txt 파일 보기

@@ -0,0 +1 @@
1
+[C3S] Einladung zu Barcamp und Generalversammlung

+ 1
- 0
c3smembership/templates/mail/bcga2018_invite_subject_en.txt 파일 보기

@@ -0,0 +1 @@
1
+[C3S] Invitation to Barcamp and General Assembly

+ 7
- 4
c3smembership/tests/test_api_views.py 파일 보기

@@ -59,14 +59,17 @@ class TestApiViews(unittest.TestCase):
59 59
                 name_of_colsoc=u"GEMA",
60 60
                 num_shares=u'23',
61 61
             )
62
+        # pylint: disable=no-member
62 63
         DBSession.add(member1)
63
-        member1.email_invite_token_bcgv17 = u'MEMBERS_TOKEN'
64
+        member1.email_invite_token_bcgv18 = u'MEMBERS_TOKEN'
65
+        # pylint: disable=no-member
64 66
         DBSession.flush()
65 67
 
66 68
         self.testapp = TestApp(app)
67 69
 
68 70
     def tearDown(self):
69 71
         testing.tearDown()
72
+        # pylint: disable=no-member
70 73
         DBSession.close()
71 74
         DBSession.remove()
72 75
 
@@ -109,7 +112,7 @@ class TestApiViews(unittest.TestCase):
109 112
         # now use the correct auth token
110 113
         _auth_info = {'X-messaging-token': 'SECRETAUTHTOKEN'}
111 114
 
112
-        # ..but a non-existing refcode (email_invite_token_bcgv17)
115
+        # ..but a non-existing refcode (email_invite_token_bcgv18)
113 116
         # returns no user (None)
114 117
         res = self.testapp.put_json(
115 118
             '/lm', dict(token='foo'), headers=_auth_info, status=200)
@@ -121,9 +124,9 @@ class TestApiViews(unittest.TestCase):
121 124
 
122 125
         member1 = C3sMember.get_by_id(1)  # load member from DB for crosscheck
123 126
 
124
-        # now try a valid refcode (email_invite_token_bcgv17)
127
+        # now try a valid refcode (email_invite_token_bcgv18)
125 128
         res2 = self.testapp.put_json(
126
-            '/lm', dict(token=member1.email_invite_token_bcgv17),
129
+            '/lm', dict(token=member1.email_invite_token_bcgv18),
127 130
             headers=_auth_info, status=200)
128 131
         self.assertTrue(json.loads(res2.body)['firstname'], member1.firstname)
129 132
         self.assertTrue(json.loads(res2.body)['lastname'], member1.lastname)

+ 21
- 17
c3smembership/tests/test_invite_member.py 파일 보기

@@ -33,6 +33,8 @@ def init_testing_db():
33 33
         # There is a side effect of test_initialization.py after which there are
34 34
         # still records in the database although it is setup from scratch.
35 35
         # Therefore, remove all members to have an empty table.
36
+
37
+        # pylint: disable=no-member
36 38
         members = C3sMember.get_all()
37 39
         for member in members:
38 40
             DBSession.delete(member)
@@ -148,6 +150,7 @@ class TestInvitation(unittest.TestCase):
148 150
         self.session = init_testing_db()
149 151
 
150 152
     def tearDown(self):
153
+        # pylint: disable=no-member
151 154
         DBSession.close()
152 155
         DBSession.remove()
153 156
         testing.tearDown()
@@ -157,15 +160,15 @@ class TestInvitation(unittest.TestCase):
157 160
         Test the invitation procedure for one single member at a time.
158 161
 
159 162
         Load this member from the DB,
160
-        assure the email_invite_flag_bcgv17 and token are not set,
163
+        assure the email_invite_flag_bcgv18 and token are not set,
161 164
         prepare cookies, invite this member,
162
-        assure the email_invite_flag_bcgv17 and token are now set,
165
+        assure the email_invite_flag_bcgv18 and token are now set,
163 166
         """
164 167
         from c3smembership.invite_members import invite_member_bcgv
165 168
 
166 169
         member1 = C3sMember.get_by_id(1)
167
-        self.assertEqual(member1.email_invite_flag_bcgv17, False)
168
-        self.assertTrue(member1.email_invite_token_bcgv17 is None)
170
+        self.assertEqual(member1.email_invite_flag_bcgv18, False)
171
+        self.assertTrue(member1.email_invite_token_bcgv18 is None)
169 172
 
170 173
         req = testing.DummyRequest()
171 174
         # have some cookies
@@ -181,8 +184,8 @@ class TestInvitation(unittest.TestCase):
181 184
         req.matchdict = {'m_id': member1.id}
182 185
         res = invite_member_bcgv(req)
183 186
 
184
-        self.assertEqual(member1.email_invite_flag_bcgv17, True)
185
-        self.assertTrue(member1.email_invite_token_bcgv17 is not None)
187
+        self.assertEqual(member1.email_invite_flag_bcgv18, True)
188
+        self.assertTrue(member1.email_invite_token_bcgv18 is not None)
186 189
 
187 190
         # now really send email
188 191
         self.config.registry.settings['testing.mail_to_console'] = 'false'
@@ -196,23 +199,23 @@ class TestInvitation(unittest.TestCase):
196 199
                         in mailer.outbox[1].subject)
197 200
         self.assertTrue(member1.firstname
198 201
                         in mailer.outbox[1].body)
199
-        self.assertTrue(member1.email_invite_token_bcgv17
202
+        self.assertTrue(member1.email_invite_token_bcgv18
200 203
                         in mailer.outbox[1].body)
201 204
 
202 205
         # now send invitation to english member
203 206
         member2 = C3sMember.get_by_id(2)
204
-        self.assertEqual(member2.email_invite_flag_bcgv17, False)
205
-        self.assertTrue(member2.email_invite_token_bcgv17 is None)
207
+        self.assertEqual(member2.email_invite_flag_bcgv18, False)
208
+        self.assertTrue(member2.email_invite_token_bcgv18 is None)
206 209
         req.matchdict = {'m_id': member2.id}
207 210
         res = invite_member_bcgv(req)
208
-        self.assertEqual(member2.email_invite_flag_bcgv17, True)
209
-        self.assertTrue(member2.email_invite_token_bcgv17 is not None)
211
+        self.assertEqual(member2.email_invite_flag_bcgv18, True)
212
+        self.assertTrue(member2.email_invite_token_bcgv18 is not None)
210 213
         self.assertEqual(len(mailer.outbox), 3)
211 214
         self.assertTrue(u'[C3S] Invitation to Barcamp and General Assembly'
212 215
                         in mailer.outbox[2].subject)
213 216
         self.assertTrue(member2.firstname
214 217
                         in mailer.outbox[2].body)
215
-        self.assertTrue(member2.email_invite_token_bcgv17
218
+        self.assertTrue(member2.email_invite_token_bcgv18
216 219
                         in mailer.outbox[2].body)
217 220
 
218 221
     def test_invitation_batch(self):
@@ -223,8 +226,8 @@ class TestInvitation(unittest.TestCase):
223 226
 
224 227
         members = C3sMember.get_all()
225 228
         for member in members:
226
-            self.assertEqual(member.email_invite_flag_bcgv17, False)
227
-            self.assertTrue(member.email_invite_token_bcgv17 is None)
229
+            self.assertEqual(member.email_invite_flag_bcgv18, False)
230
+            self.assertTrue(member.email_invite_token_bcgv18 is None)
228 231
             self.assertTrue(member.membership_accepted is True)
229 232
 
230 233
         req = testing.DummyRequest()
@@ -238,6 +241,7 @@ class TestInvitation(unittest.TestCase):
238 241
         res = batch_invite(req)
239 242
 
240 243
         _messages = req.session.peek_flash('message_to_staff')
244
+        # pylint: disable=superfluous-parens
241 245
         print(_messages)
242 246
         self.assertTrue(
243 247
             'sent out 1 mails (to members with ids [1])' in _messages)
@@ -280,12 +284,12 @@ class TestInvitation(unittest.TestCase):
280 284
 
281 285
         for member in members:
282 286
             # has been invited
283
-            self.assertEqual(member.email_invite_flag_bcgv17, True)
287
+            self.assertEqual(member.email_invite_flag_bcgv18, True)
284 288
             # has a token
285
-            self.assertTrue(member.email_invite_token_bcgv17 is not None)
289
+            self.assertTrue(member.email_invite_token_bcgv18 is not None)
286 290
             # firstname and token are in email body
287 291
             self.assertTrue(
288 292
                 members[member.id - 1].firstname in mailer.outbox[member.id - 1].body)
289 293
             self.assertTrue(
290
-                members[member.id - 1].email_invite_token_bcgv17 in mailer.outbox[
294
+                members[member.id - 1].email_invite_token_bcgv18 in mailer.outbox[
291 295
                     member.id - 1].body)

+ 292
- 0
c3smembership/tests/test_membership_application.py 파일 보기

@@ -0,0 +1,292 @@
1
+# -*- coding: utf-8  -*-
2
+"""
3
+Tests the c3smembership.data.repository.share_repository package.
4
+"""
5
+
6
+import datetime
7
+import re
8
+import transaction
9
+import unittest
10
+
11
+from pyramid import testing
12
+from sqlalchemy import engine_from_config
13
+from sqlalchemy.sql import func
14
+from webtest import TestApp
15
+
16
+from c3smembership import main
17
+from c3smembership.data.model.base import (
18
+    DBSession,
19
+    Base,
20
+)
21
+from c3smembership.models import (
22
+    C3sStaff,
23
+    Group,
24
+    C3sMember,
25
+)
26
+
27
+
28
+class MailerDummy(object):
29
+
30
+    def __init__(self):
31
+        self._email = None
32
+
33
+    def send(self, email):
34
+        self._email = email
35
+
36
+    def get_email(self):
37
+        return self._email
38
+
39
+
40
+class GetMailerDummy(object):
41
+
42
+    def __init__(self):
43
+        self._mailer = MailerDummy()
44
+
45
+    def __call__(self, request):
46
+        return self._mailer
47
+
48
+
49
+class DateTimeDummy(object):
50
+
51
+    def __init__(self, now):
52
+        self._now = now
53
+
54
+    def now(self):
55
+        return self._now
56
+
57
+
58
+class MembershipApplicationTest(unittest.TestCase):
59
+
60
+    def setUp(self):
61
+        my_settings = {
62
+            'sqlalchemy.url': 'sqlite:///:memory:',
63
+            'api_auth_token': u"SECRETAUTHTOKEN",
64
+            'c3smembership.url': u'localhost',
65
+            'testing.mail_to_console': u'false',
66
+        }
67
+        self.config = testing.setUp()
68
+        app = main({}, **my_settings)
69
+        self.get_mailer = GetMailerDummy()
70
+        app.registry.get_mailer = self.get_mailer
71
+        app.registry.membership_application.datetime = DateTimeDummy(
72
+            datetime.datetime(2018, 4, 26, 12, 23, 34))
73
+
74
+        engine = engine_from_config(my_settings)
75
+        DBSession.configure(bind=engine)
76
+        Base.metadata.create_all(engine)
77
+
78
+        with transaction.manager:
79
+            # a group for accountants/staff
80
+            accountants_group = Group(name=u"staff")
81
+            DBSession.add(accountants_group)
82
+            DBSession.flush()
83
+            # staff personnel
84
+            staffer1 = C3sStaff(
85
+                login=u"rut",
86
+                password=u"berries",
87
+                email=u"noreply@c3s.cc",
88
+            )
89
+            staffer1.groups = [accountants_group]
90
+            DBSession.add(accountants_group)
91
+            DBSession.add(staffer1)
92
+            DBSession.flush()
93
+
94
+        self.testapp = TestApp(app)
95
+
96
+    def tearDown(self):
97
+        testing.tearDown()
98
+        DBSession.close()
99
+        DBSession.remove()
100
+
101
+    def _login(self):
102
+        """
103
+        Log into the membership backend
104
+        """
105
+        res = self.testapp.get('/login', status=200)
106
+        self.failUnless('login' in res.body)
107
+        form = res.form
108
+        form['login'] = 'rut'
109
+        form['password'] = 'berries'
110
+        res = form.submit('submit', status=302)
111
+
112
+    def _validate_dashboard_redirect(self, res):
113
+        """
114
+        Validate that res is redirecting to the dashboard
115
+        """
116
+        res = res.follow()  # being redirected to dashboard with parameters
117
+        self.__validate_dashboard(res)
118
+
119
+    def _validate_dashboard(self, res):
120
+        """
121
+        Validate that res is the dashboard
122
+        """
123
+        self.failUnless('Dashboard' in res.body)
124
+
125
+    @classmethod
126
+    def _response_to_bare_text(cls, res):
127
+        html = res.normal_body
128
+        # remove JavaScript
129
+        html = re.sub(re.compile('<script.*</script>'), '', html)
130
+        # remove all tags
131
+        html = re.sub(re.compile('<.*?>'), '', html)
132
+        # remove html characters like &nbsp;
133
+        html = re.sub(re.compile('&[A-Za-z]+;'), '', html)
134
+        return html
135
+
136
+    def test_membership_application(self):
137
+        """
138
+        Test the membership application process.
139
+
140
+         1. Enter applicant data to application form
141
+         2. Verify entered data and confirm
142
+         3. Verify sent confirmation email
143
+         4. Confirm email address via confirmation link
144
+         5. Login to backend
145
+         6. Verify applicant's detail page
146
+         7. Set payment received
147
+         8. Set signature received
148
+         9. Make member
149
+        10. Verify member details
150
+        """
151
+        self.testapp.reset()
152
+
153
+        # 1. Enter applicant data to application form
154
+        res = self.testapp.get('/', status=200)
155
+        properties = {
156
+            'firstname': u'Sönke',
157
+            'lastname': u'Blømqvist',
158
+            'email': u'soenke@example.com',
159
+            'address1': u'℅ Big Boss',
160
+            'address2': u'Håkanvägen 12',
161
+            'postcode': u'ABC1234',
162
+            'city': u'Stockholm',
163
+            'year': u'1980',
164
+            'month': u'01',
165
+            'day': u'02',
166
+            'name_of_colsoc': u'Svenska Tonsättares Internationella Musikbyrå',
167
+            'num_shares': u'15',
168
+            'password': u'worst password ever chosen',
169
+            'password-confirm': u'worst password ever chosen',
170
+        }
171
+        for key, value in properties.iteritems():
172
+            res.form[key] = value
173
+        res.form['country'].select(text=u'Sweden')
174
+        res.form['membership_type'].value__set(u'normal')
175
+        res.form['other_colsoc'].value__set(u'yes')
176
+        res.form['got_statute'].checked = True
177
+        res.form['got_dues_regulations'].checked = True
178
+        res = res.form.submit(u'submit', status=302)
179
+        res = res.follow()
180
+
181
+        # 2. Verify entered data and confirm
182
+        body = self._response_to_bare_text(res)
183
+        self.assertTrue('First Name: Sönke' in body)
184
+        self.assertTrue('Last Name: Blømqvist' in body)
185
+        self.assertTrue('Email Address: soenke@example.com' in body)
186
+        self.assertTrue('Address Line 1: ℅ Big Boss' in body)
187
+        self.assertTrue('Address Line 2: Håkanvägen 12' in body)
188
+        self.assertTrue('Postal Code: ABC1234' in body)
189
+        self.assertTrue('City: Stockholm' in body)
190
+        self.assertTrue('Country: SE' in body)
191
+        self.assertTrue('Date of Birth: 1980-01-02' in body)
192
+        self.assertTrue('Type of Membership:normal' in body)
193
+        self.assertTrue('Member of other Collecting Society: yes' in body)
194
+        self.assertTrue('Membership(s): Svenska Tonsättares Internationella Musikbyrå' in body)
195
+        self.assertTrue('Number of Shares: 15' in body)
196
+        self.assertTrue('Cost of Shares (50 € each): 750 €' in body)
197
+        res = res.forms[1].submit(status=200)
198
+
199
+        # 3. Verify sent confirmation email
200
+        mailer = self.get_mailer(None)
201
+        email = mailer.get_email()
202
+        self.assertEqual(email.recipients, ['soenke@example.com'])
203
+        self.assertEqual(email.subject, 'C3S: confirm your email address and load your PDF')
204
+
205
+        # 4. Confirm email address via confirmation link
206
+        match = re.search(
207
+            'localhost(?P<url>[^\s]+)',
208
+            email.body)
209
+
210
+        self.assertTrue(match is not None)
211
+        res = self.testapp.get(
212
+            match.group('url'),
213
+            status=200)
214
+
215
+        self.assertTrue(u'password in order to verify your email' in res.body)
216
+        res.form['password'] = 'worst password ever chosen'
217
+        res = res.form.submit(u'submit', status=200)
218
+
219
+        # 5. Login to backend
220
+        self.testapp.reset()
221
+        self._login()
222
+
223
+        # 6. Verify applicant's detail page
224
+        member_id = DBSession.query(func.max(C3sMember.id)).scalar()
225
+        res = self.testapp.get('/detail/{0}'.format(member_id), status=200)
226
+
227
+        body = self._response_to_bare_text(res)
228
+        self.assertTrue('firstname Sönke' in body)
229
+        self.assertTrue('lastname Blømqvist' in body)
230
+        self.assertTrue('email soenke@example.com' in body)
231
+        self.assertTrue('email confirmed? Yes' in body)
232
+        self.assertTrue('address1 ℅ Big Boss' in body)
233
+        self.assertTrue('address2 Håkanvägen 12' in body)
234
+        self.assertTrue('postcode ABC1234' in body)
235
+        self.assertTrue('city Stockholm' in body)
236
+        self.assertTrue('country SE' in body)
237
+        self.assertTrue('date_of_birth 1980-01-02' in body)
238
+        self.assertTrue('membership_accepted  No' in body)
239
+        self.assertTrue('entity type Person' in body)
240
+        self.assertTrue('membership type normal' in body)
241
+        self.assertTrue('member_of_colsoc Yes' in body)
242
+        self.assertTrue('name_of_colsoc Svenska Tonsättares Internationella Musikbyrå' in body)
243
+        self.assertTrue('date_of_submission ' in body)
244
+        self.assertTrue('signature received?   Nein' in body)
245
+        self.assertTrue('signature confirmed (mail sent)?No' in body)
246
+        self.assertTrue('payment received?   Nein' in body)
247
+        self.assertTrue('payment confirmed?No' in body)
248
+        self.assertTrue('# shares  total: 15' in body)
249
+        # TODO:
250
+        # - code
251
+        # - locale, set explicitly and test both German and English
252
+        # - date of submission
253
+
254
+        # 7. Set payment received
255
+        res = self.testapp.get(
256
+            '/switch_pay/{0}'.format(member_id),
257
+            headers={'Referer': 'asdf'},
258
+            status=302)
259
+        res = res.follow()
260
+        body = self._response_to_bare_text(res)
261
+        self.assertTrue('payment received?    Ja' in body)
262
+        self.assertTrue('payment reception date 2018-04-26 12:23:34' in body)
263
+
264
+        # 8. Set signature received
265
+        res = self.testapp.get(
266
+            '/switch_sig/{0}'.format(member_id),
267
+            headers={'Referer': 'asdf'},
268
+            status=302)
269
+        res = res.follow()
270
+        body = self._response_to_bare_text(res)
271
+        self.assertTrue('signature received?    Ja' in body)
272
+        self.assertTrue('signature reception date2018-04-26 12:23:34' in body)
273
+
274
+        # 9. Make member
275
+        res = self.testapp.get(
276
+            '/make_member/{0}'.format(member_id),
277
+            headers={'Referer': 'asdf'},
278
+            status=200)
279
+        res.form['membership_date'] = '2018-04-27'
280
+        res = res.form.submit('submit', status=302)
281
+        res = res.follow()
282
+
283
+        # 10. Verify member details
284
+        membership_number = C3sMember.get_next_free_membership_number() - 1
285
+        body = self._response_to_bare_text(res)
286
+        self.assertTrue('membership_accepted  Yes' in body)
287
+        self.assertTrue(
288
+            'membership_number  {0}'.format(membership_number) in body)
289
+        self.assertTrue('membership_date 2018-04-27' in body)
290
+        self.assertTrue('# shares  total: 15' in body)
291
+        self.assertTrue('1 package(s)' in body)
292
+        self.assertTrue('15 shares   (2018-04-27)' in body)

+ 267
- 326
c3smembership/tests/test_models.py
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
파일 보기


+ 2
- 0
c3smembership/tests/test_webtest.py 파일 보기

@@ -14,6 +14,7 @@ There are two 'areas' covered:
14 14
 # import os
15 15
 import unittest
16 16
 from pyramid import testing
17
+from pyramid_mailer import get_mailer
17 18
 from c3smembership.data.model.base import (
18 19
     DBSession,
19 20
     Base,
@@ -665,6 +666,7 @@ class FunctionalTests(unittest.TestCase):
665 666
 
666 667
         from c3smembership import main
667 668
         app = main({}, **my_settings)
669
+        app.registry.get_mailer = get_mailer
668 670
 
669 671
         from webtest import TestApp
670 672
         self.testapp = TestApp(app)

+ 103
- 162
c3smembership/views/afm.py 파일 보기

@@ -17,15 +17,18 @@ Tests for these functions can be found in
17 17
 
18 18
 """
19 19
 
20
+from datetime import (
21
+    date,
22
+    datetime,
23
+)
24
+import logging
25
+from types import NoneType
26
+
20 27
 import colander
21 28
 from colander import (
22 29
     Invalid,
23 30
     Range,
24 31
 )
25
-from datetime import (
26
-    date,
27
-    datetime,
28
-)
29 32
 import deform
30 33
 from deform import ValidationFailure
31 34
 from c3smembership.deform_text_input_slider_widget import (
@@ -35,35 +38,49 @@ from c3smembership.deform_text_input_slider_widget import (
35 38
 from pyramid.i18n import (
36 39
     get_locale_name,
37 40
 )
41
+from pyramid.httpexceptions import HTTPFound
38 42
 from pyramid.view import view_config
39
-from pyramid_mailer import get_mailer
40 43
 from pyramid_mailer.message import Message
41
-from pyramid.httpexceptions import HTTPFound
42
-from sqlalchemy.exc import (
43
-    IntegrityError,
44
-    InvalidRequestError,
45 44
 )
46 45
 
47 46
 from types import NoneType
48 47
 from c3smembership.data.model.base import DBSession
49 48
 from c3smembership.models import C3sMember
50
-from c3smembership.utils import (
51
-    generate_pdf,
52
-    accountant_mail,
53
-    country_codes
54
-)
55 49
 from c3smembership.presentation.i18n import (
56 50
     _,
57 51
     ZPT_RENDERER,
58 52
 )
53
+from c3smembership.utils import (
54
+    generate_pdf,
55
+    accountant_mail,
56
+)
59 57
 import customization
60 58
 
61 59
 DEBUG = False
62 60
 LOGGING = True
63 61
 
64 62
 if LOGGING:
65
-    import logging
66
-    log = logging.getLogger(__name__)
63
+    LOG = logging.getLogger(__name__)
64
+
65
+
66
+def statute_validator(node, value):
67
+    """
68
+    Validator for statute confirmation.
69
+    """
70
+    if not value:
71
+        # raise without additional error message as the description
72
+        # already explains the necessity of the checkbox
73
+        raise Invalid(node, u'')
74
+
75
+
76
+def dues_regulations_validator(node, value):
77
+    """
78
+    Validator for dues regulations confirmation.
79
+    """
80
+    if not value:
81
+        # raise without additional error message as the description
82
+        # already explains the necessity of the checkbox
83
+        raise Invalid(node, u'')
67 84
 
68 85
 
69 86
 @view_config(renderer='c3smembership:templates/join.pt',
@@ -96,6 +113,7 @@ def join_c3s(request):
96 113
     except AttributeError:
97 114
         print(dir(customization))
98 115
         country_default = 'GB'
116
+
99 117
     if DEBUG:
100 118
         print("== locale is :" + str(locale_name))
101 119
         print("== choosing :" + str(country_default))
@@ -169,7 +187,9 @@ def join_c3s(request):
169 187
                     date.today().year-18,
170 188
                     date.today().month,
171 189
                     date.today().day),
172
-                min_err=_(u'Sorry, we do not believe that you are that old'),
190
+                min_err=_(
191
+                    u'Sorry, but we do not believe that the birthday you '
192
+                    u'entered is correct.'),
173 193
                 max_err=_(
174 194
                     u'Unfortunately, the membership application of an '
175 195
                     u'underaged person is currently not possible via our web '
@@ -241,7 +261,6 @@ def join_c3s(request):
241 261
             oid='membership_custom_fee',
242 262
             default=customization.membership_fee_custom_min,
243 263
             validator=Range(min=customization.membership_fee_custom_min,max=None,min_err=_(u'please enter at least the minimum fee for sustaining members'))
244
-
245 264
         )
246 265
 
247 266
 
@@ -275,15 +294,6 @@ def join_c3s(request):
275 294
         some legal requirements
276 295
         """
277 296
 
278
-        def statute_validator(node, value):
279
-            """
280
-            Validator for statute confirmation.
281
-            """
282
-            if not value:
283
-                # raise without additional error message as the description
284
-                # already explains the necessity of the checkbox
285
-                raise Invalid(node, u'')
286
-
287 297
         got_statute = colander.SchemaNode(
288 298
             colander.Bool(true_val=u'yes'),
289 299
             #title=(u''),
@@ -300,16 +310,7 @@ def join_c3s(request):
300 310
             validator=statute_validator,
301 311
             required=True,
302 312
             oid='got_statute',
303
-            #label=_('Yes, really'),
304 313
         )
305
-        def dues_regulations_validator(node, value):
306
-            """
307
-            Validator for dues regulations confirmation.
308
-            """
309
-            if not value:
310
-                # raise without additional error message as the description
311
-                # already explains the necessity of the checkbox
312
-                raise Invalid(node, u'')
313 314
 
314 315
         got_dues_regulations = colander.SchemaNode(
315 316
             colander.Bool(true_val=u'yes'),
@@ -325,10 +326,8 @@ def join_c3s(request):
325 326
             validator=dues_regulations_validator,
326 327
             required=True,
327 328
             oid='got_dues_regulations',
328
-            #label=_('Yes'),
329 329
         )
330 330
 
331
-
332 331
     class MembershipForm(colander.Schema):
333 332
         """
334 333
         The Form consists of
@@ -336,28 +335,16 @@ def join_c3s(request):
336 335
         - Membership Information
337 336
         - Shares
338 337
         """
339
-        person = PersonalData(
340
-            title=_(u'Personal Data'),
341
-        )
338
+        person = PersonalData(title=_(u'Personal Data'))
342 339
         if len(customization.membership_types) > 1 or customization.enable_colsoc_association :
343
-            membership_info = MembershipInfo(
344
-                title=_(u'Membership Data')
345
-            )
346
-        shares = Shares(
347
-            title=_(u'Shares')
348
-        )
349
-        try:
350
-            customization.membership_fees
351
-        except NameError:
352
-            pass
353
-        else:
354
-            fees = Fees(
355
-                title=_(u'Membership Fees')
356
-            )
357
-        acknowledge_terms = TermsInfo(
358
-            title=_(u'Acknowledgement')
359
-        )
340
+            membership_info = MembershipInfo(title=_(u'Membership Data'))
341
+
342
+        shares = Shares(title=_(u'Shares'))
360 343
 
344
+        if customization.membership_fees:
345
+            fees = Fees(title=_(u'Membership Fees'))
346
+
347
+        acknowledge_terms = TermsInfo(title=_(u'Acknowledgement'))
361 348
 
362 349
     schema = MembershipForm()
363 350
 
@@ -400,72 +387,16 @@ def join_c3s(request):
400 387
             if form['person']['password'].error is None:
401 388
                 form['person']['password'].error = Invalid(
402 389
                     None,
403
-                    _(u'Please re-enter your password.'))
390
+                    _(
391
+                        u'Please re-enter your password. For security '
392
+                        u'reasons your password is not cached and therefore '
393
+                        u'needs to be re-entered in case of validation '
394
+                        u'issues.'
395
+                    ))
404 396
                 validation_failure = ValidationFailure(form, None, form.error)
405 397
 
406 398
             return {'form': validation_failure.render()}
407 399
 
408
-        def make_random_string():
409
-            """
410
-            used as email confirmation code
411
-            """
412
-            import random
413
-            import string
414
-            return u''.join(
415
-                random.choice(
416
-                    string.ascii_uppercase + string.digits
417
-                ) for x in range(10))
418
-
419
-        # make confirmation code and
420
-        randomstring = make_random_string()
421
-        # check if confirmation code is already used
422
-        while C3sMember.check_for_existing_confirm_code(randomstring):
423
-            # create a new one, if the new one already exists in the database
424
-            randomstring = make_random_string()  # pragma: no cover
425
-
426
-        # to store the data in the DB, an objet is created
427
-        coopMemberArgs = dict(
428
-            firstname=appstruct['person']['firstname'],
429
-            lastname=appstruct['person']['lastname'],
430
-            email=appstruct['person']['email'],
431
-            password=appstruct['person']['password'],
432
-            address1=appstruct['person']['address1'],
433
-            address2=appstruct['person']['address2'],
434
-            postcode=appstruct['person']['postcode'],
435
-            city=appstruct['person']['city'],
436
-            country=appstruct['person']['country'],
437
-            locale=appstruct['person']['locale'],
438
-            date_of_birth=appstruct['person']['date_of_birth'],
439
-            email_is_confirmed=False,
440
-            email_confirm_code=randomstring,
441
-            date_of_submission=datetime.now(),
442
-            num_shares=appstruct['shares']['num_shares'],
443
-        )
444
-
445
-        if customization.enable_colsoc_association:
446
-            coopMemberArgs['member_of_colsoc']=(
447
-                appstruct['membership_info']['member_of_colsoc'] == u'yes'),
448
-            coopMemberArgs['name_of_colsoc']=appstruct['membership_info']['name_of_colsoc']
449
-
450
-        if customization.membership_fees and len(customization.membership_fees) > 1:
451
-            coopMemberArgs['member_type']=appstruct['fees']['member_type']
452
-            if coopMemberArgs['member_type'] == 'sustaining':
453
-                coopMemberArgs['fee'] = appstruct['fees']['member_custom_fee']
454
-            else:
455
-                coopMemberArgs['fee'] = [ v for v,t,d in customization.membership_fees if t == appstruct['fees']['member_type'] ][0]
456
-
457
-
458
-        member = C3sMember(**coopMemberArgs)
459
-        dbsession = DBSession()
460
-        try:
461
-            dbsession.add(member)
462
-            appstruct['email_confirm_code'] = randomstring
463
-
464
-        except InvalidRequestError as ire:  # pragma: no cover
465
-            print("InvalidRequestError! %s") % ire
466
-        except IntegrityError as integrity_error:  # pragma: no cover
467
-            print("IntegrityError! %s") % integrity_error
468
-
469 400
         # redirect to success page, then return the PDF
470 401
         # first, store appstruct in session
471 402
         request.session['appstruct'] = appstruct
@@ -481,8 +412,6 @@ def join_c3s(request):
481 412
     # if the form was submitted and gathered info shown on the success page,
482 413
     # BUT the user wants to correct their information:
483 414
     else:
484
-        if 'edit' in request.POST:
485
-            print(request.POST['edit'])
486 415
         # remove annoying message from other session
487 416
         deleted_msg = request.session.pop_flash()
488 417
         del deleted_msg
@@ -512,7 +441,6 @@ def show_success(request):
512 441
         appstruct = request.session['appstruct']
513 442
         # delete old messages from the session (from invalid form input)
514 443
         request.session.pop_flash('message_above_form')
515
-        # print("show_success: locale: %s") % appstruct['locale']
516 444
         return {
517 445
             'firstname': appstruct['person']['firstname'],
518 446
             'lastname': appstruct['person']['lastname'],
@@ -534,15 +462,58 @@ def success_check_email(request):
534 462
     # check if user has used the form (good) or 'guessed' this URL (bad)
535 463
 
536 464
     if 'appstruct' in request.session:
465
+
466
+        appstruct = request.session['appstruct']
467
+
468
+        def make_random_string():
469
+            """
470
+            used as email confirmation code
471
+            """
472
+            import random
473
+            import string
474
+            return u''.join(
475
+                random.choice(
476
+                    string.ascii_uppercase + string.digits
477
+                ) for x in range(10))
478
+
479
+        # make confirmation code and
480
+        email_confirm_code = make_random_string()
481
+        # check if confirmation code is already used
482
+        while C3sMember.check_for_existing_confirm_code(email_confirm_code):
483
+            # create a new one, if the new one already exists in the database
484
+            email_confirm_code = make_random_string()  # pragma: no cover
485
+
486
+        # to store the data in the DB, an objet is created
487
+        member = C3sMember(
488
+            firstname=appstruct['person']['firstname'],
489
+            lastname=appstruct['person']['lastname'],
490
+            email=appstruct['person']['email'],
491
+            password=appstruct['person']['password'],
492
+            address1=appstruct['person']['address1'],
493
+            address2=appstruct['person']['address2'],
494
+            postcode=appstruct['person']['postcode'],
495
+            city=appstruct['person']['city'],
496
+            country=appstruct['person']['country'],
497
+            locale=appstruct['person']['locale'],
498
+            date_of_birth=appstruct['person']['date_of_birth'],
499
+            email_is_confirmed=False,
500
+            email_confirm_code=email_confirm_code,
501
+            date_of_submission=datetime.now(),
502
+            membership_type=appstruct['membership_info']['membership_type'],
503
+            member_of_colsoc=(
504
+                appstruct['membership_info']['member_of_colsoc'] == u'yes'),
505
+            name_of_colsoc=appstruct['membership_info']['name_of_colsoc'],
506
+            num_shares=appstruct['shares']['num_shares'],
507
+        )
508
+        DBSession().add(member)
509
+
537 510
         # we do have valid info from the form in the session (good)
538 511
         appstruct = request.session['appstruct']
539
-        from pyramid_mailer.message import Message
540 512
         try:
541
-            mailer = get_mailer(request)
513
+            mailer = request.registry.get_mailer(request)
542 514
         except:
543 515
             return HTTPFound(location=request.route_url('join'))
544 516
 
545
-
546 517
         the_mail_body = customization.address_confirmation_mail.get(appstruct['person']['locale'],'en')
547 518
         the_mail = Message(
548 519
             subject=request.localizer.translate(_(
@@ -555,14 +526,13 @@ def success_check_email(request):
555 526
                 appstruct['person']['lastname'],
556 527
                 request.registry.settings['c3smembership.url'],
557 528
                 appstruct['person']['email'],
558
-                appstruct['email_confirm_code']
529
+                email_confirm_code
559 530
             )
560 531
         )
561 532
         if 'true' in request.registry.settings['testing.mail_to_console']:
562
-            # print(the_mail.body)
563
-            log.info(the_mail.subject)
564
-            log.info(the_mail.recipients)
565
-            log.info(the_mail.body)
533
+            LOG.info(the_mail.subject)
534
+            LOG.info(the_mail.recipients)
535
+            LOG.info(the_mail.body)
566 536
             # just logging, not printing, b/c test fails otherwise:
567 537
             # env/bin/nosetests
568 538
             #    c3smembership/tests/test_views_webdriver.py:
@@ -580,28 +550,6 @@ def success_check_email(request):
580 550
     return HTTPFound(location=request.route_url('join'))
581 551
 
582 552
 
583
-# @view_config(
584
-#     renderer='templates/verify_password.pt',
585
-#     route_name='verify_password')
586
-# def verify_password(request):
587
-#     """
588
-#     This view is called via links sent in mails to verify mail addresses.
589
-#     It extracts both email and verification code from the URL
590
-#     and checks if there is a match in the database.
591
-#     """
592
-#     #dbsession = DBSession()
593
-#     # collect data from the URL/matchdict
594
-#     user_email = request.matchdict['email']
595
-#     #print(user_email)
596
-#     confirm_code = request.matchdict['code']
597
-#     #print(confirm_code)
598
-#     # get matching dataset from DB
599
-#     member = C3sMember.get_by_code(confirm_code)  # returns a member or None
600
-#     #print(member)
601
-
602
-#     return {'foo': 'bar'}
603
-
604
-
605 553
 @view_config(
606 554
     renderer='c3smembership:templates/verify_password.pt',
607 555
     route_name='verify_email_password')
@@ -622,11 +570,7 @@ def success_verify_email(request):
622 570
     # we need to have a url to send the form to
623 571
     post_url = '/verify/' + user_email + '/' + confirm_code
624 572
 
625
-    # ToDo unify errors for not_found email, wrong password and wrong confirm code to avoid leaking
626
-    error_message=_(u'Your email, password, or confirmation code could not be found')
627
-
628 573
     if 'submit' in request.POST:
629
-        # print("the form was submitted")
630 574
         request.session.pop_flash('message_above_form')
631 575
         request.session.pop_flash('message_above_login')
632 576
         # check for password ! ! !
@@ -660,7 +604,6 @@ def success_verify_email(request):
660 604
         # -member
661 605
 
662 606
         if (member.email == user_email) and correct:
663
-            # print("-- found member, code matches, password too. COOL!")
664 607
             # set the email_is_confirmed flag in the DB for this signee
665 608
             member.email_is_confirmed = True
666 609
             # dbsession.flush()
@@ -697,7 +640,7 @@ def success_verify_email(request):
697 640
             request.session['appstruct'] = appstruct
698 641
 
699 642
             # log this person in, using the session
700
-            log.info('verified code and password for id %s', member.id)
643
+            LOG.info('verified code and password for id %s', member.id)
701 644
             request.session.save()
702 645
             return {
703 646
                 'firstname': member.firstname,
@@ -737,9 +680,8 @@ def show_success_pdf(request):
737 680
     # check if user has used form or 'guessed' this URL
738 681
     if 'appstruct' in request.session:
739 682
         # we do have valid info from the form in the session
740
-        # print("-- valid session with data found")
741 683
         # send mail to accountants // prepare a mailer
742
-        mailer = get_mailer(request)
684
+        mailer = request.registry.get_mailer(request)
743 685
         # prepare mail
744 686
         appstruct = request.session['appstruct']
745 687
         message_recipient = request.registry.settings['c3smembership.mailaddr']
@@ -780,5 +722,4 @@ go fix it!
780 722
 
781 723
         return generate_pdf(request.session['appstruct'])
782 724
     # 'else': send user to the form
783
-    # print("-- no valid session with data found")
784 725
     return HTTPFound(location=request.route_url('join'))

+ 39
- 9
c3smembership/views/tests/test_afm.py 파일 보기

@@ -4,6 +4,7 @@ from datetime import date
4 4
 import unittest
5 5
 # from pyramid.config import Configurator
6 6
 from pyramid import testing
7
+from pyramid_mailer import get_mailer
7 8
 from sqlalchemy import engine_from_config
8 9
 from c3smembership.data.model.base import (
9 10
     DBSession,
@@ -19,6 +20,7 @@ from c3smembership.models import (
19 20
 )
20 21
 from c3smembership.data.model.base import DBSession
21 22
 from c3smembership import main
23
+import c3smembership.views.afm as afm
22 24
 
23 25
 
24 26
 def _initTestingDB():
@@ -29,6 +31,18 @@ def _initTestingDB():
29 31
     return session
30 32
 
31 33
 
34
+class DummyDate(object):
35
+
36
+    def __init__(self, today):
37
+        self._today = today
38
+
39
+    def __call__(self, *args, **kwargs):
40
+        return date(*args, **kwargs)
41
+
42
+    def today(self):
43
+        return self._today
44
+
45
+
32 46
 class TestViews(unittest.TestCase):
33 47
     """
34 48
     very basic tests for the main views
@@ -40,6 +54,7 @@ class TestViews(unittest.TestCase):
40 54
             'c3smembership.url'] = 'https://yes.c3s.cc'
41 55
         self.config.registry.settings['c3smembership.mailaddr'] = 'c@c3s.cc'
42 56
         self.config.registry.settings['testing.mail_to_console'] = 'false'
57
+        self.config.registry.get_mailer = get_mailer
43 58
 
44 59
         DBSession.remove()
45 60
         self.session = _initTestingDB()
@@ -79,7 +94,6 @@ class TestViews(unittest.TestCase):
79 94
         """
80 95
         from c3smembership.views.afm import success_check_email
81 96
         self.config.add_route('join', '/')
82
-        from pyramid_mailer import get_mailer
83 97
         request = testing.DummyRequest(
84 98
             params={
85 99
                 'appstruct': {
@@ -93,9 +107,23 @@ class TestViews(unittest.TestCase):
93 107
                 'firstname': 'foo',
94 108
                 'lastname': 'bar',
95 109
                 'email': 'bar@shri.de',
110
+                'password': 'bad password',
111
+                'address1': 'Some Street',
112
+                'address2': '',
113
+                'postcode': 'ABC123',
114
+                'city': 'Stockholm',
115
+                'country': 'SE',
96 116
                 'locale': 'de',
117
+                'date_of_birth': '1980-01-01',
118
+            },
119
+            'membership_info': {
120
+                'membership_type': 'person',
121
+                'member_of_colsoc': 'no',
122
+                'name_of_colsoc': '',
123
+            },
124
+            'shares': {
125
+                'num_shares': '3',
97 126
             },
98
-            'email_confirm_code': '12345678',
99 127
         }
100 128
         mailer = get_mailer(request)
101 129
         result = success_check_email(request)
@@ -109,7 +137,7 @@ class TestViews(unittest.TestCase):
109 137
             'C3S: confirm your email address and load your PDF')
110 138
         # self.assertEqual(mailer.outbox[0]., "hello world")
111 139
 
112
-        verif_link = "https://yes.c3s.cc/verify/bar@shri.de/12345678"
140
+        verif_link = "https://yes.c3s.cc/verify/bar@shri.de/"
113 141
         self.assertTrue("Hallo foo bar!" in mailer.outbox[0].body)
114 142
         self.assertTrue(verif_link in mailer.outbox[0].body)
115 143
 
@@ -190,9 +218,10 @@ class TestViews(unittest.TestCase):
190 218
         # success for 18th birthday
191 219
         res = self.testapp.get('/', status=200)
192 220
         form = self._fill_form_valid_natural(res.form)
193
-        form['year'] = unicode(date.today().year-18)
194
-        form['month'] = unicode(date.today().month)
195
-        form['day'] = unicode(date.today().day)
221
+        afm.date = DummyDate(date(2018, 4, 29))
222
+        form['year'] = u'2000'
223
+        form['month'] = u'04'
224
+        form['day'] = u'29'
196 225
         res = form.submit(u'submit', status=302)
197 226
         res = res.follow()
198 227
         self.assertTrue('information below to be correct' in res.body)
@@ -200,9 +229,10 @@ class TestViews(unittest.TestCase):
200 229
         # failure on test one day before 18th birthday
201 230
         res = self.testapp.get('/', status=200)
202 231
         form = self._fill_form_valid_natural(res.form)
203
-        form['year'] = unicode(date.today().year-18)
204
-        form['month'] = unicode(date.today().month)
205
-        form['day'] = unicode(date.today().day+1)
232
+        afm.date = DummyDate(date(2018, 4, 29))
233
+        form['year'] = u'2000'
234
+        form['month'] = u'04'
235
+        form['day'] = u'30'
206 236
         res = form.submit(u'submit', status=200)
207 237
         self.assertTrue('underaged person is currently not' in res.body)
208 238
 

Loading…
취소
저장