Refactor MembershipWorks handling into separate class/file

This commit is contained in:
Adam Goldsmith 2020-02-06 17:48:09 -05:00
parent 25532bf21b
commit c52b76534c
5 changed files with 93 additions and 63 deletions

82
MembershipWorks.py Normal file
View File

@ -0,0 +1,82 @@
import csv
from io import StringIO
import requests
BASE_URL = "https://api.membershipworks.com/v1/"
class MembershipWorksRemoteError(Exception):
def __init__(self, reason, r):
super().__init__(
f"Error when attempting {reason}: {r.status_code} {r.reason}\n{r.text}")
class MembershipWorks:
def __init__(self):
self.auth_token = None
self.org_num = None
def login(self, username, password):
"""Authenticate against the membershipworks api"""
r = requests.post(BASE_URL + 'usr',
data={"_st": "all",
"eml": username,
"org": "10000",
"pwd": password})
if r.status_code != 200 or 'SF' not in r.json():
raise MembershipWorksRemoteError('login', r)
self.auth_token = r.json()['SF']
self.org_num = r.json()['org']
def _inject_auth(self, kwargs):
# TODO: should probably be a decorator or something
if self.auth_token is None:
raise RuntimeError('Not Logged in to MembershipWorks')
# add auth token to params
if 'params' not in kwargs:
kwargs['params'] = {}
kwargs['params']["SF"] = self.auth_token
def _get(self, *args, **kwargs):
self._inject_auth(kwargs)
# TODO: should probably do some error handling in here
return requests.get(*args, **kwargs)
def _post(self, *args, **kwargs):
self._inject_auth(kwargs)
# TODO: should probably do some error handling in here
return requests.post(*args, **kwargs)
def get_member_ids(self, folders):
# TODO: this is hardcoded for CMS
folder_map = {
'members': '5ae37979f033bfe8534f8799',
'staff': '5771675edcdf126302a2f6b9',
'misc': '5b69ee9bf033bf8e7346c434'
}
r = self._get(BASE_URL + "ylp", params={
"lbl": ",".join([folder_map[f] for f in folders]),
"org": self.org_num,
"var": "_id,nam,ctc"})
if r.status_code != 200 or 'usr' not in r.json():
raise MembershipWorksRemoteError('user listing', r)
# get list of member/staff IDs
return [user['uid'] for user in r.json()['usr']]
def get_members(self, folders, columns):
""" Pull the members csv from the membershipworks api
folders: a list of the names of the folders to get
(see folder_map in this function for mapping to ids)
columns: which columns to get"""
ids = self.get_member_ids(folders)
# get members CSV
# TODO: maybe can just use previous get instead? would return JSON
r = self._post(BASE_URL + "csv",
data={"_rt": "946702800", # unknown
"mux": "", # unknown
"tid": ",".join(ids), # ids of members to get
"var": columns})
if r.status_code != 200:
raise MembershipWorksRemoteError('csv generation', r)
return list(csv.DictReader(StringIO(r.text)))

View File

