Initial commit

This commit is contained in:
Stefan Wehrmeyer 2022-02-15 21:09:09 +01:00
commit 19170142c3
20 changed files with 894 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
*.pyc
*.mo
*.egg-info/
__pycache__
.vscode/

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2022 FragDenStaat.de / Stefan Wehrmeyer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

3
MANIFEST.in Normal file
View file

@ -0,0 +1,3 @@
include README.md LICENSE
recursive-include froide_govplan/templates *
recursive-include froide_govplan/static *

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# Froide govplan
A Django app that allows tracking government plans.

View file

@ -0,0 +1,3 @@
__version__ = "0.0.1"
default_app_config = "froide_govplan.apps.FroideGovPlanConfig"

82
froide_govplan/admin.py Normal file
View file

@ -0,0 +1,82 @@
from django import forms
from django.contrib import admin
from django.urls import reverse_lazy
from tinymce.widgets import TinyMCE
from froide.helper.widgets import TagAutocompleteWidget
from .models import Government, GovernmentPlan, GovernmentPlanUpdate
class GovernmentPlanAdminForm(forms.ModelForm):
class Meta:
model = GovernmentPlan
fields = "__all__"
widgets = {
"categories": TagAutocompleteWidget(
autocomplete_url=reverse_lazy("api:category-autocomplete")
),
}
class GovernmentPlanUpdateAdminForm(forms.ModelForm):
class Meta:
model = GovernmentPlanUpdate
fields = "__all__"
widgets = {
"content": TinyMCE(attrs={'cols': 80, 'rows': 30})
}
class GovernmentAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("name",)}
list_display = ("name", "public", "start_date", "end_date")
list_filter = ("public",)
class GovernmentPlanAdmin(admin.ModelAdmin):
form = GovernmentPlanAdminForm
save_on_top = True
prepopulated_fields = {"slug": ("title",)}
raw_id_fields = ("responsible_publicbody",)
list_display = (
"title",
"public",
"status",
"rating",
)
list_filter = ("status", "rating", "public", "government")
class GovernmentPlanUpdateAdmin(admin.ModelAdmin):
form = GovernmentPlanUpdateAdminForm
raw_id_fields = ("user", "foirequest")
list_display = (
"plan",
"user",
"timestamp",
"status",
"rating",
"public",
)
list_filter = (
"status",
"public",
)
search_fields = ("title", "plan__title",)
date_hierarchy = "timestamp"
def get_queryset(self, request):
qs = super().get_queryset(request)
qs = qs.prefetch_related(
"plan",
"user",
)
return qs
admin.site.register(Government, GovernmentAdmin)
admin.site.register(GovernmentPlan, GovernmentPlanAdmin)
admin.site.register(GovernmentPlanUpdate, GovernmentPlanUpdateAdmin)

7
froide_govplan/apps.py Normal file
View file

@ -0,0 +1,7 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class FroideGovPlanConfig(AppConfig):
name = "froide_govplan"
verbose_name = _("GovPlan App")

View file

@ -0,0 +1,13 @@
from django.utils.translation import gettext_lazy as _
from cms.app_base import CMSApp
from cms.apphook_pool import apphook_pool
@apphook_pool.register
class GovPlanCMSApp(CMSApp):
name = _("GovPlan CMS App")
app_name = "govplan"
def get_urls(self, page=None, language=None, **kwargs):
return ["froide_govplan.urls"]

View file

@ -0,0 +1,24 @@
from django.utils.translation import gettext_lazy as _
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from .models import PLUGIN_TEMPLATES, GovernmentPlansCMSPlugin
@plugin_pool.register_plugin
class GovernmentPlansPlugin(CMSPluginBase):
name = _("Government plans")
model = GovernmentPlansCMSPlugin
filter_horizontal = ("categories",)
cache = True
def get_render_template(self, context, instance, placeholder):
return instance.template or PLUGIN_TEMPLATES[0][0]
def render(self, context, instance, placeholder):
context = super().render(context, instance, placeholder)
context["object_list"] = instance.get_plans(
context["request"], published_only=False
)
return context

View file

@ -0,0 +1,149 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-02-15 22:13+0100\n"
"PO-Revision-Date: 2022-02-15 22:18+0100\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Last-Translator: Stefan Wehrmeyer <stefan.wehrmeyer@okfn.de>\n"
"Language-Team: \n"
"X-Generator: Poedit 2.3\n"
#: apps.py:7
msgid "GovPlan App"
msgstr ""
#: cms_apps.py:9
msgid "GovPlan CMS App"
msgstr ""
#: cms_plugins.py:11 models.py:109
msgid "Government plans"
msgstr "Regierungsvorhaben"
#: models.py:23
msgid "not started"
msgstr "nicht begonnen"
#: models.py:24
msgid "started"
msgstr "begonnen"
#: models.py:25
msgid "partially implemented"
msgstr "teilweise umgesetzt"
#: models.py:26
msgid "implemented"
msgstr "umgesetzt"
#: models.py:27
msgid "deferred"
msgstr "verschoben"
#: models.py:31
msgid "terrible"
msgstr "sehr schlecht"
#: models.py:32
msgid "bad"
msgstr "schlecht"
#: models.py:33
msgid "OK"
msgstr "mittelmäßig"
#: models.py:34
msgid "good"
msgstr "gut"
#: models.py:35
msgid "excellent"
msgstr "sehr gut"
#: models.py:52
msgid "Government"
msgstr "Regierung"
#: models.py:53
msgid "Governments"
msgstr "Regierungen"
#: models.py:66
msgid "Categorized Government Plan"
msgstr "Kategorisiertes Regierungsvorhaben"
#: models.py:67
msgid "Categorized Government Plans"
msgstr "Kategorisierte Regierungsvorhaben"
#: models.py:79
msgid "image"
msgstr "Bild"
#: models.py:94 models.py:180
msgid "categories"
msgstr "Kategorien"
#: models.py:108
msgid "Government plan"
msgstr "Regierungsvorhaben"
#: models.py:160
msgid "Plan update"
msgstr "Entwicklung beim Vorhaben"
#: models.py:161
msgid "Plan updates"
msgstr "Entwicklungen beim Vorhaben"
#: models.py:170
msgid "Normal"
msgstr "Standard"
#: models.py:184
msgid "number of plans"
msgstr "Anzahl der Vorhaben"
#: models.py:184
msgid "0 means all the plans"
msgstr "0 heißt all Vorhaben"
#: models.py:187
msgid "offset"
msgstr "Start"
#: models.py:189
msgid "number of plans to skip from top of list"
msgstr "Anzahl der Vorhaben, die ausgelassen werden"
#: models.py:192
msgid "template"
msgstr "Template"
#: models.py:196
msgid "template used to display the plugin"
msgstr "Template, mit dem das Plugin angezeigt wird"
#: models.py:215
msgid "All matching plans"
msgstr "Alle zutreffenden Vorhaben"
#: models.py:216
#, python-format
msgid "%s matching plans"
msgstr "%s zutreffenden Vorhaben"
#: urls.py:10
msgctxt "url part"
msgid "<slug:gov>/plan/<slug:plan>/"
msgstr "<slug:gov>/vorhaben/<slug:plan>/"

View file

@ -0,0 +1,108 @@
# Generated by Django 3.2.8 on 2022-02-15 19:55
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import filer.fields.image
import taggit.managers
class Migration(migrations.Migration):
initial = True
dependencies = [
('publicbody', '0039_publicbody_alternative_emails'),
('auth', '0012_alter_user_first_name_max_length'),
('foirequest', '0054_alter_foirequest_options'),
migrations.swappable_dependency(settings.FILER_IMAGE_MODEL),
('organization', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='CategorizedGovernmentPlan',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
options={
'verbose_name': 'Categorized Government Plan',
'verbose_name_plural': 'Categorized Government Plans',
},
),
migrations.CreateModel(
name='Government',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('slug', models.SlugField(max_length=255, unique=True)),
('public', models.BooleanField(default=False)),
('description', models.TextField(blank=True)),
('start_date', models.DateField(blank=True, null=True)),
('end_date', models.DateField(blank=True, null=True)),
('planning_document', models.URLField(blank=True)),
('jurisdiction', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='publicbody.jurisdiction')),
],
options={
'verbose_name': 'Government',
'verbose_name_plural': 'Governments',
},
),
migrations.CreateModel(
name='GovernmentPlan',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('slug', models.SlugField(max_length=255, unique=True)),
('description', models.TextField(blank=True)),
('public', models.BooleanField(default=False)),
('status', models.CharField(choices=[('not_started', 'not started'), ('started', 'started'), ('partially_implemented', 'partially implemented'), ('implemented', 'implemented'), ('deferred', 'deferred')], default='needs_approval', max_length=25)),
('rating', models.IntegerField(blank=True, choices=[(1, 'terrible'), (2, 'bad'), (3, 'OK'), (4, 'good'), (5, 'excellent')], null=True)),
('reference', models.CharField(blank=True, max_length=255)),
('categories', taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='froide_govplan.CategorizedGovernmentPlan', to='publicbody.Category', verbose_name='categories')),
('government', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='froide_govplan.government')),
('group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='auth.group')),
('image', filer.fields.image.FilerImageField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.FILER_IMAGE_MODEL, verbose_name='image')),
('responsible_publicbody', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='publicbody.publicbody')),
],
options={
'verbose_name': 'Government plan',
'verbose_name_plural': 'Government plans',
'ordering': ('reference', 'title'),
},
),
migrations.CreateModel(
name='GovernmentPlanUpdate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
('title', models.CharField(blank=True, max_length=1024)),
('content', models.TextField(blank=True)),
('status', models.CharField(blank=True, choices=[('not_started', 'not started'), ('started', 'started'), ('partially_implemented', 'partially implemented'), ('implemented', 'implemented'), ('deferred', 'deferred')], default='', max_length=25)),
('rating', models.IntegerField(blank=True, choices=[(1, 'terrible'), (2, 'bad'), (3, 'OK'), (4, 'good'), (5, 'excellent')], null=True)),
('public', models.BooleanField(default=False)),
('foirequest', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='foirequest.foirequest')),
('organization', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='organization.organization')),
('plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='updates', to='froide_govplan.governmentplan')),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Plan update',
'verbose_name_plural': 'Plan updates',
'ordering': ('-timestamp',),
'get_latest_by': 'timestamp',
},
),
migrations.AddField(
model_name='categorizedgovernmentplan',
name='content_object',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='froide_govplan.governmentplan'),
),
migrations.AddField(
model_name='categorizedgovernmentplan',
name='tag',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='categorized_governmentplan', to='publicbody.category'),
),
]

