mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-27 13:47:58 +01:00
batch tagging
This commit is contained in:
parent
9f690218f2
commit
8f445e49f9
7 changed files with 129 additions and 1 deletions
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
61
lib/philomena_web/controllers/admin/batch/tag_controller.ex
Normal file
61
lib/philomena_web/controllers/admin/batch/tag_controller.ex
Normal 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
|
|
@ -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
|
||||
|
|
9
lib/philomena_web/templates/image/_quick_tag.html.slime
Normal file
9
lib/philomena_web/templates/image/_quick_tag.html.slime
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
Loading…
Reference in a new issue