@ -1,15 +1,12 @@
import csv
from ruamel.yaml import YAML from ruamel.yaml import YAML
import urllib3 import urllib3
import os import os
import sys
from io import StringIO
import requests
from hid.DoorController import DoorController from hid.DoorController import DoorController
from passwords import (DOOR_USERNAME, DOOR_PASSWORD, from passwords import DOOR_USERNAME, DOOR_PASSWORD
MEMBERSHIPWORKS_USERNAME, MEMBERSHIPWORKS_PASSWORD) from passwords import MEMBERSHIPWORKS_USERNAME, MEMBERSHIPWORKS_PASSWORD
from MembershipWorks import MembershipWorks
# it's fine, ssl certs are for losers anyway # it's fine, ssl certs are for losers anyway
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
@ -28,51 +25,5 @@ doors = {doorName: DoorController(doorData['ip'],
memberLevels = config['memberLevels'] memberLevels = config['memberLevels']
doorSpecificSchedules = config['doorSpecificSchedules'] doorSpecificSchedules = config['doorSpecificSchedules']
def getMembershipworksData(folders, columns): membershipworks = MembershipWorks()
""" Pull the members csv from the membershipworks api membershipworks.login(MEMBERSHIPWORKS_USERNAME, MEMBERSHIPWORKS_PASSWORD)
folders: a list of the names of the folders to get
(see folder_map in this function for mapping to ids)
columns: which columns to get"""
BASE_URL = "https://api.membershipworks.com/v1/"
folder_map = {'members': '5ae37979f033bfe8534f8799',
'staff': '5771675edcdf126302a2f6b9',
'misc': '5b69ee9bf033bf8e7346c434'}
# login
r = requests.post(BASE_URL + 'usr',
data={"_st": "all",
"eml": MEMBERSHIPWORKS_USERNAME,
"org": "10000",
"pwd": MEMBERSHIPWORKS_PASSWORD})
if r.status_code != 200 or 'SF' not in r.json():
print("MembershipWorks Login Error: ", r.status_code, r.reason)
print(r.text)
sys.exit(1)
login_data = r.json()
# get list of member/staff IDs
r = requests.get(BASE_URL + "ylp",
params={"SF": login_data['SF'],
"lbl": ",".join([folder_map[f] for f in folders]),
"org": login_data['org'],
"var": "_id,nam,ctc"})
if r.status_code != 200 or 'usr' not in r.json():
print("MembershipWorks User Listing Error: ", r.status_code, r.reason)
print(r.text)
sys.exit(1)
ids = [user['uid'] for user in r.json()['usr']]
# get members CSV
# TODO: maybe can just use previous get instead? would return JSON
r = requests.post(BASE_URL + "csv",
params={"SF": login_data['SF']},
data={"_rt": "946702800", # unknown
"mux": "", # unknown
"tid": ",".join(ids), # ids of members to get
"var": columns})
if r.status_code != 200:
print("MembershipWorks CSV Generation Error: ", r.status_code, r.reason)
print(r.text)
sys.exit(1)
return list(csv.DictReader(StringIO(r.text)))

View File

@ -1,11 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from common import (doors, getMembershipworksData, memberLevels, from common import doors, membershipworks, memberLevels, doorSpecificSchedules
doorSpecificSchedules)
from hid.Credential import Credential from hid.Credential import Credential
from hid.DoorController import ROOT, E from hid.DoorController import ROOT, E
class Member(): class Member():
def __init__(self, forename="", surname="", membershipWorksID="", def __init__(self, forename="", surname="", membershipWorksID="",
middleName="", email="", phone="", middleName="", email="", phone="",
@ -230,7 +228,7 @@ def update_door(door, members):
def main(): def main():
memberData = getMembershipworksData( memberData = membershipworks.get_members(
['members', 'staff', 'misc'], ['members', 'staff', 'misc'],
"_id,nam,phn,eml,lvl,lbl,xws,xms,xsc,xas,xfd,xac,xcf,xeh,xse") "_id,nam,phn,eml,lvl,lbl,xws,xms,xsc,xas,xfd,xac,xcf,xeh,xse")

View File

@ -6,8 +6,7 @@ from flask import Flask, render_template, request
app = Flask(__name__) app = Flask(__name__)
from common import * from common import doors, membershipworks
from hid.DoorController import DoorController
def parse_list(member, regex): def parse_list(member, regex):
data_list = [] data_list = []
@ -46,7 +45,7 @@ def main():
return render_template("members.html", return render_template("members.html",
error="Enter at least 3 characters to search") error="Enter at least 3 characters to search")
data = getMembershipworksData( data = membershipworks.get_members(
['members', 'staff'], ['members', 'staff'],
"lvl,xws,xms,xsc,xas,xfd,xac,phn,eml,lbl,xcf,nam,end") "lvl,xws,xms,xsc,xas,xfd,xac,phn,eml,lbl,xcf,nam,end")

View File

@ -4,7 +4,7 @@ import re
import string import string
import subprocess import subprocess
from common import getMembershipworksData from common import membershipworks
LDAP_BASE = "cn=users,dc=sawtooth,dc=claremontmakerspace,dc=org" LDAP_BASE = "cn=users,dc=sawtooth,dc=claremontmakerspace,dc=org"
GROUP_BASE = "cn=groups,dc=sawtooth,dc=claremontmakerspace,dc=org" GROUP_BASE = "cn=groups,dc=sawtooth,dc=claremontmakerspace,dc=org"
@ -32,7 +32,7 @@ def makeAppendGroups(member):
for group in groups], []) for group in groups], [])
def main(): def main():
members = getMembershipworksData(['members', 'staff'], members = membershipworks.get_members(['members', 'staff'],
"lvl,phn,eml,lbl,nam,end,_id") "lvl,phn,eml,lbl,nam,end,_id")
makeGroups(members) makeGroups(members)