Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/LAN-699' into LAN-699
Browse files Browse the repository at this point in the history
  • Loading branch information
diePuppe committed Aug 4, 2023
2 parents df3035d + 9d56c80 commit 020ec72
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 53 deletions.
70 changes: 20 additions & 50 deletions landa/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@

import frappe

from landa.water_body_management.doctype.water_body.water_body import (
build_water_body_cache,
build_water_body_data,
)


@frappe.whitelist(allow_guest=True, methods=["GET"])
def organization(id: str = None) -> List[Dict]:
Expand Down Expand Up @@ -71,56 +76,21 @@ def organization(id: str = None) -> List[Dict]:
@frappe.whitelist(allow_guest=True, methods=["GET"])
def water_body(id: str = None, fishing_area: str = None) -> List[Dict]:
"""Return a list of water bodies with fish species and special provisions."""
filters = [
["Water Body", "is_active", "=", 1],
["Water Body", "display_in_fishing_guide", "=", 1],
]
if id and isinstance(id, str):
filters.append(["Water Body", "name", "=", id])
if id:
# We do not cache ID since it's uniqueness makes the API performant
return build_water_body_data(id, fishing_area)

if fishing_area and isinstance(fishing_area, str):
filters.append(["Water Body", "fishing_area", "=", fishing_area])
key = fishing_area or "all"
cache_exists = frappe.cache().hexists("water_body_data", key)

if not cache_exists:
# Build the cache (for future calls)
build_water_body_cache(fishing_area)

# return the cached result
return get_water_body_cache(key)

water_bodies = frappe.get_all(
"Water Body",
filters=filters,
fields=[
"name as id",
"title",
"fishing_area",
"fishing_area_name",
"organization",
"organization_name",
"has_master_key_system",
"guest_passes_available",
"general_public_information",
"current_public_information",
"water_body_size as size",
"water_body_size_unit as size_unit",
"location",
],
)

for water_body in water_bodies:
water_body["fish_species"] = frappe.get_all(
"Fish Species Table",
filters={"parent": water_body["id"]},
pluck="fish_species",
)

water_body["special_provisions"] = frappe.get_all(
"Water Body Special Provision Table",
filters={"parent": water_body["id"]},
fields=["water_body_special_provision as id", "short_code"],
)

water_body["organizations"] = frappe.get_all(
"Water Body Management Local Organization",
filters={"water_body": water_body["id"]},
fields=["organization as id", "organization_name"],
)

if water_body.location:
water_body["geojson"] = json.loads(water_body.location)

return water_bodies
def get_water_body_cache(key: str) -> List[Dict]:
"""Return a **CACHED** list of water bodies with fish species and special provisions."""
return frappe.cache().hget("water_body_data", key)
3 changes: 3 additions & 0 deletions landa/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@
"on_update": "landa.organization_management.user.user.on_update",
"on_trash": "landa.organization_management.user.user.on_trash",
},
"Workspace": {
"validate": "landa.workspace.validate",
},
}

# Scheduled Tasks
Expand Down
2 changes: 2 additions & 0 deletions landa/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ landa.patches.set_billing_and_shipping_defaults
landa.patches.delete_old_scheduled_job_logs
landa.patches.update_system_settings
landa.patches.delete_customized_workspaces # 2023-06-06
landa.patches.build_water_body_cache
landa.patches.set_hide_custom_in_user_workspaces
11 changes: 11 additions & 0 deletions landa/patches/build_water_body_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import frappe

from landa.water_body_management.doctype.water_body.water_body import build_water_body_cache


def execute():
build_water_body_cache() # Cache all Water Bodies

fishing_areas = frappe.get_all("Fishing Area", pluck="name")
for area in fishing_areas:
build_water_body_cache(fishing_area=area) # Cache Fishing Area wise
17 changes: 17 additions & 0 deletions landa/patches/set_hide_custom_in_user_workspaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import frappe

from landa.workspace import LANDA_WORKSPACES


