This commit is contained in:
Rebel4788 2024-11-22 05:22:17 +00:00 committed by GitHub
commit fe230fa862
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 758 additions and 2 deletions

View file

@ -0,0 +1,29 @@
@import colors
.content-wrapper.banned-posts
width: 100%
max-width: 45em
table
border-spacing: 0
width: 100%
tr.default td
background: $default-banned-post-background-color
td, th
padding: .4em
&.color
input[type=text]
width: 8em
&.usages
text-align: center
&.remove, &.set-default
white-space: pre
th
white-space: nowrap
&:first-child
padding-left: 0
&:last-child
padding-right: 0
tfoot
display: none
form
width: auto

View file

@ -0,0 +1,13 @@
<tr data-category='<%- ctx.postBan.checksum %>'>
<td class='name'>
<%- ctx.postBan.checksum %>
</td>
<td class='time'>
<%= ctx.makeRelativeTime(ctx.postBan.time) %>
</td>
<% if (ctx.canDelete) { %>
<td class='remove'>
<a href>Unban</a>
</td>
<% } %>
</tr>

View file

@ -0,0 +1,25 @@
<div class='content-wrapper banned-posts'>
<form>
<h1>Banned posts</h1>
<div class="table-wrap">
<table>
<thead>
<tr>
<th class='checksum'>Checksum</th>
<th class='time'>Time of ban</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class='messages'></div>
<% if (ctx.canDelete) { %>
<div class='buttons'>
<input type='submit' class='save' value='Save changes'>
</div>
<% } %>
</form>
</div>

View file

@ -108,7 +108,7 @@
</section> </section>
<% } %> <% } %>
<% if (ctx.canFeaturePosts || ctx.canDeletePosts || ctx.canMergePosts) { %> <% if (ctx.canFeaturePosts || ctx.canDeletePosts || ctx.canMergePosts || ctx.canBanPosts) { %>
<section class='management'> <section class='management'>
<ul> <ul>
<% if (ctx.canFeaturePosts) { %> <% if (ctx.canFeaturePosts) { %>
@ -120,6 +120,9 @@
<% if (ctx.canDeletePosts) { %> <% if (ctx.canDeletePosts) { %>
<li><a href class='delete'>Delete this post</a></li> <li><a href class='delete'>Delete this post</a></li>
<% } %> <% } %>
<% if (ctx.canBanPosts) { %>
<li><a href class='ban'>Ban this post</a></li>
<% } %>
</ul> </ul>
</section> </section>
<% } %> <% } %>

View file

@ -0,0 +1,59 @@
"use strict";
const api = require("../api.js");
const BannedPostList = require("../models/banned_post_list.js");
const topNavigation = require("../models/top_navigation.js");
const BannedPostsView = require("../views/banned_posts_view.js");
const EmptyView = require("../views/empty_view.js");
class BannedPostController {
constructor() {
if (!api.hasPrivilege("posts:ban:list")) {
this._view = new EmptyView();
this._view.showError(
"You don't have privileges to view banned posts."
);
return;
}
topNavigation.activate("banned-posts");
topNavigation.setTitle("Listing banned posts");
BannedPostList.get().then(
(response) => {
this._bannedPosts = response.results;
this._view = new BannedPostsView({
bannedPosts: this._bannedPosts,
canDelete: api.hasPrivilege("poolCategories:delete")
});
this._view.addEventListener("submit", (e) =>
this._evtSubmit(e)
);
},
(error) => {
this._view = new EmptyView();
this._view.showError(error.message);
}
);
}
_evtSubmit(e) {
this._view.clearMessages();
this._view.disableForm();
this._bannedPosts.save().then(
() => {
this._view.enableForm();
this._view.showSuccess("Changes saved.");
},
(error) => {
this._view.enableForm();
this._view.showError(error.message);
}
);
}
}
module.exports = (router) => {
router.enter(["banned-posts"], (ctx, next) => {
ctx.controller = new BannedPostController(ctx, next);
});
};

View file

@ -88,6 +88,9 @@ class PostMainController extends BasePostController {
this._view.sidebarControl.addEventListener("delete", (e) => this._view.sidebarControl.addEventListener("delete", (e) =>
this._evtDeletePost(e) this._evtDeletePost(e)
); );
this._view.sidebarControl.addEventListener("ban", (e) =>
this._evtBanPost(e)
);
this._view.sidebarControl.addEventListener("merge", (e) => this._view.sidebarControl.addEventListener("merge", (e) =>
this._evtMergePost(e) this._evtMergePost(e)
); );
@ -165,6 +168,22 @@ class PostMainController extends BasePostController {
); );
} }
_evtBanPost(e) {
this._view.sidebarControl.disableForm();
this._view.sidebarControl.clearMessages();
e.detail.post.ban().then(
() => {
misc.disableExitConfirmation();
const ctx = router.show(uri.formatClientLink("posts"));
ctx.controller.showSuccess("Post banned.");
},
(error) => {
this._view.sidebarControl.showError(error.message);
this._view.sidebarControl.enableForm();
}
);
}
_evtUpdatePost(e) { _evtUpdatePost(e) {
this._view.sidebarControl.disableForm(); this._view.sidebarControl.disableForm();
this._view.sidebarControl.clearMessages(); this._view.sidebarControl.clearMessages();

View file

@ -53,6 +53,9 @@ class TopNavigationController {
if (!api.hasPrivilege("pools:list")) { if (!api.hasPrivilege("pools:list")) {
topNavigation.hide("pools"); topNavigation.hide("pools");
} }
if (!api.hasPrivilege("posts:ban:list")) {
topNavigation.hide("banned-posts");
}
if (api.isLoggedIn()) { if (api.isLoggedIn()) {
if (!api.hasPrivilege("users:create:any")) { if (!api.hasPrivilege("users:create:any")) {
topNavigation.hide("register"); topNavigation.hide("register");

View file

@ -46,6 +46,7 @@ class PostEditSidebarControl extends events.EventTarget {
"posts:create:anonymous" "posts:create:anonymous"
), ),
canDeletePosts: api.hasPrivilege("posts:delete"), canDeletePosts: api.hasPrivilege("posts:delete"),
canBanPosts: api.hasPrivilege("posts:ban"),
canFeaturePosts: api.hasPrivilege("posts:feature"), canFeaturePosts: api.hasPrivilege("posts:feature"),
canMergePosts: api.hasPrivilege("posts:merge"), canMergePosts: api.hasPrivilege("posts:merge"),
}) })
@ -186,6 +187,12 @@ class PostEditSidebarControl extends events.EventTarget {
); );
} }
if (this._banLinkNode) {
this._banLinkNode.addEventListener("click", (e) =>
this._evtBanClick(e)
);
}
this._postNotesOverlayControl.addEventListener("blur", (e) => this._postNotesOverlayControl.addEventListener("blur", (e) =>
this._evtNoteBlur(e) this._evtNoteBlur(e)
); );
@ -301,6 +308,19 @@ class PostEditSidebarControl extends events.EventTarget {
} }
} }
_evtBanClick(e) {
e.preventDefault();
if (confirm("Are you sure you want to ban this post?")) {
this.dispatchEvent(
new CustomEvent("ban", {
detail: {
post: this._post,
},
})
);
}
}
_evtNoteTextChangeRequest(e) { _evtNoteTextChangeRequest(e) {
if (this._editedNote) { if (this._editedNote) {
this._editedNote.text = this._noteTextareaNode.value; this._editedNote.text = this._noteTextareaNode.value;
@ -517,6 +537,11 @@ class PostEditSidebarControl extends events.EventTarget {
return this._formNode.querySelector(".management .delete"); return this._formNode.querySelector(".management .delete");
} }
get _banLinkNode() {
return this._formNode.querySelector(".management .ban");
}
get _addNoteLinkNode() { get _addNoteLinkNode() {
return this._formNode.querySelector(".notes .add"); return this._formNode.querySelector(".notes .add");
} }

View file

@ -83,6 +83,7 @@ Promise.resolve()
controllers.push( controllers.push(
require("./controllers/user_registration_controller.js") require("./controllers/user_registration_controller.js")
); );
controllers.push(require("./controllers/banned_post_controller.js"));
// 404 controller needs to be registered last // 404 controller needs to be registered last
controllers.push(require("./controllers/not_found_controller.js")); controllers.push(require("./controllers/not_found_controller.js"));

View file

@ -0,0 +1,57 @@
"use strict";
const api = require("../api.js");
const uri = require("../util/uri.js");
const events = require("../events.js");
class BannedPost extends events.EventTarget {
constructor() {
super();
this._checksum = "";
this._time = new Date();
}
get checksum() {
return this._checksum;
}
get time() {
return this._time;
}
set checksum(value) {
this._checksum = value;
}
set time(value) {
this._time = value;
}
static fromResponse(response) {
const ret = new BannedPost();
ret._updateFromResponse(response);
return ret;
}
delete() {
return api
.delete(uri.formatApiLink("post-ban", this._checksum))
.then((response) => {
this.dispatchEvent(
new CustomEvent("delete", {
detail: {
bannedPost: this,
},
})
);
return Promise.resolve();
});
}
_updateFromResponse(response) {
this._checksum = response.checksum;
this.time = response.time;
}
}
module.exports = BannedPost;

View file

@ -0,0 +1,47 @@
const api = require("../api.js");
const uri = require("../util/uri.js");
const AbstractList = require("./abstract_list.js");
const BannedPost = require("./banned_post.js");
class BannedPostList extends AbstractList {
constructor() {
super();
this._deletedBans = [];
this.addEventListener("remove", (e) => this._evtBannedPostDeleted(e));
}
static get() {
return api
.get(uri.formatApiLink("post-ban"))
.then((response) => {
return Promise.resolve(
Object.assign({}, response, {
results: BannedPostList.fromResponse(
response.results
),
})
);
});
}
save() {
let promises = [];
for (let bannedPost of this._deletedBans) {
promises.push(bannedPost.delete());
}
return Promise.all(promises).then((response) => {
this._deletedBans = [];
return Promise.resolve();
});
}
_evtBannedPostDeleted(e) {
this._deletedBans.push(e.detail.bannedPost);
}
}
BannedPostList._itemClass = BannedPost;
BannedPostList._itemName = "bannedPost";
module.exports = BannedPostList;

View file

@ -334,6 +334,24 @@ class Post extends events.EventTarget {
}); });
} }
ban() {
return api
.post(uri.formatApiLink("post-ban"), {
post_id: this.id,
version: this._version
})
.then((response) => {
this.dispatchEvent(
new CustomEvent("ban", {
detail: {
post: this
}
})
);
return Promise.resolve();
})
}
delete() { delete() {
return api return api
.delete(uri.formatApiLink("post", this.id), { .delete(uri.formatApiLink("post", this.id), {

View file

@ -90,6 +90,7 @@ function _makeTopNavigation() {
ret.add("login", new TopNavigationItem("L", "Log in", "login")); ret.add("login", new TopNavigationItem("L", "Log in", "login"));
ret.add("logout", new TopNavigationItem("O", "Logout", "logout")); ret.add("logout", new TopNavigationItem("O", "Logout", "logout"));
ret.add("help", new TopNavigationItem("E", "Help", "help")); ret.add("help", new TopNavigationItem("E", "Help", "help"));
ret.add("banned-posts", new TopNavigationItem("B", "Banned posts", "banned-posts"));
ret.add( ret.add(
"settings", "settings",
new TopNavigationItem(null, "<i class='fa fa-cog'></i>", "settings") new TopNavigationItem(null, "<i class='fa fa-cog'></i>", "settings")

View file

@ -0,0 +1,103 @@
"use strict";
const events = require("../events.js");
const views = require("../util/views.js");
const BannedPost = require("../models/banned_post.js");
const template = views.getTemplate("banned-post-list");
const rowTemplate = views.getTemplate("banned-post-entry");
class BannedPostsView extends events.EventTarget {
constructor(ctx) {
super();
this._ctx = ctx;
this._hostNode = document.getElementById("content-holder");
views.replaceContent(this._hostNode, template(ctx));
views.syncScrollPosition();
views.decorateValidator(this._formNode);
const bannedPostsToAdd = Array.from(ctx.bannedPosts);
for (let bannedPost of bannedPostsToAdd) {
this._addBannedPostRowNode(bannedPost);
}
ctx.bannedPosts.addEventListener("remove", (e) =>
this._evtBannedPostDeleted(e)
);
this._formNode.addEventListener("submit", (e) =>
this._evtSaveButtonClick(e, ctx)
);
}
enableForm() {
views.enableForm(this._formNode);
}
disableForm() {
views.disableForm(this._formNode);
}
clearMessages() {
views.clearMessages(this._hostNode);
}
showSuccess(message) {
views.showSuccess(this._hostNode, message);
}
showError(message) {
views.showError(this._hostNode, message);
}
get _formNode() {
return this._hostNode.querySelector("form");
}
get _tableBodyNode() {
return this._hostNode.querySelector("tbody");
}
_addBannedPostRowNode(bannedPost) {
const rowNode = rowTemplate(
Object.assign({}, this._ctx, { postBan: bannedPost })
);
const removeLinkNode = rowNode.querySelector(".remove a");
if (removeLinkNode) {
removeLinkNode.addEventListener("click", (e) =>
this._evtDeleteButtonClick(e, rowNode)
);
}
this._tableBodyNode.appendChild(rowNode);
rowNode._bannedPost = bannedPost;
bannedPost._rowNode = rowNode;
}
_removeBannedPostRowNode(bannedPost) {
const rowNode = bannedPost._rowNode;
rowNode.parentNode.removeChild(rowNode);
}
_evtBannedPostDeleted(e) {
this._removeBannedPostRowNode(e.detail.bannedPost);
}
_evtDeleteButtonClick(e, rowNode, link) {
e.preventDefault();
if (e.target.classList.contains("inactive")) {
return;
}
this._ctx.bannedPosts.remove(rowNode._bannedPost);
}
_evtSaveButtonClick(e, ctx) {
e.preventDefault();
this.dispatchEvent(new CustomEvent("submit"));
}
}
module.exports = BannedPostsView;

View file

@ -39,6 +39,7 @@
- [Getting post](#getting-post) - [Getting post](#getting-post)
- [Getting around post](#getting-around-post) - [Getting around post](#getting-around-post)
- [Deleting post](#deleting-post) - [Deleting post](#deleting-post)
- [Banning post](#banning-post)
- [Merging posts](#merging-posts) - [Merging posts](#merging-posts)
- [Rating post](#rating-post) - [Rating post](#rating-post)
- [Adding post to favorites](#adding-post-to-favorites) - [Adding post to favorites](#adding-post-to-favorites)
@ -911,7 +912,7 @@ data.
- **Output** - **Output**
A [post resource](#post). A [post resource](#banned-post).
- **Errors** - **Errors**
@ -1004,6 +1005,81 @@ data.
Deletes existing post. Related posts and tags are kept. Deletes existing post. Related posts and tags are kept.
## Listing banned posts
- **Request**
`GET /post-ban`
- **Output**
An [unpaged search result](#unpaged-search-result) of [banned posts](#postban).
- **Errors**
- the post does not exist
- privileges are too low
- **Description**
Retrieves information about an existing post.
## Banning post
- **Request**
`POST /post-ban`
- **Input**
```json5
{
"post_id": <post id>
"version": <version>
}
```
- **Output**
```json5
{}
```
- **Errors**
- the version is outdated
- the post does not exist
- privileges are too low
- **Description**
Deletes existing post, then adds the sha1sum of the post file to a ban list.
Users will not be able to upload the file again.
Related posts and tags are kept.
## Undoing post ban
- **Request**
`DELETE /post-ban/<image_hash>`
- **Output**
```json5
{}
```
- **Errors**
- there is no banned image with that hash
- privileges are too low
- **Description**
Removes a banned image from the ban list. Takes a SHA-1 hash of the image as input.
## Merging posts ## Merging posts
- **Request** - **Request**
@ -2582,6 +2658,26 @@ An ordered list of posts, with a description and category.
A [pool resource](#pool) stripped down to `id`, `names`, `category`, A [pool resource](#pool) stripped down to `id`, `names`, `category`,
`description` and `postCount` fields. `description` and `postCount` fields.
## Banned post
**Description**
A record of a post that has been banned.
**Structure**
```json5
{
"checksum": <sha-hash>,
"time": <time-of-ban>
}
```
**Field meaning**
- `<sha-hash>`: SHA-1 hash of an image that has been banned
- `<time-of-ban>`: time the post was banned
## Comment ## Comment
**Description** **Description**

View file

@ -116,6 +116,9 @@ privileges:
'posts:bulk-edit:tags': power 'posts:bulk-edit:tags': power
'posts:bulk-edit:safety': power 'posts:bulk-edit:safety': power
'posts:bulk-edit:delete': power 'posts:bulk-edit:delete': power
'posts:ban:create': moderator
'posts:ban:delete': moderator
'posts:ban:list': moderator
'tags:create': regular 'tags:create': regular
'tags:edit:names': power 'tags:edit:names': power

View file

@ -10,3 +10,4 @@ import szurubooru.api.tag_category_api
import szurubooru.api.upload_api import szurubooru.api.upload_api
import szurubooru.api.user_api import szurubooru.api.user_api
import szurubooru.api.user_token_api import szurubooru.api.user_token_api
import szurubooru.api.ban_api

View file

@ -0,0 +1,58 @@
from datetime import datetime
from typing import Dict, List, Optional
from szurubooru.api import post_api
from szurubooru.func import posts
from szurubooru.model.bans import PostBan
from szurubooru import db, errors, model, rest, search
from szurubooru.func import (
auth,
bans,
serialization,
snapshots,
versions,
)
def _get_ban_by_hash(hash: str) -> Optional[PostBan]:
try:
return bans.get_bans_by_hash(hash)
except:
return None
_search_executor = search.Executor(search.configs.BanSearchConfig())
def _serialize(ctx: rest.Context, ban: model.PostBan) -> rest.Response:
return bans.serialize_ban(
ban, options=serialization.get_serialization_options(ctx)
)
@rest.routes.delete("/post-ban/(?P<image_hash>[^/]+)/?")
def unban_post(ctx: rest.Context, params: Dict[str, str]) -> rest.Response:
auth.verify_privilege(ctx.user, "posts:ban:delete")
ban = _get_ban_by_hash(params["image_hash"])
bans.delete(ban)
ctx.session.commit()
return {}
@rest.routes.post("/post-ban/?")
def ban_post(ctx: rest.Context, params: Dict[str, str]) -> rest.Response:
auth.verify_privilege(ctx.user, "posts:ban:create")
# post = post_api._get_post(params)
post = posts.get_post_by_id(ctx.get_param_as_int("post_id"))
versions.verify_version(post, ctx)
posts.ban(bans.create_ban(post))
snapshots.delete(post, ctx.user)
posts.delete(post)
ctx.session.commit()
return {}
@rest.routes.get("/post-ban/?")
def get_bans(ctx: rest.Context, _params: Dict[str, str] = {}) -> rest.Response:
auth.verify_privilege(ctx.user, "posts:ban:list")
return _search_executor.execute_and_serialize(
ctx, lambda ban: _serialize(ctx, ban)
)

View file

@ -0,0 +1,68 @@
from datetime import datetime
from typing import Any, Callable, Dict, List, Optional, Tuple
from szurubooru import db, errors, model, rest
from szurubooru.func import (
serialization,
)
class PostBannedError(errors.ValidationError):
def __init__(self, message: str = "This file was banned", extra_fields: Dict[str, str] = None) -> None:
super().__init__(message, extra_fields)
class HashNotBannedError(errors.ValidationError):
pass
class BanSerializer(serialization.BaseSerializer):
def __init__(self, ban: model.PostBan) -> None:
self.ban = ban
def _serializers(self) -> Dict[str, Callable[[], Any]]:
return {
"checksum": self.serialize_checksum,
"time": self.serialize_time
}
def serialize_checksum(self) -> Any:
return self.ban.checksum
def serialize_time(self) -> Any:
return self.ban.time
def create_ban(post: model.Post) -> model.PostBan:
ban = model.PostBan()
ban.checksum = post.checksum
ban.time = datetime.utcnow()
db.session.add(ban)
return ban
def try_get_ban_by_checksum(checksum: str) -> Optional[model.PostBan]:
return (
db.session.query(model.PostBan)
.filter(model.PostBan.checksum == checksum)
.one_or_none()
)
def get_bans_by_hash(hash: str) -> model.PostBan:
ban = try_get_ban_by_checksum(hash)
if ban is None:
raise HashNotBannedError("Hash %s is not banned" % hash)
return ban
def delete(ban: model.PostBan) -> None:
db.session.delete(ban)
def serialize_ban(
ban: model.PostBan, options: List[str] = []
) -> Optional[rest.Response]:
if not ban:
return None
return BanSerializer(ban).serialize(options)

View file

@ -4,6 +4,7 @@ from datetime import datetime
from typing import Any, Callable, Dict, List, Optional, Tuple from typing import Any, Callable, Dict, List, Optional, Tuple
import sqlalchemy as sa import sqlalchemy as sa
from szurubooru.func.bans import PostBannedError
from szurubooru import config, db, errors, model, rest from szurubooru import config, db, errors, model, rest
from szurubooru.func import ( from szurubooru.func import (
@ -50,6 +51,7 @@ class PostAlreadyUploadedError(errors.ValidationError):
) )
class InvalidPostIdError(errors.ValidationError): class InvalidPostIdError(errors.ValidationError):
pass pass
@ -634,6 +636,7 @@ def update_post_content(post: model.Post, content: Optional[bytes]) -> None:
.filter(model.Post.post_id != post.post_id) .filter(model.Post.post_id != post.post_id)
.one_or_none() .one_or_none()
) )
if ( if (
other_post other_post
and other_post.post_id and other_post.post_id
@ -641,6 +644,15 @@ def update_post_content(post: model.Post, content: Optional[bytes]) -> None:
): ):
raise PostAlreadyUploadedError(other_post) raise PostAlreadyUploadedError(other_post)
post_ban = (db.session.query(model.PostBan)
.filter(model.PostBan.checksum == post.checksum)
.one_or_none()
)
if (post_ban):
raise PostBannedError()
if update_signature: if update_signature:
purge_post_signature(post) purge_post_signature(post)
post.signature = generate_post_signature(post, content) post.signature = generate_post_signature(post, content)
@ -806,6 +818,11 @@ def delete(post: model.Post) -> None:
db.session.delete(post) db.session.delete(post)
def ban(ban: model.PostBan) -> None:
assert ban
db.session.add(ban)
def merge_posts( def merge_posts(
source_post: model.Post, target_post: model.Post, replace_content: bool source_post: model.Post, target_post: model.Post, replace_content: bool
) -> None: ) -> None:

View file

@ -0,0 +1,29 @@
'''
create ban table
Revision ID: cc2956cb8ee7
Created at: 2023-05-12 02:04:22.592006
'''
import sqlalchemy as sa
from alembic import op
revision = 'cc2956cb8ee7'
down_revision = 'adcd63ff76a2'
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
"post_ban",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("checksum", sa.Unicode(64), nullable=False),
sa.Column("time", sa.DateTime, nullable=False),
sa.PrimaryKeyConstraint("id")
)
op.create_unique_constraint("uq_ban_checksum", "post_ban", ["checksum"])
def downgrade():
op.drop_table("post_ban")

View file

@ -1,5 +1,6 @@
import szurubooru.model.util import szurubooru.model.util
from szurubooru.model.base import Base from szurubooru.model.base import Base
from szurubooru.model.bans import PostBan
from szurubooru.model.comment import Comment, CommentScore from szurubooru.model.comment import Comment, CommentScore
from szurubooru.model.pool import Pool, PoolName, PoolPost from szurubooru.model.pool import Pool, PoolName, PoolPost
from szurubooru.model.pool_category import PoolCategory from szurubooru.model.pool_category import PoolCategory

View file

@ -0,0 +1,12 @@
from typing import List
import sqlalchemy as sa
from szurubooru.model.base import Base
class PostBan(Base):
__tablename__ = "post_ban"
ban_id = sa.Column("id", sa.Integer, primary_key=True)
checksum = sa.Column("checksum", sa.Unicode(64), nullable=False)
time = sa.Column("time", sa.DateTime, nullable=False)

View file

@ -4,3 +4,4 @@ from .post_search_config import PostSearchConfig
from .snapshot_search_config import SnapshotSearchConfig from .snapshot_search_config import SnapshotSearchConfig
from .tag_search_config import TagSearchConfig from .tag_search_config import TagSearchConfig
from .user_search_config import UserSearchConfig from .user_search_config import UserSearchConfig
from .ban_search_config import BanSearchConfig

View file

@ -0,0 +1,67 @@
from typing import Dict, Tuple
import sqlalchemy as sa
from szurubooru import db, model
from szurubooru.func import util
from szurubooru.search.configs import util as search_util
from szurubooru.search.configs.base_search_config import (
BaseSearchConfig,
Filter,
)
from szurubooru.search.typing import SaColumn, SaQuery
class BanSearchConfig(BaseSearchConfig):
def create_filter_query(self, _disable_eager_loads: bool) -> SaQuery:
return db.session.query(model.PostBan)
def create_count_query(self, _disable_eager_loads: bool) -> SaQuery:
return db.session.query(model.PostBan)
def create_around_query(self) -> SaQuery:
raise NotImplementedError()
def finalize_query(self, query: SaQuery) -> SaQuery:
return query.order_by(model.PostBan.time.asc())
@property
def anonymous_filter(self) -> Filter:
return search_util.create_subquery_filter(
model.PostBan.checksum,
model.PostBan.time,
search_util.create_str_filter,
)
@property
def named_filters(self) -> Dict[str, Filter]:
return util.unalias_dict(
[
(
["time"],
search_util.create_date_filter(
model.PostBan.time,
),
),
(
["checksum"],
search_util.create_subquery_filter(
model.PostBan.checksum,
search_util.create_str_filter,
),
)
]
)
@property
def sort_columns(self) -> Dict[str, Tuple[SaColumn, str]]:
return util.unalias_dict(
[
(
["random"],
(sa.sql.expression.func.random(), self.SORT_NONE),
),
(["checksum"], (model.PostBan.checksum, self.SORT_ASC)),
(["time"], (model.PostBan.time, self.SORT_ASC))
]
)