├── wcivf ├── __init__.py ├── apps │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── apps.py │ │ ├── permissions.py │ │ └── urls.py │ ├── core │ │ ├── __init__.py │ │ ├── models.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ └── forbidden_markdown │ │ │ │ ├── input │ │ │ │ ├── strikethrough.txt │ │ │ │ ├── horizontal_rules.txt │ │ │ │ ├── headings.txt │ │ │ │ ├── html.txt │ │ │ │ ├── code.txt │ │ │ │ ├── blockquote.txt │ │ │ │ ├── images.txt │ │ │ │ ├── tables.txt │ │ │ │ └── links.txt │ │ │ │ └── expected │ │ │ │ ├── strikethrough.txt │ │ │ │ ├── horizontal_rules.txt │ │ │ │ ├── headings.txt │ │ │ │ ├── html.txt │ │ │ │ ├── images.txt │ │ │ │ ├── blockquote.txt │ │ │ │ ├── code.txt │ │ │ │ ├── tables.txt │ │ │ │ └── links.txt │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ ├── __init__.py │ │ │ │ ├── setup_django_site.py │ │ │ │ └── truncate_replicated_tables.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0004_delete_loggedpostcode.py │ │ │ ├── 0003_auto_20210225_1103.py │ │ │ └── 0002_auto_20170526_1448.py │ │ ├── apps.py │ │ ├── forms.py │ │ ├── middleware.py │ │ ├── utils.py │ │ ├── db_routers.py │ │ └── context_processors.py │ ├── elections │ │ ├── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0014_remove_post_elections.py │ │ │ ├── 0034_update_ordering.py │ │ │ ├── 0028_add_territory_to_posts.py │ │ │ ├── 0015_auto_20170304_1354.py │ │ │ ├── 0030_post_organization_type.py │ │ │ ├── 0038_default_winner_count.py │ │ │ ├── 0009_election_for_post_role.py │ │ │ ├── 0040_postelection_requires_voter_id.py │ │ │ ├── 0016_postelection_contested.py │ │ │ ├── 0018_postelection_locked.py │ │ │ ├── 0005_election_election_type.py │ │ │ ├── 0006_votingsystem_uses_lists.py │ │ │ ├── 0033_auto_20210210_1422.py │ │ │ ├── 0020_postelection_ballot_paper_id.py │ │ │ ├── 0021_postelection_winner_count.py │ │ │ ├── 0024_default_for_has_by_elections.py │ │ │ ├── 0017_parl-2017-06-08-weight.py │ │ │ ├── 0027_unique_on_ballot_id.py │ │ │ ├── 0032_auto_20210210_1155.py │ │ │ ├── 0019_election_metadata.py │ │ │ ├── 0023_election_any_non_by_elections.py │ │ │ ├── 0011_auto_20170303_1823.py │ │ │ ├── 0007_auto_20160405_0857.py │ │ │ ├── 0037_dummypostelection.py │ │ │ ├── 0035_auto_20210928_1303.py │ │ │ ├── 0002_auto_20160323_1533.py │ │ │ ├── 0029_wikipedia_on_ballot.py │ │ │ ├── 0047_alter_postelection_metadata_and_more.py │ │ │ ├── 0008_auto_20160405_1412.py │ │ │ ├── 0039_remove_historic_metadata.py │ │ │ ├── 0026_postelection_voting_system.py │ │ │ ├── 0045_alter_post_territory.py │ │ │ ├── 0013_auto_20170304_1354.py │ │ │ └── 0044_fix_territory.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ ├── __init__.py │ │ │ │ └── set_election_weight.py │ │ ├── templatetags │ │ │ ├── __init__.py │ │ │ └── postcode_tags.py │ │ ├── apps.py │ │ ├── views │ │ │ └── __init__.py │ │ ├── templates │ │ │ └── elections │ │ │ │ └── includes │ │ │ │ ├── _by_election_reason.html │ │ │ │ ├── _voting_system_breadcrumbs.html │ │ │ │ ├── _people_list.html │ │ │ │ ├── _ams_breadcrumbs.html │ │ │ │ ├── _post_breadcrumbs.html │ │ │ │ ├── _postcode_meta_title.html │ │ │ │ ├── _election_breadcrumbs.html │ │ │ │ ├── _postcode_meta_description.html │ │ │ │ ├── _ld_candidate.html │ │ │ │ ├── _elections_breadcrumbs.html │ │ │ │ ├── _postcode_search_form.html │ │ │ │ ├── _postcode_breadcrumbs.html │ │ │ │ ├── _election_list.html │ │ │ │ ├── _post_meta_title.html │ │ │ │ ├── eu_results.html │ │ │ │ ├── _previous_party_affiliations.html │ │ │ │ ├── _contact_details_registration.html │ │ │ │ ├── _council_contact_details.html │ │ │ │ └── _post_meta_description.html │ │ ├── tests │ │ │ └── test_tests.py │ │ ├── admin.py │ │ ├── postal_votes.py │ │ └── sitemaps.py │ ├── feedback │ │ ├── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0003_auto_20210225_1103.py │ │ │ ├── 0004_feedback_sources.py │ │ │ ├── 0006_feedback_flagged_as_spam.py │ │ │ ├── 0002_feedback_token.py │ │ │ ├── 0011_alter_noelectionfeedback_options.py │ │ │ ├── 0005_feedback_vote.py │ │ │ ├── 0007_alter_feedback_token.py │ │ │ └── 0009_alter_feedback_vote.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ └── __init__.py │ │ ├── tests.py │ │ ├── apps.py │ │ ├── templates │ │ │ └── feedback │ │ │ │ ├── feedback_form_view.html │ │ │ │ ├── no_election_feedback_form_view.html │ │ │ │ └── admin │ │ │ │ └── change_list.html │ │ ├── context_processors.py │ │ └── urls.py │ ├── leaflets │ │ ├── __init__.py │ │ ├── api │ │ │ ├── __init__.py │ │ │ └── serializers.py │ │ ├── tests │ │ │ └── __init__.,py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ └── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ └── 0002_auto_20210603_0922.py │ │ └── models.py │ ├── parishes │ │ ├── __init__.py │ │ ├── tests │ │ │ └── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ └── 0002_alter_parishcouncilelection_is_contested.py │ │ └── management │ │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── import_parish_council_elections.py │ ├── parties │ │ ├── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0018_alter_partydescription_options.py │ │ │ ├── 0009_manifesto_easy_read_url.py │ │ │ ├── 0020_party_alternative_name.py │ │ │ ├── 0002_auto_20160412_1739.py │ │ │ ├── 0010_localparty_is_local.py │ │ │ ├── 0011_replace_emblem_with_emblem_url.py │ │ │ ├── 0003_auto_20160422_1148.py │ │ │ ├── 0023_localparty_bluesky_localparty_instagram.py │ │ │ ├── 0012_add_youtube_and_contact_url.py │ │ │ ├── 0008_auto_20180422_1248.py │ │ │ ├── 0007_auto_20180417_1733.py │ │ │ └── 0021_party_nations.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ └── __init__.py │ │ ├── apps.py │ │ ├── admin.py │ │ ├── sitemaps.py │ │ ├── templates │ │ │ └── parties │ │ │ │ └── party_list.html │ │ ├── urls.py │ │ └── tests │ │ │ └── factories.py │ ├── people │ │ ├── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0008_remove_person_party.py │ │ │ ├── 0031_person_sort_name.py │ │ │ ├── 0032_person_death_date.py │ │ │ ├── 0036_auto_20191213_1028.py │ │ │ ├── 0035_personpost_votes_cast.py │ │ │ ├── 0045_person_delisted.py │ │ │ ├── 0016_auto_20170518_1028.py │ │ │ ├── 0040_person_blog_url.py │ │ │ ├── 0043_personpost_rank.py │ │ │ ├── 0009_auto_20170303_1823.py │ │ │ ├── 0044_person_mastodon_username.py │ │ │ ├── 0024_personpost_elected.py │ │ │ ├── 0005_person_wikipedia_bio.py │ │ │ ├── 0021_person_twfy_id.py │ │ │ ├── 0011_person_statement_to_voters.py │ │ │ ├── 0013_auto_20170515_2047.py │ │ │ ├── 0020_person_favourite_biscuit.py │ │ │ ├── 0034_auto_20191213_0942.py │ │ │ ├── 0047_person_statement_to_voters_last_updated.py │ │ │ ├── 0027_person_party_ppc_page_url.py │ │ │ ├── 0010_auto_20170306_1206.py │ │ │ ├── 0012_auto_20170515_1641.py │ │ │ ├── 0015_auto_20170518_1015.py │ │ │ ├── 0017_auto_20170518_1158.py │ │ │ ├── 0026_auto_20180417_1944.py │ │ │ ├── 0029_person_last_updated.py │ │ │ ├── 0050_alter_personredirect_options_and_more.py │ │ │ ├── 0051_alter_personredirect_old_person_id_and_more.py │ │ │ ├── 0046_personpost_deselected_personpost_deselected_source.py │ │ │ ├── 0042_personpost_previous_party_affiliations.py │ │ │ ├── 0039_auto_20210928_1303.py │ │ │ ├── 0006_personpost_party.py │ │ │ ├── 0007_personpost_election.py │ │ │ ├── 0002_auto_20160412_1739.py │ │ │ ├── 0030_insta-youtube.py │ │ │ ├── 0049_personredirect.py │ │ │ └── 0041_dummycandidacy_dummyperson.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ ├── __init__.py │ │ │ │ ├── delete_deleted_people.py │ │ │ │ └── import_facebook_ads.py │ │ ├── templatetags │ │ │ ├── __init__.py │ │ │ └── person_tags.py │ │ ├── templates │ │ │ └── people │ │ │ │ ├── includes │ │ │ │ ├── _person_meta_title.html │ │ │ │ ├── _person_meta_description.html │ │ │ │ ├── _person_hustings_card.html │ │ │ │ ├── intros │ │ │ │ │ ├── _votes_cast.html │ │ │ │ │ ├── _independent_mayor_or_pcc.html │ │ │ │ │ ├── _independent.html │ │ │ │ │ ├── _mayor_or_pcc.html │ │ │ │ │ ├── _constituency.html │ │ │ │ │ ├── _speaker.html │ │ │ │ │ ├── _parl.html │ │ │ │ │ └── base.html │ │ │ │ ├── _person_about_card.html │ │ │ │ └── _person_multiple_current_candidacies.html │ │ │ │ └── dummy_statements │ │ │ │ ├── sofia-williamson.txt │ │ │ │ └── rhuanedd-llewelyn.txt │ │ ├── apps.py │ │ ├── static │ │ │ └── people │ │ │ │ └── images │ │ │ │ ├── blank-man.png │ │ │ │ ├── blank-woman.png │ │ │ │ ├── blank-avatar.png │ │ │ │ └── blank-woman-bg.png │ │ ├── admin.py │ │ ├── sitemaps.py │ │ ├── urls.py │ │ └── tests │ │ │ └── test_people_factories.py │ ├── peoplecvs │ │ ├── __init__.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ └── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0003_delete_cvs.py │ │ │ └── 0002_auto_20170522_1324.py │ │ ├── README.md │ │ └── models.py │ ├── pledges │ │ ├── __init__.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ └── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ └── 0002_auto_20180502_0946.py │ │ ├── apps.py │ │ └── models.py │ ├── ppc_2024 │ │ ├── __init__.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ └── __init__.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── apps.py │ │ ├── urls.py │ │ └── filters.py │ ├── administrations │ │ ├── __init__.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── admin.py │ │ ├── models.py │ │ ├── templates │ │ │ └── responsibilities │ │ │ │ ├── org_id │ │ │ │ ├── WMCA.html │ │ │ │ └── parl-hoc.html │ │ │ │ ├── post_type │ │ │ │ ├── SPC.html │ │ │ │ ├── WAC.html │ │ │ │ ├── GLA_A.html │ │ │ │ ├── LBW.html │ │ │ │ ├── MTW.html │ │ │ │ ├── LGE.html │ │ │ │ ├── SPE.html │ │ │ │ ├── DIW.html │ │ │ │ ├── CED.html │ │ │ │ ├── GLA_C.html │ │ │ │ ├── UTW.html │ │ │ │ ├── PCC.html │ │ │ │ └── NIE.html │ │ │ │ └── single_id │ │ │ │ ├── O::NWM::mayor.html │ │ │ │ ├── O::north-of-tyne::mayor.html │ │ │ │ ├── O::HCK::mayor.html │ │ │ │ ├── O::LCE::mayor.html │ │ │ │ ├── O::BDF::mayor.html │ │ │ │ ├── O::WECA::mayor.html │ │ │ │ ├── O::MAS::mayor.html │ │ │ │ ├── O::SLF::mayor.html │ │ │ │ ├── O::london::mayor.html │ │ │ │ ├── O::WAT::mayor.html │ │ │ │ ├── O::DNC::mayor.html │ │ │ │ ├── O::NTY::mayor.html │ │ │ │ ├── O::CPCA::mayor.html │ │ │ │ ├── O::LEW::mayor.html │ │ │ │ ├── O::SCR::mayor.html │ │ │ │ ├── O::EMCA::mayor.html │ │ │ │ ├── O::CRY::mayor.html │ │ │ │ ├── O::west-yorkshire::mayor.html │ │ │ │ ├── O::MDB::mayor.html │ │ │ │ ├── O::LCR::mayor.html │ │ │ │ └── O::NEMC::mayor.html │ │ ├── apps.py │ │ ├── forms.py │ │ ├── urls.py │ │ └── tests │ │ │ └── test_admistration_object.py │ ├── hustings │ │ ├── tests │ │ │ └── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0002_auto_20170601_2155.py │ │ │ ├── 0004_auto_20170602_1031.py │ │ │ ├── 0003_husting_video_url.py │ │ │ ├── 0005_auto_20210326_1111.py │ │ │ ├── 0009_remove_postcode_amend_location.py │ │ │ ├── 0007_auto_20210419_1539.py │ │ │ ├── 0008_url_max_length.py │ │ │ ├── 0006_increase_url_max_length.py │ │ │ └── 0011_husting_status.py │ │ ├── __init__.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ └── __init__.py │ │ ├── context_processors.py │ │ ├── urls.py │ │ ├── api │ │ │ └── serializers.py │ │ ├── importers.py │ │ ├── templates │ │ │ └── hustings │ │ │ │ └── includes │ │ │ │ ├── _person.html │ │ │ │ └── _ld_husting.html │ │ ├── admin.py │ │ └── README.md │ ├── mailing_list │ │ └── __init__.py │ ├── news_mentions │ │ ├── __init__.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ └── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0003_auto_20210225_1103.py │ │ │ └── 0002_ballotnewsarticle_publisher.py │ │ ├── apps.py │ │ ├── models.py │ │ └── templates │ │ │ └── news_mentions │ │ │ └── news_articles.html │ ├── profiles │ │ ├── __init__.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ └── __init__.py │ │ ├── admin.py │ │ └── models.py │ └── referendums │ │ ├── __init__.py │ │ ├── tests │ │ └── __init__.py │ │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── import_referendums.py │ │ ├── migrations │ │ ├── __init__.py │ │ └── 0002_referendum_ballot.py │ │ ├── templates │ │ └── referendums │ │ │ └── detail.html │ │ └── importers.py ├── assets │ ├── js │ │ └── scripts.js │ └── images │ │ ├── logo.png │ │ ├── slice1.png │ │ ├── logo_icon.png │ │ ├── ryan-evans.png │ │ ├── blank-avatar.png │ │ ├── jimmy-jordan.png │ │ ├── og_image_logo.png │ │ ├── sarah-jarman.png │ │ ├── rhuanedd-llewelyn.png │ │ ├── sofia-williamson.png │ │ └── maze_illustration_no_text.png ├── settings │ ├── __init__.py │ ├── lambda.py │ ├── ci.py │ └── local.example.py ├── templates │ ├── robots.txt │ └── opensearch.xml ├── utils.py └── wsgi.py ├── .circleci └── tests │ └── __init__.py ├── .python-version ├── results_app.png ├── CODE_OF_CONDUCT.md ├── deploy ├── after_install │ ├── compilemessages.sh │ ├── install_python_deps.sh │ └── collectstatic.sh ├── deploy-env-vars.json ├── files │ ├── scripts │ │ ├── install-uv.sh │ │ └── remove_db_replication.sh │ └── systemd │ │ ├── cloudwatch.service │ │ ├── db_replication.service │ │ └── gunicorn.service ├── start_application.sh └── validate_application.sh ├── .coveragerc ├── .tx └── config ├── transifex.yml ├── manage.py ├── .pre-commit-config.yaml ├── CHANGELOG.txt ├── docs ├── sslcertificate.md └── s3.md └── samconfig.toml /wcivf/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.circleci/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /wcivf/apps/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wcivf/apps/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/core/models.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/assets/js/scripts.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/elections/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/leaflets/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/parishes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/parties/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/people/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/peoplecvs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/pledges/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/ppc_2024/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/api/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/core/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/core/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/leaflets/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/leaflets/tests/__init__.,py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/mailing_list/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/news_mentions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/parishes/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/profiles/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wcivf/apps/referendums/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/leaflets/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/leaflets/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/parishes/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/parties/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/peoplecvs/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/peoplecvs/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/pledges/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/pledges/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/ppc_2024/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/ppc_2024/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/profiles/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/referendums/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/core/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/elections/management/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/management/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wcivf/apps/news_mentions/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/news_mentions/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/parties/management/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wcivf/apps/people/management/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wcivf/apps/people/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wcivf/apps/profiles/management/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wcivf/apps/referendums/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/referendums/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/leaflets/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/parishes/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/peoplecvs/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/pledges/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/ppc_2024/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/elections/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wcivf/apps/news_mentions/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/parties/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wcivf/apps/people/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wcivf/apps/profiles/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wcivf/apps/referendums/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/settings/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import * # noqa 2 | -------------------------------------------------------------------------------- /wcivf/templates/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/admin.py: -------------------------------------------------------------------------------- 1 | # Register your models here. 2 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/models.py: -------------------------------------------------------------------------------- 1 | # Create your models here. 2 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/org_id/WMCA.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/post_type/SPC.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/post_type/WAC.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a package. 3 | """ 4 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/post_type/GLA_A.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/management/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Hello, this is a package now. 3 | """ 4 | -------------------------------------------------------------------------------- /wcivf/apps/people/templates/people/includes/_person_meta_title.html: -------------------------------------------------------------------------------- 1 | {{ object.title }} 2 | -------------------------------------------------------------------------------- /results_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/results_app.png -------------------------------------------------------------------------------- /wcivf/apps/hustings/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Look - it's a package. 3 | """ 4 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/tests.py: -------------------------------------------------------------------------------- 1 | # from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Please read and follow our [Code of Conduct](https://democracyclub.org.uk/code-of-conduct/). 2 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/input/strikethrough.txt: -------------------------------------------------------------------------------- 1 | Strikethrough uses two tildes. ~~Scratch this.~~ 2 | -------------------------------------------------------------------------------- /wcivf/apps/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | name = "api" 6 | -------------------------------------------------------------------------------- /wcivf/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/wcivf/assets/images/logo.png -------------------------------------------------------------------------------- /wcivf/apps/core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | name = "core" 6 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/expected/strikethrough.txt: -------------------------------------------------------------------------------- 1 |

Strikethrough uses two tildes. ~~Scratch this.~~

2 | -------------------------------------------------------------------------------- /wcivf/assets/images/slice1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/wcivf/assets/images/slice1.png -------------------------------------------------------------------------------- /wcivf/apps/people/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PeopleConfig(AppConfig): 5 | name = "people" 6 | -------------------------------------------------------------------------------- /wcivf/assets/images/logo_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/wcivf/assets/images/logo_icon.png -------------------------------------------------------------------------------- /wcivf/assets/images/ryan-evans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/wcivf/assets/images/ryan-evans.png -------------------------------------------------------------------------------- /wcivf/apps/feedback/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class FeedbackConfig(AppConfig): 5 | name = "feedback" 6 | -------------------------------------------------------------------------------- /wcivf/apps/parties/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PartiesConfig(AppConfig): 5 | name = "parties" 6 | -------------------------------------------------------------------------------- /wcivf/apps/pledges/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PledgesConfig(AppConfig): 5 | name = "pledges" 6 | -------------------------------------------------------------------------------- /wcivf/assets/images/blank-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/wcivf/assets/images/blank-avatar.png -------------------------------------------------------------------------------- /wcivf/assets/images/jimmy-jordan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/wcivf/assets/images/jimmy-jordan.png -------------------------------------------------------------------------------- /wcivf/assets/images/og_image_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/wcivf/assets/images/og_image_logo.png -------------------------------------------------------------------------------- /wcivf/assets/images/sarah-jarman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/wcivf/assets/images/sarah-jarman.png -------------------------------------------------------------------------------- /wcivf/apps/elections/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ElectionsConfig(AppConfig): 5 | name = "elections" 6 | -------------------------------------------------------------------------------- /wcivf/assets/images/rhuanedd-llewelyn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/wcivf/assets/images/rhuanedd-llewelyn.png -------------------------------------------------------------------------------- /wcivf/assets/images/sofia-williamson.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/wcivf/assets/images/sofia-williamson.png -------------------------------------------------------------------------------- /deploy/after_install/compilemessages.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xeE 3 | 4 | cd /var/www/wcivf/code/ 5 | uv run python manage.py compilemessages 6 | -------------------------------------------------------------------------------- /deploy/after_install/install_python_deps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xeE pipefail 3 | 4 | cd /var/www/wcivf/code/ 5 | uv sync --group deploy 6 | -------------------------------------------------------------------------------- /wcivf/apps/elections/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .postcode_view import PostcodeView, PostcodeiCalView # noqa 2 | from .election_views import * # noqa 3 | -------------------------------------------------------------------------------- /wcivf/apps/news_mentions/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class NewsMentionsConfig(AppConfig): 5 | name = "news_mentions" 6 | -------------------------------------------------------------------------------- /wcivf/assets/images/maze_illustration_no_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/wcivf/assets/images/maze_illustration_no_text.png -------------------------------------------------------------------------------- /wcivf/apps/people/static/people/images/blank-man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/wcivf/apps/people/static/people/images/blank-man.png -------------------------------------------------------------------------------- /wcivf/apps/people/static/people/images/blank-woman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/wcivf/apps/people/static/people/images/blank-woman.png -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | */tests/* 4 | */apps.py 5 | */migrations/* 6 | */migrations/* 7 | wcivf/wsgi.py 8 | */lib/python3.4/site-packages/* 9 | -------------------------------------------------------------------------------- /wcivf/apps/people/static/people/images/blank-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/wcivf/apps/people/static/people/images/blank-avatar.png -------------------------------------------------------------------------------- /wcivf/apps/people/static/people/images/blank-woman-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyClub/WhoCanIVoteFor/HEAD/wcivf/apps/people/static/people/images/blank-woman-bg.png -------------------------------------------------------------------------------- /deploy/after_install/collectstatic.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xeE 3 | 4 | cd /var/www/wcivf/code/ 5 | uv run python /var/www/wcivf/code/manage.py collectstatic --noinput --clear 6 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/post_type/LBW.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | 6 | 7 | {% endfilter %} 8 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_by_election_reason.html: -------------------------------------------------------------------------------- 1 | {% if object.by_election_reason_text %} 2 |

{{ object.by_election_reason_text }}

