Skip to content

Commit

Permalink
Add admin actions for adding/removing tags/categories on Articles
Browse files Browse the repository at this point in the history
  • Loading branch information
StuartMacKay committed Sep 29, 2023
1 parent b22fe09 commit 4e86f26
Show file tree
Hide file tree
Showing 5 changed files with 363 additions and 2 deletions.
169 changes: 167 additions & 2 deletions src/feeds/admin/article.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
from urllib.parse import quote

from django import forms
from django.contrib import admin
from django.contrib import admin, messages
from django.contrib.admin import helpers
from django.contrib.admin.utils import model_ngettext
from django.contrib.admin.widgets import AutocompleteSelectMultiple
from django.db import models
from django.forms import Textarea
from django.shortcuts import render
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _

import tagulous.admin # type: ignore

from feeds.admin.prettifiers import prettify
from feeds.models import Article
from feeds.models import Article, Category, Tag


def retitle(title):
Expand Down Expand Up @@ -73,6 +77,42 @@ def __init__(self, *args, **kwargs):
self.fields["archive_url"].help_text = mark_safe(message % search_url)


class AddTagsForm(forms.Form):
tags = forms.ModelMultipleChoiceField(
label="Add the following tags",
queryset=Tag.objects.all(),
widget=AutocompleteSelectMultiple(Article._meta.get_field("tags"), admin.site),
)


class RemoveTagsForm(forms.Form):
tags = forms.ModelMultipleChoiceField(
label="Remove the following tags",
queryset=Tag.objects.all(),
widget=AutocompleteSelectMultiple(Article._meta.get_field("tags"), admin.site),
)


class AddCategoriesForm(forms.Form):
categories = forms.ModelMultipleChoiceField(
label="Add the following categories",
queryset=Category.objects.all(),
widget=AutocompleteSelectMultiple(
Article._meta.get_field("categories"), admin.site
),
)


class RemoveCategoriesForm(forms.Form):
categories = forms.ModelMultipleChoiceField(
label="Remove the following categories",
queryset=Category.objects.all(),
widget=AutocompleteSelectMultiple(
Article._meta.get_field("categories"), admin.site
),
)


