def ar(rlist, rtype, sortfield): # Return brief records to save bandwidth if rtype == "ANIMAL": rlist = asm3.animal.get_animals_brief(rlist) if rtype == "PERSON": pass # TODO: for r in rlist: r["RESULTTYPE"] = rtype if sortfield == "RELEVANCE": # How "relevant" is this record to what was searched for? # animal name and code weight higher than other elements. # Note that the code below modifies inbound var q, so by the # time we read it here, it should only contain the search term # itself. Weight everything else by last changed date so there # is some semblance of useful order for less relevant items qlow = q.lower() if rtype == "ANIMAL": r["SORTON"] = r["LASTCHANGEDDATE"] if r["SORTON"] is None: r["SORTON"] = THE_PAST if r["ANIMALNAME"].lower( ) == qlow or r["SHELTERCODE"].lower( ) == qlow or r["SHORTCODE"].lower() == qlow: r["SORTON"] = now() # Put matches where term present just behind direct matches elif r["ANIMALNAME"].lower().find( qlow) != -1 or r["SHELTERCODE"].lower().find( qlow) != -1 or r["SHORTCODE"].lower().find( qlow) != -1: r["SORTON"] = now() - datetime.timedelta(seconds=1) elif rtype == "PERSON": r["SORTON"] = r["LASTCHANGEDDATE"] if r["SORTON"] is None: r["SORTON"] = THE_PAST # Count how many of the keywords in the search were present # in the owner name field - if it's all of them then raise # the relevance. qw = qlow.split(" ") qm = 0 for w in qw: if r["OWNERNAME"].lower().find(w) != -1: qm += 1 if qm == len(qw): r["SORTON"] = now() # Put matches where term present just behind direct matches if r["OWNERSURNAME"].lower().find( qlow) or r["OWNERNAME"].lower().find(qlow): r["SORTON"] = now() - datetime.timedelta(seconds=1) elif rtype == "LICENCE": r["SORTON"] = r["ISSUEDATE"] if r["SORTON"] is None: r["SORTON"] = THE_PAST if r["LICENCENUMBER"].lower() == qlow: r["SORTON"] = now() else: r["SORTON"] = r["LASTCHANGEDDATE"] else: r["SORTON"] = r[sortfield] if r["SORTON"] is None and sortfield.endswith("DATE"): r["SORTON"] = THE_PAST results.append(r)
def flood_protect(method, remoteip): """ Implements flood protection for methods. Keeps a list of timestamps in an in memory cache for the method and IP address. If this IP makes more than the request limit for the period, the request is rejected and the IP banned for a period. method: The service method we're protecting remoteip: The ip address of the caller """ CACHE_TTL = 120 # Flood protection only operates for a minute or so keep entry alive for a couple remoteip = str(remoteip).replace( ", ", "") # X-FORWARDED-FOR can be a list, remove commas cache_key = "m%sr%s" % (method, remoteip) # Get the entry for this IP v = asm3.cachemem.get(cache_key) if v is None: v = { "b": None, "h": [asm3.i18n.now()] } # b = banned until, h = list of hits as timestamps asm3.cachemem.put(cache_key, v, CACHE_TTL) else: # asm3.al.debug("protecting '%s' from '%s': cache: %s" % (method, remoteip, v), "service.flood_protect") # Is this IP banned? if v["b"] is not None and now() < v["b"]: asm3.al.error( "%s is banned from calling '%s' until '%s'" % (remoteip, method, v["b"]), "service.flood_protect") message = "You cannot call '%s' until '%s'" % (method, v["b"]) raise asm3.utils.ASMError(message) # Add a hit for now v["h"].append(now()) request_limit, periods, banneds = FLOOD_PROTECT_METHODS[method] # Calculate how long ago period in s was and how many requests this IP has made in the period cutoff = subtract_seconds(now(), periods) requests_in_period = 0 for d in v["h"]: if d > cutoff: requests_in_period += 1 # Are we over the limit? if requests_in_period > request_limit: v["b"] = add_seconds( now(), banneds ) # Mark this IP banned for this method for the ban period asm3.cachemem.put(cache_key, v, CACHE_TTL) asm3.al.error( "%s has called '%s', %s times in the last %d seconds. Banning until '%s' (%s seconds)" % (remoteip, method, request_limit, periods, v["b"], banneds), "service.flood_protect") message = "You have already called '%s', %s times in the last %d seconds, please wait %d seconds before trying again." % ( method, request_limit, periods, banneds) raise asm3.utils.ASMError(message) else: # Update the cache with the new hit and continue asm3.cachemem.put(cache_key, v, CACHE_TTL)
def auto_update_urgencies(dbo): """ Finds all animals where the next UrgencyUpdateDate field is greater than or equal to today and the urgency is larger than High (so we can never reach Urgent). """ update_period_days = asm3.configuration.waiting_list_urgency_update_period( dbo) if update_period_days == 0: asm3.al.debug( "urgency update period is 0, not updating waiting list entries", "waitinglist.auto_update_urgencies", dbo) return rows = dbo.query("SELECT a.* " \ "FROM animalwaitinglist a WHERE UrgencyUpdateDate <= ? " \ "AND Urgency > 2", [dbo.today()]) updates = [] for r in rows: asm3.al.debug("increasing urgency of waitinglist entry %d" % r.ID, "waitinglist.auto_update_urgencies", dbo) updates.append((now(dbo.timezone), add_days(r.URGENCYUPDATEDATE, update_period_days), r.URGENCY - 1, r.ID)) if len(updates) > 0: dbo.execute_many("UPDATE animalwaitinglist SET " \ "UrgencyLastUpdatedDate=?, " \ "UrgencyUpdateDate=?, " \ "Urgency=? " \ "WHERE ID=? ", updates)
def create_animal_from_found(dbo, username, aid): """ Creates an animal record from a found animal with the id given """ a = dbo.first_row( dbo.query("SELECT * FROM animalfound WHERE ID = %d" % int(aid)) ) l = dbo.locale data = { "animalname": _("Found Animal {0}", l).format(aid), "markings": str(a["DISTFEAT"]), "species": str(a["ANIMALTYPEID"]), "comments": str(a["COMMENTS"]), "broughtinby": str(a["OWNERID"]), "originalowner": str(a["OWNERID"]), "animaltype": asm3.configuration.default_type(dbo), "breed1": a["BREEDID"], "breed2": a["BREEDID"], "basecolour": str(a["BASECOLOURID"]), "microchipped": asm3.utils.iif(a["MICROCHIPNUMBER"] is not None and a["MICROCHIPNUMBER"] != "", "1", "0"), "microchipnumber": a["MICROCHIPNUMBER"], "size": asm3.configuration.default_size(dbo), "internallocation": asm3.configuration.default_location(dbo), "dateofbirth": python2display(l, subtract_years(now(dbo.timezone))), "estimateddob": "1", } # If we're creating shelter codes manually, we need to put something unique # in there for now. Use the id if asm3.configuration.manual_codes(dbo): data["sheltercode"] = "FA" + str(aid) data["shortcode"] = "FA" + str(aid) nextid, dummy = asm3.animal.insert_animal_from_form(dbo, asm3.utils.PostedData(data, l), username) return nextid
def auto_remove_waitinglist(dbo): """ Finds and automatically marks entries removed that have gone past the last contact date + weeks. """ l = dbo.locale rows = dbo.query("SELECT a.ID, a.DateOfLastOwnerContact, " \ "a.AutoRemovePolicy " \ "FROM animalwaitinglist a WHERE a.DateRemovedFromList Is Null " \ "AND AutoRemovePolicy > 0 AND DateOfLastOwnerContact Is Not Null") updates = [] for r in rows: xdate = add_days(r.DATEOFLASTOWNERCONTACT, 7 * r.AUTOREMOVEPOLICY) if after(now(dbo.timezone), xdate): asm3.al.debug("auto removing waitinglist entry %d due to policy" % r.ID, "waitinglist.auto_remove_waitinglist", dbo) updates.append((now(dbo.timezone), _("Auto removed due to lack of owner contact.", l), r.ID)) if len(updates) > 0: dbo.execute_many("UPDATE animalwaitinglist SET DateRemovedFromList = ?, " \ "ReasonForRemoval=? WHERE ID=?", updates)
def get_waitinglist(dbo, priorityfloor = 5, species = -1, size = -1, addresscontains = "", includeremoved = 0, namecontains = "", descriptioncontains = "", siteid = 0): """ Retrieves the waiting list priorityfloor: The lowest urgency to show (1 = urgent, 5 = lowest) species: A species filter or -1 for all size: A size filter or -1 for all addresscontains: A partial address includeremoved: Whether or not to include removed entries namecontains: A partial name descriptioncontains: A partial description """ l = dbo.locale ands = [] values = [] def add(a, v = None): ands.append(a) if v: values.append(v) add("a.Urgency <= ?", priorityfloor) if includeremoved == 0: add("a.DateRemovedFromList Is Null") if species != -1: add("a.SpeciesID = ?", species) if size != -1: add("a.Size = ?", size) if addresscontains != "": ands.append("(%s OR %s)" % (dbo.sql_ilike("OwnerAddress"), dbo.sql_ilike("OwnerTown"))) v = "%%%s%%" % addresscontains.lower() values.append(v) values.append(v) if namecontains != "": add(dbo.sql_ilike("OwnerName"), "%%%s%%" % namecontains.lower()) if descriptioncontains != "": add(dbo.sql_ilike("AnimalDescription"), "%%%s%%" % descriptioncontains.lower()) if siteid != 0: add("(o.SiteID = 0 OR o.SiteID = ?)", siteid) sql = "%s WHERE %s ORDER BY a.Urgency, a.DatePutOnList" % (get_waitinglist_query(dbo), " AND ".join(ands)) rows = dbo.query(sql, values) wlh = asm3.configuration.waiting_list_highlights(dbo).split(" ") ranks = get_waitinglist_ranks(dbo) for r in rows: r.HIGHLIGHT = "" for hi in wlh: if hi != "": if hi.find("|") == -1: wid = hi h = "1" else: wid, h = hi.split("|") if wid == str(r.WLID).strip(): r.HIGHLIGHT = h break if r.WLID in ranks: r.RANK = ranks[r.WLID] else: r.RANK = "" r.TIMEONLIST = date_diff(l, r.DATEPUTONLIST, now(dbo.timezone), asm3.configuration.date_diff_cutoffs(dbo) ) return rows
def reports_email(dbo): """ Batch email reports """ try: # Email any daily reports for local time of now extreports.email_daily_reports(dbo, i18n.now(dbo.timezone)) except: em = str(sys.exc_info()[0]) al.error("FAIL: running daily email of reports_email: %s" % em, "cron.reports_email", dbo, sys.exc_info())
def get_waitinglist_by_id(dbo, wid): """ Returns a single waitinglist record for the ID given """ l = dbo.locale r = dbo.first_row( dbo.query( get_waitinglist_query(dbo) + " WHERE a.ID = ?", [wid]) ) if not r: return None ranks = get_waitinglist_ranks(dbo) if r.WLID in ranks: r.RANK = ranks[r.WLID] else: r.RANK = "" r.TIMEONLIST = date_diff(l, r.DATEPUTONLIST, now(dbo.timezone)) return r
def create_waitinglist_from_found(dbo, username, aid): """ Creates a waiting list entry from a found animal with the id given """ a = dbo.first_row( dbo.query("SELECT * FROM animalfound WHERE ID = %d" % int(aid)) ) l = dbo.locale data = { "dateputon": python2display(l, now(dbo.timezone)), "description": str(a["DISTFEAT"]), "species": str(a["ANIMALTYPEID"]), "comments": str(a["COMMENTS"]), "owner": str(a["OWNERID"]), "breed1": a["BREEDID"], "breed2": a["BREEDID"], "basecolour": str(a["BASECOLOURID"]), "urgency": str(asm3.configuration.waiting_list_default_urgency(dbo)) } nextid = asm3.waitinglist.insert_waitinglist_from_form(dbo, asm3.utils.PostedData(data, dbo.locale), username) return nextid
def stock_take_from_mobile_form(dbo, username, post): """ Post should contain sl{ID} values for new balances. """ if post.integer("usagetype") == 0: raise asm3.utils.ASMValidationError("No usage type passed") for k in post.data.keys(): if k.startswith("sl"): slid = asm3.utils.cint(k.replace("sl", "")) sl = get_stocklevel(dbo, slid) slb = asm3.utils.cfloat(sl.BALANCE) # balance sln = post.floating(k) # new balance # If the balance hasn't changed, do nothing if slb == sln: continue # Update the level dbo.update("stocklevel", slid, {"Balance": sln}) # Write a stock usage record for the difference insert_stockusage(dbo, username, slid, sln - slb, now(dbo.timezone), post.integer("usagetype"), "")
def create_animal(dbo, username, wlid): """ Creates an animal record from a waiting list entry with the id given """ l = dbo.locale a = dbo.first_row( dbo.query("SELECT * FROM animalwaitinglist WHERE ID = ?", [wlid])) data = { "animalname": _("Waiting List {0}", l).format(wlid), "markings": str(a["ANIMALDESCRIPTION"]), "reasonforentry": str(a["REASONFORWANTINGTOPART"]), "species": str(a["SPECIESID"]), "hiddenanimaldetails": str(a["COMMENTS"]), "broughtinby": str(a["OWNERID"]), "originalowner": str(a["OWNERID"]), "animaltype": asm3.configuration.default_type(dbo), "entryreason": asm3.configuration.default_entry_reason(dbo), "breed1": asm3.configuration.default_breed(dbo), "breed2": asm3.configuration.default_breed(dbo), "basecolour": asm3.configuration.default_colour(dbo), "size": asm3.configuration.default_size(dbo), "internallocation": asm3.configuration.default_location(dbo), "dateofbirth": python2display(l, subtract_years(now(dbo.timezone))), "estimateddob": "1" } # If we aren't showing the time brought in, set it to midnight if not asm3.configuration.add_animals_show_time_brought_in(dbo): data["timebroughtin"] = "00:00:00" # If we're creating shelter codes manually, we need to put something unique # in there for now. Use the id if asm3.configuration.manual_codes(dbo): data["sheltercode"] = "WL" + str(wlid) data["shortcode"] = "WL" + str(wlid) nextid, code = asm3.animal.insert_animal_from_form( dbo, asm3.utils.PostedData(data, l), username) # Now that we've created our animal, we should remove this entry from the waiting list dbo.update( "animalwaitinglist", wlid, { "DateRemovedFromList": dbo.today(), "ReasonForRemoval": _("Moved to animal record {0}", l).format(code) }, username) # If there were any logs and media entries on the waiting list, create them on the animal # Media for me in dbo.query( "SELECT * FROM media WHERE LinkTypeID = ? AND LinkID = ?", (asm3.media.WAITINGLIST, wlid)): ext = me.medianame ext = ext[ext.rfind("."):].lower() mediaid = dbo.get_id("media") medianame = "%d%s" % (mediaid, ext) dbo.insert( "media", { "ID": mediaid, "DBFSID": 0, "MediaSize": 0, "MediaName": medianame, "MediaMimeType": asm3.media.mime_type(medianame), "MediaType": me.mediatype, "MediaNotes": me.medianotes, "WebsitePhoto": me.websitephoto, "WebsiteVideo": me.websitevideo, "DocPhoto": me.docphoto, "ExcludeFromPublish": me.excludefrompublish, # ASM2_COMPATIBILITY "NewSinceLastPublish": 1, "UpdatedSinceLastPublish": 0, # ASM2_COMPATIBILITY "LinkID": nextid, "LinkTypeID": asm3.media.ANIMAL, "Date": me.date, "CreatedDate": me.createddate, "RetainUntil": me.retainuntil }, generateID=False) # Now clone the dbfs item pointed to by this media item if it's a file if me.mediatype == asm3.media.MEDIATYPE_FILE: filedata = asm3.dbfs.get_string(dbo, me.medianame) dbfsid = asm3.dbfs.put_string(dbo, medianame, "/animal/%d" % nextid, filedata) dbo.execute( "UPDATE media SET DBFSID = ?, MediaSize = ? WHERE ID = ?", (dbfsid, len(filedata), mediaid)) # Logs for lo in dbo.query("SELECT * FROM log WHERE LinkType = ? AND LinkID = ?", (asm3.log.WAITINGLIST, wlid)): dbo.insert( "log", { "LinkID": nextid, "LinkType": asm3.log.ANIMAL, "LogTypeID": lo.LOGTYPEID, "Date": lo.DATE, "Comments": lo.COMMENTS }, username) return nextid