Example #1
0
def member_list_print_view(request):
    """
    This view produces printable HTML output, i.e. HTML without links

    It was used before the PDF-generating view above existed
    """
    all_members = C3sMember.member_listing(
        'lastname', how_many=C3sMember.get_number(), offset=0, order=u'asc')
    member_list = []
    count = 0
    for member in all_members:
        if member.is_member():
            # check membership number
            try:
                assert(member.membership_number is not None)
            except AssertionError:
                if DEBUG:  # pragma: no cover
                    print u"failed at id {} lastname {}".format(
                        member.id, member.lastname)
            member_list.append(member)
            count += 1
    # sort members alphabetically
    import locale
    locale.setlocale(locale.LC_ALL, "de_DE.UTF-8")

    member_list.sort(key=lambda x: x.firstname, cmp=locale.strcoll)
    member_list.sort(key=lambda x: x.lastname, cmp=locale.strcoll)

    return {
        'members': member_list,
        'count': count,
        '_today': date.today(),
    }
Example #2
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'))
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))
Example #4
0
def export_yes_emails(request):
    """
    export the database to a CSV file
    """
    datasets = C3sMember.member_listing(
        "id", how_many=C3sMember.get_number(), order='asc')
    rows = []  # start with empty list
    for m in datasets:
        if m.signature_received and m.payment_received:
            rows.append(
                (m.firstname + ' ' + m.lastname + ' <' + m.email + '>',))
    return {
        'header': ['Vorname Nachname <*****@*****.**>', ],
        'rows': rows}
Example #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))
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)}))
Example #7
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)
    )
Example #8
0
def dashboard(request):
    """
    The Dashboard.

    This view lets accountants view
    the **list of applications for membership**.

    Some details can be seen (name, email, link to more details)
    as well as their membership application *progress*:

    - has their signature arrived?
    - how about the payment?
    - have reminders been sent? receptions confirmed?

    There are also links to *edit* or *delete* one of the datasets.

    Once all requirements are fulfilled,
    an application can be turned into a membership from here:
    a button shows up.
    """

    pagination = request.pagination
    try:
        members = C3sMember.nonmember_listing(
            pagination.paging.content_offset,
            pagination.paging.page_size,
            pagination.sorting.sort_property,
            pagination.sorting.sort_direction)
    except (InvalidPropertyException, InvalidSortDirection):
        raise ParameterValidationException(
            'Page does not exist.',
            request.route_url(request.matched_route.name))
    return {
        'members': members,
    }
Example #9
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))
Example #10
0
def regenerate_pdf(request):
    """
    Staffers can regenerate an applicants PDF and send it to her.
    """
    code = request.matchdict['code']
    member = C3sMember.get_by_code(code)

    if member is None:
        return get_dashboard_redirect(request)
    appstruct = {
        'firstname': member.firstname,
        'lastname': member.lastname,
        'address1': member.address1,
        'address2': member.address2,
        'postcode': member.postcode,
        'city': member.city,
        'email': member.email,
        'email_confirm_code': member.email_confirm_code,
        'country': member.country,
        '_LOCALE_': member.locale,
        'membership_type': member.membership_type,
        'num_shares': member.num_shares,
        'date_of_birth': member.date_of_birth,
        'date_of_submission': member.date_of_submission,
    }
    LOG.info(
        "%s regenerated the PDF for code %s",
        authenticated_userid(request),
        code)
    return generate_pdf(appstruct)
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)
Example #12
0
def verify_mailaddress_conf(request):
    '''
    let member confirm her email address by clicking a link
    '''
    user_email = request.matchdict['email']
    refcode = request.matchdict['refcode']
    token = request.matchdict['token']
    # try to get entry from DB
    afm = C3sMember.get_by_code(refcode)
    if isinstance(afm, NoneType):  # no entry?
        #print "entry not found"
        return {
            'confirmed': False,
            'firstname': 'foo',
            'lastname': 'bar',
            'result_msg': 'bad URL / bad codes. please contact [email protected]!',
        }
    # check token
    if ('_used' in afm.email_confirm_token):  # token was invalidated already
        #print "the token is empty"
        return {
            'confirmed': False,
            'firstname': afm.firstname,
            'lastname': afm.lastname,
            'result_msg': 'your token is invalid. please contact [email protected]!',
        }

    try:
        assert(afm.email_confirm_token in token)
        assert(token in afm.email_confirm_token)
        assert(afm.email in user_email)
        assert(user_email in afm.email)
    except:
        return {
            'confirmed': False,
            'firstname': 'foo',
            'lastname': 'bar',
            'result_msg': 'bad token/email. please contact [email protected]!',
        }

    afm.email_is_confirmed = True
    afm.email_confirm_token += u'_used'
    DBSession.flush()
    # notify staff
    message = Message(
        subject='[C3S Yes!] afm email confirmed',
        sender='*****@*****.**',
        recipients=[request.registry.settings['c3smembership.mailaddr'], ],
        body=u'see {}/detail/{}'.format(
            request.registry.settings['c3smembership.url'],
            afm.id)
    )
    mailer = get_mailer(request)
    mailer.send(message)
    return {
        'confirmed': True,
        'firstname': afm.firstname,
        'lastname': afm.lastname,
        'result_msg': u'',
    }
Example #13
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'],
                                       )
                     )
Example #14
0
def regenerate_pdf(request):
    """
    staffers can regenerate a users pdf
    """
    _code = request.matchdict['code']
    _member = C3sMember.get_by_code(_code)

    if _member is None:  # that memberid did not produce good results
        return HTTPFound(  # back to base
            request.route_url('dashboard_only'))
    _appstruct = {
        'firstname': _member.firstname,
        'lastname': _member.lastname,
        'address1': _member.address1,
        'address2': _member.address2,
        'postcode': _member.postcode,
        'city': _member.city,
        'email': _member.email,
        'email_confirm_code': _member.email_confirm_code,
        'country': _member.country,
        '_LOCALE_': _member.locale,
        'membership_type': _member.membership_type,
        'num_shares': _member.num_shares,
        'date_of_birth': _member.date_of_birth,
        'date_of_submission': _member.date_of_submission,
    }
    log.info(
        "%s regenerated the PDF for code %s" % (
            authenticated_userid(request), _code))
    return generate_pdf(_appstruct)
Example #15
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)))
Example #16
0
def export_yes_emails(request):  # pragma: no cover
    """
    Export the members email addresses to a CSV file.

    XXX TODO: implement a test-case
    """
    datasets = C3sMember.member_listing(
        "id", how_many=C3sMember.get_number(), order='asc')
    rows = []  # start with empty list
    for member in datasets:
        if member.signature_received and member.payment_received:
            rows.append('{firstname} {lastname} <{email}>'.format(
                firstname=member.firstname,
                lastname=member.lastname,
                email=member.email))
    return {
        'header': ['Vorname Nachname <*****@*****.**>', ],
        'rows': rows}
Example #17
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)
Example #18
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.')}
            )
        )
Example #19
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')
Example #20
0
def delete_afms(request):
    '''
    delete a bunch of AfMs in one go
    '''
    class DeleteAfMRange(colander.MappingSchema):
        first = colander.SchemaNode(
            colander.Integer(),
            title='first ID to delete'
        )
        last = colander.SchemaNode(
            colander.Integer(),
            title='last ID to delete'
        )
    schema = DeleteAfMRange()
    delete_range_form = deform.Form(
        schema,
        buttons=[deform.Button('delete_them', 'DELETE')]
    )
    if 'first' in request.POST:
        print "form was submitted!"
        print "first ID to delete: %s" % request.POST['first']
        controls = request.POST.items()
        try:
            appstruct = delete_range_form.validate(controls)
            #print('validated!')
            #print appstruct
            _first = appstruct['first']
            _last = appstruct['last']
            assert(_first < _last)
        except ValidationFailure, e:
            return {
                'resetform': e.render()
            }
        # delete entries here :-)
        for i in range(_first, _last+1):
            #print i
            try:
                #_del = C3sMember.delete_by_id(i)
                C3sMember.delete_by_id(i)
                #print 'deleted %s' % _del
            except:
                print 'id %s didnt exist'
        return HTTPFound(request.route_url('dashboard_only'))
Example #21
0
def autocomplete_input_values(request):
    '''
    AJAX view/helper function
    returns the matching set of values for autocomplete/quicksearch

    this function and view expects a parameter 'term' (?term=foo) containing a
    string to find matching entries (e.g. starting with 'foo') in the database
    '''
    text = request.params.get('term', '')
    return C3sMember.get_matching_codes(text)
Example #22
0
def autocomplete_people_search(request):
    '''
    AJAX view/helper function
    returns the matching set of values for autocomplete/quicksearch

    this function and view expects a parameter 'term' (?term=foo) containing a
    string to find matching entries (e.g. starting with 'foo') in the database
    '''
    text = request.params.get('term', '')
    # print u"DEBUG: autocomp. people search for: {}".format(text)
    return C3sMember.get_matching_people(text)
Example #23
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))
Example #24
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')
Example #25
0
def stats_view(request):
    """
    This view lets accountants view statistics:
    how many membership applications, real members, shares, etc.
    """
    return {
        # form submissions
        '_number_of_datasets': C3sMember.get_number(),
        'afm_shares_unpaid': C3sMember.afm_num_shares_unpaid(),
        'afm_shares_paid': C3sMember.afm_num_shares_paid(),
        # shares
        #'num_shares': Shares.get_total_shares(),
        # memberships
        #'num_memberships': Membership.get_number(),
        #'num_ms_nat': Membership.get_number(),  # XXX check!
        #'num_ms_jur': '0',  # XXX Membership.num_ms_jur(),
        #'num_ms_norm': Membership.num_ms_norm(),
        #'num_ms_inves': Membership.num_ms_invest(),
        # staff figures
        'num_staff': len(C3sStaff.get_all())
    }
Example #26
0
def search_codes(request):
    '''
    Search for Reference Codes

    We use a form with autocomplete to let staff find entries faster.
    '''
    # check for input from "find people" form
    if 'code_to_show' in request.POST:
        try:
            _code = request.POST['code_to_show']
            # print u"_code = {}".format(_code)
            _code_ = _code.split(' ')[0]
            # print u"_code_ = {}".format(_code_)
            _entry = C3sMember.get_by_code(_code_)
            # print _entry

            return HTTPFound(
                location=request.route_url(
                    'detail',
                    memberid=_entry.id)
            )
        except:
            pass

    '''
    we use another form with autocomplete to let staff find entries faster
    '''
    class AutocompleteRefCodeForm(colander.MappingSchema):
        code_to_show = colander.SchemaNode(
            colander.String(),
            title='Code finden (quicksearch; Groß-/Kleinschreibung beachten!)',
            widget=deform.widget.AutocompleteInputWidget(
                min_length=1,
                css_class="form-inline",
                values=request.route_path(
                    'autocomplete_input_values',
                    traverse=('autocomplete_input_values')
                )
            )
        )

    schema = AutocompleteRefCodeForm()
    form = deform.Form(
        schema,
        css_class="form-inline",
        buttons=('go!',),
    )
    refcodeformhtml = form.render()

    return {
        'refcodeform': refcodeformhtml,
    }
    '''
Example #27
0
def export_db(request):
    """
    export the database to a CSV file
    """
    datasets = C3sMember.member_listing(
        "id", how_many=C3sMember.get_number(), order='asc')
    header = ['firstname', 'lastname', 'email',
              'password', 'last_password_change',
              'address1', 'address2', 'postcode', 'city', 'country',
              'locale', 'date_of_birth',
              'email_is_confirmed', 'email_confirm_code',
              'num_shares', 'date_of_submission',
              'membership_type',
              'member_of_colsoc', 'name_of_colsoc',
              'signature_received', 'signature_received_date',
              'payment_received', 'payment_received_date',
              'signature_confirmed', 'signature_confirmed_date',
              'payment_confirmed', 'payment_confirmed_date',
              'accountant_comment',
              ]
    rows = []  # start with empty list
    for m in datasets:
        rows.append(
            (m.firstname, m.lastname, m.email,
             m.password, m.last_password_change,
             m.address1, m.address2, m.postcode, m.city, m.country,
             m.locale, m.date_of_birth,
             m.email_is_confirmed, m.email_confirm_code,
             m.num_shares, m.date_of_submission,
             m.membership_type,
             m.member_of_colsoc, m.name_of_colsoc,
             m.signature_received, m.signature_received_date,
             m.payment_received, m.payment_received_date,
             m.signature_confirmed, m.signature_confirmed_date,
             m.payment_confirmed, m.payment_confirmed_date,
             m.accountant_comment)
        )
    return {
        'header': header,
        'rows': rows}
Example #28
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)
Example #29
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)
Example #30
0
def delete_afms(request):
    '''
    Delete a bunch of AfMs in one go.

    I wrote this while implementing mass import to ease development 8-)
    '''
    class DeleteAfMRange(colander.MappingSchema):
        first = colander.SchemaNode(
            colander.Integer(),
            title='first ID to delete'
        )
        last = colander.SchemaNode(
            colander.Integer(),
            title='last ID to delete'
        )
    schema = DeleteAfMRange()
    delete_range_form = deform.Form(
        schema,
        buttons=[deform.Button('delete_them', 'DELETE')]
    )
    if 'first' in request.POST:
        # print "form was submitted!"
        # print "first ID to delete: %s" % request.POST['first']
        controls = request.POST.items()
        try:
            appstruct = delete_range_form.validate(controls)
            _first = appstruct['first']
            _last = appstruct['last']
            assert(_first < _last)  # XXX TODO: how about just one id? test!
        except ValidationFailure, e:
            return {
                'resetform': e.render()
            }
        for i in range(_first, _last+1):
            try:
                C3sMember.delete_by_id(i)
            except:
                print 'id %s didnt exist'
        return HTTPFound(request.route_url('dashboard'))
Example #31
0
        member = C3sMember.get_by_code(confirm_code)  # returns member or None

        if isinstance(member, NoneType):
            # member not found: FAIL!
            not_found_msg = _(
                u"Not found. Check verification URL. "
                "If all seems right, please use the form again.")
            return {
                'correct': False,
                'namepart': '',
                'result_msg': not_found_msg,
            }

        # check if the password is valid
        try:
            correct = C3sMember.check_password(member.id, _passwd)
        except AttributeError:
            correct = False
            request.session.flash(
                _(u'Wrong Password!'),
                'message_above_login')

        # check if info from DB makes sense
        # -member

        if (member.email == user_email) and correct:
            # set the email_is_confirmed flag in the DB for this signee
            member.email_is_confirmed = True
            # dbsession.flush()
            namepart = member.firstname + member.lastname
            import re
def membership_content_size_provider():
    return C3sMember.get_num_members_accepted()
Example #33
0
def success_verify_email(request):
    """
    This view is called via links sent in mails to verify mail addresses.
    It extracts both email and verification code from the URL.
    It will ask for a password
    and checks if there is a match in the database.
    """
    #dbsession = DBSession()
    #print('#'*80)
    # collect data from the URL/matchdict
    user_email = request.matchdict['email']
    #print(user_email)
    confirm_code = request.matchdict['code']
    #print(confirm_code)
    # if we want to ask the user for her password (through a form)
    # we need to have a url to send the form to
    post_url = '/verify/' + user_email + '/' + confirm_code

    if 'submit' in request.POST:
        #print("the form was submitted")
        request.session.pop_flash('message_above_form')
        # check for password ! ! !
        if 'password' in request.POST:
            _passwd = request.POST['password']
            #print("The password: %s" % _passwd)
        #else:
        # message: missing password!
        #    print('# message: missing password!')

        # get matching dataset from DB
        member = C3sMember.get_by_code(confirm_code)  # returns member or None

        if isinstance(member, NoneType):
            # member not found: FAIL!
            # print("a matching entry for this code was not found.")
            not_found_msg = _(u"Not found. Check verification URL. "
                              "If all seems right, please use the form again.")
            return {
                'correct': False,
                'namepart': '',
                'result_msg': not_found_msg,
            }

        # check if the password is valid
        try:
            correct = C3sMember.check_password(member.id, _passwd)
        except AttributeError:
            correct = False
            request.session.flash(_(u'Wrong Password!'), 'message_above_form')
        #print("member: %s" % member)
        #print("passwd correct? %s" % correct)
        # check if info from DB makes sense
        # -member

        if ((member.email == user_email) and correct):
            #print("-- found member, code matches, password too. COOL!")
            # set the email_is_confirmed flag in the DB for this signee
            member.email_is_confirmed = True
            #dbsession.flush()
            namepart = member.firstname + member.lastname
            import re
            PdfFileNamePart = re.sub(  # replace characters
                '[^a-zA-Z0-9]',  # other than these
                '_',  # with an underscore
                namepart)

            appstruct = {
                'firstname': member.firstname,
                'lastname': member.lastname,
                'email': member.email,
                'address1': member.address1,
                'address2': member.address2,
                'postcode': member.postcode,
                'city': member.city,
                'country': member.country,
                '_LOCALE_': member.locale,
                'date_of_birth': member.date_of_birth,
                'date_of_submission': member.date_of_submission,
                #'activity': set(activities),
                #'invest_member': u'yes' if member.invest_member else u'no',
                'membership_type': member.membership_type,
                'member_of_colsoc':
                u'yes' if member.member_of_colsoc else u'no',
                'name_of_colsoc': member.name_of_colsoc,
                #'opt_band': signee.opt_band,
                #'opt_URL': signee.opt_URL,
                'num_shares': member.num_shares,
            }
            request.session['appstruct'] = appstruct

            # log this person in, using the session
            log.info('verified code and password for id %s' % member.id)
            request.session.save()
            return {
                'firstname': member.firstname,
                'lastname': member.lastname,
                'correct': True,
                'namepart': PdfFileNamePart,
                'result_msg': _("Success. Load your PDF!")
            }
    # else: code did not match OR SOMETHING...
    # just display the form
    request.session.flash(_(u"Please enter your password."),
                          'message_above_form',
                          allow_duplicate=False)
    return {
        'post_url': post_url,
        'firstname': '',
        'lastname': '',
        'namepart': '',
        'correct': False,
        'result_msg': "something went wrong."
    }
Example #34
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)
    def test_membership_application(self):
        """
        Test the membership application process.

         1. Enter applicant data to application form
         2. Verify entered data and confirm
         3. Verify sent confirmation email
         4. Confirm email address via confirmation link
         5. Login to backend
         6. Verify applicant's detail page
         7. Set payment received
         8. Set signature received
         9. Make member
        10. Verify member details
        """
        self.testapp.reset()

        # 1. Enter applicant data to application form
        res = self.testapp.get('/', status=200)
        properties = {
            'firstname': u'Sönke',
            'lastname': u'Blømqvist',
            'email': u'*****@*****.**',
            'address1': u'℅ Big Boss',
            'address2': u'Håkanvägen 12',
            'postcode': u'ABC1234',
            'city': u'Stockholm',
            'year': u'1980',
            'month': u'01',
            'day': u'02',
            'name_of_colsoc': u'Svenska Tonsättares Internationella Musikbyrå',
            'num_shares': u'15',
            'password': u'worst password ever chosen',
            'password-confirm': u'worst password ever chosen',
        }
        for key, value in properties.iteritems():
            res.form[key] = value
        res.form['country'].select(text=u'Sweden')
        res.form['membership_type'].value__set(u'normal')
        res.form['other_colsoc'].value__set(u'yes')
        res.form['got_statute'].checked = True
        res.form['got_dues_regulations'].checked = True
        res = res.form.submit(u'submit', status=302)
        res = res.follow()

        # 2. Verify entered data and confirm
        body = self._response_to_bare_text(res)
        self.assertTrue('First Name: Sönke' in body)
        self.assertTrue('Last Name: Blømqvist' in body)
        self.assertTrue('Email Address: [email protected]' in body)
        self.assertTrue('Address Line 1: ℅ Big Boss' in body)
        self.assertTrue('Address Line 2: Håkanvägen 12' in body)
        self.assertTrue('Postal Code: ABC1234' in body)
        self.assertTrue('City: Stockholm' in body)
        self.assertTrue('Country: SE' in body)
        self.assertTrue('Date of Birth: 1980-01-02' in body)
        self.assertTrue('Type of Membership:normal' in body)
        self.assertTrue('Member of other Collecting Society: yes' in body)
        self.assertTrue(
            'Membership(s): Svenska Tonsättares Internationella Musikbyrå' in
            body)
        self.assertTrue('Number of Shares: 15' in body)
        self.assertTrue('Cost of Shares (50 € each): 750 €' in body)
        res = res.forms[1].submit(status=200)

        # 3. Verify sent confirmation email
        mailer = self.get_mailer(None)
        email = mailer.get_email()
        self.assertEqual(email.recipients, ['*****@*****.**'])
        self.assertEqual(email.subject,
                         'C3S: confirm your email address and load your PDF')

        # 4. Confirm email address via confirmation link
        match = re.search('localhost(?P<url>[^\s]+)', email.body)

        self.assertTrue(match is not None)
        res = self.testapp.get(match.group('url'), status=200)

        self.assertTrue(u'password in order to verify your email' in res.body)
        res.form['password'] = '******'
        res = res.form.submit(u'submit', status=200)

        # 5. Login to backend
        self.testapp.reset()
        self._login()

        # 6. Verify applicant's detail page
        member_id = DBSession.query(func.max(C3sMember.id)).scalar()
        res = self.testapp.get('/detail/{0}'.format(member_id), status=200)

        body = self._response_to_bare_text(res)
        self.assertTrue('firstname Sönke' in body)
        self.assertTrue('lastname Blømqvist' in body)
        self.assertTrue('email [email protected]' in body)
        self.assertTrue('email confirmed? Yes' in body)
        self.assertTrue('address1 ℅ Big Boss' in body)
        self.assertTrue('address2 Håkanvägen 12' in body)
        self.assertTrue('postcode ABC1234' in body)
        self.assertTrue('city Stockholm' in body)
        self.assertTrue('country SE' in body)
        self.assertTrue('date_of_birth 1980-01-02' in body)
        self.assertTrue('membership_accepted  No' in body)
        self.assertTrue('entity type Person' in body)
        self.assertTrue('membership type normal' in body)
        self.assertTrue('member_of_colsoc Yes' in body)
        self.assertTrue(
            'name_of_colsoc Svenska Tonsättares Internationella Musikbyrå' in
            body)
        self.assertTrue('date_of_submission ' in body)
        self.assertTrue('signature received?   Nein' in body)
        self.assertTrue('signature confirmed (mail sent)?No' in body)
        self.assertTrue('payment received?   Nein' in body)
        self.assertTrue('payment confirmed?No' in body)
        self.assertTrue('# shares  total: 15' in body)
        # TODO:
        # - code
        # - locale, set explicitly and test both German and English
        # - date of submission

        # 7. Set payment received
        res = self.testapp.get('/switch_pay/{0}'.format(member_id),
                               headers={'Referer': 'asdf'},
                               status=302)
        res = res.follow()
        body = self._response_to_bare_text(res)
        self.assertTrue('payment received?    Ja' in body)
        self.assertTrue('payment reception date 2018-04-26 12:23:34' in body)

        # 8. Set signature received
        res = self.testapp.get('/switch_sig/{0}'.format(member_id),
                               headers={'Referer': 'asdf'},
                               status=302)
        res = res.follow()
        body = self._response_to_bare_text(res)
        self.assertTrue('signature received?    Ja' in body)
        self.assertTrue('signature reception date2018-04-26 12:23:34' in body)

        # 9. Make member
        res = self.testapp.get('/make_member/{0}'.format(member_id),
                               headers={'Referer': 'asdf'},
                               status=200)
        res.form['membership_date'] = '2018-04-27'
        res = res.form.submit('submit', status=302)
        res = res.follow()

        # 10. Verify member details
        membership_number = C3sMember.get_next_free_membership_number() - 1
        body = self._response_to_bare_text(res)
        self.assertTrue('membership_accepted  Yes' in body)
        self.assertTrue(
            'membership_number  {0}'.format(membership_number) in body)
        self.assertTrue('membership_date 2018-04-27' in body)
        self.assertTrue('# shares  total: 15' in body)
        self.assertTrue('1 package(s)' in body)
        self.assertTrue('15 shares   (2018-04-27)' in body)
Example #36
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')
Example #37
0
def stats_view(request):
    """
    This view lets accountants view statistics:
    how many membership applications, real members, shares, etc.
    """
    # countries_dict = C3sMember.get_countries_list()
    _cl = C3sMember.get_countries_list()
    _cl_sorted = _cl.items()
    # print "die liste: {}".format(_cl_sorted)
    import operator
    _cl_sorted.sort(key=operator.itemgetter(1), reverse=True)
    # print "sortiert: {}".format(_cl_sorted)
    share_information = request.registry.share_information
    return {
        # form submissions
        '_number_of_datasets': C3sMember.get_number(),
        'afm_shares_unpaid': C3sMember.afm_num_shares_unpaid(),
        'afm_shares_paid': C3sMember.afm_num_shares_paid(),
        # shares
        'num_shares_members': share_information.get_share_count(),
        # 'num_shares_mem_norm': Shares.get_sum_norm(),
        # 'num_shares_mem_inv': Shares.get_sum_inv(),

        # memberships
        'num_members_accepted': C3sMember.get_num_members_accepted(),
        'num_non_accepted': C3sMember.get_num_non_accepted(),
        'num_nonmember_listing': C3sMember.nonmember_listing_count(),
        'num_duplicates': len(C3sMember.get_duplicates()),
        # 'num_empty_slots': C3sMember.get_num_empty_slots(),
        # normal persons vs. legal entities
        'num_ms_nat_acc': C3sMember.get_num_mem_nat_acc(),
        'num_ms_jur_acc': C3sMember.get_num_mem_jur_acc(),
        # normal vs. investing memberships
        'num_ms_norm': C3sMember.get_num_mem_norm(),
        'num_ms_inves': C3sMember.get_num_mem_invest(),
        'num_ms_features': C3sMember.get_num_mem_other_features(),
        'num_membership_lost': C3sMember.get_num_membership_lost(),
        # membership_numbers
        'num_memnums': C3sMember.get_num_membership_numbers(),
        'max_memnum': C3sMember.get_highest_membership_number(),
        'next_memnum': C3sMember.get_next_free_membership_number(),

        # countries
        'num_countries': C3sMember.get_num_countries(),
        'countries_list': _cl_sorted,
        #    key=lambda x: x[1]
        # ),  # XXX TODO: sorte

        # dues stats
        'dues15_stats': Dues15Invoice.get_monthly_stats(),
        'dues16_stats': Dues16Invoice.get_monthly_stats(),
        'dues17_stats': Dues17Invoice.get_monthly_stats(),

        # staff figures
        'num_staff': len(C3sStaff.get_all())
    }
Example #38
0
def make_dues16_invoice_no_pdf(request):
    """
    Create invoice PDFs on-the-fly.

    This view checks supplied information (in URL) against info in database
    and returns
    - an error message OR
    - a PDF as receipt

    === ===========================================================
    URL http://app:port/dues_invoice_no/EMAIL/CAQJGCGUFW/C3S-dues16-0001.pdf
    === ===========================================================

    """
    token = request.matchdict['code']
    invoice_number = request.matchdict['i']

    try:
        member = C3sMember.get_by_dues16_token(token)
        assert member is not None
        assert member.dues16_token == token
    except AssertionError:
        request.session.flash(
            u"This member and token did not match!",
            'message_to_user'  # message queue for user
        )
        return HTTPFound(request.route_url('error_page'))

    try:
        invoice = Dues16Invoice.get_by_invoice_no(
            invoice_number.lstrip('0'))
        assert invoice is not None
    except AssertionError:
        request.session.flash(
            u"No invoice found!",
            'message_to_user'  # message queue for user
        )
        return HTTPFound(request.route_url('error_page'))

    # sanity check: invoice token must match with token
    try:
        assert(invoice.token == token)
    except AssertionError:
        request.session.flash(
            u"Token did not match!",
            'message_to_user'  # message queue for user
        )
        return HTTPFound(request.route_url('error_page'))

    # sanity check: invoice must not be reversal
    try:
        assert(not invoice.is_reversal)
    except AssertionError:
        request.session.flash(
            u"Token did not match!",
            'message_to_user'  # message queue for user
        )
        return HTTPFound(request.route_url('error_page'))

    # return a pdf file
    pdf_file = make_invoice_pdf_pdflatex(member, invoice)
    response = Response(content_type='application/pdf')
    pdf_file.seek(0)  # rewind to beginning
    response.app_iter = open(pdf_file.name, "r")
    return response