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