Add list of posts to pools
This commit is contained in:
parent
d59ecb8e23
commit
e6bf102bc0
29 changed files with 267 additions and 117 deletions
|
@ -14,7 +14,7 @@
|
||||||
white-space: nowrap
|
white-space: nowrap
|
||||||
background: $top-navigation-color
|
background: $top-navigation-color
|
||||||
.names
|
.names
|
||||||
width: 28%
|
width: 84%
|
||||||
.usages
|
.usages
|
||||||
text-align: center
|
text-align: center
|
||||||
width: 8%
|
width: 8%
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
&:not(:last-child):after
|
&:not(:last-child):after
|
||||||
content: ', '
|
content: ', '
|
||||||
@media (max-width: 800px)
|
@media (max-width: 800px)
|
||||||
.implications, .suggestions
|
.posts
|
||||||
display: none
|
display: none
|
||||||
|
|
||||||
.pool-list-header
|
.pool-list-header
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>uploader</code></td>
|
<td><code>uploader</code></td>
|
||||||
<td>uploaded by given use (accepts wildcards)r</td>
|
<td>uploaded by given user (accepts wildcards)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>upload</code></td>
|
<td><code>upload</code></td>
|
||||||
|
@ -42,6 +42,10 @@
|
||||||
<td><code>source</code></td>
|
<td><code>source</code></td>
|
||||||
<td>having given source URL (accepts wildcards)</td>
|
<td>having given source URL (accepts wildcards)</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>pool</code></td>
|
||||||
|
<td>belonging to given pool ID</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>tag-count</code></td>
|
<td><code>tag-count</code></td>
|
||||||
<td>having given number of tags</td>
|
<td>having given number of tags</td>
|
||||||
|
|
|
@ -22,6 +22,12 @@
|
||||||
value: '',
|
value: '',
|
||||||
}) %>
|
}) %>
|
||||||
</li>
|
</li>
|
||||||
|
<li class='posts'>
|
||||||
|
<%= ctx.makeTextInput({
|
||||||
|
text: 'Posts',
|
||||||
|
value: '',
|
||||||
|
}) %>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<% if (ctx.canCreate) { %>
|
<% if (ctx.canCreate) { %>
|
||||||
|
|
|
@ -28,6 +28,14 @@
|
||||||
}) %>
|
}) %>
|
||||||
<% } %>
|
<% } %>
|
||||||
</li>
|
</li>
|
||||||
|
<li class='posts'>
|
||||||
|
<% if (ctx.canEditPosts) { %>
|
||||||
|
<%= ctx.makeTextInput({
|
||||||
|
text: 'Posts',
|
||||||
|
value: ctx.pool.posts.map(post => post.id).join(' ')
|
||||||
|
}) %>
|
||||||
|
<% } %>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<% if (ctx.canEditAnything) { %>
|
<% if (ctx.canEditAnything) { %>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<p>Posts between the two pools will be combined.
|
<p>Posts in the two pools will be combined.
|
||||||
Category needs to be handled manually.</p>
|
Category needs to be handled manually.</p>
|
||||||
|
|
||||||
<%= ctx.makeCheckbox({required: true, text: 'I confirm that I want to merge this pool.'}) %>
|
<%= ctx.makeCheckbox({required: true, text: 'I confirm that I want to merge this pool.'}) %>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
Aliases:<br/>
|
Aliases:<br/>
|
||||||
<ul><!--
|
<ul><!--
|
||||||
--><% for (let name of ctx.pool.names.slice(1)) { %><!--
|
--><% for (let name of ctx.pool.names.slice(1)) { %><!--
|
||||||
--><li><%= ctx.makePoolLink(ctx.pool, false, false, name) %></li><!--
|
--><li><%= ctx.makePoolLink(ctx.pool.id, false, false, ctx.pool, name) %></li><!--
|
||||||
--><% } %><!--
|
--><% } %><!--
|
||||||
--></ul>
|
--></ul>
|
||||||
</section>
|
</section>
|
||||||
|
@ -18,6 +18,6 @@
|
||||||
<section class='description'>
|
<section class='description'>
|
||||||
<hr/>
|
<hr/>
|
||||||
<%= ctx.makeMarkdown(ctx.pool.description || 'This pool has no description yet.') %>
|
<%= ctx.makeMarkdown(ctx.pool.description || 'This pool has no description yet.') %>
|
||||||
<p>This pool has <a href='<%- ctx.formatClientLink('posts', {query: ctx.escapeColons(ctx.pool.names[0])}) %>'><%- ctx.pool.postCount %> post(s)</a>.</p>
|
<p>This pool has <a href='<%- ctx.formatClientLink('posts', {query: 'pool:' + ctx.pool.id}) %>'><%- ctx.pool.postCount %> post(s)</a>.</p>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -30,12 +30,12 @@
|
||||||
<td class='names'>
|
<td class='names'>
|
||||||
<ul>
|
<ul>
|
||||||
<% for (let name of pool.names) { %>
|
<% for (let name of pool.names) { %>
|
||||||
<li><%= ctx.makePoolLink(pool, false, false, name) %></li>
|
<li><%= ctx.makePoolLink(pool.id, false, false, pool, name) %></li>
|
||||||
<% } %>
|
<% } %>
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
<td class='post-count'>
|
<td class='post-count'>
|
||||||
<a href='<%- ctx.formatClientLink('pools', {query: 'pool:' + pool.id}) %>'><%- pool.postCount %></a>
|
<a href='<%- ctx.formatClientLink('posts', {query: 'pool:' + pool.id}) %>'><%- pool.postCount %></a>
|
||||||
</td>
|
</td>
|
||||||
<td class='creation-time'>
|
<td class='creation-time'>
|
||||||
<%= ctx.makeRelativeTime(pool.creationTime) %>
|
<%= ctx.makeRelativeTime(pool.creationTime) %>
|
||||||
|
|
|
@ -5,6 +5,7 @@ const api = require('../api.js');
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
const uri = require('../util/uri.js');
|
const uri = require('../util/uri.js');
|
||||||
const Pool = require('../models/pool.js');
|
const Pool = require('../models/pool.js');
|
||||||
|
const Post = require('../models/post.js');
|
||||||
const PoolCategoryList = require('../models/pool_category_list.js');
|
const PoolCategoryList = require('../models/pool_category_list.js');
|
||||||
const topNavigation = require('../models/top_navigation.js');
|
const topNavigation = require('../models/top_navigation.js');
|
||||||
const PoolView = require('../views/pool_view.js');
|
const PoolView = require('../views/pool_view.js');
|
||||||
|
@ -42,6 +43,7 @@ class PoolController {
|
||||||
canEditNames: api.hasPrivilege('pools:edit:names'),
|
canEditNames: api.hasPrivilege('pools:edit:names'),
|
||||||
canEditCategory: api.hasPrivilege('pools:edit:category'),
|
canEditCategory: api.hasPrivilege('pools:edit:category'),
|
||||||
canEditDescription: api.hasPrivilege('pools:edit:description'),
|
canEditDescription: api.hasPrivilege('pools:edit:description'),
|
||||||
|
canEditPosts: api.hasPrivilege('pools:edit:posts'),
|
||||||
canMerge: api.hasPrivilege('pools:merge'),
|
canMerge: api.hasPrivilege('pools:merge'),
|
||||||
canDelete: api.hasPrivilege('pools:delete'),
|
canDelete: api.hasPrivilege('pools:delete'),
|
||||||
categories: categories,
|
categories: categories,
|
||||||
|
@ -84,6 +86,12 @@ class PoolController {
|
||||||
if (e.detail.description !== undefined) {
|
if (e.detail.description !== undefined) {
|
||||||
e.detail.pool.description = e.detail.description;
|
e.detail.pool.description = e.detail.description;
|
||||||
}
|
}
|
||||||
|
if (e.detail.posts !== undefined) {
|
||||||
|
e.detail.pool.posts.clear()
|
||||||
|
for (let post_id of e.detail.posts) {
|
||||||
|
e.detail.pool.posts.add(Post.fromResponse({ id: parseInt(post_id) }))
|
||||||
|
}
|
||||||
|
}
|
||||||
e.detail.pool.save().then(() => {
|
e.detail.pool.save().then(() => {
|
||||||
this._view.showSuccess('Pool saved.');
|
this._view.showSuccess('Pool saved.');
|
||||||
this._view.enableForm();
|
this._view.enableForm();
|
||||||
|
|
|
@ -39,7 +39,7 @@ class PoolCreateController {
|
||||||
_evtCreate(e) {
|
_evtCreate(e) {
|
||||||
this._view.clearMessages();
|
this._view.clearMessages();
|
||||||
this._view.disableForm();
|
this._view.disableForm();
|
||||||
e.detail.pool.save()
|
api.post(uri.formatApiLink('pool'), e.detail)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this._view.clearMessages();
|
this._view.clearMessages();
|
||||||
misc.disableExitConfirmation();
|
misc.disableExitConfirmation();
|
||||||
|
@ -50,10 +50,6 @@ class PoolCreateController {
|
||||||
this._view.enableForm();
|
this._view.enableForm();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_evtChange(e) {
|
|
||||||
misc.enableExitConfirmation();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = router => {
|
module.exports = router => {
|
||||||
|
|
|
@ -13,26 +13,26 @@ const EmptyView = require('../views/empty_view.js');
|
||||||
const fields = [
|
const fields = [
|
||||||
'id',
|
'id',
|
||||||
'names',
|
'names',
|
||||||
/* 'suggestions',
|
'posts',
|
||||||
* 'implications', */
|
|
||||||
'creationTime',
|
'creationTime',
|
||||||
'postCount',
|
'postCount',
|
||||||
'category'];
|
'category'];
|
||||||
|
|
||||||
class PoolListController {
|
class PoolListController {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
|
this._pageController = new PageController();
|
||||||
|
|
||||||
if (!api.hasPrivilege('pools:list')) {
|
if (!api.hasPrivilege('pools:list')) {
|
||||||
this._view = new EmptyView();
|
this._view = new EmptyView();
|
||||||
this._view.showError('You don\'t have privileges to view pools.');
|
this._view.showError('You don\'t have privileges to view pools.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._ctx = ctx;
|
||||||
|
|
||||||
topNavigation.activate('pools');
|
topNavigation.activate('pools');
|
||||||
topNavigation.setTitle('Listing pools');
|
topNavigation.setTitle('Listing pools');
|
||||||
|
|
||||||
this._ctx = ctx;
|
|
||||||
this._pageController = new PageController();
|
|
||||||
|
|
||||||
this._headerView = new PoolsHeaderView({
|
this._headerView = new PoolsHeaderView({
|
||||||
hostNode: this._pageController.view.pageHeaderHolderNode,
|
hostNode: this._pageController.view.pageHeaderHolderNode,
|
||||||
parameters: ctx.parameters,
|
parameters: ctx.parameters,
|
||||||
|
|
|
@ -17,18 +17,19 @@ const fields = [
|
||||||
|
|
||||||
class PostListController {
|
class PostListController {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
|
this._pageController = new PageController();
|
||||||
|
|
||||||
if (!api.hasPrivilege('posts:list')) {
|
if (!api.hasPrivilege('posts:list')) {
|
||||||
this._view = new EmptyView();
|
this._view = new EmptyView();
|
||||||
this._view.showError('You don\'t have privileges to view posts.');
|
this._view.showError('You don\'t have privileges to view posts.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._ctx = ctx;
|
||||||
|
|
||||||
topNavigation.activate('posts');
|
topNavigation.activate('posts');
|
||||||
topNavigation.setTitle('Listing posts');
|
topNavigation.setTitle('Listing posts');
|
||||||
|
|
||||||
this._ctx = ctx;
|
|
||||||
this._pageController = new PageController();
|
|
||||||
|
|
||||||
this._headerView = new PostsHeaderView({
|
this._headerView = new PostsHeaderView({
|
||||||
hostNode: this._pageController.view.pageHeaderHolderNode,
|
hostNode: this._pageController.view.pageHeaderHolderNode,
|
||||||
parameters: ctx.parameters,
|
parameters: ctx.parameters,
|
||||||
|
|
|
@ -20,18 +20,19 @@ const fields = [
|
||||||
|
|
||||||
class TagListController {
|
class TagListController {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
|
this._pageController = new PageController();
|
||||||
|
|
||||||
if (!api.hasPrivilege('tags:list')) {
|
if (!api.hasPrivilege('tags:list')) {
|
||||||
this._view = new EmptyView();
|
this._view = new EmptyView();
|
||||||
this._view.showError('You don\'t have privileges to view tags.');
|
this._view.showError('You don\'t have privileges to view tags.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._ctx = ctx;
|
||||||
|
|
||||||
topNavigation.activate('tags');
|
topNavigation.activate('tags');
|
||||||
topNavigation.setTitle('Listing tags');
|
topNavigation.setTitle('Listing tags');
|
||||||
|
|
||||||
this._ctx = ctx;
|
|
||||||
this._pageController = new PageController();
|
|
||||||
|
|
||||||
this._headerView = new TagsHeaderView({
|
this._headerView = new TagsHeaderView({
|
||||||
hostNode: this._pageController.view.pageHeaderHolderNode,
|
hostNode: this._pageController.view.pageHeaderHolderNode,
|
||||||
parameters: ctx.parameters,
|
parameters: ctx.parameters,
|
||||||
|
|
|
@ -12,6 +12,8 @@ const EmptyView = require('../views/empty_view.js');
|
||||||
|
|
||||||
class UserListController {
|
class UserListController {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
|
this._pageController = new PageController();
|
||||||
|
|
||||||
if (!api.hasPrivilege('users:list')) {
|
if (!api.hasPrivilege('users:list')) {
|
||||||
this._view = new EmptyView();
|
this._view = new EmptyView();
|
||||||
this._view.showError('You don\'t have privileges to view users.');
|
this._view.showError('You don\'t have privileges to view users.');
|
||||||
|
@ -22,7 +24,6 @@ class UserListController {
|
||||||
topNavigation.setTitle('Listing users');
|
topNavigation.setTitle('Listing users');
|
||||||
|
|
||||||
this._ctx = ctx;
|
this._ctx = ctx;
|
||||||
this._pageController = new PageController();
|
|
||||||
|
|
||||||
this._headerView = new UsersHeaderView({
|
this._headerView = new UsersHeaderView({
|
||||||
hostNode: this._pageController.view.pageHeaderHolderNode,
|
hostNode: this._pageController.view.pageHeaderHolderNode,
|
||||||
|
|
|
@ -9,10 +9,6 @@ function _poolListToMatches(pools, options) {
|
||||||
return pool2.postCount - pool1.postCount;
|
return pool2.postCount - pool1.postCount;
|
||||||
}).map(pool => {
|
}).map(pool => {
|
||||||
let cssName = misc.makeCssName(pool.category, 'pool');
|
let cssName = misc.makeCssName(pool.category, 'pool');
|
||||||
// TODO
|
|
||||||
if (options.isPooledWith(pool.id)) {
|
|
||||||
cssName += ' disabled';
|
|
||||||
}
|
|
||||||
const caption = (
|
const caption = (
|
||||||
'<span class="' + cssName + '">'
|
'<span class="' + cssName + '">'
|
||||||
+ misc.escapeHtml(pool.names[0] + ' (' + pool.postCount + ')')
|
+ misc.escapeHtml(pool.names[0] + ' (' + pool.postCount + ')')
|
||||||
|
@ -28,10 +24,6 @@ class PoolAutoCompleteControl extends AutoCompleteControl {
|
||||||
constructor(input, options) {
|
constructor(input, options) {
|
||||||
const minLengthForPartialSearch = 3;
|
const minLengthForPartialSearch = 3;
|
||||||
|
|
||||||
options = Object.assign({
|
|
||||||
isPooledWith: poolId => false,
|
|
||||||
}, options);
|
|
||||||
|
|
||||||
options.getMatches = text => {
|
options.getMatches = text => {
|
||||||
const term = misc.escapeSearchTerm(text);
|
const term = misc.escapeSearchTerm(text);
|
||||||
const query = (
|
const query = (
|
||||||
|
|
|
@ -7,15 +7,13 @@ const misc = require('../util/misc.js');
|
||||||
|
|
||||||
class Pool extends events.EventTarget {
|
class Pool extends events.EventTarget {
|
||||||
constructor() {
|
constructor() {
|
||||||
// const PoolList = require('./pool_list.js');
|
const PostList = require('./post_list.js');
|
||||||
|
|
||||||
super();
|
super();
|
||||||
this._orig = {};
|
this._orig = {};
|
||||||
|
|
||||||
for (let obj of [this, this._orig]) {
|
for (let obj of [this, this._orig]) {
|
||||||
// TODO
|
obj._posts = new PostList();
|
||||||
// obj._suggestions = new PoolList();
|
|
||||||
// obj._implications = new PoolList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._updateFromResponse({});
|
this._updateFromResponse({});
|
||||||
|
@ -25,8 +23,7 @@ class Pool extends events.EventTarget {
|
||||||
get names() { return this._names; }
|
get names() { return this._names; }
|
||||||
get category() { return this._category; }
|
get category() { return this._category; }
|
||||||
get description() { return this._description; }
|
get description() { return this._description; }
|
||||||
/* get suggestions() { return this._suggestions; }
|
get posts() { return this._posts; }
|
||||||
* get implications() { return this._implications; } */
|
|
||||||
get postCount() { return this._postCount; }
|
get postCount() { return this._postCount; }
|
||||||
get creationTime() { return this._creationTime; }
|
get creationTime() { return this._creationTime; }
|
||||||
get lastEditTime() { return this._lastEditTime; }
|
get lastEditTime() { return this._lastEditTime; }
|
||||||
|
@ -61,15 +58,9 @@ class Pool extends events.EventTarget {
|
||||||
if (this._description !== this._orig._description) {
|
if (this._description !== this._orig._description) {
|
||||||
detail.description = this._description;
|
detail.description = this._description;
|
||||||
}
|
}
|
||||||
// TODO
|
if (misc.arraysDiffer(this._posts, this._orig._posts)) {
|
||||||
// if (misc.arraysDiffer(this._implications, this._orig._implications)) {
|
detail.posts = this._posts.map(post => post.id);
|
||||||
// detail.implications = this._implications.map(
|
}
|
||||||
// relation => relation.names[0]);
|
|
||||||
// }
|
|
||||||
// if (misc.arraysDiffer(this._suggestions, this._orig._suggestions)) {
|
|
||||||
// detail.suggestions = this._suggestions.map(
|
|
||||||
// relation => relation.names[0]);
|
|
||||||
// }
|
|
||||||
|
|
||||||
let promise = this._id ?
|
let promise = this._id ?
|
||||||
api.put(uri.formatApiLink('pool', this._id), detail) :
|
api.put(uri.formatApiLink('pool', this._id), detail) :
|
||||||
|
@ -138,13 +129,11 @@ class Pool extends events.EventTarget {
|
||||||
_description: response.description,
|
_description: response.description,
|
||||||
_creationTime: response.creationTime,
|
_creationTime: response.creationTime,
|
||||||
_lastEditTime: response.lastEditTime,
|
_lastEditTime: response.lastEditTime,
|
||||||
_postCount: response.usages || 0,
|
_postCount: response.postCount || 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let obj of [this, this._orig]) {
|
for (let obj of [this, this._orig]) {
|
||||||
// TODO
|
obj._posts.sync(response.posts);
|
||||||
// obj._suggestions.sync(response.suggestions);
|
|
||||||
// obj._implications.sync(response.implications);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(this, map);
|
Object.assign(this, map);
|
||||||
|
|
|
@ -163,11 +163,6 @@ function escapeHtml(unsafe) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function arraysDiffer(source1, source2, orderImportant) {
|
function arraysDiffer(source1, source2, orderImportant) {
|
||||||
if ((source1 instanceof Array && source2 === undefined)
|
|
||||||
|| (source1 === undefined && source2 instanceof Array)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
source1 = [...source1];
|
source1 = [...source1];
|
||||||
source2 = [...source2];
|
source2 = [...source2];
|
||||||
if (orderImportant === true) {
|
if (orderImportant === true) {
|
||||||
|
|
|
@ -221,20 +221,20 @@ function makeTagLink(name, includeHash, includeCount, tag) {
|
||||||
misc.escapeHtml(text));
|
misc.escapeHtml(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
function makePoolLink(pool, includeHash, includeCount, name) {
|
function makePoolLink(id, includeHash, includeCount, pool, name) {
|
||||||
const category = pool.category;
|
const category = pool ? pool.category : 'unknown';
|
||||||
let text = name ? name : pool.names[0];
|
let text = name ? name : pool.names[0];
|
||||||
if (includeHash === true) {
|
if (includeHash === true) {
|
||||||
text = '#' + text;
|
text = '#' + text;
|
||||||
}
|
}
|
||||||
if (includeCount === true) {
|
if (includeCount === true) {
|
||||||
text += ' (' + pool.postCount + ')';
|
text += ' (' + (pool ? pool.postCount : 0) + ')';
|
||||||
}
|
}
|
||||||
return api.hasPrivilege('pools:view') ?
|
return api.hasPrivilege('pools:view') ?
|
||||||
makeElement(
|
makeElement(
|
||||||
'a',
|
'a',
|
||||||
{
|
{
|
||||||
href: uri.formatClientLink('pool', pool.id),
|
href: uri.formatClientLink('pool', id),
|
||||||
class: misc.makeCssName(category, 'pool'),
|
class: misc.makeCssName(category, 'pool'),
|
||||||
},
|
},
|
||||||
misc.escapeHtml(text)) :
|
misc.escapeHtml(text)) :
|
||||||
|
|
|
@ -22,8 +22,13 @@ class PoolCreateView extends events.EventTarget {
|
||||||
'input', e => this._evtNameInput(e));
|
'input', e => this._evtNameInput(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._postsFieldNode) {
|
||||||
|
this._postsFieldNode.addEventListener(
|
||||||
|
'input', e => this._evtPostsInput(e));
|
||||||
|
}
|
||||||
|
|
||||||
for (let node of this._formNode.querySelectorAll(
|
for (let node of this._formNode.querySelectorAll(
|
||||||
'input, select, textarea')) {
|
'input, select, textarea, posts')) {
|
||||||
node.addEventListener(
|
node.addEventListener(
|
||||||
'change', e => {
|
'change', e => {
|
||||||
this.dispatchEvent(new CustomEvent('change'));
|
this.dispatchEvent(new CustomEvent('change'));
|
||||||
|
@ -74,16 +79,31 @@ class PoolCreateView extends events.EventTarget {
|
||||||
this._namesFieldNode.setCustomValidity('');
|
this._namesFieldNode.setCustomValidity('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_evtPostsInput(e) {
|
||||||
|
const regex = /^\d+$/;
|
||||||
|
const list = misc.splitByWhitespace(this._postsFieldNode.value);
|
||||||
|
|
||||||
|
for (let item of list) {
|
||||||
|
if (!regex.test(item)) {
|
||||||
|
this._postsFieldNode.setCustomValidity(
|
||||||
|
`Pool ID "${item}" is not an integer.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._postsFieldNode.setCustomValidity('');
|
||||||
|
}
|
||||||
|
|
||||||
_evtSubmit(e) {
|
_evtSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let pool = new Pool()
|
|
||||||
pool.names = misc.splitByWhitespace(this._namesFieldNode.value);
|
|
||||||
pool.category = this._categoryFieldNode.value;
|
|
||||||
pool.description = this._descriptionFieldNode.value;
|
|
||||||
|
|
||||||
this.dispatchEvent(new CustomEvent('submit', {
|
this.dispatchEvent(new CustomEvent('submit', {
|
||||||
detail: {
|
detail: {
|
||||||
pool: pool,
|
names: misc.splitByWhitespace(this._namesFieldNode.value),
|
||||||
|
category: this._categoryFieldNode.value,
|
||||||
|
description: this._descriptionFieldNode.value,
|
||||||
|
posts: misc.splitByWhitespace(this._postsFieldNode.value)
|
||||||
|
.map(i => parseInt(i, 10))
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -103,6 +123,10 @@ class PoolCreateView extends events.EventTarget {
|
||||||
get _descriptionFieldNode() {
|
get _descriptionFieldNode() {
|
||||||
return this._formNode.querySelector('.description textarea');
|
return this._formNode.querySelector('.description textarea');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _postsFieldNode() {
|
||||||
|
return this._formNode.querySelector('.posts input');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PoolCreateView;
|
module.exports = PoolCreateView;
|
||||||
|
|
|
@ -4,6 +4,7 @@ const events = require('../events.js');
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
const views = require('../util/views.js');
|
const views = require('../util/views.js');
|
||||||
|
const Post = require('../models/post.js');
|
||||||
|
|
||||||
const template = views.getTemplate('pool-edit');
|
const template = views.getTemplate('pool-edit');
|
||||||
|
|
||||||
|
@ -22,8 +23,13 @@ class PoolEditView extends events.EventTarget {
|
||||||
'input', e => this._evtNameInput(e));
|
'input', e => this._evtNameInput(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._postsFieldNode) {
|
||||||
|
this._postsFieldNode.addEventListener(
|
||||||
|
'input', e => this._evtPostsInput(e));
|
||||||
|
}
|
||||||
|
|
||||||
for (let node of this._formNode.querySelectorAll(
|
for (let node of this._formNode.querySelectorAll(
|
||||||
'input, select, textarea')) {
|
'input, select, textarea, posts')) {
|
||||||
node.addEventListener(
|
node.addEventListener(
|
||||||
'change', e => {
|
'change', e => {
|
||||||
this.dispatchEvent(new CustomEvent('change'));
|
this.dispatchEvent(new CustomEvent('change'));
|
||||||
|
@ -74,6 +80,21 @@ class PoolEditView extends events.EventTarget {
|
||||||
this._namesFieldNode.setCustomValidity('');
|
this._namesFieldNode.setCustomValidity('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_evtPostsInput(e) {
|
||||||
|
const regex = /^\d+$/;
|
||||||
|
const list = misc.splitByWhitespace(this._postsFieldNode.value);
|
||||||
|
|
||||||
|
for (let item of list) {
|
||||||
|
if (!regex.test(item)) {
|
||||||
|
this._postsFieldNode.setCustomValidity(
|
||||||
|
`Pool ID "${item}" is not an integer.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._postsFieldNode.setCustomValidity('');
|
||||||
|
}
|
||||||
|
|
||||||
_evtSubmit(e) {
|
_evtSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.dispatchEvent(new CustomEvent('submit', {
|
this.dispatchEvent(new CustomEvent('submit', {
|
||||||
|
@ -91,6 +112,10 @@ class PoolEditView extends events.EventTarget {
|
||||||
description: this._descriptionFieldNode ?
|
description: this._descriptionFieldNode ?
|
||||||
this._descriptionFieldNode.value :
|
this._descriptionFieldNode.value :
|
||||||
undefined,
|
undefined,
|
||||||
|
|
||||||
|
posts: this._postsFieldNode ?
|
||||||
|
misc.splitByWhitespace(this._postsFieldNode.value) :
|
||||||
|
undefined,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -110,6 +135,10 @@ class PoolEditView extends events.EventTarget {
|
||||||
get _descriptionFieldNode() {
|
get _descriptionFieldNode() {
|
||||||
return this._formNode.querySelector('.description textarea');
|
return this._formNode.querySelector('.description textarea');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _postsFieldNode() {
|
||||||
|
return this._formNode.querySelector('.posts input');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PoolEditView;
|
module.exports = PoolEditView;
|
||||||
|
|
|
@ -36,6 +36,8 @@ function _makeResourceLink(type, id) {
|
||||||
return views.makeTagLink(id, true);
|
return views.makeTagLink(id, true);
|
||||||
} else if (type === 'tag_category') {
|
} else if (type === 'tag_category') {
|
||||||
return 'category "' + id + '"';
|
return 'category "' + id + '"';
|
||||||
|
} else if (type === 'pool') {
|
||||||
|
return views.makePoolLink(id, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,6 +115,19 @@ function _makeItemModification(type, data) {
|
||||||
if (diff.flags) {
|
if (diff.flags) {
|
||||||
_extend(lines, ['Changed flags']);
|
_extend(lines, ['Changed flags']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if (type === 'pool') {
|
||||||
|
if (diff.names) {
|
||||||
|
_extend(lines, _formatBasicChange(diff.names, 'names'));
|
||||||
|
}
|
||||||
|
if (diff.category) {
|
||||||
|
_extend(
|
||||||
|
lines, _formatBasicChange(diff.category, 'category'));
|
||||||
|
}
|
||||||
|
if (diff.posts) {
|
||||||
|
_extend(
|
||||||
|
lines, _formatBasicChange(diff.posts, 'posts'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('<br/>');
|
return lines.join('<br/>');
|
||||||
|
|
|
@ -129,6 +129,10 @@ privileges:
|
||||||
'tag_categories:set_default': moderator
|
'tag_categories:set_default': moderator
|
||||||
|
|
||||||
'pools:create': regular
|
'pools:create': regular
|
||||||
|
'pools:edit:names': power
|
||||||
|
'pools:edit:category': power
|
||||||
|
'pools:edit:description': power
|
||||||
|
'pools:edit:posts': power
|
||||||
'pools:list': regular
|
'pools:list': regular
|
||||||
'pools:view': anonymous
|
'pools:view': anonymous
|
||||||
'pools:merge': moderator
|
'pools:merge': moderator
|
||||||
|
|
|
@ -16,17 +16,6 @@ def _get_pool(params: Dict[str, str]) -> model.Pool:
|
||||||
return pools.get_pool_by_id(params['pool_id'])
|
return pools.get_pool_by_id(params['pool_id'])
|
||||||
|
|
||||||
|
|
||||||
# def _create_if_needed(pool_names: List[str], user: model.User) -> None:
|
|
||||||
# if not pool_names:
|
|
||||||
# return
|
|
||||||
# _existing_pools, new_pools = pools.get_or_create_pools_by_names(pool_names)
|
|
||||||
# if len(new_pools):
|
|
||||||
# auth.verify_privilege(user, 'pools:create')
|
|
||||||
# db.session.flush()
|
|
||||||
# for pool in new_pools:
|
|
||||||
# snapshots.create(pool, user)
|
|
||||||
|
|
||||||
|
|
||||||
@rest.routes.get('/pools/?')
|
@rest.routes.get('/pools/?')
|
||||||
def get_pools(ctx: rest.Context, _params: Dict[str, str] = {}) -> rest.Response:
|
def get_pools(ctx: rest.Context, _params: Dict[str, str] = {}) -> rest.Response:
|
||||||
auth.verify_privilege(ctx.user, 'pools:list')
|
auth.verify_privilege(ctx.user, 'pools:list')
|
||||||
|
@ -34,7 +23,7 @@ def get_pools(ctx: rest.Context, _params: Dict[str, str] = {}) -> rest.Response:
|
||||||
ctx, lambda pool: _serialize(ctx, pool))
|
ctx, lambda pool: _serialize(ctx, pool))
|
||||||
|
|
||||||
|
|
||||||
@rest.routes.post('/pools/?')
|
@rest.routes.post('/pool/?')
|
||||||
def create_pool(
|
def create_pool(
|
||||||
ctx: rest.Context, _params: Dict[str, str] = {}) -> rest.Response:
|
ctx: rest.Context, _params: Dict[str, str] = {}) -> rest.Response:
|
||||||
auth.verify_privilege(ctx.user, 'pools:create')
|
auth.verify_privilege(ctx.user, 'pools:create')
|
||||||
|
@ -42,14 +31,9 @@ def create_pool(
|
||||||
names = ctx.get_param_as_string_list('names')
|
names = ctx.get_param_as_string_list('names')
|
||||||
category = ctx.get_param_as_string('category')
|
category = ctx.get_param_as_string('category')
|
||||||
description = ctx.get_param_as_string('description', default='')
|
description = ctx.get_param_as_string('description', default='')
|
||||||
# TODO
|
posts = ctx.get_param_as_int_list('posts', default=[])
|
||||||
# suggestions = ctx.get_param_as_string_list('suggestions', default=[])
|
|
||||||
# implications = ctx.get_param_as_string_list('implications', default=[])
|
|
||||||
|
|
||||||
# _create_if_needed(suggestions, ctx.user)
|
pool = pools.create_pool(names, category, posts)
|
||||||
# _create_if_needed(implications, ctx.user)
|
|
||||||
|
|
||||||
pool = pools.create_pool(names, category)
|
|
||||||
pools.update_pool_description(pool, description)
|
pools.update_pool_description(pool, description)
|
||||||
ctx.session.add(pool)
|
ctx.session.add(pool)
|
||||||
ctx.session.flush()
|
ctx.session.flush()
|
||||||
|
@ -81,17 +65,10 @@ def update_pool(ctx: rest.Context, params: Dict[str, str]) -> rest.Response:
|
||||||
auth.verify_privilege(ctx.user, 'pools:edit:description')
|
auth.verify_privilege(ctx.user, 'pools:edit:description')
|
||||||
pools.update_pool_description(
|
pools.update_pool_description(
|
||||||
pool, ctx.get_param_as_string('description'))
|
pool, ctx.get_param_as_string('description'))
|
||||||
# TODO
|
if ctx.has_param('posts'):
|
||||||
# if ctx.has_param('suggestions'):
|
auth.verify_privilege(ctx.user, 'pools:edit:posts')
|
||||||
# auth.verify_privilege(ctx.user, 'pools:edit:suggestions')
|
posts = ctx.get_param_as_int_list('posts')
|
||||||
# suggestions = ctx.get_param_as_string_list('suggestions')
|
pools.update_pool_posts(pool, posts)
|
||||||
# _create_if_needed(suggestions, ctx.user)
|
|
||||||
# pools.update_pool_suggestions(pool, suggestions)
|
|
||||||
# if ctx.has_param('implications'):
|
|
||||||
# auth.verify_privilege(ctx.user, 'pools:edit:implications')
|
|
||||||
# implications = ctx.get_param_as_string_list('implications')
|
|
||||||
# _create_if_needed(implications, ctx.user)
|
|
||||||
# pools.update_pool_implications(pool, implications)
|
|
||||||
pool.last_edit_time = datetime.utcnow()
|
pool.last_edit_time = datetime.utcnow()
|
||||||
ctx.session.flush()
|
ctx.session.flush()
|
||||||
snapshots.modify(pool, ctx.user)
|
snapshots.modify(pool, ctx.user)
|
||||||
|
|
|
@ -3,7 +3,7 @@ from typing import Any, Optional, Tuple, List, Dict, Callable
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from szurubooru import config, db, model, errors, rest
|
from szurubooru import config, db, model, errors, rest
|
||||||
from szurubooru.func import util, pool_categories, serialization
|
from szurubooru.func import util, pool_categories, serialization, posts
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ class InvalidPoolNameError(errors.ValidationError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidPoolRelationError(errors.ValidationError):
|
class InvalidPoolDuplicateError(errors.ValidationError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,6 +60,10 @@ def _check_name_intersection(
|
||||||
return len(set(names1).intersection(names2)) > 0
|
return len(set(names1).intersection(names2)) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def _check_post_duplication(post_ids: List[int]) -> bool:
|
||||||
|
return len(post_ids) != len(set(post_ids))
|
||||||
|
|
||||||
|
|
||||||
def sort_pools(pools: List[model.Pool]) -> List[model.Pool]:
|
def sort_pools(pools: List[model.Pool]) -> List[model.Pool]:
|
||||||
default_category_name = pool_categories.get_default_category_name()
|
default_category_name = pool_categories.get_default_category_name()
|
||||||
return sorted(
|
return sorted(
|
||||||
|
@ -84,7 +88,8 @@ class PoolSerializer(serialization.BaseSerializer):
|
||||||
'description': self.serialize_description,
|
'description': self.serialize_description,
|
||||||
'creationTime': self.serialize_creation_time,
|
'creationTime': self.serialize_creation_time,
|
||||||
'lastEditTime': self.serialize_last_edit_time,
|
'lastEditTime': self.serialize_last_edit_time,
|
||||||
'postCount': self.serialize_post_count
|
'postCount': self.serialize_post_count,
|
||||||
|
'posts': self.serialize_posts
|
||||||
}
|
}
|
||||||
|
|
||||||
def serialize_id(self) -> Any:
|
def serialize_id(self) -> Any:
|
||||||
|
@ -111,6 +116,13 @@ class PoolSerializer(serialization.BaseSerializer):
|
||||||
def serialize_post_count(self) -> Any:
|
def serialize_post_count(self) -> Any:
|
||||||
return self.pool.post_count
|
return self.pool.post_count
|
||||||
|
|
||||||
|
def serialize_posts(self) -> Any:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'id': post.post_id
|
||||||
|
}
|
||||||
|
for post in self.pool.posts]
|
||||||
|
|
||||||
|
|
||||||
def serialize_pool(
|
def serialize_pool(
|
||||||
pool: model.Pool, options: List[str] = []) -> Optional[rest.Response]:
|
pool: model.Pool, options: List[str] = []) -> Optional[rest.Response]:
|
||||||
|
@ -180,7 +192,8 @@ def get_or_create_pools_by_names(
|
||||||
if not found:
|
if not found:
|
||||||
new_pool = create_pool(
|
new_pool = create_pool(
|
||||||
names=[name],
|
names=[name],
|
||||||
category_name=pool_category_name)
|
category_name=pool_category_name,
|
||||||
|
post_ids=[])
|
||||||
db.session.add(new_pool)
|
db.session.add(new_pool)
|
||||||
new_pools.append(new_pool)
|
new_pools.append(new_pool)
|
||||||
return existing_pools, new_pools
|
return existing_pools, new_pools
|
||||||
|
@ -245,11 +258,13 @@ def merge_pools(source_pool: model.Pool, target_pool: model.Pool) -> None:
|
||||||
|
|
||||||
def create_pool(
|
def create_pool(
|
||||||
names: List[str],
|
names: List[str],
|
||||||
category_name: str) -> model.Pool:
|
category_name: str,
|
||||||
|
post_ids: List[int]) -> model.Pool:
|
||||||
pool = model.Pool()
|
pool = model.Pool()
|
||||||
pool.creation_time = datetime.utcnow()
|
pool.creation_time = datetime.utcnow()
|
||||||
update_pool_names(pool, names)
|
update_pool_names(pool, names)
|
||||||
update_pool_category_name(pool, category_name)
|
update_pool_category_name(pool, category_name)
|
||||||
|
update_pool_posts(pool, post_ids)
|
||||||
return pool
|
return pool
|
||||||
|
|
||||||
|
|
||||||
|
@ -299,4 +314,13 @@ def update_pool_description(pool: model.Pool, description: str) -> None:
|
||||||
if util.value_exceeds_column_size(description, model.Pool.description):
|
if util.value_exceeds_column_size(description, model.Pool.description):
|
||||||
raise InvalidPoolDescriptionError('Description is too long.')
|
raise InvalidPoolDescriptionError('Description is too long.')
|
||||||
pool.description = description or None
|
pool.description = description or None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def update_pool_posts(pool: model.Pool, post_ids: List[int]) -> None:
|
||||||
|
assert pool
|
||||||
|
if _check_post_duplication(post_ids):
|
||||||
|
raise InvalidPoolDuplicateError('Duplicate post in pool.')
|
||||||
|
pool.posts.clear()
|
||||||
|
for post in posts.get_posts_by_ids(post_ids):
|
||||||
|
pool.posts.append(post)
|
||||||
|
|
|
@ -334,6 +334,22 @@ def get_post_by_id(post_id: int) -> model.Post:
|
||||||
return post
|
return post
|
||||||
|
|
||||||
|
|
||||||
|
def get_posts_by_ids(ids: List[int]) -> List[model.Pool]:
|
||||||
|
if len(ids) == 0:
|
||||||
|
return []
|
||||||
|
posts = (
|
||||||
|
db.session.query(model.Post)
|
||||||
|
.filter(
|
||||||
|
sa.sql.or_(
|
||||||
|
model.Post.post_id == post_id
|
||||||
|
for post_id in ids))
|
||||||
|
.all())
|
||||||
|
id_order = {
|
||||||
|
v: k for k, v in enumerate(ids)
|
||||||
|
}
|
||||||
|
return sorted(posts, key=lambda post: id_order.get(post.post_id))
|
||||||
|
|
||||||
|
|
||||||
def try_get_current_post_feature() -> Optional[model.PostFeature]:
|
def try_get_current_post_feature() -> Optional[model.PostFeature]:
|
||||||
return (
|
return (
|
||||||
db.session
|
db.session
|
||||||
|
|
|
@ -38,7 +38,7 @@ def get_pool_snapshot(pool: model.Pool) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
'names': [pool_name.name for pool_name in pool.names],
|
'names': [pool_name.name for pool_name in pool.names],
|
||||||
'category': pool.category.name,
|
'category': pool.category.name,
|
||||||
# TODO
|
'posts': [post.post_id for post in pool.posts]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -45,8 +45,18 @@ def upgrade():
|
||||||
sa.PrimaryKeyConstraint('pool_name_id'),
|
sa.PrimaryKeyConstraint('pool_name_id'),
|
||||||
sa.UniqueConstraint('name'))
|
sa.UniqueConstraint('name'))
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'pool_post',
|
||||||
|
sa.Column('pool_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('post_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('ord', sa.Integer(), nullable=False, index=True),
|
||||||
|
sa.ForeignKeyConstraint(['pool_id'], ['pool.id'], ondelete='CASCADE'),
|
||||||
|
sa.ForeignKeyConstraint(['post_id'], ['post.id'], ondelete='CASCADE'),
|
||||||
|
sa.PrimaryKeyConstraint('pool_id', 'post_id'))
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
op.drop_index(op.f('ix_pool_name_ord'), table_name='pool_name')
|
op.drop_index(op.f('ix_pool_name_ord'), table_name='pool_name')
|
||||||
|
op.drop_table('pool_post')
|
||||||
op.drop_table('pool_name')
|
op.drop_table('pool_name')
|
||||||
op.drop_table('pool')
|
op.drop_table('pool')
|
||||||
op.drop_table('pool_category')
|
op.drop_table('pool_category')
|
||||||
|
|
|
@ -11,7 +11,7 @@ from szurubooru.model.post import (
|
||||||
PostNote,
|
PostNote,
|
||||||
PostFeature,
|
PostFeature,
|
||||||
PostSignature)
|
PostSignature)
|
||||||
from szurubooru.model.pool import Pool, PoolName
|
from szurubooru.model.pool import Pool, PoolName, PoolPost
|
||||||
from szurubooru.model.pool_category import PoolCategory
|
from szurubooru.model.pool_category import PoolCategory
|
||||||
from szurubooru.model.comment import Comment, CommentScore
|
from szurubooru.model.comment import Comment, CommentScore
|
||||||
from szurubooru.model.snapshot import Snapshot
|
from szurubooru.model.snapshot import Snapshot
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.ext.orderinglist import ordering_list
|
||||||
|
from sqlalchemy.ext.associationproxy import association_proxy
|
||||||
from szurubooru.model.base import Base
|
from szurubooru.model.base import Base
|
||||||
|
import szurubooru.model as model
|
||||||
|
|
||||||
|
|
||||||
class PoolName(Base):
|
class PoolName(Base):
|
||||||
|
@ -18,6 +21,32 @@ class PoolName(Base):
|
||||||
def __init__(self, name: str, order: int) -> None:
|
def __init__(self, name: str, order: int) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.order = order
|
self.order = order
|
||||||
|
|
||||||
|
|
||||||
|
class PoolPost(Base):
|
||||||
|
__tablename__ = 'pool_post'
|
||||||
|
|
||||||
|
pool_id = sa.Column(
|
||||||
|
'pool_id',
|
||||||
|
sa.Integer,
|
||||||
|
sa.ForeignKey('pool.id'),
|
||||||
|
nullable=False,
|
||||||
|
primary_key=True,
|
||||||
|
index=True)
|
||||||
|
post_id = sa.Column(
|
||||||
|
'post_id',
|
||||||
|
sa.Integer,
|
||||||
|
sa.ForeignKey('post.id'),
|
||||||
|
nullable=False,
|
||||||
|
primary_key=True,
|
||||||
|
index=True)
|
||||||
|
order = sa.Column('ord', sa.Integer, nullable=False, index=True)
|
||||||
|
|
||||||
|
pool = sa.orm.relationship('Pool', back_populates='_posts')
|
||||||
|
post = sa.orm.relationship('Post')
|
||||||
|
|
||||||
|
def __init__(self, post: model.Post) -> None:
|
||||||
|
self.post_id = post.post_id
|
||||||
|
|
||||||
class Pool(Base):
|
class Pool(Base):
|
||||||
__tablename__ = 'pool'
|
__tablename__ = 'pool'
|
||||||
|
@ -40,18 +69,23 @@ class Pool(Base):
|
||||||
cascade='all,delete-orphan',
|
cascade='all,delete-orphan',
|
||||||
lazy='joined',
|
lazy='joined',
|
||||||
order_by='PoolName.order')
|
order_by='PoolName.order')
|
||||||
|
_posts = sa.orm.relationship(
|
||||||
|
'PoolPost',
|
||||||
|
back_populates='pool',
|
||||||
|
cascade='all,delete-orphan',
|
||||||
|
lazy='joined',
|
||||||
|
order_by='PoolPost.order',
|
||||||
|
collection_class=ordering_list('order'))
|
||||||
|
posts = association_proxy('_posts', 'post')
|
||||||
|
|
||||||
# post_count = sa.orm.column_property(
|
|
||||||
# sa.sql.expression.select(
|
|
||||||
# [sa.sql.expression.func.count(PostPool.post_id)])
|
|
||||||
# .where(PostPool.pool_id == pool_id)
|
|
||||||
# .correlate_except(PostPool))
|
|
||||||
# TODO
|
|
||||||
from random import randint
|
|
||||||
post_count = sa.orm.column_property(
|
post_count = sa.orm.column_property(
|
||||||
sa.sql.expression.select([randint(1, 1000)])
|
(
|
||||||
.limit(1)
|
sa.sql.expression.select(
|
||||||
.as_scalar())
|
[sa.sql.expression.func.count(PoolPost.post_id)])
|
||||||
|
.where(PoolPost.pool_id == pool_id)
|
||||||
|
.as_scalar()
|
||||||
|
),
|
||||||
|
deferred=True)
|
||||||
|
|
||||||
first_name = sa.orm.column_property(
|
first_name = sa.orm.column_property(
|
||||||
(
|
(
|
||||||
|
@ -63,7 +97,6 @@ class Pool(Base):
|
||||||
),
|
),
|
||||||
deferred=True)
|
deferred=True)
|
||||||
|
|
||||||
|
|
||||||
__mapper_args__ = {
|
__mapper_args__ = {
|
||||||
'version_id_col': version,
|
'version_id_col': version,
|
||||||
'version_id_generator': False,
|
'version_id_generator': False,
|
||||||
|
|
|
@ -104,6 +104,18 @@ def _note_filter(
|
||||||
search_util.create_str_filter)(query, criterion, negated)
|
search_util.create_str_filter)(query, criterion, negated)
|
||||||
|
|
||||||
|
|
||||||
|
def _pool_filter(
|
||||||
|
query: SaQuery,
|
||||||
|
criterion: Optional[criteria.BaseCriterion],
|
||||||
|
negated: bool) -> SaQuery:
|
||||||
|
assert criterion
|
||||||
|
return search_util.create_subquery_filter(
|
||||||
|
model.Post.post_id,
|
||||||
|
model.PoolPost.post_id,
|
||||||
|
model.PoolPost.pool_id,
|
||||||
|
search_util.create_num_filter)(query, criterion, negated)
|
||||||
|
|
||||||
|
|
||||||
class PostSearchConfig(BaseSearchConfig):
|
class PostSearchConfig(BaseSearchConfig):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.user = None # type: Optional[model.User]
|
self.user = None # type: Optional[model.User]
|
||||||
|
@ -350,6 +362,11 @@ class PostSearchConfig(BaseSearchConfig):
|
||||||
search_util.create_str_filter(
|
search_util.create_str_filter(
|
||||||
model.Post.flags_string, _flag_transformer)
|
model.Post.flags_string, _flag_transformer)
|
||||||
),
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
['pool'],
|
||||||
|
_pool_filter
|
||||||
|
),
|
||||||
])
|
])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
Loading…
Reference in a new issue