Skip to content

Commit

Permalink
Merge pull request #331 from digital-land/local_plans
Browse files Browse the repository at this point in the history
Local plans
  • Loading branch information
ssadhu-sl authored Dec 16, 2024
2 parents 11b7ba9 + 481566c commit 75b84af
Show file tree
Hide file tree
Showing 13 changed files with 11,761 additions and 29 deletions.
24 changes: 23 additions & 1 deletion application/core/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import numbers
from application.settings import get_settings
import logging
from datetime import datetime

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -252,7 +253,10 @@ def make_link_filter(eval_ctx, url, **kwargs):


def get_entity_geometry(entity):
data = entity.geojson.geometry if entity.geojson else None
data = None
if entity and entity.geojson is not None:
data = entity.geojson.geometry

if data is None:
logger.warning(
f"No geojson for entity that has a typology of geography: {entity.entity}",
Expand Down Expand Up @@ -301,3 +305,21 @@ def get_os_oauth2_token():
return "null"
else:
return jsonResult


def format_date(date_str):
if not date_str:
return date_str

date_obj = datetime.strptime(date_str, "%Y-%m-%d")
day = date_obj.day

# Determine the ordinal suffix
if 11 <= day <= 13: # Special case for teens
suffix = "th"
else:
suffix = {1: "st", 2: "nd", 3: "rd"}.get(day % 10, "th")

# Format the date with the ordinal suffix
formatted_date = f"{day}{suffix} {date_obj.strftime('%B %Y')}"
return formatted_date
2 changes: 2 additions & 0 deletions application/core/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
get_entity_geometry,
get_entity_paint_options,
get_os_oauth2_token,
format_date,
)

from application.core.utils import model_dumps
Expand Down Expand Up @@ -77,6 +78,7 @@ def random_int(n=1):
templates.env.filters["slugify"] = to_slug
templates.env.filters["extract_component_key"] = extract_component_key
templates.env.filters["get_entity_geometry"] = get_entity_geometry
templates.env.filters["format_date"] = format_date

# TODO This is a filter which should only need one variable, apparently ther
# eis something called context processors that we should use
Expand Down
47 changes: 47 additions & 0 deletions application/data_access/entity_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
)
from application.db.models import EntityOrm, OldEntityOrm
from application.search.enum import GeometryRelation, PeriodOption
from sqlalchemy.types import Date
from sqlalchemy.sql.expression import cast

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -342,3 +344,48 @@ def _apply_limit_and_pagination_filters(query, params):
if params.get("offset") is not None:
query = query.offset(params["offset"])
return query


def get_linked_entities(
session: Session, dataset: str, reference: str, linked_dataset: str = None
) -> List[EntityModel]:
query = (
session.query(EntityOrm)
.filter(EntityOrm.dataset == dataset)
.filter(EntityOrm.json.contains({linked_dataset: reference}))
)

if dataset in ["local-plan-timetable"]:
query = query.order_by(cast(EntityOrm.json["event-date"].astext, Date).desc())

entities = query.all()
return [entity_factory(e) for e in entities]


def fetchEntityFromReference(
session: Session, dataset: str, reference: str
) -> EntityModel:
entity = (
session.query(EntityOrm)
.filter(EntityOrm.dataset == dataset)
.filter(EntityOrm.reference == reference)
).one_or_none()

if entity:
return entity_factory(entity)
return None


def get_organisations(session: Session) -> List[EntityModel]:
organisations = (
session.query(EntityOrm)
.filter(EntityOrm.typology == "organisation")
.filter(EntityOrm.organisation_entity.isnot(None))
.filter(EntityOrm.name.isnot(None))
.distinct()
.all()
)
if organisations:
return [entity_factory(e) for e in organisations]
else:
return []
66 changes: 66 additions & 0 deletions application/routers/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
from application.data_access.entity_queries import (
get_entity_query,
get_entity_search,
get_organisations,
lookup_entity_link,
get_linked_entities,
fetchEntityFromReference,
)
from application.data_access.dataset_queries import get_dataset_names

Expand Down Expand Up @@ -146,12 +149,16 @@ def handle_entity_response(
dataset_fields = [dataset_field["dataset"] for dataset_field in dataset_fields]

dataset = get_dataset_query(session, e.dataset)

organisation_entity, _, _ = get_entity_query(session, e.organisation_entity)

entityLinkFields = [
"article-4-direction",
"permitted-development-rights",
"tree-preservation-order",
"local-plan-boundary",
"local-plan",
"local-plan-event",
]

linked_entities = {}
Expand All @@ -166,12 +173,19 @@ def handle_entity_response(
if linked_entity is not None:
linked_entities[field] = linked_entity

# Fetch linked local plans/document/timetable
local_plans, local_plan_boundary_geojson = fetch_linked_local_plans(
session, e_dict_sorted
)

return templates.TemplateResponse(
"entity.html",
{
"request": request,
"row": e_dict_sorted,
"local_plan_geojson": local_plan_boundary_geojson,
"linked_entities": linked_entities,
"local_plans": local_plans,
"entity": e,
"pipeline_name": e.dataset,
"references": [],
Expand All @@ -189,6 +203,51 @@ def handle_entity_response(
)


linked_datasets = {
"local-plan-boundary": ["local-plan"],
"local-plan": [
"local-plan-document",
"local-plan-timetable",
"local-plan-boundary",
],
}


def fetch_linked_local_plans(session: Session, e_dict_sorted: Dict = None):
results = {}
local_plan_boundary_geojson = None
dataset = e_dict_sorted["dataset"]
reference = e_dict_sorted["reference"]
if dataset in linked_datasets:
linked_dataset_value = linked_datasets[dataset]
for linked_dataset in linked_dataset_value:
if dataset == "local-plan" and linked_dataset == "local-plan-boundary":
if linked_dataset in e_dict_sorted:
local_plan_boundary_geojson = fetchEntityFromReference(
session, linked_dataset, e_dict_sorted[linked_dataset]
)
linked_entities = get_linked_entities(
session, linked_dataset, reference, linked_dataset=dataset
)
results[linked_dataset] = linked_entities

# Handle special case for "local-plan-timetable"
if dataset == "local-plan" and linked_dataset == "local-plan-timetable":
for entity in linked_entities:
if (
hasattr(entity, "local_plan_event")
and entity.local_plan_event
and not entity.local_plan_event.startswith("estimated")
):
entity.local_plan_event = fetchEntityFromReference(
session, "local-plan-event", entity.local_plan_event
)
else:
entity.local_plan_event = None

return results, local_plan_boundary_geojson


def get_entity(
request: Request,
entity: int = Path(default=Required, description="Entity id"),
Expand Down Expand Up @@ -329,6 +388,12 @@ def search_entities(
local_authorities = get_local_authorities(session, "local-authority")
local_authorities = [la.dict() for la in local_authorities]

organisations = get_organisations(session)
columns = ["entity", "organisation_entity", "name"]
organisations_list = [
organisation.dict(include=set(columns)) for organisation in organisations
]

if links.get("prev") is not None:
prev_url = links["prev"]
else:
Expand All @@ -350,6 +415,7 @@ def search_entities(
"datasets": datasets,
"local_authorities": local_authorities,
"typologies": typologies,
"organisations": organisations_list,
"query": {"params": params},
"active_filters": [
filter_name
Expand Down
Loading

0 comments on commit 75b84af

Please sign in to comment.