Browse Source

staffers can send out confirmation emails for signatures and payment

Christoph Scheid 6 years ago
parent
commit
f76af4ece9

+ 53
- 0
alembic.ini View File

@@ -0,0 +1,53 @@
1
+# A generic, single database configuration.
2
+
3
+[alembic]
4
+# path to migration scripts
5
+script_location = alembic
6
+
7
+# template used to generate migration files
8
+# file_template = %%(rev)s_%%(slug)s
9
+
10
+# max length of characters to apply to the
11
+# "slug" field
12
+#truncate_slug_length = 40
13
+
14
+# set to 'true' to run the environment during
15
+# the 'revision' command, regardless of autogenerate
16
+# revision_environment = false
17
+
18
+sqlalchemy.url = sqlite:///c3sMembership.db
19
+
20
+# Logging configuration
21
+[loggers]
22
+keys = root,sqlalchemy,alembic
23
+
24
+[handlers]
25
+keys = console
26
+
27
+[formatters]
28
+keys = generic
29
+
30
+[logger_root]
31
+level = WARN
32
+handlers = console
33
+qualname =
34
+
35
+[logger_sqlalchemy]
36
+level = WARN
37
+handlers =
38
+qualname = sqlalchemy.engine
39
+
40
+[logger_alembic]
41
+level = INFO
42
+handlers =
43
+qualname = alembic
44
+
45
+[handler_console]
46
+class = StreamHandler
47
+args = (sys.stderr,)
48
+level = NOTSET
49
+formatter = generic
50
+
51
+[formatter_generic]
52
+format = %(levelname)-5.5s [%(name)s] %(message)s
53
+datefmt = %H:%M:%S

+ 3
- 0
alembic/README View File

@@ -0,0 +1,3 @@
1
+Generic single-database configuration.
2
+
3
+see https://alembic.readthedocs.org/en/latest/tutorial.html

+ 73
- 0
alembic/env.py View File

@@ -0,0 +1,73 @@
1
+from __future__ import with_statement
2
+from alembic import context
3
+from sqlalchemy import engine_from_config, pool
4
+from logging.config import fileConfig
5
+
6
+# this is the Alembic Config object, which provides
7
+# access to the values within the .ini file in use.
8
+config = context.config
9
+
10
+# Interpret the config file for Python logging.
11
+# This line sets up loggers basically.
12
+fileConfig(config.config_file_name)
13
+
14
+# add your model's MetaData object here
15
+# for 'autogenerate' support
16
+# from myapp import mymodel
17
+# target_metadata = mymodel.Base.metadata
18
+from c3smembership.models import Base
19
+target_metadata = Base.metadata
20
+
21
+# other values from the config, defined by the needs of env.py,
22
+# can be acquired:
23
+# my_important_option = config.get_main_option("my_important_option")
24
+# ... etc.
25
+
26
+
27
+def run_migrations_offline():
28
+    """Run migrations in 'offline' mode.
29
+
30
+    This configures the context with just a URL
31
+    and not an Engine, though an Engine is acceptable
32
+    here as well.  By skipping the Engine creation
33
+    we don't even need a DBAPI to be available.
34
+
35
+    Calls to context.execute() here emit the given string to the
36
+    script output.
37
+
38
+    """
39
+    url = config.get_main_option("sqlalchemy.url")
40
+    context.configure(url=url)
41
+
42
+    with context.begin_transaction():
43
+        context.run_migrations()
44
+
45
+
46
+def run_migrations_online():
47
+    """Run migrations in 'online' mode.
48
+
49
+    In this scenario we need to create an Engine
50
+    and associate a connection with the context.
51
+
52
+    """
53
+    engine = engine_from_config(
54
+        config.get_section(config.config_ini_section),
55
+        prefix='sqlalchemy.',
56
+        poolclass=pool.NullPool)
57
+
58
+    connection = engine.connect()
59
+    context.configure(
60
+        connection=connection,
61
+        target_metadata=target_metadata
62
+    )
63
+
64
+    try:
65
+        with context.begin_transaction():
66
+            context.run_migrations()
67
+    finally:
68
+        connection.close()
69
+
70
+if context.is_offline_mode():
71
+    run_migrations_offline()
72
+else:
73
+    run_migrations_online()

+ 22
- 0
alembic/script.py.mako View File

@@ -0,0 +1,22 @@
1
+"""${message}
2
+
3
+Revision ID: ${up_revision}
4
+Revises: ${down_revision}
5
+Create Date: ${create_date}
6
+
7
+"""
8
+
9
+# revision identifiers, used by Alembic.
10
+revision = ${repr(up_revision)}
11
+down_revision = ${repr(down_revision)}
12
+
13
+from alembic import op
14
+import sqlalchemy as sa
15
+${imports if imports else ""}
16
+
17
+def upgrade():
18
+    ${upgrades if upgrades else "pass"}
19
+
20
+
21
+def downgrade():
22
+    ${downgrades if downgrades else "pass"}

+ 32
- 0
alembic/versions/17343990ccc0_add_signature_and_paymant_confirmation_.py View File

@@ -0,0 +1,32 @@
1
+"""add signature and paymant confirmation fields
2
+
3
+Revision ID: 17343990ccc0
4
+Revises: None
5
+Create Date: 2013-12-11 14:01:32.099898
6
+
7
+"""
8
+
9
+# revision identifiers, used by Alembic.
10
+revision = '17343990ccc0'
11
+down_revision = None
12
+
13
+from alembic import op
14
+import sqlalchemy as sa
15
+
16
+
17
+def upgrade():
18
+    ### commands auto generated by Alembic - please adjust! ###
19
+    op.add_column('members', sa.Column('payment_confirmed', sa.Boolean(), nullable=True))
20
+    op.add_column('members', sa.Column('payment_confirmed_date', sa.DateTime(), nullable=True))
21
+    op.add_column('members', sa.Column('signature_confirmed', sa.Boolean(), nullable=True))
22
+    op.add_column('members', sa.Column('signature_confirmed_date', sa.DateTime(), nullable=True))
23
+    ### end Alembic commands ###
24
+
25
+
26
+def downgrade():
27
+    ### commands auto generated by Alembic - please adjust! ###
28
+    op.drop_column('members', 'signature_confirmed_date')
29
+    op.drop_column('members', 'signature_confirmed')
30
+    op.drop_column('members', 'payment_confirmed_date')
31
+    op.drop_column('members', 'payment_confirmed')
32
+    ### end Alembic commands ###

+ 2
- 0
c3smembership/__init__.py View File

@@ -64,8 +64,10 @@ def main(global_config, **settings):
64 64
     config.add_route('dashboard', '/dashboard/{number}')
65 65
     config.add_route('detail', '/detail/{memberid}')
66 66
     config.add_route('switch_sig', '/switch_sig/{memberid}')
67
+    config.add_route('mail_sig_confirmation', '/mail_sig_conf/{memberid}')
67 68
     config.add_route('regenerate_pdf', '/re_C3S_SCE_AFM_{code}.pdf')
68 69
     config.add_route('switch_pay', '/switch_pay/{memberid}')
70
+    config.add_route('mail_pay_confirmation', '/mail_pay_conf/{memberid}')
69 71
     config.add_route('delete_entry', '/delete/{memberid}')
70 72
     config.add_route('login', '/login')
71 73
     config.add_route('logout', '/logout')

+ 58
- 0
c3smembership/accountants_views.py View File

@@ -5,6 +5,10 @@ from c3smembership.models import (
5 5
     C3sStaff,
6 6
 )
7 7
 from c3smembership.utils import generate_pdf
8
+from c3smembership.mail_utils import (
9
+    make_signature_confirmation_emailbody,
10
+    make_payment_confirmation_emailbody
11
+)
8 12
 from pkg_resources import resource_filename
9 13
 import colander
10 14
 import deform
@@ -21,6 +25,8 @@ from pyramid.security import (
21 25
     forget,
22 26
     authenticated_userid,
23 27
 )
