tag description and implication editing

This commit is contained in:
byte[] 2019-12-14 16:18:49 -05:00
parent 2eb39b43aa
commit 4f3390f728
10 changed files with 233 additions and 17 deletions

View file

@ -4,9 +4,13 @@ defmodule Philomena.Tags do
""" """
import Ecto.Query, warn: false import Ecto.Query, warn: false
alias Ecto.Multi
alias Philomena.Repo alias Philomena.Repo
alias Philomena.Tags.Tag alias Philomena.Tags.Tag
alias Philomena.Tags.Uploader
alias Philomena.Images
alias Philomena.Images.Image
@spec get_or_create_tags(String.t()) :: List.t() @spec get_or_create_tags(String.t()) :: List.t()
def get_or_create_tags(tag_list) do def get_or_create_tags(tag_list) do
@ -96,11 +100,44 @@ defmodule Philomena.Tags do
""" """
def update_tag(%Tag{} = tag, attrs) do def update_tag(%Tag{} = tag, attrs) do
tag_input = Tag.parse_tag_list(attrs["implied_tag_list"])
implied_tags =
Tag
|> where([t], t.name in ^tag_input)
|> Repo.all()
tag tag
|> Tag.changeset(attrs) |> Tag.changeset(attrs, implied_tags)
|> Repo.update() |> Repo.update()
end end
def update_tag_image(%Tag{} = tag, attrs) do
changeset = Uploader.analyze_upload(tag, attrs)
Multi.new
|> Multi.update(:tag, changeset)
|> Multi.run(:update_file, fn _repo, %{tag: tag} ->
Uploader.persist_upload(tag)
Uploader.unpersist_old_upload(tag)
{:ok, nil}
end)
|> Repo.isolated_transaction(:serializable)
end
def remove_tag_image(%Tag{} = tag) do
changeset = Tag.remove_image_changeset(tag)
Multi.new
|> Multi.update(:tag, changeset)
|> Multi.run(:remove_file, fn _repo, %{tag: tag} ->
Uploader.unpersist_old_upload(tag)
{:ok, nil}
end)
|> Repo.isolated_transaction(:serializable)
end
@doc """ @doc """
Deletes a Tag. Deletes a Tag.
@ -114,7 +151,19 @@ defmodule Philomena.Tags do
""" """
def delete_tag(%Tag{} = tag) do def delete_tag(%Tag{} = tag) do
Repo.delete(tag) image_ids =
Image
|> join(:inner, [i], _ in assoc(i, :tags))
|> where([_i, t], t.id == ^tag.id)
|> select([i, _t], i.id)
|> Repo.all()
{:ok, _tag} = Repo.delete(tag)
Image
|> where([i], i.id in ^image_ids)
|> preload(^Images.indexing_preloads())
|> Image.reindex()
end end
@doc """ @doc """

View file

@ -50,7 +50,7 @@ defmodule Philomena.Tags.Tag do
schema "tags" do schema "tags" do
belongs_to :aliased_tag, Tag, source: :aliased_tag_id belongs_to :aliased_tag, Tag, source: :aliased_tag_id
has_many :aliases, Tag, foreign_key: :aliased_tag_id has_many :aliases, Tag, foreign_key: :aliased_tag_id
many_to_many :implied_tags, Tag, join_through: "tags_implied_tags", join_keys: [tag_id: :id, implied_tag_id: :id] many_to_many :implied_tags, Tag, join_through: "tags_implied_tags", join_keys: [tag_id: :id, implied_tag_id: :id], on_replace: :delete
many_to_many :implied_by_tags, Tag, join_through: "tags_implied_tags", join_keys: [implied_tag_id: :id, tag_id: :id] many_to_many :implied_by_tags, Tag, join_through: "tags_implied_tags", join_keys: [implied_tag_id: :id, tag_id: :id]
has_many :public_links, UserLink, where: [public: true, aasm_state: "verified"] has_many :public_links, UserLink, where: [public: true, aasm_state: "verified"]
has_many :dnp_entries, DnpEntry, where: [aasm_state: "listed"] has_many :dnp_entries, DnpEntry, where: [aasm_state: "listed"]
@ -68,17 +68,42 @@ defmodule Philomena.Tags.Tag do
field :image_mime_type, :string field :image_mime_type, :string
field :mod_notes, :string field :mod_notes, :string
field :uploaded_image, :string, virtual: true
field :removed_image, :string, virtual: true
field :implied_tag_list, :string, virtual: true
timestamps(inserted_at: :created_at) timestamps(inserted_at: :created_at)
end end
@doc false @doc false
def changeset(tag, attrs) do def changeset(tag, attrs) do
tag tag
|> cast(attrs, []) |> cast(attrs, [:category, :description, :short_description, :mod_notes])
|> put_change(:implied_tag_list, Enum.map_join(tag.implied_tags, ",", & &1.name))
|> validate_required([]) |> validate_required([])
end end
@doc false def changeset(tag, attrs, implied_tags) do
tag
|> cast(attrs, [:category, :description, :short_description, :mod_notes])
|> put_assoc(:implied_tags, implied_tags)
|> validate_required([])
end
def image_changeset(tag, attrs) do
tag
|> cast(attrs, [:image, :image_format, :image_mime_type, :uploaded_image])
|> validate_required([:image, :image_format, :image_mime_type])
|> validate_inclusion(:image_mime_type, ~W(image/gif image/jpeg image/png))
end
def remove_image_changeset(tag) do
change(tag)
|> put_change(:removed_image, tag.image)
|> put_change(:image, nil)
end
def creation_changeset(tag, attrs) do def creation_changeset(tag, attrs) do
tag tag
|> cast(attrs, [:name]) |> cast(attrs, [:name])
@ -112,6 +137,20 @@ defmodule Philomena.Tags.Tag do
}) })
end end
def categories do
[
"error",
"rating",
"origin",
"character",
"oc",
"species",
"content-fanmade",
"content-official",
"spoiler"
]
end
def clean_tag_name(name) do def clean_tag_name(name) do
# Downcase, replace extra runs of spaces, replace unicode quotes # Downcase, replace extra runs of spaces, replace unicode quotes
# with ascii quotes, trim space from end # with ascii quotes, trim space from end

View file

@ -0,0 +1,24 @@
defmodule Philomena.Tags.Uploader do
@moduledoc """
Upload and processing callback logic for Tag images.
"""
alias Philomena.Tags.Tag
alias Philomena.Uploader
def analyze_upload(tag, params) do
Uploader.analyze_upload(tag, "image", params["image"], &Tag.image_changeset/2)
end
def persist_upload(tag) do
Uploader.persist_upload(tag, tag_file_root(), "image")
end
def unpersist_old_upload(tag) do
Uploader.unpersist_old_upload(tag, tag_file_root(), "image")
end
defp tag_file_root do
Application.get_env(:philomena, :tag_file_root)
end
end

View file

@ -80,6 +80,9 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
# Hide topics # Hide topics
def can?(%User{role: "moderator"}, :hide, %Topic{}), do: true def can?(%User{role: "moderator"}, :hide, %Topic{}), do: true
# Edit tags
def can?(%User{role: "moderator"}, :edit, %Tag{}), do: true
# #
# Assistants can... # Assistants can...
# #

View file

@ -14,7 +14,7 @@ defmodule PhilomenaWeb.Admin.Report.ClaimController do
conn conn
|> put_flash(:info, "Successfully marked report as in progress") |> put_flash(:info, "Successfully marked report as in progress")
|> redirect(to: Routes.admin_report_path(conn, :show, report)) |> redirect(to: Routes.admin_report_path(conn, :index))
{:error, _changeset} -> {:error, _changeset} ->
conn conn
@ -31,4 +31,4 @@ defmodule PhilomenaWeb.Admin.Report.ClaimController do
|> put_flash(:info, "Successfully released report.") |> put_flash(:info, "Successfully released report.")
|> redirect(to: Routes.admin_report_path(conn, :show, report)) |> redirect(to: Routes.admin_report_path(conn, :show, report))
end end
end end

View file

@ -5,10 +5,9 @@ defmodule PhilomenaWeb.TagController do
alias Philomena.{Tags, Tags.Tag} alias Philomena.{Tags, Tags.Tag}
alias Philomena.Textile.Renderer alias Philomena.Textile.Renderer
alias Philomena.Interactions alias Philomena.Interactions
alias Philomena.Repo
import Ecto.Query
plug PhilomenaWeb.RecodeParameterPlug, [name: "id"] when action in [:show] plug PhilomenaWeb.RecodeParameterPlug, [name: "id"] when action in [:show]
plug :load_and_authorize_resource, model: Tag, id_field: "slug", only: [:show, :edit, :update, :delete], preload: [:aliases, :implied_tags, :implied_by_tags, :dnp_entries, public_links: :user]
def index(conn, params) do def index(conn, params) do
query_string = params["tq"] || "*" query_string = params["tq"] || "*"
@ -32,14 +31,9 @@ defmodule PhilomenaWeb.TagController do
end end
end end
def show(conn, %{"id" => slug}) do def show(conn, _params) do
user = conn.assigns.current_user user = conn.assigns.current_user
tag = conn.assigns.tag
tag =
Tag
|> where(slug: ^slug)
|> preload([:aliases, :implied_tags, :implied_by_tags, :dnp_entries, public_links: :user])
|> Repo.one()
{images, _tags} = {images, _tags} =
ImageLoader.query(conn, %{term: %{"namespaced_tags.name" => tag.name}}) ImageLoader.query(conn, %{term: %{"namespaced_tags.name" => tag.name}})
@ -73,6 +67,35 @@ defmodule PhilomenaWeb.TagController do
) )
end end
def edit(conn, _params) do
changeset = Tags.change_tag(conn.assigns.tag)
render(conn, "edit.html", changeset: changeset)
end
def update(conn, %{"tag" => tag_params}) do
case Tags.update_tag(conn.assigns.tag, tag_params) do
{:ok, tag} ->
Tags.reindex_tag(tag)
conn
|> put_flash(:info, "Tag successfully updated.")
|> redirect(to: Routes.tag_path(conn, :show, tag))
{:error, changeset} ->
render(conn, "edit.html", changeset: changeset)
end
end
def delete(conn, _params) do
spawn fn ->
Tags.delete_tag(conn.assigns.tag)
end
conn
|> put_flash(:info, "Tag scheduled for deletion.")
|> redirect(to: "/")
end
def escape_name(%{name: name}) do def escape_name(%{name: name}) do
name = name =
name name

View file

@ -208,6 +208,8 @@ defmodule PhilomenaWeb.Router do
resources "/reject", DuplicateReport.RejectController, only: [:create], singleton: true resources "/reject", DuplicateReport.RejectController, only: [:create], singleton: true
resources "/claim", DuplicateReport.ClaimController, only: [:create, :delete], singleton: true resources "/claim", DuplicateReport.ClaimController, only: [:create, :delete], singleton: true
end end
resources "/tags", TagController, only: [:edit, :update, :delete]
end end
scope "/", PhilomenaWeb do scope "/", PhilomenaWeb do

View file

@ -8,6 +8,9 @@
.flex__grow .flex__grow
= render PhilomenaWeb.TagView, "_tag.html", tag: @tag, conn: @conn = render PhilomenaWeb.TagView, "_tag.html", tag: @tag, conn: @conn
= link "Tag changes", to: Routes.tag_tag_change_path(@conn, :index, @tag), class: "detail-link" = link "Tag changes", to: Routes.tag_tag_change_path(@conn, :index, @tag), class: "detail-link"
= if manages_tags?(@conn) do
= link "Edit details", to: Routes.tag_path(@conn, :edit, @tag), class: "detail-link"
br br
= if @tag.short_description not in [nil, ""] do = if @tag.short_description not in [nil, ""] do
@ -81,4 +84,4 @@
| ( | (
= link "more info", to: "#" = link "more info", to: "#"
| ) | )

View file

@ -0,0 +1,65 @@
h1 Editing Tag
= form_for @changeset, Routes.tag_path(@conn, :update, @tag), [class: "form"], fn f ->
= if @changeset.action do
.alert.alert-danger
p Oops, something went wrong! Please check the errors below.
h2
= @tag.name
.field
' Category:
= select f, :category, tag_categories(), class: "input"
h4 Description
.field
=> label f, :short_description, "Short description:"
= text_input f, :short_description, class: "input input--wide"
.field
=> label f, :description, "Long description:"
= textarea f, :description, class: "input input--wide"
.field
=> label f, :mod_notes, "Mod notes:"
= textarea f, :mod_notes, class: "input input--wide"
h4 Implied Tags
.fieldlabel Tags in this list will be added when this tag is added to an image.
.field
= render PhilomenaWeb.TagView, "_tag_editor.html", f: f, name: :implied_tag_list, type: :edit, conn: @conn
/- if can? :manage, Tag
h4 Tag Merging (Aliasing)
.fieldlabel Merge with target tag for searches, user links, filters, etc.; soft keeps the tag around for redirection purposes as an "alias"
.field
= select_tag :merge_mode, options_for_select({ Soft: :alias, Hard: :hard_merge }, :alias), class: "input"
= text_input f, :target_tag_name, class: "input", placeholder: "Target tag name", autocapitalize: "none", value: @tag.aliased_tag_name
br
= submit "Save Tag", class: "button button--state-primary"
/ not ready yet
br
br
input.toggle-box#tag-management checked="false" type="checkbox"
label for="tag-management" Tag Processing
.toggle-box-container
.toggle-box-container__content
= link_to "Rebuild index", admin_tag_reindex_path(@tag), class: "button", data: { confirm: t("are_you_sure") }, method: :post
p Use this if the tag displays the wrong number of images or returns the wrong search results.
= link_to "Recreate slug", admin_tag_slug_path(@tag), class: "button", method: :post
p
| Use this for old tags with invalid slugs (
code
' /mlp/ →
= Tag.generate_slug "/mlp/"
| )
= link_to "Destroy tag", admin_tag_path(@tag), class: "button button--state-danger", data: { confirm: t("are_you_sure") }, method: :delete
p
strong Irreversible. Use with extreme caution!
ul
li Intended use is removing garbage tags.
li Will remove tag changes on the tag, but not on images or profiles.
li Will fail if the tag is the target of an alias, is implied by other tags, or is a rating tag.

View file

@ -6,6 +6,14 @@ defmodule PhilomenaWeb.TagView do
alias Philomena.Repo alias Philomena.Repo
import Ecto.Query import Ecto.Query
def tag_categories do
[[key: "-", value: ""] | Tag.categories]
end
def manages_tags?(conn) do
can?(conn, :edit, %Tag{})
end
def tag_image(%{image: image}) do def tag_image(%{image: image}) do
tag_url_root() <> "/" <> image tag_url_root() <> "/" <> image
end end