def POST(self, *args, **kwargs): """ if id is set, we edit corresponding link, else we create new one """ if 'id' in kwargs: #link = filter(lambda x: x.id == int(kwargs['id']), Link) lid = int(kwargs['id']) link = list(filter(lambda x: x.id == lid, Link)) if len(link) == 0: return routing.HTTP_400("'%s' id not found" % kwargs['id']) link = link[0] else: link = Link() errors = {} # simple fields for name, mandatory in {'link': True, 'name': True, 'description': False}.iteritems(): #NOTE: mandatory fields must be non-null (or empty size) # others can be unset or empty if mandatory and (name not in kwargs or len(kwargs[name])) == 0: errors[name] = (10, 'field required'); continue elif name not in kwargs: continue try: value = link.fieldesc(name).cast(kwargs[name]) except ValueError: errors[name] = (11, 'invalid type'); continue setattr(link, name, value) # test link is unique #TODO: must be done at ORM level (with save() method) _link = link.link if 'name' not in errors and not link.saved() and\ len(filter(lambda x: x.link == _link, Link)) > 0: errors['link'] = (13, 'not unique') # relational field: tags. we get tag names as input... tagnames = kwargs['tags'] # tentacles workaroung if len(tagnames) == 0: errors['tags'] = (12, 'at least one value required') else: tags = list(filter(lambda t: t.name in tagnames, Tag)) newtags = [Tag(name=name) for name in tagnames if name not in [t.name for t in tags]] for t in link.tags: tags.remove(t) if t in tags else link.tags.remove(t) for t in set(tags).union(newtags): link.tags.append(t) if len(errors) > 0: return routing.HTTP_409(errors), link link.save() return routing.HTTP_200(link.id), link
def PUT(self, content): if 'link' not in content or 'name' not in content: return routing.HTTP_400("link, name are mandatory keys") if 'id' in content and len(filter(lambda x: x.id == content['id'], Link)) > 0: return routing.HTTP_400("id already exists") if len(filter(lambda x: x.link == content['link'], Link)) > 0: return routing.HTTP_400("link must be unique") link = Link() for key, value in content.iteritems(): if key == 'tags': continue if not key in link.__fields__: return routing.HTTP_409("unknown field '%s'" % key) setattr(link, key, value) """We query all tags (by tagname), and create new ones""" if 'tags' in content: link.tags = list(filter(lambda t: t.name in content['tags'], Tag)) link.tags.extend([Tag(name=t) for t in content['tags'] if t not in [tag.name for tag in link.tags]]) link.save() return link.id
def bytag(self, tagname): #short query form is not available for now #res = list(filter(lambda x: tagname in [name for tag in x.tags], Link)) tags = list(filter(lambda l: l.name == tagname, Tag)) if len(tags) == 0: return routing.HTTP_404() tag0 = tags[0] return list(map(lambda l: l.id, filter(lambda l: tag0 in l.tags, Link)))
def _binary(self, field, request, id, content): link = list(filter(lambda x: x.id == id, Link)) if len(link) == 0: return routing.HTTP_404() link = link[0] if request.method == 'PUT': setattr(link, field, content.read()) link.save() return None elif request.method == 'DELETE': setattr(link, field, None) link.save() return None # GET data = getattr(link, field) if data is None: return routing.HTTP_204() import magic m = magic.open(magic.MAGIC_MIME) m.load() mime = m.buffer(data) m.close() if mime is not None and len(mime) > 0: request.setHeader('Content-Type', mime.split(';')[0]) request.write(str(getattr(link, field))) request.finish() return server.NOT_DONE_YET
def PUT(self, content): if 'name' not in content: return (400, "*name* key is mandatory") if 'id' in content and len(filter(lambda x: x.id == content['id'], Tag)) > 0: return (400, "id already exists") if len(filter(lambda x: x.link == content['name'], Tag)) > 0: return (400, "name must be unique") tag = Tag() for key, value in content.iteritems(): if not key in tag.__fields__: return(409, "unknown field '%s'" % key) setattr(tag, key, value) tag.save() return tag.id
def DELETE(self, id): """ NOTE: associated tags, even if specially created for this link, are not deleted """ tags = list(filter(lambda x: x.id == id, Tag)) if len(tags) == 0: return (404, "not found") elif len(tags) > 1: return (500, "return several tags for the same id") tags[0].delete() return (200, True)
def DELETE(self, id): """ NOTE: associated tags, even if specially created for this link, are not deleted """ links = list(filter(lambda x: x.id == id, Link)) if len(links) == 0: return routing.HTTP_404("not found") elif len(links) > 1: return routing.HTTP_500("return several links for the same id") links[0].delete() return routing.HTTP_200()
def GET(self, id, **kwargs): tag = list(filter(lambda x: x.id == id, Tag)) if len(tag) == 0: return (404, None) tag = tag[0] res = {} for name, fld in tag.__fields__.iteritems(): if isinstance(fld, Reference): continue res[name] = getattr(tag, name) return res
def search(q, **kwargs): """ search format: a+b-c+"d e"-"f+g" give tags: +a, +b, -c, +d e, -f g NOTES: . in url encoding, '+' == space and '& %2B == '+' we accept both as plus sign (+ is visually better) """ # parse querystring print 'search=', q """ import shlex def sign(tag): if tag[0] in ('+', '-'): return (tag[0], tag[1:].decode('utf8')) return ('+', tag.decode('utf8')) tags = [sign(tag) for tag in shlex.split(q)] """ import re tags = [(s if s == '-' else '+', t.strip('"')) for (s, t) in re.findall("(^|[ +-])([^\" +-]+|\"[^\"]+\")", q)] plus = map(lambda t: t[1], filter(lambda t: t[0] == '+', tags)) minus = map(lambda t: t[1], filter(lambda t: t[0] == '-', tags)) plus = list(filter(lambda t: t.name in plus, Tag)) minus = list(filter(lambda t: t.name in minus, Tag)) links = list(filter(lambda l: l.tags.issuperset(plus) and l.tags.isdisjoint(minus), Link)) #TODO: howto made it simple/clearer ? def rset(tag, sign): setattr(tag, 'sign', sign); return tag tags = map(lambda t: rset(t, '+'), plus) tags.extend(map(lambda t: rset(t, '-'), minus)) tags.sort(key=lambda t: t.name) related = list(filter(lambda t: not t.Link__tags.isdisjoint(links) and t not in plus, Tag)) return Template('tag.html', title="Tag ☄ "+q, #query=q, tagname=q, links=links, tags=tags, related=related )
def GET(self, id, __referer__=None, **kwargs): link = list(filter(lambda x: x.id == id, Link)) if len(link) == 0: return routing.HTTP_404("%s link not found" % id) link = link[0] res = {} for name, fld in link.__fields__.iteritems(): if isinstance(fld, Reference): continue res[name] = getattr(link, name) #TODO: should be done in a generic way # the best way should probably be to have a tentacles json backend # For that, we need to be able to switch backend for an object # JSON encoder should also understand datetime object if isinstance(fld, Datetime) and res[name] is not None: res[name] = str(res[name]) res['tags'] = dict([(t.name, None) for t in link.tags]) return res
def initialize(self, root): plugins = filter(lambda x: x.active is True, Plugin) for plugin in plugins: try: sys.path.insert(0, plugin.path) mod = __import__(plugin.name, globals(), locals(), [], -1) except ImportError, e: print "/!\ Cannot import %s plugin:" % plugin.name, e mod.CONTEXT = AppContext(mod) mod.PLUGIN = plugin plugin.MODULE = mod plugin.root = PluginNode(plugin) root.putChild(plugin.name, plugin.root) self.plugins[plugin.name] = plugin # check UUID matching if mod.UUID != plugin.uuid: print "/!\ plugin UUID does not match database one (%s vs %s)" % \ (plugin.uuid, mod.UUID) continue # set default values #TODO: use a generic directory #TODO: should be done earlier, before @callback decorators were called if not hasattr(mod, 'AUTHENTICATION'): mod.AUTHENTICATION = False # load URL callbacks raw_urls = mod.__dict__.get('URLS', {}) for url, target in raw_urls.iteritems(): """Looping on URLs we must determine if url is: . a raw string (i.e: /foo/bar) . a simple regex (i.e: /foo/{bar}) . a full regex (i.e: /foo/(?<bar>[^/]+)) """ if not isinstance(target, (tuple,list)): target = (target,) for _target in target: if isinstance(_target, static.File): content_type = _target.content_type if hasattr(_target,'content_type') else '*/*' plugin.addurl(url, content_type, _target); continue elif isinstance(_target, template.Template): plugin.addurl(url, _target.content_type, TemplateNode(mod, _target)); continue # resolve unbound methods if hasattr(_target, 'im_self') and _target.im_self is None: #callback = getattr(self.instances[callback.im_class], callback.__name__) #callback = getattr(self.__classinstance(callback.im_class), callback.__name__) _target = self.boundmethod(_target) if not hasattr(_target, '__callable__'): raise Exception('%s is not callable' % _target.__name__) elif 'url' in _target.__callable__: raise Exception("%s url cannot be redefined (is %s, try to set %s)" %\ (_target.__name__, _target.__callable__['url'], url)) _target.__callable__['url'] = url plugin.addurl(url, _target.__callable__['content_type'], FuncNode(_target)) # load plugin callbacks """ mod.__callbacks__ contains callback functions but we don't know at which classes it belongs (cause when decorator is called, class methods are not yet binded to its class) What we try to do here is to find method parent class. NOTE: for the moment, we only search at level 1 (no recursion) """ for (name, obj) in inspect.getmembers(mod): if inspect.isfunction(obj) and '__callable__' in dir(obj): #netnode.putChild(name, FuncNode(obj)) # get original url if not set explicitly if 'url' not in obj.__callable__: obj.__callable__['url'] = obj.__callable__['_url'] node = FuncNode(obj) plugin.addurl(node.url, obj.__callable__['content_type'], node) #NOTE: why limited to callable objects only elif inspect.isclass(obj) and issubclass(obj, Callable) and obj is not Callable: self.__classinit(obj, plugin)