3 | {% endif %} 4 | -------------------------------------------------------------------------------- /wcivf/apps/people/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Person, PersonPost 4 | 5 | admin.site.register(Person) 6 | admin.site.register(PersonPost) 7 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/input/horizontal_rules.txt: -------------------------------------------------------------------------------- 1 | Three or more... 2 | 3 | --- 4 | 5 | Hyphens 6 | 7 | *** 8 | 9 | Asterisks 10 | 11 | ___ 12 | 13 | Underscores 14 | -------------------------------------------------------------------------------- /wcivf/apps/ppc_2024/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class Ppc2024Config(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "ppc_2024" 7 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/expected/horizontal_rules.txt: -------------------------------------------------------------------------------- 1 |

Three or more...

2 |

---

3 |

Hyphens

4 |

***

5 |

Asterisks

6 |

___

7 |

Underscores

8 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def show_hustings_cta(request): 5 | return {"SHOW_HUSTINGS_CTA": getattr(settings, "SHOW_HUSTINGS_CTA", False)} 6 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AdministrationsConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "administrations" 7 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from localflavor.gb.forms import GBPostcodeField 3 | 4 | 5 | class YourAreaPostcodeForm(forms.Form): 6 | postcode = GBPostcodeField(label="Enter your postcode") 7 | -------------------------------------------------------------------------------- /wcivf/utils.py: -------------------------------------------------------------------------------- 1 | class NoOpOutputWrapper: 2 | """ 3 | Used in place of django.core.management.base.OutputWrapper 4 | to support quiet mode in mgmt commands. 5 | """ 6 | 7 | def write(self, *args): 8 | pass 9 | -------------------------------------------------------------------------------- /deploy/deploy-env-vars.json: -------------------------------------------------------------------------------- 1 | { 2 | "PROJECT_NAME": "wcivf", 3 | "APP_NAME": "wcivf", 4 | "PROJECT_ROOT": "/var/www/wcivf", 5 | "ENV_FILE_PATH": "/var/www/wcivf/code/.env", 6 | "CRON_EMAIL": "developers@democracyclub.org.uk" 7 | } 8 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/templates/feedback/feedback_form_view.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | {% load i18n %} 4 | 5 | {% include "feedback/feedback_form.html" with feedback_form=form %} 6 | 7 | {% endblock content %} 8 | -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | type = PO 4 | 5 | [WhoCanIVoteFor.djangopo] 6 | file_filter = locale//LC_MESSAGES/django.po 7 | source_file = locale/en/LC_MESSAGES/django.po 8 | source_lang = en 9 | type = PO 10 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/input/headings.txt: -------------------------------------------------------------------------------- 1 | # H1 2 | ## H2 3 | ### H3 4 | #### H4 5 | ##### H5 6 | ###### H6 7 | 8 | Alternatively, for H1 and H2, an underline-ish style: 9 | 10 | Alt-H1 11 | ====== 12 | 13 | Alt-H2 14 | ------ -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/input/html.txt: -------------------------------------------------------------------------------- 1 |
2 |
Definition list
3 |
Is something people use sometimes.
4 | 5 |
Markdown in HTML
6 |
Does *not* work **very** well. Use HTML tags.
7 |
8 | -------------------------------------------------------------------------------- /wcivf/apps/profiles/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Profile 4 | 5 | 6 | class ProfileAdmin(admin.ModelAdmin): 7 | raw_id_fields = ("person_post",) 8 | 9 | 10 | admin.site.register(Profile, ProfileAdmin) 11 | -------------------------------------------------------------------------------- /transifex.yml: -------------------------------------------------------------------------------- 1 | git: 2 | filters: 3 | - filter_type: file 4 | file_format: PO 5 | source_file: locale/en/LC_MESSAGES/django.po 6 | source_language: en 7 | translation_files_expression: 'locale//LC_MESSAGES/django.po' 8 | 9 | 10 | -------------------------------------------------------------------------------- /deploy/files/scripts/install-uv.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xeE 3 | 4 | UV_CONSTRAINT="==0.9.*" 5 | 6 | if [ "$CI" = "true" ]; then 7 | pip install uv"$UV_CONSTRAINT" 8 | else 9 | sudo PIP_BREAK_SYSTEM_PACKAGES=1 pip install uv"$UV_CONSTRAINT" 10 | fi 11 | -------------------------------------------------------------------------------- /wcivf/apps/parties/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Party 4 | 5 | 6 | class PartyAdmin(admin.ModelAdmin): 7 | readonly_fields = ("party_id", "party_name", "emblem_url") 8 | 9 | 10 | admin.site.register(Party, PartyAdmin) 11 | -------------------------------------------------------------------------------- /wcivf/apps/people/templates/people/includes/_person_meta_description.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% autoescape off %}{% blocktrans trimmed with intro=object.text_intro %}{{ intro }}Get the latest information on this candidate at {{ SITE_TITLE }}{% endblocktrans %}{% endautoescape %} -------------------------------------------------------------------------------- /wcivf/apps/hustings/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from hustings.views import AddHustingView 3 | 4 | urlpatterns = [ 5 | path( 6 | "add/", 7 | AddHustingView.as_view(), 8 | name="add-husting", 9 | ), 10 | ] 11 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_voting_system_breadcrumbs.html: -------------------------------------------------------------------------------- 1 | {% extends "elections/includes/_elections_breadcrumbs.html" %} 2 | {% load i18n %} 3 | 4 | {% block extra_crumbs %} 5 |
  • 6 | {{ voting_system }} 7 |
  • 8 | {% endblock extra_crumbs %} 9 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/templates/feedback/no_election_feedback_form_view.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | {% load i18n %} 4 | 5 | {% include "feedback/no_election_feedback_form.html" with no_election_feedback_form=form %} 6 | 7 | {% endblock content %} 8 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_people_list.html: -------------------------------------------------------------------------------- 1 |
      2 | {% for person_post in people %} 3 | {% include "elections/includes/_person_card.html" with person_post=person_post %} 4 | {% endfor %} 5 |
    6 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/expected/headings.txt: -------------------------------------------------------------------------------- 1 |

    # H1
    2 | ## H2
    3 | ### H3
    4 | #### H4
    5 | ##### H5
    6 | ###### H6

    7 |

    Alternatively, for H1 and H2, an underline-ish style:

    8 |

    Alt-H1
    9 | ======

    10 |

    Alt-H2
    11 | ------

    12 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_ams_breadcrumbs.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | {% extends "elections/includes/_elections_breadcrumbs.html" %} 4 | 5 | {% block extra_crumbs %} 6 |
  • 7 | {% trans "The Additional Member System" %} 8 |
  • 9 | {% endblock extra_crumbs %} -------------------------------------------------------------------------------- /wcivf/apps/people/templatetags/person_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | from ..models import PersonPost 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.simple_tag 9 | def person_post_info(person, post): 10 | return PersonPost.objects.get(person=person, post=post) 11 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_post_breadcrumbs.html: -------------------------------------------------------------------------------- 1 | {% extends "elections/includes/_election_breadcrumbs.html" %} 2 | {% load i18n %} 3 | {% block extra_crumbs %} 4 | {{ block.super }} 5 |
  • 6 | {{ postelection.friendly_name }} 7 |
  • 8 | {% endblock extra_crumbs %} 9 | -------------------------------------------------------------------------------- /wcivf/apps/parties/sitemaps.py: -------------------------------------------------------------------------------- 1 | from django.contrib.sitemaps import Sitemap 2 | 3 | from .models import Party 4 | 5 | 6 | class PartySitemap(Sitemap): 7 | changefreq = "daily" 8 | priority = 0.5 9 | protocol = "https" 10 | 11 | def items(self): 12 | return Party.objects.all() 13 | -------------------------------------------------------------------------------- /wcivf/apps/peoplecvs/README.md: -------------------------------------------------------------------------------- 1 | Gets candidates' CVs from the [Democracy Club CVs](http://cv.democracyclub.org.uk/) site. 2 | 3 | (This app is called `peoplecvs`, rather than `cvs`, to avoid Sublime hiding it because it thinks it's a CVS-related file!) 4 | 5 | To load all CVs, run `python manage.py import_cvs` 6 | -------------------------------------------------------------------------------- /wcivf/apps/elections/tests/test_tests.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.test import TestCase 4 | from elections.models import Election 5 | 6 | 7 | class TestTests(TestCase): 8 | def test_election(self): 9 | e = Election(election_date=datetime.datetime.today(), current=True) 10 | e.save() 11 | -------------------------------------------------------------------------------- /wcivf/apps/people/sitemaps.py: -------------------------------------------------------------------------------- 1 | from django.contrib.sitemaps import Sitemap 2 | 3 | from .models import Person 4 | 5 | 6 | class PersonSitemap(Sitemap): 7 | changefreq = "daily" 8 | priority = 0.9 9 | protocol = "https" 10 | 11 | def items(self): 12 | return Person.objects.all().order_by("pk") 13 | -------------------------------------------------------------------------------- /deploy/start_application.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xeE 3 | 4 | # enabling will allow the services to start if the instance reboots 5 | systemctl enable wcivf_db_replication.service 6 | systemctl enable wcivf_gunicorn.service 7 | 8 | systemctl start wcivf_db_replication.service 9 | systemctl start wcivf_gunicorn.service 10 | -------------------------------------------------------------------------------- /deploy/validate_application.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xeuo pipefail 3 | 4 | # if either check returns a non-zero exit code, return exit code 1 to 5 | # ensure CodeDeploy recognises the validation failed 6 | systemctl is-active --quiet wcivf_db_replication || exit 1 7 | systemctl is-active --quiet wcivf_gunicorn.service || exit 1 8 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_postcode_meta_title.html: -------------------------------------------------------------------------------- 1 | {% if postelections.count == 0 %}Election candidates in {{ postcode }}{% else %} 2 | {% regroup postelections by election.election_date as header_elections_by_date %}{{ header_elections_by_date.0.list.0.election.name }} candidates in {{ postcode }}{% endif %} 3 | -------------------------------------------------------------------------------- /wcivf/apps/profiles/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Profile(models.Model): 5 | person_post = models.OneToOneField( 6 | "people.PersonPost", primary_key=True, on_delete=models.CASCADE 7 | ) 8 | text = models.TextField(blank=True) 9 | url = models.CharField(blank=True, max_length=800) 10 | -------------------------------------------------------------------------------- /wcivf/apps/parties/templates/parties/party_list.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 |
    4 | {% for party in parties %} 5 | 10 | {% endfor %} 11 |
    12 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | import dotenv 6 | 7 | if __name__ == "__main__": 8 | dotenv.read_dotenv() 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wcivf.settings") 10 | 11 | from django.core.management import execute_from_command_line 12 | 13 | execute_from_command_line(sys.argv) 14 | -------------------------------------------------------------------------------- /wcivf/apps/elections/management/commands/set_election_weight.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from elections.models import Election 3 | 4 | 5 | class Command(BaseCommand): 6 | def handle(self, **options): 7 | Election.objects.filter(slug="parl.2017-06-08").update( 8 | election_weight=100 9 | ) 10 | -------------------------------------------------------------------------------- /wcivf/apps/people/templates/people/includes/_person_hustings_card.html: -------------------------------------------------------------------------------- 1 | 2 | {% with hustings=object.featured_candidacy.post_election.husting_set.displayable %} 3 | {% if hustings and object.current_or_future_candidacies %} 4 | {% include "hustings/includes/_person.html" with hustings=hustings person=object %} 5 | {% endif %} 6 | {% endwith %} 7 | -------------------------------------------------------------------------------- /wcivf/apps/parties/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import re_path 2 | 3 | from .views import PartiesView, PartyView 4 | 5 | urlpatterns = [ 6 | re_path(r"^$", PartiesView.as_view(), name="parties_view"), 7 | re_path( 8 | r"^(?P[^/]+)/(?P.*)$", 9 | PartyView.as_view(), 10 | name="party_view", 11 | ), 12 | ] 13 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/expected/html.txt: -------------------------------------------------------------------------------- 1 |

    <dl>
    2 | <dt>Definition list</dt>
    3 | <dd>Is something people use sometimes.</dd>

    4 |

    <dt>Markdown in HTML</dt>
    5 | <dd>Does not work very well. Use HTML <em>tags</em>.</dd>
    6 | </dl>

    7 | -------------------------------------------------------------------------------- /deploy/files/systemd/cloudwatch.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Starts the cloudwatch agent with our config file 3 | Before=wcivf_db_replication.service 4 | 5 | [Service] 6 | User=root 7 | ExecStart=/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/root/.cloudwatch.json -s 8 | 9 | [Install] 10 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_election_breadcrumbs.html: -------------------------------------------------------------------------------- 1 | {% extends "elections/includes/_elections_breadcrumbs.html" %} 2 | {% load i18n %} 3 | {% block extra_crumbs %} 4 |
  • 5 | 6 | {{ election.nice_election_name }} 7 | 8 |
  • 9 | {% endblock extra_crumbs %} 10 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/context_processors.py: -------------------------------------------------------------------------------- 1 | from .forms import FeedbackForm, NoElectionFeedbackForm 2 | 3 | 4 | def feedback_form(request): 5 | return { 6 | "feedback_form": FeedbackForm(initial={"source_url": request.path}), 7 | "no_election_feedback_form": NoElectionFeedbackForm( 8 | initial={"source_url": request.path} 9 | ), 10 | } 11 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::NWM::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | 6 | The Mayor of Newham is a directly elected mayor responsible for the executive function of the council. 7 | 8 | [Find more about the mayor](https://www.newham.gov.uk/council/mayor-newham-1) 9 | 10 | 11 | {% endfilter %} 12 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::north-of-tyne::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | The North of Tyne Combined Authority was a mayoral combined authority which consisted of the local authorities of 6 | Newcastle upon Tyne, North Tyneside, and Northumberland, all in North East England. 7 | 8 | {% endfilter %} 9 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/input/code.txt: -------------------------------------------------------------------------------- 1 | Inline `code` has `back-ticks around` it. 2 | 3 | ```javascript 4 | var s = "JavaScript syntax highlighting"; 5 | alert(s); 6 | ``` 7 | 8 | ```python 9 | s = "Python syntax highlighting" 10 | print s 11 | ``` 12 | 13 | ``` 14 | No language indicated, so no syntax highlighting. 15 | But let's throw in a tag. 16 | ``` 17 | -------------------------------------------------------------------------------- /wcivf/apps/core/management/commands/setup_django_site.py: -------------------------------------------------------------------------------- 1 | """ 2 | Hack because Django makes it hard to use data migrations to do this :/ 3 | """ 4 | 5 | from django.contrib.sites.models import Site 6 | from django.core.management.base import BaseCommand 7 | 8 | 9 | class Command(BaseCommand): 10 | def handle(self, **options): 11 | Site.objects.all().update(domain="whocanivotefor.co.uk") 12 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/input/blockquote.txt: -------------------------------------------------------------------------------- 1 | > Blockquotes are very handy in email to emulate reply text. 2 | > This line is part of the same quote. 3 | 4 | Quote break. 5 | 6 | > This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. 7 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/input/images.txt: -------------------------------------------------------------------------------- 1 | Here's our logo (hover to see the title text): 2 | 3 | Inline-style: 4 | ![alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 1") 5 | 6 | Reference-style: 7 | ![alt text][logo] 8 | 9 | [logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 2" 10 | -------------------------------------------------------------------------------- /wcivf/apps/core/migrations/0004_delete_loggedpostcode.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.6 on 2023-11-17 11:52 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("core", "0003_auto_20210225_1103"), 9 | ] 10 | 11 | operations = [ 12 | migrations.DeleteModel( 13 | name="LoggedPostcode", 14 | ), 15 | ] 16 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/post_type/MTW.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | Metropolitan district councils are responsible for services like: 6 | 7 | * schools 8 | * social services 9 | * waste collection 10 | * roads 11 | 12 | [Understand how your council works](https://www.gov.uk/understand-how-your-council-works) 13 | {% endfilter %} 14 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0008_remove_person_party.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-05-01 15:51 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("people", "0007_personpost_election")] 10 | 11 | operations = [migrations.RemoveField(model_name="person", name="party")] 12 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::HCK::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | 6 | The directly elected Mayor provides leadership and direction to the borough. They have overall responsibility for council policy and delivery of services. 7 | 8 | [Find more about the mayor's role](https://hackney.gov.uk/mayor) 9 | 10 | 11 | {% endfilter %} 12 | -------------------------------------------------------------------------------- /wcivf/apps/api/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions 2 | 3 | 4 | class ReadOnly(permissions.BasePermission): 5 | def has_object_permission(self, request, view, obj): 6 | # Read permissions are allowed to any request, 7 | # so we'll always allow GET, HEAD or OPTIONS requests. 8 | if request.method in permissions.SAFE_METHODS: 9 | return True 10 | return False 11 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0014_remove_post_elections.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.6 on 2017-03-04 13:54 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0013_auto_20170304_1354")] 10 | 11 | operations = [migrations.RemoveField(model_name="post", name="elections")] 12 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_postcode_meta_description.html: -------------------------------------------------------------------------------- 1 | {% regroup postelections by election.election_date as header_elections_by_date %} 2 | {% if postelections.count == 0 %}No upcoming elections in {{ postcode }}{% else %}On {{ header_elections_by_date.0.grouper }}, registered voters in {{ postcode }} can vote in the {{ header_elections_by_date.0.list.0.election.name }}. Find out more about the candidates.{% endif %} -------------------------------------------------------------------------------- /wcivf/apps/hustings/api/serializers.py: -------------------------------------------------------------------------------- 1 | from hustings.models import Husting 2 | from rest_framework import serializers 3 | 4 | 5 | class HustingSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Husting 8 | fields = ( 9 | "title", 10 | "url", 11 | "starts", 12 | "ends", 13 | "location", 14 | "postevent_url", 15 | ) 16 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/expected/images.txt: -------------------------------------------------------------------------------- 1 |

    Here's our logo (hover to see the title text):

    2 |

    Inline-style:
    3 | ![alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 1")

    4 |

    Reference-style:
    5 | ![alt text][logo]

    6 |

    [logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 2"

    7 | -------------------------------------------------------------------------------- /wcivf/apps/pledges/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class CandidatePledge(models.Model): 5 | person = models.ForeignKey( 6 | "people.person", related_name="pledges", on_delete=models.CASCADE 7 | ) 8 | ballot_paper = models.ForeignKey( 9 | "elections.PostElection", on_delete=models.CASCADE 10 | ) 11 | question = models.TextField(blank=True) 12 | answer = models.TextField(blank=True) 13 | -------------------------------------------------------------------------------- /wcivf/apps/peoplecvs/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from people.models import Person 3 | 4 | 5 | class CV(models.Model): 6 | """ 7 | A candidate's CV. 8 | """ 9 | 10 | person = models.OneToOneField(Person, on_delete=models.CASCADE) 11 | url = models.URLField(blank=True, null=True) 12 | thumb_url = models.URLField(blank=True, null=True) 13 | last_modified = models.DateTimeField(blank=True, null=True) 14 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/expected/blockquote.txt: -------------------------------------------------------------------------------- 1 |

    > Blockquotes are very handy in email to emulate reply text.
    2 | > This line is part of the same quote.

    3 |

    Quote break.

    4 |

    > This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can put Markdown into a blockquote.

    5 | -------------------------------------------------------------------------------- /wcivf/settings/lambda.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .base import * # noqa 4 | 5 | DATABASES["default"] = { # noqa 6 | "ENGINE": "django.db.backends.postgresql", 7 | "NAME": os.environ.get("RDS_DB_NAME"), 8 | "USER": "wcivf", 9 | "PASSWORD": os.environ.get("RDS_DB_PASSWORD"), 10 | "HOST": os.environ.get("RDS_HOST"), 11 | "PORT": os.environ.get("RDS_DB_PORT", "5432"), 12 | } 13 | EE_BASE = "https://elections.democracyclub.org.uk" 14 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::LCE::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | 6 | The City Mayor has responsibility for all council decisions during their four-year term 7 | and for selecting up to nine councillors as a supporting cabinet. 8 | 9 | [Find more about the mayor](https://www.leicester.gov.uk/your-council/city-mayor-peter-soulsby/) 10 | 11 | 12 | {% endfilter %} 13 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/expected/code.txt: -------------------------------------------------------------------------------- 1 |

    Inline `code` has `back-ticks around` it.

    2 |

    ```javascript
    3 | var s = "JavaScript syntax highlighting";
    4 | alert(s);
    5 | ```

    6 |

    ```python
    7 | s = "Python syntax highlighting"
    8 | print s
    9 | ```

    10 |

    ```
    11 | No language indicated, so no syntax highlighting.
    12 | But let's throw in a <b>tag</b>.
    13 | ```

    14 | -------------------------------------------------------------------------------- /wcivf/apps/core/migrations/0003_auto_20210225_1103.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.18 on 2021-02-25 11:03 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("core", "0002_auto_20170526_1448"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="loggedpostcode", 14 | options={"get_latest_by": "modified"}, 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/migrations/0003_auto_20210225_1103.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.18 on 2021-02-25 11:03 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("feedback", "0002_feedback_token"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="feedback", 14 | options={"get_latest_by": "modified"}, 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_ld_candidate.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::BDF::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | 6 | The Mayor of Bedford is a directly elected mayor. The Mayor appoints Councillors to his 7 | Executive and is responsible for the majority of Council services. 8 | 9 | 10 | [Find more about the mayor](https://www.bedford.gov.uk/your-council/councillors-and-senior-staff/mayor-bedford-borough) 11 | 12 | 13 | {% endfilter %} 14 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/migrations/0002_auto_20170601_2155.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-06-01 21:55 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("hustings", "0001_initial")] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="husting", options={"ordering": ["-starts"]} 14 | ) 15 | ] 16 | -------------------------------------------------------------------------------- /wcivf/apps/news_mentions/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django_extensions.db.models import TimeStampedModel 3 | 4 | 5 | class BallotNewsArticle(TimeStampedModel): 6 | ballot = models.ForeignKey( 7 | "elections.PostElection", on_delete=models.CASCADE 8 | ) 9 | url = models.URLField(max_length=800) 10 | title = models.CharField(max_length=800) 11 | summary = models.TextField() 12 | publisher = models.CharField(max_length=500, blank=True) 13 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0031_person_sort_name.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.7 on 2019-11-28 14:17 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("people", "0030_insta-youtube")] 8 | 9 | operations = [ 10 | migrations.AddField( 11 | model_name="person", 12 | name="sort_name", 13 | field=models.CharField(max_length=255, null=True), 14 | ) 15 | ] 16 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/post_type/LGE.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | 6 | The council is responsible for services like: 7 | 8 | * planning 9 | * waste and recycling services 10 | * leisure and community services 11 | * building control and local economic 12 | * cultural development 13 | 14 | [Understand how your council works](https://www.nidirect.gov.uk/articles/local-councils) 15 | 16 | {% endfilter %} 17 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0034_update_ordering.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.18 on 2021-04-22 14:00 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("elections", "0033_auto_20210210_1422"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="election", 14 | options={"ordering": ["election_date", "election_weight"]}, 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /wcivf/apps/leaflets/api/serializers.py: -------------------------------------------------------------------------------- 1 | from leaflets.models import Leaflet 2 | from rest_framework import serializers 3 | 4 | 5 | class LeafletSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Leaflet 8 | fields = ("leaflet_id", "thumb_url", "leaflet_url") 9 | 10 | leaflet_url = serializers.SerializerMethodField() 11 | 12 | def get_leaflet_url(self, obj: Leaflet): 13 | return f"https://electionleaflets.org/leaflets/{obj.leaflet_id}/" 14 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0032_person_death_date.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.7 on 2019-11-28 17:34 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("people", "0031_person_sort_name")] 8 | 9 | operations = [ 10 | migrations.AddField( 11 | model_name="person", 12 | name="death_date", 13 | field=models.CharField(max_length=255, null=True), 14 | ) 15 | ] 16 | -------------------------------------------------------------------------------- /wcivf/apps/parties/migrations/0018_alter_partydescription_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.6 on 2023-04-20 13:41 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("parties", "0017_partyemblem"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="partydescription", 14 | options={"ordering": ["date_description_approved"]}, 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0036_auto_20191213_1028.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.7 on 2019-12-13 10:28 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("people", "0035_personpost_votes_cast")] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="personpost", 12 | name="votes_cast", 13 | field=models.PositiveIntegerField(null=True), 14 | ) 15 | ] 16 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/post_type/SPE.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | MSPs take part in examining and passing Bills (proposed laws). They represent you in the Scottish Parliament and 6 | your local area and they examine the work of the Scottish Government. 7 | 8 | [Read more about your MSPs](https://www.parliament.scot/msps/current-and-previous-msps/search-results?postcode={{ postcode }}) 9 | 10 | 11 | {% endfilter %} 12 | -------------------------------------------------------------------------------- /wcivf/apps/news_mentions/migrations/0003_auto_20210225_1103.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.18 on 2021-02-25 11:03 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("news_mentions", "0002_ballotnewsarticle_publisher"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="ballotnewsarticle", 14 | options={"get_latest_by": "modified"}, 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0035_personpost_votes_cast.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.7 on 2019-12-13 10:21 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("people", "0034_auto_20191213_0942")] 8 | 9 | operations = [ 10 | migrations.AddField( 11 | model_name="personpost", 12 | name="votes_cast", 13 | field=models.PositiveSmallIntegerField(null=True), 14 | ) 15 | ] 16 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/post_type/DIW.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | District councils cover a smaller area than county councils. They’re usually responsible for services like: 6 | 7 | * rubbish collection 8 | * recycling 9 | * Council Tax collections 10 | * housing 11 | * planning applications 12 | 13 | 14 | [Understand how your council works](https://www.gov.uk/understand-how-your-council-works) 15 | {% endfilter %} 16 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0028_add_territory_to_posts.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("elections", "0027_unique_on_ballot_id")] 8 | 9 | operations = [ 10 | migrations.AddField( 11 | model_name="post", 12 | name="territory", 13 | field=models.CharField(blank=True, max_length=3, serialize=True), 14 | ) 15 | ] 16 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/migrations/0004_feedback_sources.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.18 on 2021-04-20 17:02 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("feedback", "0003_auto_20210225_1103"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="feedback", 14 | name="sources", 15 | field=models.TextField(blank=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/parties/migrations/0009_manifesto_easy_read_url.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.7 on 2019-11-20 14:51 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("parties", "0008_auto_20180422_1248")] 8 | 9 | operations = [ 10 | migrations.AddField( 11 | model_name="manifesto", 12 | name="easy_read_url", 13 | field=models.URLField(blank=True, max_length=800), 14 | ) 15 | ] 16 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0015_auto_20170304_1354.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.6 on 2017-03-04 13:54 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0014_remove_post_elections")] 10 | 11 | operations = [ 12 | migrations.RenameField( 13 | model_name="post", old_name="elections2", new_name="elections" 14 | ) 15 | ] 16 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0030_post_organization_type.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.7 on 2020-02-10 14:25 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("elections", "0029_wikipedia_on_ballot")] 8 | 9 | operations = [ 10 | migrations.AddField( 11 | model_name="post", 12 | name="organization_type", 13 | field=models.CharField(blank=True, max_length=100), 14 | ) 15 | ] 16 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/migrations/0004_auto_20170602_1031.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-06-02 10:31 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("hustings", "0003_husting_video_url")] 10 | 11 | operations = [ 12 | migrations.RenameField( 13 | model_name="husting", old_name="video_url", new_name="postevent_url" 14 | ) 15 | ] 16 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0045_person_delisted.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.6 on 2023-09-11 16:26 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("people", "0044_person_mastodon_username"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="person", 14 | name="delisted", 15 | field=models.BooleanField(default=False), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/importers.py: -------------------------------------------------------------------------------- 1 | from core.mixins import ReadFromFileMixin, ReadFromUrlMixin 2 | 3 | 4 | class HustingImporter(ReadFromUrlMixin, ReadFromFileMixin): 5 | def __init__(self, url=None, file_path=None): 6 | self.url = url 7 | self.file_path = file_path 8 | 9 | @property 10 | def rows(self): 11 | if self.file_path: 12 | yield from self.read_from_file(path_to_file=self.file_path) 13 | else: 14 | yield from self.read_from_url(url=self.url) 15 | -------------------------------------------------------------------------------- /wcivf/apps/news_mentions/migrations/0002_ballotnewsarticle_publisher.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.7 on 2019-11-20 17:25 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("news_mentions", "0001_initial")] 8 | 9 | operations = [ 10 | migrations.AddField( 11 | model_name="ballotnewsarticle", 12 | name="publisher", 13 | field=models.CharField(blank=True, max_length=500), 14 | ) 15 | ] 16 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0016_auto_20170518_1028.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-18 10:28 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("people", "0015_auto_20170518_1015")] 10 | 11 | operations = [ 12 | migrations.RemoveField(model_name="person", name="elections"), 13 | migrations.RemoveField(model_name="person", name="posts"), 14 | ] 15 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/post_type/CED.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | County councils are responsible for services across the whole of a county, like: 6 | 7 | * education 8 | * transport 9 | * planning 10 | * fire and public safety 11 | * social care 12 | * libraries 13 | * waste management 14 | * trading standards 15 | 16 | [Understand how your council works](https://www.gov.uk/understand-how-your-council-works) 17 | {% endfilter %} 18 | -------------------------------------------------------------------------------- /wcivf/apps/core/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.utils.translation import gettext_lazy as _ 3 | from localflavor.gb.forms import GBPostcodeField 4 | 5 | 6 | class PostcodeLookupForm(forms.Form): 7 | postcode = GBPostcodeField(label=_("Enter your postcode")) 8 | 9 | def __init__(self, autofocus=False, *args, **kwargs): 10 | super(PostcodeLookupForm, self).__init__(*args, **kwargs) 11 | if autofocus: 12 | self.fields["postcode"].widget.attrs["autofocus"] = "autofocus" 13 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/migrations/0006_feedback_flagged_as_spam.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-04-26 09:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("feedback", "0005_feedback_vote"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="feedback", 14 | name="flagged_as_spam", 15 | field=models.BooleanField(default=False), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/templates/hustings/includes/_person.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 |
    3 |
    4 |

    {% trans "Election events" %}

    5 |

    {% blocktrans with person=person.name %}You can meet candidates and question them at events (often known as 'hustings'). Here are some events where {{ person }} may be appearing:{% endblocktrans %}

    6 | {% include "hustings/includes/_list.html" with hustings=hustings %} 7 |
    8 |
    9 | -------------------------------------------------------------------------------- /wcivf/apps/leaflets/migrations/0002_auto_20210603_0922.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.20 on 2021-06-03 09:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("leaflets", "0001_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="leaflet", 14 | name="thumb_url", 15 | field=models.URLField(blank=True, max_length=800, null=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0040_person_blog_url.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.7 on 2022-01-26 18:21 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("people", "0039_auto_20210928_1303"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="person", 14 | name="blog_url", 15 | field=models.CharField(blank=True, max_length=800, null=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/people/templates/people/includes/intros/_votes_cast.html: -------------------------------------------------------------------------------- 1 | {% load humanize %} 2 | {% load i18n %} 3 | 4 | {% if candidacy.elected %} 5 | {% blocktrans trimmed with num_votes=candidacy.votes_cast|intcomma %} 6 | They were elected with {{ num_votes }} votes. 7 | {% endblocktrans %} 8 | {% else %} 9 | {% blocktrans trimmed with num_votes=candidacy.votes_cast|intcomma %} 10 | They received {{ num_votes }} votes. 11 | {% endblocktrans %} 12 | {% endif %} 13 | -------------------------------------------------------------------------------- /deploy/files/systemd/db_replication.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Manages DB replication subscription 3 | Before=${PROJECT_NAME}_gunicorn.service 4 | After=postgresql.service 5 | 6 | [Service] 7 | User=${PROJECT_NAME} 8 | Type=oneshot 9 | RemainAfterExit=true 10 | ExecStart=${PROJECT_ROOT}/setup_db_replication.sh 11 | ExecStop=${PROJECT_ROOT}/remove_db_replication.sh 12 | 13 | StandardOutput=file:/var/log/db_replication/logs.log 14 | StandardError=file:/var/log/db_replication/logs.log 15 | 16 | [Install] 17 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0043_personpost_rank.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.6 on 2023-05-05 09:01 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("people", "0042_personpost_previous_party_affiliations"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="personpost", 14 | name="rank", 15 | field=models.PositiveIntegerField(null=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/rtts/djhtml 3 | rev: 3.0.6 4 | hooks: 5 | - id: djhtml 6 | - repo: https://github.com/charliermarsh/ruff-pre-commit 7 | # Ruff version. 8 | rev: 'v0.4.6' 9 | hooks: 10 | - id: ruff 11 | args: [--fix, --exit-non-zero-on-fix, --extend-exclude, TCH] 12 | - id: ruff-format 13 | - repo: local 14 | hooks: 15 | - id: pip-check 16 | name: pip-check 17 | entry: pip check 18 | files: ^requirements\S*\.txt$ 19 | language: system 20 | -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 6 | ## [Unreleased] 7 | 8 | ## 2023-11-17 9 | 10 | ### Added 11 | 12 | - A Change log 13 | 14 | ### Changed 15 | 16 | - Upgraded DC logging client to version 1.0.1 17 | 18 | ### Removed 19 | 20 | - LoggedPostcode model and related code. 21 | Postcodes will no longer be logged in the database, and will now only be logged into AWS Firehose 22 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import re_path 2 | 3 | from .views import YourArea 4 | 5 | urlpatterns = [ 6 | re_path( 7 | r"^(?P[^/]+)/(?P[^/]+)/$", 8 | YourArea.as_view(), 9 | name="your_area_view", 10 | ), 11 | re_path( 12 | r"^(?P[^/]+)/$", 13 | YourArea.as_view(), 14 | name="your_area_view", 15 | ), 16 | re_path( 17 | r"^$", 18 | YourArea.as_view(), 19 | name="your_area_view", 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0038_default_winner_count.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-05-23 15:19 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("elections", "0037_dummypostelection"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="postelection", 14 | name="winner_count", 15 | field=models.IntegerField(blank=True, default=1), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/parishes/migrations/0002_alter_parishcouncilelection_is_contested.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-09-28 13:03 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("parishes", "0001_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="parishcouncilelection", 14 | name="is_contested", 15 | field=models.BooleanField(null=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0009_auto_20170303_1823.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.6 on 2017-03-03 18:23 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("people", "0008_remove_person_party")] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="personpost", 14 | options={"ordering": ("-election__election_date",)}, 15 | ) 16 | ] 17 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0044_person_mastodon_username.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-07-11 13:51 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("people", "0043_personpost_rank"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="person", 14 | name="mastodon_username", 15 | field=models.CharField(blank=True, max_length=800, null=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/parties/migrations/0020_party_alternative_name.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.6 on 2023-06-14 15:25 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("parties", "0019_alter_partydescription_options_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="party", 14 | name="alternative_name", 15 | field=models.CharField(max_length=765, null=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0024_personpost_elected.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-06-06 15:51 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("people", "0023_auto_20170605_1559")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="personpost", 14 | name="elected", 15 | field=models.NullBooleanField(), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::WECA::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | The Mayor of the West of England is the directly elected mayor who leads the West of England Combined Authority. The body, a combined authority, is responsible for the strategic administration of the West of England, including planning, transport and skills. 6 | 7 | [Find more about the mayor and combined authority](https://www.westofengland-ca.gov.uk/what-we-do/) 8 | 9 | 10 | {% endfilter %} 11 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/migrations/0002_feedback_token.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-04-29 19:42 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("feedback", "0001_initial")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="feedback", 14 | name="token", 15 | field=models.CharField(blank=True, max_length=100), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0005_person_wikipedia_bio.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-04-14 12:28 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("people", "0004_auto_20160414_0927")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="person", 14 | name="wikipedia_bio", 15 | field=models.TextField(null=True), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/parishes/management/commands/import_parish_council_elections.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from parishes.importers import ParishCouncilElectionImporter 3 | 4 | 5 | class Command(BaseCommand): 6 | URL = "https://docs.google.com/spreadsheets/d/e/2PACX-1vTbfedaTDoTT3UcJOcOJHojISaSb06yzjFNvTiIUxHd7oWsN0wqFNqHXoklcTeK2G7aoUUH9NHvWy0q/pub?gid=756401424&single=true&output=csv" 7 | 8 | def handle(self, **options): 9 | importer = ParishCouncilElectionImporter(url=self.URL) 10 | importer.import_objects() 11 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0009_election_for_post_role.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-04-30 19:41 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0008_auto_20160405_1412")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="election", 14 | name="for_post_role", 15 | field=models.TextField(blank=True), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0040_postelection_requires_voter_id.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.6 on 2023-03-22 11:03 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("elections", "0039_remove_historic_metadata"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="postelection", 14 | name="requires_voter_id", 15 | field=models.CharField(blank=True, max_length=50, null=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/migrations/0003_husting_video_url.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-06-02 10:28 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("hustings", "0002_auto_20170601_2155")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="husting", 14 | name="video_url", 15 | field=models.URLField(blank=True, null=True), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/migrations/0005_auto_20210326_1111.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.18 on 2021-03-26 11:11 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("hustings", "0004_auto_20170602_1031"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="husting", 14 | name="postevent_url", 15 | field=models.URLField(blank=True, default=""), 16 | preserve_default=False, 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0021_person_twfy_id.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-06-02 15:17 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("people", "0020_person_favourite_biscuit")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="person", 14 | name="twfy_id", 15 | field=models.IntegerField(blank=True, null=True), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /deploy/files/systemd/gunicorn.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=WCIVF gunicorn daemon 3 | After=network.target 4 | [Service] 5 | User=wcivf 6 | Group=wcivf 7 | WorkingDirectory=/var/www/wcivf/code/ 8 | ExecStart=/bin/bash -c 'PATH=/var/www/wcivf/code/.venv/bin/:$PATH exec /var/www/wcivf/code/.venv/bin/gunicorn \ 9 | --access-logfile - \ 10 | --workers 3 \ 11 | --bind 0.0.0.0:8000 \ 12 | --worker-class=gevent \ 13 | wcivf.wsgi:application' 14 | ExecReload=/bin/kill -s HUP $MAINPID 15 | ExecStop=/bin/kill -s TERM $MAINPID 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::MAS::mayor.html: -------------------------------------------------------------------------------- 1 | 2 | {% load markdown_filter %} 3 | 4 | {# fmt:off #} 5 | {% filter markdown %} 6 | It is the job of the Mayor to represent the council and its residents, make key decisions on policies, services and how the council spends its money. 7 | 8 | The Mayor is supported by a Cabinet of councillors who help him or her develop and implement these policies. 9 | 10 | [Find more about the Mayor](https://www.mansfield.gov.uk/council-councillors-democracy/meet-mayor-1) 11 | 12 | 13 | {% endfilter %} 14 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0016_postelection_contested.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.6 on 2017-03-22 16:59 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0015_auto_20170304_1354")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="postelection", 14 | name="contested", 15 | field=models.BooleanField(default=True), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0018_postelection_locked.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.10 on 2018-03-04 13:18 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0017_parl-2017-06-08-weight")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="postelection", 14 | name="locked", 15 | field=models.BooleanField(default=False), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/parties/migrations/0002_auto_20160412_1739.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-04-12 17:39 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("parties", "0001_initial")] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="party", 14 | name="emblem", 15 | field=models.ImageField(null=True, upload_to="parties/emblems"), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0011_person_statement_to_voters.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.6 on 2017-04-28 10:58 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("people", "0010_auto_20170306_1206")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="person", 14 | name="statement_to_voters", 15 | field=models.TextField(null=True), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_elections_breadcrumbs.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | 17 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0005_election_election_type.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-03-26 10:39 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0004_auto_20160326_1038")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="election", 14 | name="election_type", 15 | field=models.CharField(blank=True, max_length=100), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0006_votingsystem_uses_lists.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-04-04 18:06 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0005_election_election_type")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="votingsystem", 14 | name="uses_lists", 15 | field=models.BooleanField(default=False), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0013_auto_20170515_2047.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-15 20:47 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("people", "0012_auto_20170515_1641")] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="person", 14 | name="ynr_id", 15 | field=models.CharField(db_index=True, max_length=255, unique=True), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0020_person_favourite_biscuit.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-06-02 14:04 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("people", "0019_associatedcompany")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="person", 14 | name="favourite_biscuit", 15 | field=models.CharField(max_length=800, null=True), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0034_auto_20191213_0942.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.7 on 2019-12-13 09:42 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("people", "0033_facebookadvert")] 8 | 9 | operations = [ 10 | migrations.AlterModelOptions( 11 | name="facebookadvert", 12 | options={ 13 | "get_latest_by": "ad_json__ad_delivery_start_time", 14 | "ordering": ("-ad_json__ad_delivery_start_time",), 15 | }, 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0047_person_statement_to_voters_last_updated.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.10 on 2024-03-20 14:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("people", "0046_personpost_deselected_personpost_deselected_source"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="person", 14 | name="statement_to_voters_last_updated", 15 | field=models.DateTimeField(null=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0033_auto_20210210_1422.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-02-10 14:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("elections", "0032_auto_20210210_1155"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="post", 14 | name="ynr_id", 15 | field=models.CharField( 16 | max_length=100, primary_key=True, serialize=False 17 | ), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /wcivf/apps/ppc_2024/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic import RedirectView 3 | 4 | urlpatterns = [ 5 | path( 6 | "", 7 | RedirectView.as_view( 8 | url="https://whocanivotefor.co.uk/elections/parl.2024-07-04/uk-parliament-elections/" 9 | ), 10 | name="home", 11 | ), 12 | path( 13 | "details/", 14 | RedirectView.as_view( 15 | url="https://whocanivotefor.co.uk/elections/parl.2024-07-04/uk-parliament-elections/" 16 | ), 17 | name="details", 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/tests/test_admistration_object.py: -------------------------------------------------------------------------------- 1 | # from administrations.helpers import BASE_DATA_PATH, Administration 2 | # 3 | # 4 | # def test_all_files_parse(): 5 | # for filename in BASE_DATA_PATH.glob("*.json"): 6 | # admin_class = Administration(filename.stem) 7 | # has_template = False 8 | # try: 9 | # admin_class.responsibilities_template() 10 | # has_template = True 11 | # except: 12 | # raise 13 | # if not has_template: 14 | # print(admin_class.admin_id, admin_class.role_name()) 15 | -------------------------------------------------------------------------------- /wcivf/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for wcivf project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | import dotenv 13 | from django.core.wsgi import get_wsgi_application 14 | 15 | dotenv.read_dotenv( 16 | os.path.join(os.path.dirname(os.path.dirname(__file__)), ".env") 17 | ) 18 | 19 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wcivf.settings") 20 | 21 | application = get_wsgi_application() 22 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0020_postelection_ballot_paper_id.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.12 on 2018-04-17 16:12 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0019_election_metadata")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="postelection", 14 | name="ballot_paper_id", 15 | field=models.CharField(blank=True, max_length=800), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0027_person_party_ppc_page_url.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.12 on 2018-05-03 12:20 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("people", "0026_auto_20180417_1944")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="person", 14 | name="party_ppc_page_url", 15 | field=models.CharField(blank=True, max_length=800, null=True), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0021_postelection_winner_count.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.12 on 2018-05-02 09:46 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0020_postelection_ballot_paper_id")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="postelection", 14 | name="winner_count", 15 | field=models.IntegerField(blank=True, null=True), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0024_default_for_has_by_elections.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.20 on 2019-03-21 19:22 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0023_election_any_non_by_elections")] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="election", 14 | name="any_non_by_elections", 15 | field=models.BooleanField(default=False), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import re_path 2 | 3 | from .views import ( 4 | FeedbackFormView, 5 | NoElectionFeedbackFormView, 6 | RecordJsonFeedback, 7 | ) 8 | 9 | urlpatterns = [ 10 | re_path( 11 | r"^submit_initial", 12 | RecordJsonFeedback.as_view(), 13 | name="json_feedback_view", 14 | ), 15 | re_path(r"^$", FeedbackFormView.as_view(), name="feedback_form_view"), 16 | re_path( 17 | r"^no_election$", 18 | NoElectionFeedbackFormView.as_view(), 19 | name="no_election_feedback_form_view", 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/post_type/GLA_C.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% load markdown_filter %} 4 | 5 | {# fmt:off #} 6 | {% filter markdown %} 7 | The Assembly holds the Mayor and Mayoral advisers to account by publicly examining policies and programmes through committee meetings, plenary sessions, site visits and investigations. 8 | 9 | Eleven Assembly Members represent the whole capital and 14 are elected by constituencies 10 | 11 | [Read more about what the London Assembly](https://www.london.gov.uk/who-we-are/what-london-assembly-does/about-london-assembly) 12 | 13 | {% endfilter %} 14 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0017_parl-2017-06-08-weight.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-06-08 18:21 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | def change_weight(apps, schema_editor): 9 | Election = apps.get_model("elections", "Election") 10 | Election.objects.filter(slug="parl.2017-06-08").update(election_weight=100) 11 | 12 | 13 | class Migration(migrations.Migration): 14 | dependencies = [("elections", "0016_postelection_contested")] 15 | 16 | operations = [migrations.RunPython(change_weight)] 17 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0027_unique_on_ballot_id.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.20 on 2019-05-04 11:05 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0026_postelection_voting_system")] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="postelection", 14 | name="ballot_paper_id", 15 | field=models.CharField(blank=True, max_length=800, unique=True), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0032_auto_20210210_1155.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-02-10 11:55 2 | 3 | from django.db import migrations 4 | 5 | 6 | def forwards(apps, schema_editor): 7 | Post = apps.get_model("elections", "Post") 8 | count, _ = Post.objects.filter(ynr_id="").delete() 9 | 10 | 11 | class Migration(migrations.Migration): 12 | dependencies = [ 13 | ("elections", "0031_post_division_type"), 14 | ] 15 | 16 | operations = [ 17 | migrations.RunPython( 18 | code=forwards, reverse_code=migrations.RunPython.noop 19 | ) 20 | ] 21 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0010_auto_20170306_1206.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.6 on 2017-03-06 12:06 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [ 10 | ("elections", "0015_auto_20170304_1354"), 11 | ("people", "0009_auto_20170303_1823"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterUniqueTogether( 16 | name="personpost", 17 | unique_together={("person", "post", "election")}, 18 | ) 19 | ] 20 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/post_type/UTW.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | Unitary authorities are responsible for services like: 6 | 7 | * education 8 | * transport 9 | * planning 10 | * fire and public safety 11 | * social care 12 | * libraries 13 | * waste management 14 | * trading standards 15 | * rubbish collection 16 | * recycling 17 | * Council Tax collections 18 | * housing 19 | * planning applications 20 | 21 | 22 | [Understand how your council works](https://www.gov.uk/understand-how-your-council-works) 23 | {% endfilter %} 24 | -------------------------------------------------------------------------------- /wcivf/apps/peoplecvs/migrations/0003_delete_cvs.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.20 on 2021-04-19 13:32 2 | 3 | from django.apps.registry import apps 4 | from django.db import migrations 5 | 6 | 7 | def delete_cvs(app, schema_editor): 8 | CV = apps.get_model("peoplecvs", "CV") 9 | CV.objects.all().delete() 10 | 11 | 12 | class Migration(migrations.Migration): 13 | dependencies = [ 14 | ("peoplecvs", "0002_auto_20170522_1324"), 15 | ] 16 | 17 | operations = [ 18 | migrations.RunPython( 19 | code=delete_cvs, reverse_code=migrations.RunPython.noop 20 | ) 21 | ] 22 | -------------------------------------------------------------------------------- /wcivf/apps/core/middleware.py: -------------------------------------------------------------------------------- 1 | class UTMTrackerMiddleware(object): 2 | def __init__(self, get_response): 3 | self.get_response = get_response 4 | 5 | def __call__(self, request): 6 | self.process_request(request) 7 | return self.get_response(request) 8 | 9 | def process_request(self, request): 10 | def _get_value_from_req(key): 11 | return (key, request.GET.get(key, None)) 12 | 13 | keys = ("utm_source", "utm_medium", "utm_campaign") 14 | utm_data = {k: v for k, v in map(_get_value_from_req, keys) if v} 15 | request.session["utm_data"] = utm_data 16 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/migrations/0011_alter_noelectionfeedback_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.20 on 2025-08-28 12:10 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("feedback", "0010_noelectionfeedback"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="noelectionfeedback", 14 | options={ 15 | "verbose_name": "No Election Page Feedback", 16 | "verbose_name_plural": "No Election Page Feedback", 17 | }, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /wcivf/apps/core/utils.py: -------------------------------------------------------------------------------- 1 | from django.db.models import Transform 2 | 3 | 4 | class LastWord(Transform): 5 | """ 6 | Split a field on space and get the last element 7 | """ 8 | 9 | function = "LastWord" 10 | template = """ 11 | (regexp_split_to_array(%(field)s, ' '))[ 12 | array_upper(regexp_split_to_array(%(field)s, ' '), 1) 13 | ] 14 | """ 15 | 16 | def __init__(self, column, output_field=None): 17 | super(LastWord, self).__init__(column, output_field=output_field) 18 | 19 | def as_postgresql(self, compiler, connection): 20 | return self.as_sql(compiler, connection) 21 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0019_election_metadata.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.10 on 2018-04-10 12:33 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.postgres.fields.jsonb 6 | from django.db import migrations 7 | 8 | 9 | class Migration(migrations.Migration): 10 | dependencies = [("elections", "0018_postelection_locked")] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="election", 15 | name="metadata", 16 | field=django.contrib.postgres.fields.jsonb.JSONField(null=True), 17 | ) 18 | ] 19 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0023_election_any_non_by_elections.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.20 on 2019-03-21 18:45 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0022_auto_20181031_1603")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="election", 14 | name="any_non_by_elections", 15 | field=models.BooleanField(default=False), 16 | preserve_default=False, 17 | ) 18 | ] 19 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/post_type/PCC.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | Police and crime commissioners are elected in areas of England and Wales to make sure that local police meet the needs of the community. 6 | 7 | They are responsible for: 8 | 9 | * how your area is policed 10 | * the police budget 11 | * the amount of Council Tax charged for the police 12 | * the information you get about what the local police are doing 13 | * appointing and dismissing the chief constable (the most senior police officer for the area) 14 | 15 | {% endfilter %} 16 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/migrations/0005_feedback_vote.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.7 on 2021-11-23 15:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("feedback", "0004_feedback_sources"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="feedback", 14 | name="vote", 15 | field=models.CharField( 16 | blank=True, 17 | choices=[("YES", "Yes"), ("NO", "No")], 18 | max_length=100, 19 | ), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /wcivf/apps/core/migrations/0002_auto_20170526_1448.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-26 14:48 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | def add_site(apps, schema_editor): 9 | Site = apps.get_model("sites", "Site") 10 | 11 | site = Site( 12 | pk=1, name="whocanivotefor.co.uk", domain="whocanivotefor.co.uk" 13 | ) 14 | site.save() 15 | 16 | 17 | class Migration(migrations.Migration): 18 | dependencies = [("core", "0001_initial"), ("sites", "0001_initial")] 19 | 20 | operations = [migrations.RunPython(add_site)] 21 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0012_auto_20170515_1641.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-15 16:41 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("people", "0011_person_statement_to_voters")] 10 | 11 | operations = [ 12 | migrations.RemoveField(model_name="person", name="photo"), 13 | migrations.AddField( 14 | model_name="person", 15 | name="photo_url", 16 | field=models.URLField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::SLF::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | 6 | The City Mayor leads the council and has overall responsibility for the delivery of all council services. 7 | 8 | They develop budgets to meet the needs of the city's residents, through the city plan, make 9 | sure the work of the council is conducted in accordance with the constitution and the law and that all decisions are 10 | open, transparent and understandable. 11 | 12 | [Find more about the mayor's role](https://www.salford.gov.uk/cmrole) 13 | 14 | 15 | {% endfilter %} 16 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::london::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | The Mayor sets an overall vision for London. He has a duty to create plans and policies for the capital covering: 6 | 7 | * Arts & Culture 8 | * Business & Economy 9 | * Environment 10 | * Fire 11 | * Health 12 | * Housing and Land 13 | * Planning 14 | * Policing & Crime 15 | * Regeneration 16 | * Sport 17 | * Transport 18 | * Young People 19 | 20 | [Read more about what the Mayor of London does](https://www.london.gov.uk/who-we-are/what-mayor-does) 21 | 22 | {% endfilter %} 23 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/input/tables.txt: -------------------------------------------------------------------------------- 1 | Colons can be used to align columns. 2 | 3 | | Tables | Are | Cool | 4 | | ------------- |:-------------:| -----:| 5 | | col 3 is | right-aligned | $1600 | 6 | | col 2 is | centered | $12 | 7 | | zebra stripes | are neat | $1 | 8 | 9 | There must be at least 3 dashes separating each header cell. 10 | The outer pipes (|) are optional, and you don't need to make the 11 | raw Markdown line up prettily. You can also use inline Markdown. 12 | 13 | Markdown | Less | Pretty 14 | --- | --- | --- 15 | *Still* | `renders` | **nicely** 16 | 1 | 2 | 3 17 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::WAT::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | 6 | The Mayor of Watford is a directly elected and the council's principal 7 | public spokesperson, representing the council and the borough on local, 8 | national and international platforms. The Mayor is responsible for: 9 | 10 | 11 | * the overall political direction of the council 12 | * the scheme of delegation for Executive functions 13 | * Chairing the Cabinet 14 | 15 | 16 | [Find more about the mayor](https://www.watford.gov.uk/electedmayor) 17 | 18 | 19 | {% endfilter %} 20 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0011_auto_20170303_1823.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.6 on 2017-03-03 18:23 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0010_auto_20160502_1542")] 10 | 11 | operations = [ 12 | migrations.RemoveField(model_name="post", name="election"), 13 | migrations.AddField( 14 | model_name="post", 15 | name="elections", 16 | field=models.ManyToManyField(to="elections.Election"), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /wcivf/apps/parties/migrations/0010_localparty_is_local.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.18 on 2021-03-29 14:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("parties", "0009_manifesto_easy_read_url"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="localparty", 14 | name="is_local", 15 | field=models.BooleanField( 16 | default=True, 17 | help_text="Used to identify if obj is related to a local election", 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /wcivf/apps/parties/migrations/0011_replace_emblem_with_emblem_url.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.7 on 2021-11-05 15:53 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("parties", "0010_localparty_is_local"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="party", 14 | name="emblem", 15 | ), 16 | migrations.AddField( 17 | model_name="party", 18 | name="emblem_url", 19 | field=models.URLField(blank=True, null=True), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0007_auto_20160405_0857.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-04-05 08:57 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0006_votingsystem_uses_lists")] 10 | 11 | operations = [ 12 | migrations.RemoveField(model_name="votingsystem", name="uses_lists"), 13 | migrations.AddField( 14 | model_name="election", 15 | name="uses_lists", 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0037_dummypostelection.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.10 on 2022-03-09 15:25 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("elections", "0036_add_timestamps"), 9 | ] 10 | 11 | operations = [ 12 | migrations.CreateModel( 13 | name="DummyPostElection", 14 | fields=[], 15 | options={ 16 | "proxy": True, 17 | "indexes": [], 18 | "constraints": [], 19 | }, 20 | bases=("elections.postelection",), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_postcode_search_form.html: -------------------------------------------------------------------------------- 1 | {% load dc_forms %} 2 | {% load i18n %} 3 | 4 |
    5 |
    6 |

    {% trans "All elections where you live" %}

    7 |

    {% trans "Enter your postcode to get information about elections, your candidates and where to vote." %}

    8 |
    9 | {% csrf_token %} 10 | {{ postcode_form|dc_form }} 11 | 12 |
    13 |
    14 |
    15 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/migrations/0009_remove_postcode_amend_location.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.10 on 2022-03-17 14:51 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("hustings", "0008_url_max_length"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="husting", 14 | name="postcode", 15 | ), 16 | migrations.AlterField( 17 | model_name="husting", 18 | name="location", 19 | field=models.CharField(blank=True, default="", max_length=250), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /wcivf/apps/peoplecvs/migrations/0002_auto_20170522_1324.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-22 13:24 3 | from __future__ import unicode_literals 4 | 5 | import django.db.models.deletion 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | dependencies = [("peoplecvs", "0001_initial")] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="cv", 15 | name="person", 16 | field=models.OneToOneField( 17 | on_delete=django.db.models.deletion.CASCADE, to="people.Person" 18 | ), 19 | ) 20 | ] 21 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/migrations/0007_auto_20210419_1539.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.18 on 2021-04-19 15:39 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("hustings", "0006_increase_url_max_length"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="husting", 14 | name="postevent_url", 15 | field=models.URLField(blank=True), 16 | ), 17 | migrations.AlterField( 18 | model_name="husting", 19 | name="url", 20 | field=models.URLField(), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0035_auto_20210928_1303.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-09-28 13:03 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("elections", "0034_update_ordering"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="election", 14 | name="metadata", 15 | field=models.JSONField(null=True), 16 | ), 17 | migrations.AlterField( 18 | model_name="postelection", 19 | name="metadata", 20 | field=models.JSONField(null=True), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_postcode_breadcrumbs.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 16 | -------------------------------------------------------------------------------- /wcivf/apps/referendums/management/commands/import_referendums.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from referendums.importers import ReferendumImporter 3 | 4 | 5 | class Command(BaseCommand): 6 | URL = "https://docs.google.com/spreadsheets/d/e/2PACX-1vQHIsqVdfcZnCNgrwgtcS_ihMQbFn2S5T1ncUGKUDz2B3ONC9cUjOcdFWRtBmUxDN-f3PNEfW7bucmp/pub?gid=0&single=true&output=csv" 7 | 8 | def handle(self, **options): 9 | self.stdout.write("IN import_referendums") 10 | importer = ReferendumImporter(url=self.URL) 11 | self.stdout.write("INITIALISED THE IMPORTER") 12 | 13 | importer.import_objects() 14 | self.stdout.write("COMPLETE") 15 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/migrations/0007_alter_feedback_token.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-18 16:38 2 | 3 | import feedback.models 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("feedback", "0006_feedback_flagged_as_spam"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="feedback", 15 | name="token", 16 | field=models.CharField( 17 | blank=True, 18 | default=feedback.models.generate_feedback_token, 19 | max_length=100, 20 | ), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/settings/ci.py: -------------------------------------------------------------------------------- 1 | STORAGES = { 2 | "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"}, 3 | "staticfiles": {"BACKEND": "pipeline.storage.PipelineStorage"}, 4 | } 5 | 6 | CACHES = { 7 | "default": { 8 | "BACKEND": "django.core.cache.backends.dummy.DummyCache", 9 | } 10 | } 11 | 12 | DATABASES = { 13 | "default": { 14 | "ENGINE": "django.db.backends.postgresql", 15 | "NAME": "wcivf", 16 | "USER": "wcivf", 17 | "PASSWORD": "", 18 | "HOST": "", 19 | "PORT": "", 20 | } 21 | } 22 | 23 | SECRET_KEY = "just_for_ci" 24 | YNR_API_KEY = None 25 | EE_BASE = "https://elections.democracyclub.org.uk" 26 | -------------------------------------------------------------------------------- /wcivf/apps/ppc_2024/filters.py: -------------------------------------------------------------------------------- 1 | import django_filters 2 | from dc_utils.filter_widgets import DSLinkWidget 3 | from ppc_2024.models import PPCPerson 4 | 5 | 6 | class PPCFilter(django_filters.FilterSet): 7 | region = django_filters.AllValuesFilter( 8 | widget=DSLinkWidget, 9 | field_name="region_name", 10 | lookup_expr="contains", 11 | label="Region", 12 | ) 13 | party_name = django_filters.AllValuesFilter( 14 | widget=DSLinkWidget, 15 | field_name="party__party_name", 16 | lookup_expr="exact", 17 | label="Party Name", 18 | ) 19 | 20 | class Meta: 21 | model = PPCPerson 22 | fields = ["region"] 23 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0002_auto_20160323_1533.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-03-23 15:33 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0001_initial")] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="election", options={"ordering": ["election_date"]} 14 | ), 15 | migrations.AddField( 16 | model_name="election", 17 | name="ballot_colour", 18 | field=models.CharField(blank=True, max_length=100), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/migrations/0008_url_max_length.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.20 on 2021-04-26 16:12 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("hustings", "0007_auto_20210419_1539"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="husting", 14 | name="postevent_url", 15 | field=models.URLField(blank=True, max_length=800), 16 | ), 17 | migrations.AlterField( 18 | model_name="husting", 19 | name="url", 20 | field=models.URLField(max_length=800), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0015_auto_20170518_1015.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-18 10:15 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("people", "0014_auto_20170518_1009")] 10 | 11 | operations = [ 12 | migrations.RemoveField(model_name="person", name="id"), 13 | migrations.AlterField( 14 | model_name="person", 15 | name="ynr_id", 16 | field=models.CharField( 17 | max_length=255, primary_key=True, serialize=False 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::DNC::mayor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% load markdown_filter %} 4 | 5 | {# fmt:off #} 6 | {% filter markdown %} 7 | 8 | Doncaster Council is led by an Elected Mayor and Cabinet. The Mayor: 9 | 10 | * is the council's political leader and is elected by Doncaster constituents to serve a four-year term of office. 11 | * has executive powers and is responsible for the effective implementation of council policy and delivering services. 12 | * has nine cabinet members to advise, support and lead on their own specific portfolio. 13 | 14 | 15 | [Find more about the Mayor](https://www.doncaster.gov.uk/mayor/mayor-home) 16 | 17 | 18 | {% endfilter %} 19 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::NTY::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | 6 | 7 | The role of the Elected Mayor is to : 8 | 9 | * be the Authority’s principal public spokesperson 10 | * give overall political direction to the Authority 11 | * appoint the Cabinet and Deputy Mayor 12 | * decide on the scheme of delegation for Cabinet functions 13 | * chair meetings of the Cabinet 14 | * represent the Authority on such external executive bodies as the Elected Mayor decides 15 | 16 | [Find more about the mayor's role](https://my.northtyneside.gov.uk/category/613/about-elected-mayor) 17 | 18 | 19 | {% endfilter %} 20 | -------------------------------------------------------------------------------- /wcivf/templates/opensearch.xml: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | {{ SITE_TITLE }} 4 | {{ SITE_TITLE }} 5 | UTF-8 6 | {{ CANONICAL_URL }}{% static 'images/logo_icon.svg' %} 7 | 8 | {{ CANONICAL_URL }}/elections 9 | 10 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/expected/tables.txt: -------------------------------------------------------------------------------- 1 |

    Colons can be used to align columns.

    2 |

    | Tables | Are | Cool |
    3 | | ------------- |:-------------:| -----:|
    4 | | col 3 is | right-aligned | $1600 |
    5 | | col 2 is | centered | $12 |
    6 | | zebra stripes | are neat | $1 |

    7 |

    There must be at least 3 dashes separating each header cell.
    8 | The outer pipes (|) are optional, and you don't need to make the
    9 | raw Markdown line up prettily. You can also use inline Markdown.

    10 |

    Markdown | Less | Pretty
    11 | --- | --- | ---
    12 | Still | `renders` | nicely
    13 | 1 | 2 | 3

    14 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0029_wikipedia_on_ballot.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.7 on 2019-12-10 21:51 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("elections", "0028_add_territory_to_posts")] 8 | 9 | operations = [ 10 | migrations.AddField( 11 | model_name="postelection", 12 | name="wikipedia_bio", 13 | field=models.TextField(null=True), 14 | ), 15 | migrations.AddField( 16 | model_name="postelection", 17 | name="wikipedia_url", 18 | field=models.CharField(blank=True, max_length=800, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/migrations/0006_increase_url_max_length.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.18 on 2021-04-12 09:01 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("hustings", "0005_auto_20210326_1111"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="husting", 14 | name="postevent_url", 15 | field=models.URLField(blank=True, max_length=800), 16 | ), 17 | migrations.AlterField( 18 | model_name="husting", 19 | name="url", 20 | field=models.URLField(max_length=800), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/apps/leaflets/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from people.models import Person 3 | 4 | 5 | class LeafletQuerySet(models.QuerySet): 6 | def latest_four(self): 7 | return self.order_by("date_uploaded_to_electionleaflets")[:4] 8 | 9 | 10 | class Leaflet(models.Model): 11 | person = models.ForeignKey(Person, on_delete=models.CASCADE) 12 | leaflet_id = models.IntegerField() 13 | thumb_url = models.URLField(null=True, blank=True, max_length=800) 14 | date_uploaded_to_electionleaflets = models.DateTimeField( 15 | null=True, blank=True 16 | ) 17 | created_at = models.DateTimeField(auto_now_add=True) 18 | 19 | objects = LeafletQuerySet.as_manager() 20 | -------------------------------------------------------------------------------- /wcivf/apps/people/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import DummyPersonView, EmailPersonView, PersonView 4 | 5 | urlpatterns = [ 6 | path( 7 | "dummy-profile//", 8 | DummyPersonView.as_view(), 9 | name="dummy-profile", 10 | ), 11 | path( 12 | "/email/", 13 | EmailPersonView.as_view(), 14 | name="email_person_view", 15 | ), 16 | path( 17 | "/", 18 | PersonView.as_view(), 19 | name="person_view", 20 | ), 21 | path( 22 | "/", 23 | PersonView.as_view(), 24 | name="person_view", 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::CPCA::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | 6 | The role of the Mayor of Cambridgeshire and Peterborough is to deliver economic prosperity across the region, to 7 | make Cambridgeshire and Peterborough a leading place in the world to live, learn, 8 | work and do business. 9 | 10 | The Mayor oversees a £20 million annual budget devolved from government, and also has devolved powers to spend up to 11 | £800 million on local housing, infrastructure and jobs. 12 | 13 | [Find more about the mayor's role](https://cambridgeshirepeterborough-ca.gov.uk/about-us/mayor/) 14 | 15 | 16 | {% endfilter %} 17 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0017_auto_20170518_1158.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-18 11:58 3 | from __future__ import unicode_literals 4 | 5 | import django.db.models.deletion 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | dependencies = [("people", "0016_auto_20170518_1028")] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="personpost", 15 | name="election", 16 | field=models.ForeignKey( 17 | on_delete=django.db.models.deletion.DO_NOTHING, 18 | to="elections.Election", 19 | ), 20 | ) 21 | ] 22 | -------------------------------------------------------------------------------- /wcivf/apps/api/urls.py: -------------------------------------------------------------------------------- 1 | from api import views 2 | from django.urls import include, path 3 | from rest_framework import routers 4 | 5 | router = routers.DefaultRouter() 6 | 7 | router.register( 8 | r"candidates_for_postcode", 9 | views.CandidatesAndElectionsForPostcodeViewSet, 10 | basename="candidates-for-postcode", 11 | ) 12 | router.register( 13 | r"candidates_for_ballots", 14 | views.CandidatesAndElectionsForBallots, 15 | basename="candidates-for-ballots", 16 | ) 17 | 18 | 19 | urlpatterns = [ 20 | path(r"", include(router.urls)), 21 | path( 22 | "last-updated-timestamps/", 23 | views.LastUpdatedView.as_view(), 24 | name="last-updated-timestamps", 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /wcivf/apps/core/management/commands/truncate_replicated_tables.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.db import connection 3 | from elections.models import VotingSystem 4 | 5 | 6 | class Command(BaseCommand): 7 | def handle(self, **options): 8 | with connection.cursor() as cursor: 9 | cursor.execute( 10 | """ 11 | BEGIN; 12 | TRUNCATE "auth_permission", "auth_group_permissions", "auth_user_user_permissions", "django_admin_log", "django_content_type", "django_site", "django_migrations" RESTART IDENTITY; 13 | COMMIT; 14 | """ 15 | ) 16 | VotingSystem.objects.all().delete() 17 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_election_list.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | {% regroup elections by election_date as elections_by_type %} 4 | {% for election_group in elections_by_type %} 5 |

    6 | {{ election_group.grouper|date:"j F Y" }} 7 |

    8 | 9 |
    10 | {% for election in election_group.list %} 11 | 16 | {% endfor %} 17 |
    18 | {% endfor %} 19 | -------------------------------------------------------------------------------- /wcivf/apps/parties/migrations/0003_auto_20160422_1148.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-04-22 11:48 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("parties", "0002_auto_20160412_1739")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="party", 14 | name="description", 15 | field=models.TextField(blank=True), 16 | ), 17 | migrations.AddField( 18 | model_name="party", 19 | name="wikipedia_url", 20 | field=models.URLField(blank=True), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0026_auto_20180417_1944.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.12 on 2018-04-17 19:44 3 | from __future__ import unicode_literals 4 | 5 | import django.db.models.deletion 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | dependencies = [("people", "0025_personpost_post_election")] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="personpost", 15 | name="post_election", 16 | field=models.ForeignKey( 17 | on_delete=django.db.models.deletion.CASCADE, 18 | to="elections.PostElection", 19 | ), 20 | ) 21 | ] 22 | -------------------------------------------------------------------------------- /wcivf/apps/parties/migrations/0023_localparty_bluesky_localparty_instagram.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.18 on 2025-03-06 14:38 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("parties", "0022_nationalparty"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="localparty", 14 | name="bluesky", 15 | field=models.URLField(blank=True, max_length=100), 16 | ), 17 | migrations.AddField( 18 | model_name="localparty", 19 | name="instagram", 20 | field=models.URLField(blank=True, max_length=100), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/apps/people/templates/people/includes/intros/_independent_mayor_or_pcc.html: -------------------------------------------------------------------------------- 1 | {% extends "people/includes/intros/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block intro_line %} 5 | {% if verb == "is" %} 6 | {% blocktrans trimmed %} 7 | {{ person_name }} is an independent candidate for 8 | {{ election_name }}. 9 | {% endblocktrans %} 10 | {% else %} 11 | {% blocktrans trimmed %} 12 | {{ person_name }} was an independent candidate for 13 | {{ election_name }}. 14 | {% endblocktrans %} 15 | {% endif %} 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0029_person_last_updated.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.15 on 2018-10-12 10:32 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | from django.utils import timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | dependencies = [("people", "0028_person_pk_to_int")] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="person", options={"get_latest_by": "last_updated"} 15 | ), 16 | migrations.AddField( 17 | model_name="person", 18 | name="last_updated", 19 | field=models.DateTimeField(default=timezone.now), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0050_alter_personredirect_options_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.20 on 2025-10-13 15:47 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("people", "0049_personredirect"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="personredirect", 14 | options={"get_latest_by": "ynr_created"}, 15 | ), 16 | migrations.AddField( 17 | model_name="personredirect", 18 | name="ynr_created", 19 | field=models.DateTimeField(default="2010-01-01"), 20 | preserve_default=False, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0051_alter_personredirect_old_person_id_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.2.7 on 2025-10-14 20:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("people", "0050_alter_personredirect_options_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="personredirect", 14 | name="old_person_id", 15 | field=models.IntegerField(db_index=True), 16 | ), 17 | migrations.AlterUniqueTogether( 18 | name="personredirect", 19 | unique_together={("old_person_id", "new_person_id")}, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/org_id/parl-hoc.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | Members of Parliament (MPs) represent your interests and concerns in the House of Commons. 6 | 7 | MPs consider and can propose new laws as well as raising issues that matter to you in the House. This includes asking government 8 | ministers questions about current issues including those which affect local constituents. 9 | 10 | MPs split their time between working in Parliament itself, working in the constituency that elected them and working 11 | for their political party. 12 | 13 | [Find out more about MPs](https://www.parliament.uk/about/mps-and-lords/members/mps/) 14 | {% endfilter %} 15 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_post_meta_title.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% if postelection.cancelled %} 3 | {% blocktrans with election_name=object.election.name %}{{ election_name }}: This election has been cancelled.{% endblocktrans %} 4 | {% else %} 5 | {% if postelection.people|length %} 6 | {% blocktrans trimmed with election_name=object.election.name num_candidates=postelection.people|length post_label=object.post.label%}{{ election_name }}: The {{ num_candidates }} candidates in {{ post_label }}{% endblocktrans %} 7 | {% else %} 8 | {% blocktrans trimmed with election_name=object.election.name %}{{ election_name }}: No candidates known yet.{% endblocktrans %} 9 | {% endif %} 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /wcivf/apps/pledges/migrations/0002_auto_20180502_0946.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.12 on 2018-05-02 09:46 3 | from __future__ import unicode_literals 4 | 5 | import django.db.models.deletion 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | dependencies = [("pledges", "0001_initial")] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="candidatepledge", 15 | name="person", 16 | field=models.ForeignKey( 17 | on_delete=django.db.models.deletion.CASCADE, 18 | related_name="pledges", 19 | to="people.Person", 20 | ), 21 | ) 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0047_alter_postelection_metadata_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.20 on 2025-08-07 14:09 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("elections", "0046_postelection_by_election_reason"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="postelection", 14 | name="metadata", 15 | field=models.JSONField(blank=True, null=True), 16 | ), 17 | migrations.AlterField( 18 | model_name="postelection", 19 | name="wikipedia_bio", 20 | field=models.TextField(blank=True, null=True), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/apps/parties/migrations/0012_add_youtube_and_contact_url.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.10 on 2022-02-11 16:28 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("parties", "0011_replace_emblem_with_emblem_url"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="localparty", 14 | name="contact_page_url", 15 | field=models.URLField(blank=True, max_length=800), 16 | ), 17 | migrations.AddField( 18 | model_name="localparty", 19 | name="youtube_profile_url", 20 | field=models.URLField(blank=True, max_length=800), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0046_personpost_deselected_personpost_deselected_source.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.6 on 2024-01-17 16:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("people", "0045_person_delisted"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="personpost", 14 | name="deselected", 15 | field=models.BooleanField(default=False), 16 | ), 17 | migrations.AddField( 18 | model_name="personpost", 19 | name="deselected_source", 20 | field=models.CharField(blank=True, max_length=800, null=True), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0008_auto_20160405_1412.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-04-05 14:12 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0007_auto_20160405_0857")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="election", 14 | name="voter_age", 15 | field=models.CharField(blank=True, max_length=100), 16 | ), 17 | migrations.AddField( 18 | model_name="election", 19 | name="voter_citizenship", 20 | field=models.TextField(blank=True), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/apps/people/templates/people/includes/intros/_independent.html: -------------------------------------------------------------------------------- 1 | {% extends "people/includes/intros/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block intro_line %} 5 | {% if verb == "is" %} 6 | {% blocktrans trimmed %} 7 | {{ person_name }} is an independent candidate in {{ post_label }} 8 | in the {{ election_name }}. 9 | {% endblocktrans %} 10 | {% else %} 11 | {{ person_name }} was an independent candidate in {{ post_label }} 12 | in the {{ election_name }}. 13 | {% endif %} 14 | 15 | 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /wcivf/apps/parties/migrations/0008_auto_20180422_1248.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.12 on 2018-04-22 12:48 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("parties", "0007_auto_20180417_1733")] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="manifesto", 14 | name="pdf_url", 15 | field=models.URLField(blank=True, max_length=800), 16 | ), 17 | migrations.AlterField( 18 | model_name="manifesto", 19 | name="web_url", 20 | field=models.URLField(blank=True, max_length=800), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0042_personpost_previous_party_affiliations.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-04-12 12:23 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("parties", "0012_add_youtube_and_contact_url"), 9 | ("people", "0041_dummycandidacy_dummyperson"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="personpost", 15 | name="previous_party_affiliations", 16 | field=models.ManyToManyField( 17 | blank=True, 18 | related_name="affiliated_memberships", 19 | to="parties.Party", 20 | ), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wcivf/apps/people/tests/test_people_factories.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from people.models import Person 3 | from people.tests.factories import PersonFactory 4 | 5 | 6 | class TestFactories(TestCase): 7 | """ 8 | Meta tests to ensure that the factories are working 9 | """ 10 | 11 | def _test_save(self, model, factory, factory_kwargs={}): 12 | self.assertEqual(model.objects.all().count(), 0) 13 | created_model = factory.create(**factory_kwargs) 14 | self.assertEqual(model.objects.all().count(), 1) 15 | return created_model 16 | 17 | def test_person_factory(self): 18 | model = self._test_save(Person, PersonFactory, {"name": "Jane Smith"}) 19 | self.assertEqual(model.name, "Jane Smith") 20 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::LEW::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | The Mayor is directly elected and appoints a Cabinet to lead on decision-making across the Council. Their 6 | responsibilities include: 7 | 8 | * overall budget, strategy and policy responsibility 9 | * trade unions 10 | * service transformation 11 | * Bakerloo line extension 12 | * inward investment 13 | * London Councils, Local Government Association and key stakeholder relationships 14 | * Lewisham Strategic Partnership (LSP) 15 | * communications 16 | * Young Mayor's programme 17 | 18 | 19 | [Find more about the mayor's role](https://lewisham.gov.uk/mayorandcouncil/mayor) 20 | 21 | 22 | {% endfilter %} 23 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::SCR::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | The Mayor is elected to represent 1.4 million residents in the region. The Mayor acts to promote South Yorkshire as 6 | a place to live, work, visit and invest. 7 | 8 | The MCA has powers and funding which are used to help create jobs, grow the economy, up-skill the workforce, support 9 | the public transport network and develop new infrastructure and homes in South Yorkshire. Many decisions are made by 10 | the MCA and Mayor together, but the Mayor can make some decisions independently. 11 | 12 | [Find more about the mayor's role](https://www.southyorkshire-ca.gov.uk/about-the-mayor/role) 13 | 14 | 15 | {% endfilter %} 16 | -------------------------------------------------------------------------------- /wcivf/apps/referendums/migrations/0002_referendum_ballot.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-09-10 13:39 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("elections", "0034_update_ordering"), 10 | ("referendums", "0001_initial"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="referendum", 16 | name="ballot", 17 | field=models.OneToOneField( 18 | blank=True, 19 | null=True, 20 | on_delete=django.db.models.deletion.CASCADE, 21 | to="elections.PostElection", 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::EMCA::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | 6 | The Mayor has responsibility for transport, skills and adult education, housing and land, net-zero and economic 7 | development. 8 | 9 | As the Chair of the East Midlands Combined County Authority, the Mayor works with partners across the region and 10 | beyond to deliver transformational change for the East Midlands. 11 | 12 | The Mayor also acts as a champion for the region, influencing Government at a national level to boost growth and 13 | innovation, shape policy and access further funding. 14 | 15 | [Find more about the mayor's role](https://www.eastmidlands-cca.gov.uk/the-mayor/) 16 | 17 | 18 | {% endfilter %} 19 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0039_remove_historic_metadata.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations 2 | 3 | 4 | def remove_historical_metadata(apps, schema_editor): 5 | PostElection = apps.get_model("elections", "PostElection") 6 | for post_election in PostElection.objects.filter(metadata__isnull=False): 7 | metadata = post_election.metadata 8 | if metadata.get("cancelled", False): 9 | post_election.metadata = None 10 | post_election.save() 11 | 12 | 13 | class Migration(migrations.Migration): 14 | dependencies = [("elections", "0038_default_winner_count")] 15 | 16 | operations = [ 17 | migrations.RunPython( 18 | remove_historical_metadata, 19 | migrations.RunPython.noop, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0026_postelection_voting_system.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.20 on 2019-04-28 17:31 3 | from __future__ import unicode_literals 4 | 5 | import django.db.models.deletion 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | dependencies = [("elections", "0025_auto_20190412_1528")] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="postelection", 15 | name="voting_system", 16 | field=models.ForeignKey( 17 | blank=True, 18 | null=True, 19 | on_delete=django.db.models.deletion.CASCADE, 20 | to="elections.VotingSystem", 21 | ), 22 | ) 23 | ] 24 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0039_auto_20210928_1303.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-09-28 13:03 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("people", "0038_add_party_name_to_personposts"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="facebookadvert", 14 | name="ad_json", 15 | field=models.JSONField( 16 | help_text="The JSON returned from the Facebook Graph API for this advert" 17 | ), 18 | ), 19 | migrations.AlterField( 20 | model_name="personpost", 21 | name="elected", 22 | field=models.BooleanField(null=True), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/eu_results.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | {% if not card == 0 %} 4 | 5 |
    6 |
    7 |

    {% trans "European Parliament election results" %}

    8 |

    9 | {% trans "The results of the UK European Parliament elections are published on the European Parliament's election results website." %} 10 |

    11 | {% trans "View results" %} 12 |
    13 |
    14 | {% else %} 15 |

    {% trans "European Parliament election results" %}

    16 |

    {% trans "View results" %}

    17 | {% endif %} 18 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0006_personpost_party.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-04-22 10:03 3 | from __future__ import unicode_literals 4 | 5 | import django.db.models.deletion 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | dependencies = [ 11 | ("parties", "0002_auto_20160412_1739"), 12 | ("people", "0005_person_wikipedia_bio"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name="personpost", 18 | name="party", 19 | field=models.ForeignKey( 20 | null=True, 21 | on_delete=django.db.models.deletion.CASCADE, 22 | to="parties.Party", 23 | ), 24 | ) 25 | ] 26 | -------------------------------------------------------------------------------- /wcivf/apps/people/templates/people/dummy_statements/sofia-williamson.txt: -------------------------------------------------------------------------------- 1 | I decided to stand as a candidate in this year's election because I'm sick and tired of the mess in our local area. As a resident of Llantalbot for 45 years, I've seen that this is an issue that the council has completely failed to tackle. There is a long-standing litter issue on St.Alban's Road which backs on to Llantalbot park. Incredibly, despite the long-standing nature of the issue there is a distinct lack of bins on this road, and no warning signs of fines to deter those responsible. On top of this, just a few streets away on Ffordd Dderw, the situation with dog mess is unacceptable. We need tough fines for those people who do not clean up after their dogs, and more bins to make it easy for people to dispose of the mess. Llantalbot deserves better. -------------------------------------------------------------------------------- /wcivf/apps/core/db_routers.py: -------------------------------------------------------------------------------- 1 | from django_middleware_global_request import get_request 2 | 3 | 4 | class PrincipalRDSRouter: 5 | def db_for_read(self, model, **hints): 6 | request = get_request() 7 | if request and request.path.startswith("/admin"): 8 | # read from the replica in the admin 9 | # to prevent race conditions 10 | return "principal" 11 | 12 | return "default" 13 | 14 | def db_for_write(self, model, **hints): 15 | return "principal" 16 | 17 | def allow_relation(self, obj1, obj2, **hints): 18 | """ 19 | Allow any relation between objects in different databases. 20 | """ 21 | return True 22 | 23 | def allow_migrate(self, db, app_label, model_name=None, **hints): 24 | return True 25 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::CRY::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | The directly elected Mayor provides leadership and direction to the borough and the Council and has overall 6 | responsibility for council policy and the delivery of services. 7 | 8 | The elected Mayor has a mandate to represent the whole borough and will speak on its behalf locally, regionally, 9 | nationally and internationally. It will be for them to decide how this role will work in practice, within the 10 | boundaries set out in the Council’s Constitution. 11 | 12 | [Find more about the mayor's role](https://www.croydon.gov.uk/council-and-elections/mayors-croydon/elected-mayor-croydon/role-elected-mayor#lgd-guides__title) 13 | 14 | 15 | {% endfilter %} 16 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0007_personpost_election.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-05-01 14:37 3 | from __future__ import unicode_literals 4 | 5 | import django.db.models.deletion 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | dependencies = [ 11 | ("elections", "0009_election_for_post_role"), 12 | ("people", "0006_personpost_party"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name="personpost", 18 | name="election", 19 | field=models.ForeignKey( 20 | null=True, 21 | on_delete=django.db.models.deletion.CASCADE, 22 | to="elections.Election", 23 | ), 24 | ) 25 | ] 26 | -------------------------------------------------------------------------------- /wcivf/apps/elections/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Election, Post, PostElection, VotingSystem 4 | 5 | 6 | class ElectionAdmin(admin.ModelAdmin): 7 | list_filter = ("election_type", "voting_system") 8 | list_display = ("name", "slug", "election_date") 9 | search_fields = ("slug", "name") 10 | date_hierarchy = "election_date" 11 | 12 | 13 | class PostAdmin(admin.ModelAdmin): 14 | list_display = ("area_name", "role") 15 | list_filter = ("organization", "role") 16 | 17 | 18 | class PostElectionAdmin(admin.ModelAdmin): 19 | search_fields = ("ballot_paper_id",) 20 | 21 | 22 | admin.site.register(Election, ElectionAdmin) 23 | admin.site.register(Post, PostAdmin) 24 | admin.site.register(VotingSystem) 25 | admin.site.register(PostElection, PostElectionAdmin) 26 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/migrations/0009_alter_feedback_vote.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.20 on 2025-07-30 12:09 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("feedback", "0008_alter_feedback_found_useful_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="feedback", 14 | name="vote", 15 | field=models.CharField( 16 | blank=True, 17 | choices=[ 18 | ("MORE_LIKELY", "More likely"), 19 | ("LESS_LIKELY", "Less likely"), 20 | ("NO_DIFFERENCE", "No change"), 21 | ], 22 | max_length=100, 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/migrations/0011_husting_status.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-06-17 18:53 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("hustings", "0010_husting_created_husting_modified"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="husting", 14 | name="status", 15 | field=models.CharField( 16 | choices=[ 17 | ("suggested", "Suggested"), 18 | ("published", "Published"), 19 | ("rejected", "Rejected"), 20 | ("unpublished", "Unpublished"), 21 | ], 22 | default=False, 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /wcivf/apps/people/templates/people/includes/_person_about_card.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% if object.wikipedia_bio or object.favourite_biscuit %} 3 |

    4 | {% blocktrans trimmed with name=object.name %}About {{ name }}{% endblocktrans %} 5 |

    6 | {% if object.wikipedia_bio %} 7 |

    {% trans "Wikipedia" %}

    8 |

    {{ object.wikipedia_bio }}

    9 | {% trans "Read more on Wikipedia" %} 10 | {% endif %} 11 | {% if object.favourite_biscuit %} 12 | {% blocktrans trimmed with biscuit=object.favourite_biscuit%} 13 |

    Favourite Biscuit

    14 |

    {{ biscuit }}

    15 | {% endblocktrans %} 16 | {% endif %} 17 | 18 | {% endif %} 19 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0045_alter_post_territory.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.20 on 2025-07-21 12:33 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("elections", "0044_fix_territory"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="post", 14 | name="territory", 15 | field=models.CharField( 16 | choices=[ 17 | ("ENG", "England"), 18 | ("NIR", "Northern Ireland"), 19 | ("SCT", "Scotland"), 20 | ("WLS", "Wales"), 21 | ], 22 | max_length=3, 23 | verbose_name="Territory", 24 | ), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_previous_party_affiliations.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load static %} 3 | {% load humanize %} 4 | 5 |

    6 | {% for candidacy in candidacies %} 7 | {% blocktrans trimmed with person_name=person.name count counter=candidacy.previous_party_affiliations.count %} 8 | {{ person_name }} has declared their affiliation with the following party in the past 12 months: 9 | {% plural %} 10 | {{ person_name}} has declared their affiliation with the following parties in the past 12 months: 11 | {% endblocktrans %} 12 | {% for previous_party in candidacy.previous_party_affiliations.all %} 13 | {{ previous_party.party_name }}{% if forloop and not forloop.last %}, {% endif %} 14 | {% endfor %} 15 | {% endfor %} 16 |

    17 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::west-yorkshire::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | 6 | The West Yorkshire Combined Authority brings together the local authorities of Bradford, Calderdale, Kirklees, Leeds 7 | and Wakefield. It develops and delivers policies, programmes and services which directly 8 | benefit the people of West Yorkshire. 9 | 10 | The Mayor of West Yorkshire is a directly elected mayor responsible for the metropolitan county of West Yorkshire in 11 | England. The Mayor chairs and leads the West Yorkshire Combined Authority, and assumes the office and powers of the 12 | West Yorkshire Police and Crime Commissioner. 13 | 14 | 15 | [Find more about the mayor's role](https://www.westyorks-ca.gov.uk/about-us/) 16 | 17 | 18 | {% endfilter %} 19 | -------------------------------------------------------------------------------- /wcivf/apps/feedback/templates/feedback/admin/change_list.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/change_list.html" %} 2 | {% block object-tools %} 3 |
      4 | {% block object-tools-items %} 5 | {% if has_add_permission %} 6 | {{ block.super }} 7 | {% endif %} 8 | {% if module_name == "No Election Page Feedback" %} 9 |
    • Export All
    • 10 | {% else %} 11 |
    • Export All
    • 12 |
    • Export with comments
    • 13 | {% endif %} 14 | {% endblock %} 15 |
    16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /wcivf/apps/people/templates/people/includes/intros/_mayor_or_pcc.html: -------------------------------------------------------------------------------- 1 | {% extends "people/includes/intros/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block intro_line %} 5 | {% if verb == "is" %} 6 | {% blocktrans trimmed %} 7 | {{ person_name }} is the {{ party_name }} 8 | candidate for {{ election_name }}. 9 | {% endblocktrans %} 10 | {% else %} 11 | {% blocktrans trimmed %} 12 | {{ person_name }} was the {{ party_name }} 13 | candidate for {{ election_name }}. 14 | {% endblocktrans %} 15 | 16 | {% endif %} 17 | 18 | 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/admin.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib import admin 3 | 4 | from .models import Husting 5 | 6 | 7 | class HustingAdmin(admin.ModelAdmin): 8 | list_display = ["title", "status", "starts", "ends", "url"] 9 | list_filter = ("starts", "status") 10 | search_fields = ("post_election__ballot_paper_id",) 11 | autocomplete_fields = ["post_election"] 12 | date_hierarchy = "starts" 13 | 14 | def formfield_for_dbfield(self, db_field, request, **kwargs): 15 | if db_field.name in ["title", "url", "location", "postevent_url"]: 16 | kwargs["widget"] = forms.Textarea 17 | if db_field.name == "status": 18 | kwargs["widget"] = forms.RadioSelect 19 | return super().formfield_for_dbfield(db_field, request, **kwargs) 20 | 21 | 22 | admin.site.register(Husting, HustingAdmin) 23 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0002_auto_20160412_1739.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-04-12 17:39 3 | from __future__ import unicode_literals 4 | 5 | import django.db.models.deletion 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | dependencies = [ 11 | ("parties", "0002_auto_20160412_1739"), 12 | ("people", "0001_initial"), 13 | ] 14 | 15 | operations = [ 16 | migrations.RemoveField(model_name="person", name="party_name"), 17 | migrations.AddField( 18 | model_name="person", 19 | name="party", 20 | field=models.ForeignKey( 21 | null=True, 22 | on_delete=django.db.models.deletion.CASCADE, 23 | to="parties.Party", 24 | ), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /docs/sslcertificate.md: -------------------------------------------------------------------------------- 1 | ## SSL Certificate 2 | 3 | You will need to use the AWS Console to provision an SSL certificate to be used in the deployment. Follow the steps below to create the SSL certificate. 4 | 5 | - In the AWS console, go to Certificate Manager 6 | - Change the region to US East (N. Virginia) (us-east-1) 7 | - Choose "Request a Certificate" and select public certificate 8 | - Add the domain and optionally a wildcard for the sub domain e.g. ‘wcivf.club’ and ‘* wcivf.club’ 9 | - Choose DNS validation, add any tags, and confirm 10 | - On the review screen, open the domain and click the button “Create record in Route 53”. Repeat for any additional domains you added. This will create DNS records in route 53 to validate the certificate. 11 | - The ARN of the created certificate is then used when deploying to a new environment - this is outlined elsewhere in the documentation. 12 | -------------------------------------------------------------------------------- /wcivf/apps/elections/postal_votes.py: -------------------------------------------------------------------------------- 1 | import csv 2 | from datetime import datetime 3 | from pathlib import Path 4 | 5 | 6 | def get_postal_vote_dispatch_dates(council_id): 7 | with open( 8 | Path(__file__).parent / "data" / "20250501_postal_votes.csv" 9 | ) as csvfile: 10 | reader = csv.DictReader(csvfile) 11 | dispatch_dates = {row.pop("Reg"): row for row in list(reader)} 12 | 13 | if council_id not in dispatch_dates: 14 | return None 15 | 16 | try: 17 | row = dispatch_dates[council_id] 18 | return [ 19 | datetime.strptime(row["Date 1"], "%d/%m/%Y").date(), 20 | datetime.strptime(row["Date 2"], "%d/%m/%Y").date(), 21 | datetime.strptime(row["Date 3"], "%d/%m/%Y").date(), 22 | ] 23 | except ValueError: 24 | return None 25 | -------------------------------------------------------------------------------- /wcivf/apps/people/templates/people/includes/intros/_constituency.html: -------------------------------------------------------------------------------- 1 | {% extends "people/includes/intros/base.html" %} 2 | {% load i18n %} 3 | {% block intro_line %} 4 | {% if verb == "is" %} 5 | {% blocktrans trimmed %} 6 | {{ name }} is the {{ party_name }} candidate for {{ post_label }} constituency in the 7 | {{ election_name }}. 8 | {% endblocktrans %} 9 | {% else %} 10 | {% blocktrans trimmed %} 11 | {{ name }} was the {{ party_name }} candidate for {{ post_label }} constituency in the 12 | {{ election_name }}. 13 | {% endblocktrans %} 14 | {% endif %} 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0013_auto_20170304_1354.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.6 on 2017-03-04 13:54 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("elections", "0012_auto_20170304_1351")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="post", 14 | name="elections2", 15 | field=models.ManyToManyField( 16 | through="elections.PostElection", to="elections.Election" 17 | ), 18 | ), 19 | migrations.AlterField( 20 | model_name="post", 21 | name="elections", 22 | field=models.ManyToManyField( 23 | related_name="fake", to="elections.Election" 24 | ), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_contact_details_registration.html: -------------------------------------------------------------------------------- 1 | {% load postcode_tags %} 2 | {% load i18n %} 3 | 4 | {% if council and registration and council.address != registration.address and not postcode|ni_postcode %} 5 |

    {% trans "Electoral Registration" %}

    6 | {% include "elections/includes/_council_contact_details.html" with contact_details=registration with_address=True %} 7 |

    {% trans "Your Local Council" %}

    8 | {% include "elections/includes/_council_contact_details.html" with contact_details=council with_address=True %} 9 | {% elif council %} 10 | {% include "elections/includes/_council_contact_details.html" with contact_details=council with_address=True %} 11 | {% elif registration %} 12 | {% include "elections/includes/_council_contact_details.html" with contact_details=registration with_address=True %} 13 | {% endif %} 14 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/README.md: -------------------------------------------------------------------------------- 1 | A husting originally referred to a native Germanic governing assembly, the thing. 2 | 3 | By metonymy, the term may now refer to any event, such as debates or speeches, 4 | during an election campaign where one or more of the representative candidates are present. 5 | The term is used synonymously with stump in the United States. 6 | 7 | **Or so says [Wikipedia](https://en.wikipedia.org/wiki/Husting)** 8 | 9 | 10 | To load the hustings from a Google Sheet check the URLS in `import_hustings` are correct. 11 | 12 | Then run `python manage.py import_hustings`. 13 | 14 | You can also import hustings from a csv file by using the `--filename` flag and passing the 15 | path to the csv file. However this will delete all existing hustings and only create those 16 | from within the file, so be careful when using this - it is mainly intended to help with 17 | local development. 18 | -------------------------------------------------------------------------------- /wcivf/apps/people/templates/people/dummy_statements/rhuanedd-llewelyn.txt: -------------------------------------------------------------------------------- 1 | I am a qualified doctor and a volunteer with Llantalbot Good Neighbours. I am a founder and trustee of Llantalbot Pride. I chose to settle in Llantalbot because I loved the town for its warm feel. I felt part of community. I have believed in my town and proud to be in Llantalbot. 2 | 3 | One issue close to my heart is Llantalbot park. I love this park - it's where I used to take my children when they were young. But over recent years it's been plagued by vandalism. If you choose me to represent you, I'll make sure that the concerns about the park are heard. It needs a bit of love and care, including new fences, new play equipment, and the gates to be opened and locked in the mornings and evenings, just like many other parks have across the county. I woud also love to see more cycle paths to help our town get healthier and more eco-friendly. 4 | -------------------------------------------------------------------------------- /wcivf/apps/parties/tests/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from elections.tests.factories import PostElectionFactory 3 | from parties.models import LocalParty, NationalParty, Party 4 | 5 | 6 | class PartyFactory(factory.django.DjangoModelFactory): 7 | class Meta: 8 | model = Party 9 | django_get_or_create = ("party_id",) 10 | 11 | party_id = "PP01" 12 | party_name = "Test Party" 13 | 14 | 15 | class LocalPartyFactory(factory.django.DjangoModelFactory): 16 | parent = factory.SubFactory(PartyFactory) 17 | post_election = factory.SubFactory(PostElectionFactory) 18 | 19 | class Meta: 20 | model = LocalParty 21 | 22 | 23 | class NationalPartyFactory(factory.django.DjangoModelFactory): 24 | parent = factory.SubFactory(PartyFactory) 25 | post_election = factory.SubFactory(PostElectionFactory) 26 | 27 | class Meta: 28 | model = NationalParty 29 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/input/links.txt: -------------------------------------------------------------------------------- 1 | [I'm an inline-style link](https://www.google.com) 2 | 3 | [I'm an inline-style link with title](https://www.google.com "Google's Homepage") 4 | 5 | [I'm a reference-style link][Arbitrary case-insensitive reference text] 6 | 7 | [I'm a relative reference to a repository file](../blob/master/LICENSE) 8 | 9 | [You can use numbers for reference-style link definitions][1] 10 | 11 | Or leave it empty and use the [link text itself]. 12 | 13 | URLs and URLs in angle brackets will automatically get turned into links. 14 | http://www.example.com or and sometimes 15 | example.com (but not on Github, for example). 16 | 17 | Some text to show that the reference links can follow later. 18 | 19 | [arbitrary case-insensitive reference text]: https://www.mozilla.org 20 | [1]: http://slashdot.org 21 | [link text itself]: http://www.reddit.com 22 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0030_insta-youtube.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.4 on 2019-10-31 22:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("people", "0029_person_last_updated")] 8 | 9 | operations = [ 10 | migrations.AddField( 11 | model_name="person", 12 | name="instagram_id", 13 | field=models.CharField(blank=True, max_length=800, null=True), 14 | ), 15 | migrations.AddField( 16 | model_name="person", 17 | name="instagram_url", 18 | field=models.CharField(blank=True, max_length=800, null=True), 19 | ), 20 | migrations.AddField( 21 | model_name="person", 22 | name="youtube_profile", 23 | field=models.CharField(blank=True, max_length=800, null=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /wcivf/apps/people/templates/people/includes/_person_multiple_current_candidacies.html: -------------------------------------------------------------------------------- 1 | {% load i18n static %} 2 | 3 |
    {{ person_name }} 4 | {% with party=candidacies.0.party %} 5 | {% blocktrans trimmed with party_name=party.party_name party_link=party.get_absolute_url a_or_an=party.is_independent|yesno:_("an,a") %} 6 | {{ verb }} {{ a_or_an }} {{ party_name }} candidate in the following elections: 7 | {% endblocktrans %} 8 | {% endwith %} 9 |
    10 | 11 | {% for candidacy in candidacies %} 12 | 20 | {% endfor %} 21 | -------------------------------------------------------------------------------- /wcivf/apps/elections/sitemaps.py: -------------------------------------------------------------------------------- 1 | from django.contrib.sitemaps import Sitemap 2 | from django.db.models import Q 3 | 4 | from .models import Election, PostElection 5 | 6 | 7 | class ElectionSitemap(Sitemap): 8 | changefreq = "weekly" 9 | priority = 0.5 10 | protocol = "https" 11 | 12 | def items(self): 13 | return Election.objects.all() 14 | 15 | 16 | class PostElectionSitemap(Sitemap): 17 | changefreq = "weekly" 18 | priority = 0.9 19 | protocol = "https" 20 | 21 | # Only include posts for general elections, since 22 | # otherwise the sitemap gets close to the Google limit. 23 | # of 50,000 URLs. 24 | def items(self): 25 | return PostElection.objects.filter( 26 | Q(election__election_type="parl") 27 | | Q(election__election_type="2010") 28 | | Q(election__election_type="2015") 29 | ).order_by("-election__election_date") 30 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::MDB::mayor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% load markdown_filter %} 4 | 5 | {# fmt:off #} 6 | {% filter markdown %} 7 | The Mayor of Middlesbrough leads the Middlesbrough Council and represents the borough at local, regional, and national levels. The Mayor's responsibilities include setting strategic priorities, overseeing economic development, and managing budgets for public services. The Mayor also works to improve transportation, housing, education, and community safety. Additionally, the Mayor engages with residents, businesses, and other stakeholders to address local issues and promote the well-being of the community. The role involves both independent decision-making and collaboration with the council and other local authorities. 8 | 9 | [Find more about the council](https://www.middlesbrough.gov.uk/council-and-democracy/about-the-council/) 10 | 11 | 12 | {% endfilter %} 13 | -------------------------------------------------------------------------------- /wcivf/apps/news_mentions/templates/news_mentions/news_articles.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 |
    3 |
    4 |

    5 | 6 | {% trans "News articles" %} 7 |

    8 |
      9 | {% for news_article in news_articles %} 10 |
    • 11 | {% if news_article.publisher %} 12 | {{ news_article.publisher }}: 13 | {% endif %} 14 | 15 | {{ news_article.title }} 16 | 17 | {% if news_article.summary %} 18 |

      {{ news_article.summary }}

      19 | {% endif %} 20 |
    • 21 | {% endfor %} 22 |
    23 |
    24 |
    25 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0049_personredirect.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-05-30 12:44 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("people", "0048_person_blue_sky_url_person_other_url_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.CreateModel( 13 | name="PersonRedirect", 14 | fields=[ 15 | ( 16 | "id", 17 | models.AutoField( 18 | auto_created=True, 19 | primary_key=True, 20 | serialize=False, 21 | verbose_name="ID", 22 | ), 23 | ), 24 | ("old_person_id", models.IntegerField()), 25 | ("new_person_id", models.IntegerField()), 26 | ], 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /wcivf/apps/people/templates/people/includes/intros/_speaker.html: -------------------------------------------------------------------------------- 1 | {% extends "people/includes/intros/base.html" %} 2 | {% load i18n %} 3 | {% block intro_line %} 4 | {% if verb == "is" %} 5 | {% blocktrans trimmed %} 6 | {{ person_name }} is the Speaker seeking re-election in 7 | {{ post_label }} constituency in the 8 | {{ election_name }}. 9 | {% endblocktrans %} 10 | {% else %} 11 | {% blocktrans trimmed %} 12 | {{ person_name }} was the Speaker seeking re-election in 13 | {{ post_label }} constituency in the 14 | {{ election_name }}. 15 | {% endblocktrans %} 16 | {% endif %} 17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /deploy/files/scripts/remove_db_replication.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -xeEo pipefail 3 | 4 | set -a 5 | source /var/www/wcivf/code/.env 6 | set +a 7 | 8 | # rotate the log file otherwise output is lost in cloudwatch 9 | echo "" > /var/log/db_replication/logs.log 10 | 11 | USER=`whoami` 12 | METADATA_TOKEN=$(curl -X PUT "http://instance-data/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" --fail --silent) 13 | INSTANCE_ID=$(curl "http://instance-data/latest/meta-data/instance-id" -H "X-aws-ec2-metadata-token: $METADATA_TOKEN" --fail --silent) 14 | SUBSCRIPTION=${USER}_${INSTANCE_ID:2} 15 | 16 | drop_subscription () { 17 | psql -U "$USER" -c "DROP SUBSCRIPTION $SUBSCRIPTION;" 18 | } 19 | 20 | # if subscription is active it will fail - repeat until inactive 21 | until drop_subscription; do 22 | echo "Trying to drop subscription again..." 23 | done 24 | 25 | echo "Subscription dropped" 26 | touch "${PROJECT_ROOT}"/home/server_dirty 27 | -------------------------------------------------------------------------------- /wcivf/apps/core/tests/forbidden_markdown/expected/links.txt: -------------------------------------------------------------------------------- 1 |

    [I'm an inline-style link](https://www.google.com)

    2 |

    [I'm an inline-style link with title](https://www.google.com "Google's Homepage")

    3 |

    [I'm a reference-style link][Arbitrary case-insensitive reference text]

    4 |

    [I'm a relative reference to a repository file](../blob/master/LICENSE)

    5 |

    [You can use numbers for reference-style link definitions][1]

    6 |

    Or leave it empty and use the [link text itself].

    7 |

    URLs and URLs in angle brackets will automatically get turned into links.
    8 | http://www.example.com or <http://www.example.com> and sometimes
    9 | example.com (but not on Github, for example).

    10 |

    Some text to show that the reference links can follow later.

    11 |

    [arbitrary case-insensitive reference text]: https://www.mozilla.org
    12 | [1]: http://slashdot.org
    13 | [link text itself]: http://www.reddit.com

    14 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_council_contact_details.html: -------------------------------------------------------------------------------- 1 |

    2 |

    3 | {{ contact_details.name }} 4 | 5 | {% if with_address %} 6 | {% if contact_details.address %} 7 | {{ contact_details.address|linebreaksbr }} 8 | {% endif %} 9 | {% if contact_details.postcode %} 10 |
    {{ contact_details.postcode }} 11 | {% endif %} 12 |
    13 | {% else %} 14 |
    15 | {% endif %} 16 | 17 | 18 | {{ contact_details.website }} 19 | 20 |
    {{ contact_details.email }} 21 | {% for number in contact_details.phone_numbers %} 22 |
    {{ number }} 23 | {% endfor %} 24 |
    25 |

    26 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templatetags/postcode_tags.py: -------------------------------------------------------------------------------- 1 | import re 2 | from datetime import datetime 3 | from typing import List 4 | 5 | from django import template 6 | from django.template.defaultfilters import stringfilter 7 | from elections.models import PostElection 8 | 9 | register = template.Library() 10 | 11 | 12 | @register.filter(name="ni_postcode") 13 | @stringfilter 14 | def ni_postcode(postcode): 15 | if re.match("^BT.*", postcode): 16 | return True 17 | return False 18 | 19 | 20 | @register.filter(name="todate") 21 | def convert_str_date(value): 22 | return datetime.strptime(value, "%Y-%m-%d").date() 23 | 24 | 25 | @register.filter(name="totime") 26 | def convert_str_time(value): 27 | return datetime.strptime(value, "%H:%M:%S").time() 28 | 29 | 30 | @register.filter(name="uncancelled_ballots") 31 | def uncancelled_ballots(ballots: List[PostElection]): 32 | return [ballot for ballot in ballots if not ballot.cancelled] 33 | -------------------------------------------------------------------------------- /wcivf/apps/hustings/templates/hustings/includes/_ld_husting.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 21 | -------------------------------------------------------------------------------- /wcivf/apps/people/management/commands/delete_deleted_people.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.utils import timezone 3 | from people.import_helpers import YNRPersonImporter 4 | 5 | 6 | class Command(BaseCommand): 7 | help = "Deletes People objects that have been delted in YNR" 8 | 9 | def add_arguments(self, parser): 10 | parser.add_argument( 11 | "--date", 12 | action="store", 13 | dest="date", 14 | default=False, 15 | help="The date to look for deleted people. If not used defaults to today", 16 | ) 17 | 18 | def handle(self, **options): 19 | date = options["date"] or timezone.now().date() 20 | importer = YNRPersonImporter( 21 | params={ 22 | "created": str(date), 23 | "action_type": "person-delete", 24 | } 25 | ) 26 | importer.delete_deleted_people() 27 | -------------------------------------------------------------------------------- /wcivf/apps/people/migrations/0041_dummycandidacy_dummyperson.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.10 on 2022-03-09 15:25 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("people", "0040_person_blog_url"), 9 | ] 10 | 11 | operations = [ 12 | migrations.CreateModel( 13 | name="DummyCandidacy", 14 | fields=[], 15 | options={ 16 | "proxy": True, 17 | "indexes": [], 18 | "constraints": [], 19 | }, 20 | bases=("people.personpost",), 21 | ), 22 | migrations.CreateModel( 23 | name="DummyPerson", 24 | fields=[], 25 | options={ 26 | "proxy": True, 27 | "indexes": [], 28 | "constraints": [], 29 | }, 30 | bases=("people.person",), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /wcivf/apps/core/context_processors.py: -------------------------------------------------------------------------------- 1 | from core.helpers import clean_postcode 2 | from django.conf import settings 3 | 4 | from .forms import PostcodeLookupForm 5 | 6 | 7 | def use_compress_css(request): 8 | return { 9 | "USE_COMPRESSED_CSS": getattr(settings, "USE_COMPRESSED_CSS", False) 10 | } 11 | 12 | 13 | def use_i18n(request): 14 | return {"USE_I18N": getattr(settings, "USE_I18N", False)} 15 | 16 | 17 | def postcode_form(request): 18 | return {"postcode_form": PostcodeLookupForm()} 19 | 20 | 21 | def referer_postcode(request): 22 | referer_parts = request.META.get("HTTP_REFERER", "") 23 | referer_parts = referer_parts.strip("/").split("/") 24 | if len(referer_parts) >= 2 and referer_parts[-2] == "elections": 25 | possible_postcode = clean_postcode(referer_parts[-1]) 26 | if len(possible_postcode) < 8: 27 | return {"referer_postcode": possible_postcode.replace("%20", "")} 28 | return {} 29 | -------------------------------------------------------------------------------- /wcivf/apps/elections/templates/elections/includes/_post_meta_description.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | {% if postelection.cancelled %} 4 | {% blocktrans with election_name=object.election.name %}{{ election_name }}: This election has been cancelled. {% endblocktrans %} 5 | {% elif postelection.people|length %} 6 | {% autoescape off %} 7 | {% blocktrans trimmed with election_name=object.election.name num_candidates=postelection.people|length election_date=object.election.election_date|date:"j M Y" %} 8 | See all {{ num_candidates }} candidates in the {{ election_name }} on {{ election_date }}:{% endblocktrans %} 9 | {% for pp in postelection.people %} 10 | {{ pp.person.name }} ({{ pp.party_name }}) 11 | {% endfor %} 12 | {% endautoescape %} 13 | {% else %} 14 | {% blocktrans trimmed with election_name=object.election.name %} {{ election_name }}: No candidates known yet.{% endblocktrans %} 15 | {% endif %} 16 | -------------------------------------------------------------------------------- /wcivf/apps/parties/migrations/0007_auto_20180417_1733.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.12 on 2018-04-17 17:33 3 | from __future__ import unicode_literals 4 | 5 | import django.db.models.deletion 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | dependencies = [ 11 | ("elections", "0020_postelection_ballot_paper_id"), 12 | ("parties", "0006_auto_20180417_1711"), 13 | ] 14 | 15 | operations = [ 16 | migrations.RemoveField(model_name="localparty", name="ballot_papers"), 17 | migrations.AddField( 18 | model_name="localparty", 19 | name="post_election", 20 | field=models.ForeignKey( 21 | default=1, 22 | on_delete=django.db.models.deletion.CASCADE, 23 | to="elections.PostElection", 24 | ), 25 | preserve_default=False, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /docs/s3.md: -------------------------------------------------------------------------------- 1 | ## S3 buckets 2 | 3 | ### Deployment artifact bucket 4 | 5 | In the [AWS S3 web UI](https://s3.console.aws.amazon.com/s3/home?region=eu-west-2), create an S3 bucket. 6 | 7 | - Name: `wcivf-deployment-artifacts--` 8 | - Region: eu-west-2 9 | - Public access: entirely disabled 10 | - Versioning: disabled 11 | - Encryption: doesn't matter 12 | 13 | After creation, view the bucket and select the "Management" tab. 14 | 15 | Select "Create lifecycle rule". 16 | 17 | - Rule name: `delete-any-file-1-day-after-upload` 18 | - Apply to all objects 19 | - Tick the options for: 20 | - "Expire current versions of objects" 21 | - "Delete expired delete markers or incomplete multipart uploads" 22 | - For "Expire current versions of objects": 23 | - Enter "1" day 24 | - For "Delete expired delete markers or incomplete multipart uploads": 25 | - Tick "Delete incomplete multipart uploads" 26 | - Enter "1" day 27 | 28 | Create the rule. 29 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/post_type/NIE.html: -------------------------------------------------------------------------------- 1 | 2 | {% load markdown_filter %} 3 | 4 | {# fmt:off #} 5 | {% filter markdown %} 6 | Members of the Legislative Assembly (MLAs) are elected by, and represent the people of, Northern Ireland. MLAs pass laws and examine policy on transferred matters like health, education, the environment, social work and housing. They scrutinise the work of Ministers and hold Executive (Government) Departments to account. 7 | 8 | Every MLA has an office in their constituency to enable them to provide a constituency service. They meet with constituents, listen to their views and act on their behalf to resolve problems. MLAs can contact relevant bodies and can raise the profile of an issue in the media. They communicate with their constituents by email, telephone, letter and social media. 9 | 10 | 11 | [Read more about your MLAs](https://www.niassembly.gov.uk/about-the-assembly/how-the-assembly-works/about-your-mlas/) 12 | 13 | {% endfilter %} 14 | -------------------------------------------------------------------------------- /wcivf/apps/referendums/templates/referendums/detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load humanize %} 4 | 5 | {% block page_title %}{% include "elections/includes/_post_meta_title.html" %}{% endblock page_title %} 6 | {% block page_description %}{% include "elections/includes/_post_meta_description.html" %}{% endblock page_description %} 7 | {% block og_title_content %}{% include "elections/includes/_post_meta_title.html" %}{% endblock og_title_content %} 8 | {% block og_description_content %}{% include "elections/includes/_post_meta_description.html" %}{% endblock og_description_content %} 9 | 10 | {% block content %} 11 | {% include "elections/includes/_post_breadcrumbs.html" %} 12 | 13 |
    14 | {% include "referendums/includes/_card.html" with referendum=object.referendum %} 15 | 16 | {% include "elections/includes/_postcode_search_form.html" %} 17 | 18 | {% include "feedback/feedback_form.html" %} 19 |
    20 | 21 | {% endblock content %} 22 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::LCR::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | Metro Mayors are directly elected by the people in their area and chair their Combined Authority. The Metro Mayor 6 | exercises the powers and functions devolved from government, set out in the local area’s devolution deal. 7 | 8 | The UK government has devolved a range of powers and responsibilities to the Liverpool City Region Combined 9 | Authority and the Mayor of the Liverpool City Region. These include: 10 | 11 | * Apprenticeships 12 | * Business growth, innovation and support 13 | * Children’s services 14 | * Culture and tourism 15 | * Employment and skills 16 | * Energy and environment 17 | * Some taxes and finance 18 | * Health and social care 19 | * Housing and planning 20 | * Justice 21 | * Transport 22 | 23 | [Find more about the mayor's role](https://www.liverpoolcityregion-ca.gov.uk/your-metro-mayor) 24 | 25 | 26 | {% endfilter %} 27 | -------------------------------------------------------------------------------- /wcivf/apps/people/templates/people/includes/intros/_parl.html: -------------------------------------------------------------------------------- 1 | {% extends "people/includes/intros/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block intro_line %} 5 | 6 | {% if verb == "is" %} 7 | {% blocktrans trimmed %} 8 | {{ person_name }} is a {{ party_name }} 9 | candidate in {{ post_label }} 10 | constituency in the {{ election_name }}. 11 | {% endblocktrans %} 12 | {% else %} 13 | {% blocktrans trimmed %} 14 | {{ person_name }} was a {{ party_name }} 15 | candidate in {{ post_label }} 16 | constituency in the {{ election_name }}. 17 | {% endblocktrans %} 18 | {% endif %} 19 | 20 | 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /wcivf/apps/elections/migrations/0044_fix_territory.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations 2 | 3 | post_map = { 4 | "E": "ENG", 5 | "W": "WLS", 6 | "N": "NIR", 7 | "S": "SCT", 8 | } 9 | 10 | 11 | def fix_territory(apps, schema_editor): 12 | Post = apps.get_model("elections", "Post") 13 | posts = Post.objects.using(schema_editor.connection.alias).exclude( 14 | territory__in=["ENG", "WLS", "NIR", "SCT"] 15 | ) 16 | for post in posts: 17 | if not post.ynr_id.startswith("gss:"): 18 | raise ValueError(f"Can't infer territory for post {post.ynr_id}") 19 | post.territory = post_map[post.ynr_id[4]] 20 | post.save() 21 | 22 | 23 | class Migration(migrations.Migration): 24 | dependencies = [ 25 | ("elections", "0043_remove_election_ballot_papers_issued_and_more"), 26 | ] 27 | 28 | operations = [ 29 | migrations.RunPython( 30 | code=fix_territory, reverse_code=migrations.RunPython.noop 31 | ) 32 | ] 33 | -------------------------------------------------------------------------------- /wcivf/apps/people/management/commands/import_facebook_ads.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from django.core.management.base import BaseCommand 3 | from people.models import FacebookAdvert 4 | 5 | 6 | class Command(BaseCommand): 7 | def handle(self, **options): 8 | url = "https://candidates.democracyclub.org.uk/api/next/facebook_adverts/?page_size=200" 9 | while url: 10 | req = requests.get(url) 11 | req.raise_for_status() 12 | results = req.json() 13 | self.import_ads(results.get("results", [])) 14 | url = results.get("next") 15 | 16 | def import_ads(self, results): 17 | for result in results: 18 | FacebookAdvert.objects.update_or_create( 19 | ad_id=result["ad_id"], 20 | defaults={ 21 | "ad_json": result["ad_json"], 22 | "person_id": result["person"]["id"], 23 | "image_url": result["image"], 24 | }, 25 | ) 26 | -------------------------------------------------------------------------------- /wcivf/apps/administrations/templates/responsibilities/single_id/O::NEMC::mayor.html: -------------------------------------------------------------------------------- 1 | {% load markdown_filter %} 2 | 3 | {# fmt:off #} 4 | {% filter markdown %} 5 | 6 | Combined authorities allow a group of local authorities to pool appropriate responsibility and receive certain 7 | devolved functions from central government in order to deliver transport and economic policy more effectively over a 8 | wider area. The mayor is the leader of the North East Mayoral Combined Authority and has responsibility for: 9 | 10 | * housing and regeneration 11 | * education, skills and training 12 | * the adult education budget 13 | * the functional power of competence 14 | * housing and planning, including mayoral development areas and corporations, land and acquisition powers 15 | * finance, through council precepts and business rate supplements 16 | * transport, including bus grants and franchising powers 17 | 18 | [Find more about the mayor's role](https://www.northeast-ca.gov.uk/about/the-mayor) 19 | 20 | 21 | {% endfilter %} 22 | -------------------------------------------------------------------------------- /wcivf/apps/parties/migrations/0021_party_nations.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.6 on 2023-07-14 13:06 2 | 3 | import django.contrib.postgres.fields 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("parties", "0020_party_alternative_name"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="party", 15 | name="nations", 16 | field=django.contrib.postgres.fields.ArrayField( 17 | base_field=models.CharField(max_length=3), 18 | help_text='\n Some subset of ["ENG", "SCO", "WAL"],\n depending on where the party fields candidates. \n Nullable as not applicable to NI-based parties.\n ', 19 | max_length=3, 20 | null=True, 21 | size=None, 22 | verbose_name="Party nations", 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /wcivf/apps/referendums/importers.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from core.mixins import ImportAdditionalElectionMixin 4 | from referendums.models import Referendum 5 | 6 | 7 | class ReferendumImporter(ImportAdditionalElectionMixin): 8 | model = Referendum 9 | 10 | def create_object(self, row): 11 | """ 12 | Create a Referendum object for the data and add M2M relationship. 13 | If there is no question we skip the row. 14 | If we cant find a matching ballot we skip the row. 15 | """ 16 | if not row["question"]: 17 | return sys.stdout.write("No question to use, skipping\n") 18 | 19 | ballots = self.get_ballots(election_id=row.pop("election_id")) 20 | if not ballots: 21 | return sys.stdout.write("No ballots so skipping referendum\n") 22 | 23 | referendum = Referendum.objects.create(**row) 24 | referendum.ballot_dict.set(ballots) 25 | sys.stdout.write(f"Created Referendum {referendum.pk}\n") 26 | return referendum 27 | -------------------------------------------------------------------------------- /wcivf/apps/people/templates/people/includes/intros/base.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% block intro_line %} 3 | {% if verb == "is" %} 4 | {% blocktrans trimmed %} 5 | {{ person_name }} is a {{ party_name }} 6 | candidate in {{ post_label }} in the 7 | {{ election_name }}. 8 | {% endblocktrans %} 9 | 10 | {% else %} 11 | {% blocktrans trimmed %} 12 | {{ person_name }} was a {{ party_name }} 13 | candidate in {{ post_label }} in the 14 | {{ election_name }}. 15 | {% endblocktrans %} 16 | {% endif %} 17 | 18 | 19 | {% endblock %} 20 | {% if candidacy.votes_cast %} 21 | {% include "people/includes/intros/_votes_cast.html" %} 22 | {% endif %} 23 | -------------------------------------------------------------------------------- /wcivf/settings/local.example.py: -------------------------------------------------------------------------------- 1 | DATABASES = { 2 | "default": { 3 | "ENGINE": "django.db.backends.postgresql", 4 | "NAME": "wcivf", 5 | "USER": "wcivf", 6 | "PASSWORD": "wcivf", 7 | } 8 | } 9 | 10 | 11 | CACHES = { 12 | "default": { 13 | "BACKEND": "django.core.cache.backends.dummy.DummyCache", 14 | } 15 | } 16 | 17 | ALLOWED_HOSTS = ["*"] 18 | 19 | YNR_BASE = "https://candidates.democracyclub.org.uk" 20 | 21 | EE_BASE = "https://elections.democracyclub.org.uk" 22 | 23 | INTERNAL_IPS = ["127.0.0.1"] 24 | 25 | SECRET_KEY = "not_for_production" 26 | 27 | DEBUG = True 28 | 29 | EMAIL_SIGNUP_BACKEND = "local_db" 30 | 31 | # SHOW_GB_ID_MESSAGING = False 32 | # SHOW_RESULTS_CHART = False 33 | 34 | # These example paths should only be necessary for M1 Mac users 35 | 36 | # GDAL_LIBRARY_PATH = "/opt/homebrew/Cellar/gdal/3.5.1_3/lib/libgdal.dylib" 37 | 38 | # GEOS_LIBRARY_PATH= '/opt/homebrew/Cellar/geos/3.11.0/lib/libgeos_c.1.17.0.dylib' 39 | 40 | SHOW_UPCOMING_ELECTIONS = True 41 | -------------------------------------------------------------------------------- /samconfig.toml: -------------------------------------------------------------------------------- 1 | version = 0.1 2 | [development] 3 | [development.deploy] 4 | [development.deploy.parameters] 5 | stack_name = "WCIVFControllerApp" 6 | s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-bpwbpsluzkgd" 7 | s3_prefix = "WCIVFController" 8 | region = "eu-west-2" 9 | confirm_changeset = false 10 | fail_on_empty_changeset = false 11 | capabilities = "CAPABILITY_IAM" 12 | force_upload = true 13 | 14 | [staging] 15 | [staging.deploy] 16 | [staging.deploy.parameters] 17 | stack_name = "WCIVFApp" 18 | s3_bucket = "wcivf-deployment-artifacts-tpssjdsa" 19 | region = "eu-west-2" 20 | confirm_changeset = false 21 | fail_on_empty_changeset = false 22 | capabilities = "CAPABILITY_IAM" 23 | force_upload = true 24 | 25 | [production] 26 | [production.deploy] 27 | [production.deploy.parameters] 28 | stack_name = "WCIVFApp" 29 | s3_bucket = "wcivf-deployment-artifacts-production-d9tjh34h" 30 | region = "eu-west-2" 31 | confirm_changeset = false 32 | fail_on_empty_changeset = false 33 | capabilities = "CAPABILITY_IAM" 34 | force_upload = true 35 | --------------------------------------------------------------------------------