Browse Source

Code cleanup

production
Markus Lorenz 5 years ago
parent
commit
66ee274b5e
8 changed files with 532 additions and 500 deletions
  1. +28
    -30
      c3smembership/__init__.py
  2. +39
    -41
      c3smembership/accountants_views.py
  3. +84
    -89
      c3smembership/annual_accounting.py
  4. +188
    -139
      c3smembership/import_export.py
  5. +3
    -7
      c3smembership/membership_list.py
  6. +183
    -187
      c3smembership/models.py
  7. +1
    -1
      c3smembership/security/request.py
  8. +6
    -6
      c3smembership/tests/test_models.py

+ 28
- 30
c3smembership/__init__.py View File

@ -2,7 +2,12 @@
This module holds the main method: config and route declarations
"""
import os
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.config import Configurator
from pyramid_beaker import session_factory_from_settings
from sqlalchemy import engine_from_config
from c3smembership.presentation.parameter_validation import (
@ -14,15 +19,11 @@ from c3smembership.security import (
Root,
groupfinder
)
from pyramid_beaker import session_factory_from_settings
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from c3smembership.presentation.views.dashboard import dashboard_content_size_provider
from c3smembership.presentation.views.membership_listing import membership_content_size_provider
from pkg_resources import get_distribution
__version__ = get_distribution('c3sMembership').version
__version__ = open(os.path.join(os.path.abspath(os.path.dirname(__file__)), '../VERSION')).read()
def main(global_config, **settings):
@ -43,20 +44,24 @@ def main(global_config, **settings):
authorization_policy=authz_policy,
session_factory=session_factory,
root_factory=Root)
# using a custom request with user information
config.set_request_factory(RequestWithUserAttribute)
config.include('pyramid_mailer')
config.include('pyramid_chameleon') # for pyramid 1.5a... for later
config.include('pyramid_chameleon')
config.include('cornice')
config.include('c3smembership.presentation.pagination')
config.add_translation_dirs(
'colander:locale/',
'deform:locale/', # copy deform.po and .mo to locale/de/LC_MESSAGES/
'deform:locale/',
'c3smembership:locale/')
config.add_static_view('static_deform', 'deform:static')
config.add_static_view('static',
'c3smembership:static', cache_max_age=3600)
config.add_static_view(
'static',
'c3smembership:static', cache_max_age=3600)
config.add_static_view(
'docs',
'../docs/_build/html/', cache_max_age=3600)
@ -73,7 +78,7 @@ def main(global_config, **settings):
config.add_renderer(name='csv',
factory='c3smembership.renderers.CSVRenderer')
# ## Membership application process
## Membership application process
# Step 1 (join.pt): home is /, the membership application form
config.add_route('join', '/')
# Step 2 (success.pt): check and edit data
@ -89,11 +94,7 @@ def main(global_config, **settings):
config.add_route(
'verify_afm_email',
'/vae/{refcode}/{token}/{email}') # verify afm email
# config.add_route(
# 'verify_member_email',
# '/vfe/{refcode}/{token}/{email}') # verify founders mail?
# routes & views for staff
# applications for membership
config.add_route('dashboard', '/dashboard')
config.make_pagination_route(
@ -110,18 +111,14 @@ def main(global_config, **settings):
config.add_route('detail', '/detail/{memberid}')
config.add_route('edit', '/edit/{_id}')
from models import C3sMember
# TODO: move application layer setup to separate module
from c3smembership.models import C3sMember
from c3smembership.business.membership_application import MembershipApplication
membership_application = MembershipApplication(C3sMember)
config.registry.membership_application = membership_application
config.add_route('switch_sig', '/switch_sig/{memberid}')
config.add_route('switch_pay', '/switch_pay/{memberid}')
config.add_route('mail_sig_confirmation', '/mail_sig_conf/{memberid}')
config.add_route('regenerate_pdf', '/re_C3S_SCE_AFM_{code}.pdf')
config.add_route('mail_pay_confirmation', '/mail_pay_conf/{memberid}')
@ -137,12 +134,15 @@ def main(global_config, **settings):
config.add_route('import_all', '/import_all')
config.add_route('import_with_ids', '/import_with_ids')
config.add_route('logout', '/logout')
# gather missing information
config.add_route('mail_mtype_form', '/mtype/{afmid}') # mail link to form
config.add_route('mtype_form', '/mtype/{refcode}/{token}/{email}') # form
config.add_route('mtype_thanks', '/mtype_thanks') # thanks
# applications for membership
config.add_route('afms_awaiting_approval', '/afms_awaiting_approval')
# memberships
config.add_route('make_member', '/make_member/{afm_id}')
config.add_route('merge_member', '/merge_member/{afm_id}/{mid}')
@ -162,14 +162,9 @@ def main(global_config, **settings):
config.add_route('membership_listing_aufstockers',
'/aml_aufstockers')
# membership dues 2015
config.add_route('send_dues15_invoice_email', '/dues15_invoice/{member_id}')
config.add_route('send_dues15_invoice_batch', '/dues15_invoice_batch')
# config.add_route('send_dues_receipt_mail',
# '/dues_receipt_mail/{member_id}')
# config.add_route('make_dues_invoice_pdf', # retired! use route below!
# '/dues_invoice/{email}/{code}/invoice.pdf')
config.add_route('make_dues15_invoice_no_pdf',
'/dues15_invoice_no/{email}/{code}/C3S-dues15-{i}.pdf')
config.add_route('dues15_reduction',
@ -179,7 +174,6 @@ def main(global_config, **settings):
config.add_route('dues15_notice', '/dues15_notice/{member_id}')
config.add_route('dues15_listing', '/dues15_listing')
# membership dues 2016
config.add_route('send_dues16_invoice_email', '/dues16_invoice/{member_id}')
config.add_route('send_dues16_invoice_batch', '/dues16_invoice_batch')
@ -192,15 +186,14 @@ def main(global_config, **settings):
config.add_route('dues16_notice', '/dues16_notice/{member_id}')
config.add_route('dues16_listing', '/dues16_listing')
from c3smembership.models import C3sMember, Dues15Invoice
# TODO: move application layer setup to separate module
from c3smembership.models import Dues15Invoice
from c3smembership.data.model.base import DBSession
from c3smembership.business.dues_invoice_archiving import DuesInvoiceArchiving
from c3smembership.views.membership_dues import (
make_invoice_pdf_pdflatex,
make_reversal_pdf_pdflatex,
)
import os
invoices_archive_path = os.path.abspath(
os.path.join(
os.path.dirname(os.path.abspath(__file__)),
@ -223,21 +216,26 @@ def main(global_config, **settings):
config.add_route('shares_detail', '/shares_detail/{id}')
config.add_route('shares_edit', '/shares_edit/{id}')
config.add_route('shares_delete', '/shares_delete/{id}')
# membership_certificate
config.add_route('certificate_mail', '/cert_mail/{id}')
config.add_route('certificate_pdf', '/cert/{id}/C3S_{name}_{token}.pdf')
config.add_route('certificate_pdf_staff', '/cert/{id}/C3S_{name}.pdf')
# annual reports
config.add_route('annual_reporting', '/annual_reporting')
# invite people
config.add_route('invite_member', '/invite_member/{m_id}')
config.add_route('invite_batch', '/invite_batch/{number}')
# search for people
config.add_route('search_people', '/search_people')
config.add_route('autocomplete_people_search', '/aps/')
# search for codes
config.add_route('search_codes', '/search_codes')
config.add_route('autocomplete_input_values', '/aiv/')
# fix the database
config.scan()
return config.make_wsgi_app()

+ 39
- 41
c3smembership/accountants_views.py View File

@ -11,53 +11,51 @@ This module holds views for accountants to do accounting stuff.
- ReGenerate a PDF for an application
"""
from c3smembership.models import (
C3sMember,
C3sStaff,
Dues15Invoice,
Dues16Invoice,
)
from c3smembership.presentation.i18n import _
import logging
from c3smembership.presentation.views.dashboard import get_dashboard_redirect
from c3smembership.presentation.schemas.accountant_login import (
AccountantLogin
)
from c3smembership.mail_utils import (
make_signature_confirmation_email,
make_payment_confirmation_email,
send_message,
)
from c3smembership.mail_reminders_util import (
make_signature_reminder_email,
make_payment_reminder_email,
from datetime import (
datetime,
date,
)
from c3smembership.utils import generate_pdf
import deform
from deform import ValidationFailure
from types import NoneType
from pyramid.view import view_config
import deform
from deform import ValidationFailure
from pyramid_mailer.message import Message
from pyramid.httpexceptions import HTTPFound
from pyramid.security import (
remember,
forget,
authenticated_userid,
)
from pyramid_mailer.message import Message
from pyramid.url import route_url
from datetime import (
datetime,
date,
from pyramid.view import view_config
from c3smembership.mail_utils import (
make_payment_confirmation_email,
send_message,
)
from c3smembership.mail_reminders_util import (
make_signature_reminder_email,
make_payment_reminder_email,
)
from c3smembership.models import (
C3sMember,
C3sStaff,
Dues15Invoice,
Dues16Invoice,
)
from c3smembership.presentation.i18n import _
from c3smembership.presentation.schemas.accountant_login import (
AccountantLogin
)
from c3smembership.presentation.views.dashboard import get_dashboard_redirect
from c3smembership.utils import generate_pdf
DEBUG = False
LOGGING = True
if LOGGING: # pragma: no cover
import logging
LOG = logging.getLogger(__name__)
DEBUG = False
LOG = logging.getLogger(__name__)
@view_config(renderer='templates/login.pt',
@ -173,24 +171,24 @@ def delete_entry(request):
if deletion_confirmed:
memberid = request.matchdict['memberid']
_member = C3sMember.get_by_id(memberid)
member_lastname = _member.lastname
member_firstname = _member.firstname
member = C3sMember.get_by_id(memberid)
member_lastname = member.lastname
member_firstname = member.firstname
C3sMember.delete_by_id(_member.id)
C3sMember.delete_by_id(member.id)
LOG.info(
"member.id %s was deleted by %s",
_member.id,
member.id,
request.user.login,
)
_message = "member.id %s was deleted" % _member.id
request.session.flash(_message, 'messages')
message = "member.id %s was deleted" % member.id
request.session.flash(message, 'messages')
_msgstr = u'Member with id {0} \"{1}, {2}\" was deleted.'
msgstr = u'Member with id {0} \"{1}, {2}\" was deleted.'
return HTTPFound(
request.route_url(
redirection_view,
_query={'message': _msgstr.format(
_query={'message': msgstr.format(
memberid,
member_lastname,
member_firstname)},


+ 84
- 89
c3smembership/annual_accounting.py View File

@ -1,25 +1,27 @@
"""
The Annual Report shows a bunch of numbers and lists:
The Annual Report shows a bunch of numbers and lists:
- the number of members and
- the number of shares
- the number of members and
- the number of shares
... acquired during a given timeframe.
... acquired during a given timeframe.
The default timeframe is the current year,
but start date and end date can be specified to 'debug' other periods, too.
The default timeframe is the current year, but start date and end date can be
specified to 'debug' other periods, too.
The report also shows the number of (and a list of)
shares paid but not 'approved' yet
- as part of a membership or
- when additional shares were acquired
The report also shows the number of (and a list of) shares paid but not
'approved' yet
- as part of a membership or
- when additional shares were acquired
"""
from datetime import (
date,
datetime,
)
import colander
from datetime import date
from datetime import datetime
import deform
from deform import ValidationFailure
from pyramid.view import view_config
@ -52,6 +54,9 @@ def annual_report(request): # pragma: no cover
# construct a form
class DatesForm(colander.Schema):
"""
Defines the colander schema for start and end date.
"""
startdate = colander.SchemaNode(
colander.Date(),
title='start date',
@ -70,31 +75,25 @@ def annual_report(request): # pragma: no cover
# if the form has been used and SUBMITTED, check contents
if 'submit' in request.POST:
# print("SUBMITTED!")
controls = request.POST.items()
try:
appstruct = form.validate(controls)
# print("the appstruct from the form: %s \n") % appstruct
# for thing in appstruct:
# print("the thing: %s") % thing
# print("type: %s") % type(thing)
start = appstruct['startdate'] # date
end = appstruct['enddate'] # date
# convert to datetime
start = appstruct['startdate']
end = appstruct['enddate']
start_date = datetime(start.year, start.month, start.day)
end_date = datetime(end.year, end.month, end.day)
except ValidationFailure, e: # pragma: no cover
except ValidationFailure, validation_failure: # pragma: no cover
print(e)
print(validation_failure)
request.session.flash(
_(u"Please note: There were errors, "
"please check the form below."),
_(u'Please note: There were errors, please check the form '
u'below.'),
'message_above_form',
allow_duplicate=False)
return{
'form': e.render(),
return {
'form': validation_failure.render(),
'num_members': 0,
'num_shares': 0,
'new_members': [],
@ -111,93 +110,89 @@ def annual_report(request): # pragma: no cover
# prepare: get information from the database
# get memberships
_all_members = C3sMember.get_all()
all_members = C3sMember.get_all()
# prepare filtering and counting
_members = [] # all the members matching the criteria
_num_members = 0
members = [] # all the members matching the criteria
members_count = 0
_num_afm_shares_paid_unapproved = 0
_afm_shares_paid_unapproved = []
afm_shares_paid_unapproved_cnt = 0
afm_shares_paid_unapproved = []
# now filter and count the afms and members
for m in _all_members:
for member in all_members:
if (
m.membership_accepted and # unneccessary!?
(m.membership_date >= start_date) and
(m.membership_date <= end_date)
member.membership_accepted and # unneccessary!?
(member.membership_date >= start_date) and
(member.membership_date <= end_date)
):
# add this item to the list
_members.append(m)
_num_members += 1
# also count the shares
# for _s in item.shares:
# if (_s.date_of_acquisition <= _date):
# _count_shares += _s.number
members.append(member)
members_count += 1
elif ( # member is not accepted yet
(
(not m.membership_accepted) or
(m.membership_accepted is None)
(not member.membership_accepted) or
(member.membership_accepted is None)
) and
# but payment has been received during timespan
(m.payment_received) and
(member.payment_received) and
(datetime(
m.payment_received_date.year,
m.payment_received_date.month,
m.payment_received_date.day,
member.payment_received_date.year,
member.payment_received_date.month,
member.payment_received_date.day,
) >= start_date) and
(datetime(
m.payment_received_date.year,
m.payment_received_date.month,
m.payment_received_date.day,
member.payment_received_date.year,
member.payment_received_date.month,
member.payment_received_date.day,
) <= end_date)):
_num_afm_shares_paid_unapproved += m.num_shares
_afm_shares_paid_unapproved.append(m)
afm_shares_paid_unapproved_cnt += member.num_shares
afm_shares_paid_unapproved.append(member)
# shares
_count_shares = 0
_new_shares = []
_num_shares_paid_unapproved = 0
_shares_paid_unapproved = []
shares_count = 0
new_shares = []
shares_paid_unapproved_count = 0
shares_paid_unapproved = []
_all_shares = Shares.get_all()
for s in _all_shares:
if s is not None:
all_shares = Shares.get_all()
for share in all_shares:
if share is not None:
if ( # shares approved during span
(datetime(
s.date_of_acquisition.year,
s.date_of_acquisition.month,
s.date_of_acquisition.day,
share.date_of_acquisition.year,
share.date_of_acquisition.month,
share.date_of_acquisition.day,
) >= start_date) and
(datetime(
s.date_of_acquisition.year,
s.date_of_acquisition.month,
s.date_of_acquisition.day,
share.date_of_acquisition.year,
share.date_of_acquisition.month,
share.date_of_acquisition.day,
) <= end_date)
):
_count_shares += s.number
_new_shares.append(s)
shares_count += share.number
new_shares.append(share)
elif ( # shares NOT approved before end of span
(datetime(
s.date_of_acquisition.year,
s.date_of_acquisition.month,
s.date_of_acquisition.day,
share.date_of_acquisition.year,
share.date_of_acquisition.month,
share.date_of_acquisition.day,
) >= end_date) and
(datetime( # payment received during ...
s.payment_received_date.year,
s.payment_received_date.month,
s.payment_received_date.day,
share.payment_received_date.year,
share.payment_received_date.month,
share.payment_received_date.day,
) >= start_date) and
(datetime( # payment received during ...
s.payment_received_date.year,
s.payment_received_date.month,
s.payment_received_date.day,
share.payment_received_date.year,
share.payment_received_date.month,
share.payment_received_date.day,
) <= end_date)
):
_num_shares_paid_unapproved += s.number
_shares_paid_unapproved.append(s)
shares_paid_unapproved_count += share.number
shares_paid_unapproved.append(share)
html = form.render()
@ -208,16 +203,16 @@ def annual_report(request): # pragma: no cover
'end_date': end_date,
'datetime': datetime,
# members
'new_members': _members,
'num_members': _num_members,
'new_members': members,
'num_members': members_count,
# shares
'num_shares': _count_shares,
'sum_shares': _count_shares * 50,
'new_shares': _new_shares,
'num_shares': shares_count,
'sum_shares': shares_count * 50,
'new_shares': new_shares,
# afm shares paid but unapproved
'num_afm_shares_paid_unapproved': _num_afm_shares_paid_unapproved,
'afm_paid_unapproved_shares': _afm_shares_paid_unapproved,
'num_afm_shares_paid_unapproved': afm_shares_paid_unapproved_cnt,
'afm_paid_unapproved_shares': afm_shares_paid_unapproved,
# other shares paid but unapproved
'num_shares_paid_unapproved': _num_shares_paid_unapproved,
'paid_unapproved_shares': _shares_paid_unapproved,
'num_shares_paid_unapproved': shares_paid_unapproved_count,
'paid_unapproved_shares': shares_paid_unapproved,
}

+ 188
- 139
c3smembership/import_export.py View File

@ -9,28 +9,28 @@ This module has **import** and **export** functionality.
- Export all email addresses for Mailman (CSV)
- Export only the members (CSV)
"""
from datetime import datetime
from pyramid.httpexceptions import HTTPFound
from pyramid.security import authenticated_userid
from pyramid.view import view_config
import logging
import tempfile
import unicodecsv
from sqlalchemy.exc import (
IntegrityError,
ResourceClosedError,
)
import tempfile
import unicodecsv
from pyramid.httpexceptions import HTTPFound
from pyramid.security import authenticated_userid
from pyramid.view import view_config
from c3smembership.data.model.base import DBSession
from c3smembership.models import (
C3sMember,
)
DEBUG = False
LOGGING = True
if LOGGING: # pragma: no cover
import logging
log = logging.getLogger(__name__)
DEBUG = False
LOG = logging.getLogger(__name__)
@view_config(renderer='templates/import.pt',
@ -45,23 +45,22 @@ def import_db(request):
New members are merged into the master instance of the database.
"""
try: # check if the file exists
with open('import/import.csv', 'r') as f:
with open('import/import.csv', 'r') as import_file:
# store contents in tempfile
content = tempfile.NamedTemporaryFile()
content.write(f.read())
content.write(import_file.read())
content.seek(0) # rewind to beginning
except IOError, ioe: # pragma: no cover
print ioe
return {'message': "file not found.",
# 'codes': ''
}
return {'message': 'file not found.'}
# reader for CSV files
r = unicodecsv.reader(content.file, delimiter=';',
encoding='utf-8',
quoting=unicodecsv.QUOTE_ALL
)
header = r.next() # first line is the header.
import_file = unicodecsv.reader(
content.file,
delimiter=';',
encoding='utf-8',
quoting=unicodecsv.QUOTE_ALL)
header = import_file.next() # first line is the header.
# print("the header: %s" % header)
# check it for compatibility
try:
@ -80,12 +79,10 @@ def import_db(request):
u'payment_confirmed', u'payment_confirmed_date', # 25, 26
u'accountant_comment' # 27
]
except AssertionError, ae: # pragma: no cover
print ae
print "the header of the CSV does not match what we expect"
return {'message': "header fields mismatch. NOT importing",
# 'codes': _codes,
}
except AssertionError, assertion_error: # pragma: no cover
print assertion_error
print 'the header of the CSV does not match what we expect'
return {'message': 'header fields mismatch. NOT importing'}
# remember the codes imported
# _codes = []
@ -97,7 +94,7 @@ def import_db(request):
# import pdb
# pdb.set_trace()
try:
row = r.next()
row = import_file.next()
except:
break
counter += 1
@ -209,34 +206,34 @@ def import_db(request):
dbsession = DBSession
dbsession.add(import_member)
dbsession.flush()
log.info(
"%s imported dataset %s" % (
authenticated_userid(request),
import_member.email_confirm_code))
LOG.info(
"%s imported dataset %s",
authenticated_userid(request),
import_member.email_confirm_code)
request.session.flash(
"imported dataset %s" % (import_member.email_confirm_code),
'messages',
)
# print('done with %s!' % counter)
except ResourceClosedError, rce: # pragma: no cover
# XXX can't catch this exception,
# TODO: can't catch this exception,
# because it happens somwhere else, later, deeper !?!
print "transaction was aborted/resource closed"
print rce
return {'message': ("tried import of dataset(s) with "
"existing confirm code. ABORTED!")}
except IntegrityError, ie: # pragma: no cover
except IntegrityError, integrity_error: # pragma: no cover
print "integrity error"
dbsession.rollback()
print ie
if 'column email_confirm_code is not unique' in ie.message:
print integrity_error
if 'column email_confirm_code is not unique' in integrity_error.message:
print("import of dataset %s failed, because the confirmation"
"code already existed" % counter)
return {'message': ("tried import of dataset(s) with "
"existing confirm code. ABORTED!")}
except StopIteration, si: # pragma: no cover
except StopIteration, stop_iteration: # pragma: no cover
print "stop iteration reached"
print si
print stop_iteration
return {'message': "file found, StopIteration reached."}
# except:
# print "passing"
@ -270,24 +267,23 @@ def import_db_with_ids(request): # pragma: no cover
XXX TODO: implement a test-case
"""
try: # check if the file exists
with open('import/import.csv', 'r') as f:
with open('import/import.csv', 'r') as import_file:
# store contents in tempfile
content = tempfile.NamedTemporaryFile()
content.write(f.read())
content.write(import_file.read())
content.seek(0) # rewind to beginning
print "found the impport file."
except IOError, ioe:
print ioe
return {'message': "file not found.",
# 'codes': ''
}
return {'message': 'file not found.'}
# reader for CSV files
r = unicodecsv.reader(content.file, delimiter=';',
encoding='utf-8',
quoting=unicodecsv.QUOTE_ALL
)
header = r.next() # first line is the header.
import_file = unicodecsv.reader(
content.file,
delimiter=';',
encoding='utf-8',
quoting=unicodecsv.QUOTE_ALL)
header = import_file.next() # first line is the header.
# print("the header: %s" % header)
# check it for compatibility
try:
@ -305,19 +301,17 @@ def import_db_with_ids(request): # pragma: no cover
u'payment_received', u'payment_received_date', # 21, 22
u'signature_confirmed', u'signature_confirmed_date', # 23, 24
u'payment_confirmed', u'payment_confirmed_date', # 25, 26
u'accountant_comment' # 27
u'accountant_comment', # 27
]
assert header == expected_header
except AssertionError, ae:
print ae
except AssertionError, assertion_error:
print assertion_error
print "the header of the CSV does not match what we expect"
print "expected:"
print expected_header
print "got:"
print header
return {'message': "header fields mismatch. NOT importing",
# 'codes': _codes,
}
return {'message': 'header fields mismatch. NOT importing'}
# remember the codes imported
# _codes = []
@ -329,7 +323,7 @@ def import_db_with_ids(request): # pragma: no cover
# import pdb
# pdb.set_trace()
try:
row = r.next()
row = import_file.next()
except:
break
counter += 1
@ -411,30 +405,30 @@ def import_db_with_ids(request): # pragma: no cover
dbsession = DBSession
dbsession.add(import_member)
dbsession.flush()
log.info(
"%s imported dataset %s" % (
authenticated_userid(request),
import_member.email_confirm_code))
LOG.info(
'%s imported dataset %s',
authenticated_userid(request),
import_member.email_confirm_code)
# print('done with %s!' % counter)
except ResourceClosedError, rce:
# XXX can't catch this exception,
# TODO: can't catch this exception,
# because it happens somwhere else, later, deeper !?!
print "transaction was aborted/resource closed"
print rce
return {'message': ("tried import of dataset(s) with "
"existing confirm code. ABORTED!")}
except IntegrityError, ie:
except IntegrityError, integrity_error:
print "integrity error"
dbsession.rollback()
print ie
if 'column email_confirm_code is not unique' in ie.message:
print integrity_error
if 'column email_confirm_code is not unique' in integrity_error.message:
print("import of dataset %s failed, because the confirmation"
"code already existed" % counter)
return {'message': ("tried import of dataset(s) with "
"existing confirm code. ABORTED!")}
except StopIteration, si:
except StopIteration, stop_iteration:
print "stop iteration reached"
print si
print stop_iteration
return {'message': "file found, StopIteration reached."}
# except:
# print "passing"
@ -454,37 +448,66 @@ def export_db(request):
"""
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',
]
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)
)
for member in datasets:
rows.append((
member.firstname,
member.lastname,
member.email,
member.password,
member.last_password_change,
member.address1,
member.address2,
member.postcode,
member.city,
member.country,
member.locale,
member.date_of_birth,
member.email_is_confirmed,
member.email_confirm_code,
member.num_shares,
member.date_of_submission,
member.membership_type,
member.member_of_colsoc,
member.name_of_colsoc,
member.signature_received,
member.signature_received_date,
member.payment_received,
member.payment_received_date,
member.signature_confirmed,
member.signature_confirmed_date,
member.payment_confirmed,
member.payment_confirmed_date,
member.accountant_comment))
return {
'header': header,
'rows': rows}
@ -502,10 +525,12 @@ def export_yes_emails(request): # pragma: no cover
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 + '>',))
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 <devnull@c3s.cc>', ],
'rows': rows}
@ -523,48 +548,72 @@ def export_memberships(request): # pragma: no cover
_num = C3sMember.get_number()
datasets = C3sMember.get_members(
'id', how_many=_num, offset=0, order=u'asc')
header = ['firstname', 'lastname', 'email',
'address1', 'address2', 'postcode', 'city', 'country',
'locale', 'date_of_birth',
# 'email_is_confirmed',
'email_confirm_code',
'membership_date',
'num_shares', # 'date_of_submission',
# 'shares list (number+date)',
'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',
'is_legalentity',
'court of law',
'registration number',
]
header = [
'firstname',
'lastname',
'email',
'address1',
'address2',
'postcode',
'city',
'country',
'locale',
'date_of_birth',
# 'email_is_confirmed',
'email_confirm_code',
'membership_date',
'num_shares',
# 'date_of_submission',
# 'shares list (number+date)',
'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',
'is_legalentity',
'court of law',
'registration number',]
rows = [] # start with empty list
for m in datasets:
rows.append(
(m.firstname, m.lastname, m.email,
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.membership_date,
m.num_shares,
# m.date_of_submission,
# '+'.join(str(s.id)+'('+str(s.number)+')' for s in m.shares),
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,
m.is_legalentity,
m.court_of_law,
m.registration_number,)
)
for member in datasets:
rows.append((
member.firstname,
member.lastname,
member.email,
member.address1,
member.address2,
member.postcode,
member.city,
member.country,
member.locale,
member.date_of_birth,
# member.email_is_confirmed,
member.email_confirm_code,
member.membership_date,
member.num_shares,
# member.date_of_submission,
# '+'.join(str(s.id)+'('+str(s.number)+')' for s in member.shares),
member.membership_type,
member.member_of_colsoc,
member.name_of_colsoc,
member.signature_received,
member.signature_received_date,
member.payment_received,
member.payment_received_date,
# member.signature_confirmed,
member.signature_confirmed_date,
# member.payment_confirmed,
member.payment_confirmed_date,
member.accountant_comment,
member.is_legalentity,
member.court_of_law,
member.registration_number,))
return {
'header': header,
'rows': rows}

+ 3
- 7
c3smembership/membership_list.py View File

@ -55,7 +55,6 @@ def member_list_date_pdf_view(request):
If the date is not parseable, an error message is shown.
"""
DEBUG = False
try:
_date_m = request.matchdict['date']
_date = datetime.strptime(_date_m, '%Y-%m-%d').date()
@ -92,7 +91,6 @@ def member_list_date_pdf_view(request):
They are added to a list and counted.
Their shares (those acquired before the date) are counted as well.
"""
# filter and count memberships and shares
for item in _all_members:
@ -209,7 +207,7 @@ def member_list_date_pdf_view(request):
if member.membership_loss_date is not None:
membership_loss += \
member.membership_loss_date.strftime('%d.%m.%Y') + \
'\linebreak '
'\\linebreak '
if member.membership_loss_type is not None:
membership_loss += member.membership_loss_type
latex_file.write(
@ -291,7 +289,6 @@ def member_list_print_view(request):
try:
assert(member.membership_number is not None)
except AssertionError:
pass
if DEBUG: # pragma: no cover
print u"failed at id {} lastname {}".format(
member.id, member.lastname)
@ -329,7 +326,6 @@ def merge_member_view(request):
The second entry in the C3sMember table is given the 'is_duplicate' flag
and also the 'duplicate_of' is given the *id* of the original entry.
"""
DEBUG = False
_id = request.matchdict['afm_id']
_mid = request.matchdict['mid']
if DEBUG: # pragma: no cover
@ -338,7 +334,7 @@ def merge_member_view(request):
orig = C3sMember.get_by_id(_mid)
merg = C3sMember.get_by_id(_id)
if not (orig.membership_accepted):
if not orig.membership_accepted:
request.session.flash(
'you can only merge to accepted members!',
'merge_message')
@ -350,7 +346,7 @@ def merge_member_view(request):
'merge_message')
return HTTPFound(request.route_url('make_member', afm_id=_id))
# XXX TODO: this needs fixing!!!
# TODO: this needs fixing!!!
# date must be set manually according to date of approval of the board
_date_for_shares = merg.signature_received_date if (
merg.signature_received_date > merg.payment_received_date


+ 183
- 187
c3smembership/models.py View File

@ -23,45 +23,45 @@ from datetime import (
datetime,
)
from decimal import Decimal
import cryptacular.bcrypt
import math
import re
from sqlalchemy import (
Table,
and_,
Boolean,
Column,
Date,
DateTime,
distinct,
ForeignKey,
Integer,
Boolean,
DateTime,
Date,
Unicode,
or_,
and_,
Table,
Unicode,
)
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.sql import func
from sqlalchemy.sql import expression
from sqlalchemy.orm import (
scoped_session,
sessionmaker,
relationship,
synonym
)
import sqlalchemy.types as types
from zope.sqlalchemy import ZopeTransactionExtension
import cryptacular.bcrypt
from c3smembership.data.model.base import (
Base,
DBSession,
)
crypt = cryptacular.bcrypt.BCRYPTPasswordManager()
CRYPT = cryptacular.bcrypt.BCRYPTPasswordManager()
def hash_password(password):
return unicode(crypt.encode(password))
"""
Calculates the password hash.
"""
return unicode(CRYPT.encode(password))
# TODO: Use standard SQLAlchemy Decimal when a database is used which supports
@ -94,10 +94,16 @@ DatabaseDecimal = SqliteDecimal
class InvalidPropertyException(Exception):
"""
Exception indicating an invalid property value.
"""
pass
class InvalidSortDirection(Exception):
"""
Exception indicating an invalid sort direction.
"""
pass
@ -110,6 +116,7 @@ class Group(Base):
Users in group 'staff' may do things others may not.
"""
__tablename__ = 'groups'
# pylint: disable=invalid-name
id = Column(Integer, primary_key=True, nullable=False)
"""technical id. / number in table (Integer, Primary Key)"""
name = Column(Unicode(30), unique=True, nullable=False)
@ -155,6 +162,7 @@ class C3sStaff(Base):
"""
__tablename__ = 'staff'
# pylint: disable=invalid-name
id = Column(Integer, primary_key=True)
"""technical id. / number in table (integer, primary key)"""
login = Column(Unicode(255), unique=True)
@ -191,7 +199,7 @@ class C3sStaff(Base):
password = synonym('_password', descriptor=password)
@classmethod
def get_by_id(cls, id):
def get_by_id(cls, staff_id):
"""
Get C3sStaff object by id.
@ -202,7 +210,7 @@ class C3sStaff(Base):
* **object**: C3sStaff object with relevant id, if exists.
* **None**: if id can't be found.
"""
return DBSession.query(cls).filter(cls.id == id).first()
return DBSession.query(cls).filter(cls.id == staff_id).first()
@classmethod
def get_by_login(cls, login):
@ -232,11 +240,11 @@ class C3sStaff(Base):
and the hash from the database
"""
staffer = cls.get_by_login(login)
return crypt.check(staffer.password, password)
return CRYPT.check(staffer.password, password)
# this one is used by RequestWithUserAttribute
@classmethod
def check_user_or_None(cls, login):
def check_user_or_none(cls, login):
"""
Check whether a user by that login exists in the database.
@ -251,14 +259,13 @@ class C3sStaff(Base):
return login
@classmethod
def delete_by_id(cls, id):
def delete_by_id(cls, staff_id):
"""
Delete one C3sStaff object by id.
"""
_del = DBSession.query(cls).filter(cls.id == id).first()
_del.groups = []
DBSession.query(cls).filter(cls.id == id).delete()
return
row = DBSession.query(cls).filter(cls.id == staff_id).first()
row.groups = []
DBSession.query(cls).filter(cls.id == staff_id).delete()
@classmethod
def get_all(cls):
@ -286,6 +293,7 @@ class Shares(Base):
"""
__tablename__ = 'shares'
# pylint: disable=invalid-name
id = Column(Integer, primary_key=True)
"""technical id. / number in table (integer, primary key)"""
number = Column(Integer())
@ -382,6 +390,7 @@ class C3sMember(Base):
Some attributes have been added over time to cater for different needs.
"""
__tablename__ = 'members'
# pylint: disable=invalid-name
id = Column(Integer, primary_key=True)
"""technical id. / number in table (integer, primary key)"""
@ -446,7 +455,7 @@ class C3sMember(Base):
* id of entry considered as original or relevant for membership
"""
# shares
num_shares = Column(Integer()) # XXX TODO: check for number <= max_shares
num_shares = Column(Integer())
"""Integer
* The number of shares from the time of afm submission
@ -702,16 +711,16 @@ class C3sMember(Base):
@hybrid_property
def dues15_balance(self):
"""
XXX TODO write this docstring
XXX TODO write testcase in test_models.py
TODO: write this docstring
TODO: write testcase in test_models.py
"""
return self._dues15_balance
@dues15_balance.setter
def dues15_balance(self, dues15_balance):
"""
XXX TODO write this docstring
XXX TODO write testcase in test_models.py
TODO: write this docstring
TODO: write testcase in test_models.py
"""
self._dues15_balance = dues15_balance
self.dues15_balanced = self._dues15_balance == Decimal('0')
@ -719,36 +728,36 @@ class C3sMember(Base):
@hybrid_property
def dues15_amount_reduced(self):
"""
XXX TODO write this docstring
XXX TODO write testcase in test_models.py
TODO: write this docstring
TODO: write testcase in test_models.py
"""
return self._dues15_amount_reduced
@dues15_amount_reduced.setter
def dues15_amount_reduced(self, dues15_amount_reduced):
"""
XXX TODO write this docstring
XXX TODO write testcase in test_models.py
TODO: write this docstring
TODO: write testcase in test_models.py
"""
self._dues15_amount_reduced = dues15_amount_reduced
self.dues15_reduced = \
not math.isnan(self.dues15_amount_reduced) \
and \
self.dues15_amount_reduced != self.dues15_amount
self.dues15_reduced = (
not math.isnan(self.dues15_amount_reduced)
and
self.dues15_amount_reduced != self.dues15_amount)
@hybrid_property
def dues16_balance(self):
"""
XXX TODO write this docstring
XXX TODO write testcase in test_models.py
TODO: write this docstring
TODO: write testcase in test_models.py
"""
return self._dues16_balance
@dues16_balance.setter
def dues16_balance(self, dues16_balance):
"""
XXX TODO write this docstring
XXX TODO write testcase in test_models.py
TODO: write this docstring
TODO: write testcase in test_models.py
"""
self._dues16_balance = dues16_balance
self.dues16_balanced = self._dues16_balance == Decimal('0')
@ -756,16 +765,16 @@ class C3sMember(Base):
@hybrid_property
def dues16_amount_reduced(self):
"""
XXX TODO write this docstring
XXX TODO write testcase in test_models.py
TODO: write this docstring
TODO: write testcase in test_models.py
"""
return self._dues16_amount_reduced
@dues16_amount_reduced.setter
def dues16_amount_reduced(self, dues16_amount_reduced):
"""
XXX TODO write this docstring
XXX TODO write testcase in test_models.py
TODO: write this docstring
TODO: write testcase in test_models.py
"""
self._dues16_amount_reduced = dues16_amount_reduced
self.dues16_reduced = \
@ -810,13 +819,10 @@ class C3sMember(Base):
"""
check = DBSession.query(cls).filter(
cls.email_confirm_code == email_confirm_code).first()
if check: # pragma: no cover
return True
else:
return False
return (check is not None)
@classmethod
def get_by_id(cls, _id):
def get_by_id(cls, member_id):
"""
Get one C3sMember object by id.
@ -824,22 +830,22 @@ class C3sMember(Base):
* **C3sMember object**, if id exists.
* **None**, if id does not exist.
"""
return DBSession.query(cls).filter(cls.id == _id).first()
return DBSession.query(cls).filter(cls.id == member_id).first()
@classmethod
def get_by_email(cls, _email):
def get_by_email(cls, email):
"""return one or more members by email (a list!)"""
return DBSession.query(cls).filter(cls.email == _email).all()
return DBSession.query(cls).filter(cls.email == email).all()
@classmethod
def get_by_dues15_token(cls, _code):
def get_by_dues15_token(cls, code):
"""return one member by fee token"""
return DBSession.query(cls).filter(cls.dues15_token == _code).first()
return DBSession.query(cls).filter(cls.dues15_token == code).first()
@classmethod
def get_by_dues16_token(cls, _code):
def get_by_dues16_token(cls, code):
"""return one member by fee token"""
return DBSession.query(cls).filter(cls.dues16_token == _code).first()
return DBSession.query(cls).filter(cls.dues16_token == code).first()
@classmethod
def get_all(cls):
@ -870,7 +876,9 @@ class C3sMember(Base):
or_(
(cls.email_invite_flag_bcgv16 == 0),
(cls.email_invite_flag_bcgv16 == ''),
(cls.email_invite_flag_bcgv16 == None), # noqa
# noqa
# pylint: disable=singleton-comparison
(cls.email_invite_flag_bcgv16 == None),
)
)
).slice(0, num).all()
@ -922,7 +930,7 @@ class C3sMember(Base):
)).slice(0, num).all()
@classmethod
def delete_by_id(cls, _id):
def delete_by_id(cls, member_id):
"""
Delete one C3sMember entry by id.
@ -933,7 +941,7 @@ class C3sMember(Base):
* **1** on success
* **0** else
"""
return DBSession.query(cls).filter(cls.id == _id).delete()
return DBSession.query(cls).filter(cls.id == member_id).delete()
# listings
@classmethod
@ -973,12 +981,12 @@ class C3sMember(Base):
except:
raise Exception("Invalid order_by ({0}) or order value "
"({1})".format(order_by, order))
_how_many = int(offset) + int(how_many)
_offset = int(offset)
q = DBSession.query(cls).filter(
count = int(offset) + int(how_many)
offset = int(offset)
query = DBSession.query(cls).filter(
cls.membership_accepted == 1
).order_by(order_function()).slice(_offset, _how_many)
return q
).order_by(order_function()).slice(offset, count)
return query
# statistical stuff
@classmethod
@ -988,20 +996,19 @@ class C3sMember(Base):
Returns:
bag (list containing duplicates): postal codes in DE"""
all = DBSession.query(cls).filter(
rows = DBSession.query(cls).filter(
cls.country == 'DE'
).all()
postal_codes_de = []
for i in all:
for row in rows:
try:
int(i.postcode)
len(i.postcode) == 5
postal_codes_de.append(i.postcode)
except:
int(row.postcode)
if len(row.postcode) == 5:
postal_codes_de.