forked from CMS/memberPlumbing
Refactor doorUpdater to add support for door specific schedules
This commit is contained in:
parent
d5be64c37d
commit
25532bf21b
@ -26,6 +26,7 @@ doors = {doorName: DoorController(doorData['ip'],
|
|||||||
for doorName, doorData in config["doors"].items()}
|
for doorName, doorData in config["doors"].items()}
|
||||||
|
|
||||||
memberLevels = config['memberLevels']
|
memberLevels = config['memberLevels']
|
||||||
|
doorSpecificSchedules = config['doorSpecificSchedules']
|
||||||
|
|
||||||
def getMembershipworksData(folders, columns):
|
def getMembershipworksData(folders, columns):
|
||||||
""" Pull the members csv from the membershipworks api
|
""" Pull the members csv from the membershipworks api
|
||||||
|
12
config.yaml
12
config.yaml
@ -14,3 +14,15 @@ memberLevels:
|
|||||||
CMS Unlimited: Unlimited
|
CMS Unlimited: Unlimited
|
||||||
CMS Nights & Weekends: Nights and Weekends
|
CMS Nights & Weekends: Nights and Weekends
|
||||||
CMS Day Pass: Unlimited
|
CMS Day Pass: Unlimited
|
||||||
|
|
||||||
|
# {schedule: {property: [doors]}}
|
||||||
|
doorSpecificSchedules:
|
||||||
|
Extended Hours:
|
||||||
|
Access Front Door and Studio Space During Extended Hours?:
|
||||||
|
- Front Door
|
||||||
|
- Studio Space
|
||||||
|
- Storage Closet
|
||||||
|
Access Permitted Shops During Extended Hours?:
|
||||||
|
- Metal Shop
|
||||||
|
- Wood Shop
|
||||||
|
- Wood Shop Rear
|
||||||
|
138
doorUpdater.py
138
doorUpdater.py
@ -1,15 +1,16 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from common import doors, getMembershipworksData, memberLevels
|
from common import (doors, getMembershipworksData, memberLevels,
|
||||||
|
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="",
|
||||||
cardholderID=None, doorAccess=[], onHold=False,
|
cardholderID=None, doorAccess=[], onHold=False,
|
||||||
credentials=set(), levels=[], schedules=[]):
|
credentials=set(), levels=[], extraLevels=[], schedules=[]):
|
||||||
self.forename = forename
|
self.forename = forename
|
||||||
self.surname = surname
|
self.surname = surname
|
||||||
self.membershipWorksID = membershipWorksID
|
self.membershipWorksID = membershipWorksID
|
||||||
@ -36,50 +37,79 @@ Levels: {self.levels}
|
|||||||
Schedules: {self.schedules}
|
Schedules: {self.schedules}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class MembershipworksMember(Member):
|
||||||
|
def __init__(self, data):
|
||||||
|
super().__init__(data["First Name"],
|
||||||
|
data["Last Name"],
|
||||||
|
membershipWorksID=data["Account ID"],
|
||||||
|
email=data["Email"],
|
||||||
|
phone=data["Phone"],
|
||||||
|
onHold=data["Account on Hold"] != "")
|
||||||
|
|
||||||
|
if data["Access Card Number"] != "":
|
||||||
|
self.credentials = set([Credential(
|
||||||
|
code=(data["Access Card Facility Code"],
|
||||||
|
data["Access Card Number"]))])
|
||||||
|
else:
|
||||||
|
self.credentials = set()
|
||||||
|
|
||||||
|
levels = {k: v for k, v in memberLevels.items() if data[k] == k}
|
||||||
|
self.levels = list(levels.keys())
|
||||||
|
self.schedules = list(levels.values())
|
||||||
|
|
||||||
|
self.extraLevels = {
|
||||||
|
schedule: sum((doors for prop, doors in props.items()
|
||||||
|
if data[prop] == "Y"),
|
||||||
|
[])
|
||||||
|
for schedule, props in doorSpecificSchedules.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.doorAccess = [
|
||||||
|
door for door, doorData in doors.items()
|
||||||
|
if data["Access " + doorData.access + "?"] == "Y"
|
||||||
|
]
|
||||||
|
|
||||||
|
def to_DoorMember(self, door):
|
||||||
|
doorLevels = [k for k, v in self.extraLevels.items() if door.name in v]
|
||||||
|
|
||||||
|
if door.name not in self.doorAccess or self.onHold:
|
||||||
|
schedules = []
|
||||||
|
else:
|
||||||
|
schedules = self.schedules + doorLevels
|
||||||
|
|
||||||
|
dm = DoorMember(door, **self.__dict__) # this is probably terrible
|
||||||
|
dm.levels = self.levels + doorLevels
|
||||||
|
dm.schedules = schedules
|
||||||
|
|
||||||
|
return dm
|
||||||
|
|
||||||
|
|
||||||
|
class DoorMember(Member):
|
||||||
|
def __init__(self, door, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.door = door
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_cardholder(cls, data):
|
def from_cardholder(cls, data, door):
|
||||||
# TODO: maybe keep all attribs, and just add them in
|
ch = cls(door=door,
|
||||||
# from_MembershipWorks?
|
|
||||||
credentials = set(
|
|
||||||
Credential(hex=(c.attrib['rawCardNumber']))
|
|
||||||
for c in data.findall('{*}Credential'))
|
|
||||||
roles = [r.attrib['scheduleName'] for r in data.findall('{*}Role')]
|
|
||||||
levels = data.attrib.get('custom1', "").split('|')
|
|
||||||
return cls(
|
|
||||||
forename=data.get('forename', ""),
|
forename=data.get('forename', ""),
|
||||||
surname=data.get('surname', ""),
|
surname=data.get('surname', ""),
|
||||||
membershipWorksID=data.attrib.get('custom2', ""),
|
membershipWorksID=data.attrib.get('custom2', ""),
|
||||||
middleName=data.attrib.get('middleName', ""),
|
middleName=data.attrib.get('middleName', ""),
|
||||||
email=data.attrib.get('email', ""),
|
email=data.attrib.get('email', ""),
|
||||||
phone=data.attrib.get('phone', ""),
|
phone=data.attrib.get('phone', ""),
|
||||||
cardholderID=data.attrib['cardholderID'],
|
cardholderID=data.attrib['cardholderID'])
|
||||||
credentials=credentials,
|
|
||||||
levels=levels,
|
|
||||||
schedules=roles)
|
|
||||||
|
|
||||||
@classmethod
|
ch.credentials = set(
|
||||||
def from_MembershipWorks(cls, data):
|
Credential(hex=(c.attrib['rawCardNumber']))
|
||||||
if data["Access Card Number"] != "":
|
for c in data.findall('{*}Credential'))
|
||||||
credentials = set([Credential(
|
|
||||||
code=(data["Access Card Facility Code"],
|
|
||||||
data["Access Card Number"]))])
|
|
||||||
else:
|
|
||||||
credentials = set()
|
|
||||||
|
|
||||||
levels = {k: v for k, v in memberLevels.items() if data[k] == k}
|
ch.levels = data.attrib.get('custom1', "").split('|')
|
||||||
doorAccess = [door for door, doorData in doors.items()
|
ch.schedules = [r.attrib['scheduleName']
|
||||||
if data["Access " + doorData.access + "?"] == "Y"]
|
for r in data.findall('{*}Role')]
|
||||||
|
|
||||||
return cls(data["First Name"],
|
return ch
|
||||||
data["Last Name"],
|
|
||||||
membershipWorksID=data["Account ID"],
|
|
||||||
email=data["Email"],
|
|
||||||
phone=data["Phone"],
|
|
||||||
doorAccess=doorAccess,
|
|
||||||
onHold=data["Account on Hold"] != "",
|
|
||||||
credentials=credentials,
|
|
||||||
levels=list(levels.keys()),
|
|
||||||
schedules=list(levels.values()))
|
|
||||||
|
|
||||||
def attribs(self):
|
def attribs(self):
|
||||||
return {"forename": self.forename,
|
return {"forename": self.forename,
|
||||||
@ -90,18 +120,12 @@ Schedules: {self.schedules}
|
|||||||
"custom1": "|".join(self.levels).replace("&", "and"),
|
"custom1": "|".join(self.levels).replace("&", "and"),
|
||||||
"custom2": self.membershipWorksID}
|
"custom2": self.membershipWorksID}
|
||||||
|
|
||||||
def schedulesForDoor(self, door):
|
def make_schedules(self, schedulesMap):
|
||||||
if door.name not in self.doorAccess or self.onHold:
|
|
||||||
return []
|
|
||||||
else:
|
|
||||||
return self.schedules
|
|
||||||
|
|
||||||
def make_schedules(self, door, schedulesMap):
|
|
||||||
roles = [
|
roles = [
|
||||||
E.Role({"roleID": self.cardholderID,
|
E.Role({"roleID": self.cardholderID,
|
||||||
"scheduleID": schedulesMap[schedule],
|
"scheduleID": schedulesMap[schedule],
|
||||||
"resourceID": "0"})
|
"resourceID": "0"})
|
||||||
for schedule in self.schedulesForDoor(door)]
|
for schedule in self.schedules]
|
||||||
|
|
||||||
return E.RoleSet({"action": "UD", "roleSetID": self.cardholderID},
|
return E.RoleSet({"action": "UD", "roleSetID": self.cardholderID},
|
||||||
E.Roles(*roles))
|
E.Roles(*roles))
|
||||||
@ -122,14 +146,15 @@ Schedules: {self.schedules}
|
|||||||
def update_door(door, members):
|
def update_door(door, members):
|
||||||
cardFormats = door.get_cardFormats()
|
cardFormats = door.get_cardFormats()
|
||||||
cardholders = {member.membershipWorksID: member
|
cardholders = {member.membershipWorksID: member
|
||||||
for member in [Member.from_cardholder(ch)
|
for member in [DoorMember.from_cardholder(ch, door)
|
||||||
for ch in door.get_cardholders()]}
|
for ch in door.get_cardholders()]}
|
||||||
schedulesMap = door.get_scheduleMap()
|
schedulesMap = door.get_scheduleMap()
|
||||||
allCredentials = set(Credential(hex=c.attrib['rawCardNumber'])
|
allCredentials = set(Credential(hex=c.attrib['rawCardNumber'])
|
||||||
for c in door.get_credentials())
|
for c in door.get_credentials())
|
||||||
|
|
||||||
# TODO: can I combine requests?
|
# TODO: can I combine requests?
|
||||||
for member in members:
|
for membershipworksMember in members:
|
||||||
|
member = membershipworksMember.to_DoorMember(door)
|
||||||
# cardholder exists, compare contents
|
# cardholder exists, compare contents
|
||||||
if member.membershipWorksID in cardholders:
|
if member.membershipWorksID in cardholders:
|
||||||
ch = cardholders.pop(member.membershipWorksID)
|
ch = cardholders.pop(member.membershipWorksID)
|
||||||
@ -150,8 +175,8 @@ def update_door(door, members):
|
|||||||
oldCards = ch.credentials
|
oldCards = ch.credentials
|
||||||
newCards = member.credentials
|
newCards = member.credentials
|
||||||
|
|
||||||
allNewCards = set(card
|
allNewCards = set(
|
||||||
for m in members if m != member
|
card for m in members if m != membershipworksMember
|
||||||
for card in m.credentials)
|
for card in m.credentials)
|
||||||
|
|
||||||
# cards removed, and won't be reassigned to someone else
|
# cards removed, and won't be reassigned to someone else
|
||||||
@ -164,6 +189,9 @@ def update_door(door, members):
|
|||||||
|
|
||||||
if newCards - oldCards: # cards added
|
if newCards - oldCards: # cards added
|
||||||
for card in newCards & allNewCards: # new card exists in another member
|
for card in newCards & allNewCards: # new card exists in another member
|
||||||
|
print([m for m in members
|
||||||
|
for card in m.credentials
|
||||||
|
if card in newCards])
|
||||||
raise Exception(f"Duplicate Card in input data! {card}")
|
raise Exception(f"Duplicate Card in input data! {card}")
|
||||||
|
|
||||||
# card existed in door, and needs to be reassigned
|
# card existed in door, and needs to be reassigned
|
||||||
@ -179,12 +207,12 @@ def update_door(door, members):
|
|||||||
door.doXMLRequest(ROOT(member.make_credentials(
|
door.doXMLRequest(ROOT(member.make_credentials(
|
||||||
newCards - allCredentials, cardFormats)))
|
newCards - allCredentials, cardFormats)))
|
||||||
|
|
||||||
if member.schedulesForDoor(door) != ch.schedules:
|
if member.schedules != ch.schedules:
|
||||||
print("- Updating schedule for" +
|
print("- Updating schedule for" +
|
||||||
f" {member.forename} {member.surname}:" +
|
f" {member.forename} {member.surname}:" +
|
||||||
f" {ch.schedules} -> {member.schedulesForDoor(door)}")
|
f" {ch.schedules} -> {member.schedules}")
|
||||||
door.doXMLRequest(ROOT(
|
door.doXMLRequest(ROOT(
|
||||||
member.make_schedules(door, schedulesMap)))
|
member.make_schedules(schedulesMap)))
|
||||||
else: # cardholder did not exist, so add them
|
else: # cardholder did not exist, so add them
|
||||||
print(f"- Adding Member:")
|
print(f"- Adding Member:")
|
||||||
print(member)
|
print(member)
|
||||||
@ -196,7 +224,7 @@ def update_door(door, members):
|
|||||||
.attrib["cardholderID"]
|
.attrib["cardholderID"]
|
||||||
door.doXMLRequest(ROOT(
|
door.doXMLRequest(ROOT(
|
||||||
member.make_credentials(member.credentials, cardFormats),
|
member.make_credentials(member.credentials, cardFormats),
|
||||||
member.make_schedules(door, schedulesMap)))
|
member.make_schedules(schedulesMap)))
|
||||||
|
|
||||||
# TODO: delete cardholders that are no longer members?
|
# TODO: delete cardholders that are no longer members?
|
||||||
|
|
||||||
@ -204,9 +232,9 @@ def update_door(door, members):
|
|||||||
def main():
|
def main():
|
||||||
memberData = getMembershipworksData(
|
memberData = getMembershipworksData(
|
||||||
['members', 'staff', 'misc'],
|
['members', 'staff', 'misc'],
|
||||||
"_id,nam,phn,eml,lvl,lbl,xws,xms,xsc,xas,xfd,xac,xcf")
|
"_id,nam,phn,eml,lvl,lbl,xws,xms,xsc,xas,xfd,xac,xcf,xeh,xse")
|
||||||
|
|
||||||
members = [Member.from_MembershipWorks(m) for m in memberData]
|
members = [MembershipworksMember(m) for m in memberData]
|
||||||
|
|
||||||
for door in doors.values():
|
for door in doors.values():
|
||||||
print(door.name, door.ip)
|
print(door.name, door.ip)
|
||||||
|
Loading…
Reference in New Issue
Block a user