Add make request button to plan page
This commit is contained in:
parent
3051ee51f5
commit
0bfceca2e7
5 changed files with 186 additions and 110 deletions
|
|
@ -4,7 +4,7 @@ import json
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from ...models import Government
|
from ...models import Government
|
||||||
from ...utils import PlanImporter
|
from ...plan_importer import PlanImporter
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import functools
|
import functools
|
||||||
import re
|
import re
|
||||||
|
from datetime import timedelta
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
@ -19,6 +20,8 @@ from froide.follow.models import Follower
|
||||||
from froide.organization.models import Organization
|
from froide.organization.models import Organization
|
||||||
from froide.publicbody.models import Category, Jurisdiction, PublicBody
|
from froide.publicbody.models import Category, Jurisdiction, PublicBody
|
||||||
|
|
||||||
|
from .utils import PLAN_TAG_PREFIX, TAG_NAME, make_request_url
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cms.models.fields import PlaceholderField
|
from cms.models.fields import PlaceholderField
|
||||||
from cms.models.pluginmodel import CMSPlugin
|
from cms.models.pluginmodel import CMSPlugin
|
||||||
|
|
@ -232,6 +235,35 @@ class GovernmentPlan(models.Model):
|
||||||
def get_status_css(self):
|
def get_status_css(self):
|
||||||
return STATUS_CSS.get(self.status, "")
|
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):
|
class GovernmentPlanUpdate(models.Model):
|
||||||
plan = models.ForeignKey(
|
plan = models.ForeignKey(
|
||||||
|
|
|
||||||
115
froide_govplan/plan_importer.py
Normal file
115
froide_govplan/plan_importer.py
Normal file
|
|
@ -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
|
||||||
|
|
@ -82,6 +82,21 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-12 col-md-5 col-lg-3 mt-5 mt-md-0">
|
<div class="col col-12 col-md-5 col-lg-3 mt-5 mt-md-0">
|
||||||
|
|
||||||
|
{% if object.responsible_publicbody %}
|
||||||
|
<div class="mb-3">
|
||||||
|
{% if not object.has_recent_foirequest %}
|
||||||
|
<a href="{{ object.make_request_url }}" target="_blank" class="btn btn-primary">
|
||||||
|
Anfrage zum Vorhaben stellen
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
{% with foirequest=object.get_recent_foirequest %}
|
||||||
|
{% include "foirequest/snippets/request_item.html" with object=foirequest %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
{% if object.rating %}
|
{% if object.rating %}
|
||||||
<dt>Bewertung</dt>
|
<dt>Bewertung</dt>
|
||||||
|
|
|
||||||
|
|
@ -1,115 +1,29 @@
|
||||||
import datetime
|
from urllib.parse import quote, urlencode
|
||||||
import re
|
|
||||||
|
|
||||||
from django.template.defaultfilters import slugify
|
from django.conf import settings
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
from froide.publicbody.models import Category, PublicBody
|
TAG_NAME = "Koalitionstracker"
|
||||||
|
PLAN_TAG_PREFIX = "Vorhaben-"
|
||||||
from .models import GovernmentPlan, GovernmentPlanSection
|
|
||||||
|
|
||||||
|
|
||||||
class PlanImporter(object):
|
def make_request_url(plan, publicbody):
|
||||||
def __init__(self, government, col_mapping=None):
|
pb_slug = publicbody.slug
|
||||||
if col_mapping is None:
|
url = reverse("foirequest-make_request", kwargs={"publicbody_slug": pb_slug})
|
||||||
col_mapping = {}
|
subject = "Stand des Regierungsvorhabens „{}“".format(plan.title)
|
||||||
self.col_mapping = col_mapping
|
if len(subject) > 250:
|
||||||
self.government = government
|
subject = subject[:250] + "..."
|
||||||
self.post_save_list = []
|
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):
|
hide_features = ["hide_public", "hide_similar", "hide_draft"]
|
||||||
for row in reader:
|
|
||||||
self.import_row(row)
|
|
||||||
|
|
||||||
def import_row(self, row):
|
query.update({f: b"1" for f in hide_features})
|
||||||
print("importing", row)
|
query = urlencode(query, quote_via=quote)
|
||||||
title = row[self.col_mapping["title"]]
|
return "%s%s?%s" % (settings.SITE_URL, url, query)
|
||||||
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
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue