Esempio n. 1
0
File: clinic.py Progetto: tgage/asm3
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))
Esempio n. 2
0
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)
Esempio n. 3
0
    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()
Esempio n. 4
0
    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()
Esempio n. 5
0
    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()
Esempio n. 6
0
    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()