def parse_rows(rows, select_project=None): """Parse quota update data from the spreadsheet into a dictionary Expects 'rows' to include all rows, with row 0 being the header row Returns a dictionary of projects/quotas. """ # NOTE: entry[17] is a required field in the Google Form, so it is safe # to assume entry[0:16] exists. # Column index in the Google Sheet for project name # This may need to be updated if question order on the form is changed PROJECT_COLUMN = 8 project_list = [] if select_project: try: rows = select_rows(select_project, PROJECT_COLUMN, rows) if len(rows) > 2: print ("WARNING: Multiple requests found for project {}. All " "{} requests will be processed. You may need to close " "multiple tickets.").format(args.project, len(rows) - 1) except ValueError as ve: raise argparse.ArgumentError(None, ve.message) else: rows = enumerate(rows) for idx, entry in rows: # ignore row 0 (the header row) and blank rows if (idx == 0) or (entry == []): continue # skip rows that have not been through approval/notification elif (entry[0].lower().strip() != 'approved') or (entry[1] == ''): # entry[0] is Approved # entry[1] is Helpdesk Notified continue else: project = dict() # entry [2] is Reminder Sent # entry [3] is Timestamp project['email'] = entry[4].replace(u'\xa0', ' ').strip() project['user_fullname'] = entry[5] + ' ' + entry[6] # entry[7] is organization project['name'] = entry[8] # entry[9] is Type of Increase # entry[10] is End Date quotas = {'instances': entry[11], 'cores': entry[12], 'ram': entry[13], 'floatingip': entry[14], 'network': entry[15], 'port': entry[16], 'volumes': entry[17], 'snapshots': entry[18], 'gigabytes': entry[19]} unchanged_quotas = [q for q in quotas if quotas[q] == ''] for quota_name in unchanged_quotas: del quotas[quota_name] for quota_name, value in quotas.iteritems(): quotas[quota_name] = int(value) # OpenStack wants the RAM quota in MB, but the form requests it in # GB so the users aren't confused by multiplying by 1000 vs. 1024 if 'ram' in quotas: quotas['ram'] = quotas['ram'] * 1024 project['quotas'] = quotas # entry[20] is Comments - required field # entry[21] is MOC Notes - not required project['row'] = idx project_list.append(project) if not project_list: raise NoApprovedRequests(row_filter=select_project) return project_list
def parse_rows(rows, select_user=None): """Parse spreadsheet user/project data into User and Project classes Expects 'rows' to include all rows, with row 0 being the header row Returns a dictionary of projects keyed by project name, and a list of rows that were not blank but failed to parse correctly. Select_user allows caller to handle requests from one user only. """ # Column index in the Google Sheet for username # This may need to be updated if question order on the form is changed USER_COLUMN = 3 projects = {} bad_rows = [] if select_user: try: rows = select_rows(select_user, USER_COLUMN, rows) if len(rows) > 2: print( "WARNING: Multiple requests found for user {}. All {} " "requests will be processed. You may need to close " "multiple tickets.").format(args.user, len(rows) - 1) except ValueError as ve: raise argparse.ArgumentError(None, ve.message) else: rows = enumerate(rows) for idx, entry in rows: # ignore row 0 (the header row) and blank rows if (idx == 0) or (entry == []): continue elif (entry[0].lower().strip() != 'approved') or (entry[1] == ''): # Don't process requests that haven't gone through the # approval/notification process yet # entry[0] is Approved, entry[1] is Helpdesk Notified bad_rows.append((idx, ("Approval/Notification " "Incomplete: {}").format(entry[3]))) continue try: # entry[2] is Timestamp email = entry[3].replace(u'\xa0', ' ').strip() user_info = { 'user_name': email, 'email': email, 'first_name': entry[4], 'last_name': entry[5] } if entry[6] == 'No': user_info.update({ 'is_new': True, 'org': entry[7], 'role': entry[8], 'phone': entry[9], 'sponsor': entry[10], 'pin': entry[11], 'comment': entry[12] }) # entry[13] asks whether a new or existing # project = only used for form navigation # FIXME: add option to choose "no project" # for teams who sign up for a new project # together? user = User(row=idx, **user_info) if entry[14] == "": # the user chose to join an existing project # info in entry[17] to entry[19] project_name = entry[17] if project_name not in projects: project = Project(row=idx, name=project_name, contact_name=entry[18], contact_email=entry[19]) projects[project.name] = project projects[project_name].users.append(user) elif entry[14] in projects: # FIXME: # This should probably raise an error of some sort. It # covers 2 weird edge cases, either: # a) the project exists, another user from this batch # asked to be added to it # b) project doesn't exist, but another user from this # batch requested a new project with this name. # For now, while we get stuff working, just assume they are # the same project. projects[entry[14]].users.append(user) else: # a new project was requested - info in entry[14] to entry[16] project = Project(row=idx, name=entry[14], contact_name=user.first_name + " " + user.last_name, contact_email=user.email, description=entry[15], is_new=True) project.users.append(user) try: for add_user in entry[16].split(','): add_user = add_user.strip() existing_user = User(row=idx, user_name=add_user, email=add_user, is_requestor=False) project.users.append(existing_user) except IndexError: # entry[16] is the last possible filled cell in a new # project entry, so if it was left blank it's not there # FIXME by changing field order on the forms? pass except: # If the user typed something in this box but didn't follow # instructions print( "WARNING: unable to add users for project {} from " "input: {}.\nIf this is a valid request, add these " "users manually.").format(entry[14], entry[16]) projects[project.name] = project except IndexError: # Somehow a required field is blank bad_rows.append((idx, "Missing Required Field")) return projects, bad_rows