Initial commit
This commit is contained in:
commit
19170142c3
20 changed files with 894 additions and 0 deletions
3
froide_govplan/__init__.py
Normal file
3
froide_govplan/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
__version__ = "0.0.1"
|
||||
|
||||
default_app_config = "froide_govplan.apps.FroideGovPlanConfig"
|
||||
82
froide_govplan/admin.py
Normal file
82
froide_govplan/admin.py
Normal 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
7
froide_govplan/apps.py
Normal 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")
|
||||
13
froide_govplan/cms_apps.py
Normal file
13
froide_govplan/cms_apps.py
Normal 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"]
|
||||
24
froide_govplan/cms_plugins.py
Normal file
24
froide_govplan/cms_plugins.py
Normal 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
|
||||
149
froide_govplan/locale/de/LC_MESSAGES/django.po
Normal file
149
froide_govplan/locale/de/LC_MESSAGES/django.po
Normal 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>/"
|
||||
108
froide_govplan/migrations/0001_initial.py
Normal file
108
froide_govplan/migrations/0001_initial.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
42
froide_govplan/migrations/0002_auto_20220215_2113.py
Normal file
42
froide_govplan/migrations/0002_auto_20220215_2113.py
Normal 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',),
|
||||
),
|
||||
]
|
||||
0
froide_govplan/migrations/__init__.py
Normal file
0
froide_govplan/migrations/__init__.py
Normal file
243
froide_govplan/models.py
Normal file
243
froide_govplan/models.py
Normal 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]
|
||||
81
froide_govplan/templates/froide_govplan/detail.html
Normal file
81
froide_govplan/templates/froide_govplan/detail.html
Normal 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 %}
|
||||
|
|
@ -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
14
froide_govplan/urls.py
Normal 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
18
froide_govplan/views.py
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue