Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions wcivf/apps/elections/managers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.db import models
from django.db.models import Sum, Count
from django.utils import timezone
from .helpers import EEHelper

Expand All @@ -22,8 +23,6 @@ def past(self):
"""
return self.filter(current=False, election_date__lt=timezone.now())


class ElectionManager(models.Manager.from_queryset(ElectionQuerySet)):
def get_explainer(self, election):
ee = EEHelper()
ee_data = ee.get_data(election["id"])
Expand Down Expand Up @@ -62,11 +61,16 @@ def election_id_to_type(self, election_id):
parts = election_id.split(".")
return parts[0]

def past(self):
def parties(self):
"""
Allows past method on QuerySet object to be called directly from the manager
Return a list of party models that are standing in this election
"""
return self.get_queryset().past()

return (
self.values("postelection__personpost__party")
.annotate(count=Count("postelection__personpost__party"))
.order_by("-postelection__personpost__party")
)


class PostManager(models.Manager):
Expand Down Expand Up @@ -104,3 +108,8 @@ def update_or_create_from_ynr(self, post_dict):
)

return (post, created)


class PostElectionQueryset(models.QuerySet):
def seats_total(self):
return self.aggregate(sum=Sum("winner_count"))["sum"]
26 changes: 24 additions & 2 deletions wcivf/apps/elections/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import datetime
import re

from django.db.models import Count
from django.utils import timezone

import pytz
Expand All @@ -14,7 +16,7 @@
from django.utils.text import slugify

from .helpers import get_election_timetable
from .managers import ElectionManager
from .managers import PostElectionQueryset, ElectionQuerySet

LOCAL_TZ = pytz.timezone("Europe/London")

Expand Down Expand Up @@ -46,7 +48,7 @@ class Election(models.Model):
metadata = JSONField(null=True)
any_non_by_elections = models.BooleanField(default=False)

objects = ElectionManager()
objects = ElectionQuerySet.as_manager()

class Meta:
ordering = ["election_date"]
Expand Down Expand Up @@ -220,6 +222,24 @@ def pluralized_division_name(self):

return pluralise.get(suffix, f"{suffix}s")

def parties(self):
parties_with_counts = {
d["personpost__party__party_id"]: {
"count": d["personpost__party__count"]
}
for d in self.postelection_set.values("personpost__party__party_id")
.annotate(Count("personpost__party"))
.order_by("-personpost__party")
}
from parties.models import Party

party_model_qs = Party.objects.filter(
party_id__in=parties_with_counts.keys()
)
for party in party_model_qs:
parties_with_counts[party.party_id]["party"] = party
return parties_with_counts


class Post(models.Model):
"""
Expand Down Expand Up @@ -335,6 +355,8 @@ class PostElection(models.Model):
wikipedia_url = models.CharField(blank=True, null=True, max_length=800)
wikipedia_bio = models.TextField(null=True)

objects = PostElectionQueryset.as_manager()

@property
def expected_sopn_date(self):
try:
Expand Down
112 changes: 68 additions & 44 deletions wcivf/apps/elections/templates/elections/election_view.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% extends "base.html" %}

{% load markdown_deux_tags %}
{% load breadcrumb_tags %}
{% load humanize %}

Expand All @@ -8,44 +8,68 @@
{% block og_description %}The {{ object.name }} {% if object.in_past %}was{% else %}will be{% endif %} held on {{ object.election_date }}.{% endblock og_description %}

{% block content %}
<div class="ds-scope">
<div class="ds-stack">
<h1>{{ object.nice_election_name }}</h1>

<div class="card">
<h1>{{ object.nice_election_name }}</h1>

<p>The {{ object.nice_election_name }}
{% if object.is_election_day %}
<strong>is being held today</strong>.
Polls are open from {{ object.polls_open|time:"ga" }} till {{ object.polls_close|time:"ga" }}
{% else %}
{% if object.in_past %}was{% else %}will be{% endif %} held <strong>{{ object.election_date|naturalday:"\o\n l j F Y" }}</strong>.
{% endif %}
</p>
<p>The {{ object.nice_election_name }}
{% if object.is_election_day %}
<strong>is being held today</strong>.
Polls are open from {{ object.polls_open|time:"ga" }} till {{ object.polls_close|time:"ga" }}
{% else %}
{% if object.in_past %}was{% else %}will be{% endif %} held <strong>{{ object.election_date|naturalday:"\o\n l j F Y" }}</strong>.
{% endif %}
</p>