28
+from pyramid_mailer import get_mailer
29
+from pyramid_mailer.message import Message
24 30
 from pyramid.url import route_url
25 31
 from translationstring import TranslationStringFactory
26 32
 
@@ -490,3 +496,55 @@ def regenerate_pdf(request):
490 496
     }
491 497
 
492 498
     return generate_pdf(_appstruct)
499
+
500
+
501
+@view_config(permission='manage',
502
+             route_name='mail_sig_confirmation')
503
+def mail_signature_confirmation(request):
504
+    """
505
+    send a mail to membership applicant
506
+    informing her about reception of signature
507
+    """
508
+    _id = request.matchdict['memberid']
509
+    _member = C3sMember.get_by_id(_id)
510
+
511
+    message = Message(
512
+        subject=_('[C3S AFM] We have received your signature. Thanks!'),
513
+        sender='yes@c3s.cc',
514
+        recipients=[_member.email],
515
+        body=make_signature_confirmation_emailbody(_member)
516
+    )
517
+    #print(message.body)
518
+    mailer = get_mailer(request)
519
+    mailer.send(message)
520
+    _member.signature_confirmed = True
521
+    _member.signature_confirmed_date = datetime.now()
522
+    return HTTPFound(request.route_url('dashboard',
523
+                                       number=request.cookies['on_page'])
524
+                     )
525
+
526
+
527
+@view_config(permission='manage',
528
+             route_name='mail_pay_confirmation')
529
+def mail_payment_confirmation(request):
530
+    """
531
+    send a mail to membership applicant
532
+    informing her about reception of payment
533
+    """
534
+    _id = request.matchdict['memberid']
535
+    _member = C3sMember.get_by_id(_id)
536
+
537
+    message = Message(
538
+        subject=_('[C3S AFM] We have received your payment. Thanks!'),
539
+        sender='yes@c3s.cc',
540
+        recipients=[_member.email],
541
+        body=make_payment_confirmation_emailbody(_member)
542
+    )
543
+    #print(message.body)
544
+    mailer = get_mailer(request)
545
+    mailer.send(message)
546
+    _member.payment_confirmed = True
547
+    _member.payment_confirmed_date = datetime.now()
548
+    return HTTPFound(request.route_url('dashboard',
549
+                                       number=request.cookies['on_page'])
550
+                     )

+ 90
- 0
c3smembership/mail_utils.py View File

@@ -0,0 +1,90 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+def make_payment_confirmation_emailbody(_input):
5
+    """
6
+    a mail body to confirm reception of payment for shares
7
+    """
8
+    _num_shares = _input.num_shares
9
+    _sum_shares = _num_shares * 50
10
+
11
+    if 'de' in _input.locale:
12
+        _body = (u"""Liebes Neumitglied,
13
+
14
+Deine Überweisung für """ + str(_num_shares) +
15
+                 u""" Anteile (""" + str(_sum_shares) +
16
+                 u""" Euro) ist auf
17
+unserem Konto eingegangen.
18
+
19
+Falls Probleme aufgetreten sind, melde Dich bitte bei uns (yes@c3s.cc).
20
+
21
+Danke für Deinen Beitrag zur C3S!
22
+
23
+
24
+Liebe Grüße,
25
+
26
+Das C3S-Team
27
+"""
28
+                 )
29
+    else:
30
+        _body = (u"""Dear new member,
31
+
32
+Your transfer of """ + str(_sum_shares) + u" Euro for " + str(_num_shares) +
33
+                 u""" shares just showed in our bank account.
34
+
35
+In case of any problems please don't hesitate to contact us (yes@c3s.cc).
36
+
37
+Thanks a lot for your contribution to the C3S!
38
+
39
+
40
+Best wishes,
41
+
42
+The C3S Team"""
43
+                 )
44
+
45
+    return _body
46
+
47
+
48
+def make_signature_confirmation_emailbody(_input):
49
+    """
50
+    a mail body to confirm reception of signature
51
+    """
52
+    _num_shares = _input.num_shares
53
+    _sum_shares = _num_shares * 50
54
+
55
+    if 'de' in _input.locale:
56
+        _body = (u"""Liebes Neumitglied,
57
+
58
+Dein Beitrittsformular zur Zeichnung von """ +
59
+                 str(_num_shares) +
60
+                 u' Anteilen (' +
61
+                 str(_sum_shares) + u""" Euro) ist sicher bei uns gelandet.
62
+
63
+Falls Probleme aufgetreten sind, melde Dich bitte bei uns (yes@c3s.cc).
64
+
65
+Schön, dass Du ein Teil der C3S werden möchtest!
66
+
67
+
68
+Liebe Grüße,
69
+
70
+Das C3S-Team
71
+""")
72
+    else:
73
+        _body = (u"""Dear new member,
74
+
75
+Your membership application form to sign """ +
76
+                 str(_num_shares) +
77
+                 u' shares (' +
78
+                 str(_sum_shares) +
79
+                 u""" Euro) safely arrived at our homebase.
80
+
81
+In case of any problems please don't hesitate to contact us (yes@c3s.cc).
82
+
83
+Great that you want to become a part of the C3S!
84
+
85
+
86
+Best wishes,
87
+
88
+The C3S Team
89
+""")
90
+    return _body

