batch tagging

This commit is contained in:
byte[] 2019-12-16 17:11:16 -05:00
parent 9f690218f2
commit 8f445e49f9
7 changed files with 129 additions and 1 deletions

View file

@ -11,6 +11,7 @@ defmodule Philomena.Images do
alias Philomena.Images.Image
alias Philomena.Images.Hider
alias Philomena.Images.Uploader
alias Philomena.Images.Tagging
alias Philomena.ImageFeatures.ImageFeature
alias Philomena.SourceChanges.SourceChange
alias Philomena.TagChanges.TagChange
@ -224,7 +225,7 @@ defmodule Philomena.Images do
Multi.new
|> Multi.run(:image, fn repo, _chg ->
image
|> repo.preload(:tags, force: true)
|> repo.preload(:tags)
|> Image.tag_changeset(%{}, old_tags, new_tags)
|> repo.update()
|> case do
@ -363,6 +364,48 @@ defmodule Philomena.Images do
end
def unhide_image(image), do: {:ok, image}
def batch_update(image_ids, added_tags, removed_tags, tag_change_attributes) do
added_tags = Enum.map(added_tags, & &1.id)
removed_tags = Enum.map(removed_tags, & &1.id)
# Change everything in one go, ignoring any validation errors
# Note: computing the Cartesian product
insertions =
for tag_id <- added_tags, image_id <- image_ids do
%{tag_id: tag_id, image_id: image_id}
end
deletions =
Tagging
|> where([t], t.image_id in ^image_ids and t.tag_id in ^removed_tags)
|> select([t], [t.image_id, t.tag_id])
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
tag_change_attributes = Map.merge(tag_change_attributes, %{created_at: now, updated_at: now})
Repo.transaction fn ->
{added_count, inserted} = Repo.insert_all(Tagging, insertions, on_conflict: :nothing, returning: [:image_id, :tag_id])
{removed_count, deleted} = Repo.delete_all(deletions)
inserted = Enum.map(inserted, &[&1.image_id, &1.tag_id])
added_changes = Enum.map(inserted, fn [image_id, tag_id] ->
Map.merge(tag_change_attributes, %{image_id: image_id, tag_id: tag_id, added: true})
end)
removed_changes = Enum.map(deleted, fn [image_id, tag_id] ->
Map.merge(tag_change_attributes, %{image_id: image_id, tag_id: tag_id, added: false})
end)
changes = added_changes ++ removed_changes
Repo.insert_all(TagChange, changes)
Repo.update_all(where(Tag, [t], t.id in ^added_tags), inc: [images_count: added_count])
Repo.update_all(where(Tag, [t], t.id in ^removed_tags), inc: [images_count: -removed_count])
end
end
@doc """
Deletes a Image.

View file

@ -147,6 +147,9 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
# Users and anonymous users can...
#
# Batch tag
def can?(%User{role_map: %{"Tag" => "batch_update"}}, :batch_update, Tag), do: true
# Edit their description and personal title
def can?(%User{id: id}, :edit_description, %User{id: id}), do: true
def can?(%User{id: id}, :edit_title, %User{id: id}), do: true

View file

@ -0,0 +1,61 @@
defmodule PhilomenaWeb.Admin.Batch.TagController do
use PhilomenaWeb, :controller
alias Philomena.Tags.Tag
alias Philomena.Tags
alias Philomena.Images
alias Philomena.Repo
import Ecto.Query
plug :verify_authorized
plug PhilomenaWeb.UserAttributionPlug
def update(conn, %{"tags" => tags, "image_ids" => image_ids}) do
tags = Tag.parse_tag_list(tags)
added_tag_names = Enum.reject(tags, &String.starts_with?(&1, "-"))
removed_tag_names =
tags
|> Enum.filter(&String.starts_with?(&1, "-"))
|> Enum.map(&String.slice(&1, 1..-1))
added_tags =
Tag
|> where([t], t.name in ^added_tag_names)
|> Repo.all()
removed_tags =
Tag
|> where([t], t.name in ^removed_tag_names)
|> Repo.all()
attributes = conn.assigns.attributes
attributes = %{
ip: attributes[:ip],
fingerprint: attributes[:fingerprint],
user_agent: attributes[:user_agent],
referrer: attributes[:referrer],
user_id: attributes[:user].id
}
image_ids = Enum.map(image_ids, &String.to_integer/1)
case Images.batch_update(image_ids, added_tags, removed_tags, attributes) do
{:ok, _} ->
Images.reindex_images(image_ids)
Tags.reindex_tags(added_tags ++ removed_tags)
json(conn, %{succeeded: image_ids, failed: []})
_error ->
json(conn, %{succeeded: [], failed: image_ids})
end
end
defp verify_authorized(conn, _opts) do
case Canada.Can.can?(conn.assigns.current_user, :batch_update, Tag) do
true -> conn
_false -> PhilomenaWeb.NotAuthorizedPlug.call(conn)
end
end
end

View file

@ -220,6 +220,8 @@ defmodule PhilomenaWeb.Router do
resources "/users", UserController, only: [:index, :edit, :update] do
resources "/avatar", User.AvatarController, only: [:delete], singleton: true
end
resources "/batch/tags", Batch.TagController, only: [:update], singleton: true
end
resources "/duplicate_reports", DuplicateReportController, only: [] do

View file

@ -0,0 +1,9 @@
a.js-quick-tag href="#" title="Add tags to the images on this page"
i.fa.fa-tags
span.hide-mobile.hide-limited-desktop<> Tag
a.js-quick-tag--abort.hidden href="#"
i.fa.fa-exclamation-triangle
span.hide-mobile.hide-limited-desktop<> Abort Tagging
a.js-quick-tag--submit.hidden href="#"
i.fa.fa-tags
span.hide-mobile.hide-limited-desktop<> Submit Tag Changes

View file

@ -12,8 +12,12 @@ elixir:
section.block__header.flex
span.block__header__title.hide-mobile
=> header
= pagination
.flex__right
= quick_tag @conn
= info_row @conn, tags
.block__content.js-resizable-media-container

View file

@ -156,6 +156,12 @@ defmodule PhilomenaWeb.ImageView do
render PhilomenaWeb.TagView, "_tags_row.html", conn: conn, tags: tags
end
def quick_tag(conn) do
if can?(conn, :batch_update, Tag) do
render PhilomenaWeb.ImageView, "_quick_tag.html", conn: conn
end
end
def deleter(%{deleter: %{name: name}}), do: name
def deleter(_image), do: "System"