Esempio n. 1
0
    def test_calculate_partial_dues15(self):
        """
        A test to check if partial dues are calculated the right way.

        "Partial dues" means you have to pay for half a year only, for example.
        """
        from c3smembership.views.membership_dues import (
            calculate_partial_dues15)
        member = C3sMember.get_by_id(1)
        res = calculate_partial_dues15(member)
        # print res
        # print member.membership_date
        assert res == (u'q1_2015', D('50'))

        # english member
        member_en = C3sMember.get_by_id(2)
        res = calculate_partial_dues15(member_en)
        # print res
        assert res == (u'q1_2015', D('50'))

        member_en.membership_date = date(2015, 6, 1)
        res = calculate_partial_dues15(member_en)
        # print res
        assert res == (u'q2_2015', D('37.50'))

        member_en.membership_date = date(2015, 9, 1)
        res = calculate_partial_dues15(member_en)
        # print res
        assert res == (u'q3_2015', D('25'))

        member_en.membership_date = date(2015, 11, 1)
        res = calculate_partial_dues15(member_en)
        # print res
        assert res == (u'q4_2015', D('12.50'))
Esempio n. 2
0
def mail_payment_confirmation(request):
    """
    send a mail to membership applicant
    informing her about reception of payment
    """
    _id = request.matchdict['memberid']
    _member = C3sMember.get_by_id(_id)

    if _member.locale == 'de':
        _subject = u'[C3S AFM] Wir haben Deine Zahlung erhalten. Dankeschön!'
    else:
        _subject = u'[C3S AFM] We have received your payment. Thanks!'

    message = Message(
        subject=_subject,
        sender='*****@*****.**',
        recipients=[_member.email],
        body=make_payment_confirmation_emailbody(_member)
    )
    #print(message.body)
    mailer = get_mailer(request)
    mailer.send(message)
    _member.payment_confirmed = True
    _member.payment_confirmed_date = datetime.now()
    return HTTPFound(request.route_url('dashboard',
                                       number=request.cookies['on_page'],
                                       order=request.cookies['order'],
                                       orderby=request.cookies['orderby'],
                                       )
                     )
Esempio n. 3
0
def mail_payment_reminder(request):
    """
    send a mail to membership applicant
    reminding her about lack of signature
    """
    _id = request.matchdict['memberid']
    _member = C3sMember.get_by_id(_id)

    message = Message(
        subject=u"C3S: don't forget to pay your shares / Bitte Anteile bezahlen",
        sender='*****@*****.**',
        #bcc=[request.registry.settings['reminder_blindcopy']],
        recipients=[_member.email],
        body=make_payment_reminder_emailbody(_member)
    )
    mailer = get_mailer(request)
    mailer.send(message)
    try:  # if value is int
        _member.sent_payment_reminder += 1
    except:  # pragma: no cover
        # if value was None (after migration of DB schema)
        _member.sent_payment_reminder = 1
    _member.sent_payment_reminder_date = datetime.now()
    return HTTPFound(request.route_url(
        'dashboard',
        number=request.cookies['on_page'],
        order=request.cookies['order'],
        orderby=request.cookies['orderby']) + '#member_' + str(_member.id)
    )
def send_certificate_email(request):
    """
    Send email to a member with a link
    so the member can get her membership certificate.
    """
    _special_condition = False  # for redirects to referrer

    mid = request.matchdict["id"]
    member = C3sMember.get_by_id(mid)
    if isinstance(member, NoneType) or not member.is_member():
        return Response("that id does not exist or is not an accepted member. go back", status="404 Not Found")
    # create a token for the certificate
    member.certificate_token = make_random_token()

    email_subject, email_body = make_membership_certificate_email(request, member)

    the_message = Message(subject=email_subject, sender="*****@*****.**", recipients=[member.email], body=email_body)
    send_message(request, the_message)

    member.certificate_email = True
    member.certificate_email_date = datetime.now()

    try:  # pragma: no cover
        if "detail" in request.referrer:
            _special_condition = True
    except TypeError:  # pragma: no cover
        pass

    if _special_condition:  # pragma: no cover
        return HTTPFound(location=request.referrer + "#certificate")
    else:
        return get_memberhip_listing_redirect(request, member.id)
Esempio n. 5
0
def switch_pay(request):
    """
    This view lets accountants switch member signature info
    has their signature arrived?
    """
    memberid = request.matchdict['memberid']
    dashboard_page = request.cookies['on_page']
    order = request.cookies['order']
    order_by = request.cookies['orderby']
    _member = C3sMember.get_by_id(memberid)

    if _member.payment_received is True:  # change to NOT SET
        _member.payment_received = False
        _member.payment_received_date = datetime(1970, 1, 1)
    elif _member.payment_received is False:  # set to NOW
        _member.payment_received = True
        _member.payment_received_date = datetime.now()

    log.info(
        "payment info of member.id %s changed by %s to %s" % (
            _member.id,
            request.user.login,
            _member.payment_received
        )
    )
    return HTTPFound(
        request.route_url('dashboard',
                          number=dashboard_page, order=order, orderby=order_by))
Esempio n. 6
0
def switch_sig(request):
    """
    This view lets accountants switch member signature info
    has their signature arrived?
    """
    memberid = request.matchdict['memberid']
    #log.info("the id: %s" % memberid)

    # store the dashboard page the admin came from
    dashboard_page = request.cookies['on_page']
    order = request.cookies['order']
    order_by = request.cookies['orderby']

    _member = C3sMember.get_by_id(memberid)
    if _member.signature_received is True:
        _member.signature_received = False
        _member.signature_received_date = datetime(1970, 1, 1)
    elif _member.signature_received is False:
        _member.signature_received = True
        _member.signature_received_date = datetime.now()

    log.info(
        "signature status of member.id %s changed by %s to %s" % (
            _member.id,
            request.user.login,
            _member.signature_received
        )
    )

    return HTTPFound(
        request.route_url('dashboard',
                          number=dashboard_page, order=order, orderby=order_by))
Esempio n. 7
0
    def test_shares_edit(self):
        '''
        tests for the shares_edit view
        '''
        # unauthorized access must be prevented
        res = self.testapp.reset()  # delete cookie
        res = self.testapp.get('/shares_edit/1', status=403)
        assert('Access was denied to this resource' in res.body)
        res = self.testapp.get('/login', status=200)
        self.failUnless('login' in res.body)
        # try valid user
        form = res.form
        form['login'] = u'rut'
        form['password'] = u'berries'
        res2 = form.submit('submit', status=302)
        # # being logged in ...
        res3 = res2.follow()  # being redirected to dashboard with parameters
        self.failUnless('Dashboard' in res3.body)

        # no member in DB, so redirecting to dashboard
        res = self.testapp.get('/shares_edit/1', status=302)
        res2 = res.follow()

        self.make_member_with_shares()

        # now there is a member with shares in the DB
        #
        # lets try invalid input
        res = self.testapp.get('/shares_edit/foo', status=302)
        res2 = res.follow()
        self.failUnless('Dashboard' in res2.body)

        # now try valid id
        res = self.testapp.get('/shares_edit/1', status=200)
        self.failUnless('Edit Details for Shares' in res.body)

        # now we change details, really editing that member
        form = res.form

        if DEBUG:
            print "form.fields: {}".format(form.fields)

        self.assertTrue('2' in form['number'].value)
        self.assertTrue(datetime.today().strftime(
            '%Y-%m-%d') in form['date_of_acquisition'].value)
        # print(form['date_of_acquisition'].value)
        form['number'] = u'3'
        form['date_of_acquisition'] = u'2015-01-02'

        # try to submit now. this must fail,
        # because the date of birth is wrong
        # ... and other dates are missing
        res2 = form.submit('submit', status=200)

        # check data in DB
        _m1 = C3sMember.get_by_id(1)
        self.assertTrue(_m1.shares[0].number is 3)
        self.assertTrue(str(
            _m1.shares[0].date_of_acquisition) in str(datetime(2015, 1, 2)))
Esempio n. 8
0
    def test_send_certificate_email_english(self):
        """
        test the send_certificate_email view (english)
        """
        if DEBUG:
            print('test_send_certificate_email_english')
        from c3smembership.membership_certificate import send_certificate_email
        self.config.add_route('join', '/')
        self.config.add_route('dashboard', '/')
        self.config.add_route('certificate_pdf', '/')
        self.config.add_route('membership_listing_backend', '/')
        from pyramid_mailer import get_mailer
        request = testing.DummyRequest()
        request.matchdict = {
            'id': '2',
            'name': 'foobar',
            'token': 'hotzenplotz'  # WRONG/INVALID token
        }
        request.cookies['on_page'] = 1
        request.cookies['order'] = 'asc'
        request.cookies['orderby'] = 'id'

        mailer = get_mailer(request)
        result = send_certificate_email(request)
        # print result
        self.assertTrue(result.status_code == 404)  # not found
        self.assertEqual(len(mailer.outbox), 0)

        request.matchdict = {
            'id': '2',
            'name': 'foobar',
            'token': 'hotzenplotz123'
        }
        member2 = C3sMember.get_by_id(2)
        member2.membership_accepted = True

        # the request needs stuff to be in the cookie (for redirects)
        request.cookies['m_on_page'] = 23
        request.cookies['m_order'] = 'asc'
        request.cookies['m_orderby'] = 'id'
        request.referrer = 'dashboard'

        result = send_certificate_email(request)
        # print result
        self.assertTrue(result.status_code == 302)  # redirect

        self.assertEqual(len(mailer.outbox), 1)
        self.assertEqual(
            mailer.outbox[0].subject,
            u"C3S membership certificate"
        )
        # print mailer.outbox[0].body
        self.assertTrue(
            u"Hello AAASomeFirstnäme XXXSomeLastnäme,"
            in mailer.outbox[0].body)
        self.assertTrue(
            u"your personal membership certificate" in mailer.outbox[0].body)
Esempio n. 9
0
def dues16_notice(request):
    """
    notice of arrival for transferral of dues
    """
    # member: sanity checks
    try:
        member_id = request.matchdict['member_id']
        member = C3sMember.get_by_id(member_id)  # is in database
        assert member.membership_accepted  # is a member
        assert 'investing' not in member.membership_type  # is normal member
    except (KeyError, AssertionError):  # pragma: no cover
        request.session.flash(
            u"No member OR not accepted OR not normal member",
            'dues16notice_message_to_staff'  # message queue for staff
        )
        return HTTPFound(
            request.route_url('detail', memberid=member.id) + '#dues16')

    # sanity check: the given amount is a positive decimal
    try:
        paid_amount = D(request.POST['amount'])
        assert not paid_amount.is_signed()
        if DEBUG:
            print("DEBUG: payment of {}".format(paid_amount))
    except (KeyError, AssertionError):  # pragma: no cover
        request.session.flash(
            (u"Invalid amount to pay: '{}' "
             u"Use the dot ('.') as decimal mark, e.g. '23.42'".format(
                 request.POST['amount'])),
            'dues16notice_message_to_staff'  # message queue for user
        )
        return HTTPFound(
            request.route_url('detail', memberid=member.id) + '#dues16')

    # sanity check: the given date is a valid date
    try:
        paid_date = datetime.strptime(
            request.POST['payment_date'], '%Y-%m-%d')

        if DEBUG:
            print("DEBUG: payment received on {}".format(paid_date))
    except (KeyError, AssertionError):  # pragma: no cover
        request.session.flash(
            (u"Invalid date for payment: '{}' "
             u"Use YYYY-MM-DD, e.g. '2016-09-11'".format(
                 request.POST['payment_date'])),
            'dues16notice_message_to_staff'  # message queue for user
        )
        return HTTPFound(
            request.route_url('detail', memberid=member.id) + '#dues16')

    # persist info about payment
    member.set_dues16_payment(paid_amount, paid_date)

    return HTTPFound(
        request.route_url('detail', memberid=member.id) + '#dues16')
Esempio n. 10
0
def mail_mtype_fixer_link(request):
    '''
    send email to member to set her membership type details by visiting a form
    '''
    afmid = request.matchdict['afmid']
    afm = C3sMember.get_by_id(afmid)
    if isinstance(afm, NoneType):
        request.session.flash(
            'id not found. no mail sent.',
            'messages')
        return HTTPFound(request.route_url('dashboard',
                                           number=request.cookies['on_page'],
                                           order=request.cookies['order'],
                                           orderby=request.cookies['orderby']))

    import random
    import string
    _looong_token = u''.join(
        random.choice(
            string.ascii_uppercase + string.digits) for x in range(13))
    _url = (request.registry.settings['c3smembership.url'] +
            '/mtype/' + afm.email_confirm_code +
            '/' + _looong_token + '/' + afm.email)

    from .mail_mtype_util import make_mtype_email_body
    _body = make_mtype_email_body(afm, _url)

    log.info("mailing membership status form link to AFM # %s" % afm.id)

    if afm.locale == 'de':
        _subject = u'[C3S] Hilfe benötigt: Dein Mitgliedschaftsstatus'
    else:
        _subject = u'[C3S] Help needed: Your Membership Status'

    message = Message(
        subject=_subject,
        sender='*****@*****.**',
        recipients=[
            afm.email,
            request.registry.settings['c3smembership.mailaddr']],
        body=_body
    )
    #print(message.subject)
    #print(message.body)
    mailer = get_mailer(request)
    mailer.send(message)
    afm.mtype_confirm_token = _looong_token
    afm.mtype_email_date = datetime.now()
    afm.membership_type = u'pending'
    return HTTPFound(request.route_url('dashboard',
                                       number=request.cookies['on_page'],
                                       order=request.cookies['order'],
                                       orderby=request.cookies['orderby']) +
                     '#member_' + str(afm.id))