+ 6
- 0
c3smembership/models.py View File

@@ -172,9 +172,15 @@ class C3sMember(Base):
172 172
     signature_received = Column(Boolean, default=False)
173 173
     signature_received_date = Column(
174 174
         DateTime(), default=datetime(1970, 1, 1))
175
+    signature_confirmed = Column(Boolean, default=False)
176
+    signature_confirmed_date = Column(
177
+        DateTime(), default=datetime(1970, 1, 1))
175 178
     payment_received = Column(Boolean, default=False)
176 179
     payment_received_date = Column(
177 180
         DateTime(), default=datetime(1970, 1, 1))
181
+    payment_confirmed = Column(Boolean, default=False)
182
+    payment_confirmed_date = Column(
183
+        DateTime(), default=datetime(1970, 1, 1))
178 184
     accountant_comment = Column(Unicode(255))
179 185
     membership_type = Column(Unicode(255))
180 186
     member_of_colsoc = Column(Boolean, default=False)

+ 32
- 6
c3smembership/templates/dashboard.pt View File

@@ -42,8 +42,10 @@
42 42
             <td>email</td>
43 43
             <td>email<br />confirmed?</td>
44 44
             <td>recreate<br />PDF</td>
45
-            <td>signature<br />received?</td>
46
-            <td>payment<br />received?</td>
45
+            <td>sig.<br />rec'd?</td>
46
+            <td>send sig.<br />conf. email</td>
47
+            <td>paym.<br />rec'd?</td>
48
+            <td>send paym.<br />conf. email</td>
47 49
             <td># shares</td>
48 50
             <td>edit</td>
49 51
             <td>delete</td>
@@ -58,28 +60,52 @@
58 60
 	    <td><a href="/re_C3S_SCE_AFM_${member.email_confirm_code}.pdf">PDF</a></td>
59 61
 	    <td>
60 62
               <div tal:condition="not member.signature_received">
61
-                <a href="${request.route_url('switch_sig', memberid=member.id)}">
63
+                <a href="${request.route_url('switch_sig', memberid=member.id)}"
64
+                   title="no signature received as of now. click to toggle...">
62 65
                   <img src="${request.static_url('c3smembership:static/flash_red.gif')}" width="20px" height="20px" />
63 66
                 </a>
64 67
               </div>
65 68
               <div tal:condition="member.signature_received">
66
-                <a href="${request.route_url('switch_sig', memberid=member.id)}">
69
+                <a href="${request.route_url('switch_sig', memberid=member.id)}"
70
+                   title="signature received at ${member.signature_received_date}. click to unset...">
67 71
                   <img src="${request.static_url('c3smembership:static/green.png')}" width="20px" height="20px" />
68 72
                 </a>
69 73
               </div>
70 74
             </td>
71 75
 	    <td>
