From 0bfceca2e766465f486e3c1e1cbec6f85024a47a Mon Sep 17 00:00:00 2001 From: Stefan Wehrmeyer Date: Tue, 15 Mar 2022 14:35:16 +0100 Subject: [PATCH] Add make request button to plan page --- .../management/commands/import_govplan.py | 2 +- froide_govplan/models.py | 32 +++++ froide_govplan/plan_importer.py | 115 +++++++++++++++ .../templates/froide_govplan/detail.html | 15 ++ froide_govplan/utils.py | 132 +++--------------- 5 files changed, 186 insertions(+), 110 deletions(-) create mode 100644 froide_govplan/plan_importer.py diff --git a/froide_govplan/management/commands/import_govplan.py b/froide_govplan/management/commands/import_govplan.py index 803d58e..ccc092c 100644 --- a/froide_govplan/management/commands/import_govplan.py +++ b/froide_govplan/management/commands/import_govplan.py @@ -4,7 +4,7 @@ import json from django.core.management.base import BaseCommand from ...models import Government -from ...utils import PlanImporter +from ...plan_importer import PlanImporter class Command(BaseCommand): diff --git a/froide_govplan/models.py b/froide_govplan/models.py index 2b01ed0..fb2ac85 100644 --- a/froide_govplan/models.py +++ b/froide_govplan/models.py @@ -1,5 +1,6 @@ import functools import re +from datetime import timedelta from urllib.parse import urlparse from django.conf import settings @@ -19,6 +20,8 @@ from froide.follow.models import Follower from froide.organization.models import Organization from froide.publicbody.models import Category, Jurisdiction, PublicBody +from .utils import PLAN_TAG_PREFIX, TAG_NAME, make_request_url + try: from cms.models.fields import PlaceholderField from cms.models.pluginmodel import CMSPlugin @@ -232,6 +235,35 @@ class GovernmentPlan(models.Model): def get_status_css(self): return STATUS_CSS.get(self.status, "") + def make_request_url(self): + if not self.responsible_publicbody: + return [] + return make_request_url(self, self.responsible_publicbody) + + def has_recent_foirequest(self): + frs = self.get_related_foirequests() + ago = timezone.now() - timedelta(days=90) + return any(fr.first_message > ago for fr in frs) + + def get_recent_foirequest(self): + return self.get_related_foirequests()[0] + + def get_related_foirequests(self): + if not self.responsible_publicbody: + return [] + if hasattr(self, "_related_foirequests"): + return self._related_foirequests + self._related_foirequests = ( + FoiRequest.objects.filter( + visibility=FoiRequest.VISIBILITY.VISIBLE_TO_PUBLIC, + public_body=self.responsible_publicbody, + ) + .filter(tags__name=TAG_NAME) + .filter(tags__name="{}{}".format(PLAN_TAG_PREFIX, self.slug)) + .order_by("-first_message") + ) + return self._related_foirequests + class GovernmentPlanUpdate(models.Model): plan = models.ForeignKey( diff --git a/froide_govplan/plan_importer.py b/froide_govplan/plan_importer.py new file mode 100644 index 0000000..e530de8 --- /dev/null +++ b/froide_govplan/plan_importer.py @@ -0,0 +1,115 @@ +import datetime +import re + +from django.template.defaultfilters import slugify + +from froide.publicbody.models import Category, PublicBody + +from .models import GovernmentPlan, GovernmentPlanSection + + +class PlanImporter(object): + def __init__(self, government, col_mapping=None): + if col_mapping is None: + col_mapping = {} + self.col_mapping = col_mapping + self.government = government + self.post_save_list = [] + + def import_rows(self, reader): + for row in reader: + self.import_row(row) + + def import_row(self, row): + print("importing", row) + title = row[self.col_mapping["title"]] + if not title: + return + plan = GovernmentPlan.objects.filter( + government=self.government, title=title + ).first() + + if not plan: + plan = GovernmentPlan(government=self.government) + + self.post_save_list = [] + for col, row_col in self.col_mapping.items(): + method_name = "handle_{}".format(col) + if hasattr(self, method_name): + getattr(self, method_name)(plan, row[row_col]) + else: + setattr(plan, col, row[row_col]) + plan.save() + for func in self.post_save_list: + func(plan) + + def handle_title(self, plan, title): + plan.title = title + plan.slug = slugify(title) + + def handle_categories(self, plan, category_name): + categories = [ + x.strip() for x in re.split(r", | & | und ", category_name) if x.strip() + ] + self.make_section(category_name, "-".join(categories), categories) + if categories: + self.post_save_list.append(lambda p: p.categories.set(*categories)) + + def make_section(self, section_name, section_slug, categories): + slug = slugify(section_slug) + section, _created = GovernmentPlanSection.objects.get_or_create( + slug=slug, + defaults={ + "government": self.government, + "title": section_name, + }, + ) + section.categories.set([self.get_category(c) for c in categories]) + + def get_category(self, cat_name): + return Category.objects.get(name=cat_name) + + def handle_reference(self, plan, reference): + plan.reference = ", ".join(re.split(r"\s*[,/]\s*", reference)) + + def handle_responsible_publicbody(self, plan, pb): + if not pb.strip(): + return + pb = PublicBody.objects.get( + jurisdiction=self.government.jurisdiction, + other_names__iregex=r"(\W|^){}(\W|$)".format(pb), + ) + plan.responsible_publicbody = pb + + def handle_due_date(self, plan, date_descr): + if not date_descr.strip(): + return + + def parse_date(date_descr): + match = re.search(r"(\d{4})", date_descr) + if not match: + return + year = int(match.group(1)) + if "Mitte" in date_descr: + return datetime.date(year, 7, 1) + if "Ende" in date_descr: + return datetime.date(year, 12, 1) + if "Anfang" in date_descr: + return datetime.date(year, 3, 1) + if "Innerhalb" in date_descr: + return datetime.date(year, 12, 31) + if "Juni" in date_descr: + return datetime.date(year, 6, 1) + return datetime.date(year, 1, 1) + + plan.due_date = parse_date(date_descr) + + def handle_status(self, plan, status): + status = status.strip() + if not status or status == "noch nicht umgesetzt": + status = "not_started" + if status == "umgesetzt": + status = "implemented" + if status == "begonnen": + status = "started" + plan.status = status diff --git a/froide_govplan/templates/froide_govplan/detail.html b/froide_govplan/templates/froide_govplan/detail.html index c178e59..10b8902 100644 --- a/froide_govplan/templates/froide_govplan/detail.html +++ b/froide_govplan/templates/froide_govplan/detail.html @@ -82,6 +82,21 @@ {% endif %}
+ + {% if object.responsible_publicbody %} +
+ {% if not object.has_recent_foirequest %} + + Anfrage zum Vorhaben stellen + + {% else %} + {% with foirequest=object.get_recent_foirequest %} + {% include "foirequest/snippets/request_item.html" with object=foirequest %} + {% endwith %} + {% endif %} +
+ {% endif %} +
{% if object.rating %}
Bewertung
diff --git a/froide_govplan/utils.py b/froide_govplan/utils.py index e530de8..69c4bad 100644 --- a/froide_govplan/utils.py +++ b/froide_govplan/utils.py @@ -1,115 +1,29 @@ -import datetime -import re +from urllib.parse import quote, urlencode -from django.template.defaultfilters import slugify +from django.conf import settings +from django.urls import reverse -from froide.publicbody.models import Category, PublicBody - -from .models import GovernmentPlan, GovernmentPlanSection +TAG_NAME = "Koalitionstracker" +PLAN_TAG_PREFIX = "Vorhaben-" -class PlanImporter(object): - def __init__(self, government, col_mapping=None): - if col_mapping is None: - col_mapping = {} - self.col_mapping = col_mapping - self.government = government - self.post_save_list = [] +def make_request_url(plan, publicbody): + pb_slug = publicbody.slug + url = reverse("foirequest-make_request", kwargs={"publicbody_slug": pb_slug}) + subject = "Stand des Regierungsvorhabens „{}“".format(plan.title) + if len(subject) > 250: + subject = subject[:250] + "..." + body = "Dokumente, die den Stand des Regierungsvorhabens „{}“ (siehe Koalitionsvertrag), dokumentieren.".format( + plan.title + ) + query = { + "subject": subject.encode("utf-8"), + "body": body, + "tags": "{},{}{}".format(TAG_NAME, PLAN_TAG_PREFIX, plan.slug), + } - def import_rows(self, reader): - for row in reader: - self.import_row(row) + hide_features = ["hide_public", "hide_similar", "hide_draft"] - def import_row(self, row): - print("importing", row) - title = row[self.col_mapping["title"]] - if not title: - return - plan = GovernmentPlan.objects.filter( - government=self.government, title=title - ).first() - - if not plan: - plan = GovernmentPlan(government=self.government) - - self.post_save_list = [] - for col, row_col in self.col_mapping.items(): - method_name = "handle_{}".format(col) - if hasattr(self, method_name): - getattr(self, method_name)(plan, row[row_col]) - else: - setattr(plan, col, row[row_col]) - plan.save() - for func in self.post_save_list: - func(plan) - - def handle_title(self, plan, title): - plan.title = title - plan.slug = slugify(title) - - def handle_categories(self, plan, category_name): - categories = [ - x.strip() for x in re.split(r", | & | und ", category_name) if x.strip() - ] - self.make_section(category_name, "-".join(categories), categories) - if categories: - self.post_save_list.append(lambda p: p.categories.set(*categories)) - - def make_section(self, section_name, section_slug, categories): - slug = slugify(section_slug) - section, _created = GovernmentPlanSection.objects.get_or_create( - slug=slug, - defaults={ - "government": self.government, - "title": section_name, - }, - ) - section.categories.set([self.get_category(c) for c in categories]) - - def get_category(self, cat_name): - return Category.objects.get(name=cat_name) - - def handle_reference(self, plan, reference): - plan.reference = ", ".join(re.split(r"\s*[,/]\s*", reference)) - - def handle_responsible_publicbody(self, plan, pb): - if not pb.strip(): - return - pb = PublicBody.objects.get( - jurisdiction=self.government.jurisdiction, - other_names__iregex=r"(\W|^){}(\W|$)".format(pb), - ) - plan.responsible_publicbody = pb - - def handle_due_date(self, plan, date_descr): - if not date_descr.strip(): - return - - def parse_date(date_descr): - match = re.search(r"(\d{4})", date_descr) - if not match: - return - year = int(match.group(1)) - if "Mitte" in date_descr: - return datetime.date(year, 7, 1) - if "Ende" in date_descr: - return datetime.date(year, 12, 1) - if "Anfang" in date_descr: - return datetime.date(year, 3, 1) - if "Innerhalb" in date_descr: - return datetime.date(year, 12, 31) - if "Juni" in date_descr: - return datetime.date(year, 6, 1) - return datetime.date(year, 1, 1) - - plan.due_date = parse_date(date_descr) - - def handle_status(self, plan, status): - status = status.strip() - if not status or status == "noch nicht umgesetzt": - status = "not_started" - if status == "umgesetzt": - status = "implemented" - if status == "begonnen": - status = "started" - plan.status = status + query.update({f: b"1" for f in hide_features}) + query = urlencode(query, quote_via=quote) + return "%s%s?%s" % (settings.SITE_URL, url, query)