def delete_entry(request):
    """
    This view lets accountants delete entries (doublettes)
    """
    memberid = request.matchdict["memberid"]
    dashboard_page = request.cookies["on_page"]
    _member = C3sMember.get_by_id(memberid)

    C3sMember.delete_by_id(_member.id)
    log.info("member.id %s was deleted by %s" % (_member.id, request.user.login))

    return HTTPFound(request.route_url("dashboard", number=dashboard_page))
Esempio n. 12
0
    def test_generate_certificate_english(self):
        """
        test the certificate download view (english)
        """
        from c3smembership.membership_certificate import generate_certificate
        request = testing.DummyRequest()
        request.matchdict = {
            'id': '2',
            'name': 'foobar',
            'token': 'hotzenplotz'
        }

        result = generate_certificate(request)
        if DEBUG:
            print(result)

        # check: this is *not* found because the token is *invalid*
        self.assertTrue(result.status_code == 404)  # not found

        request.matchdict = {
            'id': '2',
            'name': 'foobar',
            'token': 'hotzenplotz123'
        }
        member2 = C3sMember.get_by_id(2)
        member2.certificate_token = u'hotzenplotz123'
        # now the database matches the matchdict

        member2.certificate_email_date = datetime.now() - timedelta(weeks=1)
        member2.membership_accepted = True
        member2.membership_loss_date = date.today() - timedelta(days=1)
        result = generate_certificate(request)
        self.assertEqual(result.status_code, 404)

        member2.certificate_email_date = datetime.now() - timedelta(weeks=1)
        member2.membership_accepted = True
        member2.membership_loss_date = date.today() + timedelta(days=1)
        result = generate_certificate(request)
        self.assertEqual(result.status_code, 200)

        member2.certificate_email_date = datetime.now() - timedelta(weeks=1)
        member2.membership_accepted = True
        member2.membership_loss_date = None
        result = generate_certificate(request)

        if DEBUG:  # pragma: no cover
            print("size of resulting certificate PDF here: {}".format(
                len(result.body)))
            print("min and max: {} {}".format(
                _min_PDF_size, _max_PDF_size))

        self.assertTrue(_min_PDF_size < len(result.body) < _max_PDF_size)
        self.assertTrue(result.content_type == 'application/pdf')
Esempio n. 13
0
def invite_member_BCGV(request):
    '''
    Send email to member with link to ticketing.

    === =====================================
    URL http://app:port/invite_member/{m_id}
    === =====================================
    '''
    mid = request.matchdict['m_id']
    _m = C3sMember.get_by_id(mid)
    if isinstance(_m, NoneType):
        request.session.flash(
            'id not found. no mail sent.',
            'messages')
        return get_dashboard_redirect(request)

    # prepare a random token iff none is set
    if _m.email_invite_token_bcgv16 is None:
        _m.email_invite_token_bcgv16 = make_random_token()
    _url = (
        request.registry.settings['ticketing.url'] +
        '/lu/' + _m.email_invite_token_bcgv16 +
        '/' + _m.email)

    email_subject, email_body = make_bcga16_invitation_email(_m, _url)

    log.info("mailing event invitation to to member id %s" % _m.id)

    message = Message(
        subject=email_subject,
        sender='*****@*****.**',
        recipients=[_m.email],
        body=email_body,
        extra_headers={
            'Reply-To': '*****@*****.**',
            }
    )

    print_mail = True if 'true' in request.registry.settings[
        'testing.mail_to_console'] else False

    if print_mail:  # pragma: no cover
        print(message.body.encode('utf-8'))
    else:
        # print "sending mail"
        mailer = get_mailer(request)
        mailer.send(message)

    # _m._token = _looong_token
    _m.email_invite_flag_bcgv16 = True
    _m.email_invite_date_bcgv16 = datetime.now()
    return get_dashboard_redirect(request, _m.id)
Esempio n. 14
0
    def test_api_userinfo(self):
        """
        Test the api_userinfo service.

        * must be a PUT, not a GET request
        * the auth header must be present
        * returns None if members refcode does not match
        * returns firstname, lastname, email, membership type
        """
        # try a GET -- must fail
        res = self.testapp.get("/lm", status=405)
        self.assertTrue("405 Method Not Allowed" in res.body)
        self.assertTrue("The method GET is not allowed for this resource." in res.body)

        # try a PUT -- fails under certain conditions
        with self.assertRaises(ValueError):
            res = self.testapp.put("/lm", status=200)
            # ValueError: No JSON object could be decoded

        # try a PUT -- fails under certain conditions
        with self.assertRaises(KeyError):
            res = self.testapp.put_json("/lm", dict(id=1))  # status=200)
            # KeyError: 'token'
        # missing auth token -- must fail
        with self.assertRaises(KeyError):
            res = self.testapp.put_json("/lm", dict(token=1))  # status=200)
            # KeyError: 'HTTP_X_MESSAGING_TOKEN'

        # try false auth token -- must fail: 401 unauthorized
        _headers = {"X-messaging-token": "bar"}
        res = self.testapp.put_json("/lm", dict(token=1), headers=_headers, status=401)

        # now use the correct auth token
        _auth_info = {"X-messaging-token": "SECRETAUTHTOKEN"}

        # ..but a non-existing refcode (email_invite_token_bcgv16)
        # returns no user (None)
        res = self.testapp.put_json("/lm", dict(token="foo"), headers=_auth_info, status=200)
        # body: {"lastname": "None", "firstname": "None"}
        self.assertTrue(json.loads(res.body)["firstname"], "None")
        self.assertTrue(json.loads(res.body)["lastname"], "None")

        self.testapp.reset()

        m1 = C3sMember.get_by_id(1)  # load member from DB for crosscheck

        # now try a valid refcode (email_invite_token_bcgv16)
        res2 = self.testapp.put_json("/lm", dict(token=m1.email_invite_token_bcgv16), headers=_auth_info, status=200)
        self.assertTrue(json.loads(res2.body)["firstname"], m1.firstname)
        self.assertTrue(json.loads(res2.body)["lastname"], m1.lastname)
        self.assertTrue(json.loads(res2.body)["email"], m1.email)
        self.assertTrue(json.loads(res2.body)["mtype"], m1.membership_type)
Esempio n. 15
0
    def test_merge_member_view(self):
        '''
        Tests for the merge_member_view
        '''
        res = self.testapp.reset()  # delete cookies

        afm = C3sMember.get_by_id(2)  # an application
        m = C3sMember.get_by_id(1)  # an accepted member

        self.assertTrue(afm.membership_accepted is False)
        self.assertEqual(afm.num_shares, 2)
        self.assertEqual(afm.shares, [])

        self.assertTrue(m.membership_accepted is True)
        self.assertEqual(m.num_shares, 23)
        self.assertEqual(len(m.shares), 2)  # 2 shares packages

        # try unauthenticated access -- must fail!
        res = self.testapp.get(
            '/merge_member/{afm_id}/{mid}'.format(
                afm_id=afm.id,
                mid=m.id),
            status=403)
        self.failUnless('Access was denied to this resource' in res.body)

        # authenticate/authorize
        self._MemberTestsBase__login()

        res = self.testapp.get(
            '/merge_member/{afm_id}/{mid}'.format(
                afm_id=afm.id,
                mid=m.id),
            status=302)  # redirect!

        self.assertTrue(afm.membership_accepted is False)
        self.assertTrue(m.membership_accepted is True)
        self.assertEqual(m.num_shares, 25)
        self.assertEqual(len(m.shares), 3)  # 2 shares packages
Esempio n. 16
0
    def test_generate_certificate_staff(self):
        """
        test the certificate generation option in the backend
        """
        from c3smembership.membership_certificate import \
            generate_certificate_staff
        request = testing.DummyRequest()

        # wrong id
        request.matchdict = {
            'id': '1000',  # token is not necessary here
        }
        result = generate_certificate_staff(request)
        self.assertTrue('Not found. Please check URL.' in result.body)

        m1 = C3sMember.get_by_id(1)

        # membership not accepted
        request.matchdict = {
            'id': '1',  # token is not necessary here
        }
        result = generate_certificate_staff(request)
        self.assertTrue('is not an accepted member' in result.body)
        self.assertEqual(result.status_code, 404)

        # was accepted but lost membership
        m1.membership_accepted = True
        m1.membership_loss_date = date.today() - timedelta(days=1)
        result = generate_certificate_staff(request)
        self.assertTrue('is not an accepted member' in result.body)
        self.assertEqual(result.status_code, 404)

        # was accepted and loses membership in the future
        m1.membership_accepted = True
        m1.membership_loss_date = date.today() + timedelta(days=1)
        result = generate_certificate_staff(request)
        self.assertEqual(result.status_code, 200)

        # accepted member
        m1.membership_accepted = True
        m1.membership_loss_date = None
        result = generate_certificate_staff(request)
        # print("result: {}".format(result))
        if DEBUG:  # pragma: no cover
            print("size of resulting certificate PDF: {}".format(
                len(result.body)))

        self.assertTrue(_min_PDF_size < len(result.body) < _max_PDF_size)
        self.assertTrue(result.content_type == 'application/pdf')
Esempio n. 17
0
def mail_mtype_fixer_link(request):
    '''
    Send email to prospective member
    to let her set her membership type details by visiting a form.

    Was needed for crowdfunders from startnext: data was missing.
    '''
    afmid = request.matchdict['afmid']
    afm = C3sMember.get_by_id(afmid)
    if isinstance(afm, NoneType):
        request.session.flash(
            'id not found. no mail sent.',
            'messages')
        return get_dashboard_redirect(request)

    import random
    import string
    _looong_token = u''.join(
        random.choice(
            string.ascii_uppercase + string.digits) for x in range(13))
    _url = (request.registry.settings['c3smembership.url'] +
            '/mtype/' + afm.email_confirm_code +
            '/' + _looong_token + '/' + afm.email)

    from .mail_mtype_util import make_mtype_email_body
    _body = make_mtype_email_body(afm, _url)

    log.info("mailing membership status form link to AFM # %s" % afm.id)

    if afm.locale == 'de':
        _subject = u'[C3S] Hilfe benötigt: Dein Mitgliedschaftsstatus'
    else:
        _subject = u'[C3S] Help needed: Your Membership Status'

    message = Message(
        subject=_subject,
        sender='*****@*****.**',
        recipients=[
            afm.email,
            request.registry.settings['c3smembership.mailaddr']],
        body=_body
    )
    mailer = get_mailer(request)
    mailer.send(message)
    afm.mtype_confirm_token = _looong_token
    afm.mtype_email_date = datetime.now()
    afm.membership_type = u'pending'
    return get_dashboard_redirect(request, afm.id)
Esempio n. 18
0
def delete_entry(request):
    """
    This view lets accountants delete entries (doublettes)
    """
    memberid = request.matchdict['memberid']
    _member = C3sMember.get_by_id(memberid)

    C3sMember.delete_by_id(_member.id)
    log.info(
        "member.id %s was deleted by %s" % (
            _member.id,
            request.user.login,
        )
    )
    return HTTPFound(
        request.route_url('dashboard_only',  _query={'message': 'Member with id {0} was deleted.'.format(memberid)}))
Esempio n. 19
0
def generate_certificate_staff(request):
    """
    Generate the membership_certificate of any member for staffers.
    """
    mid = request.matchdict["id"]
    # print("Member id from matchdict: {}".format(mid))

    # chech if member exists
    member = C3sMember.get_by_id(mid)
    if member is None:
        return Response("Not found. Please check URL.")

    if isinstance(member, NoneType) or not member.is_member():
        return Response("Member with this id ({}) is not an accepted member!".format(mid), status="404 Not Found")

    return gen_cert(member)
Esempio n. 20
0
def mail_signature_reminder(request):
    """
    send a mail to membership applicant
    reminding her about lack of signature
    """
    _id = request.matchdict['memberid']
    _member = C3sMember.get_by_id(_id)
    if isinstance(_member, NoneType):
        request.session.flash(
            'that member was not found! (id: {})'.format(_id),
            'messages'
        )
        return HTTPFound(
            request.route_url(
                'dashboard',
                number=request.cookies['on_page'],
                order=request.cookies['order'],
                orderby=request.cookies['orderby']))

    # first reminder? second?
    #if ((_member.sent_signature_reminder is None
    #) or (    ):
    #_first =
    message = Message(
        subject=u"C3S: don't forget to send your form / Bitte Beitrittsformular einsenden",
        sender='*****@*****.**',
        #bcc=[request.registry.settings['reminder_blindcopy']],
        recipients=[_member.email],
        body=make_signature_reminder_emailbody(_member)
    )
    mailer = get_mailer(request)
    mailer.send(message)
    #print u"the mail: {}".format(message.body)
    #import pdb
    #pdb.set_trace()
    try:  # if value is int
        _member.sent_signature_reminder += 1
    except:  # pragma: no cover
        # if value was None (after migration of DB schema)
        _member.sent_signature_reminder = 1
    _member.sent_signature_reminder_date = datetime.now()
    return HTTPFound(request.route_url(
        'dashboard',
        number=request.cookies['on_page'],
        order=request.cookies['order'],
        orderby=request.cookies['orderby']) + '#member_' + str(_member.id)
    )
Esempio n. 21
0
def get_member(request):
    """
    This function serves an AJAX-call from the dashboard.

    There will be one call per application for membership listed!
    """
    memberid = request.matchdict['memberid']
    member = C3sMember.get_by_id(memberid)
    if member is None:
        return {}
    else:
        return {
            'id': member.id,
            'firstname': member.firstname,
            'lastname': member.lastname
        }
    return None