class ArticleAdmin(admin.ModelAdmin):
list_display = (
"title",
Expand All @@ -94,6 +134,13 @@ class ArticleAdmin(admin.ModelAdmin):

ordering = ("-date",)

actions = [
"add_tags",
"remove_tags",
"add_categories",
"remove_categories",
]

readonly_fields = (
"code",
"pretty_data",
Expand Down Expand Up @@ -145,6 +192,124 @@ def tagged(self, obj):
def pretty_data(self, instance):
return mark_safe(prettify(instance.data))

@admin.action(description=_("Add Tags to selected Articles"))
def add_tags(modeladmin, request, queryset):
if request.POST.get("post"):
tags = request.POST.getlist("tags")

for article in queryset:
article.tags.add(*tags)

if count := queryset.count():
modeladmin.message_user(
request,
_("Successfully added tags to %(count)d %(items)s.")
% {"count": count, "items": model_ngettext(modeladmin.opts, count)},
messages.SUCCESS,
)
return None

context = {
**modeladmin.admin_site.each_context(request),
"title": _("Add Tags"),
"queryset": queryset,
"opts": modeladmin.model._meta,
"action_checkbox_name": helpers.ACTION_CHECKBOX_NAME,
"media": modeladmin.media,
"form": AddTagsForm(),
}

return render(request, "admin/add_tags.html", context=context)

@admin.action(description=_("Remove Tags from selected Articles"))
def remove_tags(modeladmin, request, queryset):
if request.POST.get("post"):
tags = request.POST.getlist("tags")

for article in queryset:
article.tags.remove(*tags)

if count := queryset.count():
modeladmin.message_user(
request,
_("Successfully removed tags from %(count)d %(items)s.")
% {"count": count, "items": model_ngettext(modeladmin.opts, count)},
messages.SUCCESS,
)
return None

context = {
**modeladmin.admin_site.each_context(request),
"title": _("Remove Tags"),
"queryset": queryset,
"opts": modeladmin.model._meta,
"action_checkbox_name": helpers.ACTION_CHECKBOX_NAME,
"media": modeladmin.media,
"form": RemoveTagsForm(),
}

return render(request, "admin/remove_tags.html", context=context)

@admin.action(description=_("Add Categories to selected Articles"))
def add_categories(modeladmin, request, queryset):
if request.POST.get("post"):
pks = request.POST.getlist("categories")
categories = Category.objects.filter(pk__in=pks)

for article in queryset:
article.categories.add(*categories)

if count := queryset.count():
modeladmin.message_user(
request,
_("Successfully added categories to %(count)d %(items)s.")
% {"count": count, "items": model_ngettext(modeladmin.opts, count)},
messages.SUCCESS,
)
return None

context = {
**modeladmin.admin_site.each_context(request),
"title": _("Add Categories"),
"queryset": queryset,
"opts": modeladmin.model._meta,
"action_checkbox_name": helpers.ACTION_CHECKBOX_NAME,
"media": modeladmin.media,
"form": AddCategoriesForm(),
}

return render(request, "admin/add_categories.html", context=context)

@admin.action(description=_("Remove Categories from selected Articles"))
def remove_categories(modeladmin, request, queryset):
if request.POST.get("post"):
pks = request.POST.getlist("categories")
categories = Category.objects.filter(pk__in=pks)

for article in queryset:
article.categories.remove(*categories)

if count := queryset.count():
modeladmin.message_user(
request,
_("Successfully removed categories from %(count)d %(items)s.")
% {"count": count, "items": model_ngettext(modeladmin.opts, count)},
messages.SUCCESS,
)
return None

context = {
**modeladmin.admin_site.each_context(request),
"title": _("Remove Categories"),
"queryset": queryset,
"opts": modeladmin.model._meta,
"action_checkbox_name": helpers.ACTION_CHECKBOX_NAME,
"media": modeladmin.media,
"form": RemoveCategoriesForm(),
}

return render(request, "admin/remove_categories.html", context=context)

def get_queryset(self, request):
return (
super(ArticleAdmin, self)
Expand Down
49 changes: 49 additions & 0 deletions src/feeds/templates/admin/add_categories.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{% extends "admin/base_site.html" %}
{% load i18n l10n admin_urls static %}

{% block extrahead %}
{{ block.super }}
{{ media }}
{{ form.media }}
<script src="{% static 'admin/js/cancel.js' %}" async></script>
{% endblock %}

{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation delete-selected-confirmation{% endblock %}

{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
&rsaquo; {% translate 'Add Categories' %}
</div>
{% endblock %}

{% block content %}
<h2>
{% translate "Selected Articles" %}
</h2>

<form method="post">{% csrf_token %}
<div>
<ul style="margin-bottom: 20px">
{% for obj in queryset %}
<li>
{{ obj.title }}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}">
</li>
{% endfor %}
</ul>

<div style="margin-bottom: 20px">
<label for="categories">{{ form.categories.label }}:</label>
{{ form.categories }}
</div>

<input type="hidden" name="action" value="add_categories">
<input type="hidden" name="post" value="yes">
<input type="submit" value="{% translate 'Yes, I’m sure' %}">
<a href="#" class="button cancel-link">{% translate "No, take me back" %}</a>
</div>
</form>
{% endblock %}
49 changes: 49 additions & 0 deletions src/feeds/templates/admin/add_tags.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{% extends "admin/base_site.html" %}
{% load i18n l10n admin_urls static %}

{% block extrahead %}
{{ block.super }}
{{ media }}
{{ form.media }}
<script src="{% static 'admin/js/cancel.js' %}" async></script>
{% endblock %}

{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation delete-selected-confirmation{% endblock %}

{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
&rsaquo; {% translate 'Add Tags' %}
</div>
{% endblock %}

{% block content %}
<h2>
{% translate "Selected Articles" %}
</h2>

<form method="post">{% csrf_token %}
<div>
<ul style="margin-bottom: 20px">
{% for obj in queryset %}
<li>
{{ obj.title }}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}">
</li>
{% endfor %}
</ul>

<div style="margin-bottom: 20px">
<label for="tags">{{ form.tags.label }}:</label>
{{ form.tags }}
</div>

<input type="hidden" name="action" value="add_tags">
<input type="hidden" name="post" value="yes">
<input type="submit" value="{% translate 'Yes, I’m sure' %}">
<a href="#" class="button cancel-link">{% translate "No, take me back" %}</a>
</div>
</form>
{% endblock %}
49 changes: 49 additions & 0 deletions src/feeds/templates/admin/remove_categories.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
add_categories.html{% extends "admin/base_site.html" %}
{% load i18n l10n admin_urls static %}

{% block extrahead %}
{{ block.super }}
{{ media }}
{{ form.media }}
<script src="{% static 'admin/js/cancel.js' %}" async></script>
{% endblock %}

{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation delete-selected-confirmation{% endblock %}

{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
&rsaquo; {% translate 'Remove Categories' %}
</div>
{% endblock %}

{% block content %}
<h2>
{% translate "Selected Articles" %}
</h2>

<form method="post">{% csrf_token %}
<div>
<ul style="margin-bottom: 20px">
{% for obj in queryset %}
<li>
{{ obj.title }}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}">
</li>
{% endfor %}
</ul>

<div style="margin-bottom: 20px">
<label for="categories">{{ form.categories.label }}:</label>
{{ form.categories }}
</div>

<input type="hidden" name="action" value="remove_categories">
<input type="hidden" name="post" value="yes">
<input type="submit" value="{% translate 'Yes, I’m sure' %}">
<a href="#" class="button cancel-link">{% translate "No, take me back" %}</a>
</div>
</form>
{% endblock %}
Loading

0 comments on commit 4e86f26

Please sign in to comment.