View file

@ -0,0 +1,42 @@
# Generated by Django 3.2.8 on 2022-02-15 20:13
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('publicbody', '0039_publicbody_alternative_emails'),
('organization', '0001_initial'),
('cms', '0022_auto_20180620_1551'),
('froide_govplan', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='governmentplan',
name='organization',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='organization.organization'),
),
migrations.AddField(
model_name='governmentplanupdate',
name='url',
field=models.URLField(blank=True),
),
migrations.CreateModel(
name='GovernmentPlansCMSPlugin',
fields=[
('cmsplugin_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='froide_govplan_governmentplanscmsplugin', serialize=False, to='cms.cmsplugin')),
('count', models.PositiveIntegerField(default=1, help_text='0 means all the plans', verbose_name='number of plans')),
('offset', models.PositiveIntegerField(default=0, help_text='number of plans to skip from top of list', verbose_name='offset')),
('template', models.CharField(blank=True, choices=[('froide_govplan/plugins/default.html', 'Normal')], help_text='template used to display the plugin', max_length=250, verbose_name='template')),
('categories', models.ManyToManyField(blank=True, to='publicbody.Category', verbose_name='categories')),
('government', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='froide_govplan.government')),
],
options={
'abstract': False,
},
bases=('cms.cmsplugin',),
),
]

View file

243
froide_govplan/models.py Normal file
View file

@ -0,0 +1,243 @@
from django.conf import settings
from django.contrib.auth.models import Group
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from filer.fields.image import FilerImageField
from taggit.managers import TaggableManager
from taggit.models import TaggedItemBase
from froide.foirequest.models import FoiRequest
from froide.organization.models import Organization
from froide.publicbody.models import Category, Jurisdiction, PublicBody
try:
from cms.models.pluginmodel import CMSPlugin
except ImportError:
CMSPlugin = None
class PlanStatus(models.TextChoices):
NOT_STARTED = ("not_started", _("not started"))
STARTED = ("started", _("started"))
PARTIALLY_IMPLEMENTED = ("partially_implemented", _("partially implemented"))
IMPLEMENTED = ("implemented", _("implemented"))
DEFERRED = ("deferred", _("deferred"))
class PlanRating(models.IntegerChoices):
TERRIBLE = 1, _("terrible")
BAD = 2, _("bad")
OK = 3, _("OK")
GOOD = 4, _("good")
EXCELLENT = 5, _("excellent")
class Government(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
public = models.BooleanField(default=False)
jurisdiction = models.ForeignKey(Jurisdiction, null=True, on_delete=models.SET_NULL)
description = models.TextField(blank=True)
start_date = models.DateField(null=True, blank=True)
end_date = models.DateField(null=True, blank=True)
planning_document = models.URLField(blank=True)
class Meta:
verbose_name = _("Government")
verbose_name_plural = _("Governments")
def __str__(self):
return self.name
class CategorizedGovernmentPlan(TaggedItemBase):
tag = models.ForeignKey(
Category, on_delete=models.CASCADE, related_name="categorized_governmentplan"
)
content_object = models.ForeignKey("GovernmentPlan", on_delete=models.CASCADE)
class Meta:
verbose_name = _("Categorized Government Plan")
verbose_name_plural = _("Categorized Government Plans")
class GovernmentPlan(models.Model):
government = models.ForeignKey(Government, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
image = FilerImageField(
null=True,
blank=True,
default=None,
verbose_name=_("image"),
on_delete=models.SET_NULL,
)
description = models.TextField(blank=True)
public = models.BooleanField(default=False)
status = models.CharField(
max_length=25, choices=PlanStatus.choices, default="needs_approval"
)
rating = models.IntegerField(choices=PlanRating.choices, null=True, blank=True)
reference = models.CharField(max_length=255, blank=True)
categories = TaggableManager(
through=CategorizedGovernmentPlan, verbose_name=_("categories"), blank=True
)
responsible_publicbody = models.ForeignKey(
PublicBody, null=True, blank=True, on_delete=models.SET_NULL
)
organization = models.ForeignKey(
Organization, null=True, blank=True, on_delete=models.SET_NULL
)
group = models.ForeignKey(Group, null=True, blank=True, on_delete=models.SET_NULL)
class Meta:
ordering = ("reference", "title")
verbose_name = _("Government plan")
verbose_name_plural = _("Government plans")
def __str__(self):
return 'GovernmentPlan "%s" (#%s)' % (self.title, self.pk)
def get_absolute_url(self):
return reverse("govplan:plan", kwargs={
"gov": self.government.slug,
"plan": self.slug
})
def get_absolute_domain_url(self):
return settings.SITE_URL + self.get_absolute_url()
def get_reference_link(self):
if self.reference.startswith("https://"):
return self.reference
return "{}#{}".format(
self.government.planning_document,
self.reference
)
class GovernmentPlanUpdate(models.Model):
plan = models.ForeignKey(
GovernmentPlan, on_delete=models.CASCADE, related_name="updates"
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL
)
organization = models.ForeignKey(
Organization, null=True, blank=True, on_delete=models.SET_NULL
)
timestamp = models.DateTimeField(default=timezone.now)
title = models.CharField(max_length=1024, blank=True)
content = models.TextField(blank=True)
url = models.URLField(blank=True)
status = models.CharField(
max_length=25, choices=PlanStatus.choices, default="", blank=True
)
rating = models.IntegerField(choices=PlanRating.choices, null=True, blank=True)
public = models.BooleanField(default=False)
foirequest = models.ForeignKey(
FoiRequest, null=True, blank=True, on_delete=models.SET_NULL
)
class Meta:
ordering = ("-timestamp",)
get_latest_by = "timestamp"
verbose_name = _("Plan update")
verbose_name_plural = _("Plan updates")
def __str__(self):
return "Plan Update (%s)" % (self.pk,)
if CMSPlugin:
PLUGIN_TEMPLATES = [
("froide_govplan/plugins/default.html", _("Normal")),
]
class GovernmentPlansCMSPlugin(CMSPlugin):
"""
CMS Plugin for displaying latest articles
"""
government = models.ForeignKey(Government, null=True, blank=True, on_delete=models.SET_NULL)
categories = models.ManyToManyField(
Category, verbose_name=_("categories"), blank=True
)
count = models.PositiveIntegerField(
_("number of plans"), default=1, help_text=_("0 means all the plans")
)
offset = models.PositiveIntegerField(
_("offset"),
default=0,
help_text=_("number of plans to skip from top of list"),
)
template = models.CharField(
_("template"),
blank=True,
max_length=250,
choices=PLUGIN_TEMPLATES,
help_text=_("template used to display the plugin"),
)
@property
def render_template(self):
"""
Override render_template to use
the template_to_render attribute
"""
return self.template_to_render
def copy_relations(self, old_instance):
"""
Duplicate ManyToMany relations on plugin copy
"""
self.categories.set(old_instance.categories.all())
def __str__(self):
if self.count == 0:
return str(_("All matching plans"))
return _("%s matching plans") % self.count
def get_plans(self, request, published_only=True):
if (
published_only
or not request
or not getattr(request, "toolbar", False)
or not request.toolbar.edit_mode_active
):
plans = GovernmentPlan.objects.filter(public=True)
else:
plans = GovernmentPlan.objects.all()
filters = {}
if self.government:
filters["government"] = self.government
cat_list = self.categories.all().values_list("id", flat=True)
if cat_list:
filters["categories__in"] = cat_list
plans = plans.filter(**filters).distinct()
plans = plans.prefetch_related(
"categories", "government", "organization"
)
if self.count == 0:
return plans[self.offset:]
return plans[self.offset : self.offset + self.count]

View file

@ -0,0 +1,81 @@
{% extends CMS_TEMPLATE %}
{% load i18n %}
{% load markup %}
{% load cms_tags %}
{% block title %}{{ object.title }}{% endblock %}
{% block app_body %}
<div class="container mt-3 mb-5">
<div class="jumbotron">
<small class="badge badge-light">Vorhaben der {{ object.government.name }}</small>
<h1 class="display-4 mt-0">
{{ object.title }}
</h1>
</div>
{% if object.image %}
{# this should not be a dependency! #}
{% include "fds_blog/includes/_picture.html" with picture=object.image %}
{% endif %}
<p>Status: {{ object.get_status_display }}</p>
{% if object.rating %}
<p>Rating: {{ object.get_rating_display }}</p>
{% endif %}
{{ object.description | safe }}
<p>
<a href="{{ object.get_reference_link }}">Quelle</a>
</p>
{% if object.responsible_publicbody %}
<p>
Federführung:
<a href="{{ object.responsible_publicbody.get_absolute_url }}">
{{ object.responsible_publicbody.name }}
</a>
</p>
{% endif %}
{% if object.organization %}
<p>
Dieses Vorhaben wird beobachtet von
<a href="{{ object.organization.website }}">
{{ object.organization.name }}
</a>
</p>
{% endif %}
<h3>Neueste Entwicklungen</h3>
{% for update in updates %}
<h4>{{ update.title }}</h4>
<time>{{ update.timestamp|date:"DATE_FORMAT" }}</time>
<small>von {% if update.user %}{{ update.user.get_full_name }}{% endif %}{% if update.organization %}({{ update.organization.name }}){% endif %}
</small>
{{ update.content|markdown }}
{% if update.url %}
<a href="{{ update.url }}">Mehr lesen</a>
{% endif %}
{% if update.foirequest %}
<p>
Relevante Anfrage:
<a href="{{ update.foirequest.get_absolute_url }}">
{{ update.foirequest.title }}
</a>
</p>
{% endif %}
{% empty %}
Keine Entwicklungen
{% endfor %}
</div>
{% endblock %}

View file

@ -0,0 +1,9 @@
<ul>
{% for object in object_list %}
<li>
<a href="{{ object.get_absolute_url }}">
{{ object.title }}
</a>
</li>
{% endfor %}
</ul>

14
froide_govplan/urls.py Normal file
View file

@ -0,0 +1,14 @@
from django.urls import path
from django.utils.translation import pgettext_lazy
from .views import GovPlanDetailView
app_name = "govplan"
urlpatterns = [
path(
pgettext_lazy("url part", "<slug:gov>/plan/<slug:plan>/"),
GovPlanDetailView.as_view(),
name="plan",
),
]

18
froide_govplan/views.py Normal file
View file

@ -0,0 +1,18 @@
from django.views.generic import DetailView
from .models import GovernmentPlan
class GovPlanDetailView(DetailView):
slug_url_kwarg = "plan"
template_name = "froide_govplan/detail.html"
def get_queryset(self):
return GovernmentPlan.objects.filter(public=True)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["updates"] = self.object.updates.filter(public=True).order_by(
"-timestamp"
)
return context

17
setup.cfg Normal file
View file

@ -0,0 +1,17 @@
[wheel]
universal = 1
[flake8]
extend-ignore = E203,E501,C901
max-line-length = 88
select = C,E,F,W,B,B950
exclude = .git,__pycache__,docs/source/conf.py,old,build,dist
max-complexity = 10
[isort]
profile = black
src_paths = froide
default_section = THIRDPARTY
known_first_party = froide
known_django = django
sections = FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER

52
setup.py Normal file
View file

@ -0,0 +1,52 @@
#!/usr/bin/env python
from __future__ import print_function
import os
import re
import codecs
from setuptools import setup, find_packages
def read(*parts):
filename = os.path.join(os.path.dirname(__file__), *parts)
with codecs.open(filename, encoding='utf-8') as fp:
return fp.read()
def find_version(*file_paths):
version_file = read(*file_paths)
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError("Unable to find version string.")
setup(
name="froide_govplan",
version=find_version("froide_govplan", "__init__.py"),
url='https://github.com/okfde/froide-govplan',
license='MIT',
description="Froide govplan app",
long_description=read('README.md'),
author='Stefan Wehrmeyer',
author_email='mail@stefanwehrmeyer.com',
packages=find_packages(),
install_requires=[
'froide',
],
include_package_data=True,
classifiers=[
'Development Status :: 5 - Production/Stable',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Topic :: Utilities',
],
zip_safe=False,
)