def get_onlineform_html(dbo, formid, completedocument=True): """ Get the selected online form as HTML """ h = [] l = dbo.locale form = db.query(dbo, "SELECT * FROM onlineform WHERE ID = %d" % formid)[0] if completedocument: header = get_onlineform_header(dbo) h.append(header.replace("$$TITLE$$", form["NAME"])) h.append('<h2 class="asm-onlineform-title">%s</h2>' % form["NAME"]) h.append('<p class="asm-onlineform-description">%s</p>' % form["DESCRIPTION"]) h.append('<form action="%s/service" method="post">' % BASE_URL) h.append('<input type="hidden" name="method" value="online_form_post" />') h.append('<input type="hidden" name="account" value="%s" />' % dbo.alias) h.append('<input type="hidden" name="redirect" value="%s" />' % form["REDIRECTURLAFTERPOST"]) h.append('<input type="hidden" name="flags" value="%s" />' % form["SETOWNERFLAGS"]) h.append('<input type="hidden" name="formname" value="%s" />' % html.escape(form["NAME"])) h.append('<table width="100%" class="asm-onlineform-table">') for f in get_onlineformfields(dbo, formid): fname = f["FIELDNAME"] + "_" + str(f["ID"]) h.append('<tr class="asm-onlineform-tr">') h.append('<td class="asm-onlineform-td">') h.append('<label for="f%d">%s</label>' % (f["ID"], f["LABEL"])) h.append('</td>') h.append('<td class="asm-onlineform-td">') if f["FIELDTYPE"] == FIELDTYPE_YESNO: h.append('<select name="%s" title="%s"><option>%s</option><option>%s</option></select>' % \ ( html.escape(fname), utils.nulltostr(f["TOOLTIP"]), i18n._("No", l), i18n._("Yes", l))) elif f["FIELDTYPE"] == FIELDTYPE_TEXT: h.append('<input type="text" name="%s" title="%s" />' % (html.escape(fname), utils.nulltostr(f["TOOLTIP"]))) elif f["FIELDTYPE"] == FIELDTYPE_NOTES: h.append('<textarea name="%s" title="%s"></textarea>' % (html.escape(fname), utils.nulltostr(f["TOOLTIP"]))) elif f["FIELDTYPE"] == FIELDTYPE_LOOKUP: h.append('<select name="%s" title="%s">' % (html.escape(fname), utils.nulltostr(f["TOOLTIP"]))) for lv in utils.nulltostr(f["LOOKUPS"]).split("|"): h.append('<option>%s</option>' % lv) h.append('</select>') h.append('</td>') h.append('</tr>') h.append('</table>') h.append( '<p style="text-align: center"><input type="submit" value="Submit" /></p>' ) h.append('</form>') if completedocument: footer = get_onlineform_footer(dbo) h.append(footer.replace("$$TITLE$$", form["NAME"])) return "\n".join(h)
def get_active_users(dbo): """ Returns a list of active users on the system USERNAME, SINCE, MESSAGES """ cachekey = "%s_activity" % dbo.database return utils.nulltostr(cachemem.get(cachekey))
def update_user_activity(dbo, user, timenow = True): """ If timenow is True, updates this user's last activity time to now. If timenow is False, removes this user from the active list. """ if dbo is None or user is None: return cachekey = "%s_activity" % dbo.database ac = utils.nulltostr(cachemem.get(cachekey)) # Prune old activity and remove the current user nc = [] for a in ac.split(","): # If there are any errors reading or parsing # the entry, skip it try: if a != "": u, d = a.split("=") # if the last seen value was more than an hour ago, # don't bother adding that user p = i18n.parse_date("%Y-%m-%d %H:%M:%S", d) if i18n.subtract_hours(i18n.now(dbo.timezone), 1) > p: continue # Don't add the current user if u == user: continue nc.append(a) except: continue # Add this user with the new time if timenow: nc.append("%s=%s" % (user, i18n.format_date("%Y-%m-%d %H:%M:%S", i18n.now(dbo.timezone)))) cachemem.put(cachekey, ",".join(nc), 3600 * 8)
def update_user_activity(dbo, user, timenow=True): """ If timenow is True, updates this user's last activity time to now. If timenow is False, removes this user from the active list. """ if dbo is None or user is None: return cachekey = "%s_activity" % dbo.database ac = utils.nulltostr(cachemem.get(cachekey)) # Prune old activity and remove the current user nc = [] for a in ac.split(","): # If there are any errors reading or parsing # the entry, skip it try: if a != "": u, d = a.split("=") # if the last seen value was more than an hour ago, # don't bother adding that user p = i18n.parse_date("%Y-%m-%d %H:%M:%S", d) if i18n.subtract_hours(i18n.now(dbo.timezone), 1) > p: continue # Don't add the current user if u == user: continue nc.append(a) except: continue # Add this user with the new time if timenow: nc.append( "%s=%s" % (user, i18n.format_date("%Y-%m-%d %H:%M:%S", i18n.now( dbo.timezone)))) cachemem.put(cachekey, ",".join(nc), 3600 * 8)
def get_onlineform_html(dbo, formid, completedocument = True): """ Get the selected online form as HTML """ h = [] l = dbo.locale form = db.query(dbo, "SELECT * FROM onlineform WHERE ID = %d" % formid)[0] if completedocument: header = get_onlineform_header(dbo) h.append(header.replace("$$TITLE$$", form["NAME"])) h.append('<h2 class="asm-onlineform-title">%s</h2>' % form["NAME"]) h.append('<p class="asm-onlineform-description">%s</p>' % form["DESCRIPTION"]) h.append('<form action="%s/service" method="post">' % BASE_URL) h.append('<input type="hidden" name="method" value="online_form_post" />') h.append('<input type="hidden" name="account" value="%s" />' % dbo.alias) h.append('<input type="hidden" name="redirect" value="%s" />' % form["REDIRECTURLAFTERPOST"]) h.append('<input type="hidden" name="flags" value="%s" />' % form["SETOWNERFLAGS"]) h.append('<input type="hidden" name="formname" value="%s" />' % html.escape(form["NAME"])) h.append('<table width="100%" class="asm-onlineform-table">') for f in get_onlineformfields(dbo, formid): fname = f["FIELDNAME"] + "_" + str(f["ID"]) h.append('<tr class="asm-onlineform-tr">') h.append('<td class="asm-onlineform-td">') h.append('<label for="f%d">%s</label>' % ( f["ID"], f["LABEL"] )) h.append('</td>') h.append('<td class="asm-onlineform-td">') if f["FIELDTYPE"] == FIELDTYPE_YESNO: h.append('<select name="%s" title="%s"><option>%s</option><option>%s</option></select>' % \ ( html.escape(fname), utils.nulltostr(f["TOOLTIP"]), i18n._("No", l), i18n._("Yes", l))) elif f["FIELDTYPE"] == FIELDTYPE_TEXT: h.append('<input type="text" name="%s" title="%s" />' % ( html.escape(fname), utils.nulltostr(f["TOOLTIP"]))) elif f["FIELDTYPE"] == FIELDTYPE_NOTES: h.append('<textarea name="%s" title="%s"></textarea>' % ( html.escape(fname), utils.nulltostr(f["TOOLTIP"]))) elif f["FIELDTYPE"] == FIELDTYPE_LOOKUP: h.append('<select name="%s" title="%s">' % ( html.escape(fname), utils.nulltostr(f["TOOLTIP"]))) for lv in utils.nulltostr(f["LOOKUPS"]).split("|"): h.append('<option>%s</option>' % lv) h.append('</select>') h.append('</td>') h.append('</tr>') h.append('</table>') h.append('<p style="text-align: center"><input type="submit" value="Submit" /></p>') h.append('</form>') if completedocument: footer = get_onlineform_footer(dbo) h.append(footer.replace("$$TITLE$$", form["NAME"])) return "\n".join(h)
def animals_to_page(dbo, animals, style="", speciesid=0, animaltypeid=0, locationid=0): """ Returns a page of animals. animals: A resultset containing animal records style: The HTML publishing template to use speciesid: 0 for all species, or a specific one animaltypeid: 0 for all animal types or a specific one locationid: 0 for all internal locations or a specific one """ # Get the specified template head, body, foot = template.get_html_template(dbo, style) if head == "": head, body, foot = get_animal_view_template(dbo) # Substitute the header and footer tags org_tags = wordprocessor.org_tags(dbo, "system") head = wordprocessor.substitute_tags(head, org_tags, True, "$$", "$$") foot = wordprocessor.substitute_tags(foot, org_tags, True, "$$", "$$") # Run through each animal and generate body sections bodies = [] for a in animals: if speciesid > 0 and a.SPECIESID != speciesid: continue if animaltypeid > 0 and a.ANIMALTYPEID != animaltypeid: continue if locationid > 0 and a.SHELTERLOCATION != locationid: continue # Translate website media name to the service call for images if smcom.active(): a.WEBSITEMEDIANAME = "%s?account=%s&method=animal_image&animalid=%d" % ( SERVICE_URL, dbo.database, a.ID) else: a.WEBSITEMEDIANAME = "%s?method=animal_image&animalid=%d" % ( SERVICE_URL, a.ID) # Generate tags for this row tags = wordprocessor.animal_tags_publisher(dbo, a) tags = wordprocessor.append_tags(tags, org_tags) # Add extra tags for websitemedianame2-4 if they exist if a.WEBSITEIMAGECOUNT > 1: tags["WEBMEDIAFILENAME2"] = "%s&seq=2" % a.WEBSITEMEDIANAME if a.WEBSITEIMAGECOUNT > 2: tags["WEBMEDIAFILENAME3"] = "%s&seq=3" % a.WEBSITEMEDIANAME if a.WEBSITEIMAGECOUNT > 3: tags["WEBMEDIAFILENAME4"] = "%s&seq=4" % a.WEBSITEMEDIANAME # Set the description if configuration.publisher_use_comments(dbo): a.WEBSITEMEDIANOTES = a.ANIMALCOMMENTS # Add extra publishing text, preserving the line endings notes = utils.nulltostr(a.WEBSITEMEDIANOTES) notes += configuration.third_party_publisher_sig(dbo).replace( "\n", "<br/>") tags["WEBMEDIANOTES"] = notes bodies.append( wordprocessor.substitute_tags(body, tags, True, "$$", "$$")) return "%s\n%s\n%s" % (head, "\n".join(bodies), foot)
def substituteBodyTags(self, searchin, a): """ Substitutes any tags in the body for animal data """ tags = wordprocessor.animal_tags_publisher(self.dbo, a) tags["TotalAnimals"] = str(self.totalAnimals) tags["IMAGE"] = str(a["WEBSITEMEDIANAME"]) # Note: WEBSITEMEDIANOTES becomes ANIMALCOMMENTS in get_animal_data when publisher_use_comments is on notes = utils.nulltostr(a["WEBSITEMEDIANOTES"]) # Add any extra text notes += configuration.third_party_publisher_sig(self.dbo) # Preserve line endings in the bio notes = notes.replace("\n", "**le**") tags["WEBMEDIANOTES"] = notes output = wordprocessor.substitute_tags(searchin, tags, True, "$$", "$$") output = output.replace("**le**", "<br />") return output
def get_animal_view(dbo, animalid): """ Constructs the animal view page to the template. """ a = dbo.first_row( get_animal_data(dbo, animalid=animalid, include_additional_fields=True, strip_personal_data=True)) # If the animal is not adoptable, bail out if a is None: raise utils.ASMPermissionError("animal is not adoptable (None)") if not is_animal_adoptable(dbo, a): raise utils.ASMPermissionError("animal is not adoptable (False)") # If the option is on, use animal comments as the notes if configuration.publisher_use_comments(dbo): a.WEBSITEMEDIANOTES = a.ANIMALCOMMENTS head, body, foot = get_animal_view_template(dbo) if head == "": head = "<!DOCTYPE html>\n<html>\n<head>\n<title>$$SHELTERCODE$$ - $$ANIMALNAME$$</title></head>\n<body>" body = "<h2>$$SHELTERCODE$$ - $$ANIMALNAME$$</h2><p><img src='$$WEBMEDIAFILENAME$$'/></p><p>$$WEBMEDIANOTES$$</p>" foot = "</body>\n</html>" if smcom.active(): a.WEBSITEMEDIANAME = "%s?account=%s&method=animal_image&animalid=%d" % ( SERVICE_URL, dbo.database, animalid) else: a.WEBSITEMEDIANAME = "%s?method=animal_image&animalid=%d" % ( SERVICE_URL, animalid) s = head + body + foot tags = wordprocessor.animal_tags_publisher(dbo, a) tags = wordprocessor.append_tags(tags, wordprocessor.org_tags(dbo, "system")) # Add extra tags for websitemedianame2-10 if they exist for x in range(2, 11): if a.WEBSITEIMAGECOUNT > x - 1: tags["WEBMEDIAFILENAME%d" % x] = "%s&seq=%d" % (a.WEBSITEMEDIANAME, x) # Add extra publishing text, preserving the line endings notes = utils.nulltostr(a.WEBSITEMEDIANOTES) notes += configuration.third_party_publisher_sig(dbo) notes = notes.replace("\n", "**le**") tags["WEBMEDIANOTES"] = notes tags["WEBSITEMEDIANOTES"] = notes s = wordprocessor.substitute_tags(s, tags, True, "$$", "$$") s = s.replace("**le**", "<br />") return s
def get_onlineform_html(dbo, formid, completedocument = True): """ Get the selected online form as HTML """ h = [] l = dbo.locale form = db.query(dbo, "SELECT * FROM onlineform WHERE ID = %d" % formid)[0] formfields = get_onlineformfields(dbo, formid) # If a date field has been used, we need to pull jquery ui into the header # and have it applied to all date fields. needjqui = False for f in formfields: if f["FIELDTYPE"] == FIELDTYPE_DATE: needjqui = True if completedocument: header = get_onlineform_header(dbo) if needjqui: df = i18n.get_display_date_format(l) df = df.replace("%Y", "yy").replace("%m", "mm").replace("%d", "dd") header = header.replace("</head>", """ %s %s %s <script> $(document).ready(function() { $(".asm-onlineform-date").datepicker({ dateFormat: '%s' }); }); </script> </head>""" % (JQUERY_UI_CSS.replace("%(theme)s", "smoothness"), JQUERY_JS, JQUERY_UI_JS, df)) h.append(header.replace("$$TITLE$$", form["NAME"])) h.append('<h2 class="asm-onlineform-title">%s</h2>' % form["NAME"]) if form["DESCRIPTION"] is not None and form["DESCRIPTION"] != "": h.append('<p class="asm-onlineform-description">%s</p>' % form["DESCRIPTION"]) h.append(utils.nulltostr(form["HEADER"])) h.append('<form action="%s/service" method="post" accept-charset="utf-8">' % BASE_URL) h.append('<input type="hidden" name="method" value="online_form_post" />') h.append('<input type="hidden" name="account" value="%s" />' % dbo.alias) h.append('<input type="hidden" name="redirect" value="%s" />' % form["REDIRECTURLAFTERPOST"]) h.append('<input type="hidden" name="flags" value="%s" />' % form["SETOWNERFLAGS"]) h.append('<input type="hidden" name="formname" value="%s" />' % html.escape(form["NAME"])) h.append('<table width="100%" class="asm-onlineform-table">') for f in formfields: fname = f["FIELDNAME"] + "_" + str(f["ID"]) h.append('<tr class="asm-onlineform-tr">') if f["FIELDTYPE"] == FIELDTYPE_RAWMARKUP: h.append('<td class="asm-onlineform-td" colspan="2">') else: # Add label and cell wrapper if it's not raw markup h.append('<td class="asm-onlineform-td">') h.append('<label for="f%d">%s</label>' % ( f["ID"], f["LABEL"] )) h.append('</td>') h.append('<td class="asm-onlineform-td">') required = "" if f["MANDATORY"] == 1: required = "required=\"required\"" h.append('<span class="asm-onlineform-required" style="color: #ff0000;">*</span>') else: h.append('<span class="asm-onlineform-notrequired" style="visibility: hidden">*</span>') if f["FIELDTYPE"] == FIELDTYPE_YESNO: h.append('<select class="asm-onlineform-yesno" name="%s" title="%s"><option>%s</option><option>%s</option></select>' % \ ( html.escape(fname), utils.nulltostr(f["TOOLTIP"]), i18n._("No", l), i18n._("Yes", l))) elif f["FIELDTYPE"] == FIELDTYPE_TEXT: h.append('<input class="asm-onlineform-text" type="text" name="%s" title="%s" %s />' % ( html.escape(fname), utils.nulltostr(f["TOOLTIP"]), required)) elif f["FIELDTYPE"] == FIELDTYPE_DATE: h.append('<input class="asm-onlineform-date" type="text" name="%s" title="%s" %s />' % ( html.escape(fname), utils.nulltostr(f["TOOLTIP"]), required)) elif f["FIELDTYPE"] == FIELDTYPE_NOTES: h.append('<textarea class="asm-onlineform-notes" name="%s" title="%s" %s></textarea>' % ( html.escape(fname), utils.nulltostr(f["TOOLTIP"]), required)) elif f["FIELDTYPE"] == FIELDTYPE_LOOKUP: h.append('<select class="asm-onlineform-lookup" name="%s" title="%s" %s>' % ( html.escape(fname), utils.nulltostr(f["TOOLTIP"]), required)) for lv in utils.nulltostr(f["LOOKUPS"]).split("|"): h.append('<option>%s</option>' % lv) h.append('</select>') elif f["FIELDTYPE"] == FIELDTYPE_SHELTERANIMAL: h.append('<select class="asm-onlineform-shelteranimal" name="%s" title="%s" %s>' % ( html.escape(fname), utils.nulltostr(f["TOOLTIP"]), required)) h.append('<option></option>') for a in animal.get_animals_on_shelter_namecode(dbo): h.append('<option value="%s">%s (%s)</option>' % (a["ANIMALNAME"], a["ANIMALNAME"], a["SHELTERCODE"])) h.append('</select>') elif f["FIELDTYPE"] == FIELDTYPE_ADOPTABLEANIMAL: h.append('<select class="asm-onlineform-adoptableanimal" name="%s" title="%s" %s>' % ( html.escape(fname), utils.nulltostr(f["TOOLTIP"]), required)) h.append('<option></option>') pc = publish.PublishCriteria(configuration.publisher_presets(dbo)) rs = publish.get_animal_data(dbo, pc, True) for a in rs: h.append('<option value="%s">%s (%s)</option>' % (a["ANIMALNAME"], a["ANIMALNAME"], a["SHELTERCODE"])) h.append('</select>') elif f["FIELDTYPE"] == FIELDTYPE_COLOUR: h.append('<select class="asm-onlineform-colour" name="%s" title="%s" %s>' % ( html.escape(fname), utils.nulltostr(f["TOOLTIP"]), required)) for l in lookups.get_basecolours(dbo): h.append('<option>%s</option>' % l["BASECOLOUR"]) h.append('</select>') elif f["FIELDTYPE"] == FIELDTYPE_BREED: h.append('<select class="asm-onlineform-breed" name="%s" title="%s" %s>' % ( html.escape(fname), utils.nulltostr(f["TOOLTIP"]), required)) for l in lookups.get_breeds(dbo): h.append('<option>%s</option>' % l["BREEDNAME"]) h.append('</select>') elif f["FIELDTYPE"] == FIELDTYPE_SPECIES: h.append('<select class="asm-onlineform-species" name="%s" title="%s" %s>' % ( html.escape(fname), utils.nulltostr(f["TOOLTIP"]), required)) for l in lookups.get_species(dbo): h.append('<option>%s</option>' % l["SPECIESNAME"]) h.append('</select>') elif f["FIELDTYPE"] == FIELDTYPE_RAWMARKUP: h.append(utils.nulltostr(f["TOOLTIP"])) h.append('</td>') h.append('</tr>') h.append('</table>') h.append('<p style="text-align: center"><input type="submit" value="Submit" /></p>') h.append('</form>') if completedocument: h.append(utils.nulltostr(form["FOOTER"])) footer = get_onlineform_footer(dbo) h.append(footer.replace("$$TITLE$$", form["NAME"])) return "\n".join(h)
def run(self): def xe(s): if s is None: return "" return s.replace("&", "&").replace("<", "<").replace(">", ">") self.log(self.publisherName + " starting...") if self.isPublisherExecuting(): return self.updatePublisherProgress(0) self.setLastError("") self.setStartPublishing() userid = configuration.vetenvoy_user_id(self.dbo) userpassword = configuration.vetenvoy_user_password(self.dbo) if userid == "" or userpassword == "": self.setLastError("VetEnvoy userid and userpassword must be set") return animals = get_microchip_data(self.dbo, self.microchipPatterns, self.publisherKey) if len(animals) == 0: self.setLastError("No animals found to publish.") return anCount = 0 processed_animals = [] failed_animals = [] for an in animals: try: anCount += 1 self.log("Processing: %s: %s (%d of %d)" % (an["SHELTERCODE"], an["ANIMALNAME"], anCount, len(animals))) self.updatePublisherProgress( self.getProgress(anCount, len(animals))) # If the user cancelled, stop now if self.shouldStopPublishing(): self.log("User cancelled publish. Stopping.") self.resetPublisherProgress() return # Validate certain items aren't blank so we aren't registering bogus data if utils.nulltostr(an["CURRENTOWNERADDRESS"].strip()) == "": self.logError( "Address for the new owner is blank, cannot process") continue if utils.nulltostr(an["CURRENTOWNERPOSTCODE"].strip()) == "": self.logError( "Postal code for the new owner is blank, cannot process" ) continue if an["IDENTICHIPDATE"] is None: self.logError( "Microchip date cannot be blank, cannot process") continue # Make sure the length is actually suitable if not len(an["IDENTICHIPNUMBER"]) in (9, 10, 15): self.logError( "Microchip length is not 9, 10 or 15, cannot process") continue # Construct the XML document x = '<?xml version="1.0" encoding="UTF-8"?>\n' \ '<MicrochipRegistration ' \ 'version="1.32" ' \ 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' \ 'xsi:schemaLocation="https://www.vetenvoytest.com/partner/files/Chip%201.32.xsd">' \ '<Identification>' \ ' <PracticeID>' + userid + '</PracticeID>' \ ' <PinNo></PinNo>' \ ' <Source></Source>' \ '</Identification>' \ '<OwnerDetails>' \ ' <Salutation>' + xe(an["CURRENTOWNERTITLE"]) + '</Salutation>' \ ' <Initials>' + xe(an["CURRENTOWNERINITIALS"]) + '</Initials>' \ ' <Forenames>' + xe(an["CURRENTOWNERFORENAMES"]) + '</Forenames>' \ ' <Surname>' + xe(an["CURRENTOWNERSURNAME"]) + '</Surname>' \ ' <Address>' \ ' <Line1>'+ xe(an["CURRENTOWNERADDRESS"]) + '</Line1>' \ ' <LineOther>'+ xe(an["CURRENTOWNERTOWN"]) + '</LineOther>' \ ' <PostalCode>' + xe(an["CURRENTOWNERPOSTCODE"]) + '</PostalCode>' \ ' <County_State>'+ xe(an["CURRENTOWNERCOUNTY"]) + '</County_State>' \ ' <Country>USA</Country>' \ ' </Address>' \ ' <DaytimePhone><Number>' + xe(an["CURRENTOWNERWORKTELEPHONE"]) + '</Number><Note/></DaytimePhone>' \ ' <EveningPhone><Number>' + xe(an["CURRENTOWNERHOMETELEPHONE"]) + '</Number><Note/></EveningPhone>' \ ' <MobilePhone><Number>' + xe(an["CURRENTOWNERMOBILETELEPHONE"]) + '</Number><Note/></MobilePhone>' \ ' <EmergencyPhone><Number/><Note/></EmergencyPhone>' \ ' <OtherPhone><Number/><Note/></OtherPhone>' \ ' <EmailAddress>' + xe(an["CURRENTOWNEREMAILADDRESS"]) + '</EmailAddress>' \ ' <Fax />' \ '</OwnerDetails>' \ '<PetDetails>' \ ' <Name>' + xe(an["ANIMALNAME"]) + '</Name>' \ ' <Species>' + self.get_vetenvoy_species(an["SPECIESID"]) + '</Species>' \ ' <Breed><FreeText>' + xe(an["BREEDNAME"]) + '</FreeText><Code/></Breed>' \ ' <DateOfBirth>' + i18n.format_date("%m/%d/%Y", an["DATEOFBIRTH"]) + '</DateOfBirth>' \ ' <Gender>' + an["SEXNAME"][0:1] + '</Gender>' \ ' <Colour>' + xe(an["BASECOLOURNAME"]) + '</Colour>' \ ' <Markings>' + xe(an["MARKINGS"]) + '</Markings>' \ ' <Neutered>' + (an["NEUTERED"] == 1 and "true" or "false") + '</Neutered>' \ ' <NotableConditions>' + xe(an["HEALTHPROBLEMS"]) + '</NotableConditions>' \ '</PetDetails>' \ '<MicrochipDetails>' \ ' <MicrochipNumber>' + xe(an["IDENTICHIPNUMBER"]) + '</MicrochipNumber>' \ ' <ImplantDate>' + i18n.format_date("%m/%d/%Y", an["IDENTICHIPDATE"]) + '</ImplantDate>' \ ' <ImplanterName>' + xe(an["CREATEDBY"]) + '</ImplanterName>' \ '</MicrochipDetails>' \ '<ThirdPartyDisclosure>true</ThirdPartyDisclosure>' \ '<ReceiveMail>true</ReceiveMail>' \ '<ReceiveEmail>true</ReceiveEmail>' \ '<Authorisation>true</Authorisation>' \ '</MicrochipRegistration>' # Build our auth headers authheaders = { "UserId": userid, "UserPassword": userpassword, "VendorPassword": VETENVOY_US_VENDOR_PASSWORD, "RecipientId": self.recipientId } # Start a new conversation with VetEnvoy's microchip handler url = VETENVOY_US_BASE_URL + "Chip/NewConversationId" self.log( "Contacting vetenvoy to start a new conversation: %s" % url) try: r = utils.get_url(url, authheaders) self.log("Got response: %s" % r["response"]) conversationid = re.findall('c id="(.+?)"', r["response"]) if len(conversationid) == 0: self.log( "Could not parse conversation id, abandoning run") break conversationid = conversationid[0] self.log("Got conversationid: %s" % conversationid) # Now post the XML document self.log("Posting microchip registration document: %s" % x) r = utils.post_xml( VETENVOY_US_BASE_URL + "Chip/" + conversationid, x, authheaders) self.log("Response %d, HTTP headers: %s, body: %s" % (r["status"], r["headers"], r["response"])) if r["status"] != 200: raise Exception(r["response"]) # Look in the headers for successful results wassuccess = False SUCCESS = ("54000", "54100", "54108") for code in SUCCESS: if str(r["headers"]).find(code) != -1: self.log( "successful %s response header found, marking processed" % code) processed_animals.append(an) # Mark success in the log self.logSuccess( "Processed: %s: %s (%d of %d)" % (an["SHELTERCODE"], an["ANIMALNAME"], anCount, len(animals))) wassuccess = True break # If we saw an account not found message, there's no point sending # anything else as they will all trigger the same error if str(r["headers"]).find("54101") != -1 and str( r["headers"]).find("Account Not Found") != -1: self.logError( "received HomeAgain 54101 'account not found' response header - abandoning run and disabling publisher" ) configuration.publishers_enabled_disable( self.dbo, "veha") break if str(r["headers"]).find("54101") != -1 and str( r["headers"]).find("sender not recognized") != -1: self.logError( "received AKC Reunite 54101 'sender not recognized' response header - abandoning run and disabling publisher" ) configuration.publishers_enabled_disable( self.dbo, "vear") break if not wassuccess: self.logError( "no successful response header %s received" % str(SUCCESS)) an["FAILMESSAGE"] = "%s: %s" % ( self.getHeader(r["headers"], "ResultCode"), self.getHeader(r["headers"], "ResultDetails")) failed_animals.append(an) except Exception as err: em = str(err) self.logError("Failed registering microchip: %s" % em, sys.exc_info()) continue except Exception as err: self.logError( "Failed processing animal: %s, %s" % (str(an["SHELTERCODE"]), err), sys.exc_info()) # Only mark processed if we aren't using VetEnvoy's test URL if len(processed_animals) > 0 and VETENVOY_US_BASE_URL.find( "test") == -1: self.log("successfully processed %d animals, marking sent" % len(processed_animals)) self.markAnimalsPublished(processed_animals) if len(failed_animals) > 0 and VETENVOY_US_BASE_URL.find("test") == -1: self.log("failed processing %d animals, marking failed" % len(failed_animals)) self.markAnimalsPublishFailed(failed_animals) if VETENVOY_US_BASE_URL.find("test") != -1: self.log("VetEnvoy test mode, not marking animals published") self.saveLog() self.setPublisherComplete()
def insert_adoption_from_form(dbo, username, data, creating=[]): """ Inserts a movement from the workflow adopt an animal screen. Returns the new movement id creating is an ongoing list of animals we're already going to create adoptions for. It prevents a never ending recursive loop of animal1 being bonded to animal2 that's bonded to animal1, etc. """ l = dbo.locale # Validate that we have a movement date before doing anthing if None == utils.df_kd(data, "movementdate", l): raise utils.ASMValidationError( i18n._("Adoption movements must have a valid adoption date.", l)) # Get the animal record for this adoption a = animal.get_animal(dbo, utils.df_ki(data, "animal")) if a is None: raise utils.ASMValidationError( "Adoption POST has an invalid animal ID: %d" % utils.df_ki(data, "animal")) al.debug( "Creating adoption for %d (%s - %s)" % (a["ID"], a["SHELTERCODE"], a["ANIMALNAME"]), "movement.insert_adoption_from_form", dbo) creating.append(a["ID"]) # If the animal is bonded to other animals, we call this function # again with a copy of the data and the bonded animal substituted # so we can create their adoption records too. if a["BONDEDANIMALID"] is not None and a["BONDEDANIMALID"] != 0 and a[ "BONDEDANIMALID"] not in creating: al.debug( "Found bond to animal %d, creating adoption..." % a["BONDEDANIMALID"], "movement.insert_adoption_from_form", dbo) newdata = dict(data) newdata["animal"] = str(a["BONDEDANIMALID"]) insert_adoption_from_form(dbo, username, newdata, creating) if a["BONDEDANIMAL2ID"] is not None and a["BONDEDANIMAL2ID"] != 0 and a[ "BONDEDANIMAL2ID"] not in creating: al.debug( "Found bond to animal %d, creating adoption..." % a["BONDEDANIMAL2ID"], "movement.insert_adoption_from_form", dbo) newdata = dict(data) newdata["animal"] = str(a["BONDEDANIMAL2ID"]) insert_adoption_from_form(dbo, username, newdata, creating) cancel_reserves = configuration.cancel_reserves_on_adoption(dbo) # Prepare a dictionary of data for the movement table via insert_movement_from_form move_dict = { "person": utils.df_ks(data, "person"), "animal": utils.df_ks(data, "animal"), "adoptionno": utils.df_ks(data, "movementnumber"), "movementdate": utils.df_ks(data, "movementdate"), "type": str(ADOPTION), "donation": utils.df_ks(data, "amount"), "insurance": utils.df_ks(data, "insurance"), "returncategory": configuration.default_return_reason(dbo), "trial": utils.df_ks(data, "trial"), "trialenddate": utils.df_ks(data, "trialenddate") } # Is this animal currently on foster? If so, return the foster fm = get_animal_movements(dbo, utils.df_ki(data, "animal")) for m in fm: if m["MOVEMENTTYPE"] == FOSTER and m["RETURNDATE"] is None: return_movement(dbo, m["ID"], utils.df_ki(data, "animal"), utils.df_kd(data, "movementdate", l)) # Is this animal current at a retailer? If so, return it from the # retailer and set the originalretailermovement and retailerid fields # on our new adoption movement so it can be linked back for m in fm: if m["MOVEMENTTYPE"] == RETAILER and m["RETURNDATE"] is None: return_movement(dbo, m["ID"], utils.df_ki(data, "animal"), utils.df_kd(data, "movementdate", l)) move_dict["originalretailermovement"] = str(m["ID"]) move_dict["retailer"] = str(m["OWNERID"]) # Did we say we'd like to flag the owner as homechecked? if utils.df_kc(data, "homechecked") == 1: db.execute(dbo, "UPDATE owner SET IDCheck = 1, DateLastHomeChecked = %s WHERE ID = %d" % \ ( db.dd(i18n.now(dbo.timezone)), utils.df_ki(data, "person"))) # If the animal was flagged as not available for adoption, then it # shouldn't be since we've just adopted it. db.execute( dbo, "UPDATE animal SET IsNotAvailableForAdoption = 0 WHERE ID = %s" % utils.df_ks(data, "animal")) # Is the animal reserved to the person adopting? movementid = 0 for m in fm: if m["MOVEMENTTYPE"] == NO_MOVEMENT and m["RESERVATIONDATE"] is not None \ and m["RESERVATIONCANCELLEDDATE"] is None and m["ANIMALID"] == utils.df_ki(data, "animal") \ and m["OWNERID"] == utils.df_ki(data, "person"): # yes - update the existing movement movementid = m["ID"] move_dict["movementid"] = str(movementid) move_dict["adoptionno"] = utils.padleft(movementid, 6) move_dict["reservationdate"] = str( i18n.python2display(l, m["RESERVATIONDATE"])) move_dict["comments"] = utils.nulltostr(m["COMMENTS"]) break elif cancel_reserves and m["MOVEMENTTYPE"] == NO_MOVEMENT and m["RESERVATIONDATE"] is not None \ and m["RESERVATIONCANCELLEDDATE"] is None: # no, but it's reserved to someone else and we're cancelling # reserves on adoption db.execute(dbo, "UPDATE adoption SET ReservationCancelledDate = %s WHERE ID = %d" % \ ( utils.df_d(data, "movementdate", l), m["ID"] )) if movementid != 0: update_movement_from_form(dbo, username, move_dict) else: movementid = insert_movement_from_form(dbo, username, move_dict) # Create the donation if there is one donation_amount = int(utils.df_m(data, "amount", l)) if donation_amount > 0: due = "" received = utils.df_ks(data, "movementdate") if configuration.movement_donations_default_due(dbo): due = utils.df_ks(data, "movementdate") received = "" don_dict = { "person": utils.df_ks(data, "person"), "animal": utils.df_ks(data, "animal"), "movement": str(movementid), "type": utils.df_ks(data, "donationtype"), "payment": utils.df_ks(data, "payment"), "frequency": "0", "amount": utils.df_ks(data, "amount"), "due": due, "received": received, "giftaid": utils.df_ks(data, "giftaid") } financial.insert_donation_from_form(dbo, username, don_dict) # And a second donation if there is one donation_amount = int(utils.df_m(data, "amount2", l)) if donation_amount > 0: due = "" received = utils.df_ks(data, "movementdate") if configuration.movement_donations_default_due(dbo): due = utils.df_ks(data, "movementdate") received = "" don_dict = { "person": utils.df_ks(data, "person"), "animal": utils.df_ks(data, "animal"), "movement": str(movementid), "type": utils.df_ks(data, "donationtype2"), "payment": utils.df_ks(data, "payment2"), "frequency": "0", "amount": utils.df_ks(data, "amount2"), "due": due, "received": received, "giftaid": utils.df_ks(data, "giftaid") } financial.insert_donation_from_form(dbo, username, don_dict) # Then any boarding cost record cost_amount = int(utils.df_m(data, "costamount", l)) cost_type = utils.df_ks(data, "costtype") cost_create = utils.df_ki(data, "costcreate") if cost_amount > 0 and cost_type != "" and cost_create == 1: boc_dict = { "animalid": utils.df_ks(data, "animal"), "type": cost_type, "costdate": utils.df_ks(data, "movementdate"), "cost": utils.df_ks(data, "costamount") } animal.insert_cost_from_form(dbo, username, boc_dict) return movementid
def run(self): if self.isPublisherExecuting(): return self.updatePublisherProgress(0) self.setLastError("") self.setStartPublishing() org = configuration.organisation(self.dbo) folder = configuration.foundanimals_folder(self.dbo) if folder == "": self.setLastError("No FoundAnimals folder has been set.") self.cleanup() return email = configuration.foundanimals_email(self.dbo) if email == "": self.setLastError("No FoundAnimals group email has been set.") self.cleanup() return animals = get_microchip_data(self.dbo, ["9", "0", "1"], "foundanimals", allowintake=True, organisation_email=email) if len(animals) == 0: self.setLastError("No animals found to publish.") self.cleanup(save_log=False) return if not self.openFTPSocket(): self.setLastError("Failed to open FTP socket.") if self.logSearch("530 Login") != -1: self.log( "Found 530 Login incorrect: disabling FoundAnimals publisher." ) configuration.publishers_enabled_disable(self.dbo, "fa") self.cleanup() return # foundanimals.org want data files called mmddyyyy_HHMMSS.csv in the shelter's own folder dateportion = i18n.format_date("%m%d%Y_%H%M%S", i18n.now(self.dbo.timezone)) outputfile = "%s.csv" % dateportion self.mkdir(folder) self.chdir(folder) csv = [] anCount = 0 success = [] for an in animals: try: line = [] anCount += 1 self.log("Processing: %s: %s (%d of %d)" % (an["SHELTERCODE"], an["ANIMALNAME"], anCount, len(animals))) self.updatePublisherProgress( self.getProgress(anCount, len(animals))) # If the user cancelled, stop now if self.shouldStopPublishing(): self.log("User cancelled publish. Stopping.") self.resetPublisherProgress() self.cleanup() return # Validate certain items aren't blank so we aren't registering bogus data if utils.nulltostr(an["CURRENTOWNERADDRESS"].strip()) == "": self.logError( "Address for the new owner is blank, cannot process") continue if utils.nulltostr(an["CURRENTOWNERPOSTCODE"].strip()) == "": self.logError( "Postal code for the new owner is blank, cannot process" ) continue # Make sure the length is actually suitable if not len(an["IDENTICHIPNUMBER"]) in (9, 10, 15): self.logError( "Microchip length is not 9, 10 or 15, cannot process") continue servicedate = an["ACTIVEMOVEMENTDATE"] or an[ "MOSTRECENTENTRYDATE"] if an["NONSHELTERANIMAL"] == 1: servicedate = an["IDENTICHIPDATE"] if servicedate < self.dbo.today(offset=-365 * 3): self.logError( "Service date is older than 3 years, ignoring") continue # First Name line.append("\"%s\"" % an["CURRENTOWNERFORENAMES"]) # Last Name line.append("\"%s\"" % an["CURRENTOWNERSURNAME"]) # Email Address line.append("\"%s\"" % an["CURRENTOWNEREMAILADDRESS"]) # Address 1 line.append("\"%s\"" % an["CURRENTOWNERADDRESS"]) # Address 2 line.append("\"\"") # City line.append("\"%s\"" % an["CURRENTOWNERTOWN"]) # State line.append("\"%s\"" % an["CURRENTOWNERCOUNTY"]) # Zip Code line.append("\"%s\"" % an["CURRENTOWNERPOSTCODE"]) # Home Phone line.append("\"%s\"" % an["CURRENTOWNERHOMETELEPHONE"]) # Work Phone line.append("\"%s\"" % an["CURRENTOWNERWORKTELEPHONE"]) # Cell Phone line.append("\"%s\"" % an["CURRENTOWNERMOBILETELEPHONE"]) # Pet Name line.append("\"%s\"" % an["ANIMALNAME"]) # Microchip Number line.append("\"%s\"" % an["IDENTICHIPNUMBER"]) # Service Date line.append("\"%s\"" % i18n.format_date("%m/%d/%Y", servicedate)) # Date of Birth line.append("\"%s\"" % i18n.format_date("%m/%d/%Y", an["DATEOFBIRTH"])) # Species line.append("\"%s\"" % an["PETFINDERSPECIES"]) # Sex line.append("\"%s\"" % an["SEXNAME"]) # Spayed/Neutered line.append("\"%s\"" % utils.iif(an["NEUTERED"] == 1, "Yes", "No")) # Primary Breed line.append("\"%s\"" % an["PETFINDERBREED"]) # Secondary Breed line.append("\"%s\"" % an["PETFINDERBREED2"]) # Color line.append("\"%s\"" % an["BASECOLOURNAME"]) # Implanting Organization line.append("\"%s\"" % org) # Rescue Group Email line.append("\"%s\"" % email) # Add to our CSV file csv.append(",".join(line)) # Mark success in the log self.logSuccess("Processed: %s: %s (%d of %d)" % (an["SHELTERCODE"], an["ANIMALNAME"], anCount, len(animals))) success.append(an) except Exception as err: self.logError( "Failed processing animal: %s, %s" % (str(an["SHELTERCODE"]), err), sys.exc_info()) # Bail if we didn't have anything to do if len(csv) == 0: self.log("No data left to send to foundanimals") self.cleanup() return # Mark published self.markAnimalsPublished(success) header = "First Name,Last Name,Email Address,Address 1,Address 2,City,State,Zip Code," \ "Home Phone,Work Phone,Cell Phone,Pet Name,Microchip Number,Service Date," \ "Date of Birth,Species,Sex,Spayed/Neutered,Primary Breed,Secondary Breed," \ "Color,Implanting Organization,Rescue Group Email\n" self.saveFile(os.path.join(self.publishDir, outputfile), header + "\n".join(csv)) self.log("Uploading datafile %s" % outputfile) self.upload(outputfile) self.log("Uploaded %s" % outputfile) self.log("-- FILE DATA --") self.log(header + "\n".join(csv)) self.cleanup()
def run(self): self.log("PETtrac UK Publisher starting...") if self.isPublisherExecuting(): return self.updatePublisherProgress(0) self.setLastError("") self.setStartPublishing() orgpostcode = configuration.avid_org_postcode(self.dbo) orgname = configuration.avid_org_name(self.dbo) orgserial = configuration.avid_org_serial(self.dbo) orgpassword = configuration.avid_org_password(self.dbo) avidrereg = configuration.avid_reregistration(self.dbo) orgaddress = configuration.organisation_address(self.dbo) orgtown = configuration.organisation_town(self.dbo) orgcounty = configuration.organisation_county(self.dbo) registeroverseas = configuration.avid_register_overseas(self.dbo) overseasorigin = configuration.avid_overseas_origin_country(self.dbo) if orgpostcode == "" or orgname == "" or orgserial == "" or orgpassword == "": self.setLastError( "orgpostcode, orgname, orgserial and orgpassword all need to be set for AVID publisher" ) return authuser = configuration.avid_auth_user(self.dbo) user = users.get_users(self.dbo, authuser) if avidrereg and len(user) == 0: self.setLastError( "no authorised user is set, cannot re-register chips") return if avidrereg and utils.nulltostr(user[0]["SIGNATURE"]) == "": self.setLastError( "authorised user '%s' does not have an electronic signature on file" % authuser) return chipprefix = "977%" # AVID Europe if registeroverseas: chipprefix = "a.IdentichipNumber LIKE '%'" # If overseas registration is on, send all chips to AVID animals = get_microchip_data(self.dbo, [ chipprefix, ], "pettracuk", allowintake=False or registeroverseas) if len(animals) == 0: self.setLastError("No animals found to publish.") return anCount = 0 processed_animals = [] for an in animals: try: anCount += 1 self.log("Processing: %s: %s (%d of %d)" % (an["SHELTERCODE"], an["ANIMALNAME"], anCount, len(animals))) self.updatePublisherProgress( self.getProgress(anCount, len(animals))) # If the user cancelled, stop now if self.shouldStopPublishing(): self.log("User cancelled publish. Stopping.") self.resetPublisherProgress() return # Validate certain items aren't blank so we aren't registering bogus data if utils.nulltostr(an["CURRENTOWNERADDRESS"].strip()) == "": self.logError( "Address for the new owner is blank, cannot process") continue if utils.nulltostr(an["CURRENTOWNERPOSTCODE"].strip()) == "": self.logError( "Postal code for the new owner is blank, cannot process" ) continue # Make sure the length is actually suitable if not len(an["IDENTICHIPNUMBER"]) in (9, 10, 15): self.logError( "Microchip length is not 9, 10 or 15, cannot process") continue # Sort out breed breed = an["BREEDNAME"] if breed.find("Domestic Long") != -1: breed = "DLH" if breed.find("Domestic Short") != -1: breed = "DSH" if breed.find("Domestic Medium") != -1: breed = "DSLH" # Sort out species species = an["SPECIESNAME"] if species.find("Dog") != -1: species = "Canine" elif species.find("Cat") != -1: species = "Feline" elif species.find("Bird") != -1: species = "Avian" elif species.find("Horse") != -1: species = "Equine" elif species.find("Reptile") != -1: species = "Reptilian" else: species = "Other" # Build the animal POST data fields = { "orgpostcode": orgpostcode, "orgname": orgname, "orgserial": orgserial, "orgpassword": orgpassword, "version": "1.1", "microchip": an["IDENTICHIPNUMBER"], "implantdate": i18n.format_date("%Y%m%d", an["IDENTICHIPDATE"]), "prefix": an["CURRENTOWNERTITLE"], "surname": an["CURRENTOWNERSURNAME"], "firstname": an["CURRENTOWNERFORENAMES"], "address1": an["CURRENTOWNERADDRESS"], "city": an["CURRENTOWNERTOWN"], "county": an["CURRENTOWNERCOUNTY"], "postcode": an["CURRENTOWNERPOSTCODE"], "telhome": an["CURRENTOWNERHOMETELEPHONE"], "telwork": an["CURRENTOWNERWORKTELEPHONE"], "telmobile": an["CURRENTOWNERMOBILETELEPHONE"], "telalternative": "", "email": an["CURRENTOWNEREMAILADDRESS"], "petname": an["ANIMALNAME"], "petgender": an["SEXNAME"][0:1], "petdob": i18n.format_date("%Y%m%d", an["DATEOFBIRTH"]), "petspecies": species, "petbreed": breed, "petneutered": an["NEUTERED"] == 1 and "true" or "false", "petcolour": an["BASECOLOURNAME"], "selfreg": "true", # register the shelter as alternative contact "test": "false" # if true, tells avid not to make any data changes } # If we're registering overseas chips and this chip isn't an AVID # one, set the origincountry parameter if registeroverseas and not an["IDENTICHIPNUMBER"].startswith( "977"): fields["origincountry"] = overseasorigin self.log("HTTP POST request %s: %s" % (PETTRAC_UK_POST_URL, str(fields))) r = utils.post_form(PETTRAC_UK_POST_URL, fields) self.log("HTTP response: %s" % r["response"]) # Return value is an XML fragment, look for "Registration completed successfully" if r["response"].find("successfully") != -1: self.log("successful response, marking processed") processed_animals.append(an) # Mark success in the log self.logSuccess("Processed: %s: %s (%d of %d)" % (an["SHELTERCODE"], an["ANIMALNAME"], anCount, len(animals))) # If AVID tell us the microchip is already registered, attempt to re-register elif r["response"].find("already registered") != -1: if avidrereg: self.log( "microchip already registered response, re-registering" ) pdfname = "%s-%s-%s.pdf" % (i18n.format_date( "%Y%m%d", i18n.now(self.dbo.timezone)), orgserial, an["IDENTICHIPNUMBER"]) fields["filenameupload"] = pdfname pdf = self.reregistrationPDF(fields, user[0]["SIGNATURE"], user[0]["REALNAME"], orgname, orgaddress, orgtown, orgcounty, orgpostcode) self.log( "generated re-registration PDF %s (%d bytes)" % (pdfname, len(pdf))) reregurl = PETTRAC_UK_POST_URL.replace( "onlineregistration", "onlinereregistration") self.log("HTTP multipart POST request %s: %s" % (reregurl, str(fields))) r = utils.post_multipart( reregurl, fields, {pdfname: (pdfname, pdf, "application/pdf")}) self.log("HTTP response: %s" % r["response"]) if r["response"].find("successfully") != -1: self.log( "successful re-registration response, marking processed" ) processed_animals.append(an) # Mark success in the log self.logSuccess( "Processed: %s: %s (%d of %d) (rereg)" % (an["SHELTERCODE"], an["ANIMALNAME"], anCount, len(animals))) else: self.logError( "Problem with data encountered, not marking processed" ) else: self.logError( "re-registration support is disabled, marking processed for compatibility" ) processed_animals.append(an) # There's a problem with the data we sent, flag it else: self.logError( "Problem with data encountered, not marking processed") except Exception as err: self.logError( "Failed processing animal: %s, %s" % (str(an["SHELTERCODE"]), err), sys.exc_info()) if len(processed_animals) > 0: self.log("successfully processed %d animals, marking sent" % len(processed_animals)) self.markAnimalsPublished(processed_animals) self.saveLog() self.setPublisherComplete()
def web_login(post, session, remoteip, path): """ Performs a login and sets up the user's session. Returns the username on successful login, or: FAIL - problem with user/pass/account/ip DISABLED - The database is disabled WRONGSERVER - The database is not on this server """ dbo = db.DatabaseInfo() database = post["database"] username = post["username"] password = post["password"] mobileapp = post["mobile"] == "true" nologconnection = post["nologconnection"] if len(username) > 100: username = username[0:100] # Do we have multiple databases? if MULTIPLE_DATABASES: if MULTIPLE_DATABASES_TYPE == "smcom": # Is this sheltermanager.com? If so, we need to get the # database connection info (dbo) before we can login. # If a database hasn't been supplied, let's bail out now # since we can't do anything if str(database).strip() == "": return "FAIL" else: dbo = smcom.get_database_info(database) # Bail out if there was a problem with the database if dbo.database in ("FAIL", "DISABLED", "WRONGSERVER"): return dbo.database else: # Look up the database info from our map dbo = db.get_multiple_database_info(database) if dbo.database == "FAIL": return dbo.database # Connect to the database and authenticate the username and password user = authenticate(dbo, username, password) if user is not None and not authenticate_ip(user, remoteip): al.error("user %s with ip %s failed ip restriction check '%s'" % (username, remoteip, user["IPRESTRICTION"]), "users.web_login", dbo) return "FAIL" if user is not None: al.info("%s successfully authenticated from %s" % (username, remoteip), "users.web_login", dbo) try: dbo.locked = configuration.smdb_locked(dbo) dbo.timezone = configuration.timezone(dbo) dbo.installpath = path session.locale = configuration.locale(dbo) dbo.locale = session.locale session.dbo = dbo session.user = user["USERNAME"] session.superuser = user["SUPERUSER"] session.passchange = (password == "password") session.mobileapp = mobileapp update_session(session) except: al.error("failed setting up session: %s" % str(sys.exc_info()[0]), "users.web_login", dbo, sys.exc_info()) return "FAIL" try: session.securitymap = get_security_map(dbo, user["USERNAME"]) except: # This is a pre-3002 login where the securitymap is with # the user (the error occurs because there's no role table) al.debug("role table does not exist, using securitymap from user", "users.web_login", dbo) session.securitymap = user["SECURITYMAP"] try: ur = get_users(dbo, user["USERNAME"])[0] session.roles = ur["ROLES"] session.roleids = ur["ROLEIDS"] session.siteid = utils.cint(user["SITEID"]) session.locationfilter = utils.nulltostr(user["LOCATIONFILTER"]) except: # Users coming from v2 won't have the # IPRestriction or EmailAddress fields necessary for get_users - we can't # help them right now so just give them an empty set of # roles and locationfilter until they login again after the db update session.roles = "" session.roleids = "" session.locationfilter = "" session.siteid = 0 try: # If it's a sheltermanager.com database, try and update the # last time the user connected to today if smcom.active() and database != "" and nologconnection == "": smcom.set_last_connected(dbo) except: pass try: # Mark the user logged in audit.login(dbo, username) # Check to see if any updates need performing on this database if dbupdate.check_for_updates(dbo): dbupdate.perform_updates(dbo) # We did some updates, better reload just in case config/reports/etc changed update_session(session) # Check to see if our views and sequences are out of date and need reloading if dbupdate.check_for_view_seq_changes(dbo): dbupdate.install_db_views(dbo) dbupdate.install_db_sequences(dbo) except: al.error("failed updating database: %s" % str(sys.exc_info()[0]), "users.web_login", dbo, sys.exc_info()) try: al.info("%s logged in" % user["USERNAME"], "users.login", dbo) update_user_activity(dbo, user["USERNAME"]) except: al.error("failed updating user activity: %s" % str(sys.exc_info()[0]), "users.web_login", dbo, sys.exc_info()) return "FAIL" else: al.error("database:%s username:%s password:%s failed authentication from %s" % (database, username, password, remoteip), "users.web_login", dbo) return "FAIL" return user["USERNAME"]
def web_login(post, session, remoteip, path): """ Performs a login and sets up the user's session. Returns the username on successful login, or: FAIL - problem with user/pass/account/ip DISABLED - The database is disabled WRONGSERVER - The database is not on this server """ database = post["database"] username = post["username"] password = post["password"] mobileapp = post["mobile"] == "true" nologconnection = post["nologconnection"] == "true" if len(username) > 100: username = username[0:100] dbo = db.get_database(database) if dbo.database in ("FAIL", "DISABLED", "WRONGSERVER"): return dbo.database # Connect to the database and authenticate the username and password user = authenticate(dbo, username, password) if user is not None and not authenticate_ip(user, remoteip): al.error( "user %s with ip %s failed ip restriction check '%s'" % (username, remoteip, user.IPRESTRICTION), "users.web_login", dbo) return "FAIL" if user is not None and "DISABLELOGIN" in user and user.DISABLELOGIN == 1: al.error( "user %s with ip %s failed as account has logins disabled" % (username, remoteip), "users.web_login", dbo) return "FAIL" if user is not None: al.info("%s successfully authenticated from %s" % (username, remoteip), "users.web_login", dbo) try: dbo.locked = configuration.smdb_locked(dbo) dbo.timezone = configuration.timezone(dbo) dbo.installpath = path session.locale = configuration.locale(dbo) dbo.locale = session.locale session.dbo = dbo session.user = user.USERNAME session.superuser = user.SUPERUSER session.mobileapp = mobileapp update_session(session) except: al.error("failed setting up session: %s" % str(sys.exc_info()[0]), "users.web_login", dbo, sys.exc_info()) return "FAIL" try: session.securitymap = get_security_map(dbo, user.USERNAME) except: # This is a pre-3002 login where the securitymap is with # the user (the error occurs because there's no role table) al.debug("role table does not exist, using securitymap from user", "users.web_login", dbo) session.securitymap = user.SECURITYMAP try: ur = get_users(dbo, user.USERNAME)[0] session.roles = ur.ROLES session.roleids = ur.ROLEIDS session.siteid = utils.cint(user.SITEID) session.locationfilter = utils.nulltostr(user.LOCATIONFILTER) except: # Users coming from v2 won't have the # IPRestriction or EmailAddress fields necessary for get_users - we can't # help them right now so just give them an empty set of # roles and locationfilter until they login again after the db update session.roles = "" session.roleids = "" session.locationfilter = "" session.siteid = 0 try: # Mark the user logged in if not nologconnection: audit.login(dbo, username, remoteip) # Check to see if any updates need performing on this database if dbupdate.check_for_updates(dbo): dbupdate.perform_updates(dbo) # We did some updates, better reload just in case config/reports/etc changed update_session(session) # Check to see if our views and sequences are out of date and need reloading if dbupdate.check_for_view_seq_changes(dbo): dbupdate.install_db_views(dbo) dbupdate.install_db_sequences(dbo) except: al.error("failed updating database: %s" % str(sys.exc_info()[0]), "users.web_login", dbo, sys.exc_info()) try: al.info("%s logged in" % user.USERNAME, "users.login", dbo) update_user_activity(dbo, user.USERNAME) except: al.error( "failed updating user activity: %s" % str(sys.exc_info()[0]), "users.web_login", dbo, sys.exc_info()) return "FAIL" else: al.error( "database:%s username:%s password:%s failed authentication from %s" % (database, username, password, remoteip), "users.web_login", dbo) return "FAIL" return user.USERNAME
def match(dbo, lostanimalid = 0, foundanimalid = 0, animalid = 0, limit = 0): """ Performs a lost and found match by going through all lost animals lostanimalid: Compare this lost animal against all found animals foundanimalid: Compare all lost animals against this found animal animalid: Compare all lost animals against this shelter animal limit: Stop when we hit this many matches (or 0 for all) returns a list of LostFoundMatch objects """ l = dbo.locale batch = [] matches = [] matchspecies = configuration.match_species(dbo) matchbreed = configuration.match_breed(dbo) matchage = configuration.match_age(dbo) matchsex = configuration.match_sex(dbo) matcharealost = configuration.match_area_lost(dbo) matchfeatures = configuration.match_features(dbo) matchpostcode = configuration.match_postcode(dbo) matchcolour = configuration.match_colour(dbo) matchdatewithin2weeks = configuration.match_within2weeks(dbo) matchmax = matchspecies + matchbreed + matchage + matchsex + \ matcharealost + matchfeatures + matchpostcode + matchcolour + \ matchdatewithin2weeks matchpointfloor = configuration.match_point_floor(dbo) includeshelter = configuration.match_include_shelter(dbo) fullmatch = animalid == 0 and lostanimalid == 0 and foundanimalid == 0 # Ignore records older than 6 months to keep things useful giveup = dbo.today(offset=-182) # Get our set of lost animals lostanimals = None if lostanimalid == 0: lostanimals = dbo.query(get_lostanimal_query(dbo) + \ " WHERE a.DateFound Is Null AND a.DateLost > ? ORDER BY a.DateLost", [giveup]) else: lostanimals = dbo.query(get_lostanimal_query(dbo) + \ " WHERE a.ID = ?", [lostanimalid]) oldestdate = giveup if len(lostanimals) > 0: oldestdate = lostanimals[0].DATELOST # Get the set of found animals for comparison foundanimals = None if foundanimalid == 0: foundanimals = dbo.query(get_foundanimal_query(dbo) + \ " WHERE a.ReturnToOwnerDate Is Null" \ " AND a.DateFound >= ? ", [oldestdate]) else: foundanimals = dbo.query(get_foundanimal_query(dbo) + " WHERE a.ID = ?", [foundanimalid]) # Get the set of shelter animals for comparison - anything brought in recently # that's 1. still on shelter or 2. was released to wild, transferred or escaped shelteranimals = None if includeshelter: if animalid == 0: shelteranimals = dbo.query(animal.get_animal_query(dbo) + " WHERE " + \ "(a.Archived = 0 OR a.ActiveMovementType IN (3,4,7)) " \ "AND a.DateBroughtIn > ?", [oldestdate]) else: shelteranimals = dbo.query(animal.get_animal_query(dbo) + " WHERE a.ID = ?", [animalid]) asynctask.set_progress_max(dbo, len(lostanimals)) for la in lostanimals: asynctask.increment_progress_value(dbo) # Stop if we've hit our limit if limit > 0 and len(matches) >= limit: break # Found animals (if an animal id has been given don't # check found animals) if animalid == 0: for fa in foundanimals: matchpoints = 0 if la["ANIMALTYPEID"] == fa["ANIMALTYPEID"]: matchpoints += matchspecies if la["BREEDID"] == fa["BREEDID"]: matchpoints += matchbreed if la["AGEGROUP"] == fa["AGEGROUP"]: matchpoints += matchage if la["SEX"] == fa["SEX"]: matchpoints += matchsex matchpoints += words(la["AREALOST"], fa["AREAFOUND"], matcharealost) matchpoints += words(la["DISTFEAT"], fa["DISTFEAT"], matchfeatures) if la["AREAPOSTCODE"] == fa["AREAPOSTCODE"]: matchpoints += matchpostcode if la["BASECOLOURID"] == fa["BASECOLOURID"]: matchpoints += matchcolour if date_diff_days(la["DATELOST"], fa["DATEFOUND"]) <= 14: matchpoints += matchdatewithin2weeks if matchpoints > matchmax: matchpoints = matchmax if matchpoints >= matchpointfloor: m = LostFoundMatch(dbo) m.lid = la["ID"] m.lcontactname = la["OWNERNAME"] m.lcontactnumber = la["HOMETELEPHONE"] m.larealost = la["AREALOST"] m.lareapostcode = la["AREAPOSTCODE"] m.lagegroup = la["AGEGROUP"] m.lsexid = la["SEX"] m.lsexname = la["SEXNAME"] m.lspeciesid = la["ANIMALTYPEID"] m.lspeciesname = la["SPECIESNAME"] m.lbreedid = la["BREEDID"] m.lbreedname = la["BREEDNAME"] m.ldistinguishingfeatures = la["DISTFEAT"] m.lbasecolourid = la["BASECOLOURID"] m.lbasecolourname = la["BASECOLOURNAME"] m.ldatelost = la["DATELOST"] m.fid = fa["ID"] m.fanimalid = 0 m.fcontactname = fa["OWNERNAME"] m.fcontactnumber = fa["HOMETELEPHONE"] m.fareafound = fa["AREAFOUND"] m.fareapostcode = fa["AREAPOSTCODE"] m.fagegroup = fa["AGEGROUP"] m.fsexid = fa["SEX"] m.fsexname = fa["SEXNAME"] m.fspeciesid = fa["ANIMALTYPEID"] m.fspeciesname = fa["SPECIESNAME"] m.fbreedid = fa["BREEDID"] m.fbreedname = fa["BREEDNAME"] m.fdistinguishingfeatures = fa["DISTFEAT"] m.fbasecolourid = fa["BASECOLOURID"] m.fbasecolourname = fa["BASECOLOURNAME"] m.fdatefound = fa["DATEFOUND"] m.matchpoints = int((float(matchpoints) / float(matchmax)) * 100.0) matches.append(m) if fullmatch: batch.append(m.toParams()) if limit > 0 and len(matches) >= limit: break # Shelter animals if includeshelter: for a in shelteranimals: matchpoints = 0 if la["ANIMALTYPEID"] == a["SPECIESID"]: matchpoints += matchspecies if la["BREEDID"] == a["BREEDID"] or la["BREEDID"] == a["BREED2ID"]: matchpoints += matchbreed if la["BASECOLOURID"] == a["BASECOLOURID"]: matchpoints += matchcolour if la["AGEGROUP"] == a["AGEGROUP"]: matchpoints += matchage if la["SEX"] == a["SEX"]: matchpoints += matchsex matchpoints += words(la["AREALOST"], a["ORIGINALOWNERADDRESS"], matcharealost) matchpoints += words(la["DISTFEAT"], a["MARKINGS"], matchfeatures) if utils.nulltostr(a["ORIGINALOWNERPOSTCODE"]).find(la["AREAPOSTCODE"]) != -1: matchpoints += matchpostcode if date_diff_days(la["DATELOST"], a["DATEBROUGHTIN"]) <= 14: matchpoints += matchdatewithin2weeks if matchpoints > matchmax: matchpoints = matchmax if matchpoints >= matchpointfloor: m = LostFoundMatch(dbo) m.lid = la["ID"] m.lcontactname = la["OWNERNAME"] m.lcontactnumber = la["HOMETELEPHONE"] m.larealost = la["AREALOST"] m.lareapostcode = la["AREAPOSTCODE"] m.lagegroup = la["AGEGROUP"] m.lsexid = la["SEX"] m.lsexname = la["SEXNAME"] m.lspeciesid = la["ANIMALTYPEID"] m.lspeciesname = la["SPECIESNAME"] m.lbreedid = la["BREEDID"] m.lbreedname = la["BREEDNAME"] m.ldistinguishingfeatures = la["DISTFEAT"] m.lbasecolourid = la["BASECOLOURID"] m.lbasecolourname = la["BASECOLOURNAME"] m.ldatelost = la["DATELOST"] m.fid = 0 m.fanimalid = a["ID"] m.fcontactname = _("Shelter animal {0} '{1}'", l).format(a["CODE"], a["ANIMALNAME"]) m.fcontactnumber = a["SPECIESNAME"] m.fareafound = _("On Shelter", l) m.fareapostcode = a["ORIGINALOWNERPOSTCODE"] m.fagegroup = a["AGEGROUP"] m.fsexid = a["SEX"] m.fsexname = a["SEXNAME"] m.fspeciesid = a["SPECIESID"] m.fspeciesname = a["SPECIESNAME"] m.fbreedid = a["BREEDID"] m.fbreedname = a["BREEDNAME"] m.fdistinguishingfeatures = a["MARKINGS"] m.fbasecolourid = a["BASECOLOURID"] m.fbasecolourname = a["BASECOLOURNAME"] m.fdatefound = a["DATEBROUGHTIN"] m.matchpoints = int((float(matchpoints) / float(matchmax)) * 100.0) matches.append(m) if fullmatch: batch.append(m.toParams()) if limit > 0 and len(matches) >= limit: break if fullmatch: dbo.execute("DELETE FROM animallostfoundmatch") sql = "INSERT INTO animallostfoundmatch (AnimalLostID, AnimalFoundID, AnimalID, LostContactName, LostContactNumber, " \ "LostArea, LostPostcode, LostAgeGroup, LostSex, LostSpeciesID, LostBreedID, LostFeatures, LostBaseColourID, LostDate, " \ "FoundContactName, FoundContactNumber, FoundArea, FoundPostcode, FoundAgeGroup, FoundSex, FoundSpeciesID, FoundBreedID, " \ "FoundFeatures, FoundBaseColourID, FoundDate, MatchPoints) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" if len(batch) > 0: dbo.execute_many(sql, batch) return matches
def run(self): def xe(s): if s is None: return "" return s.replace("&", "&").replace("<", "<").replace(">", ">") self.log(self.publisherName + " starting...") if self.isPublisherExecuting(): return self.updatePublisherProgress(0) self.setLastError("") self.setStartPublishing() practiceid = configuration.anibase_practice_id(self.dbo) pinno = configuration.anibase_pin_no(self.dbo) if pinno == "": self.setLastError("Anibase vet code must be set") return animals = get_microchip_data(self.dbo, ['9851', '9861'], "anibaseuk") if len(animals) == 0: self.setLastError("No animals found to publish.") return anCount = 0 processed_animals = [] for an in animals: try: anCount += 1 self.log("Processing: %s: %s (%d of %d)" % (an["SHELTERCODE"], an["ANIMALNAME"], anCount, len(animals))) self.updatePublisherProgress( self.getProgress(anCount, len(animals))) # If the user cancelled, stop now if self.shouldStopPublishing(): self.log("User cancelled publish. Stopping.") self.resetPublisherProgress() return # Validate certain items aren't blank so we aren't registering bogus data if utils.nulltostr(an["CURRENTOWNERADDRESS"].strip()) == "": self.logError( "Address for the new owner is blank, cannot process") continue if utils.nulltostr(an["CURRENTOWNERPOSTCODE"].strip()) == "": self.logError( "Postal code for the new owner is blank, cannot process" ) continue if an["IDENTICHIPDATE"] is None: self.logError( "Microchip date cannot be blank, cannot process") continue # Make sure the length is actually suitable if not len(an["IDENTICHIPNUMBER"]) in (9, 10, 15): self.logError( "Microchip length is not 9, 10 or 15, cannot process") continue # Construct the XML document x = '<?xml version="1.0" encoding="UTF-8"?>\n' \ '<MicrochipRegistration>' \ '<Identification>' \ ' <PracticeID>' + practiceid + '</PracticeID>' \ ' <PinNo>' + pinno + '</PinNo>' \ ' <Source></Source>' \ '</Identification>' \ '<OwnerDetails>' \ ' <Salutation>' + xe(an["CURRENTOWNERTITLE"]) + '</Salutation>' \ ' <Initials>' + xe(an["CURRENTOWNERINITIALS"]) + '</Initials>' \ ' <Forenames>' + xe(an["CURRENTOWNERFORENAMES"]) + '</Forenames>' \ ' <Surname>' + xe(an["CURRENTOWNERSURNAME"]) + '</Surname>' \ ' <Address>' \ ' <Line1>'+ xe(an["CURRENTOWNERADDRESS"]) + '</Line1>' \ ' <LineOther>'+ xe(an["CURRENTOWNERTOWN"]) + '</LineOther>' \ ' <PostalCode>' + xe(an["CURRENTOWNERPOSTCODE"]) + '</PostalCode>' \ ' <County_State>'+ xe(an["CURRENTOWNERCOUNTY"]) + '</County_State>' \ ' <Country>USA</Country>' \ ' </Address>' \ ' <DaytimePhone><Number>' + xe(an["CURRENTOWNERWORKTELEPHONE"]) + '</Number><Note/></DaytimePhone>' \ ' <EveningPhone><Number>' + xe(an["CURRENTOWNERHOMETELEPHONE"]) + '</Number><Note/></EveningPhone>' \ ' <MobilePhone><Number>' + xe(an["CURRENTOWNERMOBILETELEPHONE"]) + '</Number><Note/></MobilePhone>' \ ' <EmergencyPhone><Number/><Note/></EmergencyPhone>' \ ' <OtherPhone><Number/><Note/></OtherPhone>' \ ' <EmailAddress>' + xe(an["CURRENTOWNEREMAILADDRESS"]) + '</EmailAddress>' \ ' <Fax />' \ '</OwnerDetails>' \ '<PetDetails>' \ ' <Name>' + xe(an["ANIMALNAME"]) + '</Name>' \ ' <Species>' + self.get_vetxml_species(an["SPECIESID"]) + '</Species>' \ ' <Breed><FreeText>' + xe(an["BREEDNAME"]) + '</FreeText><Code/></Breed>' \ ' <DateOfBirth>' + i18n.format_date("%m/%d/%Y", an["DATEOFBIRTH"]) + '</DateOfBirth>' \ ' <Gender>' + an["SEXNAME"][0:1] + '</Gender>' \ ' <Colour>' + xe(an["BASECOLOURNAME"]) + '</Colour>' \ ' <Markings>' + xe(an["MARKINGS"]) + '</Markings>' \ ' <Neutered>' + (an["NEUTERED"] == 1 and "true" or "false") + '</Neutered>' \ ' <NotableConditions>' + xe(an["HEALTHPROBLEMS"]) + '</NotableConditions>' \ '</PetDetails>' \ '<MicrochipDetails>' \ ' <MicrochipNumber>' + xe(an["IDENTICHIPNUMBER"]) + '</MicrochipNumber>' \ ' <ImplantDate>' + i18n.format_date("%m/%d/%Y", an["IDENTICHIPDATE"]) + '</ImplantDate>' \ ' <ImplanterName>' + xe(an["CREATEDBY"]) + '</ImplanterName>' \ '</MicrochipDetails>' \ '<ThirdPartyDisclosure>true</ThirdPartyDisclosure>' \ '<ReceiveMail>true</ReceiveMail>' \ '<ReceiveEmail>true</ReceiveEmail>' \ '<Authorisation>true</Authorisation>' \ '</MicrochipRegistration>' # Build our auth headers authheaders = { "APIUSER": ANIBASE_API_USER, "APIKEY": ANIBASE_API_KEY } try: # Post the VetXML document self.log( "Posting microchip registration document to %s \n%s\n" % (ANIBASE_BASE_URL, x)) r = utils.post_xml(ANIBASE_BASE_URL, x, authheaders) self.log("Response %d, HTTP headers: %s, body: %s" % (r["status"], r["headers"], r["response"])) if r["status"] != 200: raise Exception(r["response"]) # Look in the headers for successful results wassuccess = False SUCCESS = ("54000", "54100", "54108") for code in SUCCESS: if str(r["headers"]).find(code) != -1: self.log( "successful %s response header found, marking processed" % code) processed_animals.append(an) # Mark success in the log self.logSuccess( "Processed: %s: %s (%d of %d)" % (an["SHELTERCODE"], an["ANIMALNAME"], anCount, len(animals))) wassuccess = True break # If we got a chipfound=false or chipRegisterable=false message, mark the chip as processed # and a success so we don't try and register it in future if str( r["response"] ).find("<chipFound>false</chipFound>") != -1 or str( r["response"] ).find("<chipRegisterable>false</chipRegisterable>") != -1: self.log( "chipFound=false/chipRegisterable=false response found, marking chip processed to prevent future sending" ) processed_animals.append(an) wassuccess = True # If we saw an account not found message, there's no point sending # anything else as they will all trigger the same error if str(r["headers"]).find("54101") != -1: self.logError( "received Anibase 54101 'sender not recognised' response header - abandoning run" ) break if not wassuccess: self.logError( "no successful response header %s received" % str(SUCCESS)) except Exception as err: em = str(err) self.logError("Failed registering microchip: %s" % em, sys.exc_info()) continue except Exception as err: self.logError( "Failed processing animal: %s, %s" % (str(an["SHELTERCODE"]), err), sys.exc_info()) # Only mark processed if we aren't using Anibase test URL if len(processed_animals) > 0 and ANIBASE_BASE_URL.find("test") == -1: self.log("successfully processed %d animals, marking sent" % len(processed_animals)) self.markAnimalsPublished(processed_animals) if ANIBASE_BASE_URL.find("test") != -1: self.log("Anibase test mode, not marking animals published") self.saveLog() self.setPublisherComplete()
def web_login(post, session, remoteip, path): """ Performs a login and sets up the user's session. Returns the username on successful login, or: FAIL - problem with user/pass/account/ip DISABLED - The database is disabled """ dbo = db.DatabaseInfo() database = post["database"] username = post["username"] password = post["password"] nologconnection = post["nologconnection"] # Do we have multiple databases? if MULTIPLE_DATABASES: if MULTIPLE_DATABASES_TYPE == "smcom": # Is this sheltermanager.com? If so, we need to get the # database connection info (dbo) before we can login. # If a database hasn't been supplied, let's bail out now # since we can't do anything if str(database).strip() == "": return "FAIL" else: dbo = smcom.get_database_info(database) # Bail out if there was a problem with the database if dbo.database == "FAIL" or dbo.database == "DISABLED": return dbo.database else: # Look up the database info from our map dbo = db.get_multiple_database_info(database) if dbo.database == "FAIL": return dbo.database # Connect to the database and authenticate the username and password user = authenticate(dbo, username, password) if user is not None and not authenticate_ip(user, remoteip): al.error( "user %s with ip %s failed ip restriction check '%s'" % (username, remoteip, user["IPRESTRICTION"]), "users.web_login", dbo) return "FAIL" if user is not None: al.info("%s successfully authenticated from %s" % (username, remoteip), "users.web_login", dbo) try: dbo.locked = configuration.smdb_locked(dbo) dbo.timezone = configuration.timezone(dbo) dbo.installpath = path session.locale = configuration.locale(dbo) dbo.locale = session.locale session.dbo = dbo session.user = user["USERNAME"] session.superuser = user["SUPERUSER"] session.passchange = (password == "password") update_session(session) except: al.error("failed setting up session: %s" % str(sys.exc_info()[0]), "users.web_login", dbo, sys.exc_info()) return "FAIL" try: session.securitymap = get_security_map(dbo, user["USERNAME"]) except: # This is a pre-3002 login where the securitymap is with # the user (the error occurs because there's no role table) al.debug("role table does not exist, using securitymap from user", "users.web_login", dbo) session.securitymap = user["SECURITYMAP"] try: ur = get_users(dbo, user["USERNAME"])[0] session.roles = ur["ROLES"] session.roleids = ur["ROLEIDS"] session.locationfilter = utils.nulltostr(user["LOCATIONFILTER"]) except: # Users coming from v2 won't have the # IPRestriction or EmailAddress fields necessary for get_users - we can't # help them right now so just give them an empty set of # roles and locationfilter until they login again after the db update session.roles = "" session.roleids = "" session.locationfilter = "" try: # If it's a sheltermanager.com database, try and update the # last time the user connected to today if smcom.active() and database != "" and nologconnection == "": smcom.set_last_connected(dbo) except: pass try: # Check to see if any updates need performing on this database if dbupdate.check_for_updates(dbo): dbupdate.perform_updates(dbo) # We did some updates, better reload just in case config/reports/etc changed update_session(session) # Check to see if our views and sequences are out of date and need reloading if dbupdate.check_for_view_seq_changes(dbo): dbupdate.install_db_views(dbo) dbupdate.install_db_sequences(dbo) except: al.error("failed updating database: %s" % str(sys.exc_info()[0]), "users.web_login", dbo, sys.exc_info()) try: # Log out any old users that have been hanging around auto_logout(dbo) # Let this user through login(dbo, user["USERNAME"]) except: al.error( "failed updating activeuser table: %s" % str(sys.exc_info()[0]), "users.web_login", dbo, sys.exc_info()) return "FAIL" else: al.error( "database:%s username:%s password:%s failed authentication from %s" % (database, username, password, remoteip), "users.web_login", dbo) return "FAIL" return user["USERNAME"]
def run(self): self.log("Maddies Fund Publisher starting...") BATCH_SIZE = 250 # How many animals to send in one POST PERIOD = 214 # How many days to go back when checking for fosters and adoptions (7 months * 30.5 = 214 days) if self.isPublisherExecuting(): return self.updatePublisherProgress(0) self.setLastError("") self.setStartPublishing() username = configuration.maddies_fund_username(self.dbo) password = configuration.maddies_fund_password(self.dbo) organisation = configuration.organisation(self.dbo) if username == "" or password == "": self.setLastError( "username and password all need to be set for Maddies Fund Publisher" ) self.cleanup() return # Send all fosters and adoptions for the period that haven't been sent since they last had a change. # (we use lastchangeddate instead of sent date because MPA want an update when a number of key # animal fields change, such as neuter status, microchip info, rabies tag, etc) cutoff = i18n.subtract_days(i18n.now(self.dbo.timezone), PERIOD) sql = "%s WHERE a.ActiveMovementType IN (1,2) " \ "AND a.ActiveMovementDate >= ? AND a.DeceasedDate Is Null AND a.NonShelterAnimal = 0 " \ "AND NOT EXISTS(SELECT AnimalID FROM animalpublished WHERE AnimalID = a.ID AND PublishedTo = 'maddiesfund' AND SentDate >= %s) " \ "ORDER BY a.ID" % (animal.get_animal_query(self.dbo), self.dbo.sql_greatest(["a.ActiveMovementDate", "a.LastChangedDate"])) animals = self.dbo.query(sql, [cutoff], distincton="ID") # Now find animals who have been sent previously and are now deceased (using sent date against deceased to prevent re-sends) sql = "%s WHERE a.DeceasedDate Is Not Null AND a.DeceasedDate >= ? AND " \ "EXISTS(SELECT AnimalID FROM animalpublished WHERE AnimalID = a.ID AND " \ "PublishedTo = 'maddiesfund' AND SentDate < a.DeceasedDate)" % animal.get_animal_query(self.dbo) animals += self.dbo.query(sql, [cutoff], distincton="ID") # Now find shelter animals who have been sent previously and are back (using sent date against return to prevent re-sends) sql = "%s WHERE a.Archived = 0 AND " \ "EXISTS(SELECT AnimalID FROM animalpublished WHERE AnimalID = a.ID AND " \ "PublishedTo = 'maddiesfund' AND SentDate < " \ "(SELECT MAX(ReturnDate) FROM adoption WHERE AnimalID = a.ID AND MovementType IN (1,2) AND ReturnDate Is Not Null))" % animal.get_animal_query(self.dbo) animals += self.dbo.query(sql, distincton="ID") # Now find animals who have been sent previously and have a new/changed vaccination since then sql = "%s WHERE a.Archived = 0 AND " \ "EXISTS(SELECT p.AnimalID FROM animalpublished p INNER JOIN animalvaccination av ON av.AnimalID = a.ID WHERE p.AnimalID = a.ID AND " \ "p.PublishedTo = 'maddiesfund' AND (p.SentDate < av.CreatedDate OR p.SentDate < av.LastChangedDate))" % animal.get_animal_query(self.dbo) animals += self.dbo.query(sql, [cutoff], distincton="ID") if len(animals) == 0: self.setLastError("No animals found to publish.") return # Get an authentication token token = "" try: fields = { "username": username, "password": password, "grant_type": "password" } r = utils.post_form(MADDIES_FUND_TOKEN_URL, fields) token = utils.json_parse(r["response"])["access_token"] self.log("got access token: %s (%s)" % (token, r["response"])) except Exception as err: self.setLastError( "failed to get access token: %s (request: '%s') (response: '%s')" % (err, r["requestbody"], r["response"])) self.cleanup() return anCount = 0 thisbatch = [] processed = [] for an in animals: try: anCount += 1 self.log("Processing: %s: %s (%d of %d)" % (an["SHELTERCODE"], an["ANIMALNAME"], anCount, len(animals))) self.updatePublisherProgress( self.getProgress(anCount, len(animals))) # If the user cancelled, stop now if self.shouldStopPublishing(): self.log("User cancelled publish. Stopping.") self.resetPublisherProgress() return # Build an adoption JSON object containing the adopter and animal a = { "PetID": an["ID"], "PetCode": an["SHELTERCODE"], "Site": organisation, "PetName": an["ANIMALNAME"], "PetStatus": self.getPetStatus(an), "PetLitterID": an["ACCEPTANCENUMBER"], "GroupType": utils.iif( utils.nulltostr(an["ACCEPTANCENUMBER"]) != "", "Litter", ""), "PetSpecies": an["SPECIESNAME"], "PetSex": an["SEXNAME"], "DateofBirth": self.getDate(an["DATEOFBIRTH"]), "SpayNeuterStatus": utils.iif(an["NEUTERED"] == 1, "Spayed/Neutered", ""), "Breed": an["BREEDNAME"], "Color": an["BASECOLOURNAME"], "SecondaryColor": "", "Pattern": "", "HealthStatus": an["ASILOMARINTAKECATEGORY"] + 1, # We're zero based, they use 1-base "PetBiography": an["ANIMALCOMMENTS"], "Photo": "%s?method=animal_image&account=%s&animalid=%s" % (SERVICE_URL, self.dbo.database, an["ID"]), "Microchip": an["IDENTICHIPNUMBER"], "MicrochipIssuer": lookups.get_microchip_manufacturer(self.dbo.locale, an["IDENTICHIPNUMBER"]), "RelationshipType": self.getRelationshipType(an), "FosterCareDate": self.getDate(an["ACTIVEMOVEMENTDATE"]), "FosterEndDate": "", "RabiesTag": an["RABIESTAG"], "ID": an["CURRENTOWNERID"], "Firstname": an["CURRENTOWNERFORENAMES"], "Lastname": an["CURRENTOWNERSURNAME"], "EmailAddress": self.getEmail(an["CURRENTOWNEREMAILADDRESS"]), "Street": an["CURRENTOWNERADDRESS"], "Apartment": "", "City": an["CURRENTOWNERTOWN"], "State": an["CURRENTOWNERCOUNTY"], "Zipcode": an["CURRENTOWNERPOSTCODE"], "ContactNumber": an["CURRENTOWNERHOMETELEPHONE"], "Organization": organisation, } # Build a list of intake histories - use the initial one first ph = [{ "IntakeType": an["ENTRYREASONNAME"], "IntakeDate": self.getDate(an["DATEBROUGHTIN"]), "City": utils.nulltostr(an["BROUGHTINBYOWNERTOWN"]), "State": utils.nulltostr(an["BROUGHTINBYOWNERCOUNTY"]), "LengthOwned": "" }] # Then any exit movements where the animal was returned for ra in movement.get_animal_movements(self.dbo, an["ID"]): if ra["MOVEMENTTYPE"] > 0 and ra["MOVEMENTTYPE"] not in ( 2, 8) and ra["RETURNDATE"] is not None: ph.append({ "IntakeType": ra["RETURNEDREASONNAME"], "IntakeDate": self.getDate(ra["RETURNDATE"]), "City": utils.nulltostr(ra["OWNERTOWN"]), "State": utils.nulltostr(ra["OWNERCOUNTY"]), "LengthOwned": "" # We don't have this info }) a["PetHistoryDetails"] = ph # Next add vaccination histories vh = [] for v in medical.get_vaccinations(self.dbo, an["ID"]): vh.append({ "VaccinationRecordNumber": str(v["ID"]), "VaccinationStatus": utils.iif(v["DATEOFVACCINATION"] is not None, "Completed", "Scheduled"), "VaccinationStatusDateTime": self.getDate(v["DATEREQUIRED"]), "Vaccine": v["VACCINATIONTYPE"], "Type": "", # Live/Killed - we don't keep this info yet, see issue #281 "Manufacturer": utils.nulltostr(v["MANUFACTURER"]), "VaccineLot": utils.nulltostr(v["BATCHNUMBER"]), "VaccinationNotes": v["COMMENTS"], "Length": "", # Not sure what this value is for - advised to ignore by MPA devs "RevaccinationDate": self.getDate(v["DATEEXPIRES"]) }) a["PetVaccinationDetails"] = vh thisbatch.append(a) processed.append(an) self.logSuccess("Processed: %s: %s (%d of %d)" % (an["SHELTERCODE"], an["ANIMALNAME"], anCount, len(animals))) # If we have hit our batch size, or this is the # last animal then send what we have. if len(thisbatch) == BATCH_SIZE or anCount == len(animals): j = utils.json({"Animals": thisbatch}) headers = {"Authorization": "Bearer %s" % token} self.log( "HTTP POST request %s: headers: '%s', body: '%s'" % (MADDIES_FUND_UPLOAD_URL, headers, j)) r = utils.post_json(MADDIES_FUND_UPLOAD_URL, j, headers) if r["status"] != 200: self.logError("HTTP %d response: %s" % (r["status"], r["response"])) else: self.log("HTTP %d response: %s" % (r["status"], r["response"])) self.markAnimalsPublished(processed) # start counting again thisbatch = [] processed = [] except Exception as err: self.logError( "Failed processing animal: %s, %s" % (an["SHELTERCODE"], err), sys.exc_info()) self.cleanup()
def insert_adoption_from_form(dbo, username, data, creating = []): """ Inserts a movement from the workflow adopt an animal screen. Returns the new movement id creating is an ongoing list of animals we're already going to create adoptions for. It prevents a never ending recursive loop of animal1 being bonded to animal2 that's bonded to animal1, etc. """ l = dbo.locale # Validate that we have a movement date before doing anthing if None == utils.df_kd(data, "movementdate", l): raise utils.ASMValidationError(i18n._("Adoption movements must have a valid adoption date.", l)) # Get the animal record for this adoption a = animal.get_animal(dbo, utils.df_ki(data, "animal")) if a is None: raise utils.ASMValidationError("Adoption POST has an invalid animal ID: %d" % utils.df_ki(data, "animal")) al.debug("Creating adoption for %d (%s - %s)" % (a["ID"], a["SHELTERCODE"], a["ANIMALNAME"]), "movement.insert_adoption_from_form", dbo) creating.append(a["ID"]) # If the animal is bonded to other animals, we call this function # again with a copy of the data and the bonded animal substituted # so we can create their adoption records too. if a["BONDEDANIMALID"] is not None and a["BONDEDANIMALID"] != 0 and a["BONDEDANIMALID"] not in creating: al.debug("Found bond to animal %d, creating adoption..." % a["BONDEDANIMALID"], "movement.insert_adoption_from_form", dbo) newdata = dict(data) newdata["animal"] = str(a["BONDEDANIMALID"]) insert_adoption_from_form(dbo, username, newdata, creating) if a["BONDEDANIMAL2ID"] is not None and a["BONDEDANIMAL2ID"] != 0 and a["BONDEDANIMAL2ID"] not in creating: al.debug("Found bond to animal %d, creating adoption..." % a["BONDEDANIMAL2ID"], "movement.insert_adoption_from_form", dbo) newdata = dict(data) newdata["animal"] = str(a["BONDEDANIMAL2ID"]) insert_adoption_from_form(dbo, username, newdata, creating) cancel_reserves = configuration.cancel_reserves_on_adoption(dbo) # Prepare a dictionary of data for the movement table via insert_movement_from_form move_dict = { "person" : utils.df_ks(data, "person"), "animal" : utils.df_ks(data, "animal"), "adoptionno" : utils.df_ks(data, "movementnumber"), "movementdate" : utils.df_ks(data, "movementdate"), "type" : str(ADOPTION), "donation" : utils.df_ks(data, "amount"), "insurance" : utils.df_ks(data, "insurance"), "returncategory" : configuration.default_return_reason(dbo), "trial" : utils.df_ks(data, "trial"), "trialenddate" : utils.df_ks(data, "trialenddate") } # Is this animal currently on foster? If so, return the foster fm = get_animal_movements(dbo, utils.df_ki(data, "animal")) for m in fm: if m["MOVEMENTTYPE"] == FOSTER and m["RETURNDATE"] is None: return_movement(dbo, m["ID"], utils.df_ki(data, "animal"), utils.df_kd(data, "movementdate", l)) # Is this animal current at a retailer? If so, return it from the # retailer and set the originalretailermovement and retailerid fields # on our new adoption movement so it can be linked back for m in fm: if m["MOVEMENTTYPE"] == RETAILER and m["RETURNDATE"] is None: return_movement(dbo, m["ID"], utils.df_ki(data, "animal"), utils.df_kd(data, "movementdate", l)) move_dict["originalretailermovement"] = str(m["ID"]) move_dict["retailer"] = str(m["OWNERID"]) # Did we say we'd like to flag the owner as homechecked? if utils.df_kc(data, "homechecked") == 1: db.execute(dbo, "UPDATE owner SET IDCheck = 1, DateLastHomeChecked = %s WHERE ID = %d" % \ ( db.dd(i18n.now(dbo.timezone)), utils.df_ki(data, "person"))) # If the animal was flagged as not available for adoption, then it # shouldn't be since we've just adopted it. db.execute(dbo, "UPDATE animal SET IsNotAvailableForAdoption = 0 WHERE ID = %s" % utils.df_ks(data, "animal")) # Is the animal reserved to the person adopting? movementid = 0 for m in fm: if m["MOVEMENTTYPE"] == NO_MOVEMENT and m["RESERVATIONDATE"] is not None \ and m["RESERVATIONCANCELLEDDATE"] is None and m["ANIMALID"] == utils.df_ki(data, "animal") \ and m["OWNERID"] == utils.df_ki(data, "person"): # yes - update the existing movement movementid = m["ID"] move_dict["movementid"] = str(movementid) move_dict["adoptionno"] = utils.padleft(movementid, 6) move_dict["reservationdate"] = str(i18n.python2display(l, m["RESERVATIONDATE"])) move_dict["comments"] = utils.nulltostr(m["COMMENTS"]) break elif cancel_reserves and m["MOVEMENTTYPE"] == NO_MOVEMENT and m["RESERVATIONDATE"] is not None \ and m["RESERVATIONCANCELLEDDATE"] is None: # no, but it's reserved to someone else and we're cancelling # reserves on adoption db.execute(dbo, "UPDATE adoption SET ReservationCancelledDate = %s WHERE ID = %d" % \ ( utils.df_d(data, "movementdate", l), m["ID"] )) if movementid != 0: update_movement_from_form(dbo, username, move_dict) else: movementid = insert_movement_from_form(dbo, username, move_dict) # Create the donation if there is one donation_amount = int(utils.df_m(data, "amount", l)) if donation_amount > 0: due = "" received = utils.df_ks(data, "movementdate") if configuration.movement_donations_default_due(dbo): due = utils.df_ks(data, "movementdate") received = "" don_dict = { "person" : utils.df_ks(data, "person"), "animal" : utils.df_ks(data, "animal"), "movement" : str(movementid), "type" : utils.df_ks(data, "donationtype"), "payment" : utils.df_ks(data, "payment"), "frequency" : "0", "amount" : utils.df_ks(data, "amount"), "due" : due, "received" : received, "giftaid" : utils.df_ks(data, "giftaid") } financial.insert_donation_from_form(dbo, username, don_dict) # And a second donation if there is one donation_amount = int(utils.df_m(data, "amount2", l)) if donation_amount > 0: due = "" received = utils.df_ks(data, "movementdate") if configuration.movement_donations_default_due(dbo): due = utils.df_ks(data, "movementdate") received = "" don_dict = { "person" : utils.df_ks(data, "person"), "animal" : utils.df_ks(data, "animal"), "movement" : str(movementid), "type" : utils.df_ks(data, "donationtype2"), "payment" : utils.df_ks(data, "payment2"), "frequency" : "0", "amount" : utils.df_ks(data, "amount2"), "due" : due, "received" : received, "giftaid" : utils.df_ks(data, "giftaid") } financial.insert_donation_from_form(dbo, username, don_dict) # Then any boarding cost record cost_amount = int(utils.df_m(data, "costamount", l)) cost_type = utils.df_ks(data, "costtype") cost_create = utils.df_ki(data, "costcreate") if cost_amount > 0 and cost_type != "" and cost_create == 1: boc_dict = { "animalid" : utils.df_ks(data, "animal"), "type" : cost_type, "costdate" : utils.df_ks(data, "movementdate"), "cost" : utils.df_ks(data, "costamount") } animal.insert_cost_from_form(dbo, username, boc_dict) return movementid
def run(self): self.log("PetLinkPublisher starting...") if self.isPublisherExecuting(): return self.updatePublisherProgress(0) self.setLastError("") self.setStartPublishing() plemail = configuration.petlink_email(self.dbo) plowneremail = configuration.petlink_owner_email(self.dbo) password = configuration.petlink_password(self.dbo) if plemail == "" or password == "": self.setLastError("No PetLink login has been set.") return animals = get_microchip_data(self.dbo, [ '98102', ], "petlink", organisation_email=plowneremail) if len(animals) == 0: self.setLastError("No animals found to publish.") return anCount = 0 csv = [] processed_animals = [] failed_animals = [] csv.append("Software,TransactionType,MicrochipID,FirstName,LastName,Address,City,State,ZipCode,Country," \ "Phone1,Phone2,Phone3,Email,Password,Date_of_Implant,PetName,Species,Breed,Gender," \ "Spayed_Neutered,ColorMarkings") for an in animals: try: line = [] anCount += 1 self.log("Processing: %s: %s (%d of %d) - %s %s" % \ ( an["SHELTERCODE"], an["ANIMALNAME"], anCount, len(animals), an["IDENTICHIPNUMBER"], an["CURRENTOWNERNAME"] )) self.updatePublisherProgress( self.getProgress(anCount, len(animals))) # If the user cancelled, stop now if self.shouldStopPublishing(): self.log("User cancelled publish. Stopping.") self.resetPublisherProgress() return # If the microchip number isn't 15 digits, skip it if len(an["IDENTICHIPNUMBER"].strip()) != 15: self.logError( "Chip number failed validation (%s not 15 digits), skipping." % an["IDENTICHIPNUMBER"]) continue # Validate certain items aren't blank so we aren't trying to register # things that PetLink will bounce back anyway if utils.nulltostr(an["CURRENTOWNERTOWN"]).strip() == "": self.logError( "City for the new owner is blank, cannot process") continue if utils.nulltostr(an["CURRENTOWNERCOUNTY"]).strip() == "": self.logError( "State for the new owner is blank, cannot process") continue if utils.nulltostr(an["CURRENTOWNERPOSTCODE"]).strip() == "": self.logError( "Zipcode for the new owner is blank, cannot process") continue # If there's no email or phone, PetLink won't accept it email = utils.nulltostr(an["CURRENTOWNEREMAILADDRESS"]).strip() homephone = utils.nulltostr( an["CURRENTOWNERHOMETELEPHONE"]).strip() workphone = utils.nulltostr( an["CURRENTOWNERWORKTELEPHONE"]).strip() mobilephone = utils.nulltostr( an["CURRENTOWNERMOBILETELEPHONE"]).strip() if email == "" and homephone == "" and workphone == "" and mobilephone == "": self.logError( "No email address or phone number for owner, skipping." ) continue # If there's no phone, we can't set the chip password so skip it if homephone == "" and workphone == "" and mobilephone == "": self.logError("No phone number for owner, skipping.") continue # Get the phone number and strip it of non-numeric data phone = homephone or mobilephone or workphone phone = "".join(c for c in phone if c.isdigit()) # If we don't have an email address, use [email protected] if email == "": email = "*****@*****.**" % phone # Software line.append("\"ASM\"") # TransactionType line.append( "\"%s\"" % (self.getLastPublishedDate(an["ID"]) is None and 'N' or 'T')) # MicrochipID line.append("\"%s\"" % (an["IDENTICHIPNUMBER"])) # FirstName line.append("\"%s\"" % (an["CURRENTOWNERFORENAMES"])) # LastName line.append("\"%s\"" % (an["CURRENTOWNERSURNAME"])) # Address line.append("\"%s\"" % (an["CURRENTOWNERADDRESS"])) # City line.append("\"%s\"" % (an["CURRENTOWNERTOWN"])) # State line.append("\"%s\"" % (an["CURRENTOWNERCOUNTY"])) # ZipCode line.append("\"%s\"" % (an["CURRENTOWNERPOSTCODE"])) # Country line.append("\"USA\"") # Phone1 line.append("\"%s\"" % (an["CURRENTOWNERHOMETELEPHONE"])) # Phone2 line.append("\"%s\"" % (an["CURRENTOWNERWORKTELEPHONE"])) # Phone3 line.append("\"%s\"" % (an["CURRENTOWNERMOBILETELEPHONE"])) # Email (mandatory) line.append("\"%s\"" % (email)) # Chip Password (stripped phone number) line.append("\"%s\"" % phone) # Date_of_Implant (yy-mm-dd) line.append("\"%s\"" % i18n.format_date("%y-%m-%d", an["IDENTICHIPDATE"])) # PetName line.append("\"%s\"" % an["ANIMALNAME"]) # Species line.append("\"%s\"" % an["SPECIESNAME"]) # Breed (or "Mixed Breed" for crossbreeds, Other for animals not cats and dogs) line.append("\"%s\"" % self.plBreed( an["BREEDNAME1"], an["SPECIESNAME"], an["CROSSBREED"])) # Gender line.append("\"%s\"" % an["SEXNAME"]) # Spayed_Neutered (y or n) line.append("\"%s\"" % self.plYesNo(an["NEUTERED"])) # ColorMarkings (our BaseColour field) line.append("\"%s\"" % an["BASECOLOURNAME"]) # Add to our data file. csv.append(",".join(line)) # Remember we included this one processed_animals.append(an) # Mark success in the log self.logSuccess("Processed: %s: %s (%d of %d)" % (an["SHELTERCODE"], an["ANIMALNAME"], anCount, len(animals))) except Exception as err: self.logError( "Failed processing animal: %s, %s" % (str(an["SHELTERCODE"]), err), sys.exc_info()) # If we excluded our only animals and have nothing to send, stop now if len(csv) == 1: self.log("No animals left to send to PetLink.") self.saveLog() self.setPublisherComplete() return # POST the csv data headers = { "Authorization": "Basic %s" % base64.b64encode("%s:%s" % (plemail, password)), "Content-Type": "text/csv" } self.log("Uploading data file (%d csv lines) to %s..." % (len(csv), UPLOAD_URL)) try: r = utils.post_data(UPLOAD_URL, "\n".join(csv), headers=headers) response = r["response"].decode("utf-8").encode( "ascii", "xmlcharrefreplace") self.log("req hdr: %s, \nreq data: %s" % (r["requestheaders"], r["requestbody"])) self.log("resp hdr: %s, \nresp body: %s" % (r["headers"], response)) jresp = utils.json_parse(response) # Look for remote exceptions if "exception" in jresp and jresp["exception"] != "": self.successes = 0 self.logError( "PetLink remote exception received, abandoning: %s" % jresp["exception"]) self.saveLog() self.setPublisherComplete() return # Parse any errors in the JSON response for e in jresp["errors"]: chip = "" message = "" try: # Prefer microchip= in location column, then fall back to id=/microchip= in column if e["location"].find("microchip=") != -1: chip = e["location"] chip = chip[chip.find("microchip=") + 10:] elif e["column"].find("id=") != -1: chip = e["column"].replace("id=", "") elif e["column"].find("microchip=") != -1: chip = e["column"].replace("microchip=", "") message = e["message"] except Exception as erj: try: self.logError( "Failed unpacking error message (message=%s) from '%s'" % (erj, e)) except: self.logError( "Failed decoding error message from PetLink") continue # Iterate over a copy of the processed list so we can remove animals from it # that have error emssages. for an in processed_animals[:]: if an["IDENTICHIPNUMBER"] == chip: processed_animals.remove(an) try: self.logError("%s: %s (%s) - Received error message from PetLink: %s" % \ (an["SHELTERCODE"], an["ANIMALNAME"], an["IDENTICHIPNUMBER"], message)) except Exception as erm: self.logError("%s: %s (%s) - Error decoding message from PetLink" % \ (an["SHELTERCODE"], an["ANIMALNAME"], an["IDENTICHIPNUMBER"])) continue # If the message was that the chip is already registered, # mark this animal as published but on the intake date - # this will force this publisher to put it through as a transfer # next time since we'll find a previous published record. if message.find("has already been registered") != -1: self.markAnimalPublished(an["ID"], an["DATEBROUGHTIN"]) self.log("%s: %s (%s) - Already registered, marking as PetLink TRANSFER for next publish" % \ (an["SHELTERCODE"], an["ANIMALNAME"], an["IDENTICHIPNUMBER"])) continue # If the message is one of PetLink's permanent failure conditons, # mark the chip failed so that we stop trying. # Any other error message we treat as transient and do nothing # (which means we'll attempt to register the chip the next time this publisher is run). PERMANENT_FAILURES = [ "has an existing PetLink account and their contact information is already on file", "Not a prepaid microchip", "This is a Found Animals Registry chip", "cannot be transferred - the account is locked.", "Microchip number has already been registered in the database" ] for m in PERMANENT_FAILURES: if message.find(m) != -1: an["FAILMESSAGE"] = message failed_animals.append(an) if len(processed_animals) > 0: self.markAnimalsPublished(processed_animals) if len(failed_animals) > 0: self.markAnimalsPublishFailed(failed_animals) except Exception as err: self.logError("Failed uploading data file: %s" % err) self.saveLog() self.setPublisherComplete()
def insert_adoption_from_form(dbo, username, post, creating = [], create_payments = True): """ Inserts a movement from the workflow adopt an animal screen. Returns the new movement id creating is an ongoing list of animals we're already going to create adoptions for. It prevents a never ending recursive loop of animal1 being bonded to animal2 that's bonded to animal1, etc. create_payments is True if we should create payments - don't do this for bonded animals or we'll double up all the payments. """ l = dbo.locale # Validate that we have a movement date before doing anthing if None is post.date("movementdate"): raise utils.ASMValidationError(i18n._("Adoption movements must have a valid adoption date.", l)) # Get the animal record for this adoption a = animal.get_animal(dbo, post.integer("animal")) if a is None: raise utils.ASMValidationError("Adoption POST has an invalid animal ID: %d" % post.integer("animal")) al.debug("Creating adoption for %d (%s - %s)" % (a["ID"], a["SHELTERCODE"], a["ANIMALNAME"]), "movement.insert_adoption_from_form", dbo) creating.append(a.ID) # If the animal is bonded to other animals, we call this function # again with a copy of the data and the bonded animal substituted # so we can create their adoption records too. We only do this if # the other animals are still on shelter (therefore alive). if a.BONDEDANIMALID is not None and a.BONDEDANIMALID != 0 and a.BONDEDANIMAL1ARCHIVED == 0 and a.BONDEDANIMALID not in creating: al.debug("Found bond to animal %d, creating adoption..." % a.BONDEDANIMALID, "movement.insert_adoption_from_form", dbo) newdata = dict(post.data) newdata["animal"] = str(a.BONDEDANIMALID) insert_adoption_from_form(dbo, username, utils.PostedData(newdata, dbo.locale), creating, create_payments = False) if a.BONDEDANIMAL2ID is not None and a.BONDEDANIMAL2ID != 0 and a.BONDEDANIMAL2ARCHIVED == 0 and a.BONDEDANIMAL2ID not in creating: al.debug("Found bond to animal %d, creating adoption..." % a.BONDEDANIMAL2ID, "movement.insert_adoption_from_form", dbo) newdata = dict(post.data) newdata["animal"] = str(a.BONDEDANIMAL2ID) insert_adoption_from_form(dbo, username, utils.PostedData(newdata, dbo.locale), creating, create_payments = False) cancel_reserves = configuration.cancel_reserves_on_adoption(dbo) # Prepare a dictionary of data for the movement table via insert_movement_from_form move_dict = { "person" : post["person"], "animal" : post["animal"], "adoptionno" : post["movementnumber"], "movementdate" : post["movementdate"], "type" : str(ADOPTION), "donation" : post["amount"], "insurance" : post["insurance"], "returncategory" : configuration.default_return_reason(dbo), "trial" : post["trial"], "trialenddate" : post["trialenddate"] } # Is this animal currently on foster? If so, return the foster fm = get_animal_movements(dbo, post.integer("animal")) for m in fm: if m.MOVEMENTTYPE == FOSTER and m.RETURNDATE is None: return_movement(dbo, m["ID"], post.integer("animal"), post.date("movementdate")) # Is this animal current at a retailer? If so, return it from the # retailer and set the originalretailermovement and retailerid fields # on our new adoption movement so it can be linked back for m in fm: if m.MOVEMENTTYPE == RETAILER and m.RETURNDATE is None: return_movement(dbo, m.ID, post.integer("animal"), post.date("movementdate")) move_dict["originalretailermovement"] = str(m.ID) move_dict["retailer"] = str(m["OWNERID"]) # Did we say we'd like to flag the owner as homechecked? if post.boolean("homechecked") == 1: dbo.update("owner", post.integer("person"), { "IDCheck": 1, "DateLastHomeChecked": dbo.today() }, username) # If the animal was flagged as not available for adoption, then it # shouldn't be since we've just adopted it. dbo.update("animal", a.ID, { "IsNotAvailableForAdoption": 0 }) # Is the animal reserved to the person adopting? movementid = 0 for m in fm: if m.MOVEMENTTYPE == NO_MOVEMENT and m.RESERVATIONDATE is not None \ and m.RESERVATIONCANCELLEDDATE is None and m.ANIMALID == post.integer("animal") \ and m.OWNERID == post.integer("person"): # yes - update the existing movement movementid = m.ID move_dict["movementid"] = str(movementid) move_dict["adoptionno"] = utils.padleft(movementid, 6) move_dict["reservationdate"] = str(i18n.python2display(l, m.RESERVATIONDATE)) move_dict["comments"] = utils.nulltostr(m.COMMENTS) break elif cancel_reserves and m.MOVEMENTTYPE == NO_MOVEMENT and m.RESERVATIONDATE is not None \ and m.RESERVATIONCANCELLEDDATE is None: # no, but it's reserved to someone else and we're cancelling # reserves on adoption dbo.update("adoption", m.ID, { "ReservationCancelledDate": post.date("movementdate") }, username) if movementid != 0: update_movement_from_form(dbo, username, utils.PostedData(move_dict, l)) else: movementid = insert_movement_from_form(dbo, username, utils.PostedData(move_dict, l)) # Create any payments if create_payments: financial.insert_donations_from_form(dbo, username, post, post["movementdate"], False, post["person"], post["animal"], movementid) # Then any boarding cost record cost_amount = post.integer("costamount") cost_type = post["costtype"] cost_create = post.integer("costcreate") if cost_amount > 0 and cost_type != "" and cost_create == 1: boc_dict = { "animalid" : post["animal"], "type" : cost_type, "costdate" : post["movementdate"], "costpaid" : post["movementdate"], "cost" : post["costamount"] } animal.insert_cost_from_form(dbo, username, utils.PostedData(boc_dict, l)) return movementid