diff --git a/elections2021/constants.py b/elections2021/constants.py index cc3e9c8abdc0c7013c2f27361823e551d69ec20e..9f424d7683b4deedf6fe06f825902cac99dbd954 100644 --- a/elections2021/constants.py +++ b/elections2021/constants.py @@ -111,25 +111,17 @@ NATURE = "nature" SPORT = "sport" HEALTH = "health" CULTURE = "culture" -COMPUTERS = "computers" +TECHNOLOGY = "technology" COUNTRYSIDE = "countryside" HOUSING = "housing" EDUCATION = "education" -TARGET_CHOICES = ( - (CHILDLESS, "bezdětní"), - (PARENTS, "rodiče"), - (MATURE, "zralí"), - (SENIORS, "senioři"), - (WORKING_SENIORS, "pracující senioři"), - (STUDENTS, "studenti"), - (SELF_EMPLOYED, "OSVČ"), - (SOCIALLY_WEAK, "sociálně slabí"), +TOPIC_CHOICES = ( (NATURE, "příroda"), (SPORT, "sport"), (HEALTH, "zdraví"), (CULTURE, "kultura"), - (COMPUTERS, "počítače"), + (TECHNOLOGY, "technologie"), (COUNTRYSIDE, "regiony"), (HOUSING, "bydlení"), (EDUCATION, "vzdělávání"), @@ -237,3 +229,39 @@ BENEFITS = ( ) BENEFITS_CHOICES = [(num, name) for num, name, _ in BENEFITS] + +AGE_18_29 = "18-29" +AGE_30_49 = "30-49" +AGE_50_64 = "50-64" +AGE_65_PLUS = "65-plus" + +AGE_CHOICES = ( + (AGE_18_29, "18 - 29 let"), + (AGE_30_49, "30 - 49 let"), + (AGE_50_64, "50 - 64 let"), + (AGE_65_PLUS, "65+ let"), +) + +WEALTH_BAD = "bad" +WEALTH_AVERAGE = "average" +WEALTH_GOOD = "good" + +WEALTH_CHOICES = ( + (WEALTH_BAD, "nic moc"), + (WEALTH_AVERAGE, "průměrně"), + (WEALTH_GOOD, "skvěle"), +) + +OCCUPATION_STUDENT = "student" +OCCUPATION_WORKING = "working" +OCCUPATION_BUSINESS = "business" +OCCUPATION_PARENTING = "parenting" +OCCUPATION_RETIRED = "retired" + +OCCUPATION_CHOICES = ( + (OCCUPATION_STUDENT, "studuji"), + (OCCUPATION_WORKING, "pracuji"), + (OCCUPATION_BUSINESS, "podnikám"), + (OCCUPATION_PARENTING, "na rodičovské"), + (OCCUPATION_RETIRED, "v důchodu"), +) diff --git a/elections2021/forms.py b/elections2021/forms.py index 096236346e0fa0a3457e018b5974407f521c68bf..2743ffd0a8e1b2d58295c307830ee886498b3599 100644 --- a/elections2021/forms.py +++ b/elections2021/forms.py @@ -4,6 +4,15 @@ from django import forms from django.utils.text import slugify from wagtail.admin.forms import WagtailAdminPageForm +from .constants import ( + AGE_30_49, + AGE_CHOICES, + OCCUPATION_CHOICES, + OCCUPATION_WORKING, + TOPIC_CHOICES, + WEALTH_AVERAGE, + WEALTH_CHOICES, +) from .parser import ( parse_program_html, prepare_benefit_for_all, @@ -68,3 +77,13 @@ class ProgramPointPageForm(WagtailAdminPageForm): page.save() return page + + +class ProgramAppForm(forms.Form): + age = forms.ChoiceField(choices=AGE_CHOICES, initial=AGE_30_49) + kids = forms.BooleanField(required=False, initial=True) + wealth = forms.ChoiceField(choices=WEALTH_CHOICES, initial=WEALTH_AVERAGE) + occupation = forms.ChoiceField( + choices=OCCUPATION_CHOICES, initial=OCCUPATION_WORKING + ) + topics = forms.MultipleChoiceField(required=False, choices=TOPIC_CHOICES) diff --git a/elections2021/management/commands/import_matrix.py b/elections2021/management/commands/import_matrix.py index e069f3d958a2d6c8e5f6488cb1acd50a8bfc98af..f80f41df3df128967724b12726de3523a3c35ba5 100644 --- a/elections2021/management/commands/import_matrix.py +++ b/elections2021/management/commands/import_matrix.py @@ -5,7 +5,6 @@ from django.utils.text import slugify from ...constants import ( CHILDLESS, - COMPUTERS, COUNTRYSIDE, CULTURE, EDUCATION, @@ -41,6 +40,7 @@ from ...constants import ( SOCIALLY_WEAK, SPORT, STUDENTS, + TECHNOLOGY, WORKING_SENIORS, ) from ...models import Elections2021ProgramPointPage @@ -65,7 +65,7 @@ FIELDNAMES = ( SPORT, HEALTH, CULTURE, - COMPUTERS, + TECHNOLOGY, COUNTRYSIDE, HOUSING, EDUCATION, @@ -127,6 +127,7 @@ class Command(BaseCommand): page.weight_sport = int(row[SPORT]) page.weight_health = int(row[HEALTH]) page.weight_culture = int(row[CULTURE]) + page.weight_technology = int(row[TECHNOLOGY]) page.weight_countryside = int(row[COUNTRYSIDE]) page.weight_housing = int(row[HOUSING]) page.weight_education = int(row[EDUCATION]) diff --git a/elections2021/migrations/0011_elections2021programapppage.py b/elections2021/migrations/0011_elections2021programapppage.py new file mode 100644 index 0000000000000000000000000000000000000000..58c97d435d51882242478a62c6768a052869b1a6 --- /dev/null +++ b/elections2021/migrations/0011_elections2021programapppage.py @@ -0,0 +1,65 @@ +# Generated by Django 3.2.2 on 2021-05-15 10:51 + +import django.db.models.deletion +import wagtailmetadata.models +from django.db import migrations, models + +import shared.models + + +class Migration(migrations.Migration): + + dependencies = [ + ("wagtailimages", "0023_add_choose_permissions"), + ("wagtailcore", "0062_comment_models_and_pagesubscription"), + ("elections2021", "0010_auto_20210514_0115"), + ] + + operations = [ + migrations.CreateModel( + name="Elections2021ProgramAppPage", + fields=[ + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ( + "cover_photo", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="wagtailimages.image", + verbose_name="cover foto", + ), + ), + ( + "search_image", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="wagtailimages.image", + verbose_name="Search image", + ), + ), + ], + options={ + "verbose_name": "Programová aplikace", + }, + bases=( + shared.models.SubpageMixin, + wagtailmetadata.models.WagtailImageMetadataMixin, + "wagtailcore.page", + models.Model, + ), + ), + ] diff --git a/elections2021/migrations/0012_auto_20210515_1257.py b/elections2021/migrations/0012_auto_20210515_1257.py new file mode 100644 index 0000000000000000000000000000000000000000..5bc4b17d7b4574674ef7a8d8cefe9e551d9ec8c8 --- /dev/null +++ b/elections2021/migrations/0012_auto_20210515_1257.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.2 on 2021-05-15 10:57 + +import wagtail.core.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("elections2021", "0011_elections2021programapppage"), + ] + + operations = [ + migrations.RemoveField( + model_name="elections2021programapppage", + name="cover_photo", + ), + migrations.AddField( + model_name="elections2021programapppage", + name="info_text", + field=wagtail.core.fields.RichTextField( + blank=True, null=True, verbose_name="základní informace" + ), + ), + ] diff --git a/elections2021/migrations/0013_elections2021programpointpage_weight_technology.py b/elections2021/migrations/0013_elections2021programpointpage_weight_technology.py new file mode 100644 index 0000000000000000000000000000000000000000..4b06514fa8eb78324cce1a58c6ce1cf5c6616472 --- /dev/null +++ b/elections2021/migrations/0013_elections2021programpointpage_weight_technology.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.2 on 2021-05-15 19:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("elections2021", "0012_auto_20210515_1257"), + ] + + operations = [ + migrations.AddField( + model_name="elections2021programpointpage", + name="weight_technology", + field=models.IntegerField(default=0, verbose_name="váha technologie"), + ), + ] diff --git a/elections2021/models.py b/elections2021/models.py index a4f5dac27a232839c9a40d20009fe7b57f708a86..c194bd9c3218dbf214bccdd2037ef6d1893c195f 100644 --- a/elections2021/models.py +++ b/elections2021/models.py @@ -1,9 +1,11 @@ -from collections import defaultdict +import json from functools import cached_property from django import forms +from django.conf import settings from django.core.paginator import Paginator from django.db import models +from django.db.models.functions import Greatest from django.http import HttpResponseRedirect from django.utils.translation import gettext_lazy from modelcluster.contrib.taggit import ClusterTaggableManager @@ -35,7 +37,14 @@ from .constants import ( ARTICLES_PER_PAGE, BENEFITS_CHOICES, CANDIDATE_RICH_TEXT_FEATURES, + CHILDLESS, + COUNTRYSIDE, + CULTURE, + EDUCATION, EXTRA_FEATURES, + HEALTH, + HOUSING, + MATURE, MINISTRY_AGRICULTURE, MINISTRY_ARCHETYPES, MINISTRY_BUSINESS, @@ -53,6 +62,9 @@ from .constants import ( MINISTRY_SCHOOLS, MINISTRY_SOCIAL, MINISTRY_TRANSPORT, + NATURE, + OCCUPATION_BUSINESS, + PARENTS, PARTY_CHOICES, PARTY_NAME, PIRATES, @@ -71,16 +83,22 @@ from .constants import ( REGION_CHOICES, REGION_NAME_VARIANT, REGION_OPTIONS, - REGION_PHA, REGION_SLUGS, RESTRICTED_FEATURES, + SENIORS, + SPORT, STANDARD_FEATURES, + STUDENTS, STYLE_CHOICES, STYLE_CSS, + TECHNOLOGY, TOP_CANDIDATES_NUM, + WEALTH_BAD, WHITE, + WORKING_SENIORS, ) -from .forms import ProgramPointPageForm +from .forms import ProgramAppForm, ProgramPointPageForm +from .utils import get_archetype NO_SEARCH_IMAGE_USE_PHOTO = ( "Pokud není zadán <strong>Search image</strong>, použije se <strong>hlavní " @@ -139,6 +157,7 @@ class Elections2021HomePage(Page, MetadataPageMixin): "elections2021.Elections2021CandidatesMapPage", "elections2021.Elections2021ProgramPage", "elections2021.Elections2021QuestionsPage", + "elections2021.Elections2021ProgramAppPage", ] ### OTHERS @@ -176,6 +195,14 @@ class Elections2021HomePage(Page, MetadataPageMixin): def questions_page_url(self): return get_subpage_url(self, Elections2021QuestionsPage) + @cached_property + def program_page_url(self): + return get_subpage_url(self, Elections2021ProgramPage) + + @cached_property + def program_app_page_url(self): + return get_subpage_url(self, Elections2021ProgramAppPage) + class Elections2021ArticleTag(TaggedItemBase): content_object = ParentalKey( @@ -624,17 +651,35 @@ class Elections2021ProgramPage( context["show_archetype_icon"] = True context["show_app_banner"] = True context["show_pagination"] = True - - points = self.get_children().live().specific() - context["points"] = Paginator(points, PROGRAM_POINTS_PER_PAGE).get_page( - request.GET.get("page") - ) + context["active_my_program"] = False + context["has_my_program"] = self.get_my_selection(request) is not None return context @staticmethod def get_archetype_image(archetype): return f"elections2021/images/archetype/{archetype}.svg" + def get_my_program_url(self): + return self.url + self.reverse_subpage("my_program") + + def get_my_selection(self, request): + data = request.get_signed_cookie(settings.ELECTIONS2021_COOKIE_NAME, None) + if data: + try: + return json.loads(data) + except json.JSONDecodeError: + pass + return None + + @route(r"^$") + def plan_all(self, request, param=None): + points = Elections2021ProgramPointPage.objects.live().specific() + points = Paginator(points, PROGRAM_POINTS_PER_PAGE).get_page( + request.GET.get("page") + ) + context = {"points": points} + return self.render(request, context_overrides=context) + @route(r"^plan/([a-z-]+)/$") def plan_detail(self, request, param=None): plan = param if param in PLAN_OPTIONS else None @@ -646,58 +691,40 @@ class Elections2021ProgramPage( title = next(title for key, title in PLAN_CHOICES if key == plan) head_image = self.get_archetype_image(PLAN_ARCHETYPES[plan]) - points = self.get_children().live().specific() if plan == PLAN_ECONOMICS: head_text = self.plan_economics_text - points = points.filter( - elections2021programpointpage__weight_plan_economics__gt=0 - ).order_by("-elections2021programpointpage__weight_plan_economics") + weights = ["weight_plan_economics"] elif plan == PLAN_DIGITAL: head_text = self.plan_digital_text - points = points.filter( - elections2021programpointpage__weight_plan_digital__gt=0 - ).order_by("-elections2021programpointpage__weight_plan_digital") + weights = ["weight_plan_digital"] elif plan == PLAN_CORRUPTION: head_text = self.plan_corruption_text - points = points.filter( - elections2021programpointpage__weight_plan_corruption__gt=0 - ).order_by("-elections2021programpointpage__weight_plan_corruption") + weights = ["weight_plan_corruption"] elif plan == PLAN_CLIMATE: head_text = self.plan_climate_text - points = points.filter( - elections2021programpointpage__weight_plan_climate__gt=0 - ).order_by("-elections2021programpointpage__weight_plan_climate") + weights = ["weight_plan_climate"] elif plan == PLAN_COUNTRYSIDE: head_text = self.plan_countryside_text - points = points.filter( - elections2021programpointpage__weight_plan_countryside__gt=0 - ).order_by("-elections2021programpointpage__weight_plan_countryside") + weights = ["weight_plan_countryside"] elif plan == PLAN_MANAGEMENT: head_text = self.plan_management_text - points = points.filter( - elections2021programpointpage__weight_plan_management__gt=0 - ).order_by("-elections2021programpointpage__weight_plan_management") + weights = ["weight_plan_management"] elif plan == PLAN_SAFE_COUNTRY: head_text = self.plan_safe_country_text - points = points.filter( - elections2021programpointpage__weight_plan_safe_country__gt=0 - ).order_by("-elections2021programpointpage__weight_plan_safe_country") + weights = ["weight_plan_safe_country"] elif plan == PLAN_CARE: head_text = self.plan_care_text - points = points.filter( - elections2021programpointpage__weight_plan_care__gt=0 - ).order_by("-elections2021programpointpage__weight_plan_care") + weights = ["weight_plan_care"] context = { "active_plan": plan, "title": title, "head_image": head_image, "head_text": head_text, - "points": points, + "points": Elections2021ProgramPointPage.filter_by_weights(weights), "show_app_banner": False, "show_pagination": False, } - return self.render(request, context_overrides=context) @route(r"^resort/([a-z-]+)/$") @@ -711,75 +738,106 @@ class Elections2021ProgramPage( title = next(title for key, title in MINISTRY_CHOICES if key == ministry) head_image = self.get_archetype_image(MINISTRY_ARCHETYPES[ministry]) - points = self.get_children().live().specific() if ministry == MINISTRY_TRANSPORT: - points = points.filter( - elections2021programpointpage__weight_ministry_transport__gt=0 - ).order_by("-elections2021programpointpage__weight_ministry_transport") + weights = ["weight_ministry_transport"] elif ministry == MINISTRY_FINANCES: - points = points.filter( - elections2021programpointpage__weight_ministry_finances__gt=0 - ).order_by("-elections2021programpointpage__weight_ministry_finances") + weights = ["weight_ministry_finances"] elif ministry == MINISTRY_CULTURE: - points = points.filter( - elections2021programpointpage__weight_ministry_culture__gt=0 - ).order_by("-elections2021programpointpage__weight_ministry_culture") + weights = ["weight_ministry_culture"] elif ministry == MINISTRY_DEFENSE: - points = points.filter( - elections2021programpointpage__weight_ministry_defense__gt=0 - ).order_by("-elections2021programpointpage__weight_ministry_defense") + weights = ["weight_ministry_defense"] elif ministry == MINISTRY_SOCIAL: - points = points.filter( - elections2021programpointpage__weight_ministry_social__gt=0 - ).order_by("-elections2021programpointpage__weight_ministry_social") + weights = ["weight_ministry_social"] elif ministry == MINISTRY_COUNTRYSIDE: - points = points.filter( - elections2021programpointpage__weight_ministry_countryside__gt=0 - ).order_by("-elections2021programpointpage__weight_ministry_countryside") + weights = ["weight_ministry_countryside"] elif ministry == MINISTRY_BUSINESS: - points = points.filter( - elections2021programpointpage__weight_ministry_business__gt=0 - ).order_by("-elections2021programpointpage__weight_ministry_business") + weights = ["weight_ministry_business"] elif ministry == MINISTRY_JUSTICE: - points = points.filter( - elections2021programpointpage__weight_ministry_justice__gt=0 - ).order_by("-elections2021programpointpage__weight_ministry_justice") + weights = ["weight_ministry_justice"] elif ministry == MINISTRY_SCHOOLS: - points = points.filter( - elections2021programpointpage__weight_ministry_schools__gt=0 - ).order_by("-elections2021programpointpage__weight_ministry_schools") + weights = ["weight_ministry_schools"] elif ministry == MINISTRY_INTERIOR: - points = points.filter( - elections2021programpointpage__weight_ministry_interior__gt=0 - ).order_by("-elections2021programpointpage__weight_ministry_interior") + weights = ["weight_ministry_interior"] elif ministry == MINISTRY_FOREIGN: - points = points.filter( - elections2021programpointpage__weight_ministry_foreign__gt=0 - ).order_by("-elections2021programpointpage__weight_ministry_foreign") + weights = ["weight_ministry_foreign"] elif ministry == MINISTRY_HEALTH: - points = points.filter( - elections2021programpointpage__weight_ministry_health__gt=0 - ).order_by("-elections2021programpointpage__weight_ministry_health") + weights = ["weight_ministry_health"] elif ministry == MINISTRY_AGRICULTURE: - points = points.filter( - elections2021programpointpage__weight_ministry_agriculture__gt=0 - ).order_by("-elections2021programpointpage__weight_ministry_agriculture") + weights = ["weight_ministry_agriculture"] elif ministry == MINISTRY_ENVIRONMENT: - points = points.filter( - elections2021programpointpage__weight_ministry_environment__gt=0 - ).order_by("-elections2021programpointpage__weight_ministry_environment") + weights = ["weight_ministry_environment"] context = { "active_ministry": ministry, "title": title, "head_image": head_image, "head_text": head_text, - "points": points, + "points": Elections2021ProgramPointPage.filter_by_weights(weights), "show_archetype_icon": False, "show_app_banner": False, "show_pagination": False, } + return self.render(request, context_overrides=context) + @route(r"^muj-program/$") + def my_program(self, request): + selection = self.get_my_selection(request) + if selection is None: + response = HttpResponseRedirect(self.url) + # očekávali jsme data z cookie, ale nepodařilo se je získat, tak pro + # jistotu cookie smažeme kdyby náhodou existovala s poškozenými daty + response.delete_cookie(settings.ELECTIONS2021_COOKIE_NAME) + return response + + weights = [] + + archetype = get_archetype(selection) + if archetype == CHILDLESS: + weights.append("weight_childless") + elif archetype == PARENTS: + weights.append("weight_parents") + elif archetype == MATURE: + weights.append("weight_mature") + elif archetype == STUDENTS: + weights.append("weight_students") + elif archetype == SENIORS: + weights.append("weight_seniors") + elif archetype == WORKING_SENIORS: + weights.append("weight_working_seniors") + + if selection["occupation"] == OCCUPATION_BUSINESS: + weights.append("weight_self_employed") + + if selection["wealth"] == WEALTH_BAD: + weights.append("weight_socially_weak") + + if NATURE in selection["topics"]: + weights.append("weight_nature") + if SPORT in selection["topics"]: + weights.append("weight_sport") + if HEALTH in selection["topics"]: + weights.append("weight_health") + if CULTURE in selection["topics"]: + weights.append("weight_culture") + if TECHNOLOGY in selection["topics"]: + weights.append("weight_technology") + if COUNTRYSIDE in selection["topics"]: + weights.append("weight_countryside") + if HOUSING in selection["topics"]: + weights.append("weight_housing") + if EDUCATION in selection["topics"]: + weights.append("weight_education") + + points = Elections2021ProgramPointPage.filter_by_weights(weights) + points = Paginator(points, PROGRAM_POINTS_PER_PAGE).get_page( + request.GET.get("page") + ) + context = { + "title": "Program pro mě", + "points": points, + "show_app_banner": False, + "active_my_program": True, + } return self.render(request, context_overrides=context) @@ -896,6 +954,7 @@ class Elections2021ProgramPointPage(SubpageMixin, MetadataPageMixin, Page): weight_sport = models.IntegerField("váha sport", default=0) weight_health = models.IntegerField("váha zdraví", default=0) weight_culture = models.IntegerField("váha kultura", default=0) + weight_technology = models.IntegerField("váha technologie", default=0) weight_countryside = models.IntegerField("váha regiony", default=0) weight_housing = models.IntegerField("váha bydlení", default=0) weight_education = models.IntegerField("váha vzdělávání", default=0) @@ -1021,6 +1080,7 @@ class Elections2021ProgramPointPage(SubpageMixin, MetadataPageMixin, Page): FieldPanel("weight_sport"), FieldPanel("weight_health"), FieldPanel("weight_culture"), + FieldPanel("weight_technology"), FieldPanel("weight_countryside"), FieldPanel("weight_housing"), FieldPanel("weight_education"), @@ -1130,6 +1190,20 @@ class Elections2021ProgramPointPage(SubpageMixin, MetadataPageMixin, Page): return MINISTRY_ARCHETYPES[MINISTRY_ENVIRONMENT] return "" + @classmethod + def filter_by_weights(cls, weights): + points = cls.objects.live().specific() + if len(weights) > 1: + return ( + points.annotate(max_weight=Greatest(*weights)) + .filter(max_weight__gt=0) + .order_by("-max_weight") + ) + else: + condition = {f"{weights[0]}__gt": 0} + order = f"-{weights[0]}" + return points.filter(**condition).order_by(order) + class RichQuestionBlock(blocks.StructBlock): question = blocks.CharBlock(label="otázka") @@ -1211,3 +1285,55 @@ class Elections2021QuestionsPage(SubpageMixin, MetadataPageMixin, Page): def get_meta_image(self): return self.search_image or self.photo + + +class Elections2021ProgramAppPage(SubpageMixin, MetadataPageMixin, Page): + ### FIELDS + + info_text = RichTextField( + "základní informace", blank=True, null=True, features=RESTRICTED_FEATURES + ) + + ### PANELS + + content_panels = Page.content_panels + [FieldPanel("info_text")] + + promote_panels = [ + MultiFieldPanel( + [ + FieldPanel("slug"), + FieldPanel("seo_title"), + FieldPanel("search_description"), + ImageChooserPanel("search_image"), + HelpPanel(help.build(help.NO_SEO_TITLE)), + ], + gettext_lazy("Common page configuration"), + ), + ] + + settings_panels = [CommentPanel()] + + ### RELATIONS + + parent_page_types = ["elections2021.Elections2021HomePage"] + subpage_types = [] + + ### OTHERS + + class Meta: + verbose_name = "Programová aplikace" + + def serve(self, request): + if request.method == "POST": + form = ProgramAppForm(request.POST) + if form.is_valid(): + program_page = Elections2021ProgramPage.objects.live().first() + response = HttpResponseRedirect(program_page.get_my_program_url()) + response.set_signed_cookie( + settings.ELECTIONS2021_COOKIE_NAME, + json.dumps(form.cleaned_data), + secure=settings.SESSION_COOKIE_SECURE, + httponly=settings.SESSION_COOKIE_HTTPONLY, + ) + return response + return super().serve(request) diff --git a/elections2021/templates/elections2021/elections2021_program_app_page.html b/elections2021/templates/elections2021/elections2021_program_app_page.html new file mode 100644 index 0000000000000000000000000000000000000000..5688eadcb2c46e17d80e88076d33deccedc0a43b --- /dev/null +++ b/elections2021/templates/elections2021/elections2021_program_app_page.html @@ -0,0 +1,208 @@ +{% extends "elections2021/base.html" %} +{% load wagtailcore_tags wagtailimages_tags %} + +{% block content %} + +<main class="election-program-app"> + <div class="container container--default py-24"> + <header class="grid grid-cols-3"> + <h1 class="head-alt-xl text-white">{{ page.title }}</h1> + <i class="col-start-3 ico--app text-white text-right"></i> + </header> + <section class="election-program-app__body px-8 py-16 md:py-20 md:px-16 mt-16 elevation-6"> + <form method="post"> + {% csrf_token %} + + {% if page.info_text %} + <div class="grid grid-cols-4 mb-20 md:mb-40"> + <div class="col-span-4 md:col-span-1"> + <p class="head-xs font-bold">Základní informace</p> + </div> + <div class="col-span-4 md:col-span-3 md:pl-16 mt-5 md:mt-0"> + <p class="para">{{ page.info_text|richtext }}</p> + </div> + </div> + {% endif %} + + <div class="grid grid-cols-4"> + <div class="col-span-4 md:col-span-1"> + <p class="head-xs font-bold mt-5">Kolik vám je let?</p> + </div> + <div class="col-span-4 md:col-span-3 md:pl-16 mt-5 md:mt-0 inline-flex flex-wrap"> + <div class="radio program-radio form-field__control mb-2 mr-2"> + <input type="radio" name="age" id="18-29" value="18-29"> + <label for="18-29">18 - 29 LET</label> + </div> + + <div class="radio program-radio form-field__control mb-2 mr-2"> + <input type="radio" name="age" id="30-49" value="30-49" checked=""> + <label for="30-49">30 - 49 LET</label> + </div> + + <div class="radio program-radio form-field__control mb-2 mr-2"> + <input type="radio" name="age" id="50-64" value="50-64"> + <label for="50-64">50 - 64 LET</label> + </div> + + <div class="radio program-radio form-field__control mb-2 mr-2"> + <input type="radio" name="age" id="65-plus" value="65-plus"> + <label for="65-plus">65+ LET</label> + </div> + + <hr class="w-full"> + </div> + </div> + + <div class="grid grid-cols-4"> + <div class="col-span-4 md:col-span-1"> + <p class="head-xs font-bold">Vychováváte děti?</p> + </div> + <div class="col-span-4 md:col-span-3 md:pl-16 mt-5 md:mt-0 inline-flex flex-wrap"> + <div class="radio program-radio form-field__control mb-2 mr-2"> + <input type="radio" name="kids" id="ano" value="true" checked=""> + <label for="ano">ANO</label> + </div> + + <div class="radio program-radio form-field__control mb-2 mr-2"> + <input type="radio" name="kids" id="ne" value="false"> + <label for="ne">NE</label> + </div> + + <hr class="w-full"> + </div> + </div> + + <div class="grid grid-cols-4"> + <div class="col-span-4 md:col-span-1"> + <p class="head-xs font-bold">Jak jste na tom s penězi?</p> + </div> + <div class="col-span-4 md:col-span-3 md:pl-16 mt-5 md:mt-0 inline-flex flex-wrap"> + <div class="radio program-radio form-field__control mb-2 mr-2"> + <input type="radio" name="wealth" id="bad" value="bad"> + <label for="bad">NIC MOC</label> + </div> + + <div class="radio program-radio form-field__control mb-2 mr-2"> + <input type="radio" name="wealth" id="average" value="average" checked=""> + <label for="average">PRŮMĚRNĚ</label> + </div> + + <div class="radio program-radio form-field__control mb-2 mr-2"> + <input type="radio" name="wealth" id="good" value="good"> + <label for="good">SKVĚLE</label> + </div> + + <hr class="w-full"> + </div> + </div> + + <div class="grid grid-cols-4"> + <div class="col-span-4 md:col-span-1"> + <p class="head-xs font-bold">Máte nějaké povolání?</p> + </div> + <div class="col-span-4 md:col-span-3 md:pl-16 mt-5 md:mt-0 inline-flex flex-wrap"> + <div class="radio program-radio form-field__control mb-2 mr-2"> + <input type="radio" name="occupation" id="student" value="student"> + <label for="student">STUDUJI</label> + </div> + + <div class="radio program-radio form-field__control mb-2 mr-2"> + <input type="radio" name="occupation" id="working" value="working" checked=""> + <label for="working">PRACUJI</label> + </div> + + <div class="radio program-radio form-field__control mb-2 mr-2"> + <input type="radio" name="occupation" id="business" value="business"> + <label for="business">PODNIKÁM</label> + </div> + + <div class="radio program-radio form-field__control mb-2 mr-2"> + <input type="radio" name="occupation" id="parenting" value="parenting"> + <label for="parenting">NA RODIČOVSKÉ</label> + </div> + + <div class="radio program-radio form-field__control mb-2 mr-2"> + <input type="radio" name="occupation" id="retired" value="retired"> + <label for="retired">V DŮCHODU</label> + </div> + + <hr class="w-full"> + </div> + </div> + + <div class="grid grid-cols-4"> + <div class="col-span-4 md:col-span-1"> + <p class="head-xs font-bold">Jaké téma je vám blízké?</p> + </div> + <div class="col-span-4 md:col-span-3 md:pl-16 mt-5 md:mt-0 inline-flex flex-wrap"> + <div class="checkbox program-checkbox form-field__control mr-2 mb-2 "> + <label> + <input type="checkbox" id="nature" value="nature" name="topics"> <span>PŘÍRODA</span> + </label> + </div> + + <div class="checkbox program-checkbox form-field__control mr-2 mb-2 "> + <label> + <input type="checkbox" id="sport" value="sport" name="topics"> <span>SPORT</span> + </label> + </div> + + <div class="checkbox program-checkbox form-field__control mr-2 mb-2 "> + <label> + <input type="checkbox" id="health" value="health" name="topics"> <span>ZDRAVÍ</span> + </label> + </div> + + <div class="checkbox program-checkbox form-field__control mr-2 mb-2 "> + <label> + <input type="checkbox" id="culture" value="culture" name="topics"> <span>KULTURA</span> + </label> + </div> + + <div class="checkbox program-checkbox form-field__control mr-2 mb-2 "> + <label> + <input type="checkbox" id="technology" value="technology" name="topics"> <span>TECHNOLOGIE</span> + </label> + </div> + + <div class="checkbox program-checkbox form-field__control mr-2 mb-2 "> + <label> + <input type="checkbox" id="countryside" value="countryside" name="topics"> <span>REGIONY</span> + </label> + </div> + + <div class="checkbox program-checkbox form-field__control mr-2 mb-2 "> + <label> + <input type="checkbox" id="housing" value="housing" name="topics"> <span>BYDLENÍ</span> + </label> + </div> + + <div class="checkbox program-checkbox form-field__control mr-2 mb-2 "> + <label> + <input type="checkbox" id="education" value="education" name="topics"> <span>VZDĚLÁVÁNÍ</span> + </label> + </div> + + <hr class="w-full"> + </div> + </div> + + <div class="grid grid-cols-4"> + <div class="col-span-4 md:col-span-1"> + </div> + <div class="col-span-4 md:col-span-3 md:pl-16 mt-5 md:mt-0 inline-flex flex-wrap"> + <button class="btn max-w-max "> + <div class="btn__body bg-acidgreen py-3 px-5 md:py-5 md:px-16 text-black font-bold head-xs max-w-max w-max">Ukázat program na míru</div> + </button> + + <a href="{{ page.root_page.program_page_url }}" class="sm:ml-auto w-auto text-left md:text-right mb-4 md:mb-0 mt-6 "> + Přeskočit formulář <i class="ico--chevron-right"></i> + </a> + </div> + </div> + </form> + </section> + </div> +</main> + +{% endblock %} diff --git a/elections2021/templates/elections2021/elections2021_program_page.html b/elections2021/templates/elections2021/elections2021_program_page.html index e866279820446a853ac6c9325f0a867526d37749..8b54f54864df585bac33febee312e0830e7dc258 100644 --- a/elections2021/templates/elections2021/elections2021_program_page.html +++ b/elections2021/templates/elections2021/elections2021_program_page.html @@ -1,5 +1,5 @@ {% extends "elections2021/base.html" %} -{% load static wagtailcore_tags wagtailimages_tags %} +{% load static wagtailcore_tags wagtailimages_tags wagtailroutablepage_tags %} {% block content %} @@ -21,6 +21,8 @@ </article> {% elif active_ministry %} {% include "elections2021/_page_header.html" with title=title %} +{% elif active_my_program %} + {% include "elections2021/_page_header.html" with title=title %} {% else %} {% include "elections2021/_page_header.html" with title=page.title photo=page.photo %} {% endif %} @@ -30,57 +32,64 @@ <div class="container container--default pt-8 pb-16 lg:py-24"> <div class="text-center"> - {% comment %} - <!-- set data-chosen of #progamswitch container by serverside script based on current url--> - <div id="progamswitch" class="switch mb-4" data-chosen="mujprogram"> - <a class="switch__item switch__item--active" data-chosen="mujprogram">Program pro mě</a> - <a class="switch__item" data-chosen="uplnyprogram">Úplný program</a> - </div> - {% endcomment %} - - <div class="filters"> - <div class="select inline-flex w-auto {% if active_ministry %}text-black{% else %}text-white{% endif %}"> - <select id="select_ministry" class="select__control form-field__control bg-black block filter-pirati-stan" data-chosen="{{ active_ministry }}"> - <option value="x" {% if not active_ministry %}selected="selected"{% endif %}>Podle oblasti</option> - {% for val, name in ministry_choices %} - <option value="{{ val }}" {% if active_ministry == val %}selected="selected"{% endif %}>{{ name }}</option> - {% endfor %} - </select> + {% if has_my_program %} + <div id="progamswitch" class="switch mb-4"> + {% if active_my_program %} + <span class="switch__item switch__item--active">Program pro mě</span> + <a href="{% pageurl page %}" class="switch__item">Úplný program</a> + {% else %} + <a href="{% routablepageurl page "my_program" %}" class="switch__item">Program pro mě</a> + <cpan class="switch__item switch__item--active">Úplný program</span> + {% endif %} </div> - <div class="select inline-flex w-auto {% if active_plan %}text-black{% else %}text-white{% endif %}"> - <select id="select_plan" class="select__control form-field__control bg-black block filter-pirati-stan" data-chosen="{{ active_plan }}"> - <option value="x">Podle plánu</option> - {% for val, name in plan_choices %} - <option value="{{ val }}" {% if active_plan == val %}selected=""{% endif %}>{{ name }}</option> - {% endfor %} - </select> + {% endif %} + + {% if not active_my_program %} + <div class="filters"> + <div class="select inline-flex w-auto {% if active_ministry %}text-black{% else %}text-white{% endif %}"> + <select id="select_ministry" class="select__control form-field__control bg-black block filter-pirati-stan" data-chosen="{{ active_ministry }}"> + <option value="x" {% if not active_ministry %}selected="selected"{% endif %}>Podle oblasti</option> + {% for val, name in ministry_choices %} + <option value="{{ val }}" {% if active_ministry == val %}selected="selected"{% endif %}>{{ name }}</option> + {% endfor %} + </select> + </div> + <div class="select inline-flex w-auto {% if active_plan %}text-black{% else %}text-white{% endif %}"> + <select id="select_plan" class="select__control form-field__control bg-black block filter-pirati-stan" data-chosen="{{ active_plan }}"> + <option value="x">Podle plánu</option> + {% for val, name in plan_choices %} + <option value="{{ val }}" {% if active_plan == val %}selected=""{% endif %}>{{ name }}</option> + {% endfor %} + </select> + </div> </div> - </div> - </div> - <script> - var baseUrl = "{% pageurl page %}"; - var selectMinistry = document.getElementById("select_ministry"); - var selectPlan = document.getElementById("select_plan"); - - selectMinistry.addEventListener("change", function(e) { - if (this.value == "x") { - location.href = baseUrl; - } - else { - location.href = baseUrl + "resort/" + this.value + "/"; - } - }); - - selectPlan.addEventListener("change", function(e) { - if (this.value == "x") { - location.href = baseUrl; - } - else { - location.href = baseUrl + "plan/" + this.value + "/"; - } - }); - </script> + <script> + var baseUrl = "{% pageurl page %}"; + var selectMinistry = document.getElementById("select_ministry"); + var selectPlan = document.getElementById("select_plan"); + + selectMinistry.addEventListener("change", function(e) { + if (this.value == "x") { + location.href = baseUrl; + } + else { + location.href = baseUrl + "resort/" + this.value + "/"; + } + }); + + selectPlan.addEventListener("change", function(e) { + if (this.value == "x") { + location.href = baseUrl; + } + else { + location.href = baseUrl + "plan/" + this.value + "/"; + } + }); + </script> + {% endif %} + + </div> <h1 class="head-alt-md text-center py-8 lg:pt-24 lg:pb-8">Volební program</h1> diff --git a/elections2021/templatetags/elections2021_extras.py b/elections2021/templatetags/elections2021_extras.py index 12eebd468bd12ed1c7932831a9170b31af22e1f5..5c257f2e6e6fe1b15f1c0a255e160f495ae14049 100644 --- a/elections2021/templatetags/elections2021_extras.py +++ b/elections2021/templatetags/elections2021_extras.py @@ -13,8 +13,6 @@ def format_sources_block(value): soup = bs4.BeautifulSoup(value, "html.parser") out = [] - print(soup) - for item in soup.children: for a in item.find_all("a"): a["class"] = "text-fxactivecolor underline" diff --git a/elections2021/tests/__init__.py b/elections2021/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/elections2021/tests/test_utils.py b/elections2021/tests/test_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..c540aa7035ceebebc27b1e0b9fdc86f7e33dd89f --- /dev/null +++ b/elections2021/tests/test_utils.py @@ -0,0 +1,140 @@ +import pytest + +from ..constants import ( + AGE_18_29, + AGE_30_49, + AGE_50_64, + AGE_65_PLUS, + CHILDLESS, + MATURE, + OCCUPATION_BUSINESS, + OCCUPATION_PARENTING, + OCCUPATION_RETIRED, + OCCUPATION_STUDENT, + OCCUPATION_WORKING, + PARENTS, + SENIORS, + STUDENTS, + WEALTH_AVERAGE, + WEALTH_BAD, + WEALTH_GOOD, + WORKING_SENIORS, +) +from ..utils import get_archetype + +ALL_AGES = (AGE_18_29, AGE_30_49, AGE_50_64, AGE_65_PLUS) +ALL_OCCUPATIONS = ( + OCCUPATION_WORKING, + OCCUPATION_BUSINESS, + OCCUPATION_PARENTING, + OCCUPATION_RETIRED, + OCCUPATION_STUDENT, +) +ALL_BUT_STUDENT_OCCUPATIONS = ( + OCCUPATION_WORKING, + OCCUPATION_BUSINESS, + OCCUPATION_PARENTING, + OCCUPATION_RETIRED, +) +ALL_WEALTHS = (WEALTH_BAD, WEALTH_AVERAGE, WEALTH_GOOD) +ALL_KIDS = (True, False) + + +@pytest.mark.parametrize("age", (AGE_18_29, AGE_30_49)) +@pytest.mark.parametrize("wealth", ALL_WEALTHS) +@pytest.mark.parametrize("occupation", ALL_BUT_STUDENT_OCCUPATIONS) +def test_get_archetype__childless(age, wealth, occupation): + selection = { + "kids": False, + "age": age, + "wealth": wealth, + "occupation": occupation, + "topics": [], + } + assert get_archetype(selection) == CHILDLESS + + +@pytest.mark.parametrize("age", (AGE_18_29, AGE_30_49)) +@pytest.mark.parametrize("wealth", ALL_WEALTHS) +@pytest.mark.parametrize("occupation", ALL_BUT_STUDENT_OCCUPATIONS) +def test_get_archetype__parents(age, wealth, occupation): + selection = { + "kids": True, + "age": age, + "wealth": wealth, + "occupation": occupation, + "topics": [], + } + assert get_archetype(selection) == PARENTS + + +@pytest.mark.parametrize("kids", ALL_KIDS) +@pytest.mark.parametrize("wealth", ALL_WEALTHS) +@pytest.mark.parametrize("occupation", ALL_BUT_STUDENT_OCCUPATIONS) +def test_get_archetype__mature(kids, wealth, occupation): + selection = { + "age": AGE_50_64, + "kids": kids, + "wealth": wealth, + "occupation": occupation, + "topics": [], + } + assert get_archetype(selection) == MATURE + + +@pytest.mark.parametrize("kids", ALL_KIDS) +@pytest.mark.parametrize("wealth", ALL_WEALTHS) +@pytest.mark.parametrize( + "occupation", (OCCUPATION_BUSINESS, OCCUPATION_PARENTING, OCCUPATION_RETIRED) +) +def test_get_archetype__seniors(kids, wealth, occupation): + selection = { + "age": AGE_65_PLUS, + "kids": kids, + "wealth": wealth, + "occupation": occupation, + "topics": [], + } + assert get_archetype(selection) == SENIORS + + +@pytest.mark.parametrize("kids", ALL_KIDS) +@pytest.mark.parametrize("wealth", ALL_WEALTHS) +def test_get_archetype__working_seniors(kids, wealth): + selection = { + "age": AGE_65_PLUS, + "kids": kids, + "wealth": wealth, + "occupation": OCCUPATION_WORKING, + "topics": [], + } + assert get_archetype(selection) == WORKING_SENIORS + + +@pytest.mark.parametrize("age", ALL_AGES) +@pytest.mark.parametrize("kids", ALL_KIDS) +@pytest.mark.parametrize("wealth", ALL_WEALTHS) +def test_get_archetype__students(age, kids, wealth): + selection = { + "age": age, + "kids": kids, + "wealth": wealth, + "occupation": OCCUPATION_STUDENT, + "topics": [], + } + assert get_archetype(selection) == STUDENTS + + +@pytest.mark.parametrize("age", ALL_AGES) +@pytest.mark.parametrize("kids", ALL_KIDS) +@pytest.mark.parametrize("wealth", ALL_WEALTHS) +@pytest.mark.parametrize("occupation", ALL_OCCUPATIONS) +def test_get_archetype__not_missing_combination(age, kids, wealth, occupation): + selection = { + "age": age, + "kids": kids, + "wealth": wealth, + "occupation": occupation, + "topics": [], + } + assert get_archetype(selection) is not None diff --git a/elections2021/utils.py b/elections2021/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..6fc1d4964dcd51fb9b9be53fa557d63670c6b2ee --- /dev/null +++ b/elections2021/utils.py @@ -0,0 +1,30 @@ +from .constants import ( + AGE_18_29, + AGE_30_49, + AGE_50_64, + AGE_65_PLUS, + CHILDLESS, + MATURE, + OCCUPATION_STUDENT, + OCCUPATION_WORKING, + PARENTS, + SENIORS, + STUDENTS, + WORKING_SENIORS, +) + + +def get_archetype(selection): + if selection["occupation"] == OCCUPATION_STUDENT: + return STUDENTS + if selection["age"] == AGE_50_64: + return MATURE + if selection["age"] == AGE_65_PLUS: + if selection["occupation"] == OCCUPATION_WORKING: + return WORKING_SENIORS + return SENIORS + if selection["age"] in [AGE_18_29, AGE_30_49]: + if selection["kids"]: + return PARENTS + return CHILDLESS + return None diff --git a/majak/settings/base.py b/majak/settings/base.py index 1c338096dddbce5b922631b54dd780eaf7512c84..9b3653b198436fdcebd664b1d0627ca872293673 100644 --- a/majak/settings/base.py +++ b/majak/settings/base.py @@ -257,3 +257,5 @@ CZECH_INSPIRATIONAL_NEWSLETTER_ID = env.int( ) NALODENI_API_NEWS_SUBSCRIBE_URL = env.str("NALODENI_API_NEWS_SUBSCRIBE_URL", default="") NALODENI_API_CREDENTIALS = env.str("NALODENI_API_CREDENTIALS", default="") + +ELECTIONS2021_COOKIE_NAME = "program" diff --git a/requirements/base.in b/requirements/base.in index ce93dbec40607f2d97103a454f5ab3c3265b0e59..a90545dd621a5dbae407096cbdbfc96ef85eda6d 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -17,3 +17,4 @@ sentry-sdk Markdown beautifulsoup4 bleach +ipython diff --git a/requirements/base.txt b/requirements/base.txt index d517b603cb9d23459c015c3c52288138ad0b86f8..79713326181c8082f739dd1d322130d15aaa8d57 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -6,12 +6,16 @@ # anyascii==0.2.0 # via wagtail +appnope==0.1.2 + # via ipython arrow==0.14.7 # via # -r base.in # ics asgiref==3.3.4 # via django +backcall==0.2.0 + # via ipython beautifulsoup4==4.9.3 # via # -r base.in @@ -31,6 +35,8 @@ cryptography==3.4.7 # josepy # mozilla-django-oidc # pyopenssl +decorator==5.0.8 + # via ipython django-environ==0.4.5 # via -r base.in django-extensions==3.1.3 @@ -78,12 +84,20 @@ ics==0.7 # via -r base.in idna==2.10 # via requests +ipython-genutils==0.2.0 + # via traitlets +ipython==7.23.1 + # via -r base.in +jedi==0.18.0 + # via ipython josepy==1.8.0 # via mozilla-django-oidc l18n==2020.6.1 # via wagtail markdown==3.3.4 # via -r base.in +matplotlib-inline==0.1.2 + # via ipython mozilla-django-oidc==1.2.4 # via pirates numpy==1.20.3 @@ -94,16 +108,28 @@ openpyxl==3.0.7 # via tablib packaging==20.9 # via bleach +parso==0.8.2 + # via jedi +pexpect==4.8.0 + # via ipython +pickleshare==0.7.5 + # via ipython pillow==8.2.0 # via # django-simple-captcha # wagtail pirates==0.5.0 # via -r base.in +prompt-toolkit==3.0.18 + # via ipython psycopg2-binary==2.8.6 # via -r base.in +ptyprocess==0.7.0 + # via pexpect pycparser==2.20 # via cffi +pygments==2.9.0 + # via ipython pyopenssl==20.0.1 # via josepy pyparsing==2.4.7 @@ -146,6 +172,10 @@ tatsu==5.6.1 # via ics telepath==0.1.1 # via wagtail +traitlets==5.0.5 + # via + # ipython + # matplotlib-inline urllib3==1.26.4 # via # requests @@ -156,6 +186,8 @@ wagtail==2.13 # via # -r base.in # wagtail-metadata +wcwidth==0.2.5 + # via prompt-toolkit webencodings==0.5.1 # via # bleach