Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable Tag Merging #904

Merged
merged 18 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Changelog
We believe that this should not cause a noticable performance change, and the number of queries involved should not change.
* Add Django 5.0 support (no code changes were needed, but now we test this release).
* Add Python 3.12 support
* Added functionality for tag merging

5.0.1 (2023-10-26)
~~~~~~~~~~~~~~~~~~
Expand Down
16 changes: 16 additions & 0 deletions docs/admin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,19 @@ method to the :class:`~django.contrib.admin.ModelAdmin`, using

def tag_list(self, obj):
return u", ".join(o.name for o in obj.tags.all())


Merging tags in the admin
=======================

Functionality has been added to the admin app to allow for tag "merging".
Really what is happening is a "find and replace" where the selected tags are being used.

To merge your tags follow these steps:

1. Navigate to the Tags page inside of the Taggit app
2. Select the tags that you want to merge
3. Use the dropdown action list and select `Merge selected tags` and then click `Go`
4. This will redirect you onto a new page where you can insert the new tag name.
5. Click `Merge Tags`
6. This will redirect you back to the tag list
68 changes: 68 additions & 0 deletions taggit/admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from django.contrib import admin
from django.db import transaction
from django.shortcuts import redirect, render
from django.urls import path

from taggit.models import Tag, TaggedItem

from .forms import MergeTagsForm


class TaggedItemInline(admin.StackedInline):
model = TaggedItem
Expand All @@ -14,3 +19,66 @@ class TagAdmin(admin.ModelAdmin):
ordering = ["name", "slug"]
search_fields = ["name"]
prepopulated_fields = {"slug": ["name"]}
actions = ["render_tag_form"]

def get_urls(self):
urls = super().get_urls()
custom_urls = [
path(
"merge-tags/",
self.admin_site.admin_view(self.merge_tags_view),
name="merge_tags",
),
]
return custom_urls + urls

@admin.action(description="Merge selected tags")
def render_tag_form(self, request, queryset):
selected = request.POST.getlist(admin.helpers.ACTION_CHECKBOX_NAME)
if not selected:
self.message_user(request, "Please select at least one tag.")
return redirect(request.get_full_path())

selected_tag_ids = ",".join(selected)
redirect_url = (
f"{request.get_full_path()}merge-tags/?selected_tags={selected_tag_ids}"
)
rtpg marked this conversation as resolved.
Show resolved Hide resolved

return redirect(redirect_url)

def merge_tags_view(self, request):
if request.method == "GET":
selected_tag_ids = request.GET.get("selected_tags", "").split(",")

# store selected_tag_ids in session data until they are merged
request.session["selected_tag_ids"] = selected_tag_ids
else:
selected_tag_ids = request.session.get("selected_tag_ids", [])

if request.method == "POST":
form = MergeTagsForm(request.POST)
if form.is_valid():
new_tag_name = form.cleaned_data["new_tag_name"]
new_tag, created = Tag.objects.get_or_create(name=new_tag_name)
with transaction.atomic():
for tag_id in selected_tag_ids:
tag = Tag.objects.get(id=tag_id)
tagged_items = TaggedItem.objects.filter(tag=tag)
for tagged_item in tagged_items:
tagged_item.tag = new_tag
tagged_item.save()
rtpg marked this conversation as resolved.
Show resolved Hide resolved
# tag.delete() #this will delete the selected tags after merge

# clear the selected_tag_ids from session after merge is complete
request.session.pop("selected_tag_ids", None)
return redirect("..")
guel-codes marked this conversation as resolved.
Show resolved Hide resolved
else:
print(f"Form errors: {form.errors}")
guel-codes marked this conversation as resolved.
Show resolved Hide resolved
self.message_user(request, "Form is invalid.", level="error")
guel-codes marked this conversation as resolved.
Show resolved Hide resolved

context = {
"form": MergeTagsForm(),
guel-codes marked this conversation as resolved.
Show resolved Hide resolved
"selected_tag_ids": selected_tag_ids,
}

return render(request, "admin/taggit/merge_tags_form.html", context)
8 changes: 8 additions & 0 deletions taggit/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,11 @@ def has_changed(self, initial_value, data_value):
initial_value.sort()

return initial_value != data_value


class MergeTagsForm(forms.Form):
new_tag_name = forms.CharField(
label="New Tag Name",
max_length=255,
guel-codes marked this conversation as resolved.
Show resolved Hide resolved
widget=forms.TextInput(attrs={"id": "id_new_tag_name"}),
)
29 changes: 29 additions & 0 deletions taggit/templates/admin/taggit/merge_tags_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<div id="mergeTagsModal">
guel-codes marked this conversation as resolved.
Show resolved Hide resolved
<div>
<div>
<div>
<form
id="merge-tags-form"
method="post"
action="{% url 'admin:merge_tags' %}"
>
{% csrf_token %} {% for field in form %}
<div>
{{ field.label_tag }} {{ field }} {% if field.errors %}
<ul class="errorlist">
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endfor %}

<div>
<button type="submit" class="btn btn-primary">Merge Tags</button>
</div>
</form>
</div>
</div>
</div>
</div>
Loading