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(), }
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))
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}
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)}))
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 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, }
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))
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)
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'', }
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'], ) )
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)
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)))
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}
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)
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.')} ) )
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')
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'))
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)
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)
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 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')
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()) }
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, } '''
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}
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)
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)
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'))
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()
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." }
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)
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')
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()) }
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