Add categories to settings
This commit is contained in:
6
couchpotato/core/plugins/category/__init__.py
Normal file
6
couchpotato/core/plugins/category/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .main import CategoryPlugin
|
||||
|
||||
def start():
|
||||
return CategoryPlugin()
|
||||
|
||||
config = []
|
||||
123
couchpotato/core/plugins/category/main.py
Normal file
123
couchpotato/core/plugins/category/main.py
Normal file
@@ -0,0 +1,123 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Movie, Category
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class CategoryPlugin(Plugin):
|
||||
|
||||
to_dict = {'destination': {}}
|
||||
|
||||
def __init__(self):
|
||||
addEvent('category.all', self.all)
|
||||
|
||||
addApiView('category.save', self.save)
|
||||
addApiView('category.save_order', self.saveOrder)
|
||||
addApiView('category.delete', self.delete)
|
||||
addApiView('category.list', self.allView, docs = {
|
||||
'desc': 'List all available categories',
|
||||
'return': {'type': 'object', 'example': """{
|
||||
'success': True,
|
||||
'list': array, categories
|
||||
}"""}
|
||||
})
|
||||
|
||||
def allView(self, **kwargs):
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'list': self.all()
|
||||
}
|
||||
|
||||
def all(self):
|
||||
|
||||
db = get_session()
|
||||
categories = db.query(Category).all()
|
||||
|
||||
temp = []
|
||||
for category in categories:
|
||||
temp.append(category.to_dict(self.to_dict))
|
||||
|
||||
db.expire_all()
|
||||
return temp
|
||||
|
||||
def save(self, **kwargs):
|
||||
|
||||
db = get_session()
|
||||
|
||||
c = db.query(Category).filter_by(id = kwargs.get('id')).first()
|
||||
if not c:
|
||||
c = Category()
|
||||
db.add(c)
|
||||
|
||||
c.order = kwargs.get('order', c.order if c.order else 0)
|
||||
c.label = toUnicode(kwargs.get('label'))
|
||||
c.path = toUnicode(kwargs.get('path'))
|
||||
c.ignored = toUnicode(kwargs.get('ignored'))
|
||||
c.preferred = toUnicode(kwargs.get('preferred'))
|
||||
c.required = toUnicode(kwargs.get('required'))
|
||||
|
||||
db.commit()
|
||||
|
||||
category_dict = c.to_dict(self.to_dict)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'category': category_dict
|
||||
}
|
||||
|
||||
def saveOrder(self, **kwargs):
|
||||
|
||||
db = get_session()
|
||||
|
||||
order = 0
|
||||
for category_id in kwargs.get('ids', []):
|
||||
c = db.query(Category).filter_by(id = category_id).first()
|
||||
c.order = order
|
||||
|
||||
order += 1
|
||||
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
'success': True
|
||||
}
|
||||
|
||||
def delete(self, id = None, **kwargs):
|
||||
|
||||
db = get_session()
|
||||
|
||||
success = False
|
||||
message = ''
|
||||
try:
|
||||
c = db.query(Category).filter_by(id = id).first()
|
||||
db.delete(c)
|
||||
db.commit()
|
||||
|
||||
# Force defaults on all empty category movies
|
||||
self.removeFromMovie(id)
|
||||
|
||||
success = True
|
||||
except Exception, e:
|
||||
message = log.error('Failed deleting category: %s', e)
|
||||
|
||||
db.expire_all()
|
||||
return {
|
||||
'success': success,
|
||||
'message': message
|
||||
}
|
||||
|
||||
def removeFromMovie(self, category_id):
|
||||
|
||||
db = get_session()
|
||||
movies = db.query(Movie).filter(Movie.category_id == category_id).all()
|
||||
|
||||
if len(movies) > 0:
|
||||
for movie in movies:
|
||||
movie.category_id = None
|
||||
db.commit()
|
||||
84
couchpotato/core/plugins/category/static/category.css
Normal file
84
couchpotato/core/plugins/category/static/category.css
Normal file
@@ -0,0 +1,84 @@
|
||||
.add_new_category {
|
||||
padding: 20px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
.category {
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.category > .delete {
|
||||
position: absolute;
|
||||
padding: 16px;
|
||||
right: 0;
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
color: #fd5353;
|
||||
}
|
||||
.category > .delete:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.category .ctrlHolder:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.category .formHint {
|
||||
width: 250px !important;
|
||||
vertical-align: top !important;
|
||||
margin: 0 !important;
|
||||
padding-left: 3px !important;
|
||||
opacity: 0.1;
|
||||
}
|
||||
.category:hover .formHint {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#category_ordering {
|
||||
|
||||
}
|
||||
|
||||
#category_ordering ul {
|
||||
float: left;
|
||||
margin: 0;
|
||||
width: 275px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#category_ordering li {
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
cursor: grab;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
padding: 0 5px;
|
||||
}
|
||||
#category_ordering li:last-child { border: 0; }
|
||||
|
||||
#category_ordering li .check {
|
||||
margin: 2px 10px 0 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#category_ordering li > span {
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
vertical-align: top;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
#category_ordering li .handle {
|
||||
background: url('../../static/profile_plugin/handle.png') center;
|
||||
width: 20px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
#category_ordering .formHint {
|
||||
clear: none;
|
||||
float: right;
|
||||
width: 250px;
|
||||
margin: 0;
|
||||
}
|
||||
295
couchpotato/core/plugins/category/static/category.js
Normal file
295
couchpotato/core/plugins/category/static/category.js
Normal file
@@ -0,0 +1,295 @@
|
||||
var CategoryListBase = new Class({
|
||||
|
||||
initialize: function(){
|
||||
var self = this;
|
||||
|
||||
App.addEvent('load', self.addSettings.bind(self));
|
||||
},
|
||||
|
||||
setup: function(categories){
|
||||
var self = this;
|
||||
|
||||
self.categories = []
|
||||
Array.each(categories, self.createCategory.bind(self));
|
||||
|
||||
},
|
||||
|
||||
addSettings: function(){
|
||||
var self = this;
|
||||
|
||||
self.settings = App.getPage('Settings')
|
||||
self.settings.addEvent('create', function(){
|
||||
var tab = self.settings.createSubTab('category', {
|
||||
'label': 'Categories',
|
||||
'name': 'category',
|
||||
'subtab_label': 'Category & filtering'
|
||||
}, self.settings.tabs.searcher ,'searcher');
|
||||
|
||||
self.tab = tab.tab;
|
||||
self.content = tab.content;
|
||||
|
||||
self.createList();
|
||||
self.createOrdering();
|
||||
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
createList: function(){
|
||||
var self = this;
|
||||
|
||||
var count = self.categories.length;
|
||||
|
||||
self.settings.createGroup({
|
||||
'label': 'Categories',
|
||||
'description': 'Create your own categories.'
|
||||
}).inject(self.content).adopt(
|
||||
self.category_container = new Element('div.container'),
|
||||
new Element('a.add_new_category', {
|
||||
'text': count > 0 ? 'Create another category' : 'Click here to create a category.',
|
||||
'events': {
|
||||
'click': function(){
|
||||
var category = self.createCategory();
|
||||
$(category).inject(self.category_container)
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Add categories, that aren't part of the core (for editing)
|
||||
Array.each(self.categories, function(category){
|
||||
$(category).inject(self.category_container)
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
createCategory: function(data){
|
||||
var self = this;
|
||||
|
||||
var data = data || {'id': randomString()}
|
||||
var category = new Category(data)
|
||||
self.categories.include(category)
|
||||
|
||||
return category;
|
||||
},
|
||||
|
||||
createOrdering: function(){
|
||||
var self = this;
|
||||
|
||||
var category_list;
|
||||
var group = self.settings.createGroup({
|
||||
'label': 'Category order'
|
||||
}).adopt(
|
||||
new Element('.ctrlHolder#category_ordering').adopt(
|
||||
new Element('label[text=Order]'),
|
||||
category_list = new Element('ul'),
|
||||
new Element('p.formHint', {
|
||||
'html': 'Change the order the categories are in the dropdown list.<br />First one will be default.'
|
||||
})
|
||||
)
|
||||
).inject(self.content)
|
||||
|
||||
Array.each(self.categories, function(category){
|
||||
new Element('li', {'data-id': category.data.id}).adopt(
|
||||
new Element('span.category_label', {
|
||||
'text': category.data.label
|
||||
}),
|
||||
new Element('span.handle')
|
||||
).inject(category_list);
|
||||
|
||||
});
|
||||
|
||||
// Sortable
|
||||
self.category_sortable = new Sortables(category_list, {
|
||||
'revert': true,
|
||||
'handle': '',
|
||||
'opacity': 0.5,
|
||||
'onComplete': self.saveOrdering.bind(self)
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
saveOrdering: function(){
|
||||
var self = this;
|
||||
|
||||
var ids = [];
|
||||
|
||||
self.category_sortable.list.getElements('li').each(function(el, nr){
|
||||
ids.include(el.get('data-id'));
|
||||
});
|
||||
|
||||
Api.request('category.save_order', {
|
||||
'data': {
|
||||
'ids': ids
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
window.CategoryList = new CategoryListBase();
|
||||
|
||||
var Category = new Class({
|
||||
|
||||
data: {},
|
||||
|
||||
initialize: function(data){
|
||||
var self = this;
|
||||
|
||||
self.data = data;
|
||||
self.types = [];
|
||||
|
||||
self.create();
|
||||
|
||||
self.el.addEvents({
|
||||
'change:relay(select)': self.save.bind(self, 0),
|
||||
'keyup:relay(input[type=text])': self.save.bind(self, [300])
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
var data = self.data;
|
||||
|
||||
self.el = new Element('div.category').adopt(
|
||||
self.delete_button = new Element('span.delete.icon2', {
|
||||
'events': {
|
||||
'click': self.del.bind(self)
|
||||
}
|
||||
}),
|
||||
new Element('.category_label.ctrlHolder').adopt(
|
||||
new Element('label', {'text':'Name'}),
|
||||
new Element('input.inlay', {
|
||||
'type':'text',
|
||||
'value': data.label,
|
||||
'placeholder': 'Label'
|
||||
})
|
||||
),
|
||||
new Element('.category_preferred.ctrlHolder').adopt(
|
||||
new Element('label', {'text':'Preferred'}),
|
||||
new Element('input.inlay', {
|
||||
'type':'text',
|
||||
'value': data.preferred,
|
||||
'placeholder': 'Ignored'
|
||||
})
|
||||
),
|
||||
new Element('.category_required.ctrlHolder').adopt(
|
||||
new Element('label', {'text':'Required'}),
|
||||
new Element('input.inlay', {
|
||||
'type':'text',
|
||||
'value': data.required,
|
||||
'placeholder': 'Required'
|
||||
})
|
||||
),
|
||||
new Element('.category_ignored.ctrlHolder').adopt(
|
||||
new Element('label', {'text':'Ignored'}),
|
||||
new Element('input.inlay', {
|
||||
'type':'text',
|
||||
'value': data.ignored,
|
||||
'placeholder': 'Ignored'
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
self.makeSortable()
|
||||
|
||||
},
|
||||
|
||||
save: function(delay){
|
||||
var self = this;
|
||||
|
||||
if(self.save_timer) clearTimeout(self.save_timer);
|
||||
self.save_timer = (function(){
|
||||
|
||||
var data = self.getData();
|
||||
|
||||
Api.request('category.save', {
|
||||
'data': self.getData(),
|
||||
'useSpinner': true,
|
||||
'spinnerOptions': {
|
||||
'target': self.el
|
||||
},
|
||||
'onComplete': function(json){
|
||||
if(json.success){
|
||||
self.data = json.category;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}).delay(delay, self)
|
||||
|
||||
},
|
||||
|
||||
getData: function(){
|
||||
var self = this;
|
||||
|
||||
var data = {
|
||||
'id' : self.data.id,
|
||||
'label' : self.el.getElement('.category_label input').get('value'),
|
||||
'required' : self.el.getElement('.category_required input').get('value'),
|
||||
'preferred' : self.el.getElement('.category_preferred input').get('value'),
|
||||
'ignored' : self.el.getElement('.category_ignored input').get('value')
|
||||
}
|
||||
|
||||
return data
|
||||
},
|
||||
|
||||
del: function(){
|
||||
var self = this;
|
||||
|
||||
var label = self.el.getElement('.category_label input').get('value');
|
||||
var qObj = new Question('Are you sure you want to delete <strong>"'+label+'"</strong>?', '', [{
|
||||
'text': 'Delete "'+label+'"',
|
||||
'class': 'delete',
|
||||
'events': {
|
||||
'click': function(e){
|
||||
(e).preventDefault();
|
||||
Api.request('category.delete', {
|
||||
'data': {
|
||||
'id': self.data.id
|
||||
},
|
||||
'useSpinner': true,
|
||||
'spinnerOptions': {
|
||||
'target': self.el
|
||||
},
|
||||
'onComplete': function(json){
|
||||
if(json.success) {
|
||||
qObj.close();
|
||||
self.el.destroy();
|
||||
} else {
|
||||
alert(json.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}, {
|
||||
'text': 'Cancel',
|
||||
'cancel': true
|
||||
}]);
|
||||
|
||||
},
|
||||
|
||||
makeSortable: function(){
|
||||
var self = this;
|
||||
|
||||
self.sortable = new Sortables(self.category_container, {
|
||||
'revert': true,
|
||||
'handle': '.handle',
|
||||
'opacity': 0.5,
|
||||
'onComplete': self.save.bind(self, 300)
|
||||
});
|
||||
},
|
||||
|
||||
get: function(attr){
|
||||
return this.data[attr]
|
||||
},
|
||||
|
||||
toElement: function(){
|
||||
return this.el
|
||||
}
|
||||
|
||||
});
|
||||
BIN
couchpotato/core/plugins/category/static/handle.png
Normal file
BIN
couchpotato/core/plugins/category/static/handle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 160 B |
@@ -41,7 +41,8 @@ var QualityBase = new Class({
|
||||
self.settings.addEvent('create', function(){
|
||||
var tab = self.settings.createSubTab('profile', {
|
||||
'label': 'Quality',
|
||||
'name': 'profile'
|
||||
'name': 'profile',
|
||||
'subtab_label': 'Qualities'
|
||||
}, self.settings.tabs.searcher ,'searcher');
|
||||
|
||||
self.tab = tab.tab;
|
||||
|
||||
@@ -14,25 +14,6 @@ config = [{
|
||||
'label': 'Search',
|
||||
'description': 'Options for the searchers',
|
||||
'options': [
|
||||
{
|
||||
'name': 'preferred_words',
|
||||
'label': 'Preferred words',
|
||||
'default': '',
|
||||
'description': 'These words will give the releases a higher score.'
|
||||
},
|
||||
{
|
||||
'name': 'required_words',
|
||||
'label': 'Required words',
|
||||
'default': '',
|
||||
'placeholder': 'Example: DTS, AC3 & English',
|
||||
'description': 'A release should contain at least one set of words. Sets are separated by "," and each word within a set must be separated with "&"'
|
||||
},
|
||||
{
|
||||
'name': 'ignored_words',
|
||||
'label': 'Ignored words',
|
||||
'default': 'german, dutch, french, truefrench, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub, dksubs',
|
||||
'description': 'Ignores releases that match any of these sets. (Works like explained above)'
|
||||
},
|
||||
{
|
||||
'name': 'preferred_method',
|
||||
'label': 'First search',
|
||||
@@ -50,6 +31,34 @@ config = [{
|
||||
'description': 'Search for movies even before there is a ETA. Enabling this will probably get you a lot of fakes.',
|
||||
},
|
||||
],
|
||||
}, {
|
||||
'tab': 'searcher',
|
||||
'subtab': 'category',
|
||||
'subtab_label': 'Categories',
|
||||
'name': 'filter',
|
||||
'label': 'Global filters',
|
||||
'description': 'Prefer, ignore & required words in release names',
|
||||
'options': [
|
||||
{
|
||||
'name': 'preferred_words',
|
||||
'label': 'Preferred',
|
||||
'default': '',
|
||||
'description': 'Words that give the releases a higher score.'
|
||||
},
|
||||
{
|
||||
'name': 'required_words',
|
||||
'label': 'Required',
|
||||
'default': '',
|
||||
'placeholder': 'Example: DTS, AC3 & English',
|
||||
'description': 'Release should contain at least one set of words. Sets are separated by "," and each word within a set must be separated with "&"'
|
||||
},
|
||||
{
|
||||
'name': 'ignored_words',
|
||||
'label': 'Ignored',
|
||||
'default': 'german, dutch, french, truefrench, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub, dksubs',
|
||||
'description': 'Ignores releases that match any of these sets. (Works like explained above)'
|
||||
},
|
||||
],
|
||||
}, {
|
||||
'tab': 'searcher',
|
||||
'name': 'cronjob',
|
||||
@@ -97,13 +106,14 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'searcher',
|
||||
'name': 'nzb',
|
||||
'name': 'searcher',
|
||||
'label': 'NZB',
|
||||
'wizard': True,
|
||||
'options': [
|
||||
{
|
||||
'name': 'retention',
|
||||
'default': 1000,
|
||||
'label': 'Usenet Retention',
|
||||
'default': 1500,
|
||||
'type': 'int',
|
||||
'unit': 'days'
|
||||
},
|
||||
|
||||
@@ -213,15 +213,21 @@ class Category(Entity):
|
||||
|
||||
label = Field(Unicode(50))
|
||||
order = Field(Integer, default = 0, index = True)
|
||||
core = Field(Boolean, default = False)
|
||||
hide = Field(Boolean, default = False)
|
||||
|
||||
movie = OneToMany('Movie')
|
||||
path = Field(Unicode(255))
|
||||
required = Field(Unicode(255))
|
||||
preferred = Field(Unicode(255))
|
||||
ignored = Field(Unicode(255))
|
||||
|
||||
|
||||
movie = OneToMany('Movie')
|
||||
destination = ManyToOne('Destination')
|
||||
|
||||
|
||||
class Destination(Entity):
|
||||
""""""
|
||||
|
||||
path = Field(Unicode(255))
|
||||
|
||||
category = OneToMany('Category')
|
||||
|
||||
|
||||
class ProfileType(Entity):
|
||||
""""""
|
||||
@@ -288,13 +294,6 @@ class Notification(Entity):
|
||||
data = Field(JsonType)
|
||||
|
||||
|
||||
class Folder(Entity):
|
||||
"""Renamer destination folders."""
|
||||
|
||||
path = Field(Unicode(255))
|
||||
label = Field(Unicode(255))
|
||||
|
||||
|
||||
class Properties(Entity):
|
||||
|
||||
identifier = Field(String(50), index = True)
|
||||
|
||||
@@ -161,7 +161,7 @@ Page.Settings = new Class({
|
||||
// Create subtab
|
||||
if(group.subtab){
|
||||
if (!self.tabs[group.tab].subtabs[group.subtab])
|
||||
self.createSubTab(group.subtab, {}, self.tabs[group.tab], group.tab);
|
||||
self.createSubTab(group.subtab, group, self.tabs[group.tab], group.tab);
|
||||
var content_container = self.tabs[group.tab].subtabs[group.subtab].content
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ Page.Settings = new Class({
|
||||
if(!parent_tab.subtabs_el)
|
||||
parent_tab.subtabs_el = new Element('ul.subtabs').inject(parent_tab.tab);
|
||||
|
||||
var label = tab.label || (tab.name || tab_name.replace('_', ' ')).capitalize()
|
||||
var label = tab.subtab_label || tab_name.replace('_', ' ').capitalize()
|
||||
var tab_el = new Element('li.t_'+tab_name).adopt(
|
||||
new Element('a', {
|
||||
'href': App.createUrl(self.name+'/'+parent_tab_name+'/'+tab_name),
|
||||
|
||||
@@ -70,6 +70,8 @@
|
||||
|
||||
File.Type.setup({{ json_encode(fireEvent('file.types', single = True)) }});
|
||||
|
||||
CategoryList.setup({{ json_encode(fireEvent('category.all', single = True)) }});
|
||||
|
||||
App.setup({
|
||||
'base_url': {{ json_encode(Env.get('web_base')) }},
|
||||
'args': {{ json_encode(Env.get('args')) }},
|
||||
|
||||
Reference in New Issue
Block a user