{% if object.election_type != "ref" %}
{% if election.person_set.count %}
<p>{% if object.locked %}There are {% else %}We know about {% endif %}<strong>{{ election.person_set.count }}</strong> candidates
{% if object.election_type != "ref" %}
{% if election.personpost_set.count %}
<p>{% if object.locked %}There are {% else %}We know about {% endif %}<strong>{{ election.personpost_set.count }}</strong> candidates
{% if object.in_past %}that stood{% else %}standing{% endif %} for this election,
in <strong>{{ object.post_set.count }}</strong> posts.</p>
{% if not object.in_past and not object.locked %}
<p><a href="{{ object.ynr_link }}">Add more at our candidate crowd-sourcing site</a></p>
{% endif %}
{% else %}
{% if not object.in_past and not election.slug == 'parl.2017-06-08' %}
<p><a href="{{ object.ynr_link }}">Add some candidates at our candidate crowd-sourcing site</a></p>
{% endif %}
in <strong>{{ object.postelection_set.count }}</strong> {{ object.pluralized_division_name }}.</p>
{% endif %}

<h3>{{ object.pluralized_division_name|title }}</h3>
<ul>

{{ object.description|markdown }}

<h2>Parties</h2>
<p>{{ object.parties.keys|length }} parties are contesting {{ object.postelection_set.seats_total }} seats across {{ object.postelection_set.count }} {{ object.pluralized_division_name }}.</p>
<div class="ds-grid">
{% for party_id, party_data in object.parties.items %}
<div class="ds-with-sidebar">
<div>
<div class="ds-sidebar" style="flex-basis: 5rem">{% if party_data.party.emblem %}<img src="{{ party_data.party.emblem.url }}">{% endif %}</div>
<div class="ds-not-sidebar">
{{ party_data.party.party_name }}<br>
{{ party_data.count }} candidate{{ party_data.count|pluralize }}

</div>
</div>
</div>
{% endfor %}


</div>


<h2>{{ object.pluralized_division_name|title }}</h2>
<table>
<tr>
<th>Name</th>
<th>Candidates</th>
<th>Seats</th>
</tr>
{% for postelection in object.postelection_set.all %}
<li>
<a href="{{ postelection.get_absolute_url }}">{{ postelection.post.label }}</a>
{{ postelection.short_cancelled_message_html }}
</li>
<tr>
<td><a href="{{ postelection.get_absolute_url }}">{{ postelection.post.label }}</a>
{{ postelection.short_cancelled_message_html }}</td>
<td>{{ postelection.personpost_set.count }}</td>
<td>{{ postelection.winner_count }}</td>

</tr>
{% endfor %}
</ul>
{% endif %}
<script type="application/ld+json">
</table>
{% endif %}
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "Event",
Expand All @@ -58,22 +82,22 @@ <h3>{{ object.pluralized_division_name|title }}</h3>
}
}
</script>
</div>


{% include "elections/includes/_postcode_search_form.html" %}

{#{% include "feedback/feedback_form.html" %}#}

<section>
{% include "elections/includes/_postcode_search_form.html" %}
</section>
</div>
</div>
{% endblock content %}

{% block breadcrumbs %}
<ol vocab="http://schema.org/" typeof="BreadcrumbList" class="breadcrumbs" aria-label="You are here:" role="navigation">
{% url 'home_view' as home_view %}
{% breadcrumb_item home_view 'Home' 1 %}
{% url 'elections_view' as elections %}
{% breadcrumb_item elections 'Elections' 2 %}
<li class="disabled"><span class="show-for-sr">Current: </span> {{ object.nice_election_name }}</li>
</ol>
<ol vocab="http://schema.org/" typeof="BreadcrumbList" class="breadcrumbs" aria-label="You are here:" role="navigation">
{% url 'home_view' as home_view %}
{% breadcrumb_item home_view 'Home' 1 %}
{% url 'elections_view' as elections %}
{% breadcrumb_item elections 'Elections' 2 %}
<li class="disabled"><span class="show-for-sr">Current: </span> {{ object.nice_election_name }}</li>
</ol>

{% endblock breadcrumbs %}
7 changes: 6 additions & 1 deletion wcivf/apps/elections/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
PostcodeiCalView,
RedirectPostView,
PartyListVew,
ElectionTypeForDateView,
)
from .helpers import ElectionIDSwitcher

