Add GovernmentPlanSection
This commit is contained in:
parent
9f983a1723
commit
947426a3b3
7 changed files with 214 additions and 10 deletions
|
|
@ -3,11 +3,17 @@ from django.contrib import admin, auth
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from adminsortable2.admin import SortableAdminMixin
|
||||||
from tinymce.widgets import TinyMCE
|
from tinymce.widgets import TinyMCE
|
||||||
|
|
||||||
from froide.helper.widgets import TagAutocompleteWidget
|
from froide.helper.widgets import TagAutocompleteWidget
|
||||||
|
|
||||||
from .models import Government, GovernmentPlan, GovernmentPlanUpdate
|
from .models import (
|
||||||
|
Government,
|
||||||
|
GovernmentPlan,
|
||||||
|
GovernmentPlanSection,
|
||||||
|
GovernmentPlanUpdate,
|
||||||
|
)
|
||||||
|
|
||||||
User = auth.get_user_model()
|
User = auth.get_user_model()
|
||||||
|
|
||||||
|
|
@ -208,9 +214,26 @@ class GovernmentPlanUpdateAdmin(admin.ModelAdmin):
|
||||||
return super().has_change_permission(request, obj=obj)
|
return super().has_change_permission(request, obj=obj)
|
||||||
|
|
||||||
|
|
||||||
|
class GovernmentPlanSectionAdmin(SortableAdminMixin, admin.ModelAdmin):
|
||||||
|
save_on_top = True
|
||||||
|
prepopulated_fields = {"slug": ("title",)}
|
||||||
|
search_fields = ("title",)
|
||||||
|
raw_id_fields = ("categories",)
|
||||||
|
list_display = (
|
||||||
|
"title",
|
||||||
|
"featured",
|
||||||
|
)
|
||||||
|
list_filter = (
|
||||||
|
"featured",
|
||||||
|
"categories",
|
||||||
|
"government",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Government, GovernmentAdmin)
|
admin.site.register(Government, GovernmentAdmin)
|
||||||
admin.site.register(GovernmentPlan, GovernmentPlanAdmin)
|
admin.site.register(GovernmentPlan, GovernmentPlanAdmin)
|
||||||
admin.site.register(GovernmentPlanUpdate, GovernmentPlanUpdateAdmin)
|
admin.site.register(GovernmentPlanUpdate, GovernmentPlanUpdateAdmin)
|
||||||
|
admin.site.register(GovernmentPlanSection, GovernmentPlanSectionAdmin)
|
||||||
|
|
||||||
govplan_admin_site = GovPlanAdminSite(name="govplan")
|
govplan_admin_site = GovPlanAdminSite(name="govplan")
|
||||||
govplan_admin_site.register(GovernmentPlan, GovernmentPlanAdmin)
|
govplan_admin_site.register(GovernmentPlan, GovernmentPlanAdmin)
|
||||||
|
|
|
||||||
41
froide_govplan/migrations/0005_governmentplansection.py
Normal file
41
froide_govplan/migrations/0005_governmentplansection.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Generated by Django 3.2.12 on 2022-03-14 10:14
|
||||||
|
|
||||||
|
import cms.models.fields
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import filer.fields.image
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('publicbody', '0039_publicbody_alternative_emails'),
|
||||||
|
migrations.swappable_dependency(settings.FILER_IMAGE_MODEL),
|
||||||
|
('cms', '0022_auto_20180620_1551'),
|
||||||
|
('froide_govplan', '0004_auto_20220311_2330'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GovernmentPlanSection',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('title', models.CharField(max_length=255, verbose_name='title')),
|
||||||
|
('slug', models.SlugField(max_length=255, unique=True, verbose_name='slug')),
|
||||||
|
('description', models.TextField(blank=True, verbose_name='description')),
|
||||||
|
('icon', models.CharField(blank=True, help_text='Enter an icon name from the <a href="https://fontawesome.com/v4.7.0/icons/" target="_blank">FontAwesome 4 icon set</a>', max_length=50, verbose_name='Icon')),
|
||||||
|
('order', models.PositiveIntegerField(default=0)),
|
||||||
|
('featured', models.DateTimeField(blank=True, null=True)),
|
||||||
|
('categories', models.ManyToManyField(blank=True, to='publicbody.Category')),
|
||||||
|
('content_placeholder', cms.models.fields.PlaceholderField(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, slotname='content', to='cms.placeholder')),
|
||||||
|
('government', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='froide_govplan.government', verbose_name='government')),
|
||||||
|
('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')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Government plan section',
|
||||||
|
'verbose_name_plural': 'Government plan sections',
|
||||||
|
'ordering': ('order', 'title'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -15,9 +15,11 @@ from froide.organization.models import Organization
|
||||||
from froide.publicbody.models import Category, Jurisdiction, PublicBody
|
from froide.publicbody.models import Category, Jurisdiction, PublicBody
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
from cms.models.fields import PlaceholderField
|
||||||
from cms.models.pluginmodel import CMSPlugin
|
from cms.models.pluginmodel import CMSPlugin
|
||||||
except ImportError:
|
except ImportError:
|
||||||
CMSPlugin = None
|
CMSPlugin = None
|
||||||
|
PlaceholderField = None
|
||||||
|
|
||||||
|
|
||||||
class PlanStatus(models.TextChoices):
|
class PlanStatus(models.TextChoices):
|
||||||
|
|
@ -251,6 +253,60 @@ class GovernmentPlanFollower(Follower):
|
||||||
verbose_name_plural = _("Government plan followers")
|
verbose_name_plural = _("Government plan followers")
|
||||||
|
|
||||||
|
|
||||||
|
class GovernmentPlanSection(models.Model):
|
||||||
|
government = models.ForeignKey(
|
||||||
|
Government, on_delete=models.CASCADE, verbose_name=_("government")
|
||||||
|
)
|
||||||
|
|
||||||
|
title = models.CharField(max_length=255, verbose_name=_("title"))
|
||||||
|
slug = models.SlugField(max_length=255, unique=True, verbose_name=_("slug"))
|
||||||
|
|
||||||
|
categories = models.ManyToManyField(Category, blank=True)
|
||||||
|
|
||||||
|
description = models.TextField(blank=True, verbose_name=_("description"))
|
||||||
|
image = FilerImageField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
verbose_name=_("image"),
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
)
|
||||||
|
|
||||||
|
icon = models.CharField(
|
||||||
|
_("Icon"),
|
||||||
|
max_length=50,
|
||||||
|
blank=True,
|
||||||
|
help_text=_(
|
||||||
|
"""Enter an icon name from the <a href="https://fontawesome.com/v4.7.0/icons/" target="_blank">FontAwesome 4 icon set</a>"""
|
||||||
|
),
|
||||||
|
)
|
||||||
|
order = models.PositiveIntegerField(default=0)
|
||||||
|
featured = models.DateTimeField(null=True, blank=True)
|
||||||
|
|
||||||
|
if PlaceholderField:
|
||||||
|
content_placeholder = PlaceholderField("content")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Government plan section")
|
||||||
|
verbose_name_plural = _("Government plan sections")
|
||||||
|
ordering = (
|
||||||
|
"order",
|
||||||
|
"title",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse(
|
||||||
|
"govplan:section",
|
||||||
|
kwargs={"gov": self.government.slug, "section": self.slug},
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_absolute_domain_url(self):
|
||||||
|
return settings.SITE_URL + self.get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
if CMSPlugin:
|
if CMSPlugin:
|
||||||
|
|
||||||
PLUGIN_TEMPLATES = [
|
PLUGIN_TEMPLATES = [
|
||||||
|
|
|
||||||
29
froide_govplan/templates/froide_govplan/section.html
Normal file
29
froide_govplan/templates/froide_govplan/section.html
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{% extends CMS_TEMPLATE %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% load markup %}
|
||||||
|
{% load cms_tags %}
|
||||||
|
{% load follow_tags %}
|
||||||
|
|
||||||
|
{% block title %}{{ object.title }}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block app_body %}
|
||||||
|
<div class="container mt-3 mb-5">
|
||||||
|
|
||||||
|
<div class="jumbotron">
|
||||||
|
<small class="badge badge-light">{{ 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 %}
|
||||||
|
|
||||||
|
{% render_placeholder object.content_placeholder %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -2,7 +2,7 @@ from django.urls import path
|
||||||
from django.utils.translation import pgettext_lazy
|
from django.utils.translation import pgettext_lazy
|
||||||
|
|
||||||
from .admin import govplan_admin_site
|
from .admin import govplan_admin_site
|
||||||
from .views import GovPlanDetailView
|
from .views import GovPlanDetailView, GovPlanSectionDetailView
|
||||||
|
|
||||||
app_name = "govplan"
|
app_name = "govplan"
|
||||||
|
|
||||||
|
|
@ -13,4 +13,9 @@ urlpatterns = [
|
||||||
GovPlanDetailView.as_view(),
|
GovPlanDetailView.as_view(),
|
||||||
name="plan",
|
name="plan",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
pgettext_lazy("url part", "<slug:gov>/<slug:section>/"),
|
||||||
|
GovPlanSectionDetailView.as_view(),
|
||||||
|
name="section",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ import re
|
||||||
|
|
||||||
from django.template.defaultfilters import slugify
|
from django.template.defaultfilters import slugify
|
||||||
|
|
||||||
from froide.publicbody.models import PublicBody
|
from froide.publicbody.models import Category, PublicBody
|
||||||
|
|
||||||
from .models import GovernmentPlan
|
from .models import GovernmentPlan, GovernmentPlanSection
|
||||||
|
|
||||||
|
|
||||||
class PlanImporter(object):
|
class PlanImporter(object):
|
||||||
|
|
@ -47,13 +47,28 @@ class PlanImporter(object):
|
||||||
plan.title = title
|
plan.title = title
|
||||||
plan.slug = slugify(title)
|
plan.slug = slugify(title)
|
||||||
|
|
||||||
def handle_categories(self, plan, categories):
|
def handle_categories(self, plan, category_name):
|
||||||
categories = [
|
categories = [
|
||||||
x.strip() for x in re.split(r" & | und ", categories) if x.strip()
|
x.strip() for x in re.split(r" & | und ", category_name) if x.strip()
|
||||||
]
|
]
|
||||||
|
self.make_section(category_name, "-".join(categories), categories)
|
||||||
if categories:
|
if categories:
|
||||||
self.post_save_list.append(lambda p: p.categories.set(*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):
|
def handle_reference(self, plan, reference):
|
||||||
plan.reference = ", ".join(re.split(r"\s*[,/]\s*", reference))
|
plan.reference = ", ".join(re.split(r"\s*[,/]\s*", reference))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,51 @@
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from django.views.generic import DetailView
|
from django.views.generic import DetailView
|
||||||
|
|
||||||
from .models import GovernmentPlan
|
from .models import Government, GovernmentPlan, GovernmentPlanSection
|
||||||
|
|
||||||
|
|
||||||
class GovPlanDetailView(DetailView):
|
class GovernmentMixin:
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
self.get_government()
|
||||||
|
return super().get(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_government(self):
|
||||||
|
filter_kwarg = {}
|
||||||
|
if not self.request.user.is_authenticated or not self.request.user.is_staff:
|
||||||
|
filter_kwarg["public"] = True
|
||||||
|
self.government = get_object_or_404(
|
||||||
|
Government, slug=self.kwargs["gov"], **filter_kwarg
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GovPlanSectionDetailView(GovernmentMixin, DetailView):
|
||||||
|
slug_url_kwarg = "section"
|
||||||
|
template_name = "froide_govplan/section.html"
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return GovernmentPlanSection.objects.filter(government=self.government)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context["plans"] = (
|
||||||
|
GovernmentPlan.objects.filter(
|
||||||
|
categories__in=self.object.categories.all(), government=self.government
|
||||||
|
)
|
||||||
|
.distinct()
|
||||||
|
.order_by("order", "title")
|
||||||
|
)
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class GovPlanDetailView(GovernmentMixin, DetailView):
|
||||||
slug_url_kwarg = "plan"
|
slug_url_kwarg = "plan"
|
||||||
template_name = "froide_govplan/detail.html"
|
template_name = "froide_govplan/detail.html"
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
qs = GovernmentPlan.objects.filter(government=self.government)
|
||||||
if self.request.user.is_authenticated and self.request.user.is_staff:
|
if self.request.user.is_authenticated and self.request.user.is_staff:
|
||||||
return GovernmentPlan.objects.all()
|
return qs
|
||||||
return GovernmentPlan.objects.filter(public=True)
|
return qs.filter(public=True)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue