hack a view
This commit is contained in:
parent
3c5ce29f67
commit
d63f020810
24 changed files with 1225 additions and 14 deletions
0
oxbrowser/api/__init__.py
Normal file
0
oxbrowser/api/__init__.py
Normal file
117
oxbrowser/api/actions.py
Normal file
117
oxbrowser/api/actions.py
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
import sys
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from ox.django.shortcuts import render_to_json_response, json_response
|
||||||
|
from ox.utils import json
|
||||||
|
|
||||||
|
|
||||||
|
def autodiscover():
|
||||||
|
#register api actions from all installed apps
|
||||||
|
from django.utils.importlib import import_module
|
||||||
|
from django.utils.module_loading import module_has_submodule
|
||||||
|
for app in settings.INSTALLED_APPS:
|
||||||
|
if app != 'api':
|
||||||
|
mod = import_module(app)
|
||||||
|
try:
|
||||||
|
import_module('%s.views'%app)
|
||||||
|
except:
|
||||||
|
if module_has_submodule(mod, 'views'):
|
||||||
|
raise
|
||||||
|
|
||||||
|
def trim(docstring):
|
||||||
|
if not docstring:
|
||||||
|
return ''
|
||||||
|
# Convert tabs to spaces (following the normal Python rules)
|
||||||
|
# and split into a list of lines:
|
||||||
|
lines = docstring.expandtabs().splitlines()
|
||||||
|
# Determine minimum indentation (first line doesn't count):
|
||||||
|
indent = sys.maxint
|
||||||
|
for line in lines[1:]:
|
||||||
|
stripped = line.lstrip()
|
||||||
|
if stripped:
|
||||||
|
indent = min(indent, len(line) - len(stripped))
|
||||||
|
# Remove indentation (first line is special):
|
||||||
|
trimmed = [lines[0].strip()]
|
||||||
|
if indent < sys.maxint:
|
||||||
|
for line in lines[1:]:
|
||||||
|
trimmed.append(line[indent:].rstrip())
|
||||||
|
# Strip off trailing and leading blank lines:
|
||||||
|
while trimmed and not trimmed[-1]:
|
||||||
|
trimmed.pop()
|
||||||
|
while trimmed and not trimmed[0]:
|
||||||
|
trimmed.pop(0)
|
||||||
|
# Return a single string:
|
||||||
|
return '\n'.join(trimmed)
|
||||||
|
|
||||||
|
|
||||||
|
class ApiActions(dict):
|
||||||
|
properties = {}
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
def api(request):
|
||||||
|
'''
|
||||||
|
returns list of all known api actions
|
||||||
|
param data {
|
||||||
|
docs: bool
|
||||||
|
}
|
||||||
|
if docs is true, action properties contain docstrings
|
||||||
|
return {
|
||||||
|
status: {'code': int, 'text': string},
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
'api': {
|
||||||
|
cache: true,
|
||||||
|
doc: 'recursion'
|
||||||
|
},
|
||||||
|
'hello': {
|
||||||
|
cache: true,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
data = json.loads(request.POST.get('data', '{}'))
|
||||||
|
docs = data.get('docs', False)
|
||||||
|
code = data.get('code', False)
|
||||||
|
_actions = self.keys()
|
||||||
|
_actions.sort()
|
||||||
|
actions = {}
|
||||||
|
for a in _actions:
|
||||||
|
actions[a] = self.properties[a]
|
||||||
|
if docs:
|
||||||
|
actions[a]['doc'] = self.doc(a)
|
||||||
|
if code:
|
||||||
|
actions[a]['code'] = self.code(a)
|
||||||
|
response = json_response({'actions': actions})
|
||||||
|
return render_to_json_response(response)
|
||||||
|
self.register(api)
|
||||||
|
|
||||||
|
def doc(self, f):
|
||||||
|
return trim(self[f].__doc__)
|
||||||
|
|
||||||
|
def code(self, name):
|
||||||
|
f = self[name]
|
||||||
|
if name != 'api' and hasattr(f, 'func_closure') and f.func_closure:
|
||||||
|
f = f.func_closure[0].cell_contents
|
||||||
|
info = f.func_code.co_filename[len(settings.PROJECT_ROOT)+1:]
|
||||||
|
info = u'%s:%s' % (info, f.func_code.co_firstlineno)
|
||||||
|
return info, trim(inspect.getsource(f))
|
||||||
|
|
||||||
|
def register(self, method, action=None, cache=True):
|
||||||
|
if not action:
|
||||||
|
action = method.func_name
|
||||||
|
self[action] = method
|
||||||
|
self.properties[action] = {'cache': cache}
|
||||||
|
|
||||||
|
def unregister(self, action):
|
||||||
|
if action in self:
|
||||||
|
del self[action]
|
||||||
|
|
||||||
|
actions = ApiActions()
|
||||||
|
|
0
oxbrowser/api/management/__init__.py
Normal file
0
oxbrowser/api/management/__init__.py
Normal file
0
oxbrowser/api/management/commands/__init__.py
Normal file
0
oxbrowser/api/management/commands/__init__.py
Normal file
3
oxbrowser/api/models.py
Normal file
3
oxbrowser/api/models.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
|
10
oxbrowser/api/urls.py
Normal file
10
oxbrowser/api/urls.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
|
||||||
|
from django.conf.urls.defaults import *
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = patterns("api.views",
|
||||||
|
(r'^$', 'api'),
|
||||||
|
)
|
||||||
|
|
82
oxbrowser/api/views.py
Normal file
82
oxbrowser/api/views.py
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
from __future__ import division, with_statement
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.shortcuts import render_to_response
|
||||||
|
from django.template import RequestContext
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db.models import Max, Sum
|
||||||
|
|
||||||
|
from ox.django.shortcuts import render_to_json_response, json_response
|
||||||
|
from ox.utils import json
|
||||||
|
|
||||||
|
from app.models import site_config
|
||||||
|
|
||||||
|
from actions import actions
|
||||||
|
|
||||||
|
|
||||||
|
def api(request):
|
||||||
|
if request.META['REQUEST_METHOD'] == "OPTIONS":
|
||||||
|
response = render_to_json_response({'status': {'code': 200,
|
||||||
|
'text': 'use POST'}})
|
||||||
|
response['Access-Control-Allow-Origin'] = '*'
|
||||||
|
return response
|
||||||
|
if not 'action' in request.POST:
|
||||||
|
methods = actions.keys()
|
||||||
|
api = []
|
||||||
|
for f in sorted(methods):
|
||||||
|
api.append({'name': f,
|
||||||
|
'doc': actions.doc(f).replace('\n', '<br>\n')})
|
||||||
|
context = RequestContext(request, {'api': api,
|
||||||
|
'sitename': settings.SITENAME})
|
||||||
|
return render_to_response('api.html', context)
|
||||||
|
function = request.POST['action']
|
||||||
|
#FIXME: possible to do this in f
|
||||||
|
#data = json.loads(request.POST['data'])
|
||||||
|
|
||||||
|
f = actions.get(function)
|
||||||
|
if f:
|
||||||
|
response = f(request)
|
||||||
|
else:
|
||||||
|
response = render_to_json_response(json_response(status=400,
|
||||||
|
text='Unknown function %s' % function))
|
||||||
|
response['Access-Control-Allow-Origin'] = '*'
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def init(request):
|
||||||
|
'''
|
||||||
|
return {'status': {'code': int, 'text': string},
|
||||||
|
'data': {user: object}}
|
||||||
|
'''
|
||||||
|
#data = json.loads(request.POST['data'])
|
||||||
|
response = json_response({})
|
||||||
|
config = site_config()
|
||||||
|
del config['keys'] #is this needed?
|
||||||
|
|
||||||
|
#populate max values for percent requests
|
||||||
|
for key in filter(lambda k: 'format' in k, config['itemKeys']):
|
||||||
|
'''
|
||||||
|
if key['format']['type'] == 'percent' and key['format']['args'][0] == 'auto':
|
||||||
|
name = key['id']
|
||||||
|
if name == 'popularity':
|
||||||
|
name = 'item__accessed__accessed'
|
||||||
|
value = ItemSort.objects.aggregate(Sum(name))['%s__sum'%name]
|
||||||
|
else:
|
||||||
|
value = ItemSort.objects.aggregate(Max(name))['%s__max'%name]
|
||||||
|
key['format']['args'][0] = value
|
||||||
|
'''
|
||||||
|
response['data']['site'] = config
|
||||||
|
return render_to_json_response(response)
|
||||||
|
actions.register(init)
|
||||||
|
|
||||||
|
|
||||||
|
def error(request):
|
||||||
|
'''
|
||||||
|
this action is used to test api error codes, it should return a 503 error
|
||||||
|
'''
|
||||||
|
success = error_is_success
|
||||||
|
return render_to_json_response({})
|
||||||
|
actions.register(error)
|
0
oxbrowser/app/__init__.py
Normal file
0
oxbrowser/app/__init__.py
Normal file
18
oxbrowser/app/admin.py
Normal file
18
oxbrowser/app/admin.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
import models
|
||||||
|
|
||||||
|
|
||||||
|
class PageAdmin(admin.ModelAdmin):
|
||||||
|
search_fields = ['name', 'body']
|
||||||
|
|
||||||
|
admin.site.register(models.Page, PageAdmin)
|
||||||
|
|
||||||
|
|
||||||
|
class SiteSettingsAdmin(admin.ModelAdmin):
|
||||||
|
search_fields = ['key', 'value']
|
||||||
|
|
||||||
|
admin.site.register(models.SiteSettings, SiteSettingsAdmin)
|
40
oxbrowser/app/models.py
Normal file
40
oxbrowser/app/models.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
from __future__ import division, with_statement
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.conf import settings
|
||||||
|
from ox.utils import json
|
||||||
|
|
||||||
|
|
||||||
|
class Page(models.Model):
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
modified = models.DateTimeField(auto_now=True)
|
||||||
|
name = models.CharField(max_length=1024, unique=True)
|
||||||
|
body = models.TextField(blank=True)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class SiteSettings(models.Model):
|
||||||
|
key = models.CharField(max_length=1024, unique=True)
|
||||||
|
value = models.TextField(blank=True)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.key
|
||||||
|
|
||||||
|
def site_config():
|
||||||
|
with open(settings.SITE_CONFIG) as f:
|
||||||
|
site_config = json.load(f)
|
||||||
|
|
||||||
|
site_config['site']['id'] = settings.SITEID
|
||||||
|
site_config['site']['name'] = settings.SITENAME
|
||||||
|
site_config['site']['sectionName'] = settings.SITENAME
|
||||||
|
site_config['site']['url'] = settings.URL
|
||||||
|
|
||||||
|
site_config['keys'] = {}
|
||||||
|
for key in site_config['itemKeys']:
|
||||||
|
site_config['keys'][key['id']] = key
|
||||||
|
return site_config
|
24
oxbrowser/app/tests.py
Normal file
24
oxbrowser/app/tests.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
"""
|
||||||
|
This file demonstrates two different styles of tests (one doctest and one
|
||||||
|
unittest). These will both pass when you run "manage.py test".
|
||||||
|
|
||||||
|
Replace these with more appropriate tests for your application.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleTest(TestCase):
|
||||||
|
|
||||||
|
def test_basic_addition(self):
|
||||||
|
"""
|
||||||
|
Tests that 1 + 1 always equals 2.
|
||||||
|
"""
|
||||||
|
self.failUnlessEqual(1 + 1, 2)
|
||||||
|
|
||||||
|
__test__ = {"doctest": """
|
||||||
|
Another way to test that 1 + 1 is equal to 2.
|
||||||
|
|
||||||
|
>>> 1 + 1 == 2
|
||||||
|
True
|
||||||
|
"""}
|
89
oxbrowser/app/views.py
Normal file
89
oxbrowser/app/views.py
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
from django.shortcuts import render_to_response
|
||||||
|
from django.template import RequestContext
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from ox.django.shortcuts import json_response, render_to_json_response, get_object_or_404_json
|
||||||
|
from ox.django.decorators import login_required_json
|
||||||
|
|
||||||
|
from ox.utils import json
|
||||||
|
|
||||||
|
import models
|
||||||
|
|
||||||
|
from api.actions import actions
|
||||||
|
|
||||||
|
|
||||||
|
def intro(request):
|
||||||
|
context = RequestContext(request, {'settings': settings})
|
||||||
|
return render_to_response('intro.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def index(request):
|
||||||
|
context = RequestContext(request, {'settings': settings})
|
||||||
|
return render_to_response('index.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def embed(request):
|
||||||
|
context = RequestContext(request, {'settings': settings})
|
||||||
|
return render_to_response('embed.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def timeline(request):
|
||||||
|
context = RequestContext(request, {'settings': settings})
|
||||||
|
return render_to_response('timeline.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def getPage(request):
|
||||||
|
'''
|
||||||
|
param data {
|
||||||
|
name: pagename
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: ...
|
||||||
|
data: {
|
||||||
|
name:
|
||||||
|
body:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
data = json.loads(request.POST['data'])
|
||||||
|
if isinstance(data, basestring):
|
||||||
|
name = data
|
||||||
|
else:
|
||||||
|
name = data['name']
|
||||||
|
page, created = models.Page.objects.get_or_create(name=name)
|
||||||
|
if created:
|
||||||
|
page.body = 'Insert text here'
|
||||||
|
page.save()
|
||||||
|
response = json_response({'name': page.name, 'body': page.body})
|
||||||
|
return render_to_json_response(response)
|
||||||
|
actions.register(getPage)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required_json
|
||||||
|
def editPage(request):
|
||||||
|
'''
|
||||||
|
param data {
|
||||||
|
name: pagename
|
||||||
|
body: text
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: ...
|
||||||
|
data: {
|
||||||
|
name:
|
||||||
|
body:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
if not request.user.is_staff:
|
||||||
|
response = json_response(status=403, text='permission denied')
|
||||||
|
return render_to_json_response(response)
|
||||||
|
data = json.loads(request.POST['data'])
|
||||||
|
page, created = models.Page.objects.get_or_create(name=data['name'])
|
||||||
|
page.body = data['body']
|
||||||
|
page.save()
|
||||||
|
response = json_response({'name': page.name, 'page': page.body})
|
||||||
|
return render_to_json_response(response)
|
||||||
|
actions.register(getPage)
|
||||||
|
|
87
oxbrowser/cables.json
Normal file
87
oxbrowser/cables.json
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
{
|
||||||
|
"groups": [
|
||||||
|
{"id": "origin", "title": "Origin"},
|
||||||
|
{"id": "created", "title": "Created"},
|
||||||
|
{"id": "released", "title": "Released"}
|
||||||
|
],
|
||||||
|
"itemKeys": [
|
||||||
|
{
|
||||||
|
"id": "refid",
|
||||||
|
"title": "ID",
|
||||||
|
"type": "string",
|
||||||
|
"columnRequired": false,
|
||||||
|
"columnWidth": 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "subject",
|
||||||
|
"title": "Subject",
|
||||||
|
"type": "string",
|
||||||
|
"columnRequired": true,
|
||||||
|
"columnWidth": 480,
|
||||||
|
"find": true,
|
||||||
|
"sort": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin",
|
||||||
|
"title": "Origin",
|
||||||
|
"type": "string",
|
||||||
|
"columnWidth": 140,
|
||||||
|
"find": true,
|
||||||
|
"group": true,
|
||||||
|
"sort": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "created",
|
||||||
|
"title": "Created",
|
||||||
|
"type": "date",
|
||||||
|
"columnWidth": 120,
|
||||||
|
"group": true,
|
||||||
|
"format": {"type": "date", "args": ["%a, %b %e, %Y"]}
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "released",
|
||||||
|
"title": "Released",
|
||||||
|
"type": "date",
|
||||||
|
"columnWidth": 120,
|
||||||
|
"group": true,
|
||||||
|
"format": {"type": "date", "args": ["%a, %b %e, %Y"]}
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "classification",
|
||||||
|
"title": "Classification",
|
||||||
|
"type": "string",
|
||||||
|
"columnWidth": 180,
|
||||||
|
"find": true,
|
||||||
|
"group": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tags",
|
||||||
|
"title": "Tag",
|
||||||
|
"type": ["string"],
|
||||||
|
"columnWidth": 120,
|
||||||
|
"find": true,
|
||||||
|
"group": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"itemName": {
|
||||||
|
"singular": "Cable",
|
||||||
|
"plural": "Cables"
|
||||||
|
},
|
||||||
|
"itemViews": [
|
||||||
|
{"id": "info", "title": "Info"}
|
||||||
|
],
|
||||||
|
"listViews": [
|
||||||
|
{"id": "list", "title": "as List"},
|
||||||
|
{"id": "map", "title": "on Map"}
|
||||||
|
],
|
||||||
|
"site": {
|
||||||
|
"id": "{{settings.SITEID}}",
|
||||||
|
"name": "{{settings.SITENAME}}",
|
||||||
|
"url": "{{settings.URL}}"
|
||||||
|
},
|
||||||
|
"totals": [
|
||||||
|
{"id": "items"}
|
||||||
|
]
|
||||||
|
}
|
0
oxbrowser/item/__init__.py
Normal file
0
oxbrowser/item/__init__.py
Normal file
48
oxbrowser/item/models.py
Normal file
48
oxbrowser/item/models.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from pymongo import Connection, ASCENDING, DESCENDING
|
||||||
|
try:
|
||||||
|
from bson import ObjectId
|
||||||
|
except:
|
||||||
|
from pymongo.bson import ObjectId
|
||||||
|
|
||||||
|
from app.models import site_config
|
||||||
|
|
||||||
|
connection = Connection()
|
||||||
|
db = connection[settings.MONGO_DB]
|
||||||
|
|
||||||
|
items = db.cables
|
||||||
|
facets = db.facets
|
||||||
|
|
||||||
|
#FIXME: ./manage.py update_indexes
|
||||||
|
'''
|
||||||
|
for key in site_config()['itemKeys']:
|
||||||
|
#items.ensure_index(key['id'], ASCENDING)
|
||||||
|
#items.ensure_index(key['id'], ASCENDING)
|
||||||
|
items.ensure_index(key['id'])
|
||||||
|
'''
|
||||||
|
|
||||||
|
def save(item):
|
||||||
|
#update facets
|
||||||
|
for f in self.facets:
|
||||||
|
new = filter(lambda i: i not in self.old_document.get(key, []), self.document.get(key, []))
|
||||||
|
removed = filter(lambda i: i not in self.document.get(key, []), self.old_document.get(key, []))
|
||||||
|
|
||||||
|
for k in new:
|
||||||
|
facet = facets.find_one({'facet': f, 'value': k})
|
||||||
|
if not facet:
|
||||||
|
facet = facets.insert({'facet': f, 'value': k, 'count': 0})
|
||||||
|
facet['count'] += 1
|
||||||
|
facets.save(facet)
|
||||||
|
|
||||||
|
for k in removed:
|
||||||
|
facet = facets.find_one({'facet': f, 'value': k})
|
||||||
|
if facet:
|
||||||
|
facet['count'] -= 1
|
||||||
|
if facet['count'] <= 0:
|
||||||
|
facets.remove(facet)
|
||||||
|
else:
|
||||||
|
facets.save(facet)
|
||||||
|
items.save(self.document)
|
||||||
|
|
16
oxbrowser/item/tests.py
Normal file
16
oxbrowser/item/tests.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
This file demonstrates writing tests using the unittest module. These will pass
|
||||||
|
when you run "manage.py test".
|
||||||
|
|
||||||
|
Replace this with more appropriate tests for your application.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleTest(TestCase):
|
||||||
|
def test_basic_addition(self):
|
||||||
|
"""
|
||||||
|
Tests that 1 + 1 always equals 2.
|
||||||
|
"""
|
||||||
|
self.assertEqual(1 + 1, 2)
|
9
oxbrowser/item/urls.py
Normal file
9
oxbrowser/item/urls.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
|
||||||
|
from django.conf.urls.defaults import *
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = patterns("item.views",
|
||||||
|
|
||||||
|
)
|
61
oxbrowser/item/utils.py
Normal file
61
oxbrowser/item/utils.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
#
|
||||||
|
from decimal import Decimal
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import hashlib
|
||||||
|
import unicodedata
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
import ox
|
||||||
|
import ox.iso
|
||||||
|
from ox.normalize import normalizeName, normalizeTitle
|
||||||
|
import ox.web.imdb
|
||||||
|
|
||||||
|
|
||||||
|
def parse_decimal(string):
|
||||||
|
string = string.replace(':', '/')
|
||||||
|
if '/' not in string:
|
||||||
|
string = '%s/1' % string
|
||||||
|
d = string.split('/')
|
||||||
|
return Decimal(d[0]) / Decimal(d[1])
|
||||||
|
|
||||||
|
|
||||||
|
def plural_key(term):
|
||||||
|
return {
|
||||||
|
'country': 'countries',
|
||||||
|
}.get(term, term + 's')
|
||||||
|
|
||||||
|
|
||||||
|
def sort_string(string):
|
||||||
|
string = string.replace(u'Þ', 'Th')
|
||||||
|
#pad numbered titles
|
||||||
|
string = re.sub('(\d+)', lambda x: '%010d' % int(x.group(0)), string)
|
||||||
|
return unicodedata.normalize('NFKD', string)
|
||||||
|
|
||||||
|
|
||||||
|
def sort_title(title):
|
||||||
|
#title
|
||||||
|
title = re.sub(u'[\'!¿¡,\.;\-"\:\*\[\]]', '', title)
|
||||||
|
|
||||||
|
#title = title.replace(u'Æ', 'Ae')
|
||||||
|
if isinstance(title, str):
|
||||||
|
title = unicode(title)
|
||||||
|
title = sort_string(title)
|
||||||
|
|
||||||
|
return title.strip()
|
||||||
|
|
||||||
|
def get_positions(ids, pos):
|
||||||
|
'''
|
||||||
|
>>> get_positions([1,2,3,4], [2,4])
|
||||||
|
{2: 1, 4: 3}
|
||||||
|
'''
|
||||||
|
positions = {}
|
||||||
|
for i in pos:
|
||||||
|
try:
|
||||||
|
positions[i] = ids.index(i)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return positions
|
439
oxbrowser/item/views.py
Normal file
439
oxbrowser/item/views.py
Normal file
|
@ -0,0 +1,439 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
from __future__ import division
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import mimetypes
|
||||||
|
import re
|
||||||
|
|
||||||
|
from django.db.models import Count, Sum, Max
|
||||||
|
from django.http import HttpResponse, Http404
|
||||||
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from ox.utils import json
|
||||||
|
|
||||||
|
from ox.django.decorators import login_required_json
|
||||||
|
from ox.django.shortcuts import render_to_json_response, get_object_or_404_json, json_response
|
||||||
|
from ox.django.http import HttpFileResponse
|
||||||
|
import ox
|
||||||
|
|
||||||
|
import models
|
||||||
|
import utils
|
||||||
|
|
||||||
|
from api.actions import actions
|
||||||
|
|
||||||
|
from pymongo import ASCENDING, DESCENDING
|
||||||
|
|
||||||
|
|
||||||
|
def _order_query(qs, sort, prefix='sort__'):
|
||||||
|
order_by = []
|
||||||
|
if len(sort) == 1:
|
||||||
|
if sort[0]['key'] == 'title':
|
||||||
|
sort.append({'operator': '-', 'key': 'year'})
|
||||||
|
sort.append({'operator': '+', 'key': 'director'})
|
||||||
|
elif sort[0]['key'] == 'director':
|
||||||
|
sort.append({'operator': '-', 'key': 'year'})
|
||||||
|
sort.append({'operator': '+', 'key': 'title'})
|
||||||
|
elif sort[0]['key'] == 'year':
|
||||||
|
sort.append({'operator': '+', 'key': 'director'})
|
||||||
|
sort.append({'operator': '+', 'key': 'title'})
|
||||||
|
elif not sort[0]['key'] in ('value', 'value_sort'):
|
||||||
|
sort.append({'operator': '+', 'key': 'director'})
|
||||||
|
sort.append({'operator': '-', 'key': 'year'})
|
||||||
|
sort.append({'operator': '+', 'key': 'title'})
|
||||||
|
|
||||||
|
for e in sort:
|
||||||
|
operator = e['operator']
|
||||||
|
if operator != '-':
|
||||||
|
operator = ''
|
||||||
|
key = {
|
||||||
|
'id': 'itemId',
|
||||||
|
'accessed': 'accessed__access',
|
||||||
|
'viewed': 'accessed__access',
|
||||||
|
}.get(e['key'], e['key'])
|
||||||
|
if key not in ('accessed__access', 'accessed__accessed'):
|
||||||
|
key = "%s%s" % (prefix, key)
|
||||||
|
order = '%s%s' % (operator, key)
|
||||||
|
order_by.append(order)
|
||||||
|
if order_by:
|
||||||
|
qs = qs.order_by(*order_by, nulls_last=True)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def _order_by_group(query):
|
||||||
|
if 'sort' in query:
|
||||||
|
if len(query['sort']) == 1 and query['sort'][0]['key'] == 'items':
|
||||||
|
if query['group'] == "year":
|
||||||
|
order_by = query['sort'][0]['operator'] == '-' and 'items' or '-items'
|
||||||
|
else:
|
||||||
|
order_by = query['sort'][0]['operator'] == '-' and '-items' or 'items'
|
||||||
|
if query['group'] != "keyword":
|
||||||
|
order_by = (order_by, 'value_sort')
|
||||||
|
else:
|
||||||
|
order_by = (order_by,)
|
||||||
|
else:
|
||||||
|
order_by = query['sort'][0]['operator'] == '-' and '-value_sort' or 'value_sort'
|
||||||
|
order_by = (order_by, 'items')
|
||||||
|
else:
|
||||||
|
order_by = ('-value_sort', 'items')
|
||||||
|
return order_by
|
||||||
|
|
||||||
|
def parseQuery(q):
|
||||||
|
'''
|
||||||
|
query: {
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
value: "war"
|
||||||
|
}
|
||||||
|
{
|
||||||
|
key: "year",
|
||||||
|
value: "1970-1980,
|
||||||
|
operator: "!="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "country",
|
||||||
|
value: "f",
|
||||||
|
operator: "^"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
operator: "&"
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
#FIXME: support or operator
|
||||||
|
#FIXME: support sub conditions with $or/$and
|
||||||
|
#http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-ConditionalOperators
|
||||||
|
conditions = q.get('conditions', [])
|
||||||
|
operator = q.get('operator', '&')
|
||||||
|
|
||||||
|
query = {}
|
||||||
|
for c in conditions:
|
||||||
|
key = c.get('key', 'all')
|
||||||
|
op = c.get('operator')
|
||||||
|
value = c['value']
|
||||||
|
if op == '>':
|
||||||
|
mop = '$gt'
|
||||||
|
elif op == '>=':
|
||||||
|
mop = '$gte'
|
||||||
|
elif op == '<':
|
||||||
|
mop = '$lt'
|
||||||
|
elif op == '<=':
|
||||||
|
mop = '$lte'
|
||||||
|
elif op == '!=':
|
||||||
|
mop = '$ne'
|
||||||
|
elif op == '^':
|
||||||
|
mop = None
|
||||||
|
value = re.compile(u'^%s'%value, re.IGNORECASE)
|
||||||
|
elif op == '$':
|
||||||
|
mop = None
|
||||||
|
value = re.compile(u'%s$'%value, re.IGNORECASE)
|
||||||
|
elif op == '=':
|
||||||
|
mop = None
|
||||||
|
else:
|
||||||
|
mop = None
|
||||||
|
value = re.compile(u'%s'%value, re.IGNORECASE)
|
||||||
|
|
||||||
|
if mop:
|
||||||
|
query[key]= {mop: value}
|
||||||
|
else:
|
||||||
|
query[key] = value
|
||||||
|
|
||||||
|
return query
|
||||||
|
|
||||||
|
def parse_query(data, user):
|
||||||
|
query = {}
|
||||||
|
query['range'] = [0, 100]
|
||||||
|
query['sort'] = [{'key':'title', 'operator':'+'}]
|
||||||
|
for key in ('sort', 'keys', 'group', 'range', 'position', 'positions'):
|
||||||
|
if key in data:
|
||||||
|
query[key] = data[key]
|
||||||
|
query['q'] = parseQuery(data.get('query', {}))
|
||||||
|
#query['qs'] = models.Item.objects.find(data, user)
|
||||||
|
|
||||||
|
#group by only allows sorting by name or number of itmes
|
||||||
|
return query
|
||||||
|
|
||||||
|
def find(request):
|
||||||
|
'''
|
||||||
|
param data {
|
||||||
|
'query': query,
|
||||||
|
'sort': array,
|
||||||
|
'range': array
|
||||||
|
}
|
||||||
|
|
||||||
|
query: query object, more on query syntax at
|
||||||
|
https://wiki.0x2620.org/wiki/pandora/QuerySyntax
|
||||||
|
sort: array of key, operator dics
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: "year",
|
||||||
|
operator: "-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "director",
|
||||||
|
operator: ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
range: result range, array [from, to]
|
||||||
|
keys: array of keys to return
|
||||||
|
group: group elements by, country, genre, director...
|
||||||
|
|
||||||
|
with keys, items is list of dicts with requested properties:
|
||||||
|
return {'status': {'code': int, 'text': string},
|
||||||
|
'data': {items: array}}
|
||||||
|
|
||||||
|
Groups
|
||||||
|
param data {
|
||||||
|
'query': query,
|
||||||
|
'key': string,
|
||||||
|
'group': string,
|
||||||
|
'range': array
|
||||||
|
}
|
||||||
|
|
||||||
|
query: query object, more on query syntax at
|
||||||
|
https://wiki.0x2620.org/wiki/pandora/QuerySyntax
|
||||||
|
range: result range, array [from, to]
|
||||||
|
keys: array of keys to return
|
||||||
|
group: group elements by, country, genre, director...
|
||||||
|
|
||||||
|
possible values for keys: name, items
|
||||||
|
|
||||||
|
with keys
|
||||||
|
items contains list of {'name': string, 'items': int}:
|
||||||
|
return {'status': {'code': int, 'text': string},
|
||||||
|
'data': {items: array}}
|
||||||
|
|
||||||
|
without keys: return number of items in given query
|
||||||
|
return {'status': {'code': int, 'text': string},
|
||||||
|
'data': {items: int}}
|
||||||
|
|
||||||
|
Positions
|
||||||
|
param data {
|
||||||
|
'query': query,
|
||||||
|
'positions': [],
|
||||||
|
'sort': array
|
||||||
|
}
|
||||||
|
|
||||||
|
query: query object, more on query syntax at
|
||||||
|
https://wiki.0x2620.org/wiki/pandora/QuerySyntax
|
||||||
|
positions: ids of items for which positions are required
|
||||||
|
return {
|
||||||
|
status: {...},
|
||||||
|
data: {
|
||||||
|
positions: {
|
||||||
|
id: position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
data = json.loads(request.POST['data'])
|
||||||
|
if settings.JSON_DEBUG:
|
||||||
|
print json.dumps(data, indent=2)
|
||||||
|
query = parse_query(data, request.user)
|
||||||
|
|
||||||
|
response = json_response({})
|
||||||
|
if 'group' in query:
|
||||||
|
'''
|
||||||
|
response['data']['items'] = []
|
||||||
|
items = 'items'
|
||||||
|
item_qs = query['qs']
|
||||||
|
order_by = _order_by_group(query)
|
||||||
|
qs = models.Facet.objects.filter(key=query['group']).filter(item__id__in=item_qs)
|
||||||
|
qs = qs.values('value').annotate(items=Count('id')).order_by(*order_by)
|
||||||
|
|
||||||
|
if 'positions' in query:
|
||||||
|
response['data']['positions'] = {}
|
||||||
|
ids = [j['value'] for j in qs]
|
||||||
|
response['data']['positions'] = utils.get_positions(ids, query['positions'])
|
||||||
|
elif 'range' in data:
|
||||||
|
qs = qs[query['range'][0]:query['range'][1]]
|
||||||
|
response['data']['items'] = [{'name': i['value'], 'items': i[items]} for i in qs]
|
||||||
|
else:
|
||||||
|
response['data']['items'] = qs.count()
|
||||||
|
'''
|
||||||
|
if 'positions' in query:
|
||||||
|
response['data']['positions'] = {}
|
||||||
|
elif 'range' in data:
|
||||||
|
response['data']['items'] = []
|
||||||
|
else:
|
||||||
|
response['data']['items'] = 0
|
||||||
|
elif 'position' in query:
|
||||||
|
'''
|
||||||
|
qs = _order_query(query['qs'], query['sort'])
|
||||||
|
ids = [j['itemId'] for j in qs.values('itemId')]
|
||||||
|
data['conditions'] = data['conditions'] + {
|
||||||
|
'value': query['position'],
|
||||||
|
'key': query['sort'][0]['key'],
|
||||||
|
'operator': '^'
|
||||||
|
}
|
||||||
|
query = parse_query(data, request.user)
|
||||||
|
qs = _order_query(query['qs'], query['sort'])
|
||||||
|
if qs.count() > 0:
|
||||||
|
response['data']['position'] = utils.get_positions(ids, [qs[0].itemId])[0]
|
||||||
|
'''
|
||||||
|
response['data']['position'] = -1
|
||||||
|
elif 'positions' in query:
|
||||||
|
'''
|
||||||
|
qs = _order_query(query['qs'], query['sort'])
|
||||||
|
ids = [j['itemId'] for j in qs.values('itemId')]
|
||||||
|
response['data']['positions'] = utils.get_positions(ids, query['positions'])
|
||||||
|
'''
|
||||||
|
response['data']['position'] = {}
|
||||||
|
elif 'keys' in query:
|
||||||
|
response['data']['items'] = []
|
||||||
|
'''
|
||||||
|
qs = _order_query(query['qs'], query['sort'])
|
||||||
|
_p = query['keys']
|
||||||
|
def only_p_sums(m):
|
||||||
|
r = {}
|
||||||
|
for p in _p:
|
||||||
|
if p == 'viewed' and request.user.is_authenticated():
|
||||||
|
value = m.accessed.filter(user=request.user).annotate(v=Max('access'))
|
||||||
|
r[p] = value.exists() and value[0].v or None
|
||||||
|
elif p == 'accessed':
|
||||||
|
r[p] = m.a
|
||||||
|
elif p == 'popularity':
|
||||||
|
r[p] = m.sort.popularity
|
||||||
|
else:
|
||||||
|
r[p] = m.json.get(p, '')
|
||||||
|
if 'annotations' in query:
|
||||||
|
n = query['annotations']
|
||||||
|
r['annotations'] = [a.json(layer=True)
|
||||||
|
for a in query['aqs'].filter(itemID=m.id)[:n]]
|
||||||
|
return r
|
||||||
|
def only_p(m):
|
||||||
|
r = {}
|
||||||
|
if m:
|
||||||
|
m = json.loads(m, object_hook=ox.django.fields.from_json)
|
||||||
|
for p in _p:
|
||||||
|
r[p] = m.get(p, '')
|
||||||
|
if 'annotations' in query:
|
||||||
|
n = query['annotations']
|
||||||
|
r['annotations'] = [a.json(layer=True)
|
||||||
|
for a in query['aqs'].filter(item__itemId=m['id'])[:n]]
|
||||||
|
return r
|
||||||
|
|
||||||
|
qs = qs[query['range'][0]:query['range'][1]]
|
||||||
|
#response['data']['items'] = [m.get_json(_p) for m in qs]
|
||||||
|
if 'popularity' in _p:
|
||||||
|
qs = qs.annotate(popularity=Sum('accessed__accessed'))
|
||||||
|
if 'accessed' in _p:
|
||||||
|
qs = qs.annotate(a=Max('accessed__access'))
|
||||||
|
if 'viewed' in _p or 'popularity' in _p or 'accessed' in _p:
|
||||||
|
response['data']['items'] = [only_p_sums(m) for m in qs]
|
||||||
|
else:
|
||||||
|
response['data']['items'] = [only_p(m['json']) for m in qs.values('json')]
|
||||||
|
'''
|
||||||
|
def only_p(i):
|
||||||
|
r = {}
|
||||||
|
for key in query['keys']:
|
||||||
|
r[key] = i.get(key, '')
|
||||||
|
return r
|
||||||
|
print query
|
||||||
|
skey = query['sort'][0]['key']
|
||||||
|
if query['sort'][0]['operator'] == '+':
|
||||||
|
sdir = ASCENDING
|
||||||
|
else:
|
||||||
|
sdir = DESCENDING
|
||||||
|
qs = models.items.find(query['q']).sort(skey, sdir)
|
||||||
|
if query['range'][0] < query['range'][1]:
|
||||||
|
qs = qs[query['range'][0]:query['range'][1]]
|
||||||
|
response['data']['items'] = [only_p(i) for i in qs]
|
||||||
|
|
||||||
|
else: # otherwise stats
|
||||||
|
'''
|
||||||
|
items = query['qs']
|
||||||
|
files = File.objects.filter(item__in=items).filter(size__gt=0)
|
||||||
|
r = files.aggregate(
|
||||||
|
Sum('duration'),
|
||||||
|
Sum('pixels'),
|
||||||
|
Sum('size')
|
||||||
|
)
|
||||||
|
response['data']['duration'] = r['duration__sum']
|
||||||
|
response['data']['files'] = files.count()
|
||||||
|
response['data']['items'] = items.count()
|
||||||
|
response['data']['pixels'] = r['pixels__sum']
|
||||||
|
response['data']['runtime'] = items.aggregate(Sum('sort__runtime'))['sort__runtime__sum']
|
||||||
|
response['data']['size'] = r['size__sum']
|
||||||
|
for key in ('runtime', 'duration', 'pixels', 'size'):
|
||||||
|
if response['data'][key] == None:
|
||||||
|
response['data'][key] = 0
|
||||||
|
'''
|
||||||
|
response['data']['items'] = models.items.find(query['q']).count()
|
||||||
|
|
||||||
|
return render_to_json_response(response)
|
||||||
|
actions.register(find)
|
||||||
|
|
||||||
|
|
||||||
|
def autocomplete(request):
|
||||||
|
'''
|
||||||
|
param data
|
||||||
|
key
|
||||||
|
value
|
||||||
|
operator '', '^', '$'
|
||||||
|
range
|
||||||
|
return
|
||||||
|
'''
|
||||||
|
data = json.loads(request.POST['data'])
|
||||||
|
if not 'range' in data:
|
||||||
|
data['range'] = [0, 10]
|
||||||
|
op = data.get('operator', '')
|
||||||
|
|
||||||
|
site_config = models.site_config()
|
||||||
|
key = site_config['keys'][data['key']]
|
||||||
|
order_by = key.get('autocompleteSortKey', False)
|
||||||
|
if order_by:
|
||||||
|
order_by = '-sort__%s' % order_by
|
||||||
|
else:
|
||||||
|
order_by = '-items'
|
||||||
|
sort_type = key.get('sort', key.get('type', 'string'))
|
||||||
|
if sort_type == 'title':
|
||||||
|
qs = parse_query({'query': data.get('query', {})}, request.user)['qs']
|
||||||
|
if data['value']:
|
||||||
|
if op == '':
|
||||||
|
qs = qs.filter(find__key=data['key'], find__value__icontains=data['value'])
|
||||||
|
elif op == '^':
|
||||||
|
qs = qs.filter(find__key=data['key'], find__value__istartswith=data['value'])
|
||||||
|
elif op == '$':
|
||||||
|
qs = qs.filter(find__key=data['key'], find__value__iendswith=data['value'])
|
||||||
|
qs = qs.order_by(order_by, nulls_last=True)
|
||||||
|
qs = qs[data['range'][0]:data['range'][1]]
|
||||||
|
response = json_response({})
|
||||||
|
response['data']['items'] = [i.get(data['key']) for i in qs]
|
||||||
|
else:
|
||||||
|
qs = models.Facet.objects.filter(key=data['key'])
|
||||||
|
if data['value']:
|
||||||
|
if op == '':
|
||||||
|
qs = qs.filter(value__icontains=data['value'])
|
||||||
|
elif op == '^':
|
||||||
|
qs = qs.filter(value__istartswith=data['value'])
|
||||||
|
elif op == '$':
|
||||||
|
qs = qs.filter(value__iendswith=data['value'])
|
||||||
|
qs = qs.values('value').annotate(items=Count('id'))
|
||||||
|
qs = qs.order_by(order_by)
|
||||||
|
qs = qs[data['range'][0]:data['range'][1]]
|
||||||
|
response = json_response({})
|
||||||
|
response['data']['items'] = [i['value'] for i in qs]
|
||||||
|
return render_to_json_response(response)
|
||||||
|
actions.register(autocomplete)
|
||||||
|
|
||||||
|
|
||||||
|
def get(request):
|
||||||
|
'''
|
||||||
|
param data {
|
||||||
|
id: string
|
||||||
|
keys: array
|
||||||
|
}
|
||||||
|
return item array
|
||||||
|
'''
|
||||||
|
response = json_response({})
|
||||||
|
data = json.loads(request.POST['data'])
|
||||||
|
item = models.items.find_one({'refid': data['id']})
|
||||||
|
if item:
|
||||||
|
del item['_id']
|
||||||
|
response['data'] = item
|
||||||
|
else:
|
||||||
|
response = json_response(status=404, text='not found')
|
||||||
|
return render_to_json_response(response)
|
||||||
|
actions.register(get)
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
import os
|
import os
|
||||||
from os.path import join
|
from os.path import join
|
||||||
|
|
||||||
|
SITEID = 'oxbrowser'
|
||||||
|
URL = 'http://cablegates.org'
|
||||||
SITENAME = 'oxbrowser'
|
SITENAME = 'oxbrowser'
|
||||||
|
|
||||||
PROJECT_ROOT = os.path.normpath(os.path.dirname(__file__))
|
PROJECT_ROOT = os.path.normpath(os.path.dirname(__file__))
|
||||||
|
@ -32,7 +34,7 @@ DATABASES = {
|
||||||
'PORT': ''
|
'PORT': ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MONGO_DB = 'cablegates'
|
||||||
#CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
|
#CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
|
||||||
|
|
||||||
# Local time zone for this installation. Choices can be found here:
|
# Local time zone for this installation. Choices can be found here:
|
||||||
|
@ -56,8 +58,8 @@ APPEND_SLASH = False
|
||||||
|
|
||||||
# Absolute path to the directory that holds media.
|
# Absolute path to the directory that holds media.
|
||||||
# Example: "/home/media/media.lawrence.com/"
|
# Example: "/home/media/media.lawrence.com/"
|
||||||
MEDIA_ROOT = join(PROJECT_ROOT, 'media')
|
MEDIA_ROOT = join(PROJECT_ROOT, '..', 'media')
|
||||||
STATIC_ROOT = join(PROJECT_ROOT, 'static')
|
STATIC_ROOT = join(PROJECT_ROOT, '..', 'static')
|
||||||
|
|
||||||
|
|
||||||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
||||||
|
@ -101,9 +103,14 @@ INSTALLED_APPS = (
|
||||||
'django.contrib.humanize',
|
'django.contrib.humanize',
|
||||||
'django_extensions',
|
'django_extensions',
|
||||||
#'south',
|
#'south',
|
||||||
|
'app',
|
||||||
|
'api',
|
||||||
|
'item',
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SITE_CONFIG = join(PROJECT_ROOT, 'cables.json')
|
||||||
|
|
||||||
|
|
||||||
#overwrite default settings with local settings
|
#overwrite default settings with local settings
|
||||||
try:
|
try:
|
||||||
|
|
18
oxbrowser/templates/index.html
Normal file
18
oxbrowser/templates/index.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{{settings.SITENAME}}</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
|
<link rel="shortcut icon" type="image/png" href="/static/png/icon16.png"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/css/site.css" />
|
||||||
|
<script type='text/javascript'>
|
||||||
|
if (typeof(console) == 'undefined') {
|
||||||
|
console = {log: function() {}};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="/static/oxjs/build/Ox.js"></script>
|
||||||
|
<script type="text/javascript" src="/static/js/site.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,17 +1,33 @@
|
||||||
from django.conf.urls.defaults import patterns, include, url
|
from django.conf.urls.defaults import patterns, include, url
|
||||||
|
|
||||||
# Uncomment the next two lines to enable the admin:
|
import os
|
||||||
# from django.contrib import admin
|
from ox.django.http import HttpFileResponse
|
||||||
# admin.autodiscover()
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
admin.autodiscover()
|
||||||
|
|
||||||
|
from api import actions
|
||||||
|
actions.autodiscover()
|
||||||
|
|
||||||
|
def serve_static_file(path, location, content_type):
|
||||||
|
return HttpFileResponse(location, content_type=content_type)
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
# Examples:
|
(r'^admin/', include(admin.site.urls)),
|
||||||
# url(r'^$', 'oxbrowser.views.home', name='home'),
|
|
||||||
# url(r'^oxbrowser/', include('oxbrowser.foo.urls')),
|
|
||||||
|
|
||||||
# Uncomment the admin/doc line below to enable admin documentation:
|
(r'^api/$', include('api.urls')),
|
||||||
# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
(r'^$', 'app.views.index'),
|
||||||
|
(r'', include('item.urls')),
|
||||||
# Uncomment the next line to enable the admin:
|
(r'^robots.txt$', serve_static_file, {'location': os.path.join(settings.STATIC_ROOT, 'robots.txt'), 'content_type': 'text/plain'}),
|
||||||
# url(r'^admin/', include(admin.site.urls)),
|
(r'^favicon.ico$', serve_static_file, {'location': os.path.join(settings.STATIC_ROOT, 'png/icon.16.png'), 'content_type': 'image/x-icon'}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
urlpatterns += patterns('',
|
||||||
|
(r'^data/(?P<path>.*)$', 'django.views.static.serve',
|
||||||
|
{'document_root': settings.MEDIA_ROOT}),
|
||||||
|
(r'^static/(?P<path>.*)$', 'django.views.static.serve',
|
||||||
|
{'document_root': settings.STATIC_ROOT}),
|
||||||
|
)
|
||||||
|
|
3
static/css/site.css
Normal file
3
static/css/site.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.highlight {
|
||||||
|
background: rgb(255, 255, 0);
|
||||||
|
}
|
124
static/js/site.js
Normal file
124
static/js/site.js
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
// vim: et:ts=4:sw=4:sts=4:ft=javascript
|
||||||
|
|
||||||
|
Ox.load('UI', function() {
|
||||||
|
Ox.Theme('classic');
|
||||||
|
window.app = new Ox.App({url: '/api/'}).bindEvent({
|
||||||
|
load: function(event, data) {
|
||||||
|
app.site = {
|
||||||
|
sortKeys: $.map(data.site.itemKeys, function(key, i) {
|
||||||
|
return key.columnWidth ? key : null;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
var position = 0;
|
||||||
|
app.main = Ox.SplitPanel({
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
element: app.list = Ox.TextList({
|
||||||
|
columns: $.map(app.site.sortKeys, function(key, i) {
|
||||||
|
var pos = -1;
|
||||||
|
if($.inArray(key.id, ['subject', 'origin', 'created'])>=0)
|
||||||
|
pos = position++;
|
||||||
|
return {
|
||||||
|
align: ['string', 'text'].indexOf(
|
||||||
|
Ox.isArray(key.type) ? key.type[0]: key.type
|
||||||
|
) > -1 ? 'left' : 'right',
|
||||||
|
defaultWidth: key.columnWidth,
|
||||||
|
format: key.format,
|
||||||
|
id: key.id,
|
||||||
|
operator: '+',
|
||||||
|
position: pos,
|
||||||
|
removable: !key.columnRequired,
|
||||||
|
title: key.title,
|
||||||
|
type: key.type,
|
||||||
|
unique: key.id == 'refid',
|
||||||
|
visible: $.inArray(key.id, ['subject', 'origin', 'created'])>=0,
|
||||||
|
width: key.columnWidth
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
columnsMovable: true,
|
||||||
|
columnsRemovable: true,
|
||||||
|
columnsResizable: true,
|
||||||
|
columnsVisible: true,
|
||||||
|
draggable: true,
|
||||||
|
items: function(data, callback) {
|
||||||
|
app.api.find(data, callback);
|
||||||
|
},
|
||||||
|
scrollbarVisible: true,
|
||||||
|
sort: [{key: 'created', operator: '-'}]
|
||||||
|
}).bindEvent({
|
||||||
|
select: function(data) {
|
||||||
|
data.ids.length && select(data.ids[0]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: app.cable = Ox.Element().addClass('OxSelectable').css({overflow: 'auto'}),
|
||||||
|
collapsible: true,
|
||||||
|
size: 600,
|
||||||
|
resizable: true,
|
||||||
|
resize: [200, 400, 600]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
orientation: 'horizontal'
|
||||||
|
});
|
||||||
|
app.find = Ox.Input({
|
||||||
|
clear: true,
|
||||||
|
id: 'input',
|
||||||
|
placeholder: 'Find...',
|
||||||
|
value: '',
|
||||||
|
width: 192
|
||||||
|
}).css({
|
||||||
|
float: 'right',
|
||||||
|
margin: '4px'
|
||||||
|
})
|
||||||
|
.bindEvent({
|
||||||
|
submit: function(data) {
|
||||||
|
find({conditions: [{key: 'content', value: data.value, operator: ''}]});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
app.frame = Ox.SplitPanel({
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
element: app.bar = Ox.Bar().append(app.find),
|
||||||
|
size: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: app.main
|
||||||
|
}
|
||||||
|
],
|
||||||
|
orientation: 'vertical'
|
||||||
|
}).appendTo($('body'));
|
||||||
|
if(document.location.hash) {
|
||||||
|
select(document.location.hash.substring(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
function select(id) {
|
||||||
|
app.api.get({id: id}, function(result) {
|
||||||
|
var term = app.find.value(),
|
||||||
|
header = result.data.header,
|
||||||
|
content = result.data.content;
|
||||||
|
document.location.hash = '#' + result.data.refid;
|
||||||
|
if(term) {
|
||||||
|
header = Ox.highlight(header, term, 'highlight');
|
||||||
|
content = Ox.highlight(content, term, 'highlight');
|
||||||
|
}
|
||||||
|
app.cable.html('')
|
||||||
|
.append($('<div>').css('padding', '8px')
|
||||||
|
.addClass('OxSelectable')
|
||||||
|
.append($('<pre>').html(header))
|
||||||
|
.append($('<pre>').html(content)
|
||||||
|
))
|
||||||
|
.scrollTop(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function find(query) {
|
||||||
|
app.list.options({
|
||||||
|
items: function(data, callback) {
|
||||||
|
app.api.find(Ox.extend(data, {
|
||||||
|
query: query
|
||||||
|
}), callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue