allow staff to delete/merge/unlink
This commit is contained in:
parent
c040af6e6b
commit
236a517d97
10 changed files with 122 additions and 30 deletions
|
@ -175,7 +175,10 @@ def external_search(request):
|
|||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def refetch(request):
|
||||
if request.method != "POST":
|
||||
return HttpResponseBadRequest()
|
||||
url = request.POST.get("url")
|
||||
if not url:
|
||||
return HttpResponseBadRequest()
|
||||
|
|
|
@ -105,9 +105,6 @@
|
|||
{% if user.is_authenticated %}
|
||||
<a href="{% url 'catalog:edit' item.url_path item.uuid %}">{% trans '编辑' %}{{ item.demonstrative }}</a>
|
||||
{% endif %}
|
||||
{% if user.is_staff %}
|
||||
/<a href="{% url 'catalog:delete' item.url_path item.uuid %}"> {% trans '删除' %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
<section id="content" class="container">
|
||||
<div class="grid">
|
||||
<div style="float:right;padding-left:16px">
|
||||
{% if item %}
|
||||
<div class="aside-section-wrapper">
|
||||
{% for res in form.instance.external_resources.all %}
|
||||
<div class="action-panel">
|
||||
|
@ -32,23 +33,63 @@
|
|||
{% csrf_token %}
|
||||
<input type="hidden" name="id" value="{{ res.id }}" >
|
||||
<input type="hidden" name="url" value="{{ res.url }}" >
|
||||
<input class="button" type="submit" value="{% trans '从源网站重新抓取' %}">
|
||||
<input class="button" type="submit" value="{% trans '重新抓取' %}">
|
||||
</form>
|
||||
</div>
|
||||
{% if request.user.is_staff %}
|
||||
<div class="action-panel__button-group">
|
||||
<form method="post" action="{% url 'catalog:unlink' %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="id" value="{{ res.id }}" >
|
||||
<input class="button" type="submit" value="{% trans '取消关联' %}">
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if request.user.is_staff %}
|
||||
<div class="aside-section-wrapper">
|
||||
<div class="action-panel">
|
||||
<div class="action-panel__label">{% trans '合并到另一条目' %} </div>
|
||||
<div class="action-panel__button-group">
|
||||
<form method="post" action="{% url 'catalog:merge' item.url_path item.uuid %}" onsubmit="return confirm('Confirm merging?');">
|
||||
{% csrf_token %}
|
||||
<input type="url" name="new_item_url" placeholder="目标条目URL" value="{{ item.merged_to_item.url }}" required><br>
|
||||
<input class="button" type="submit" value="{% trans '提交' %}">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-panel">
|
||||
<div class="action-panel__label">{% trans '删除' %} </div>
|
||||
<div class="action-panel__button-group">
|
||||
<form method="post" action="{% url 'catalog:delete' item.url_path item.uuid %}" onsubmit="return confirm('Confirm deletion?');">
|
||||
{% csrf_token %}
|
||||
<input class="button" type="submit" value="{% trans '提交' %}">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if item.is_deteled %}
|
||||
<!-- TODO undelete -->
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="single-section-wrapper" id="main">
|
||||
<form class="entity-form" action="{{ submit_url }}" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.media }}
|
||||
{{ form }}
|
||||
<hr/>
|
||||
<input class="button" type="submit" value="{% trans '提交' %}">
|
||||
<a href="javascript:history.go(-1)" style="float:right;">返回</a>
|
||||
</form>
|
||||
</div>
|
||||
<form class="entity-form" action="{{ submit_url }}" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.media }}
|
||||
{{ form }}
|
||||
<hr/>
|
||||
<input class="button" type="submit" value="{% trans '提交' %}">
|
||||
<a href="{{ item.url | default:'javascript:history.go(-1)' }}" style="float:right;">返回</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
@ -41,8 +41,8 @@
|
|||
<div>{% if item.translator %}{% trans '译者:' %}
|
||||
{% for translator in item.translator %}
|
||||
<span>{{ translator }}</span>{% if not forloop.last %} / {% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}</div>
|
||||
{% endfor %}
|
||||
{% endif %}</div>
|
||||
<div>{% if item.orig_title %}{% trans '原作名:' %}{{ item.orig_title }}{% endif %}</div>
|
||||
<div>{% if item.language %}{% trans '语言:' %}{{ item.language }}{% endif %}</div>
|
||||
<div>{%if item.pub_year %}{% trans '出版时间:' %}{{ item.pub_year }}{% trans '年' %}{% if item.pub_month %}{{ item.pub_month }}{% trans '月' %}{% endif %}{% endif %}</div>
|
||||
|
@ -53,7 +53,7 @@
|
|||
<div>{% if item.price %}{% trans '定价:' %}{{ item.price }}{% endif %}</div>
|
||||
<div>{% if item.pages %}{% trans '页数:' %}{{ item.pages }}{% endif %}</div>
|
||||
<div>{% if item.imprint %}{% trans '出品方:' %}{{ item.imprint }}{% endif %}</div>
|
||||
{% if item.other_info %}
|
||||
{% if item.other_info %}
|
||||
{% for k, v in item.other_info.items %}
|
||||
<div>
|
||||
{{ k }}:{{ v | urlize }}
|
||||
|
@ -70,9 +70,6 @@
|
|||
{% if user.is_authenticated %}
|
||||
<a href="{% url 'catalog:edit' item.url_path item.uuid %}">{% trans '编辑' %}{{ item.demonstrative }}</a>
|
||||
{% endif %}
|
||||
{% if user.is_staff %}
|
||||
/<a href="{% url 'catalog:delete' item.url_path item.uuid %}"> {% trans '删除' %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -45,6 +45,12 @@
|
|||
</a>
|
||||
|
||||
<div class="entity-detail__info">
|
||||
{% if item.is_deleted %}
|
||||
[DELETED]
|
||||
{% endif %}
|
||||
{% if item.merged_to_item %}
|
||||
[MERGED] <a href="{{ item.merged_to_item.url }}">{{ item.merged_to_item.title }}</a>
|
||||
{% endif %}
|
||||
{% block title %}
|
||||
<h5 class="entity-detail__title">
|
||||
{{ item.title }}
|
||||
|
@ -122,7 +128,7 @@
|
|||
<a href="{% url 'catalog:mark_list' item.url_path item.uuid %}" class="entity-marks__more-link">{% trans '全部标记' %}</a>
|
||||
<a href="{% url 'catalog:mark_list' item.url_path item.uuid 'following' %}" class="entity-marks__more-link">关注的人的标记</a>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<ul class="entity-marks__mark-list">
|
||||
{% for others_mark in mark_list %}
|
||||
<li class="entity-marks__mark">
|
||||
|
|
|
@ -163,9 +163,6 @@
|
|||
{% if user.is_authenticated %}
|
||||
<a href="{% url 'catalog:edit' item.url_path item.uuid %}">{% trans '编辑' %}{{ item.demonstrative }}</a>
|
||||
{% endif %}
|
||||
{% if user.is_staff %}
|
||||
/<a href="{% url 'catalog:delete' item.url_path item.uuid %}"> {% trans '删除' %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -69,5 +69,6 @@ urlpatterns = [
|
|||
path("search/external/", external_search, name="external_search"),
|
||||
path("fetch_refresh/<str:job_id>", fetch_refresh, name="fetch_refresh"),
|
||||
path("refetch", refetch, name="refetch"),
|
||||
path("unlink", unlink, name="unlink"),
|
||||
path("api/", api.urls),
|
||||
]
|
||||
|
|
|
@ -17,6 +17,7 @@ from django.db.models import Count
|
|||
from django.utils import timezone
|
||||
from django.core.paginator import Paginator
|
||||
from polymorphic.base import django
|
||||
from catalog.common.models import ExternalResource
|
||||
from catalog.common.sites import AbstractSite, SiteManager
|
||||
from mastodon import mastodon_request_included
|
||||
from mastodon.models import MastodonApplication
|
||||
|
@ -58,6 +59,11 @@ def retrieve(request, item_path, item_uuid):
|
|||
item_url = f"/{item_path}/{item_uuid}"
|
||||
if item.url != item_url:
|
||||
return redirect(item.url)
|
||||
skipcheck = request.GET.get("skipcheck", False) and request.user.is_staff
|
||||
if not skipcheck and item.merged_to_item:
|
||||
return redirect(item.merged_to_item.url)
|
||||
if not skipcheck and item.is_deleted:
|
||||
return HttpResponseNotFound("item not found")
|
||||
mark = None
|
||||
review = None
|
||||
mark_list = None
|
||||
|
@ -148,7 +154,7 @@ def edit(request, item_path, item_uuid):
|
|||
"catalog_edit.html",
|
||||
{
|
||||
"form": form,
|
||||
"is_update": True,
|
||||
"item": item,
|
||||
},
|
||||
)
|
||||
elif request.method == "POST":
|
||||
|
@ -176,7 +182,29 @@ def delete(request, item_path, item_uuid):
|
|||
return HttpResponseBadRequest()
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return HttpResponseBadRequest()
|
||||
item = get_object_or_404(Item, uid=base62.decode(item_uuid))
|
||||
for res in item.external_resources.all():
|
||||
res.item = None
|
||||
res.save()
|
||||
item.delete()
|
||||
return (
|
||||
redirect(item.url + "?skipcheck=1") if request.user.is_staff else redirect("/")
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def unlink(request):
|
||||
if request.method != "POST":
|
||||
return HttpResponseBadRequest()
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
res_id = request.POST.get("id")
|
||||
if not res_id:
|
||||
return HttpResponseBadRequest()
|
||||
resource = get_object_or_404(ExternalResource, id=res_id)
|
||||
resource.item = None
|
||||
resource.save()
|
||||
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -189,6 +217,7 @@ def merge(request, item_path, item_uuid):
|
|||
new_item = Item.get_by_url(request.POST.get("new_item_url"))
|
||||
if not new_item or new_item.is_deleted or new_item.merged_to_item_id:
|
||||
return HttpResponseBadRequest(b"invalid new item")
|
||||
_logger.warn(f"{request.user} merges {item} to {new_item}")
|
||||
item.merge_to(new_item)
|
||||
update_journal_for_merged_item(item_uuid)
|
||||
return redirect(new_item.url)
|
||||
|
|
20
journal/migrations/0008_alter_shelfmember_unique_together.py
Normal file
20
journal/migrations/0008_alter_shelfmember_unique_together.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 3.2.16 on 2023-01-23 05:05
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("catalog", "0002_initial"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("journal", "0007_alter_collection_catalog_item"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterUniqueTogether(
|
||||
name="shelfmember",
|
||||
unique_together={("owner", "item")},
|
||||
),
|
||||
]
|
|
@ -96,7 +96,7 @@ def query_item_category(item_category):
|
|||
|
||||
|
||||
class Piece(PolymorphicModel, UserOwnedObjectMixin):
|
||||
url_path = "piece" # subclass must specify this
|
||||
url_path = "p" # subclass must specify this
|
||||
uid = models.UUIDField(default=uuid.uuid4, editable=False, db_index=True)
|
||||
|
||||
@property
|
||||
|
@ -494,7 +494,7 @@ class ShelfMember(ListMember):
|
|||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = [["parent", "item"]]
|
||||
unique_together = [["owner", "item"]]
|
||||
|
||||
@cached_property
|
||||
def mark(self):
|
||||
|
@ -1054,12 +1054,13 @@ def update_journal_for_merged_item(legacy_item_uuid):
|
|||
_logger.error("update_journal_for_merged_item: unable to find item")
|
||||
return
|
||||
new_item = legacy_item.merged_to_item
|
||||
for cls in Content.__subclasses__ + ListMember.__subclasses__:
|
||||
_logger.info(f"update {cls.__name__}: {legacy_item} -> {new_item}")
|
||||
for cls in list(Content.__subclasses__()) + list(ListMember.__subclasses__()):
|
||||
for p in cls.objects.filter(item=legacy_item):
|
||||
try:
|
||||
p.item = new_item
|
||||
p.save(update_fields=["item_id"])
|
||||
except:
|
||||
_logger.info(f"delete duplicated piece {p}")
|
||||
_logger.warn(
|
||||
f"deleted piece {p} when merging {cls.__name__}: {legacy_item} -> {new_item}"
|
||||
)
|
||||
p.delete()
|
||||
|
|
Loading…
Add table
Reference in a new issue