Expand All @@ -29,7 +30,11 @@
PartyListVew.as_view(),
name="party_list_view",
),
#
url(
r"^(?P<election>[a-z]+\.\d{4}-\d{2}-\d{2})/$",
ElectionTypeForDateView.as_view(),
name="election_type_for_date_view",
),
url(
"^(?P<election>[a-z\-]+\.[^/]+)(?:/(?P<ignored_slug>[^/]+))?/$",
ElectionIDSwitcher(election_view=ElectionView, ballot_view=PostView),
Expand Down
79 changes: 77 additions & 2 deletions wcivf/apps/elections/views/election_views.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
from dateutil.parser import parse
from django.views.generic import TemplateView, DetailView, RedirectView
from django.http import Http404
from django.shortcuts import get_object_or_404


from django.db.models import Prefetch
from django.db.models import Prefetch, Count, Sum, Subquery, IntegerField
from django.apps import apps


from elections.views.mixins import (
NewSlugsRedirectMixin,
PostelectionsToPeopleMixin,
)
from elections.models import PostElection
from elections.models import PostElection, Election
from parties.models import LocalParty, Party
from people.models import PersonPost

Expand All @@ -34,6 +35,80 @@ def get_context_data(self, **kwargs):
return context


class ElectionTypeForDateView(TemplateView):
template_name = "elections/election_type_for_date.html"

supported_types = {
"local": "Local",
"parl": "UK Parliament",
"nia": "Northern Ireland Assembly",
"senedd": "Senedd Cymru",
"sp": "Scottish Parliament",
"gla": "Greater London Assembly",
"pcc": "Police and Crime Commissioner",
"mayor": "Mayoral",
}

def get(self, request, *args, **kwargs):
self.election_type, self.election_date = self.kwargs.get(
"election"
).split(".")
if self.election_type not in self.supported_types.keys():
raise Http404()
return super().get(request, *args, **kwargs)

def get_template_names(self):
return [
f"elections/{self.election_type}_election_type_for_date.html",
"elections/generic_election_type_for_date.html",
]

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["election_type"] = self.supported_types[self.election_type]
context["election_date"] = parse(self.election_date)

base_elections_qs = Election.objects.filter(
election_date=self.election_date, election_type=self.election_type
).order_by("name")

aggregate_qs = base_elections_qs.order_by("postelection")
context["total_candidates"] = aggregate_qs.all().aggregate(
total_candidates=Count("postelection__personpost", distinct=True)
)["total_candidates"]

context["total_seats"] = aggregate_qs.all().aggregate(
total_seats=Sum("postelection__winner_count")
)["total_seats"]

context["total_parties"] = aggregate_qs.all().aggregate(
total_parties=Count(
"postelection__personpost__party", distinct=True
),
)["total_parties"]

context["total_ballots"] = aggregate_qs.all().aggregate(
total_ballots=Count("postelection__ballot_paper_id", distinct=True),
)["total_ballots"]

context["elections"] = base_elections_qs.annotate(
parties_count=Count(
"postelection__personpost__party", distinct=True
),
candidates_count=Count("postelection__personpost", distinct=True),
ballots=Count("postelection", distinct=True),
).annotate(
seats_total=Subquery(
base_elections_qs.order_by("postelection")
.annotate(sum_value=Sum("postelection__winner_count"))
.values("sum_value"),
output_field=IntegerField(),
)
)

return context


class ElectionView(NewSlugsRedirectMixin, DetailView):
template_name = "elections/election_view.html"
model = apps.get_model("elections.Election")
Expand Down
2 changes: 1 addition & 1 deletion wcivf/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
)

MIDDLEWARE = (
"debug_toolbar.middleware.DebugToolbarMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
Expand All @@ -71,7 +72,6 @@
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"django.middleware.security.SecurityMiddleware",
"core.middleware.UTMTrackerMiddleware",
"debug_toolbar.middleware.DebugToolbarMiddleware",
)

ROOT_URLCONF = "wcivf.urls"
Expand Down