76
+              <div tal:condition="not member.signature_confirmed">
77
+                <a href="${request.route_url('mail_sig_confirmation',
78
+                         memberid=member.id)}">Mail!</a>
79
+              </div>
80
+              <div tal:condition="member.signature_confirmed">
81
+                <a href="#"
82
+                   title="${member.signature_confirmed_date}">ok</a>
83
+              </div>
84
+            </td>
85
+	    <td>
72 86
               <div tal:condition="not member.payment_received">
73
-                <a href="${request.route_url('switch_pay', memberid=member.id)}">
87
+                <a href="${request.route_url('switch_pay', memberid=member.id)}"
88
+                   title="no payment received as of now. click to toggle...">
74 89
                   <img src="${request.static_url('c3smembership:static/flash_red.gif')}" width="20px" height="20px" />
75 90
                 </a>
76 91
               </div>
77 92
               <div tal:condition="member.payment_received">
78
-                <a href="${request.route_url('switch_pay', memberid=member.id)}">
93
+                <a href="${request.route_url('switch_pay', memberid=member.id)}"
94
+                   title="payment received at ${member.payment_received_date}. click to change: we have NOT received payment!">
79 95
                   <img src="${request.static_url('c3smembership:static/green.png')}" width="20px" height="20px" />
80 96
                 </a>
81 97
               </div>
82 98
             </td>
99
+	    <td>
100
+              <div tal:condition="not member.payment_confirmed">
101
+                <a href="${request.route_url('mail_pay_confirmation',
102
+                         memberid=member.id)}">Mail!</a>
103
+              </div>
104
+              <div tal:condition="member.payment_confirmed">
105
+                <a href="#"
106
+                   title="${member.payment_confirmed_date}">ok</a>
107
+              </div>
108
+            </td>
83 109
 	    <td>${member.num_shares}</td>
84 110
 	    <td><a href="/detail/${member.id}">edit</a></td>
85 111
 	    <td><a href="/delete/${member.id}">delete</a></td>

+ 19
- 0
c3smembership/templates/detail.pt View File

@@ -57,6 +57,10 @@
57 57
 	  <td>${member.country}</td>
58 58
         </tr>
59 59
 	<tr>
60
+          <td>locale</td>
61
+	  <td>${member.locale}</td>
62
+        </tr>
63
+	<tr>
60 64
           <td>date_of_birth</td>
61 65
 	  <td>${member.date_of_birth}</td>
62 66
         </tr>
@@ -79,9 +83,24 @@
79 83
 	<tr>
80 84
           <td>signature received?</td><td>${member.signature_received or "No"}</td>
81 85
         </tr>
86
+	<tr tal:condition="member.signature_received">
87
+          <td>signature reception date</td><td>${member.signature_received_date}</td>
88
+        </tr>
89
+	<tr>
90
+          <td>signature confirmed?</td><td>${member.signature_confirmed or "No"}</td>
91
+        </tr>
92
+	<tr tal:condition="member.signature_confirmed">
93
+          <td>signature reception date</td><td>${member.signature_confirmed_date}</td>
94
+        </tr>
82 95
 	<tr>
83 96
           <td>payment received?</td><td>${member.payment_received or "No"}</td>
84 97
         </tr>
98
+	<tr tal:condition="member.payment_received">
99
+          <td>payment reception date</td><td>${member.payment_received_date}</td>
100
+        </tr>
101
+	<tr tal:condition="member.payment_confirmed">
102
+          <td>payment reception date</td><td>${member.payment_confirmed_date}</td>
103
+        </tr>
85 104
 	<tr>
86 105
           <td># shares</td>
87 106
 	  <td>${member.num_shares}</td>

+ 1
- 0
setup.py View File

@@ -25,6 +25,7 @@ requires = [
25 25
     'waitress',
26 26
     'python-gnupg',
27 27
     'unicodecsv',
28
+    'alembic',  # migrate the database when introducing new fields
28 29
 ]
29 30
 # for the translations machinery using transifex you also need to
30 31
 # "pip install transifex-client"

Loading…
Cancel
Save