Browse Source

Add categories to settings

pull/1980/merge
Ruud 12 years ago
parent
commit
dd67239b6e
  1. 6
      couchpotato/core/plugins/category/__init__.py
  2. 123
      couchpotato/core/plugins/category/main.py
  3. 84
      couchpotato/core/plugins/category/static/category.css
  4. 295
      couchpotato/core/plugins/category/static/category.js
  5. BIN
      couchpotato/core/plugins/category/static/handle.png
  6. 3
      couchpotato/core/plugins/quality/static/quality.js
  7. 52
      couchpotato/core/plugins/searcher/__init__.py
  8. 23
      couchpotato/core/settings/model.py
  9. 4
      couchpotato/static/scripts/page/settings.js
  10. 2
      couchpotato/templates/index.html

6
couchpotato/core/plugins/category/__init__.py

@ -0,0 +1,6 @@
from .main import CategoryPlugin
def start():
return CategoryPlugin()
config = []

123
couchpotato/core/plugins/category/main.py

@ -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

@ -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

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

3
couchpotato/core/plugins/quality/static/quality.js

@ -41,7 +41,8 @@ var QualityBase = new Class({
self.settings.addEvent('create', function(){ self.settings.addEvent('create', function(){
var tab = self.settings.createSubTab('profile', { var tab = self.settings.createSubTab('profile', {
'label': 'Quality', 'label': 'Quality',
'name': 'profile' 'name': 'profile',
'subtab_label': 'Qualities'
}, self.settings.tabs.searcher ,'searcher'); }, self.settings.tabs.searcher ,'searcher');
self.tab = tab.tab; self.tab = tab.tab;

52
couchpotato/core/plugins/searcher/__init__.py

@ -15,25 +15,6 @@ config = [{
'description': 'Options for the searchers', 'description': 'Options for the searchers',
'options': [ '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', 'name': 'preferred_method',
'label': 'First search', 'label': 'First search',
'description': 'Which of the methods do you prefer', 'description': 'Which of the methods do you prefer',
@ -52,6 +33,34 @@ config = [{
], ],
}, { }, {
'tab': 'searcher', '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', 'name': 'cronjob',
'label': 'Cronjob', 'label': 'Cronjob',
'advanced': True, 'advanced': True,
@ -97,13 +106,14 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'name': 'nzb', 'name': 'searcher',
'label': 'NZB', 'label': 'NZB',
'wizard': True, 'wizard': True,
'options': [ 'options': [
{ {
'name': 'retention', 'name': 'retention',
'default': 1000, 'label': 'Usenet Retention',
'default': 1500,
'type': 'int', 'type': 'int',
'unit': 'days' 'unit': 'days'
}, },

23
couchpotato/core/settings/model.py

@ -213,15 +213,21 @@ class Category(Entity):
label = Field(Unicode(50)) label = Field(Unicode(50))
order = Field(Integer, default = 0, index = True) 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)) required = Field(Unicode(255))
preferred = Field(Unicode(255)) preferred = Field(Unicode(255))
ignored = 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): class ProfileType(Entity):
"""""" """"""
@ -288,13 +294,6 @@ class Notification(Entity):
data = Field(JsonType) data = Field(JsonType)
class Folder(Entity):
"""Renamer destination folders."""
path = Field(Unicode(255))
label = Field(Unicode(255))
class Properties(Entity): class Properties(Entity):
identifier = Field(String(50), index = True) identifier = Field(String(50), index = True)

4
couchpotato/static/scripts/page/settings.js

@ -161,7 +161,7 @@ Page.Settings = new Class({
// Create subtab // Create subtab
if(group.subtab){ if(group.subtab){
if (!self.tabs[group.tab].subtabs[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 var content_container = self.tabs[group.tab].subtabs[group.subtab].content
} }
@ -243,7 +243,7 @@ Page.Settings = new Class({
if(!parent_tab.subtabs_el) if(!parent_tab.subtabs_el)
parent_tab.subtabs_el = new Element('ul.subtabs').inject(parent_tab.tab); 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( var tab_el = new Element('li.t_'+tab_name).adopt(
new Element('a', { new Element('a', {
'href': App.createUrl(self.name+'/'+parent_tab_name+'/'+tab_name), 'href': App.createUrl(self.name+'/'+parent_tab_name+'/'+tab_name),

2
couchpotato/templates/index.html

@ -70,6 +70,8 @@
File.Type.setup({{ json_encode(fireEvent('file.types', single = True)) }}); File.Type.setup({{ json_encode(fireEvent('file.types', single = True)) }});
CategoryList.setup({{ json_encode(fireEvent('category.all', single = True)) }});
App.setup({ App.setup({
'base_url': {{ json_encode(Env.get('web_base')) }}, 'base_url': {{ json_encode(Env.get('web_base')) }},
'args': {{ json_encode(Env.get('args')) }}, 'args': {{ json_encode(Env.get('args')) }},

Loading…
Cancel
Save