mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-30 14:57:59 +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.Image
|
||||||
alias Philomena.Images.Hider
|
alias Philomena.Images.Hider
|
||||||
alias Philomena.Images.Uploader
|
alias Philomena.Images.Uploader
|
||||||
|
alias Philomena.Images.Tagging
|
||||||
alias Philomena.ImageFeatures.ImageFeature
|
alias Philomena.ImageFeatures.ImageFeature
|
||||||
alias Philomena.SourceChanges.SourceChange
|
alias Philomena.SourceChanges.SourceChange
|
||||||
alias Philomena.TagChanges.TagChange
|
alias Philomena.TagChanges.TagChange
|
||||||
|
@ -224,7 +225,7 @@ defmodule Philomena.Images do
|
||||||
Multi.new
|
Multi.new
|
||||||
|> Multi.run(:image, fn repo, _chg ->
|
|> Multi.run(:image, fn repo, _chg ->
|
||||||
image
|
image
|
||||||
|> repo.preload(:tags, force: true)
|
|> repo.preload(:tags)
|
||||||
|> Image.tag_changeset(%{}, old_tags, new_tags)
|
|> Image.tag_changeset(%{}, old_tags, new_tags)
|
||||||
|> repo.update()
|
|> repo.update()
|
||||||
|> case do
|
|> case do
|
||||||
|
@ -363,6 +364,48 @@ defmodule Philomena.Images do
|
||||||
end
|
end
|
||||||
def unhide_image(image), do: {:ok, image}
|
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 """
|
@doc """
|
||||||
Deletes a Image.
|
Deletes a Image.
|
||||||
|
|
||||||
|
|
|
@ -147,6 +147,9 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
|
||||||
# Users and anonymous users can...
|
# 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
|
# Edit their description and personal title
|
||||||
def can?(%User{id: id}, :edit_description, %User{id: id}), do: true
|
def can?(%User{id: id}, :edit_description, %User{id: id}), do: true
|
||||||
def can?(%User{id: id}, :edit_title, %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 "/users", UserController, only: [:index, :edit, :update] do
|
||||||
resources "/avatar", User.AvatarController, only: [:delete], singleton: true
|
resources "/avatar", User.AvatarController, only: [:delete], singleton: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources "/batch/tags", Batch.TagController, only: [:update], singleton: true
|
||||||
end
|
end
|
||||||
|
|
||||||
resources "/duplicate_reports", DuplicateReportController, only: [] do
|
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
|
section.block__header.flex
|
||||||
span.block__header__title.hide-mobile
|
span.block__header__title.hide-mobile
|
||||||
=> header
|
=> header
|
||||||
|
|
||||||
= pagination
|
= pagination
|
||||||
|
|
||||||
|
.flex__right
|
||||||
|
= quick_tag @conn
|
||||||
|
|
||||||
= info_row @conn, tags
|
= info_row @conn, tags
|
||||||
|
|
||||||
.block__content.js-resizable-media-container
|
.block__content.js-resizable-media-container
|
||||||
|
|
|
@ -156,6 +156,12 @@ defmodule PhilomenaWeb.ImageView do
|
||||||
render PhilomenaWeb.TagView, "_tags_row.html", conn: conn, tags: tags
|
render PhilomenaWeb.TagView, "_tags_row.html", conn: conn, tags: tags
|
||||||
end
|
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(%{deleter: %{name: name}}), do: name
|
||||||
def deleter(_image), do: "System"
|
def deleter(_image), do: "System"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue