pandora/pandora/document/views.py

514 lines
18 KiB
Python

# -*- coding: utf-8 -*-
import os
import re
from glob import glob
import unicodedata
import ox
from ox.utils import json
from oxdjango.api import actions
from oxdjango.decorators import login_required_json
from oxdjango.http import HttpFileResponse
from oxdjango.shortcuts import render_to_json_response, get_object_or_404_json, json_response, HttpErrorJson
from django import forms
from django.db.models import Count, Sum
from django.conf import settings
from django.http import HttpResponse
from item import utils
from item.models import Item
from itemlist.models import List
from entity.models import Entity
from archive.chunk import process_chunk
from changelog.models import add_changelog
from . import models
from . import tasks
from . import page_views
def get_document_or_404_json(request, id):
response = {'status': {'code': 404,
'text': 'Document not found'}}
try:
document = models.Document.get(id)
except:
raise HttpErrorJson(response)
if not document.access(request.user):
raise HttpErrorJson(response)
return document
@login_required_json
def addDocument(request, data):
'''
Create new html document
takes {
title: string
}
or
Adds one or more documents to one or more items
takes {
item: string or [string], // one or more item ids (optional)
entity: string or [string], // one or more entity ids (optional)
id: string or [string] // one or more document ids
}
notes: either `item` or `entity` must be provided
returns {}
see: editDocument, findDocuments, getDocument, removeDocument, sortDocuments
'''
response = json_response()
if 'title' in data:
doc = models.Document(user=request.user, extension='html')
doc.data['title'] = data['title']
doc.save()
response = json_response(status=200, text='created')
response['data'] = doc.json(user=request.user)
add_changelog(request, data, doc.get_id())
else:
if 'ids' in data:
ids = data['ids']
else:
ids = [data['id']]
if 'item' in data:
if isinstance(data['item'], str):
item = Item.objects.get(public_id=data['item'])
if item.editable(request.user):
for id in ids:
document = models.Document.get(id)
document.add(item)
add_changelog(request, data, item.public_id)
else:
response = json_response(status=403, text='permission denied')
else:
for item in Item.objects.filter(public_id__in=data['item']):
if item.editable(request.user):
for id in ids:
document = models.Document.get(id)
document.add(item)
add_changelog(request, data, data['item'])
elif 'entity' in data:
if isinstance(data['entity'], str):
entity = Entity.get(data['entity'])
if entity.editable(request.user):
for id in ids:
document = models.Document.get(id)
entity.add(document)
add_changelog(request, data, entity.get_id())
else:
response = json_response(status=403, text='permission denied')
else:
for entity in Entity.objects.filter(id__in=map(ox.fromAZ, data['entity'])):
if entity.editable(request.user):
for id in ids:
document = models.Document.get(id)
entity.add(document)
add_changelog(request, data, data['entity'])
else:
response = json_response(status=500, text='invalid request')
return render_to_json_response(response)
actions.register(addDocument, cache=False)
@login_required_json
def editDocument(request, data):
'''
Edits data for a document
takes {
id: string, // document id
key: value, // set new data
description: string // new document description
item: string // item id (optional)
}
returns {
id: string, // document id
... // more document properties
}
notes: If `item` is present, this will not edit the global description of
the document, but its specific description in the context of the given
item.
see: addDocument, findDocuments, getDocument, removeDocument, sortDocuments
'''
response = json_response()
item = 'item' in data and Item.objects.get(public_id=data['item']) or None
if data['id']:
if isinstance(data['id'], list):
add_changelog(request, data)
t = tasks.bulk_edit.delay(data, request.user.username)
response['data']['taskId'] = t.task_id
else:
document = models.Document.get(data['id'])
if document.editable(request.user, item):
add_changelog(request, data)
document.edit(data, request.user, item)
document.save()
response['data'] = document.json(user=request.user, item=item)
else:
response = json_response(status=403, text='permission denied')
else:
response = json_response(status=500, text='invalid request')
return render_to_json_response(response)
actions.register(editDocument, cache=False)
def _order_query(qs, sort, item=None):
prefix = 'sort__'
order_by = []
for e in sort:
operator = e['operator']
if operator != '-':
operator = ''
key = {
'description': 'descriptions__description_sort'
if item else 'description',
'index': 'items__itemproperties__index',
#fixme:
'position': 'id',
'name': 'title',
}.get(e['key'], e['key'])
if key == 'resolution':
order_by.append('%swidth' % operator)
order_by.append('%sheight' % operator)
else:
if '__' not in key and key not in ('created', 'modified'):
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)
qs = qs.distinct()
return qs
def _order_by_group(query):
if 'sort' in query:
if len(query['sort']) == 1 and query['sort'][0]['key'] == 'items':
order_by = query['sort'][0]['operator'] == '-' and '-items' or 'items'
if query['group'] == "year":
secondary = query['sort'][0]['operator'] == '-' and '-sortvalue' or 'sortvalue'
order_by = (order_by, secondary)
elif query['group'] != "keyword":
order_by = (order_by, 'sortvalue')
else:
order_by = (order_by, 'value')
else:
order_by = query['sort'][0]['operator'] == '-' and '-sortvalue' or 'sortvalue'
order_by = (order_by, 'items')
else:
order_by = ('-sortvalue', 'items')
return order_by
def get_item(query):
for c in query.get('conditions', []):
if c.get('key') == 'item':
return c.get('value')
return None
def parse_query(data, user):
query = {}
query['range'] = [0, 100]
query['sort'] = [{'key': 'user', 'operator': '+'}, {'key': 'name', 'operator': '+'}]
for key in ('keys', 'group', 'file', 'range', 'position', 'positions', 'sort'):
if key in data:
query[key] = data[key]
#print(query.get('sort'), data.get('sort'))
query['qs'] = models.Document.objects.find(data, user)
query['item'] = get_item(data.get('query', {}))
return query
def findDocuments(request, data):
'''
Finds documents for a given query
takes {
query: object, // query object, see `find`
sort: [object], // list of sort objects, see `find`
range: [int, int], // range of results, per current sort order
keys: [string] // list of keys to return
}
returns {
items: [{ // list of documents
key: value, // item key and value
... // more key/value pairs
}]
}
Notes: Query keys and keys to be returned can be 'extension', 'name',
'size' and 'user'.
see: addDocument, editDocument, find, getDocument, removeDocument,
sortDocuments
'''
query = parse_query(data, request.user)
#order
qs = _order_query(query['qs'], query['sort'], query['item'])
response = json_response()
if 'group' in query:
response['data']['items'] = []
items = 'items'
document_qs = query['qs']
order_by = _order_by_group(query)
qs = models.Facet.objects.filter(key=query['group']).filter(document__id__in=document_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()
elif 'keys' in data:
qs = qs[query['range'][0]:query['range'][1]]
response['data']['items'] = [l.json(data['keys'], request.user, query['item']) for l in qs]
elif 'position' in data:
#FIXME: actually implement position requests
response['data']['position'] = 0
elif 'positions' in data:
ids = list(qs.values_list('id', flat=True))
response['data']['positions'] = utils.get_positions(ids, query['positions'], decode_id=True)
else:
r = qs.aggregate(
Sum('size')
)
response['data']['items'] = qs.count()
response['data']['size'] = r['size__sum'] or 0
return render_to_json_response(response)
actions.register(findDocuments)
def getDocument(request, data):
'''
Gets a document by id
takes {
id: string, // document id
keys: [string] // list of properties to return
}
returns {
key: value, // document key and value
... // more key/value pairs
}
see: addDocument, editDocument, findDocuments, removeDocument, sortDocuments
'''
response = json_response({})
data['keys'] = data.get('keys', [])
document = get_document_or_404_json(request, data['id'])
response['data'] = document.json(keys=data['keys'], user=request.user)
return render_to_json_response(response)
actions.register(getDocument)
@login_required_json
def removeDocument(request, data):
'''
Removes one or more documents, either from an item/entity or from the database
takes {
id or ids: string or [string], // one or more document ids
item: string // item id (optional)
entity: string // entity id (optional)
}
returns {}
notes: If `item` is present, this removes the documents from that item.
If `entity` is present, this removes the documents from that entity.
Otherwise, it removes the documents from the database.
see: addDocument, editDocument, findDocuments, getDocument, sortDocuments
'''
response = json_response()
if 'ids' in data:
ids = data['ids']
else:
ids = [data['id']]
item = 'item' in data and Item.objects.get(public_id=data['item']) or None
entity = 'entity' in data and Entity.objects.get(id=ox.fromAZ(data['entity'])) or None
if item:
if item.editable(request.user):
add_changelog(request, data, item.public_id)
for id in ids:
document = models.Document.get(id)
document.remove(item)
else:
response = json_response(status=403, text='not allowed')
elif entity:
if entity.editable(request.user):
add_changelog(request, data, entity.get_id())
for id in ids:
document = models.Document.get(id)
entity.remove(document)
else:
response = json_response(status=403, text='not allowed')
else:
add_changelog(request, data, ids)
for id in ids:
document = models.Document.get(id)
if document.editable(request.user):
document.delete()
else:
response = json_response(status=403, text='not allowed')
break
return render_to_json_response(response)
actions.register(removeDocument, cache=False)
@login_required_json
def sortDocuments(request, data):
'''
Sets the sort order for the documents associated with a given item
takes {
item: string, // item id
ids: [string] // ordered list of document ids
}
returns {}
see: addDocument, editDocument, findDocuments, removeDocument, sortDocuments
'''
index = 0
item = Item.objects.get(public_id=data['item'])
ids = data['ids']
if item.editable(request.user):
for i in ids:
document = models.Document.get(i)
models.ItemProperties.objects.filter(item=item, document=document).update(index=index)
index += 1
response = json_response()
add_changelog(request, data, item.public_id)
else:
response = json_response(status=403, text='permission denied')
return render_to_json_response(response)
actions.register(sortDocuments, cache=False)
def file(request, id, name=None):
document = get_document_or_404_json(request, id)
return HttpFileResponse(document.file.path)
def thumbnail(request, id, size=256, page=None):
size = int(size)
document = get_document_or_404_json(request, id)
if "q" in request.GET and page:
img = document.highlight_page(page, request.GET["q"], size)
return HttpResponse(img, content_type="image/jpeg")
return HttpFileResponse(document.thumbnail(size, page=page))
@login_required_json
def upload(request):
if 'id' in request.GET:
file = models.Document.get(request.GET['id'])
elif 'id' in request.POST:
file = models.Document.get(request.POST['id'])
else:
file = None
name, extension = request.POST['filename'].rsplit('.', 1)
extension = extension.lower()
response = json_response(status=400, text='this request requires POST')
if 'chunk' in request.FILES:
if file.editable(request.user):
response = process_chunk(request, file.save_chunk)
response['resultUrl'] = file.get_absolute_url()
# id is used to select document in dialog after upload
response['id'] = file.get_id()
return render_to_json_response(response)
#init upload
else:
if not file:
file = models.Document(user=request.user)
file.data['title'] = name
file.extension = extension
file.uploading = True
file.save()
elif file.editable(request.user):
#replace existing file
if file.file:
file.delete_cache()
file.file.delete()
file.uploading = True
name, extension = request.POST['filename'].rsplit('.', 1)
file.extension = extension
file.width = -1
file.pages = -1
file.save()
else:
return render_to_json_response(response)
add_changelog({
'user': request.user,
'action': 'uploadDocument',
}, {
'name': name,
'extension': extension
}, file.get_id())
upload_url = '/api/upload/document?id=%s' % file.get_id()
return render_to_json_response({
'uploadUrl': upload_url,
'url': file.get_absolute_url(),
'result': 1
})
return render_to_json_response(response)
def autocompleteDocuments(request, data):
'''
Returns autocomplete strings for a given document key and search string
takes {
key: string, // document key
value: string, // search string
operator: string, // '=', '==', '^', '$'
query: object, // document query to limit results, see `find`
range: [int, int] // range of results to return
}
returns {
items: [string, ...] // list of matching strings
}
see: autocomplete, autocompleteEntities
'''
if 'range' not in data:
data['range'] = [0, 10]
op = data.get('operator', '=')
key = utils.get_by_id(settings.CONFIG['documentKeys'], data['key'])
order_by = key.get('autocompleteSort', False)
if order_by:
for o in order_by:
if o['operator'] != '-':
o['operator'] = ''
order_by = ['%(operator)ssort__%(key)s' % o for o in order_by]
else:
order_by = ['-items']
qs = parse_query({'query': data.get('query', {})}, request.user)['qs']
response = json_response({})
response['data']['items'] = []
'''
for d in qs:
value = d.json().get(data['key'])
add = False
if value:
if op == '=' and data['value'] in value:
add = True
elif op == '==' and data['value'].lower() == value.lower():
add = True
elif op == '^' and value.lower().startswith(data['value'].lower()):
add = True
if add and value not in response['data']['items']:
response['data']['items'].append(value)
'''
sort_type = key.get('sortType', key.get('type', 'string'))
qs = models.Facet.objects.filter(key=data['key'])
if data['value']:
value = unicodedata.normalize('NFKD', data['value']).lower()
if op == '=':
qs = qs.filter(value__icontains=value)
elif op == '==':
qs = qs.filter(value__iexact=value)
elif op == '^':
qs = qs.filter(value__istartswith=value)
elif op == '$':
qs = qs.filter(value__iendswith=value)
if 'query' in data:
document_query = parse_query({'query': data.get('query', {})}, request.user)['qs']
qs = qs.filter(document__in=document_query)
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(autocompleteDocuments)