Esempio n. 22
0
    def test_generate_certificate_founder(self):
        """
        test the certificate download view (german)
        """
        from c3smembership.membership_certificate import generate_certificate
        request = testing.DummyRequest()
        request.matchdict = {
            'id': '3',
            'name': 'foobar',
            'token': 'hotzenplotz123'
        }
        member = C3sMember.get_by_id(3)
        member.certificate_token = u'hotzenplotz123'
        member.membership_accepted = True

        # need to get the date right!
        member.certificate_email_date = datetime.now(
        ) - timedelta(weeks=1)
        result = generate_certificate(request)

        # print result.body
        self.assertTrue(_min_PDF_size < len(result.body) < _max_PDF_size)
        self.assertTrue(result.content_type == 'application/pdf')

        # edge case: member has one share
        member.certificate_token = u'hotzenplotz123'
        member.num_shares = 1

        result = generate_certificate(request)
        self.assertTrue(_min_PDF_size < len(result.body) < _max_PDF_size)
        self.assertTrue(result.content_type == 'application/pdf')

        # edge case: member has one share
        member.certificate_token = u'hotzenplotz123'
        member.is_legalentity = True

        result = generate_certificate(request)
        member.locale = u'de'
        result = generate_certificate(request)

        if DEBUG:  # pragma: no cover
            print("size of resulting certificate PDF: {}".format(
                len(result.body)))

        self.assertTrue(_min_PDF_size < len(result.body) < _max_PDF_size)
        self.assertTrue(result.content_type == 'application/pdf')
Esempio n. 23
0
def generate_certificate(request):
    """
    Generate a membership_certificate for a member.

    Member must posess a link containing an id and a valid token.
    Headquarters sends links to members upon request.
    """
    mid = request.matchdict["id"]
    token = request.matchdict["token"]

    try:
        member = C3sMember.get_by_id(mid)

        if DEBUG:  # pragma: no cover
            print member.firstname
            print member.certificate_token
            print type(member.certificate_token)  # NoneType
            print token
            print type(token)  # unicode

        # token may not ne None
        assert member.certificate_token is not None
        # token must match entry in database
        assert str(member.certificate_token) in str(token)
        # and database entry must match token
        assert str(token) in str(member.certificate_token)

        # check age of token
        from datetime import timedelta

        two_weeks = timedelta(weeks=2)
        assert member.certificate_email_date is not None
        delta = datetime.now() - member.certificate_email_date
        assert delta < two_weeks
    except AssertionError:
        return Response(
            "Not found. Or invalid credentials.  <br /><br /> "
            "Please contact [email protected]. <br /><br /> "
            "Nicht gefunden. Bitte [email protected] kontaktieren.",
            status="404 Not Found",
        )

    if isinstance(member, NoneType) or not member.is_member():
        return Response("that id does not exist or is not an accepted member. go back", status="404 Not Found")

    return gen_cert(member)
Esempio n. 24
0
def delete_entry(request):
    """
    This view lets accountants delete datasets (e.g. doublettes, test entries).
    """

    deletion_confirmed = (request.params.get('deletion_confirmed', '0') == '1')
    redirection_view = request.params.get('redirect', 'dashboard')
    LOG.info('redirect to: ' + str(redirection_view))

    if deletion_confirmed:
        memberid = request.matchdict['memberid']
        member = C3sMember.get_by_id(memberid)
        member_lastname = member.lastname
        member_firstname = member.firstname

        C3sMember.delete_by_id(member.id)
        LOG.info(
            "member.id %s was deleted by %s",
            member.id,
            request.user.login,
        )
        message = "member.id %s was deleted" % member.id
        request.session.flash(message, 'messages')

        msgstr = u'Member with id {0} \"{1}, {2}\" was deleted.'
        return HTTPFound(
            request.route_url(
                redirection_view,
                _query={'message': msgstr.format(
                    memberid,
                    member_lastname,
                    member_firstname)},
                _anchor='member_{id}'.format(id=str(memberid))
            )
        )
    else:
        return HTTPFound(
            request.route_url(
                redirection_view,
                _query={'message': (
                    'Deleting the member was not confirmed'
                    ' and therefore nothing has been deleted.')}
            )
        )
Esempio n. 25
0
    def test_member_list_date_pdf_view(self):  # code lines 80-283
        '''
        Tests for the member_list_aufstockers_view

        If called with a faulty date in URL (not parseable) expect redirection
        to error page.

        Else: expect a PDF.
        '''
        _date = '2016-02-11'  # any date
        _bad_date = '2016-02-111111'  # any bad date
        res = self.testapp.reset()  # delete cookies
        res = self.testapp.get('/aml-' + _date + '.pdf', status=403)
        self.failUnless('Access was denied to this resource' in res.body)

        self._MemberTestsBase__login()

        # try a bad date (== not convertable to a date)
        res = self.testapp.get('/aml-' + _bad_date + '.pdf', status=302)
        self.assertTrue('error' in res)
        res2 = res.follow()  # follow redirect
        self.assertTrue("Invalid date!" in res2.body)
        self.assertTrue("'2016-02-111111' does not compute!" in res2.body)
        self.assertTrue('try again, please! (YYYY-MM-DD)' in res2.body)

        # try with valid date in URL
        res = self.testapp.get('/aml-' + _date + '.pdf', status=200)
        # print(res)
        self.assertTrue(20000 < len(res.body) < 100000)
        self.assertEqual(res.content_type, 'application/pdf')

        # missing coverage of code lines  # 125-134, 192-225,
        m1 = C3sMember.get_by_id(1)
        m1.membership_date = date(2015, 01, 01)
        m1.membership_number = 42
        m1.shares[0].date_of_acquisition = datetime(2015, 01, 01)
        m1.shares[1].date_of_acquisition = datetime(2015, 01, 02)

        # try with valid date in URL
        res = self.testapp.get('/aml-' + _date + '.pdf', status=200)
        # print(res)
        self.assertTrue(20000 < len(res.body) < 100000)
        self.assertEqual(res.content_type, 'application/pdf')
Esempio n. 26
0
def mail_signature_reminder(request):
    """
    Send a mail to a membership applicant
    reminding her about lack of *signature*.
    Headquarters is still waiting for the *signed form*.

    This view can only be used by staff.

    To be approved for membership applicants have to

    * Transfer money for the shares to acquire (at least one share).
    * **Send the signed form** back to headquarters.
    """
    member_id = request.matchdict['memberid']
    member = C3sMember.get_by_id(member_id)
    if isinstance(member, NoneType):
        request.session.flash(
            'that member was not found! (id: {})'.format(member_id),
            'messages'
        )
        return get_dashboard_redirect(request, member.id)

    email_subject, email_body = make_signature_reminder_email(member)
    message = Message(
        subject=email_subject,
        sender='*****@*****.**',
        recipients=[member.email],
        body=email_body
    )
    send_message(request, message)

    try:
        member.sent_signature_reminder += 1
    except TypeError:
        # if value was None (after migration of DB schema)
        member.sent_signature_reminder = 1
    member.sent_signature_reminder_date = datetime.now()
    if 'detail' in request.referrer:
        return HTTPFound(request.route_url(
            'detail',
            memberid=request.matchdict['memberid']))
    else:
        return get_dashboard_redirect(request, member.id)
Esempio n. 27
0
    def test_string_start_quarter(self):
        """
        Tests for the strings used for partial time spans.
        """
        from c3smembership.views.membership_dues import (
            string_start_quarter)
        member = C3sMember.get_by_id(1)

        member.dues15_start = 'q1_2015'
        res = string_start_quarter(member)
        # print res
        assert('Quartal 1' in res)
        member.dues15_start = 'q2_2015'
        res = string_start_quarter(member)
        # print res
        assert('Quartal 2' in res)
        member.dues15_start = 'q3_2015'
        res = string_start_quarter(member)
        # print res
        assert('Quartal 3' in res)
        member.dues15_start = 'q4_2015'
        res = string_start_quarter(member)
        # print res
        assert('Quartal 4' in res)

        member.locale = u'en'
        member.dues15_start = 'q1_2015'
        res = string_start_quarter(member)
        # print res
        assert('1st quarter' in res)
        member.dues15_start = 'q2_2015'
        res = string_start_quarter(member)
        # print res
        assert('2nd quarter' in res)
        member.dues15_start = 'q3_2015'
        res = string_start_quarter(member)
        # print res
        assert('3rd quarter' in res)
        member.dues15_start = 'q4_2015'
        res = string_start_quarter(member)
        # print res
        assert('4th quarter' in res)
Esempio n. 28
0
def member_detail(request):
    """
    This view lets accountants view member details:

    - has their signature arrived?
    - how about the payment?

    Mostly all the info about an application or membership
    in the database can be seen here.
    """
    from decimal import Decimal as D
    logged_in = authenticated_userid(request)
    memberid = request.matchdict['memberid']
    LOG.info("member details of id %s checked by %s", memberid, logged_in)

    member = C3sMember.get_by_id(memberid)

    if member is None:  # that memberid did not produce good results
        request.session.flash(
            "A Member with id "
            "{} could not be found in the DB. run for the backups!".format(
                memberid),
            'message_to_staff'
        )
        return HTTPFound(  # back to base
            request.route_url('toolbox'))

    # get the members invoices from the DB
    invoices15 = Dues15Invoice.get_by_membership_no(member.membership_number)
    invoices16 = Dues16Invoice.get_by_membership_no(member.membership_number)

    return {
        'today': date.today().strftime('%Y-%m-%d'),
        'D': D,
        'member': member,
        'invoices15': invoices15,
        'invoices16': invoices16,
        # 'form': html
    }
Esempio n. 29
0
    def test_generate_certificate_awkward_characters(self):
        """
        test the certificate generation with awkward characters in datasets
        because LaTeX interprets some characters as special characters.
        """
        from c3smembership.membership_certificate import generate_certificate
        request = testing.DummyRequest()
        request.matchdict = {
            'id': '1',
            'name': 'foobar',
            'token': 'hotzenplotz'
        }

        result = generate_certificate(request)

        self.assertTrue(result.status_code == 404)  # not found

        request.matchdict = {
            'id': '1',
            'name': 'foobar',
            'token': 'hotzenplotz123'
        }
        member = C3sMember.get_by_id(1)
        member.firstname = u"Foobar Corp & Co."
        member.lastname = u"Your Number #1"
        member.certificate_token = u'hotzenplotz123'
        member.membership_accepted = True

        # # need to get the date right!
        member.certificate_email_date = datetime.now(
        ) - timedelta(weeks=1)

        result = generate_certificate(request)
        if DEBUG:  # pragma: no cover
            print("size of resulting certificate PDF: {}".format(
                len(result.body)))

        self.assertTrue(_min_PDF_size < len(result.body) < _max_PDF_size)
        self.assertTrue(result.content_type == 'application/pdf')
Esempio n. 30
0
    def test_member_list_alphabetical_view(self):  # code lines 286-325
        '''
        tests for the member_list_alphabetical_view
        '''
        res = self.testapp.reset()  # delete cookies
        res = self.testapp.get('/aml', status=403)
        self.failUnless('Access was denied to this resource' in res.body)

        self._MemberTestsBase__login()

        member4_lost = C3sMember.get_by_id(4)
        member4_lost.membership_accepted = True
        member4_lost.membership_number = 9876

        res = self.testapp.get('/aml', status=200)
        self.assertTrue('2 Mitglieder' in res.body)

        member4_lost.membership_date = date.today() - timedelta(days=365)
        member4_lost.membership_loss_date = \
            date.today() - timedelta(days=30)

        res = self.testapp.get('/aml', status=200)
        self.assertTrue('1 Mitglieder' in res.body)
Esempio n. 31
0
def send_dues16_invoice_email(request, m_id=None):
    """
    Send email to a member to prompt her to pay the membership dues.
    - For normal members, also send link to invoice.
    - For investing members that are legal entities,
      ask for additional support depending on yearly turnover.

    This view function works both if called via URL, e.g. /dues_invoice/123
    and if called as a function with a member id as parameter.
    The latter is useful for batch processing.

    When this function is used for the first time for one member,
    some database fields are filled:
    - Invoice number
    - Invoice amount (calculated from date of membership approval by the board)
    - Invoice token
    Also, the database table of invoices (and cancellations) is appended.

    If this function gets called the second time for a member,
    no new invoice is produced, but the same mail sent again.
    """
    # either we are given a member id via url or function parameter
    try:  # view was called via http/s
        member_id = request.matchdict['member_id']
        batch = False
    except KeyError:  # ...or was called as function with parameter (see batch)
        member_id = m_id
        batch = True

    try:  # get member from DB
        member = C3sMember.get_by_id(member_id)
        assert(member is not None)
    except AssertionError:
        if not batch:
            request.session.flash(
                "member with id {} not found in DB!".format(member_id),
                'message_to_staff')
            return HTTPFound(request.route_url('toolbox'))

    # sanity check:is this a member?
    try:
        assert(member.membership_accepted)  # must be accepted member!
    except AssertionError:
        request.session.flash(
            "member {} not accepted by the board!".format(member_id),
            'message_to_staff')
        return HTTPFound(request.route_url('toolbox'))

    if 'normal' not in member.membership_type and \
            'investing' not in member.membership_type:
        request.session.flash(
            'The membership type of member {0} is not specified! The '
            'membership type must either be "normal" or "investing" in order '
            'to be able to send an invoice email.'.format(member.id),
            'message_to_staff')
        return get_memberhip_listing_redirect(request)
    if member.membership_date >= date(2017,1,1):
        request.session.flash(
            'Member {0} was not a member in 2016. Therefore, you cannot send '
            'an invoice for 2016.'.format(member.id),
            'message_to_staff')
        return get_memberhip_listing_redirect(request)

    # check if invoice no already exists.
    #     if yes: just send that email again!
    #     also: offer staffers to cancel this invoice

    if member.dues16_invoice is True:
        invoice = Dues16Invoice.get_by_invoice_no(member.dues16_invoice_no)
        member.dues16_invoice_date = datetime.now()

    else:  # if no invoice already exists:
        # make dues token and ...
        randomstring = make_random_string()
        # check if dues token is already used
        while (Dues16Invoice.check_for_existing_dues16_token(randomstring)):
            # create a new one, if the new one already exists in the database
            randomstring = make_random_string()  # pragma: no cover

        # prepare invoice number
        try:
            # either we already have an invoice number for that client...
            invoice_no = member.dues16_invoice_no
            assert invoice_no is not None
        except AssertionError:
            # ... or we create a new one and save it
            # get max invoice no from db
            max_invoice_no = Dues16Invoice.get_max_invoice_no()
            # use the next free number, save it to db
            new_invoice_no = int(max_invoice_no) + 1
            DBSession.flush()  # save dataset to DB

        # calculate dues amount (maybe partial, depending on quarter)
        dues_start, dues_amount = calculate_partial_dues16(member)

        # now we have enough info to update the member info
        # and persist invoice info for bookkeeping
        # store some info in DB/member table
        member.dues16_invoice = True
        member.dues16_invoice_no = new_invoice_no  # irrelevant for investing
        member.dues16_invoice_date = datetime.now()
        member.dues16_token = randomstring
        member.dues16_start = dues_start

        if 'normal' in member.membership_type:  # only for normal members
            member.set_dues16_amount(dues_amount)
            # store some more info about invoice in invoice table
            invoice = Dues16Invoice(
                invoice_no=member.dues16_invoice_no,
                invoice_no_string=(
                    u'C3S-dues2016-' + str(member.dues16_invoice_no).zfill(4)),
                invoice_date=member.dues16_invoice_date,
                invoice_amount=u'' + str(member.dues16_amount),
                member_id=member.id,
                membership_no=member.membership_number,
                email=member.email,
                token=member.dues16_token,
            )
            DBSession.add(invoice)
        DBSession.flush()

    # now: prepare that email
    # only normal (not investing) members *have to* pay the dues.
    # only the normal members get an invoice link and PDF produced for them.
    # only investing legalentities are asked for more support.
    if 'investing' not in member.membership_type:
        start_quarter = string_start_quarter_dues16(member)
        invoice_url = (
            request.route_url(
                'make_dues16_invoice_no_pdf',
                email=member.email,
                code=member.dues16_token,
                i=str(member.dues16_invoice_no).zfill(4)
            )
        )
        email_subject, email_body = make_dues16_invoice_email(
            member,
            invoice,
            invoice_url,
            start_quarter)
        message = Message(
            subject=email_subject,
            sender='*****@*****.**',
            recipients=[member.email],
            body=email_body,
            extra_headers={
                'Reply-To': '*****@*****.**',
            }
        )
    elif 'investing' in member.membership_type:
        if member.is_legalentity:
            email_subject, email_body = \
                make_dues_invoice_legalentity_email(member)
        else:
            email_subject, email_body = \
                make_dues_invoice_investing_email(member)
        message = Message(
            subject=email_subject,
            sender='*****@*****.**',
            recipients=[member.email],
            body=email_body,
            extra_headers={
                'Reply-To': '*****@*****.**',
            }
        )

    # print to console or send mail
    if 'true' in request.registry.settings['testing.mail_to_console']:
        print(message.body.encode('utf-8'))  # pragma: no cover
    else:
        send_message(request, message)

    # now choose where to redirect
    if 'detail' in request.referrer:
        return HTTPFound(
            request.route_url(
                'detail',
                memberid=member.id) +
            '#dues16')
    if 'toolbox' in request.referrer:
        return HTTPFound(request.route_url('toolbox'))
    else:
        return get_memberhip_listing_redirect(request, member.id)
Esempio n. 32
0
    def test_invitation(self):
        """
        Test the invitation procedure for one single member at a time.

        Load this member from the DB,
        assure the email_invite_flag_bcgv18 and token are not set,
        prepare cookies, invite this member,
        assure the email_invite_flag_bcgv18 and token are now set,
        """
        from c3smembership.invite_members import invite_member_bcgv

        member1 = C3sMember.get_by_id(1)
        self.assertEqual(member1.email_invite_flag_bcgv18, False)
        self.assertTrue(member1.email_invite_token_bcgv18 is None)

        req = testing.DummyRequest()
        # have some cookies
        req.cookies['on_page'] = 0
        req.cookies['order'] = 'asc'
        req.cookies['orderby'] = 'id'

        # try with nonexistant id
        req.matchdict = {'m_id': 10000}
        res = invite_member_bcgv(req)
        self.assertEquals(302, res.status_code)

        req.matchdict = {'m_id': member1.id}
        res = invite_member_bcgv(req)

        self.assertEqual(member1.email_invite_flag_bcgv18, True)
        self.assertTrue(member1.email_invite_token_bcgv18 is not None)

        # now really send email
        self.config.registry.settings['testing.mail_to_console'] = 'false'
        mailer = get_mailer(req)
        res = invite_member_bcgv(req)

        self.assertEqual(len(mailer.outbox), 2)
        self.assertTrue(u'[C3S] Einladung zu Barcamp und Generalversammlung'
                        in mailer.outbox[0].subject)
        self.assertTrue(u'[C3S] Einladung zu Barcamp und Generalversammlung'
                        in mailer.outbox[1].subject)
        self.assertTrue(member1.firstname
                        in mailer.outbox[1].body)
        self.assertTrue(member1.email_invite_token_bcgv18
                        in mailer.outbox[1].body)

        # now send invitation to english member
        member2 = C3sMember.get_by_id(2)
        self.assertEqual(member2.email_invite_flag_bcgv18, False)
        self.assertTrue(member2.email_invite_token_bcgv18 is None)
        req.matchdict = {'m_id': member2.id}
        res = invite_member_bcgv(req)
        self.assertEqual(member2.email_invite_flag_bcgv18, True)
        self.assertTrue(member2.email_invite_token_bcgv18 is not None)
        self.assertEqual(len(mailer.outbox), 3)
        self.assertTrue(u'[C3S] Invitation to Barcamp and General Assembly'
                        in mailer.outbox[2].subject)
        self.assertTrue(member2.firstname
                        in mailer.outbox[2].body)
        self.assertTrue(member2.email_invite_token_bcgv18
                        in mailer.outbox[2].body)
Esempio n. 33
0
def edit_member(request):
    """
    Let staff edit a member entry.
    """
    try:
        _id = request.matchdict['_id']
        assert (isinstance(int(_id), int))
        member = C3sMember.get_by_id(_id)
        if isinstance(member, NoneType):
            return HTTPFound(request.route_url('dashboard'))
    except:
        return HTTPFound(request.route_url('dashboard'))

    # if we have a valid id, we can load a members data from the db
    # and put the data in an appstruct to fill the form
    appstruct = {}
    email_is_confirmed = 'yes' if member.email_is_confirmed else 'no'
    appstruct['person'] = {
        'firstname': member.firstname,
        'lastname': member.lastname,
        'email': member.email,
        'email_is_confirmed': email_is_confirmed,
        'address1': member.address1,
        'address2': member.address2,
        'postcode': member.postcode,
        'city': member.city,
        'country': member.country,
        'date_of_birth': member.date_of_birth,
        'locale': member.locale,
    }

    appstruct['membership_meta'] = {
        'membership_accepted':
        member.membership_accepted,
        'membership_date': (
            # this is necessary because membership_date's default is
            # 1970-01-01 which should be changed to None in the future
            u'' if member.membership_date == date(1970, 1, 1) else
            member.membership_date),
        'is_duplicate':
        member.is_duplicate,
        'is_duplicate_of':
        (u'' if member.is_duplicate_of is None else member.is_duplicate_of),
        'accountant_comment': (u'' if member.accountant_comment is None else
                               member.accountant_comment),
        'signature_received':
        member.signature_received,
        'signature_received_date':
        member.signature_received_date,
        'payment_received':
        member.payment_received,
        'payment_received_date':
        member.payment_received_date,
        'membership_loss_date':
        member.membership_loss_date,
        'membership_loss_type': (u'' if member.membership_loss_type is None
                                 else member.membership_loss_type),
    }
    appstruct['membership_info'] = {
        'membership_type': member.membership_type,
        'entity_type': u'legalentity' if member.is_legalentity else 'person',
        'member_of_colsoc': 'yes' if member.member_of_colsoc else 'no',
        'name_of_colsoc': member.name_of_colsoc,
    }
    membership_loss_types = (('', _(u'(Select)')), ('resignation',
                                                    _(u'Resignation')),
                             ('expulsion', _(u'Expulsion')),
                             ('death', _(u'Death')), ('bankruptcy',
                                                      _(u'Bankruptcy')),
                             ('winding-up', _(u'Winding-up')),
                             ('shares_transfer',
                              _(u'Transfer of remaining shares')))

    class PersonalData(colander.MappingSchema):
        """
        Colander schema of the personal data for editing member data.
        """
        firstname = colander.SchemaNode(
            colander.String(),
            title=_(u'(Real) First Name'),
            oid='firstname',
        )
        lastname = colander.SchemaNode(
            colander.String(),
            title=_(u'(Real) Last Name'),
            oid='lastname',
        )
        email = colander.SchemaNode(
            colander.String(),
            title=_(u'Email Address'),
            validator=colander.Email(),
            oid='email',
        )
        email_is_confirmed = colander.SchemaNode(
            colander.String(),
            title=_(u'Email Address Confirmed'),
            widget=deform.widget.RadioChoiceWidget(values=(
                (u'yes', _(u'Yes, confirmed')),
                (u'no', _(u'No, not confirmed')),
            )),
            missing=u'',
            oid='email_is_confirmed',
        )

        passwort = colander.SchemaNode(colander.String(),
                                       widget=deform.widget.HiddenWidget(),
                                       default='NoneSet',
                                       missing='NoneSetPurposefully')
        address1 = colander.SchemaNode(
            colander.String(),
            title=_(u'Addess Line 1'),
        )
        address2 = colander.SchemaNode(
            colander.String(),
            missing=u'',
            title=_(u'Address Line 2'),
        )
        postcode = colander.SchemaNode(colander.String(),
                                       title=_(u'Postal Code'),
                                       oid='postcode')
        city = colander.SchemaNode(
            colander.String(),
            title=_(u'City'),
            oid='city',
        )
        country = colander.SchemaNode(
            colander.String(),
            title=_(u'Country'),
            default=COUNTRY_DEFAULT,
            widget=deform.widget.SelectWidget(values=country_codes),
            oid='country',
        )
        date_of_birth = colander.SchemaNode(
            colander.Date(),
            title=_(u'Date of Birth'),
            default=date(2013, 1, 1),
            oid='date_of_birth',
        )
        locale = colander.SchemaNode(
            colander.String(),
            title=_(u'Locale'),
            widget=deform.widget.SelectWidget(values=locale_codes),
            missing=u'',
        )

    @colander.deferred
    def membership_loss_date_widget(node, keywords):
        """
        Returns a text or hidden input depending on the value of
        membership_accepted within the keywords.
        """
        if keywords.get('membership_accepted'):
            return deform.widget.TextInputWidget()
        else:
            return deform.widget.HiddenWidget()

    @colander.deferred
    def membership_loss_type_widget(node, keywords):
        """
        Returns a select or hidden input depending on the value of
        membership_accepted within the keywords.
        """
        if keywords.get('membership_accepted'):
            return deform.widget.SelectWidget(values=membership_loss_types)
        else:
            return deform.widget.HiddenWidget()

    class MembershipMeta(colander.Schema):
        """
        Colander schema of the meta data for editing member data.
        """
        membership_accepted = colander.SchemaNode(
            colander.Boolean(), title=_(u'Membership Accepted'))
        membership_date = colander.SchemaNode(
            colander.Date(),
            title=_(u'Membership Acceptance Date'),
            validator=Range(
                min=date(2013, 9, 24),
                max=date.today(),
                min_err=_(u'${val} is earlier than earliest date ${min}.'),
                max_err=_(u'${val} is later than latest date ${max}.')),
            missing=date(1970, 1, 1),
            oid='membership_date',
        )
        is_duplicate = colander.SchemaNode(
            colander.Boolean(),
            title=_(u'Is Duplicate'),
            oid='is_duplicate',
        )
        is_duplicate_of = colander.SchemaNode(
            colander.String(),
            title=_(u'Duplicate Id'),
            missing=u'',
            oid='duplicate_of',
        )
        signature_received = colander.SchemaNode(
            colander.Boolean(),
            title=_(u'Signature Received'),
            oid='signature_received',
        )
        signature_received_date = colander.SchemaNode(
            colander.Date(),
            title=_('Signature Receipt Date'),
            validator=Range(
                min=date(1070, 1, 1),
                max=date.today(),
                min_err=_(u'${val} is earlier than earliest date ${min}.'),
                max_err=_(u'${val} is later than latest date ${max}.')),
            missing=date(1970, 1, 1),
        )
        payment_received = colander.SchemaNode(
            colander.Boolean(),
            title=_(u'Payment Received'),
        )
        payment_received_date = colander.SchemaNode(
            colander.Date(),
            title=_(u'Payment Receipt Date'),
            validator=Range(
                min=date(1970, 1, 1),
                max=date.today(),
                min_err=_(u'${val} is earlier than earliest date ${min}.'),
                max_err=_(u'${val} is later than latest date ${max}.')),
            missing=date(1970, 1, 1),
            oid='_received_date',
        )
        membership_loss_date = colander.SchemaNode(
            colander.Date(),
            widget=membership_loss_date_widget,
            title=_(u'Date of the loss of membership'),
            default=None,
            missing=None,
            oid='membership_loss_date',
        )
        membership_loss_type = colander.SchemaNode(
            colander.String(),
            widget=membership_loss_type_widget,
            title=_(u'Type of membership loss'),
            default=None,
            missing=None,
            oid='membership_loss_type',
        )
        accountant_comment = colander.SchemaNode(
            colander.String(),
            title=_(u'Staff Comment: (255 letters)'),
            missing=u'',
            oid='accountant_comment',
        )

    class MembershipInfo(colander.Schema):
        """
        Colander schema of the additional data for editing member data.
        """
        yes_no = (
            (u'yes', _(u'Yes')),
            (u'no', _(u'No')),
            (u'dontknow', _(u'Unknwon')),
        )

        entity_type = colander.SchemaNode(
            colander.String(),
            title=_(u'Member Category'),
            description=_(u'Please choose the member category.'),
            widget=deform.widget.RadioChoiceWidget(values=(
                (u'person', _(u'Person')),
                (u'legalentity', _(u'Legal Entity')),
            ), ),
            missing=u'',
            oid='entity_type',
        )
        membership_type = colander.SchemaNode(
            colander.String(),
            title=_(u'Type of Membership (C3S Statute § 4)'),
            description=_(u'Please choose the type of membership.'),
            widget=deform.widget.RadioChoiceWidget(values=(
                (u'normal', _(u'Member')),
                (u'investing', _(u'Investing (non-user) member')),
                (u'unknown', _(u'Unknown')),
            ), ),
            missing=u'',
            oid='membership_type',
        )
        member_of_colsoc = colander.SchemaNode(
            colander.String(),
            title=_('Member of a Collecting Society'),
            widget=deform.widget.RadioChoiceWidget(values=yes_no),
            oid='other_colsoc',
            default=u'',
            missing=u'',
        )
        name_of_colsoc = colander.SchemaNode(
            colander.String(),
            title=_(u'Names of Collecting Societies'),
            description=_(u'Please separate multiple collecting societies by '
                          u'comma.'),
            missing=u'',
            oid='colsoc_name',
        )

    def loss_type_and_date_set_validator(form, value):
        """
        Validates whether the membership loss type is set.

        Membership date and type must both be either set or unset.
        """
        if (value['membership_loss_date'] is None) != \
                (value['membership_loss_type'] is None):
            exc = colander.Invalid(form)
            exc['membership_loss_type'] = \
                _(u'Date and type of membership loss must be set both or '
                  u'none.')
            exc['membership_loss_date'] = \
                _(u'Date and type of membership loss must be set both or '
                  u'none.')
            raise exc

    def loss_date_larger_acceptance_validator(form, value):
        """
        Validates that the membership loss date is not smaller than the
        membership acceptance date.

        As the membership can't be lost before it was granted the membership
        loss date must be larger than the membership acceptance date.
        """
        if (value['membership_loss_date'] is not None
                and (value['membership_loss_date'] < value['membership_date']
                     or not value['membership_accepted'])):
            exc = colander.Invalid(form)
            exc['membership_loss_date'] = \
                _(u'Date membership loss must be larger than membership '
                  u'acceptance date.')
            raise exc

    def loss_date_resignation_validator(form, value):
        """
        Validates that the membership loss date for resignations is the 31st
        of December of any year.

        Resignations are only allowed to the end of the year.
        """
        if (value.get('membership_loss_type', '') == 'resignation'
                and value['membership_loss_date'] is not None
                and not (value['membership_loss_date'].day == 31
                         and value['membership_loss_date'].month == 12)):
            exc = colander.Invalid(form)
            exc['membership_loss_date'] = \
                _(u'Resignations are only allowed to the 31st of December '
                  u'of a year.')
            raise exc

    class MembershipForm(colander.Schema):
        """
        The form for editing membership information combining all forms for
        the subject areas.
        """
        person = PersonalData(title=_(u'Personal Data'), )
        membership_meta = MembershipMeta(
            title=_(u'Membership Bureaucracy'),
            validator=colander.All(
                loss_type_and_date_set_validator,
                loss_date_larger_acceptance_validator,
                loss_date_resignation_validator)).bind(
                    membership_accepted=member.membership_accepted, )
        membership_info = MembershipInfo(title=_(u'Membership Requirements'))

    def membership_loss_type_entity_type_validator(form, value):
        """
        Validates that only natural persons can have loss type 'death' and
        only legal entites 'winding-up'.
        """
        if (value['membership_meta']['membership_loss_type'] == 'death'
                and value['membership_info']['entity_type'] != 'person'):
            exc_type = colander.Invalid(
                form['membership_meta']['membership_loss_type'],
                _(u'The membership loss type \'death\' is only allowed for '
                  u'natural person members and not for legal entity members.'))
            exc_meta = colander.Invalid(form['membership_meta'])
            exc_meta.add(
                exc_type,
                get_child_position(
                    form['membership_meta'],
                    form['membership_meta']['membership_loss_type']))
            exc = colander.Invalid(form)
            exc.add(exc_meta, get_child_position(form,
                                                 form['membership_meta']))
            raise exc
        if (value['membership_meta']['membership_loss_type'] == 'winding-up'
                and value['membership_info']['entity_type'] != 'legalentity'):
            exc_type = colander.Invalid(
                form['membership_meta']['membership_loss_type'],
                _(u'The membership loss type \'winding-up\' is only allowed '
                  u'for legal entity members and not for natural person '
                  u'members.'))
            exc_meta = colander.Invalid(form['membership_meta'])
            exc_meta.add(
                exc_type,
                get_child_position(
                    form['membership_meta'],
                    form['membership_meta']['membership_loss_type']))
            exc = colander.Invalid(form)
            exc.add(exc_meta, get_child_position(form,
                                                 form['membership_meta']))
            raise exc

    schema = MembershipForm(
        validator=colander.All(membership_loss_type_entity_type_validator, ))
    form = deform.Form(
        schema,
        buttons=[
            deform.Button('submit', _(u'Submit')),
            deform.Button('reset', _(u'Reset')),
        ],
        renderer=ZPT_RENDERER,
        use_ajax=True,
    )

    def clean_error_messages(error):
        if error.msg is not None and type(error.msg) == list:
            error.msg = list(set(error.msg))
            if None in error.msg:
                error.msg.remove(None)
            if '' in error.msg:
                error.msg.remove('')
            error.msg = ' '.join(list(set(error.msg)))
        for child in error.children:
            clean_error_messages(child)

    # if the form has NOT been used and submitted, remove error messages if
    # any
    if 'submit' not in request.POST:
        request.session.pop_flash()

    # if the form has been used and SUBMITTED, check contents
    if 'submit' in request.POST:
        controls = request.POST.items()
        try:
            appstruct = form.validate(controls)
        except ValidationFailure as validationfailure:
            clean_error_messages(validationfailure.error)
            request.session.flash(_(u'Please note: There were errors, '
                                    u'please check the form below.'),
                                  'message_above_form',
                                  allow_duplicate=False)
            return {'form': validationfailure.render()}

        # to store the data in the DB, the old object is updated
        listing = [  # map data attributes to appstruct items
            ('firstname', appstruct['person']['firstname']),
            ('lastname', appstruct['person']['lastname']),
            ('date_of_birth', appstruct['person']['date_of_birth']),
            ('email', appstruct['person']['email']),
            ('email_is_confirmed',
             1 if appstruct['person']['email_is_confirmed'] == 'yes' else 0),
            ('address1', appstruct['person']['address1']),
            ('address2', appstruct['person']['address2']),
            ('postcode', appstruct['person']['postcode']),
            ('city', appstruct['person']['city']),
            ('country', appstruct['person']['country']),
            ('locale', appstruct['person']['locale']),
            ('membership_date',
             appstruct['membership_meta']['membership_date']),
            ('is_duplicate', appstruct['membership_meta']['is_duplicate']),
            ('is_duplicate_of',
             appstruct['membership_meta']['is_duplicate_of']),
            ('accountant_comment',
             appstruct['membership_meta']['accountant_comment']),
            ('membership_type',
             appstruct['membership_info']['membership_type']),
            ('is_legalentity', 1 if
             (appstruct['membership_info']['entity_type']
              == 'legalentity') else 0),
            ('name_of_colsoc', appstruct['membership_info']['name_of_colsoc']),
            ('signature_received',
             appstruct['membership_meta']['signature_received']),
            ('signature_received_date',
             appstruct['membership_meta']['signature_received_date']),
            ('payment_received',
             appstruct['membership_meta']['payment_received']),
            ('payment_received_date',
             appstruct['membership_meta']['payment_received_date']),
            ('membership_loss_type',
             appstruct['membership_meta'].get('membership_loss_type', None)),
            ('membership_loss_date',
             appstruct['membership_meta'].get('membership_loss_date', None)),
        ]

        for thing in listing:
            attribute_name = thing[0]
            attribute_value = thing[1]

            if member.__getattribute__(attribute_name) == attribute_value:
                pass
            else:
                LOG.info(u'{0} changes {1} of id {2} to {3}'.format(
                    authenticated_userid(request), attribute_name, member.id,
                    attribute_value))
                setattr(member, attribute_name, attribute_value)

        # membership acceptance status can be set or unset.
        if appstruct['membership_meta'][
                'membership_accepted'] == member.membership_accepted:
            pass
        else:
            member.membership_accepted = appstruct['membership_meta'][
                'membership_accepted']
            if isinstance(member.membership_number, NoneType) \
                    and member.membership_accepted:
                member.membership_number = \
                    C3sMember.get_next_free_membership_number()

        if appstruct['membership_info']['entity_type'] == 'legalentity':
            member.is_legalentity = True
        else:
            member.is_legalentity = False

        # empty the messages queue (as validation worked anyways)
        deleted_msg = request.session.pop_flash()
        del deleted_msg
        return HTTPFound(  # redirect to details page
            location=request.route_url('detail', memberid=member.id), )

    form.set_appstruct(appstruct)
    html = form.render()

    return {'form': html}
Esempio n. 34
0
def merge_member_view(request):
    """
    Merges member duplicates into one member record.

    Some people have more than one entry in our C3SMember table,
    e.g. because they used the application form more than once
    to acquire more shares.

    They shall not, however, become members twice and get more than one
    membership number. So we try and merge them:

    If a person is already a member and acquires a second package of shares,
    this package of shares is added to the former membership entry.
    The second entry in the C3sMember table is given the 'is_duplicate' flag
    and also the 'duplicate_of' is given the *id* of the original entry.
    """
    afm_id = request.matchdict['afm_id']
    member_id = request.matchdict['mid']
    if DEBUG:  # pragma: no cover
        print "shall merge {} to {}".format(afm_id, member_id)

    orig = C3sMember.get_by_id(member_id)
    merg = C3sMember.get_by_id(afm_id)

    if not orig.membership_accepted:
        request.session.flash(
            'you can only merge to accepted members!',
            'merge_message')
        HTTPFound(request.route_url('make_member', afm_id=afm_id))
    exceeds_60 = int(orig.num_shares) + int(merg.num_shares) > 60
    if exceeds_60:
        request.session.flash(
            'merger would exceed 60 shares!',
            'merge_message')
        return HTTPFound(request.route_url('make_member', afm_id=afm_id))

    # TODO: this needs fixing!!!
    # date must be set manually according to date of approval of the board
    shares_date_of_acquisition = merg.signature_received_date if (
        merg.signature_received_date > merg.payment_received_date
    ) else merg.payment_received_date

    share_acquisition = request.registry.share_acquisition
    share_id = share_acquisition.create(
        orig.membership_number,
        merg.num_shares,
        shares_date_of_acquisition)
    share_acquisition.set_signature_reception(
        share_id,
        date(
            merg.signature_received_date.year,
            merg.signature_received_date.month,
            merg.signature_received_date.day))
    share_acquisition.set_signature_confirmation(
        share_id,
        date(
            merg.signature_confirmed_date.year,
            merg.signature_confirmed_date.month,
            merg.signature_confirmed_date.day))
    share_acquisition.set_payment_reception(
        share_id,
        date(
            merg.payment_received_date.year,
            merg.payment_received_date.month,
            merg.payment_received_date.day))
    share_acquisition.set_payment_confirmation(
        share_id,
        date(
            merg.payment_confirmed_date.year,
            merg.payment_confirmed_date.month,
            merg.payment_confirmed_date.day))
    share_acquisition.set_reference_code(
        share_id,
        merg.email_confirm_code)

    DBSession.delete(merg)
    return HTTPFound(request.route_url('detail', memberid=member_id))
Esempio n. 35
0
def make_member_view(request):
    """
    Turns a membership applicant into an accepted member.

    When both the signature and the payment for the shares have arrived at
    headquarters, an application for membership can be turned into an
    **accepted membership**, if the board of directors decides so.

    This view lets staff enter a date of approval through a form.

    It also provides staff with listings of

    * members with same first name
    * members with same last name
    * members with same email address
    * members with same date of birth

    so staff can decide if this may become a proper membership
    or whether this application is a duplicate of some accepted membership
    and should be merged with some other entry.

    In case of duplicate/merge, also check if the number of shares
    when combining both entries would exceed 60,
    the maximum number of shares a member can hold.
    """
    afm_id = request.matchdict['afm_id']
    try:  # does that id make sense? member exists?
        member = C3sMember.get_by_id(afm_id)
        assert(isinstance(member, C3sMember))  # is an application
        # assert(isinstance(member.membership_number, NoneType)
        # not has number
    except AssertionError:
        return HTTPFound(
            location=request.route_url('dashboard'))
    if member.membership_accepted:
        # request.session.flash('id {} is already accepted member!')
        return HTTPFound(request.route_url('detail', memberid=member.id))

    if not (member.signature_received and member.payment_received):
        request.session.flash('signature or payment missing!', 'messages')
        return HTTPFound(request.route_url('dashboard'))

    if 'make_member' in request.POST:
        # print "yes! contents: {}".format(request.POST['make_member'])
        try:
            member.membership_date = datetime.strptime(
                request.POST['membership_date'], '%Y-%m-%d').date()
        except ValueError, value_error:
            request.session.flash(value_error.message, 'merge_message')
            return HTTPFound(
                request.route_url('make_member', afm_id=member.id))

        member.membership_accepted = True
        if member.is_legalentity:
            member.membership_type = u'investing'
        else:
            member.is_legalentity = False
        member.membership_number = C3sMember.get_next_free_membership_number()

        # Currently, the inconsistent data model stores the amount of applied
        # shares in member.num_shares which must be moved to a membership
        # application process property. As the acquisition of shares increases
        # the amount of shares and this is still read from member.num_shares,
        # this value must first be reset to 0 so that it can be increased by
        # the share acquisition. Once the new data model is complete the
        # property num_shares will not exist anymore. Instead, a membership
        # application process stores the number of applied shares and the
        # shares store the number of actual shares.
        num_shares = member.num_shares
        member.num_shares = 0

        share_id = request.registry.share_acquisition.create(
            member.membership_number,
            num_shares,
            member.membership_date)
        share_acquisition = request.registry.share_acquisition
        share_acquisition.set_signature_reception(
            share_id,
            date(
                member.signature_received_date.year,
                member.signature_received_date.month,
                member.signature_received_date.day))
        share_acquisition.set_payment_confirmation(
            share_id,
            date(
                member.payment_received_date.year,
                member.payment_received_date.month,
                member.payment_received_date.day))
        share_acquisition.set_reference_code(
            share_id,
            member.email_confirm_code)

        # return the user to the page she came from
        if 'referrer' in request.POST:
            if request.POST['referrer'] == 'dashboard':
                return HTTPFound(request.route_url('dashboard'))
            if request.POST['referrer'] == 'detail':
                return HTTPFound(
                    request.route_url('detail', memberid=member.id))
        return HTTPFound(request.route_url('detail', memberid=member.id))
Esempio n. 36
0
def new_member(request):
    '''
    let staff create a new member entry, when receiving input via dead wood
    '''

    # XXX check if submitted, etc...

    class PersonalData(colander.MappingSchema):
        """
        colander schema for membership application form
        """
        firstname = colander.SchemaNode(
            colander.String(),
            title=u'Vorname (b. Körpersch.: Ansprechpartner)',
            oid="firstname",
        )
        lastname = colander.SchemaNode(
            colander.String(),
            title=u'Nachname (b. Körpersch.: Name der Körperschaft)',
            oid="lastname",
        )
        email = colander.SchemaNode(
            colander.String(),
            title=_(u'E-Mail'),
            validator=colander.Email(),
            oid="email",
        )
        passwort = colander.SchemaNode(
            colander.String(),
            widget=deform.widget.HiddenWidget(),
            default='NoneSet',
            missing='NoneSetPurposefully'
        )
        address1 = colander.SchemaNode(
            colander.String(),
            title='Adresse Zeile 1'
        )
        address2 = colander.SchemaNode(
            colander.String(),
            missing=unicode(''),
            title='Adresse Zeile 2'
        )
        postcode = colander.SchemaNode(
            colander.String(),
            title='Postleitzahl',
            oid="postcode"
        )
        city = colander.SchemaNode(
            colander.String(),
            title='Ort',
            oid="city",
        )
        country = colander.SchemaNode(
            colander.String(),
            title='Land',
            default=country_default,
            widget=deform.widget.SelectWidget(
                values=country_codes),
            oid="country",
        )
        date_of_birth = colander.SchemaNode(
            colander.Date(),
            title='Geburtsdatum',
            # widget=deform.widget.DatePartsWidget(
            #    inline=True),
            default=date(1970, 1, 1),
            oid="date_of_birth",
        )
        locale = colander.SchemaNode(
            colander.String(),
            widget=deform.widget.HiddenWidget(),
            default='de',
            missing='de',
        )

    class MembershipInfo(colander.Schema):

        yes_no = ((u'yes', _(u'Yes')),
                  (u'no', _(u'No')),
                  (u'dontknow', _(u'Unknown')),)

        entity_type = colander.SchemaNode(
            colander.String(),
            title=(u'Person oder Körperschaft?'),
            description=u'Bitte die Kategorie des Mitglied auswählen.',
            widget=deform.widget.RadioChoiceWidget(
                values=(
                    (u'person',
                     (u'Person')),
                    (u'legalentity',
                     u'Körperschaft'),
                ),
            ),
            missing=unicode(''),
            oid='entity_type',
        )
        membership_type = colander.SchemaNode(
            colander.String(),
            title=(u'Art der Mitgliedschaft (lt. Satzung, §4)'),
            description=u'Bitte die Art der Mitgliedschaft auswählen.',
            widget=deform.widget.RadioChoiceWidget(
                values=(
                    (u'normal',
                     (u'Normales Mitglied')),
                    (u'investing',
                     u'Investierendes Mitglied'),
                    (u'unknown',
                     u'Unbekannt.'),
                ),
            ),
            missing=unicode(''),
            oid='membership_type',
        )
        member_of_colsoc = colander.SchemaNode(
            colander.String(),
            title='Mitglied einer Verwertungsgesellschaft?',
            validator=colander.OneOf([x[0] for x in yes_no]),
            widget=deform.widget.RadioChoiceWidget(values=yes_no),
            missing=unicode(''),
            oid="other_colsoc",
            # validator=colsoc_validator
        )
        name_of_colsoc = colander.SchemaNode(
            colander.String(),
            title=(u'Falls ja, welche? (Kommasepariert)'),
            missing=unicode(''),
            oid="colsoc_name",
            # validator=colander.All(
            #    colsoc_validator,
            # )
        )

    class Shares(colander.Schema):
        """
        the number of shares a member wants to hold
        """
        num_shares = colander.SchemaNode(
            colander.Integer(),
            title='Anzahl Anteile (1-60)',
            default="1",
            validator=colander.Range(
                min=1,
                max=60,
                min_err=u'mindestens 1',
                max_err=u'höchstens 60',
            ),
            oid="num_shares")

    class MembershipForm(colander.Schema):
        """
        The Form consists of
        - Personal Data
        - Membership Information
        - Shares
        """
        person = PersonalData(
            title=_(u"Personal Data"),
            # description=_(u"this is a test"),
            # css_class="thisisjustatest"
        )
        membership_info = MembershipInfo(
            title=_(u"Membership Requirements")
        )
        shares = Shares(
            title=_(u"Shares")
        )

    schema = MembershipForm()

    form = deform.Form(
        schema,
        buttons=[
            deform.Button('submit', _(u'Submit')),
            deform.Button('reset', _(u'Reset'))
        ],
        use_ajax=True,
        # renderer=zpt_renderer
    )

    # if the form has NOT been used and submitted, remove error messages if any
    if 'submit' not in request.POST:
        request.session.pop_flash()
        # print('ping!')

    # if the form has been used and SUBMITTED, check contents
    if 'submit' in request.POST:
        controls = request.POST.items()
        try:
            appstruct = form.validate(controls)
            # print("the appstruct from the form: %s \n") % appstruct
            # for thing in appstruct:
            #    print("the thing: %s") % thing
            #    print("type: %s") % type(thing)

            # data sanity: if not in collecting society, don't save
            #  collsoc name even if it was supplied through form
            # if 'no' in appstruct['membership_info']['member_of_colsoc']:
            #    appstruct['membership_info']['name_of_colsoc'] = ''
            #    print appstruct['membership_info']['name_of_colsoc']
            # print '-'*80

        except ValidationFailure as e:
            # print("Validation Failure!")
            # print("the request.POST: %s \n" % request.POST)
            # for thing in request.POST:
            #    print("the thing: %s") % thing
            #    print("type: %s") % type(thing)
            # print(e.args)
            # print(e.error)
            # print(e.message)
            request.session.flash(
                _(u"Please note: There were errors, "
                  "please check the form below."),
                'message_above_form',
                allow_duplicate=False)
            return{'form': e.render()}

        def make_random_string():
            """
            used as email confirmation code
            """
            import random
            import string
            return u''.join(
                random.choice(
                    string.ascii_uppercase + string.digits
                ) for x in range(10))

        # make confirmation code and
        randomstring = make_random_string()
        # check if confirmation code is already used
        while (C3sMember.check_for_existing_confirm_code(randomstring)):
            # create a new one, if the new one already exists in the database
            randomstring = make_random_string()  # pragma: no cover

        # to store the data in the DB, an objet is created
        member = C3sMember(
            firstname=appstruct['person']['firstname'],
            lastname=appstruct['person']['lastname'],
            email=appstruct['person']['email'],
            password='******',
            address1=appstruct['person']['address1'],
            address2=appstruct['person']['address2'],
            postcode=appstruct['person']['postcode'],
            city=appstruct['person']['city'],
            country=appstruct['person']['country'],
            locale=appstruct['person']['locale'],
            date_of_birth=appstruct['person']['date_of_birth'],
            email_is_confirmed=False,
            email_confirm_code=randomstring,
            # is_composer=('composer' in appstruct['activity']),
            # is_lyricist=('lyricist' in appstruct['activity']),
            # is_producer=('music producer' in appstruct['activity']),
            # is_remixer=('remixer' in appstruct['activity']),
            # is_dj=('dj' in appstruct['activity']),
            date_of_submission=datetime.now(),
            # invest_member=(
            #    appstruct['membership_info']['invest_member'] == u'yes'),
            membership_type=appstruct['membership_info']['membership_type'],
            member_of_colsoc=(
                appstruct['membership_info']['member_of_colsoc'] == u'yes'),
            name_of_colsoc=appstruct['membership_info']['name_of_colsoc'],
            # opt_band=appstruct['opt_band'],
            # opt_URL=appstruct['opt_URL'],
            num_shares=appstruct['shares']['num_shares'],
        )
        if 'legalentity' in appstruct['membership_info']['entity_type']:
            # print "this is a legal entity"
            member.membership_type = u'investing'
            member.is_legalentity = True

        dbsession = DBSession()

        try:
            _temp = request.url.split('?')[1].split('=')
            if 'id' in _temp[0]:
                _id = _temp[1]
                # print("the id we want to recreate: %s" % _id)

            # add a member with a DB id that had seen its entry deleted before
                _mem = C3sMember.get_by_id(_id)  # load from id
                if isinstance(_mem, NoneType):  # check deletion status
                    member.id = _id  # set id as specified
        except:
            # print "no splitable url params found, creating new entry"
            pass

        # add member at next free DB id (default if member.id not set)
        try:
            dbsession.add(member)
            dbsession.flush()
            # print(member.id)
            the_new_id = member.id
            # appstruct['email_confirm_code'] = randomstring  # ???
        except InvalidRequestError, e:  # pragma: no cover
            print("InvalidRequestError! %s") % e
        except IntegrityError, ie:  # pragma: no cover
            print("IntegrityError! %s") % ie
Esempio n. 37
0
    def test_send_dues15_invoice_email_single(self):
        """
        test the send_dues15_invoice_email view

        * calculate invoice amount and send invoice email
        ** to not accepted member
        ** to accepted member
        ** to non-existing member (wrong id)
        ** to same member (just send email, don't create new invoice)

        ... and also check email texts for
        * german normal member
        * english normal member
        * german investing member
        * english investing member
        * german investing legal entity
        * english investing legal entity

        """
        from pyramid_mailer import get_mailer
        from c3smembership.views.membership_dues import send_dues15_invoice_email
        from c3smembership.models import Dues15Invoice

        _number_of_invoices = len(Dues15Invoice.get_all())

        self.config.add_route('toolbox', '/')
        self.config.add_route('detail', '/')
        self.config.add_route('make_dues15_invoice_no_pdf', '/')

        req = testing.DummyRequest()
        req.matchdict = {
            'member_id': '1',
        }
        req.referrer = 'detail'
        res = send_dues15_invoice_email(req)
        self.assertTrue(res.status_code == 302)
        self.assertTrue('http://example.com/' in res.headers['Location'])
        # member 1 not accepted by the board. problem!

        _number_of_invoices_2 = len(Dues15Invoice.get_all())
        assert (_number_of_invoices == _number_of_invoices_2 == 0)
        # print("_number_of_invoices: {}".format(_number_of_invoices))

        m1 = C3sMember.get_by_id(1)
        m1.membership_accepted = True

        res = send_dues15_invoice_email(req)
        # print('#'*60)
        # print res
        # print('#'*60)

        _number_of_invoices_3 = len(Dues15Invoice.get_all())
        # print("_number_of_invoices 3: {}".format(_number_of_invoices_3))
        assert (_number_of_invoices_3 == 1)

        # check for outgoing email
        mailer = get_mailer(req)
        # print mailer
        # print mailer.outbox
        self.assertEqual(len(mailer.outbox), 1)
        # print mailer.outbox[0].body
        self.assertTrue(
            'Verwendungszweck: C3S-dues2015-0001' in mailer.outbox[0].body)
        """
        now we try to get an id that does not exist
        """
        req2 = testing.DummyRequest()
        req2.matchdict = {
            'member_id': '1234',
        }
        req2.referrer = 'detail'
        res2 = send_dues15_invoice_email(req2)
        self.assertTrue(res2.status_code == 302)
        self.assertTrue('http://example.com/' in res2.headers['Location'])
        """
        what if we call that function (and send email) twice?
        test that no second invoice is created in DB.
        """
        req3 = testing.DummyRequest()
        req3.matchdict = {
            'member_id': '1',
        }
        req3.referrer = 'detail'
        res3 = send_dues15_invoice_email(req3)
        self.assertTrue(res3.status_code == 302)
        self.assertTrue('http://example.com/' in res3.headers['Location'])
        _number_of_invoices_4 = len(Dues15Invoice.get_all())
        self.assertEqual(_number_of_invoices_3, _number_of_invoices_4)
        """
        check for email texts
        """
        self.assertEqual(len(mailer.outbox), 2)
        self.assertTrue(
            (u'Dein Mitgliedsbeitrag ab Quartal 1 beträgt also 50 Euro.'
             ) in mailer.outbox[0].body)
        # print(mailer.outbox[0].body)
        self.assertTrue(
            (u'Dein Mitgliedsbeitrag ab Quartal 1 beträgt also 50 Euro.'
             ) in mailer.outbox[1].body)
        # print(mailer.outbox[1].body)
        """
        send email to
        * english member,
        * investing members (de/en),
        * legal entities (de/en)
        """
        # english normal #####################################################
        m2 = C3sMember.get_by_id(2)
        m2.membership_accepted = True
        req_en_normal = testing.DummyRequest()
        req_en_normal.matchdict = {
            'member_id': '2',
        }
        req_en_normal.referrer = 'detail'
        res_en_normal = send_dues15_invoice_email(req_en_normal)
        self.assertTrue(res_en_normal.status_code == 302)
        self.assertEqual(len(mailer.outbox), 3)
        # print(mailer.outbox[2].body)
        self.assertTrue((u'Please transfer 50 Euro') in mailer.outbox[2].body)

        # german investing ###################################################
        m3 = C3sMember.get_by_id(3)
        m3.membership_accepted = True
        req_de_investing = testing.DummyRequest()
        req_de_investing.matchdict = {
            'member_id': '3',
        }
        req_de_investing.referrer = 'detail'
        res_de_investing = send_dues15_invoice_email(req_de_investing)
        self.assertTrue(res_de_investing.status_code == 302)
        self.assertEqual(len(mailer.outbox), 4)
        # print(mailer.outbox[3].body)
        self.assertTrue(
            (u'Da Du investierendes Mitglied bist') in mailer.outbox[3].body)

        # english investing ##################################################
        m4 = C3sMember.get_by_id(4)
        m4.membership_accepted = True
        req_en_investing = testing.DummyRequest()
        req_en_investing.matchdict = {
            'member_id': '4',
        }
        req_en_investing.referrer = 'detail'
        res_en_investing = send_dues15_invoice_email(req_en_investing)
        self.assertTrue(res_en_investing.status_code == 302)
        self.assertEqual(len(mailer.outbox), 5)
        # print(mailer.outbox[4].body)
        self.assertTrue(
            (u'Since you are an investing member') in mailer.outbox[4].body)

        # german legal entity ################################################
        m5 = C3sMember.get_by_id(5)
        m5.membership_accepted = True
        req_de_legalentity = testing.DummyRequest()
        req_de_legalentity.matchdict = {
            'member_id': '5',
        }
        req_de_legalentity.referrer = 'detail'
        res_de_legalentity = send_dues15_invoice_email(req_de_legalentity)
        self.assertTrue(res_de_legalentity.status_code == 302)
        self.assertEqual(len(mailer.outbox), 6)
        # print(mailer.outbox[5].body)
        self.assertTrue((u'') in mailer.outbox[5].body)

        # english legal entity ###############################################
        m6 = C3sMember.get_by_id(6)
        m6.membership_accepted = True
        req_en_legalentity = testing.DummyRequest()
        req_en_legalentity.matchdict = {
            'member_id': '6',
        }
        req_en_legalentity.referrer = 'detail'
        res_en_legalentity = send_dues15_invoice_email(req_en_legalentity)
        self.assertTrue(res_en_legalentity.status_code == 302)
        self.assertEqual(len(mailer.outbox), 7)
        # print(mailer.outbox[6].body)
        self.assertTrue((u'Da Musikverlag investierendes Mitglied ist'
                         ) in mailer.outbox[6].body)
        self.assertTrue((u'Für juristische Personen wird empfohlen'
                         ) in mailer.outbox[6].body)
Esempio n. 38
0
    def test_send_dues15_invoice_email_via_BATCH(self):
        """
        test the send_dues15_invoice_batch function
        for batch processing
        """
        # from pyramid_mailer import get_mailer
        from c3smembership.views.membership_dues import send_dues15_invoice_batch
        self.config.add_route('make_dues15_invoice_no_pdf', '/')
        self.config.add_route('make_dues15_reversal_invoice_pdf', '/')
        self.config.add_route('detail', '/detail/')
        self.config.add_route('error_page', '/error_page')
        self.config.add_route('toolbox', '/toolbox')

        # have to accept their membersip first
        m1 = C3sMember.get_by_id(1)  # german normal member
        m1.membership_accepted = True
        m2 = C3sMember.get_by_id(2)  # english normal member
        m2.membership_accepted = True
        m3 = C3sMember.get_by_id(3)  # german investing member
        m3.membership_accepted = True
        m4 = C3sMember.get_by_id(4)  # english investing member
        m4.membership_accepted = True
        m5 = C3sMember.get_by_id(5)  # german investing member
        m5.membership_accepted = True
        m5 = C3sMember.get_by_id(6)  # english investing member
        m5.membership_accepted = True

        # check number of invoices: should be 0
        _number_of_invoices_before_batch = len(Dues15Invoice.get_all())
        # print("_number_of_invoices_before_batch: {}".format(
        #    _number_of_invoices_before_batch))
        assert (_number_of_invoices_before_batch == 0)

        req = testing.DummyRequest()
        req.referrer = 'toolbox'
        res = send_dues15_invoice_batch(req)
        # print res

        # check number of invoices: should be 2
        _number_of_invoices_batch = len(Dues15Invoice.get_all())
        # print("number of invoices after batch: {}".format(
        #    _number_of_invoices_batch))
        assert (_number_of_invoices_batch == 2)

        # try to post a number for batch processing
        req_post = testing.DummyRequest(
            post={
                'submit': True,
                'number': 24
                # lots of values missing
            }, )
        req.referrer = 'toolbox'
        res = send_dues15_invoice_batch(req_post)

        assert ('sent out 5 mails (to members with ids [1, 2, 3, 4, 5])'
                in req.session.pop_flash('message_to_staff'))

        # try to batch-send once more:
        # this will respond with a redirect and tell
        # that there are no invitees left
        res2 = send_dues15_invoice_batch(req)
        # print res2
        self.assertEquals(res2.status, '302 Found')
        self.assertEquals(res2.status_code, 302)
        assert ('no invoicees left. all done!'
                in req.session.pop_flash('message_to_staff'))
        """
        and now some tests for make_dues15_invoice_no_pdf
        """
        from c3smembership.views.membership_dues import (
            make_dues15_invoice_no_pdf)
        req2 = testing.DummyRequest()

        # wrong token: must fail!
        req2.matchdict = {
            'email': m1.email,
            'code': m1.dues15_token + 'false!!!',  # must fail
            'i': u'0001',
        }

        res = make_dues15_invoice_no_pdf(req2)

        assert ('application/pdf' not in res.headers['Content-Type'])  # no PDF
        assert ('error_page' in res.headers['Location'])  # but error

        # wrong invoice number: must fail!
        req2.matchdict = {
            'email': m1.email,
            'code': m1.dues15_token,
            'i': u'1234',  # must fail
        }
        res = make_dues15_invoice_no_pdf(req2)
        assert ('application/pdf' not in res.headers['Content-Type'])  # no PDF
        assert ('error_page' in res.headers['Location'])  # but error

        # wrong invoice token: must fail!
        i2 = Dues15Invoice.get_by_invoice_no(2)
        i2.token = u'not_matching'
        req2.matchdict = {
            'email': m2.email,
            'code': m2.dues15_token,
            'i': u'3',  # must fail
        }
        res = make_dues15_invoice_no_pdf(req2)
        assert ('application/pdf' not in res.headers['Content-Type'])  # no PDF
        assert ('error_page' in res.headers['Location'])  # but error

        #######################################################################
        # one more edge case:
        # check _inv.token must match code, or else!!!
        # first, set inv_code to something wrong:
        i1 = Dues15Invoice.get_by_invoice_no(1)
        _old_i1_token = i1.token
        i1.token = u'not_right'
        req2.matchdict = {
            'email': m1.email,
            'code': m1.dues15_token,
            'i': u'0001',
        }
        res = make_dues15_invoice_no_pdf(req2)
        assert ('application/pdf' not in res.headers['Content-Type'])  # no PDF
        assert ('error_page' in res.headers['Location'])  # but error
        # reset it to what was there before
        i1.token = _old_i1_token
        #######################################################################
        # one more edge case:
        # check this invoice is not a reversal, or else no PDF!!!
        # first, set is_reversal to something wrong:
        i1 = Dues15Invoice.get_by_invoice_no(1)
        _old_i1_reversal_status = i1.is_reversal  # False
        i1.is_reversal = True
        req2.matchdict = {
            'email': m1.email,
            'code': m1.dues15_token,
            'i': u'0001',
        }
        res = make_dues15_invoice_no_pdf(req2)
        assert ('application/pdf' not in res.headers['Content-Type'])  # no PDF
        assert ('error_page' in res.headers['Location'])  # but error
        # reset it to what was there before
        i1.is_reversal = _old_i1_reversal_status
        #######################################################################

        # retry with valid token:
        req2.matchdict = {
            'email': m1.email,
            'code': m1.dues15_token,
            'i': u'0001',
        }
        res = make_dues15_invoice_no_pdf(req2)
        # m1.
        # print("length of the result: {}".format(len(res.body)))
        # print("headers of the result: {}".format((res.headers)))
        assert (60000 < len(res.body) < 80000)
        assert ('application/pdf' in res.headers['Content-Type'])
        """
        test dues listing
        """
        from c3smembership.views.membership_dues import dues15_listing
        req_list = testing.DummyRequest()
        resp_list = dues15_listing(req_list)
        # print resp_list
        # {'count': 5,
        #   'invoices': [
        #       <c3smembership.models.Dues15Invoice object at 0x7f95df761a50>,
        #       <c3smembership.models.Dues15Invoice object at 0x7f95df761690>,
        #       <c3smembership.models.Dues15Invoice object at 0x7f95df815a50>,
        #       <c3smembership.models.Dues15Invoice object at 0x7f95df761c90>,
        #       <c3smembership.models.Dues15Invoice object at 0x7f95df761c10>],
        #   '_today': datetime.date(2015, 9, 1)}
        assert (resp_list['count'] == 2)
Esempio n. 39
0
    def test_dues15_reduction(self):
        """
        test the dues15_reduction functionality
        """
        # have to accept their membersip first
        m1 = C3sMember.get_by_id(1)  # german normal member
        m1.membership_accepted = True
        m2 = C3sMember.get_by_id(2)  # english normal member
        m2.membership_accepted = True

        self.config.add_route('make_dues15_invoice_no_pdf', '/')
        self.config.add_route('make_dues15_reversal_invoice_pdf', '/')
        self.config.add_route('detail', '/detail/')
        self.config.add_route('error_page', '/error_page')
        self.config.add_route('toolbox', '/toolbox')
        req = testing.DummyRequest()
        req.referrer = 'toolbox'
        from c3smembership.views.membership_dues import send_dues15_invoice_batch
        # send out invoices. this is a prerequisite for reductions
        res = send_dues15_invoice_batch(req)
        res
        """
        test reduction of dues
        """
        # pre-check
        self.assertFalse(m1.dues15_reduced)  # not reduced yet!
        _m1_amount_reduced = m1.dues15_amount_reduced  # is Decimal('0')
        _number_of_invoices_before_reduction = len(Dues15Invoice.get_all())
        # print("_number_of_invoices_before_reduction: {}".format(
        #    _number_of_invoices_before_reduction))
        # we have 2 invoices as of now
        self.assertEqual(len(Dues15Invoice.get_all()), 2)
        # import the function under test
        from c3smembership.views.membership_dues import dues15_reduction

        #############################################################
        # try to reduce to the given calculated amount (edge case coverage)
        # this will not work, produce no new invoices
        req_reduce = testing.DummyRequest(  # prepare request
            post={
                'confirmed': 'yes',
                'submit': True,
                'amount': 50,
            }, )
        req_reduce.matchdict['member_id'] = 1  # do it for member with id 1

        res_reduce = dues15_reduction(req_reduce)  # call reduce on her

        self.assertEqual(len(Dues15Invoice.get_all()), 2)  # no new invoice

        #############################################################
        # try to reduce above the given calculated amount
        # this will not work, produce no new invoices
        req_reduce = testing.DummyRequest(  # prepare request
            post={
                'confirmed': 'yes',
                'submit': True,
                'amount': 500,
            }, )
        req_reduce.matchdict['member_id'] = 1  # do it for member with id 1

        res_reduce = dues15_reduction(req_reduce)  # call reduce on her

        self.assertEqual(len(Dues15Invoice.get_all()), 2)  # no new invoice

        #############################################################
        # valid reduction but without confirmation
        req_reduce = testing.DummyRequest(post={
            'confirmed': 'no',
            'submit': True,
            'amount': 42,
        }, )
        req_reduce.matchdict['member_id'] = 1
        res_reduce = dues15_reduction(req_reduce)
        self.assertEqual(len(Dues15Invoice.get_all()), 2)  # no new invoice

        #############################################################
        # valid reduction
        req_reduce = testing.DummyRequest(post={
            'confirmed': 'yes',
            'submit': True,
            'amount': 42,
        }, )
        req_reduce.matchdict['member_id'] = 1
        res_reduce = dues15_reduction(req_reduce)

        _number_of_invoices_after_reduction = len(Dues15Invoice.get_all())

        assert (  # two new invoices must have been issued
            (_number_of_invoices_before_reduction +
             2) == _number_of_invoices_after_reduction)
        assert (_number_of_invoices_after_reduction == 4)
        assert ('detail' in res_reduce.headers['Location'])  # 302 to detail p.
        assert (_m1_amount_reduced != m1.dues15_amount_reduced)  # changed!
        assert (m1.dues15_amount_reduced == 42)  # changed to 42!

        # check the invoice created
        _rev_inv = Dues15Invoice.get_by_invoice_no(
            _number_of_invoices_before_reduction + 1)
        _new_inv = Dues15Invoice.get_by_invoice_no(
            _number_of_invoices_before_reduction + 2)
        # print(_rev_inv.invoice_amount)
        # print(type(_rev_inv.invoice_amount))
        assert (_rev_inv.invoice_amount == D('-50'))
        assert (_new_inv.invoice_amount == D('42'))

        # we have 4 invoices as of now
        self.assertEqual(len(Dues15Invoice.get_all()), 4)

        #############################################################
        # now try to raise above the previous reduction
        req_reduce = testing.DummyRequest(post={
            'confirmed': 'yes',
            'submit': True,
            'amount': 50,
        }, )
        req_reduce.matchdict['member_id'] = 1
        res_reduce = dues15_reduction(req_reduce)

        _number_of_invoices_after_reduction = len(Dues15Invoice.get_all())

        # no new invoices were created, we still have 4 invoices
        self.assertEqual(len(Dues15Invoice.get_all()), 4)

        #############################################################
        # try to reduce to the same amount again (edge case coverage)
        req_reduce = testing.DummyRequest(
            post={
                'confirmed': 'yes',
                'submit': True,
                'amount': 42,
                # lots of values missing
            }, )
        req_reduce.matchdict['member_id'] = 1
        res_reduce = dues15_reduction(req_reduce)
        #############################################################
        # try to reduce to zero (edge case coverage)
        # print("------------- reduction to zero ahead!!!")
        req_reduce = testing.DummyRequest(
            post={
                'confirmed': 'yes',
                'submit': True,
                'amount': 0,
                # lots of values missing
            }, )
        req_reduce.matchdict['member_id'] = 1
        res_reduce = dues15_reduction(req_reduce)

        req_reduce = testing.DummyRequest(
            post={
                'confirmed': 'yes',
                'submit': True,
                'amount': 0,
                # lots of values missing
            }, )
        req_reduce.matchdict['member_id'] = 2
        res_reduce = dues15_reduction(req_reduce)
        #############################################################
        # try to reduce to zero with english member (edge case coverage)
        # how to do this if you already reduced to zero? reduce to more first!
        req_reduce = testing.DummyRequest(
            post={
                'confirmed': 'yes',
                'submit': True,
                'amount': 1,
                # lots of values missing
            }, )
        req_reduce.matchdict['member_id'] = 1
        res_reduce = dues15_reduction(req_reduce)
        m1.locale = u'en'
        req_reduce = testing.DummyRequest(
            post={
                'confirmed': 'yes',
                'submit': True,
                'amount': 0,
                # lots of values missing
            }, )
        req_reduce.matchdict['member_id'] = 1
        res_reduce = dues15_reduction(req_reduce)
        #############################################################
        """
        test reversal invoice PDF generation
        """

        from c3smembership.views.membership_dues import (
            make_dues15_reversal_invoice_pdf)
        req2 = testing.DummyRequest()

        # wrong token: must fail!
        req2.matchdict = {
            'email': m1.email,
            'code': m1.dues15_token + 'false!!!',  # must fail
            'no': u'0006',
        }
        res = make_dues15_reversal_invoice_pdf(req2)
        assert ('application/pdf' not in res.headers['Content-Type'])  # no PDF
        assert ('error_page' in res.headers['Location'])  # but error

        # wrong invoice number: must fail!
        req2.matchdict = {
            'email': m1.email,
            'code': m1.dues15_token,
            'no': u'1234',  # must fail
        }
        res = make_dues15_reversal_invoice_pdf(req2)
        assert ('application/pdf' not in res.headers['Content-Type'])  # no PDF
        assert ('error_page' in res.headers['Location'])  # but error

        # wrong invoice token: must fail!
        i2 = Dues15Invoice.get_by_invoice_no('2')
        i2.token = u'not_matching'
        req2.matchdict = {
            'email': m2.email,
            'code': m2.dues15_token,
            'no': u'2',  # must fail
        }
        res = make_dues15_reversal_invoice_pdf(req2)
        assert ('application/pdf' not in res.headers['Content-Type'])  # no PDF
        assert ('error_page' in res.headers['Location'])  # but error

        ######################################################################
        # wrong invoice type (not a reversal): must fail! (edge case coverage)
        assert (not i2.is_reversal)  # i2 is not a reversal
        i2.token = m2.dues15_token  # we give it a valid token
        req2.matchdict = {
            'email': m2.email,
            'code': m2.dues15_token,
            'no': u'0002',
        }
        res = make_dues15_reversal_invoice_pdf(req2)
        assert ('application/pdf' not in res.headers['Content-Type'])  # no PDF
        assert ('error_page' in res.headers['Location'])  # but error
        ######################################################################

        # retry with valid token:
        req2.matchdict = {
            'email': m1.email,
            'code': m1.dues15_token,
            'no': u'0003',
        }
        res = make_dues15_reversal_invoice_pdf(req2)
        # print("length of the result: {}".format(len(res.body)))
        # print("headers of the result: {}".format((res.headers)))
        assert (60000 < len(res.body) < 80000)
        assert ('application/pdf' in res.headers['Content-Type'])
Esempio n. 40
0
def dues16_reduction(request):
    """
    reduce a members dues upon valid request to do so.

    * change payable amount for member
    * cancel old invoice by issuing a cancellation
    * issue a new invoice with the new amount (if new amount != 0)

    this will only work for *normal* members.
    """
    # member: sanity checks
    try:
        member_id = request.matchdict['member_id']
        member = C3sMember.get_by_id(member_id)  # is in database
        assert member.membership_accepted  # is a member
        assert 'investing' not in member.membership_type  # is normal member
    except (KeyError, AssertionError):  # pragma: no cover
        request.session.flash(
            u"No member OR not accepted OR not normal member",
            'dues16_message_to_staff'  # message queue for staff
        )
        return HTTPFound(

            request.route_url('detail', memberid=member.id) + '#dues16')

    # sanity check: the given amount is a positive decimal
    try:
        reduced_amount = D(request.POST['amount'])
        assert not reduced_amount.is_signed()
        if DEBUG:
            print("DEBUG: reduction to {}".format(reduced_amount))
    except (KeyError, AssertionError):  # pragma: no cover
        request.session.flash(
            (u"Invalid amount to reduce to: '{}' "
             u"Use the dot ('.') as decimal mark, e.g. '23.42'".format(
                 request.POST['amount'])),
            'dues16_message_to_staff'  # message queue for user
        )
        return HTTPFound(
            request.route_url('detail', memberid=member.id) + '#dues16')

    if DEBUG:
        print("DEBUG: member.dues16_amount: {}".format(
            member.dues16_amount))
        print("DEBUG: type(member.dues16_amount): {}".format(
            type(member.dues16_amount)))
        print("DEBUG: member.dues16_reduced: {}".format(
            member.dues16_reduced))
        print("DEBUG: member.dues16_amount_reduced: {}".format(
            member.dues16_amount_reduced))
        print("DEBUG: type(member.dues16_amount_reduced): {}".format(
            type(member.dues16_amount_reduced)))

    # The hidden input 'confirmed' must have the value 'yes' which is set by
    # the confirmation dialog.
    reduction_confirmed = request.POST['confirmed']
    if reduction_confirmed != 'yes':
        request.session.flash(
            u'Die Reduktion wurde nicht bestätigt.',
            'dues16_message_to_staff'  # message queue for staff
        )
        return HTTPFound(
            request.route_url('detail', memberid=member.id) + '#dues16')

    # check the reduction amount: same as default calculated amount?
    if ((member.dues16_reduced is False) and (
            member.dues16_amount == reduced_amount)):
        request.session.flash(
            u"Dieser Beitrag ist der default-Beitrag!",
            'dues16_message_to_staff'  # message queue for staff
        )
        return HTTPFound(
            request.route_url('detail', memberid=member.id) + '#dues16')

    if reduced_amount == member.dues16_amount_reduced:
        request.session.flash(
            u"Auf diesen Beitrag wurde schon reduziert!",
            'dues16_message_to_staff'  # message queue for staff
        )
        return HTTPFound(
            request.route_url('detail', memberid=member.id) + '#dues16')

    if member.dues16_reduced \
            and reduced_amount > member.dues16_amount_reduced \
            or reduced_amount > member.dues16_amount:
        request.session.flash(
            u'Beitrag darf nicht über den berechneten oder bereits'
            u'reduzierten Wert gesetzt werden.',
            'dues16_message_to_staff'  # message queue for staff
        )
        return HTTPFound(
            request.route_url('detail', memberid=member.id) + '#dues16')

    # prepare: get highest invoice no from db
    max_invoice_no = Dues16Invoice.get_max_invoice_no()

    # things to be done:
    # * change dues amount for that member
    # * cancel old invoice by issuing a reversal invoice
    # * issue a new invoice with the new amount

    member.set_dues16_reduced_amount(reduced_amount)
    request.session.flash('reduction to {}'.format(reduced_amount),
                          'dues16_message_to_staff')

    old_invoice = Dues16Invoice.get_by_invoice_no(member.dues16_invoice_no)
    old_invoice.is_cancelled = True

    reversal_invoice_amount = -D(old_invoice.invoice_amount)

    # prepare reversal invoice number
    new_invoice_no = max_invoice_no + 1
    # create reversal invoice
    reversal_invoice = Dues16Invoice(
        invoice_no=new_invoice_no,
        invoice_no_string=(
            u'C3S-dues2016-' + str(new_invoice_no).zfill(4)) + '-S',
        invoice_date=datetime.today(),
        invoice_amount=reversal_invoice_amount.to_eng_string(),
        member_id=member.id,
        membership_no=member.membership_number,
        email=member.email,
        token=member.dues16_token,
    )
    reversal_invoice.preceding_invoice_no = old_invoice.invoice_no
    reversal_invoice.is_reversal = True
    DBSession.add(reversal_invoice)
    DBSession.flush()
    old_invoice.succeeding_invoice_no = new_invoice_no

    # check if this is an exemption (reduction to zero)
    is_exemption = False  # sane default
    # check if reduction to zero
    if reduced_amount.is_zero():
        is_exemption = True
        if DEBUG:
            print("this is an exemption: reduction to zero")
    else:
        if DEBUG:
            print("this is a reduction to {}".format(reduced_amount))

    if not is_exemption:
        # create new invoice
        new_invoice = Dues16Invoice(
            invoice_no=new_invoice_no + 1,
            invoice_no_string=(
                u'C3S-dues2016-' + str(new_invoice_no + 1).zfill(4)),
            invoice_date=datetime.today(),
            invoice_amount=u'' + str(reduced_amount),
            member_id=member.id,
            membership_no=member.membership_number,
            email=member.email,
            token=member.dues16_token,
        )
        new_invoice.is_altered = True
        new_invoice.preceding_invoice_no = reversal_invoice.invoice_no
        reversal_invoice.succeeding_invoice_no = new_invoice_no + 1
        DBSession.add(new_invoice)

        # in the members record, store the current invoice no
        member.dues16_invoice_no = new_invoice_no + 1

        DBSession.flush()  # persist newer invoices

    reversal_url = (
        request.route_url(
            'make_dues16_reversal_invoice_pdf',
            email=member.email,
            code=member.dues16_token,
            no=str(reversal_invoice.invoice_no).zfill(4)
        )
    )
    if is_exemption:
        email_subject, email_body = make_dues_exemption_email(
            member,
            reversal_url)
    else:
        invoice_url = (
            request.route_url(
                'make_dues16_invoice_no_pdf',
                email=member.email,
                code=member.dues16_token,
                i=str(new_invoice_no + 1).zfill(4)
            )
        )
        email_subject, email_body = make_dues16_reduction_email(
            member,
            new_invoice,
            invoice_url,
            reversal_url)

    message = Message(
        subject=email_subject,
        sender='*****@*****.**',
        recipients=[member.email],
        body=email_body,
        extra_headers={
            'Reply-To': '*****@*****.**',
        }
    )
    if is_exemption:
        request.session.flash('exemption email was sent to user!',
                              'dues16_message_to_staff')
    else:
        request.session.flash('update email was sent to user!',
                              'dues16_message_to_staff')
    send_message(request, message)
    return HTTPFound(
        request.route_url(
            'detail',
            memberid=member_id) +
        '#dues16')