From 25532bf21b9cab12410b56a512e16c3a94d1ed3d Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Wed, 18 Dec 2019 18:47:36 -0500 Subject: [PATCH] Refactor doorUpdater to add support for door specific schedules --- common.py | 1 + config.yaml | 12 +++++ doorUpdater.py | 144 +++++++++++++++++++++++++++++-------------------- 3 files changed, 99 insertions(+), 58 deletions(-) diff --git a/common.py b/common.py index 7aa1861..8fc469c 100644 --- a/common.py +++ b/common.py @@ -26,6 +26,7 @@ doors = {doorName: DoorController(doorData['ip'], for doorName, doorData in config["doors"].items()} memberLevels = config['memberLevels'] +doorSpecificSchedules = config['doorSpecificSchedules'] def getMembershipworksData(folders, columns): """ Pull the members csv from the membershipworks api diff --git a/config.yaml b/config.yaml index 1f91825..5375051 100644 --- a/config.yaml +++ b/config.yaml @@ -14,3 +14,15 @@ memberLevels: CMS Unlimited: Unlimited CMS Nights & Weekends: Nights and Weekends 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 diff --git a/doorUpdater.py b/doorUpdater.py index 70f6298..fc98a12 100755 --- a/doorUpdater.py +++ b/doorUpdater.py @@ -1,15 +1,16 @@ #!/usr/bin/env python3 -from common import doors, getMembershipworksData, memberLevels +from common import (doors, getMembershipworksData, memberLevels, + doorSpecificSchedules) from hid.Credential import Credential from hid.DoorController import ROOT, E class Member(): - def __init__(self, forename, surname, membershipWorksID, + def __init__(self, forename="", surname="", membershipWorksID="", middleName="", email="", phone="", cardholderID=None, doorAccess=[], onHold=False, - credentials=set(), levels=[], schedules=[]): + credentials=set(), levels=[], extraLevels=[], schedules=[]): self.forename = forename self.surname = surname self.membershipWorksID = membershipWorksID @@ -36,50 +37,79 @@ Levels: {self.levels} Schedules: {self.schedules} """ - @classmethod - def from_cardholder(cls, data): - # TODO: maybe keep all attribs, and just add them in - # 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', ""), - surname=data.get('surname', ""), - membershipWorksID=data.attrib.get('custom2', ""), - middleName=data.attrib.get('middleName', ""), - email=data.attrib.get('email', ""), - phone=data.attrib.get('phone', ""), - cardholderID=data.attrib['cardholderID'], - credentials=credentials, - levels=levels, - schedules=roles) - @classmethod - def from_MembershipWorks(cls, data): +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"] != "": - credentials = set([Credential( + self.credentials = set([Credential( code=(data["Access Card Facility Code"], data["Access Card Number"]))]) else: - credentials = set() + self.credentials = set() levels = {k: v for k, v in memberLevels.items() if data[k] == k} - doorAccess = [door for door, doorData in doors.items() - if data["Access " + doorData.access + "?"] == "Y"] + self.levels = list(levels.keys()) + self.schedules = list(levels.values()) - return cls(data["First Name"], - 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())) + 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 + def from_cardholder(cls, data, door): + ch = cls(door=door, + forename=data.get('forename', ""), + surname=data.get('surname', ""), + membershipWorksID=data.attrib.get('custom2', ""), + middleName=data.attrib.get('middleName', ""), + email=data.attrib.get('email', ""), + phone=data.attrib.get('phone', ""), + cardholderID=data.attrib['cardholderID']) + + ch.credentials = set( + Credential(hex=(c.attrib['rawCardNumber'])) + for c in data.findall('{*}Credential')) + + ch.levels = data.attrib.get('custom1', "").split('|') + ch.schedules = [r.attrib['scheduleName'] + for r in data.findall('{*}Role')] + + return ch def attribs(self): return {"forename": self.forename, @@ -90,18 +120,12 @@ Schedules: {self.schedules} "custom1": "|".join(self.levels).replace("&", "and"), "custom2": self.membershipWorksID} - def schedulesForDoor(self, door): - if door.name not in self.doorAccess or self.onHold: - return [] - else: - return self.schedules - - def make_schedules(self, door, schedulesMap): + def make_schedules(self, schedulesMap): roles = [ E.Role({"roleID": self.cardholderID, "scheduleID": schedulesMap[schedule], "resourceID": "0"}) - for schedule in self.schedulesForDoor(door)] + for schedule in self.schedules] return E.RoleSet({"action": "UD", "roleSetID": self.cardholderID}, E.Roles(*roles)) @@ -122,14 +146,15 @@ Schedules: {self.schedules} def update_door(door, members): cardFormats = door.get_cardFormats() 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()]} schedulesMap = door.get_scheduleMap() allCredentials = set(Credential(hex=c.attrib['rawCardNumber']) for c in door.get_credentials()) # TODO: can I combine requests? - for member in members: + for membershipworksMember in members: + member = membershipworksMember.to_DoorMember(door) # cardholder exists, compare contents if member.membershipWorksID in cardholders: ch = cardholders.pop(member.membershipWorksID) @@ -150,9 +175,9 @@ def update_door(door, members): oldCards = ch.credentials newCards = member.credentials - allNewCards = set(card - for m in members if m != member - for card in m.credentials) + allNewCards = set( + card for m in members if m != membershipworksMember + for card in m.credentials) # cards removed, and won't be reassigned to someone else for card in (oldCards - newCards) - allNewCards: @@ -164,6 +189,9 @@ def update_door(door, members): if newCards - oldCards: # cards added 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}") # card existed in door, and needs to be reassigned @@ -179,12 +207,12 @@ def update_door(door, members): door.doXMLRequest(ROOT(member.make_credentials( newCards - allCredentials, cardFormats))) - if member.schedulesForDoor(door) != ch.schedules: + if member.schedules != ch.schedules: print("- Updating schedule for" + f" {member.forename} {member.surname}:" + - f" {ch.schedules} -> {member.schedulesForDoor(door)}") + f" {ch.schedules} -> {member.schedules}") door.doXMLRequest(ROOT( - member.make_schedules(door, schedulesMap))) + member.make_schedules(schedulesMap))) else: # cardholder did not exist, so add them print(f"- Adding Member:") print(member) @@ -196,7 +224,7 @@ def update_door(door, members): .attrib["cardholderID"] door.doXMLRequest(ROOT( member.make_credentials(member.credentials, cardFormats), - member.make_schedules(door, schedulesMap))) + member.make_schedules(schedulesMap))) # TODO: delete cardholders that are no longer members? @@ -204,9 +232,9 @@ def update_door(door, members): def main(): memberData = getMembershipworksData( ['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(): print(door.name, door.ip)