def insert_payment_from_appointment(dbo, username, appointmentid, post): """ Creates a payment record from an appointment via the create payment dialog. """ l = dbo.locale c = get_appointment(dbo, appointmentid) d = { "person": str(c.OwnerID), "animal": str(c.AnimalID), "type": post["paymenttype"], "payment": post["paymentmethod"], "amount": str(c.Amount), "due": post["due"], "received": post["received"], "vat": utils.iif(c.IsVAT == 1, "on", ""), "vatrate": str(c.VATRate), "vatamount": str(c.VATAmount), "comments": i18n._("Appointment {0}. {1} on {2} for {3}").format( utils.padleft(c.ID, 6), c.OWNERNAME, i18n.python2display(l, c.DATETIME), c.ANIMALNAME) } return financial.insert_donation_from_form(dbo, username, utils.PostedData(d, l))
def handler(post, path, remoteip, referer, querystring): """ Handles the various service method types. post: The GET/POST parameters path: The current system path/code.PATH remoteip: The IP of the caller referer: The referer HTTP header querystring: The complete querystring return value is a tuple containing MIME type, max-age, content """ # Get service parameters account = post["account"] username = post["username"] password = post["password"] method = post["method"] animalid = post.integer("animalid") formid = post.integer("formid") seq = post.integer("seq") title = post["title"] strip_personal = post.integer("sensitive") == 0 cache_key = querystring.replace(" ", "") # Do we have a cached response for these parameters? cached_response = get_cached_response(cache_key) if cached_response is not None: al.debug("cache hit for %s" % (cache_key), "service.handler") return cached_response # Are we dealing with multiple databases, but no account was specified? if account == "" and MULTIPLE_DATABASES: return ("text/plain", 0, 0, "ERROR: No database/alias specified") dbo = db.get_database(account) if dbo.database in ("FAIL", "DISABLED", "WRONGSERVER"): al.error( "auth failed - invalid smaccount %s from %s (%s)" % (account, remoteip, dbo.database), "service.handler", dbo) return ("text/plain", 0, 0, "ERROR: Invalid database (%s)" % dbo.database) # If the database has disabled the service API, stop now if not configuration.service_enabled(dbo): al.error("Service API is disabled (%s)" % method, "service.handler", dbo) return ("text/plain", 0, 0, "ERROR: Service API is disabled") # Do any database updates need doing in this db? dbo.installpath = path if dbupdate.check_for_updates(dbo): dbupdate.perform_updates(dbo) # Does the method require us to authenticate? If so, do it. user = None securitymap = "" if method in AUTH_METHODS: # If the database has authenticated service methods disabled, stop now if not configuration.service_auth_enabled(dbo): al.error("Service API for auth methods is disabled (%s)" % method, "service.handler", dbo) return ("text/plain", 0, 0, "ERROR: Service API for authenticated methods is disabled") user = users.authenticate(dbo, username, password) if user is None: al.error( "auth failed - %s/%s is not a valid username/password from %s" % (username, password, remoteip), "service.handler", dbo) return ("text/plain", 0, 0, "ERROR: Invalid username and password") securitymap = users.get_security_map(dbo, user["USERNAME"]) # Get the preferred locale and timezone for the site l = configuration.locale(dbo) dbo.locale = l dbo.timezone = configuration.timezone(dbo) al.info("call %s->%s [%s %s]" % (username, method, str(animalid), title), "service.handler", dbo) if method == "animal_image": hotlink_protect("animal_image", referer) if utils.cint(animalid) == 0: al.error( "animal_image failed, %s is not an animalid" % str(animalid), "service.handler", dbo) return ("text/plain", 0, 0, "ERROR: Invalid animalid") else: mediadate, data = media.get_image_file_data( dbo, "animal", utils.cint(animalid), seq) if data == "NOPIC": mediadate, data = media.get_image_file_data(dbo, "nopic", 0) return set_cached_response(cache_key, "image/jpeg", 86400, 3600, data) elif method == "animal_thumbnail": if utils.cint(animalid) == 0: al.error( "animal_thumbnail failed, %s is not an animalid" % str(animalid), "service.handler", dbo) return ("text/plain", 0, 0, "ERROR: Invalid animalid") else: mediadate, data = media.get_image_file_data( dbo, "animalthumb", utils.cint(animalid), seq) if data == "NOPIC": mediadate, data = media.get_image_file_data(dbo, "nopic", 0) return set_cached_response(cache_key, "image/jpeg", 86400, 86400, data) elif method == "animal_view": if utils.cint(animalid) == 0: al.error( "animal_view failed, %s is not an animalid" % str(animalid), "service.handler", dbo) return ("text/plain", 0, 0, "ERROR: Invalid animalid") else: return set_cached_response( cache_key, "text/html", 86400, 120, publishers.html.get_animal_view(dbo, utils.cint(animalid))) elif method == "animal_view_adoptable_js": return set_cached_response( cache_key, "application/javascript", 10800, 600, publishers.html.get_animal_view_adoptable_js(dbo)) elif method == "animal_view_adoptable_html": return set_cached_response( cache_key, "text/html", 86400, 120, publishers.html.get_animal_view_adoptable_html(dbo)) elif method == "dbfs_image": hotlink_protect("dbfs_image", referer) return set_cached_response( cache_key, "image/jpeg", 86400, 86400, utils.iif(title.startswith("/"), dbfs.get_string_filepath(dbo, title), dbfs.get_string(dbo, title))) elif method == "extra_image": hotlink_protect("extra_image", referer) return set_cached_response(cache_key, "image/jpeg", 86400, 86400, dbfs.get_string(dbo, title, "/reports")) elif method == "json_adoptable_animal": if utils.cint(animalid) == 0: al.error( "json_adoptable_animal failed, %s is not an animalid" % str(animalid), "service.handler", dbo) return ("text/plain", 0, 0, "ERROR: Invalid animalid") else: users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_ANIMAL) rs = publishers.base.get_animal_data( dbo, None, utils.cint(animalid), include_additional_fields=True) return set_cached_response(cache_key, "application/json", 3600, 3600, utils.json(rs)) elif method == "html_adoptable_animals": return set_cached_response(cache_key, "text/html", 10800, 1800, \ publishers.html.get_adoptable_animals(dbo, style=post["template"], \ speciesid=post.integer("speciesid"), animaltypeid=post.integer("animaltypeid"), locationid=post.integer("locationid"))) elif method == "html_adopted_animals": return set_cached_response(cache_key, "text/html", 10800, 1800, \ publishers.html.get_adopted_animals(dbo, daysadopted=post.integer("days"), style=post["template"], \ speciesid=post.integer("speciesid"), animaltypeid=post.integer("animaltypeid"))) elif method == "html_deceased_animals": return set_cached_response(cache_key, "text/html", 10800, 1800, \ publishers.html.get_deceased_animals(dbo, daysdeceased=post.integer("days"), style=post["template"], \ speciesid=post.integer("speciesid"), animaltypeid=post.integer("animaltypeid"))) elif method == "html_held_animals": return set_cached_response(cache_key, "text/html", 10800, 1800, \ publishers.html.get_held_animals(dbo, style=post["template"], \ speciesid=post.integer("speciesid"), animaltypeid=post.integer("animaltypeid"))) elif method == "json_adoptable_animals": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_ANIMAL) rs = publishers.base.get_animal_data(dbo, None, include_additional_fields=True) if strip_personal: rs = strip_personal_data(rs) return set_cached_response(cache_key, "application/json", 3600, 3600, utils.json(rs)) elif method == "jsonp_adoptable_animals": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_ANIMAL) rs = publishers.base.get_animal_data(dbo, None, include_additional_fields=True) if strip_personal: rs = strip_personal_data(rs) return ("application/javascript", 0, 0, "%s(%s);" % (post["callback"], utils.json(rs))) elif method == "xml_adoptable_animal": if utils.cint(animalid) == 0: al.error( "xml_adoptable_animal failed, %s is not an animalid" % str(animalid), "service.handler", dbo) return ("text/plain", 0, 0, "ERROR: Invalid animalid") else: users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_ANIMAL) rs = publishers.base.get_animal_data( dbo, None, utils.cint(animalid), include_additional_fields=True) return set_cached_response(cache_key, "application/xml", 3600, 3600, html.xml(rs)) elif method == "xml_adoptable_animals": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_ANIMAL) rs = publishers.base.get_animal_data(dbo, None, include_additional_fields=True) if strip_personal: rs = strip_personal_data(rs) return set_cached_response(cache_key, "application/xml", 3600, 3600, html.xml(rs)) elif method == "json_found_animals": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_FOUND_ANIMAL) rs = lostfound.get_foundanimal_last_days(dbo) return set_cached_response(cache_key, "application/json", 3600, 3600, utils.json(rs)) elif method == "jsonp_found_animals": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_FOUND_ANIMAL) rs = lostfound.get_foundanimal_last_days(dbo) return ("application/javascript", 0, 0, "%s(%s);" % (post["callback"], utils.json(rs))) elif method == "xml_found_animals": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_FOUND_ANIMAL) rs = lostfound.get_foundanimal_last_days(dbo) return set_cached_response(cache_key, "application/json", 3600, 3600, html.xml(rs)) elif method == "json_lost_animals": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_LOST_ANIMAL) rs = lostfound.get_lostanimal_last_days(dbo) return set_cached_response(cache_key, "application/json", 3600, 3600, utils.json(rs)) elif method == "jsonp_lost_animals": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_LOST_ANIMAL) rs = lostfound.get_lostanimal_last_days(dbo) return ("application/javascript", 0, 0, "%s(%s);" % (post["callback"], utils.json(rs))) elif method == "xml_lost_animals": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_LOST_ANIMAL) rs = lostfound.get_lostanimal_last_days(dbo) return set_cached_response(cache_key, "application/json", 3600, 3600, html.xml(rs)) elif method == "json_recent_adoptions": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_ANIMAL) rs = movement.get_recent_adoptions(dbo) return set_cached_response(cache_key, "application/json", 3600, 3600, utils.json(rs)) elif method == "jsonp_recent_adoptions": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_ANIMAL) rs = movement.get_recent_adoptions(dbo) return ("application/javascript", 0, 0, "%s(%s);" % (post["callback"], utils.json(rs))) elif method == "xml_recent_adoptions": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_ANIMAL) rs = movement.get_recent_adoptions(dbo) return set_cached_response(cache_key, "application/xml", 3600, 3600, html.xml(rs)) elif method == "html_report": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_REPORT) crid = reports.get_id(dbo, title) p = reports.get_criteria_params(dbo, crid, post) rhtml = reports.execute(dbo, crid, username, p) return set_cached_response(cache_key, "text/html", 600, 600, rhtml) elif method == "csv_mail" or method == "csv_report": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_REPORT) crid = reports.get_id(dbo, title) p = reports.get_criteria_params(dbo, crid, post) rows, cols = reports.execute_query(dbo, crid, username, p) mcsv = utils.csv(l, rows, cols, True) return set_cached_response(cache_key, "text/csv", 600, 600, mcsv) elif method == "jsonp_recent_changes": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_ANIMAL) sa = animal.get_recent_changes(dbo) return ("application/javascript", 0, 0, "%s(%s);" % (post["callback"], utils.json(sa))) elif method == "json_recent_changes": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_ANIMAL) sa = animal.get_recent_changes(dbo) return set_cached_response(cache_key, "application/json", 3600, 3600, utils.json(sa)) elif method == "xml_recent_changes": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_ANIMAL) sa = animal.get_recent_changes(dbo) return set_cached_response(cache_key, "application/xml", 3600, 3600, html.xml(sa)) elif method == "jsonp_shelter_animals": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_ANIMAL) sa = animal.get_shelter_animals(dbo) if strip_personal: sa = strip_personal_data(sa) return ("application/javascript", 0, 0, "%s(%s);" % (post["callback"], utils.json(sa))) elif method == "json_shelter_animals": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_ANIMAL) sa = animal.get_shelter_animals(dbo) if strip_personal: sa = strip_personal_data(sa) return set_cached_response(cache_key, "application/json", 3600, 3600, utils.json(sa)) elif method == "xml_shelter_animals": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_ANIMAL) sa = animal.get_shelter_animals(dbo) if strip_personal: sa = strip_personal_data(sa) return set_cached_response(cache_key, "application/xml", 3600, 3600, html.xml(sa)) elif method == "rss_timeline": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.VIEW_ANIMAL) return set_cached_response(cache_key, "application/rss+xml", 3600, 3600, html.timeline_rss(dbo)) elif method == "upload_animal_image": users.check_permission_map(l, user["SUPERUSER"], securitymap, users.ADD_MEDIA) media.attach_file_from_form(dbo, username, media.ANIMAL, int(animalid), post) return ("text/plain", 0, 0, "OK") elif method == "online_form_html": if formid == 0: raise utils.ASMError( "method online_form_html requires a valid formid") return set_cached_response(cache_key, "text/html; charset=utf-8", 120, 120, onlineform.get_onlineform_html(dbo, formid)) elif method == "online_form_json": if formid == 0: raise utils.ASMError( "method online_form_json requires a valid formid") return set_cached_response(cache_key, "application/json; charset=utf-8", 30, 30, onlineform.get_onlineform_json(dbo, formid)) elif method == "online_form_post": flood_protect("online_form_post", remoteip, 15) onlineform.insert_onlineformincoming_from_form(dbo, post, remoteip) redirect = post["redirect"] if redirect == "": redirect = BASE_URL + "/static/pages/form_submitted.html" return ("redirect", 0, 0, redirect) elif method == "sign_document": if formid == 0: raise utils.ASMError( "method sign_document requires a valid formid") if post["sig"] == "": return set_cached_response(cache_key, "text/html", 2, 2, sign_document_page(dbo, formid)) else: media.sign_document(dbo, "service", formid, post["sig"], post["signdate"]) media.create_log(dbo, "service", formid, "ES02", _("Document signed", l)) return ("text/plain", 0, 0, "OK") else: al.error("invalid method '%s'" % method, "service.handler", dbo) raise utils.ASMError("Invalid method '%s'" % method)
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 run(self): self.log("PetRescuePublisher starting...") if self.isPublisherExecuting(): return self.updatePublisherProgress(0) self.setLastError("") self.setStartPublishing() token = configuration.petrescue_token(self.dbo) all_desexed = configuration.petrescue_all_desexed(self.dbo) interstate = configuration.petrescue_interstate(self.dbo) postcode = configuration.organisation_postcode(self.dbo) suburb = configuration.organisation_town(self.dbo) state = configuration.organisation_county(self.dbo) contact_name = configuration.organisation(self.dbo) contact_email = configuration.petrescue_email(self.dbo) if contact_email == "": contact_email = configuration.email(self.dbo) contact_number = configuration.organisation_telephone(self.dbo) if token == "": self.setLastError("No PetRescue auth token has been set.") return if postcode == "" or contact_email == "": self.setLastError("You need to set your organisation postcode and contact email under Settings->Options->Shelter Details->Email") return animals = self.getMatchingAnimals(includeAdditionalFields=True) processed = [] if len(animals) == 0: self.setLastError("No animals found to publish.") self.cleanup() return headers = { "Authorization": "Token token=%s" % token, "Accept": "*/*" } anCount = 0 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() self.cleanup() return isdog = an.SPECIESID == 1 iscat = an.SPECIESID == 2 ageinyears = i18n.date_diff_days(an.DATEOFBIRTH, i18n.now()) size = "" if an.SIZE == 2: size = "medium" elif an.SIZE < 2: size = "large" else: size = "small" coat = "" if an.COATTYPE == 0: coat = "short" elif an.COATTYPE == 1: coat = "long" else: coat = "medium_coat" origin = "" if an.ISTRANSFER == 1 and str(an.BROUGHTINBYOWNERNAME).lower().find("pound") == -1: origin = "shelter_transfer" elif an.ISTRANSFER == 1 and str(an.BROUGHTINBYOWNERNAME).lower().find("pound") != -1: origin = "pound_transfer" elif an.ORIGINALOWNERID > 0: origin = "owner_surrender" else: origin = "community_cat" best_feature = "Looking for love" if "BESTFEATURE" in an and an.BESTFEATURE != "": best_feature = an.BESTFEATURE breeder_id = "" if "BREEDERID" in an and an.BREEDERID != "": breeder_id = an.BREEDERID needs_constant_care = False if "NEEDSCONSTANTCARE" in an and an.NEEDSCONSTANTCARE != "" and an.NEEDSCONSTANTCARE != "0": needs_constant_care = True # Check whether we've been vaccinated, wormed and hw treated vaccinated = medical.get_vaccinated(self.dbo, an.ID) sixmonths = self.dbo.today(offset=-182) hwtreated = isdog and self.dbo.query_int("SELECT COUNT(*) FROM animalmedical WHERE LOWER(TreatmentName) LIKE ? " \ "AND LOWER(TreatmentName) LIKE ? AND StartDate>? AND AnimalID=?", ("%heart%", "%worm%", sixmonths, an.ID)) > 0 wormed = (isdog or iscat) and self.dbo.query_int("SELECT COUNT(*) FROM animalmedical WHERE LOWER(TreatmentName) LIKE ? " \ "AND LOWER(TreatmentName) NOT LIKE ? AND StartDate>? AND AnimalID=?", ("%worm%", "%heart%", sixmonths, an.ID)) > 0 # PR want a null value to hide never-treated animals, so we # turn False into a null. if not hwtreated: hwtreated = None if not wormed: wormed = None # Use the fosterer's postcode, state and suburb if available location_postcode = postcode location_state_abbr = state location_suburb = suburb if an.ACTIVEMOVEMENTID and an.ACTIVEMOVEMENTTYPE == 2: fr = self.dbo.first_row(self.dbo.query("SELECT OwnerTown, OwnerCounty, OwnerPostcode FROM adoption m " \ "INNER JOIN owner o ON m.OwnerID = o.ID WHERE m.ID=?", [ an.ACTIVEMOVEMENTID ])) if fr is not None and fr.OWNERPOSTCODE: location_postcode = fr.OWNERPOSTCODE if fr is not None and fr.OWNERCOUNTY: location_state_abbr = fr.OWNERCOUNTY if fr is not None and fr.OWNERTOWN: location_suburb = fr.OWNERTOWN # Build a list of immutable photo URLs photo_urls = [] photos = self.dbo.query("SELECT MediaName FROM media " \ "WHERE LinkTypeID = 0 AND LinkID = ? AND MediaMimeType = 'image/jpeg' " \ "AND (ExcludeFromPublish = 0 OR ExcludeFromPublish Is Null) " \ "ORDER BY WebsitePhoto DESC, ID", [an.ID]) for m in photos: photo_urls.append("%s?account=%s&method=dbfs_image&title=%s" % (SERVICE_URL, self.dbo.database, m.MEDIANAME)) # Only send microchip_number for locations with a Victoria postcode 3xxx microchip_number = "" if location_postcode.startswith("3"): microchip_number = utils.iif(an.IDENTICHIPPED == 1, an.IDENTICHIPNUMBER, "") # Construct a dictionary of info for this animal data = { "remote_id": str(an.ID), # animal identifier in ASM "remote_source": "SM%s" % self.dbo.database, # system/database identifier "name": an.ANIMALNAME.title(), # animal name (title case, they validate against caps) "shelter_code": an.SHELTERCODE, "adoption_fee": i18n.format_currency_no_symbol(self.locale, an.FEE), "species_name": an.SPECIESNAME, "breed_names": self.get_breed_names(an), # [breed1,breed2] or [breed1] "breeder_id": breeder_id, # mandatory for QLD dogs born after 2017-05-26 "mix": an.CROSSBREED == 1, # true | false "date_of_birth": i18n.format_date("%Y-%m-%d", an.DATEOFBIRTH), # iso "gender": an.SEXNAME.lower(), # male | female "personality": self.replace_html_entities(self.getDescription(an)), # 20-4000 chars of free type "best_feature": best_feature, # 25 chars free type, defaults to "Looking for love" requires BESTFEATURE additional field "location_postcode": location_postcode, # shelter/fosterer postcode "location_state_abbr": location_state_abbr, # shelter/fosterer state "location_suburb": location_suburb, # shelter/fosterer suburb "microchip_number": microchip_number, "desexed": an.NEUTERED == 1 or all_desexed, # true | false, validates to always true according to docs "contact_method": "email", # email | phone "size": utils.iif(isdog, size, ""), # dogs only - small | medium | high "senior": isdog and ageinyears > (7 * 365), # dogs only, true | false "vaccinated": vaccinated, # cats, dogs, rabbits, true | false "wormed": wormed, # cats & dogs, true | false "heart_worm_treated": hwtreated, # dogs only, true | false "coat": coat, # Only applies to cats and guinea pigs, but we send for everything: short | medium_coat | long "intake_origin": utils.iif(iscat, origin, ""), # cats only, community_cat | owner_surrender | pound_transfer | shelter_transfer "incompatible_with_cats": an.ISGOODWITHCATS == 1, "incompatible_with_dogs": an.ISGOODWITHDOGS == 1, "incompatible_with_kids_under_5": an.ISGOODWITHCHILDREN == 1, "incompatible_with_kids_6_to_12": an.ISGOODWITHCHILDREN == 1, "needs_constant_care": needs_constant_care, "adoption_process": "", # 4,000 chars how to adopt "contact_details_source": "self", # self | user | group "contact_preferred_method": "email", # email | phone "contact_name": contact_name, # name of contact details owner "contact_number": contact_number, # number to enquire about adoption "contact_email": contact_email, # email to enquire about adoption "foster_needed": False, # true | false "interstate": interstate, # true | false - can the animal be flown to another state for adoption "medical_notes": "", # DISABLED an.HEALTHPROBLEMS, # 4,000 characters medical notes "multiple_animals": an.BONDEDANIMALID > 0 or an.BONDEDANIMAL2ID > 0, # More than one animal included in listing true | false "photo_urls": photo_urls, # List of photo URL strings "status": "active" # active | removed | on_hold | rehomed | suspended | group_suspended } # PetRescue will insert/update accordingly based on whether remote_id/remote_source exists url = PETRESCUE_URL + "listings" jsondata = utils.json(data) self.log("Sending POST to %s to create/update listing: %s" % (url, jsondata)) r = utils.post_json(url, jsondata, headers=headers) if r["status"] != 200: self.logError("HTTP %d, headers: %s, response: %s" % (r["status"], r["headers"], self.utf8_to_ascii(r["response"]))) else: self.log("HTTP %d, headers: %s, response: %s" % (r["status"], r["headers"], self.utf8_to_ascii(r["response"]))) self.logSuccess("Processed: %s: %s (%d of %d)" % ( an["SHELTERCODE"], an["ANIMALNAME"], anCount, len(animals))) processed.append(an) except Exception as err: self.logError("Failed processing animal: %s, %s" % (str(an["SHELTERCODE"]), err), sys.exc_info()) try: # Get a list of all animals that we sent to PR recently (14 days) prevsent = self.dbo.query("SELECT AnimalID FROM animalpublished WHERE SentDate>=? AND PublishedTo='petrescue'", [self.dbo.today(offset=-14)]) # Build a list of IDs we just sent, along with a list of ids for animals # that we previously sent and are not in the current sent list. # This identifies the listings we need to cancel animalids_just_sent = set([ x.ID for x in animals ]) animalids_to_cancel = set([ str(x.ANIMALID) for x in prevsent if x.ANIMALID not in animalids_just_sent]) # Get the animal records for the ones we need to cancel if len(animalids_to_cancel) == 0: animals = [] else: animals = self.dbo.query("SELECT ID, ShelterCode, AnimalName, ActiveMovementDate, ActiveMovementType, DeceasedDate " \ "FROM animal a WHERE ID IN (%s)" % ",".join(animalids_to_cancel)) except Exception as err: self.logError("Failed finding listings to cancel: %s" % err, sys.exc_info()) # Cancel the inactive listings for an in animals: try: status = "on_hold" if an.ACTIVEMOVEMENTDATE is not None and an.ACTIVEMOVEMENTTYPE == 1: status = "rehomed" if an.DECEASEDDATE is not None: status = "removed" data = { "status": status } jsondata = utils.json(data) url = PETRESCUE_URL + "listings/%s/SM%s" % (an.ID, self.dbo.database) self.log("Sending PATCH to %s to update existing listing: %s" % (url, jsondata)) r = utils.patch_json(url, jsondata, headers=headers) if r["status"] == 200: self.log("HTTP %d, headers: %s, response: %s" % (r["status"], r["headers"], self.utf8_to_ascii(r["response"]))) self.logSuccess("%s - %s: Marked with new status %s" % (an.SHELTERCODE, an.ANIMALNAME, status)) # It used to be that we updated animalpublished for this animal to get sentdate to today # we don't do this now so that we'll update dead listings every day for however many days we # look back, but that's it else: self.logError("HTTP %d, headers: %s, response: %s" % (r["status"], r["headers"], self.utf8_to_ascii(r["response"]))) except Exception as err: self.logError("Failed closing listing for %s - %s: %s" % (an.SHELTERCODE, an.ANIMALNAME, err), sys.exc_info()) # Mark sent animals published self.markAnimalsPublished(processed, first=True) self.cleanup()
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("PetRescuePublisher starting...") if self.isPublisherExecuting(): return self.updatePublisherProgress(0) self.setLastError("") self.setStartPublishing() token = configuration.petrescue_token(self.dbo) postcode = configuration.organisation_postcode(self.dbo) contact_name = configuration.organisation(self.dbo) contact_email = configuration.email(self.dbo) contact_number = configuration.organisation_telephone(self.dbo) if token == "": self.setLastError("No PetRescue auth token has been set.") return if postcode == "" or contact_email == "": self.setLastError( "You need to set your organisation postcode and contact email under Settings->Options->Shelter Details->Email" ) return animals = self.getMatchingAnimals() processed = [] if len(animals) == 0: self.setLastError("No animals found to publish.") self.cleanup() return headers = {"Authorization": "Token token=%s" % token, "Accept": "*/*"} anCount = 0 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() self.cleanup() return isdog = an.SPECIESID == 1 iscat = an.SPECIESID == 2 ageinyears = i18n.date_diff_days(an.DATEOFBIRTH, i18n.now()) vaccinated = medical.get_vaccinated(self.dbo, an.ID) size = "" if an.SIZE == 2: size = "medium" elif an.SIZE < 2: size = "large" else: size = "small" coat = "" if an.COATTYPE == 0: coat = "short" elif an.COATTYPE == 1: coat = "long" else: coat = "medium_coat" origin = "" if an.ISTRANSFER == 1 and an.BROUGHTINBYOWNERNAME.lower().find( "pound") == -1: origin = "shelter_transfer" elif an.ISTRANSFER == 1 and an.BROUGHTINBYOWNERNAME.lower( ).find("pound") != -1: origin = "pound_transfer" elif an.ORIGINALOWNERID > 0: origin = "owner_surrender" else: origin = "community_cat" photo_url = "%s?account=%s&method=animal_image&animalid=%d" % ( SERVICE_URL, self.dbo.database, an.ID) # Construct a dictionary of info for this animal data = { "remote_id": str(an.ID), # animal identifier in ASM "remote_source": "SM%s" % self.dbo.database, # system/database identifier "name": an.ANIMALNAME, # animal name "adoption_fee": i18n.format_currency_no_symbol(self.locale, an.FEE), "species_name": an.SPECIESNAME, "breed_names": self.get_breed_names(an), # breed1,breed2 or breed1 "mix": an.CROSSBREED == 1, # true | false "date_of_birth": i18n.format_date("%Y-%m-%d", an.DATEOFBIRTH), # iso "gender": an.SEXNAME.lower(), # male | female "personality": an.WEBSITEMEDIANOTES, # 20-4000 chars of free type "location_postcode": postcode, # shelter postcode "postcode": postcode, # shelter postcode "microchip_number": utils.iif(an.IDENTICHIPPED == 1, an.IDENTICHIPNUMBER, ""), "desexed": an.NEUTERED == 1, # true | false, validates to always true according to docs "contact_method": "email", # email | phone "size": utils.iif(isdog, size, ""), # dogs only - small | medium | high "senior": isdog and ageinyears > 7, # dogs only, true | false "vaccinated": vaccinated, # cats, dogs, rabbits, true | false "wormed": vaccinated, # cats & dogs, true | false "heart_worm_treated": vaccinated, # dogs only, true | false "coat": utils.iif(iscat, coat, ""), # cats only, short | medium_coat | long "intake_origin": utils.iif( iscat, origin, "" ), # cats only, community_cat | owner_surrender | pound_transfer | shelter_transfer "adoption_process": "", # 4,000 chars how to adopt "contact_details_source": "self", # self | user | group "contact_preferred_method": "email", # email | phone "contact_name": contact_name, # name of contact details owner "contact_number": contact_number, # number to enquire about adoption "contact_email": contact_email, # email to enquire about adoption "foster_needed": False, # true | false "interstate": True, # true | false - can the animal be adopted to another state "medical_notes": an.HEALTHPROBLEMS, # 4,000 characters medical notes "multiple_animals": False, # More than one animal included in listing true | false "photo_urls": [photo_url], # List of photo URL strings "status": "active" # active | removed | on_hold | rehomed | suspended | group_suspended } # PetRescue will insert/update accordingly based on whether remote_id/remote_source exists url = PETRESCUE_URL + "listings" jsondata = utils.json(data) self.log("Sending POST to %s to create/update listing: %s" % (url, jsondata)) r = utils.post_json(url, jsondata, headers=headers) if r["status"] != 200: self.logError("HTTP %d, headers: %s, response: %s" % (r["status"], r["headers"], r["response"])) else: self.log("HTTP %d, headers: %s, response: %s" % (r["status"], r["headers"], r["response"])) self.logSuccess("Processed: %s: %s (%d of %d)" % (an["SHELTERCODE"], an["ANIMALNAME"], anCount, len(animals))) processed.append(an) except Exception as err: self.logError( "Failed processing animal: %s, %s" % (str(an["SHELTERCODE"]), err), sys.exc_info()) # Next, identify animals we've previously sent who: # 1. Have an active exit movement in the last month or died in the last month # 2. Have an entry in animalpublished/petrescue where the sent date is older than the active movement # 3. Have an entry in animalpublished/petrescue where the sent date is older than the deceased date animals = self.dbo.query("SELECT a.ID, a.ShelterCode, a.AnimalName, p.SentDate, a.ActiveMovementDate, a.DeceasedDate FROM animal a " \ "INNER JOIN animalpublished p ON p.AnimalID = a.ID AND p.PublishedTo='petrescue' " \ "WHERE Archived = 1 AND ((DeceasedDate Is Not Null AND DeceasedDate >= ?) OR " \ "(ActiveMovementDate Is Not Null AND ActiveMovementDate >= ? AND ActiveMovementType NOT IN (2,8))) " \ "ORDER BY a.ID", [self.dbo.today(offset=-30), self.dbo.today(offset=-30)]) for an in animals: if (an.ACTIVEMOVEMENTDATE and an.SENTDATE < an.ACTIVEMOVEMENTDATE ) or (an.DECEASEDDATE and an.SENTDATE < an.DECEASEDDATE): status = utils.iif(an.DECEASEDDATE is not None, "removed", "rehomed") data = {"status": status} jsondata = utils.json(data) url = PETRESCUE_URL + "listings/%s/SM%s" % (an.ID, self.dbo.database) self.log("Sending PATCH to %s to update existing listing: %s" % (url, jsondata)) r = utils.patch_json(url, jsondata, headers=headers) if r["status"] != 200: self.logError("HTTP %d, headers: %s, response: %s" % (r["status"], r["headers"], r["response"])) else: self.log("HTTP %d, headers: %s, response: %s" % (r["status"], r["headers"], r["response"])) self.logSuccess("%s - %s: Marked with new status %s" % (an.SHELTERCODE, an.ANIMALNAME, status)) # By marking these animals in the processed list again, their SentDate # will become today, which should exclude them from sending these status # updates to close the listing again in future processed.append(an) # Mark sent animals published self.markAnimalsPublished(processed, first=True) self.cleanup()