forked from 0x2620/pandora
new documents section
This commit is contained in:
parent
3fcbd59525
commit
e1f35b1ec8
74 changed files with 6737 additions and 631 deletions
0
pandora/documentcollection/__init__.py
Normal file
0
pandora/documentcollection/__init__.py
Normal file
132
pandora/documentcollection/managers.py
Normal file
132
pandora/documentcollection/managers.py
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
from django.db.models import Q, Manager
|
||||
|
||||
from oxdjango.managers import get_operator
|
||||
from oxdjango.query import QuerySet
|
||||
|
||||
keymap = {
|
||||
'user': 'user__username',
|
||||
}
|
||||
default_key = 'name'
|
||||
|
||||
def parseCondition(condition, user):
|
||||
'''
|
||||
'''
|
||||
k = condition.get('key', default_key)
|
||||
k = keymap.get(k, k)
|
||||
if not k:
|
||||
k = default_key
|
||||
v = condition.get('value', '')
|
||||
op = condition.get('operator')
|
||||
if not op:
|
||||
op = '='
|
||||
if op.startswith('!'):
|
||||
op = op[1:]
|
||||
exclude = True
|
||||
else:
|
||||
exclude = False
|
||||
if k == 'id':
|
||||
v = v.split(":")
|
||||
if len(v) >= 2:
|
||||
v = (v[0], ":".join(v[1:]))
|
||||
q = Q(user__username=v[0], name=v[1])
|
||||
else:
|
||||
q = Q(id__in=[])
|
||||
return q
|
||||
if k == 'subscribed':
|
||||
key = 'subscribed_users__username'
|
||||
v = user.username
|
||||
elif isinstance(v, bool):
|
||||
key = k
|
||||
else:
|
||||
key = k + get_operator(op, 'istr')
|
||||
key = str(key)
|
||||
if exclude:
|
||||
q = ~Q(**{key: v})
|
||||
else:
|
||||
q = Q(**{key: v})
|
||||
return q
|
||||
|
||||
def parseConditions(conditions, operator, user):
|
||||
'''
|
||||
conditions: [
|
||||
{
|
||||
value: "war"
|
||||
}
|
||||
{
|
||||
key: "year",
|
||||
value: "1970-1980,
|
||||
operator: "!="
|
||||
},
|
||||
{
|
||||
key: "country",
|
||||
value: "f",
|
||||
operator: "^"
|
||||
}
|
||||
],
|
||||
operator: "&"
|
||||
'''
|
||||
conn = []
|
||||
for condition in conditions:
|
||||
if 'conditions' in condition:
|
||||
q = parseConditions(condition['conditions'],
|
||||
condition.get('operator', '&'), user)
|
||||
if q:
|
||||
conn.append(q)
|
||||
pass
|
||||
else:
|
||||
conn.append(parseCondition(condition, user))
|
||||
if conn:
|
||||
q = conn[0]
|
||||
for c in conn[1:]:
|
||||
if operator == '|':
|
||||
q = q | c
|
||||
else:
|
||||
q = q & c
|
||||
return q
|
||||
return None
|
||||
|
||||
|
||||
class CollectionManager(Manager):
|
||||
|
||||
def get_query_set(self):
|
||||
return QuerySet(self.model)
|
||||
|
||||
def find(self, data, user):
|
||||
'''
|
||||
query: {
|
||||
conditions: [
|
||||
{
|
||||
value: "war"
|
||||
}
|
||||
{
|
||||
key: "year",
|
||||
value: "1970-1980,
|
||||
operator: "!="
|
||||
},
|
||||
{
|
||||
key: "country",
|
||||
value: "f",
|
||||
operator: "^"
|
||||
}
|
||||
],
|
||||
operator: "&"
|
||||
}
|
||||
'''
|
||||
|
||||
#join query with operator
|
||||
qs = self.get_query_set()
|
||||
query = data.get('query', {})
|
||||
conditions = parseConditions(query.get('conditions', []),
|
||||
query.get('operator', '&'),
|
||||
user)
|
||||
if conditions:
|
||||
qs = qs.filter(conditions)
|
||||
|
||||
if user.is_anonymous():
|
||||
qs = qs.filter(Q(status='public') | Q(status='featured'))
|
||||
else:
|
||||
qs = qs.filter(Q(status='public') | Q(status='featured') | Q(user=user))
|
||||
return qs
|
||||
84
pandora/documentcollection/migrations/0001_initial.py
Normal file
84
pandora/documentcollection/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2016-10-08 12:34
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import documentcollection.models
|
||||
import oxdjango.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('document', '0003_new_fields'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Collection',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('modified', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('status', models.CharField(default=b'private', max_length=20)),
|
||||
('query', oxdjango.fields.DictField(default={b'static': True})),
|
||||
('type', models.CharField(default=b'static', max_length=255)),
|
||||
('description', models.TextField(default=b'')),
|
||||
('icon', models.ImageField(blank=True, default=None, upload_to=documentcollection.models.get_icon_path)),
|
||||
('view', models.TextField(default=documentcollection.models.get_collectionview)),
|
||||
('sort', oxdjango.fields.TupleField(default=documentcollection.models.get_collectionsort, editable=False)),
|
||||
('poster_frames', oxdjango.fields.TupleField(default=[], editable=False)),
|
||||
('numberofdocuments', models.IntegerField(default=0)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CollectionDocument',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('modified', models.DateTimeField(auto_now=True)),
|
||||
('index', models.IntegerField(default=0)),
|
||||
('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='documentcollection.Collection')),
|
||||
('document', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='document.Document')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Position',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('section', models.CharField(max_length=255)),
|
||||
('position', models.IntegerField(default=0)),
|
||||
('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='position', to='documentcollection.Collection')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='collection_positions', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='collection',
|
||||
name='documents',
|
||||
field=models.ManyToManyField(related_name='collections', through='documentcollection.CollectionDocument', to='document.Document'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='collection',
|
||||
name='subscribed_users',
|
||||
field=models.ManyToManyField(related_name='subscribed_collections', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='collection',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='collections', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='position',
|
||||
unique_together=set([('user', 'collection', 'section')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='collection',
|
||||
unique_together=set([('user', 'name')]),
|
||||
),
|
||||
]
|
||||
0
pandora/documentcollection/migrations/__init__.py
Normal file
0
pandora/documentcollection/migrations/__init__.py
Normal file
321
pandora/documentcollection/models.py
Normal file
321
pandora/documentcollection/models.py
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
from __future__ import division, print_function, absolute_import
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from glob import glob
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Max
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
import ox
|
||||
|
||||
from oxdjango.fields import DictField, TupleField
|
||||
|
||||
from archive import extract
|
||||
|
||||
from . import managers
|
||||
|
||||
|
||||
def get_path(f, x):
|
||||
return f.path(x)
|
||||
|
||||
def get_icon_path(f, x):
|
||||
return get_path(f, 'icon.jpg')
|
||||
|
||||
def get_collectionview():
|
||||
return settings.CONFIG['user']['ui']['collectionView']
|
||||
|
||||
def get_collectionsort():
|
||||
return tuple(settings.CONFIG['user']['ui']['collectionSort'])
|
||||
|
||||
class Collection(models.Model):
|
||||
|
||||
class Meta:
|
||||
unique_together = ("user", "name")
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
modified = models.DateTimeField(auto_now=True)
|
||||
user = models.ForeignKey(User, related_name='collections')
|
||||
name = models.CharField(max_length=255)
|
||||
status = models.CharField(max_length=20, default='private')
|
||||
_status = ['private', 'public', 'featured']
|
||||
query = DictField(default={"static": True})
|
||||
type = models.CharField(max_length=255, default='static')
|
||||
description = models.TextField(default='')
|
||||
|
||||
icon = models.ImageField(default=None, blank=True, upload_to=get_icon_path)
|
||||
|
||||
view = models.TextField(default=get_collectionview)
|
||||
sort = TupleField(default=get_collectionsort, editable=False)
|
||||
|
||||
poster_frames = TupleField(default=[], editable=False)
|
||||
|
||||
#is through table still required?
|
||||
documents = models.ManyToManyField('document.Document', related_name='collections',
|
||||
through='CollectionDocument')
|
||||
|
||||
numberofdocuments = models.IntegerField(default=0)
|
||||
subscribed_users = models.ManyToManyField(User, related_name='subscribed_collections')
|
||||
|
||||
objects = managers.CollectionManager()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.query.get('static', False):
|
||||
self.type = 'static'
|
||||
else:
|
||||
self.type = 'smart'
|
||||
if self.id:
|
||||
self.numberofdocuments = self.get_numberofdocuments(self.user)
|
||||
super(Collection, self).save(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get(cls, id):
|
||||
id = id.split(':')
|
||||
username = id[0]
|
||||
collectionname = ":".join(id[1:])
|
||||
return cls.objects.get(user__username=username, name=collectionname)
|
||||
|
||||
def get_documents(self, user=None):
|
||||
if self.query.get('static', False):
|
||||
return self.documents
|
||||
from document.models import Document
|
||||
return Document.objects.find({'query': self.query}, user)
|
||||
|
||||
def get_numberofdocuments(self, user=None):
|
||||
return self.get_documents(user).count()
|
||||
|
||||
def add(self, document):
|
||||
q = self.documents.filter(id=document.id)
|
||||
if q.count() == 0:
|
||||
l = CollectionDocument()
|
||||
l.collection = self
|
||||
l.document = document
|
||||
l.index = CollectionDocument.objects.filter(collection=self).aggregate(Max('index'))['index__max']
|
||||
if l.index is None:
|
||||
l.index = 0
|
||||
else:
|
||||
l.index += 1
|
||||
l.save()
|
||||
|
||||
def remove(self, document=None, documents=None):
|
||||
if document:
|
||||
CollectionDocument.objects.all().filter(document=document, collection=self).delete()
|
||||
if documents:
|
||||
CollectionDocument.objects.all().filter(document__id__in=documents, collection=self).delete()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.get_id()
|
||||
|
||||
def get_id(self):
|
||||
return u'%s:%s' % (self.user.username, self.name)
|
||||
|
||||
def accessible(self, user):
|
||||
return self.user == user or self.status in ('public', 'featured')
|
||||
|
||||
def editable(self, user):
|
||||
if user.is_anonymous():
|
||||
return False
|
||||
if self.user == user or \
|
||||
user.is_staff or \
|
||||
user.profile.capability('canEditFeaturedCollections') is True:
|
||||
return True
|
||||
return False
|
||||
|
||||
def edit(self, data, user):
|
||||
for key in data:
|
||||
if key == 'query' and not data['query']:
|
||||
setattr(self, key, {"static": True})
|
||||
elif key == 'query' and isinstance(data[key], dict):
|
||||
setattr(self, key, data[key])
|
||||
elif key == 'type':
|
||||
if data[key] == 'static':
|
||||
self.query = {"static": True}
|
||||
self.type = 'static'
|
||||
else:
|
||||
self.type = 'smart'
|
||||
if self.query.get('static', False):
|
||||
self.query = {}
|
||||
elif key == 'status':
|
||||
value = data[key]
|
||||
if value not in self._status:
|
||||
value = self._status[0]
|
||||
if value == 'private':
|
||||
for user in self.subscribed_users.all():
|
||||
self.subscribed_users.remove(user)
|
||||
qs = Position.objects.filter(user=user, collection=self)
|
||||
if qs.count() > 1:
|
||||
pos = qs[0]
|
||||
pos.section = 'personal'
|
||||
pos.save()
|
||||
elif value == 'featured':
|
||||
if user.profile.capability('canEditFeaturedCollections'):
|
||||
pos, created = Position.objects.get_or_create(collection=self, user=user,
|
||||
section='featured')
|
||||
if created:
|
||||
qs = Position.objects.filter(user=user, section='featured')
|
||||
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
|
||||
pos.save()
|
||||
Position.objects.filter(collection=self).exclude(id=pos.id).delete()
|
||||
else:
|
||||
value = self.status
|
||||
elif self.status == 'featured' and value == 'public':
|
||||
Position.objects.filter(collection=self).delete()
|
||||
pos, created = Position.objects.get_or_create(collection=self,
|
||||
user=self.user, section='personal')
|
||||
qs = Position.objects.filter(user=self.user, section='personal')
|
||||
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
|
||||
pos.save()
|
||||
for u in self.subscribed_users.all():
|
||||
pos, created = Position.objects.get_or_create(collection=self, user=u,
|
||||
section='public')
|
||||
qs = Position.objects.filter(user=u, section='public')
|
||||
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
|
||||
pos.save()
|
||||
|
||||
self.status = value
|
||||
elif key == 'name':
|
||||
data['name'] = re.sub(' \[\d+\]$', '', data['name']).strip()
|
||||
if not data['name']:
|
||||
data['name'] = "Untitled"
|
||||
name = data['name']
|
||||
num = 1
|
||||
while Collection.objects.filter(name=name, user=self.user).exclude(id=self.id).count() > 0:
|
||||
num += 1
|
||||
name = data['name'] + ' [%d]' % num
|
||||
self.name = name
|
||||
elif key == 'description':
|
||||
self.description = ox.sanitize_html(data['description'])
|
||||
|
||||
if 'position' in data:
|
||||
pos, created = Position.objects.get_or_create(collection=self, user=user)
|
||||
pos.position = data['position']
|
||||
pos.section = 'featured'
|
||||
if self.status == 'private':
|
||||
pos.section = 'personal'
|
||||
pos.save()
|
||||
if 'posterFrames' in data:
|
||||
self.poster_frames = tuple(data['posterFrames'])
|
||||
if 'view' in data:
|
||||
self.view = data['view']
|
||||
if 'sort' in data:
|
||||
self.sort = tuple(data['sort'])
|
||||
self.save()
|
||||
if 'posterFrames' in data:
|
||||
self.update_icon()
|
||||
|
||||
def json(self, keys=None, user=None):
|
||||
if not keys:
|
||||
keys = ['id', 'name', 'user', 'type', 'query', 'status', 'subscribed', 'posterFrames', 'description', 'view']
|
||||
response = {}
|
||||
for key in keys:
|
||||
if key in ('items', 'documents'):
|
||||
response[key] = self.get_numberofdocuments(user)
|
||||
elif key == 'id':
|
||||
response[key] = self.get_id()
|
||||
elif key == 'user':
|
||||
response[key] = self.user.username
|
||||
elif key == 'query':
|
||||
if not self.query.get('static', False):
|
||||
response[key] = self.query
|
||||
elif key == 'subscribers':
|
||||
response[key] = self.subscribed_users.all().count()
|
||||
elif key == 'subscribed':
|
||||
if user and not user.is_anonymous():
|
||||
response[key] = self.subscribed_users.filter(id=user.id).exists()
|
||||
else:
|
||||
response[key] = getattr(self, {
|
||||
'posterFrames': 'poster_frames'
|
||||
}.get(key, key))
|
||||
return response
|
||||
|
||||
def path(self, name=''):
|
||||
h = "%07d" % self.id
|
||||
return os.path.join('collections', h[:2], h[2:4], h[4:6], h[6:], name)
|
||||
|
||||
def update_icon(self):
|
||||
frames = []
|
||||
#fixme
|
||||
'''
|
||||
if not self.poster_frames:
|
||||
documents = self.get_documents(self.user)
|
||||
if documents.count():
|
||||
poster_frames = []
|
||||
for i in range(0, documents.count(), max(1, int(documents.count()/4))):
|
||||
poster_frames.append({
|
||||
'document': documents[int(i)].id,
|
||||
'position': documents[int(i)].poster_frame
|
||||
})
|
||||
self.poster_frames = tuple(poster_frames)
|
||||
self.save()
|
||||
for i in self.poster_frames:
|
||||
from document.models import Document
|
||||
qs = Document.objects.filter(id=i['document'])
|
||||
if qs.count() > 0:
|
||||
if i.get('position'):
|
||||
frame = qs[0].frame(i['position'])
|
||||
if frame:
|
||||
frames.append(frame)
|
||||
'''
|
||||
self.icon.name = self.path('icon.jpg')
|
||||
icon = self.icon.path
|
||||
if frames:
|
||||
while len(frames) < 4:
|
||||
frames += frames
|
||||
folder = os.path.dirname(icon)
|
||||
ox.makedirs(folder)
|
||||
for f in glob("%s/icon*.jpg" % folder):
|
||||
os.unlink(f)
|
||||
cmd = [
|
||||
settings.collection_ICON,
|
||||
'-f', ','.join(frames),
|
||||
'-o', icon
|
||||
]
|
||||
p = subprocess.Popen(cmd, close_fds=True)
|
||||
p.wait()
|
||||
self.save()
|
||||
|
||||
def get_icon(self, size=16):
|
||||
path = self.path('icon%d.jpg' % size)
|
||||
path = os.path.join(settings.MEDIA_ROOT, path)
|
||||
if not os.path.exists(path):
|
||||
folder = os.path.dirname(path)
|
||||
ox.makedirs(folder)
|
||||
if self.icon and os.path.exists(self.icon.path):
|
||||
source = self.icon.path
|
||||
max_size = min(self.icon.width, self.icon.height)
|
||||
else:
|
||||
source = os.path.join(settings.STATIC_ROOT, 'jpg/list256.jpg')
|
||||
max_size = 256
|
||||
if size < max_size:
|
||||
extract.resize_image(source, path, size=size)
|
||||
else:
|
||||
path = source
|
||||
return path
|
||||
|
||||
class CollectionDocument(models.Model):
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
modified = models.DateTimeField(auto_now=True)
|
||||
collection = models.ForeignKey(Collection)
|
||||
index = models.IntegerField(default=0)
|
||||
document = models.ForeignKey('document.Document')
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s in %s' % (self.document, self.collection)
|
||||
|
||||
class Position(models.Model):
|
||||
|
||||
class Meta:
|
||||
unique_together = ("user", "collection", "section")
|
||||
|
||||
collection = models.ForeignKey(Collection, related_name='position')
|
||||
user = models.ForeignKey(User, related_name='collection_positions')
|
||||
section = models.CharField(max_length=255)
|
||||
position = models.IntegerField(default=0)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s/%s/%s' % (self.section, self.position, self.collection)
|
||||
|
||||
448
pandora/documentcollection/views.py
Normal file
448
pandora/documentcollection/views.py
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
from __future__ import division, print_function, absolute_import
|
||||
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
|
||||
from django.db.models import Max, Sum
|
||||
from django.db import transaction
|
||||
from django.conf import settings
|
||||
from oxdjango.decorators import login_required_json
|
||||
from oxdjango.shortcuts import render_to_json_response, get_object_or_404_json, json_response
|
||||
from oxdjango.http import HttpFileResponse
|
||||
import ox
|
||||
|
||||
from . import models
|
||||
from oxdjango.api import actions
|
||||
from item import utils
|
||||
from document.models import Document
|
||||
from user.tasks import update_numberofcollections
|
||||
from changelog.models import add_changelog
|
||||
|
||||
def get_collection_or_404_json(id):
|
||||
id = id.split(':')
|
||||
username = id[0]
|
||||
collectionname = ":".join(id[1:])
|
||||
return get_object_or_404_json(models.Collection, user__username=username, name=collectionname)
|
||||
|
||||
def _order_query(qs, sort):
|
||||
order_by = []
|
||||
for e in sort:
|
||||
operator = e['operator']
|
||||
if operator != '-':
|
||||
operator = ''
|
||||
key = {
|
||||
'subscribed': 'subscribed_users',
|
||||
'items': 'numberofitems'
|
||||
}.get(e['key'], e['key'])
|
||||
order = '%s%s' % (operator, key)
|
||||
order_by.append(order)
|
||||
if key == 'subscribers':
|
||||
qs = qs.annotate(subscribers=Sum('subscribed_users'))
|
||||
if order_by:
|
||||
qs = qs.order_by(*order_by, nulls_last=True)
|
||||
qs = qs.distinct()
|
||||
return qs
|
||||
|
||||
def parse_query(data, user):
|
||||
query = {}
|
||||
query['range'] = [0, 100]
|
||||
query['sort'] = [{'key':'user', 'operator':'+'}, {'key':'name', 'operator':'+'}]
|
||||
for key in ('keys', 'group', 'collection', 'range', 'position', 'positions', 'sort'):
|
||||
if key in data:
|
||||
query[key] = data[key]
|
||||
query['qs'] = models.Collection.objects.find(data, user)
|
||||
return query
|
||||
|
||||
|
||||
def findCollections(request, data):
|
||||
'''
|
||||
Finds collections for a given query
|
||||
takes {
|
||||
query: object, // query object, see `find`
|
||||
sort: [], // collection of sort objects, see `find`
|
||||
range: [int, int], // range of results to return
|
||||
keys: [string] // collection of properties to return
|
||||
}
|
||||
returns {
|
||||
items: [object] // collection of collection objects
|
||||
}
|
||||
notes: Possible query keys are 'featured', 'name', 'subscribed' and 'user',
|
||||
possible keys are 'featured', 'name', 'query', 'subscribed' and 'user'.
|
||||
see: addCollection, editCollection, find, getCollection, removeCollection, sortCollections
|
||||
'''
|
||||
query = parse_query(data, request.user)
|
||||
|
||||
#order
|
||||
is_section_request = query['sort'] == [{u'operator': u'+', u'key': u'position'}]
|
||||
|
||||
def is_featured_condition(x):
|
||||
return x['key'] == 'status' and \
|
||||
x['value'] == 'featured' and \
|
||||
x['operator'] in ('=', '==')
|
||||
|
||||
is_featured = any(
|
||||
is_featured_condition(x)
|
||||
for x in data.get('query', {}).get('conditions', [])
|
||||
)
|
||||
|
||||
if is_section_request:
|
||||
qs = query['qs']
|
||||
if not is_featured and not request.user.is_anonymous():
|
||||
qs = qs.filter(position__in=models.Position.objects.filter(user=request.user))
|
||||
qs = qs.order_by('position__position')
|
||||
else:
|
||||
qs = _order_query(query['qs'], query['sort'])
|
||||
|
||||
response = json_response()
|
||||
if 'keys' in data:
|
||||
qs = qs[query['range'][0]:query['range'][1]]
|
||||
|
||||
response['data']['items'] = [l.json(data['keys'], request.user) for l in qs]
|
||||
elif 'position' in data:
|
||||
#FIXME: actually implement position requests
|
||||
response['data']['position'] = 0
|
||||
elif 'positions' in data:
|
||||
ids = [i.get_id() for i in qs]
|
||||
response['data']['positions'] = utils.get_positions(ids, query['positions'])
|
||||
else:
|
||||
response['data']['items'] = qs.count()
|
||||
return render_to_json_response(response)
|
||||
actions.register(findCollections)
|
||||
|
||||
def getCollection(request, data):
|
||||
'''
|
||||
Gets a collection by id
|
||||
takes {
|
||||
id: string // collection id
|
||||
}
|
||||
returns {
|
||||
id: string, // collection id
|
||||
section: string, // collections section (like 'personal')
|
||||
... // more key/value pairs
|
||||
}
|
||||
see: addCollection, editCollection, findCollections, removeCollection, sortCollections
|
||||
'''
|
||||
if 'id' in data:
|
||||
response = json_response()
|
||||
collection = get_collection_or_404_json(data['id'])
|
||||
if collection.accessible(request.user):
|
||||
response['data'] = collection.json(user=request.user)
|
||||
else:
|
||||
response = json_response(status=403, text='not allowed')
|
||||
else:
|
||||
response = json_response(status=404, text='not found')
|
||||
return render_to_json_response(response)
|
||||
actions.register(getCollection)
|
||||
|
||||
@login_required_json
|
||||
def addCollectionItems(request, data):
|
||||
'''
|
||||
Adds one or more items to a static collection
|
||||
takes {
|
||||
collection: string, // collection id
|
||||
items: [string], // either collection of item ids
|
||||
query: object // or query object, see `find`
|
||||
}
|
||||
returns {}
|
||||
see: find, orderCollectionItems, removeCollectionItems
|
||||
'''
|
||||
collection = get_collection_or_404_json(data['collection'])
|
||||
if 'items' in data:
|
||||
if collection.editable(request.user):
|
||||
with transaction.atomic():
|
||||
items = [ox.fromAZ(id) for id in data['items']]
|
||||
for item in Document.objects.filter(id__in=items):
|
||||
collection.add(item)
|
||||
response = json_response(status=200, text='items added')
|
||||
add_changelog(request, data, data['collection'])
|
||||
else:
|
||||
response = json_response(status=403, text='not allowed')
|
||||
elif 'query' in data:
|
||||
response = json_response(status=501, text='not implemented')
|
||||
else:
|
||||
response = json_response(status=501, text='not implemented')
|
||||
return render_to_json_response(response)
|
||||
actions.register(addCollectionItems, cache=False)
|
||||
|
||||
|
||||
@login_required_json
|
||||
def removeCollectionItems(request, data):
|
||||
'''
|
||||
Removes one or more items from a static collection
|
||||
takes {
|
||||
collection: string, // collection id
|
||||
items: [itemId], // either collection of item ids
|
||||
query: object // or query object, see `find`
|
||||
}
|
||||
returns {}
|
||||
see: addCollectionItems, find, orderCollectionItems
|
||||
'''
|
||||
collection = get_collection_or_404_json(data['collection'])
|
||||
if 'items' in data:
|
||||
if collection.editable(request.user):
|
||||
items = [ox.fromAZ(id) for id in data['items']]
|
||||
collection.remove(documents=items)
|
||||
response = json_response(status=200, text='items removed')
|
||||
add_changelog(request, data, data['collection'])
|
||||
else:
|
||||
response = json_response(status=403, text='not allowed')
|
||||
elif 'query' in data:
|
||||
response = json_response(status=501, text='not implemented')
|
||||
|
||||
else:
|
||||
response = json_response(status=501, text='not implemented')
|
||||
return render_to_json_response(response)
|
||||
actions.register(removeCollectionItems, cache=False)
|
||||
|
||||
@login_required_json
|
||||
def orderCollectionItems(request, data):
|
||||
'''
|
||||
Sets the manual ordering of items in a given collection
|
||||
takes {
|
||||
collection: string, // collection id
|
||||
ids: [string] // ordered collection of item ids
|
||||
}
|
||||
returns {}
|
||||
notes: There is no UI for this yet.
|
||||
see: addCollectionItems, removeCollectionItems
|
||||
'''
|
||||
collection = get_collection_or_404_json(data['collection'])
|
||||
response = json_response()
|
||||
if collection.editable(request.user) and collection.type == 'static':
|
||||
index = 0
|
||||
with transaction.atomic():
|
||||
for i in data['ids']:
|
||||
models.CollectionItem.objects.filter(collection=collection, item__public_id=i).update(index=index)
|
||||
index += 1
|
||||
else:
|
||||
response = json_response(status=403, text='permission denied')
|
||||
return render_to_json_response(response)
|
||||
actions.register(orderCollectionItems, cache=False)
|
||||
|
||||
|
||||
@login_required_json
|
||||
def addCollection(request, data):
|
||||
'''
|
||||
Adds a new collection
|
||||
takes {
|
||||
name: value, // collection name (optional)
|
||||
... // more key/value pairs
|
||||
}
|
||||
returns {
|
||||
id: string, // collection id
|
||||
name: string, // collection name
|
||||
... // more key/value pairs
|
||||
}
|
||||
notes: Possible keys are 'description', 'items', 'name', 'query', 'sort',
|
||||
'type' and 'view'.
|
||||
see: editCollection, findCollections, getCollection, removeCollection, sortCollections
|
||||
'''
|
||||
data['name'] = re.sub(' \[\d+\]$', '', data.get('name', 'Untitled')).strip()
|
||||
name = data['name']
|
||||
if not name:
|
||||
name = "Untitled"
|
||||
num = 1
|
||||
created = False
|
||||
while not created:
|
||||
collection, created = models.Collection.objects.get_or_create(name=name, user=request.user)
|
||||
num += 1
|
||||
name = data['name'] + ' [%d]' % num
|
||||
|
||||
del data['name']
|
||||
if data:
|
||||
collection.edit(data, request.user)
|
||||
else:
|
||||
collection.save()
|
||||
update_numberofcollections.delay(request.user.username)
|
||||
|
||||
if 'items' in data:
|
||||
items = [ox.fromAZ(id) for id in data['items']]
|
||||
for item in Document.objects.filter(id__in=items):
|
||||
collection.add(item)
|
||||
|
||||
if collection.status == 'featured':
|
||||
pos, created = models.Position.objects.get_or_create(collection=collection,
|
||||
user=request.user, section='featured')
|
||||
qs = models.Position.objects.filter(section='featured')
|
||||
else:
|
||||
pos, created = models.Position.objects.get_or_create(collection=collection,
|
||||
user=request.user, section='personal')
|
||||
qs = models.Position.objects.filter(user=request.user, section='personal')
|
||||
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
|
||||
pos.save()
|
||||
response = json_response(status=200, text='created')
|
||||
response['data'] = collection.json()
|
||||
add_changelog(request, data, collection.get_id())
|
||||
return render_to_json_response(response)
|
||||
actions.register(addCollection, cache=False)
|
||||
|
||||
|
||||
@login_required_json
|
||||
def editCollection(request, data):
|
||||
'''
|
||||
Edits a collection
|
||||
takes {
|
||||
id: string, // collection id
|
||||
key: value, // property id and new value
|
||||
... // more key/value pairs
|
||||
}
|
||||
returns {
|
||||
id: string, // collection id
|
||||
... // more key/value pairs
|
||||
}
|
||||
notes: Possible keys are 'name', 'position', 'posterFrames', 'query' and
|
||||
'status'. 'posterFrames' is an array of {item, position}. If you change
|
||||
'status', you have to pass 'position' (the position of the collection in its new
|
||||
collection folder).
|
||||
see: addCollection, findCollections, getCollection, removeCollection, sortCollections
|
||||
'''
|
||||
collection = get_collection_or_404_json(data['id'])
|
||||
if collection.editable(request.user):
|
||||
response = json_response()
|
||||
collection.edit(data, request.user)
|
||||
response['data'] = collection.json(user=request.user)
|
||||
add_changelog(request, data)
|
||||
else:
|
||||
response = json_response(status=403, text='not allowed')
|
||||
return render_to_json_response(response)
|
||||
actions.register(editCollection, cache=False)
|
||||
|
||||
@login_required_json
|
||||
def removeCollection(request, data):
|
||||
'''
|
||||
Removes a collection
|
||||
takes {
|
||||
id: string // collection id
|
||||
}
|
||||
returns {}
|
||||
see: addCollection, editCollection, findCollections, getCollection, sortCollections
|
||||
'''
|
||||
collection = get_collection_or_404_json(data['id'])
|
||||
response = json_response()
|
||||
if collection.editable(request.user):
|
||||
add_changelog(request, data)
|
||||
collection.delete()
|
||||
update_numberofcollections.delay(request.user.username)
|
||||
else:
|
||||
response = json_response(status=403, text='not allowed')
|
||||
return render_to_json_response(response)
|
||||
actions.register(removeCollection, cache=False)
|
||||
|
||||
|
||||
@login_required_json
|
||||
def subscribeToCollection(request, data):
|
||||
'''
|
||||
Adds a collection to favorites
|
||||
takes {
|
||||
id: string, // collection id
|
||||
user: string // username (admin-only)
|
||||
}
|
||||
returns {}
|
||||
see: unsubscribeFromCollection
|
||||
'''
|
||||
collection = get_collection_or_404_json(data['id'])
|
||||
user = request.user
|
||||
if collection.status == 'public' and \
|
||||
collection.subscribed_users.filter(username=user.username).count() == 0:
|
||||
collection.subscribed_users.add(user)
|
||||
pos, created = models.Position.objects.get_or_create(collection=collection, user=user, section='public')
|
||||
if created:
|
||||
qs = models.Position.objects.filter(user=user, section='public')
|
||||
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
|
||||
pos.save()
|
||||
add_changelog(request, data)
|
||||
response = json_response()
|
||||
return render_to_json_response(response)
|
||||
actions.register(subscribeToCollection, cache=False)
|
||||
|
||||
|
||||
@login_required_json
|
||||
def unsubscribeFromCollection(request, data):
|
||||
'''
|
||||
Removes a collection from favorites
|
||||
takes {
|
||||
id: string, // collection id
|
||||
user: string // username (admin-only)
|
||||
}
|
||||
returns {}
|
||||
see: subscribeToCollection
|
||||
'''
|
||||
collection = get_collection_or_404_json(data['id'])
|
||||
user = request.user
|
||||
collection.subscribed_users.remove(user)
|
||||
models.Position.objects.filter(collection=collection, user=user, section='public').delete()
|
||||
response = json_response()
|
||||
add_changelog(request, data)
|
||||
return render_to_json_response(response)
|
||||
actions.register(unsubscribeFromCollection, cache=False)
|
||||
|
||||
|
||||
@login_required_json
|
||||
def sortCollections(request, data):
|
||||
'''
|
||||
Sets the order of collections in a given section
|
||||
takes {
|
||||
section: string, // collections section
|
||||
ids: [string] // ordered collection of collections
|
||||
}
|
||||
returns {}
|
||||
notes: Possible sections are 'personal', 'favorite' and 'featured'. Setting
|
||||
the order of featured collections requires the appropriate capability.
|
||||
see: addCollection, editCollection, findCollections, getCollection, removeCollection
|
||||
'''
|
||||
position = 0
|
||||
section = data['section']
|
||||
section = {
|
||||
'favorite': 'public'
|
||||
}.get(section, section)
|
||||
#ids = collection(set(data['ids']))
|
||||
ids = data['ids']
|
||||
if section == 'featured' and not request.user.profile.capability('canEditFeaturedCollections'):
|
||||
response = json_response(status=403, text='not allowed')
|
||||
else:
|
||||
user = request.user
|
||||
if section == 'featured':
|
||||
for i in ids:
|
||||
l = get_collection_or_404_json(i)
|
||||
qs = models.Position.objects.filter(section=section, collection=l)
|
||||
if qs.count() > 0:
|
||||
pos = qs[0]
|
||||
else:
|
||||
pos = models.Position(collection=l, user=user, section=section)
|
||||
if pos.position != position:
|
||||
pos.position = position
|
||||
pos.save()
|
||||
position += 1
|
||||
models.Position.objects.filter(section=section, collection=l).exclude(id=pos.id).delete()
|
||||
else:
|
||||
for i in ids:
|
||||
l = get_collection_or_404_json(i)
|
||||
pos, created = models.Position.objects.get_or_create(collection=l,
|
||||
user=request.user, section=section)
|
||||
if pos.position != position:
|
||||
pos.position = position
|
||||
pos.save()
|
||||
position += 1
|
||||
|
||||
response = json_response()
|
||||
return render_to_json_response(response)
|
||||
actions.register(sortCollections, cache=False)
|
||||
|
||||
|
||||
def icon(request, id, size=16):
|
||||
if not size:
|
||||
size = 16
|
||||
|
||||
id = id.split(':')
|
||||
username = id[0]
|
||||
collectionname = ":".join(id[1:])
|
||||
qs = models.Collection.objects.filter(user__username=username, name=collectionname)
|
||||
if qs.count() == 1 and qs[0].accessible(request.user):
|
||||
collection = qs[0]
|
||||
icon = collection.get_icon(int(size))
|
||||
else:
|
||||
icon = os.path.join(settings.STATIC_ROOT, 'jpg/list256.jpg')
|
||||
return HttpFileResponse(icon, content_type='image/jpeg')
|
||||
Loading…
Add table
Add a link
Reference in a new issue