10 changed files with 557 additions and 37 deletions
@ -0,0 +1,6 @@ |
|||
from .main import CategoryPlugin |
|||
|
|||
def start(): |
|||
return CategoryPlugin() |
|||
|
|||
config = [] |
@ -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() |
@ -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; |
|||
} |
@ -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 |
|||
} |
|||
|
|||
}); |
After Width: | Height: | Size: 160 B |
Loading…
Reference in new issue