def execute():
"""Hide custom reports in existing customized landa workspaces."""
for workspace in frappe.get_all(
"Workspace",
filters={
"for_user": ("is", "set"),
"hide_custom": 0,
"extends": ("in", LANDA_WORKSPACES),
},
pluck="name",
):
frappe.db.set_value("Workspace", workspace, "hide_custom", 1)
1 change: 1 addition & 0 deletions landa/translations/de.csv
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ Current Public Information will be removed on this date,Aktuelle Informationen w
Current Information Expires On,Aktuelle Informationen zurücksetzen am,
Fish Species Short Codes,Fischart Kürzel,
Water Body Export,Export Gewässerverzeichnis,
No permission to set Member Function Category {0},"Sie haben nicht die nötigen Rechte, um eine Mitgliedsfunktion der Kategorie {0} zu vergeben",
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ frappe.ui.form.on("Catch Log Entry", {
},
onload: (frm) => {
if (frm.is_new() && !frm.doc.year) {
frm.set_value("year", moment().year() - 1);
const today = new Date();
const current_year = today.getFullYear();
const current_month = today.getMonth();

if (!frm.doc.year) {
frm.set_value("year", current_month < 6 ? current_year - 1 : current_year);
}
}
},
refresh: (frm) => {
Expand Down
167 changes: 166 additions & 1 deletion landa/water_body_management/doctype/water_body/water_body.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Copyright (c) 2021, Real Experts GmbH and contributors
# For license information, please see license.txt

from collections import defaultdict
from json import loads
from typing import Dict, List

import frappe
from frappe import _
Expand All @@ -11,6 +13,12 @@


class WaterBody(Document):
def on_update(self):
rebuild_water_body_cache(self.fishing_area)

def on_trash(self):
rebuild_water_body_cache(self.fishing_area)

def validate(self):
self.validate_edit_access()
self.validate_blacklisted_fish_species()
Expand All @@ -36,6 +44,19 @@ def validate_blacklisted_fish_species(self):
)


def rebuild_water_body_cache(fishing_area: str = None):
"""
Rebuilds water body cache for all water bodies **AND** fishing area wise.
"""
# Invalidate Cache
frappe.cache().hdel("water_body_data", "all")
build_water_body_cache()

if fishing_area:
frappe.cache().hdel("water_body_data", fishing_area)
build_water_body_cache(fishing_area=fishing_area)


def remove_outdated_information():
for name in frappe.get_all(
"Water Body",
Expand All @@ -49,3 +70,147 @@ def remove_outdated_information():
water_body.current_public_information = None
water_body.current_information_expires_on = None
water_body.save()


def build_water_body_cache(fishing_area: str = None):
"""
Build the water body cache for all water bodies **OR** fishing area wise.
"""
water_bodies = build_water_body_data(fishing_area=fishing_area)
key = fishing_area or "all"
frappe.cache().hset("water_body_data", key, water_bodies)


def build_water_body_data(id: str = None, fishing_area: str = None) -> List[Dict]:
"""
Return a list of water bodies with fish species and special provisions
"""
result = query_water_body_data(id=id, fishing_area=fishing_area)
return consolidate_water_body_data(water_body_data=result)


def query_water_body_data(id: str = None, fishing_area: str = None) -> List[Dict]:
water_body = frappe.qb.DocType("Water Body")
fish_species_table = frappe.qb.DocType("Fish Species Table")
wb_provision_table = frappe.qb.DocType("Water Body Special Provision Table")
wb_local_org_table = frappe.qb.DocType("Water Body Management Local Organization")

query = (
frappe.qb.from_(water_body)
.left_join(fish_species_table)
.on(fish_species_table.parent == water_body.name)
.left_join(wb_provision_table)
.on(wb_provision_table.parent == water_body.name)
.left_join(wb_local_org_table)
.on(wb_local_org_table.water_body == water_body.name)
.select(
water_body.name.as_("id"),
water_body.title,
water_body.fishing_area,
water_body.fishing_area_name,
water_body.organization,
water_body.organization_name,
water_body.has_master_key_system,
water_body.guest_passes_available,
water_body.general_public_information,
water_body.current_public_information,
water_body.water_body_size.as_("size"),
water_body.water_body_size_unit.as_("size_unit"),
water_body.location,
fish_species_table.fish_species,
wb_provision_table.water_body_special_provision,
wb_provision_table.short_code,
wb_local_org_table.organization.as_("local_organization"),
wb_local_org_table.organization_name.as_("local_organization_name"),
)
.where(water_body.is_active == 1)
.where(water_body.display_in_fishing_guide == 1)
)

if id and isinstance(id, str):
query = query.where(water_body.name == id)

if fishing_area and isinstance(fishing_area, str):
query = query.where(water_body.fishing_area == fishing_area)

return query.run(as_dict=True)


def consolidate_water_body_data(water_body_data: List[Dict]) -> List[Dict]:
"""
Deduplicate the water body data such that each water body has a list of unique
fish species, special provisions and local organizations.
"""
water_body_map = {} # {water_body_name: water_body_data}
fish_species_map, provision_map, local_org_map = (
defaultdict(list),
defaultdict(list),
defaultdict(list),
)

for entry in water_body_data:
water_body_name = entry.get("id")
if not water_body_name in water_body_map:
# Add entry to map if it does not exist
water_body_map[water_body_name] = init_row(water_body_row=entry)

result_entry = water_body_map[water_body_name]

# Add unique child table and Water Body Management Local Organization data
fish_species = entry.get("fish_species")
add_to_map(fish_species, "fish_species", entry, fish_species_map, result_entry)

provision = entry.get("water_body_special_provision")
add_to_map(provision, "special_provisions", entry, provision_map, result_entry)

org = entry.get("local_organization")
add_to_map(org, "organizations", entry, local_org_map, result_entry)

return [water_body_map.get(key) for key in water_body_map]


def init_row(water_body_row: Dict) -> Dict:
# Prepare row to have Water Body data (excluding child tables)
water_body_copy = water_body_row.copy()
location = water_body_copy.pop("location", None)

if location and isinstance(location, str):
water_body_copy["geojson"] = loads(location)

for field in (
"fish_species",
"water_body_special_provision",
"short_code",
"local_organization",
"local_organization_name",
):
water_body_copy.pop(field) # Remove child table fields

for field in ("fish_species", "special_provisions", "organizations"):
# Re-insert child table fields as lists
water_body_copy[field] = []

return water_body_copy


def add_to_map(value, field, water_body, checking_map, result_map):
"""
Add the value to the `result_map` if it does not exist in the `checking_map`.
Also update the `checking_map` with the value.
"""
water_body_name = water_body.get("id")
checking_result_map = checking_map[water_body_name]

if not value or (value in checking_result_map):
return

checking_result_map.append(value)

if field == "fish_species":
result_map[field].append(value)
elif field == "special_provisions":
result_map[field].append({"id": value, "short_code": water_body.get("short_code")})
else:
result_map[field].append(
{"id": value, "organization_name": water_body.get("local_organization_name")}
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
# import frappe
from frappe.model.document import Document

from landa.water_body_management.doctype.water_body.water_body import rebuild_water_body_cache


class WaterBodyManagementLocalOrganization(Document):
pass
def on_update(self):
rebuild_water_body_cache(self.fishing_area)

def on_trash(self):
rebuild_water_body_cache(self.fishing_area)
13 changes: 13 additions & 0 deletions landa/workspace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from frappe.desk.doctype.workspace.workspace import Workspace

LANDA_WORKSPACES = (
"Water Body Management",
"Organization Management",
"Order Management",
)


def validate(doc: Workspace, method=None) -> None:
# Custom reports (possibly of other users) should not be visible
if doc.for_user and doc.hide_custom == 0 and doc.extends in LANDA_WORKSPACES:
doc.hide_custom = 1
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ dependencies = [
"thefuzz",
]

[build-system]
requires = ["flit_core >=3.4,<4"]
build-backend = "flit_core.buildapi"

[tool.black]
line-length = 99

Expand Down

0 comments on commit 020ec72

Please sign in to comment.