def update_notes(): """ Updates the notes field of a specific record ; redirects to GET /hours """ ####################################################### # get index of completed record # TODO: ensure this is good, in case of template changes index = int(bottle.request.forms.get("index")) name = Cookies.get.name(bottle.request) date = Cookies.get.date(bottle.request) # TODO: again, check after template changes newNotes = bottle.request.forms.get("notesDisplay") # replace <br> with " " in case of enter button being pressed newNotes = newNotes.replace("<br>", " ").strip() # set the anchor cookie ; if the record's notes are successfully changed, it gets deleted Cookies.set.anchor(bottle.response, index) ####################################################### if newNotes: records = recorder.parseRecordsFromFile(name, date) records[index].notes = newNotes recorder.writeRecords(name, date, records) # delete cookie if task completed Cookies.delete.anchor(bottle.response) ####################################################### bottle.redirect('hours')
def delete_single_record(): """ Deletes one record from the list of records currently displayed at GET /hours ; redirects to GET /hours """ ####################################################### # get index based on which delete button was clicked / which form was submitted # TODO: ensure this is good, in case of template changes index = int(bottle.request.forms.get('index')) # get name and date cookies name = Cookies.get.name(bottle.request) date = Cookies.get.date(bottle.request) # read and parse records from file records = recorder.parseRecordsFromFile(name, date) ####################################################### # upon redirect, anchor to where the record was deleted # open that form and insert notes from the deleted record Cookies.set.anchor(bottle.response, index) Cookies.set.notes(bottle.response, records[index].notes) ####################################################### # delete record del records[index] # write back updated records recorder.writeRecords(name, date, records) ####################################################### bottle.redirect('hours')
def hours(): """ GET /hours ; Main page ; serves up base template ; other routes redirect to here 1) check for message from logging server 2) get and manipulate cookies 3) get subtotal and total 4) read records from file 5) return base template with appropriate values passed to it """ # used to send all the required data to the template in one variable TEMPLATE = argparse.Namespace() ####################################################### # if an encrypted message was returned from the logging server, get it and decrypt it TEMPLATE.msg = "" # try: # TEMPLATE.msg = crypto.getDecodedString(bottle.request.query['msg']) # except KeyError: # pass ####################################################### # get name and date cookies TEMPLATE.name = Cookies.get.name(bottle.request) TEMPLATE.date = Cookies.get.date(bottle.request) # get anchor cookie in case a record has been just edited TEMPLATE.anchor = Cookies.get.anchor(bottle.request) # delete anchor cookie after getting it Cookies.delete.anchor(bottle.response) # get notes cookie ; in the case a record was just deleted, this cookie tracks the notes the record had TEMPLATE.notes = Cookies.get.notes(bottle.request) # delete notes cookie after getting it Cookies.delete.notes(bottle.response) ####################################################### # parses through the saved records and counts the hours worked for the day / pay period TEMPLATE.subtotal = recorder.getSubtotalForDay(TEMPLATE.name, TEMPLATE.date) TEMPLATE.total = recorder.getTotalForPayPeriod(TEMPLATE.name, TEMPLATE.date) ####################################################### # try to read and parse saved records corresponding to the name and date provided TEMPLATE.records = recorder.parseRecordsFromFile(TEMPLATE.name, TEMPLATE.date) ####################################################### # additional data to send to template TEMPLATE.month = recorder.getPayPeriodMonth(TEMPLATE.date) TEMPLATE.LABELS = ENV.LABELS TEMPLATE.SENDER = ENV.SENDER TEMPLATE.RECEIVERS = ENV.RECEIVERS TEMPLATE.LOGGING_SERVER_ADDRESS = ENV.LOGGING_SERVER_ADDRESS TEMPLATE.LOGGING_SERVER_PORT = ENV.LOGGING_SERVER_PORT ####################################################### return bottle.template('hours', DATA=TEMPLATE)
def email_records(): """ Sends an SMTP email from ENV.SENDER to ENV.RECEIVERS containing records pulled with the name and date cookies ; redirects to GET /hours """ ####################################################### # get name and date cookies name = Cookies.get.name(bottle.request) date = Cookies.get.date(bottle.request) # sets flag based on user's confirmation / denial from popup alert emailConfirm = (bottle.request.forms.get("emailConfirm") == "true") ####################################################### total = recorder.getTotalForPayPeriod(name, date) ####################################################### currentTimeShort = time.strftime("%m/%d") subject = "Hours {0} (Total: {1})".format(currentTimeShort, str(total)) ####################################################### if all((emailConfirm, name, ENV.SENDER, ENV.RECEIVERS)): # get records corresponding to name and date records = recorder.parseRecordsFromFile(name, date) #TODO: check to make sure this works instead of the old commented line (make sure "\n" isn't needed at the end) body = "\n".join([record.emailFormat() for record in records]) # for record in records: # body += record.emailFormat() + "\n" # message = "Subject: %s\n\n%s" % (subject, body) # TODO: check to make sure this works instead of the old commented line message = "Subject: {subject}\n\n{body}".format(subject=subject, body=body) try: mail = smtplib.SMTP(ENV.HOST) mail.sendmail(ENV.SENDER, ENV.RECEIVERS, message) mail.quit() # if in debug mode, print info on successful email if ENV.DEBUG: cp.printOk("SENDER: {sender}".format(sender=ENV.SENDER)) cp.printOk("RECEIVERS: {receivers}".format(receivers=ENV.RECEIVERS)) cp.printOk("-- MESSAGE --\n{message}".format(message=message)) except smtplib.SMTPException as e: # TODO: make sure this works cp.printFail(e) bottle.redirect('hours')
def send_records(): """ Uses modu.crypto to encrypt and send records pulled with the name and date cookies ; redirects to GET /hours """ # TODO: test this ; not sure if it's going to be used ####################################################### name = Cookies.get.name(bottle.request) date = Cookies.get.date(bottle.request) confirm = (bottle.request.forms.get('confirm') == "true") # will use form-supplied values but defaults to values read from config file address = bottle.request.forms.get('address').strip() or ENV.LOGGING_SERVER_ADDRESS port = bottle.request.forms.get('port').strip() or ENV.LOGGING_SERVER_PORT ####################################################### if all((confirm, name, address, port)): # parse records from file records = recorder.parseRecordsFromFile(name, date) # turn records into a '\n'-separated string recordString = '\n'.join([r.emailFormat() for r in records]) # if in debug mode and about to send records, display info if ENV.DEBUG: cp.printOk("SENDING TO: {0}:{1}".format(address, port)) cp.printOK("-- RECORDS --\n{records}".format(records=recordString)) try: # encrypt and encode recordString encryptedRecords = crypto.getEncodedString(recordString) address = "/".join(bottle.request.url.split("/")[:-1]) + "/ack" # encrypts and encodes host address for rerouting back to hours encryptedAddress = crypto.getEncodedString(address) # send name, date, and encoded records to receiving server bottle.redirect('http://{address}:{port}/receive?n={name}&d={date}&r={encryptedRecords}&a={encryptedAddress}' .format(address=address, port=port, name=name, date=date, encryptedRecords=encryptedRecords, encryptedAddress=encryptedAddress)) # thrown if config/crypto not found # TODO: is this error too specific or even accurate? except TypeError as e: cp.printFail(e) bottle.redirect('hours')
def complete_end_time(): """ Completes a pending record by supplying an end time ; redirects to GET /hours """ ####################################################### # get index of completed record # TODO: ensure this is good, in case of template changes index = int(bottle.request.forms.get('index')) # get the submitted end time (which has already been pattern matched) OR get current rounded time # TODO: ensure this is good, in case of template changes end = recorder.parseTime(bottle.request.forms.get('completeEnd')) or recorder.getCurrentRoundedTime() # get name and date cookies name = Cookies.get.name(bottle.request) date = Cookies.get.date(bottle.request) # set the anchor cookie ; if the record is successfully completed, it gets deleted Cookies.set.anchor(bottle.response, index) ####################################################### # get records from file records = recorder.parseRecordsFromFile(name, date) # get particular record to complete record = records[index] # set the end time - THEN check if it is valid record.setEnd(end) # don't accept an invalid or invalidly placed record if recorder.checkIfValid(records, record, index): if not record.durationLocked: # calculate and set duration record.calculateAndSetDuration() # write back record records[index] = record recorder.writeRecords(name, date, records) # delete cookie if task completed Cookies.delete.anchor(bottle.response) ####################################################### bottle.redirect('hours')
def toggle_emergency(): """ Toggles a single record's Y/N emergency field ; writes to file ; redirects to GET /hours """ ############################################## name = Cookies.get.name(bottle.request) date = Cookies.get.date(bottle.request) ############################################## # TODO: ensure this is good, in case of template changes index = int(bottle.request.forms.get('index')) # don't delete on task completion (stay anchored to edited record to easily view the change) Cookies.set.anchor(bottle.response, index) ############################################## records = recorder.parseRecordsFromFile(name, date) records[index].emergency = "N" if records[index].emergency == "Y" else "Y" recorder.writeRecords(name, date, records) bottle.redirect('hours')
def hours_post(): """ POST /hours ; Main page post ; parses form data ; reads/writes files ; redirects to GET /hours Extra explicit because of logical complexity 1) get name, date, and index from the request 2) parse the new record from the request 3) read records on file for the user and date 4) using the name and date cookies, check if the user has seen any records for that date 5) check that the new record is valid and fits in the record list at the index and that no records have gone unseen if true: 5.1) insert the new record at the index 5.2) adjust adjacent records 5.3) write the new record list to file 5.4) delete the anchor cookie (successful POST means anchor back to the top of the page) 5.5) delete the notes cookies (since it pertains to the inserted record only) else: 5.1) save the index as the anchor cookie for GET /hours 5.2) save the new (invalid) record's notes to a cookie 6) set the name and date cookies to the values pulled from the request 7) redirect to GET /hours """ ####################################################### # name of user name = bottle.request.forms.get("name").strip() # date : either picked by user or default today date = Cookies.get.date(bottle.request) # index for inserting new Record into the list of records index = int(bottle.request.forms.get("index")) ####################################################### # parses form data and returns a Record obj newRecord = recorder.parseRecordFromHTML(bottle.request) # reads and parses Records on file records = recorder.parseRecordsFromFile(name, date) ####################################################### # if the name cookie is equal to the name pulled from the request # and the date cookie is equal to the date pulled from the request # then any records on file have already been pulled, and it is safe to insert into the list recordsPulled = ((Cookies.get.name(bottle.request) == name) and (Cookies.get.date(bottle.request) == date)) # checks if newRecord.start < newRecord.end # and that newRecord doesn't exceed the outer limits of adjacent records # also ensures that the user has seen any records that exist if recorder.checkIfValid(records, newRecord, index) and (recordsPulled or not records): # insert new record at index provided from template form records.insert(index, newRecord) # adjust timings of adjacent records in case of overlap recorder.adjustAdjacentRecords(records, index) # write back updated list recorder.writeRecords(name, date, records) # after posting a new record, delete the anchor and notes cookies Cookies.delete.anchor(bottle.response) Cookies.delete.notes(bottle.response) else: # if the record was invalid # or there were records the user hadn't seen # then save the index to anchor to the correct record form upon reload # and save the notes to avoid retyping Cookies.set.anchor(bottle.response, index) Cookies.set.notes(bottle.response, newRecord.notes) ####################################################### # set name cookie with most recently used name (for insurance mostly) Cookies.set.name(bottle.response, name) Cookies.set.date(bottle.response, date) bottle.redirect('hours')