Skip to content

Commit

Permalink
Location search now handles all area types
Browse files Browse the repository at this point in the history
Fixes #504.

AreaSearchView no longer artificially limits results to WMC area_types,
and area_search.html presents matching areas grouped by area_type.

I’ve gone with a custom `grid` layout on the area_search.html (rather
than Bootstrap columns) to more gracefully handle a variable number of
columns – there might be 1, 2, or 3, depending on the search input.

I also fixed a bug in the `highlight` filter, which raised a TypeError
when passed a None `search` parameter. `search` is None when the user
has used geolocation rather than submitting a search term.
  • Loading branch information
zarino authored and struan committed Apr 22, 2024
1 parent eefd67f commit 080c23c
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 34 deletions.
17 changes: 17 additions & 0 deletions hub/static/css/_search-results.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.search-results {
h2 {
margin-top: 3rem;
}

@include media-breakpoint-up(lg) {
display: grid;
grid-auto-flow: column;
grid-template-rows: repeat(3, auto);
grid-auto-columns: minmax(auto, 30rem); // constrain col width when less than three in grid
grid-column-gap: 2rem;
}

@include media-breakpoint-up(xl) {
grid-column-gap: 3rem;
}
}
1 change: 1 addition & 0 deletions hub/static/css/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
@import "site-footer";
@import "site-feedback";
@import "search-input";
@import "search-results";
@import "explore";
@import "homepage";
@import "area";
Expand Down
48 changes: 35 additions & 13 deletions hub/templates/hub/area_search.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
<div class="py-5">
<div class="container mt-lg-3">

<h1 class="mb-4 text-primary">Search by location</h1>
<label class="form-label" for="search">Enter a constituency name, MP name, or postcode</label>
<h1 class="mb-4 text-primary">Search for a location</h1>
<label class="form-label" for="search">Enter a postcode, or the name of a constituency, MP, or local authority</label>
<form class="row mt-2 mb-2 js-homepage-search" style="max-width: 30rem;" action="{% url 'area_search' %}">
<div class="col">
<div class="search-input search-input-lg">
{% include 'hub/includes/icons/search.html' %}
<input name="search" value="{{ search }}" type="search" id="search" class="form-control form-control-lg {% if error %}is-invalid{% endif %}">
<input name="search" value="{{ search|default_if_none:"" }}" type="search" id="search" class="form-control form-control-lg {% if error %}is-invalid{% endif %}">
</div>
{% if error %}
<div class="invalid-feedback d-block fs-6 mt-2">
Expand All @@ -25,24 +25,46 @@ <h1 class="mb-4 text-primary">Search by location</h1>
<button type="submit" class="btn btn-primary btn-lg">Search</button>
</div>
</form>
<a class="js-geolocate d-none d-flex align-items-center btn btn-link px-0" href="#">
<a class="js-geolocate d-none d-flex align-items-center btn btn-link px-0" href=".">
{% include 'hub/includes/icons/geolocate.html' with classes="me-2" %}
Use my current location
</a>

{% if areas %}
<h2 class="mt-5 mb-4 text-primary">{{ areas|length }} matching locations</h2>
{% for area in areas %}
<div class="card shadow-sm mb-3 mb-md-4" style="max-width: 30em">
<div class="card-body">
<h3 class="card-title mb-3"><a href="{{ area.get_absolute_url }}" class="stretched-link">{{ area.name|highlight:search }}</a></h3>
<div class="d-flex align-items-center">
{% include 'hub/includes/person-photo.html' with person=area.mp width="32" height="32" %}
<p class="mb-0 ms-2 text-muted">{{ area.mp.name|highlight:search }}, MP</p>
<div class="search-results">
{% for area_type in areas_by_type %}
{% if area_type.areas %}
{% include 'hub/includes/search-results-area-type-intro.html' with type=area_type.type areas=area_type.areas %}
<div>
{% for area in area_type.areas %}
<div class="card shadow-sm mb-3 mb-md-4">
<div class="card-body">
<h3 class="card-title mb-3"><a href="{{ area.get_absolute_url }}" class="stretched-link">{{ area.name|highlight:search }}</a></h3>
{% if area.mp %}
<div class="d-flex align-items-center">
{% include 'hub/includes/person-photo.html' with person=area.mp width="32" height="32" %}
<p class="mb-0 ms-2 text-muted">{{ area.mp.name|highlight:search }}, MP</p>
</div>
{% elif area.ppcs %}
{% for ppc in area.ppcs %}
<div class="d-flex align-items-center mt-2">
{% include 'hub/includes/person-photo.html' with person=ppc width="32" height="32" %}
<p class="mb-0 ms-2 text-muted">{{ ppc.name|highlight:search }} (PPC)</p>
</div>
{% endfor %}
{% endif %}
{% if area.area_type.code == "STC" %}
<p class="mb-0 text-muted">Single Tier council</p>
{% elif area.area_type.code == "DIS" %}
<p class="mb-0 text-muted">District council</p>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endfor %}
</div>
{% endfor %}
{% endif %}

</div>
Expand Down
25 changes: 25 additions & 0 deletions hub/templates/hub/includes/search-results-area-type-intro.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{% if type == "current-constituencies" %}
{% if areas|length == 1 %}
<h2 class="h4 text-primary">Current constituency</h2>
<p class="text-muted">This is the constituency currently represented by an MP in UK Parliament.</p>
{% else %}
<h2 class="h4 text-primary">Current constituencies</h2>
<p class="text-muted">These constituencies are the areas which MPs currently represent in UK Parliament.</p>
{% endif %}
{% elif type == "future-constituencies" %}
{% if areas|length == 1 %}
<h2 class="h4 text-primary">Future constituency</h2>
<p class="text-muted">This is the constituency in which parliamentary candidates will soon be standing for election.</p>
{% else %}
<h2 class="h4 text-primary">Future constituencies</h2>
<p class="text-muted">These are the constituencies in which parliamentary candidates will soon be standing for election.</p>
{% endif %}
{% elif type == "local-authorities" %}
{% if areas|length == 1 %}
<h2 class="h4 text-primary">Local authority</h2>
<p class="text-muted">The local authority has powers over local services like education, planning, and transport.</p>
{% else %}
<h2 class="h4 text-primary">Local authorities</h2>
<p class="text-muted">These bodies have powers over local services like education, planning, and transport.</p>
{% endif %}
{% endif %}
10 changes: 7 additions & 3 deletions hub/templatetags/hub_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ def splitlines(value):

@register.filter(name="highlight")
def highlight(text, search):
rgx = re.compile(re.escape(search), re.IGNORECASE)
html = rgx.sub(lambda m: f"<mark>{m.group()}</mark>", text)
return mark_safe(html)
try:
rgx = re.compile(re.escape(search), re.IGNORECASE)
html = rgx.sub(lambda m: f"<mark>{m.group()}</mark>", text)
return mark_safe(html)
except TypeError:
# search is probably None
return text


@register.filter
Expand Down
69 changes: 51 additions & 18 deletions hub/views/area.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ def get_areas_from_mapit(self, **kwargs):
elif kwargs.get("pc"):
gss_codes = mapit.postcode_point_to_gss_codes(kwargs["pc"])

areas = Area.objects.filter(gss__in=gss_codes, area_type__code="WMC")
areas = Area.objects.filter(gss__in=gss_codes)
areas = list(areas)
except (
NotFoundException,
Expand Down Expand Up @@ -427,29 +427,62 @@ def get_context_data(self, **kwargs):
context["areas"] = areas
context["error"] = error
elif search == "":
context["error"] = "Please enter a constituency name, MP name, or postcode."
context[
"error"
] = "Please enter a postcode, or the name of a constituency, MP, or local authority"
else:
areas_raw = Area.objects.filter(
name__icontains=search, area_type__code="WMC"
)
people_raw = Person.objects.filter(person_type="MP", name__icontains=search)
areas_raw = Area.objects.filter(name__icontains=search)
people_raw = Person.objects.filter(name__icontains=search)

areas = list(areas_raw)
context["areas"] = list(areas_raw)
for person in people_raw:
areas.append(person.area)
context["areas"].append(person.area)

if len(areas) == 0:
if len(context["areas"]) == 0:
context[
"error"
] = f"Sorry, we can’t find a UK location matching “{search}”. Try a nearby town or city?"
else:
for area in areas:
try:
area.mp = Person.objects.get(area=area, end_date__isnull=True)
except Person.DoesNotExist:
pass
areas.sort(key=lambda area: area.name)
context["areas"] = areas
] = f"Sorry, we can’t find any matches for “{search}”. Try a nearby town or city?"

if context["areas"] is not None and len(context["areas"]):
# Add MPs and PPCs to areas
for area in context["areas"]:
try:
area.mp = Person.objects.get(
area=area, end_date__isnull=True, person_type="MP"
)
except Person.DoesNotExist:
pass

try:
area.ppcs = Person.objects.filter(
area=area, end_date__isnull=True, person_type="PPC"
)
except Person.DoesNotExist:
pass

# Sort then split by area_type
context["areas"].sort(key=lambda area: area.name)
context["areas_by_type"] = [
{
"type": "current-constituencies",
"areas": [],
},
{
"type": "future-constituencies",
"areas": [],
},
{
"type": "local-authorities",
"areas": [],
},
]
for area in context["areas"]:
if area.area_type.code == "WMC":
context["areas_by_type"][0]["areas"].append(area)
elif area.area_type.code == "WMC23":
context["areas_by_type"][1]["areas"].append(area)
else:
context["areas_by_type"][2]["areas"].append(area)

return context

Expand Down

0 comments on commit 080c23c

Please sign in to comment.