Skip to content

Commit

Permalink
Merge pull request #30 from diging/develop
Browse files Browse the repository at this point in the history
Pre-release merge for version 0.2.2
  • Loading branch information
erickpeirson authored Oct 4, 2016
2 parents 11dfee9 + 43e9332 commit 0d591f2
Show file tree
Hide file tree
Showing 34 changed files with 1,025 additions and 949 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ python:
- "2.7"
env:
global:
- DJANGO_SETTINGS_MODULE=jars.test_settings
- DJANGO_SETTINGS_MODULE=jars.settings
before_script:
- createuser -d -U postgres jars_tests;
- psql -c 'create database jars_tests;' -U postgres
Expand Down
98 changes: 98 additions & 0 deletions cookies/authorization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from functools import wraps

from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.http import HttpResponseForbidden
from django.utils.decorators import available_attrs

from guardian.shortcuts import get_perms, remove_perm, assign_perm

from collections import defaultdict


AUTHORIZATIONS = [
('change_resource', 'Change resource'),
('view_resource', 'View resource'),
('delete_resource', 'Delete resource'),
('change_authorizations', 'Change authorizations'),
('view_authorizations', 'View authorizations'),
]

COLLECTION_AUTHORIZATIONS = [
('change_collection', 'Change collection'),
('view_resource', 'View collection'),
('delete_collection', 'Delete collection'),
('change_authorizations', 'Change authorizations'),
('view_authorizations', 'View authorizations'),
]


is_owner = lambda user, obj: getattr(obj, 'created_by', None) == user


def check_authorization(auth, user, obj):
"""
Check whether ``user`` is authorized to perform ``auth`` on ``obj``.
"""
if auth == 'is_owner':
return getattr(obj, 'created_by', None) == user
if auth == 'view_resource' and getattr(obj, 'public', False):
return True
return user.is_superuser or is_owner(user, obj) or user.has_perm(auth, obj)


# TODO: build this out.
def get_auth_filter(auth, user):
"""
"""
return ~Q(created_by=user)


def update_authorizations(auths, user, obj):
for auth in set(get_perms(user, obj)) - set(auths):
remove_perm(auth, user, obj)
for auth in set(auths) - set(get_perms(user, obj)):
assign_perm(auth, user, obj)


def list_authorizations(obj, user=None):
"""
List authorizations for ``obj``.
"""
if user is None: # All authorizations for all users.
_auths = defaultdict(list)
_users = {obj.created_by.id: obj.created_by}
_auths[obj.created_by.id].append('owns')


for user in User.objects.all():
_auths[user.id] += get_perms(user, obj)
_users[user.id] = user

return [(_users[user], auths) for user, auths in _auths.items() if auths]

# Authorizations for a specific user.
return get_perms(user, obj)


def authorization_required(perm, fn=None, login_url=None, raise_exception=False):
"""
Decorator for views. Checks ``perm`` on an object ``fn`` for the requesting
:class:`.User`\.
"""
def decorator(view_func):

@wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view(request, *args, **kwargs):
obj = fn(request, *args, **kwargs) if callable(fn) else fn
if not check_authorization(perm, request.user, obj):
if raise_exception:
msg = '%s on %s not authorized for %s' % \
(perm, obj.__unicode__(), request.user.username)
raise PermissionDenied(msg)
# TODO: make this pretty and informative.
return HttpResponseForbidden('Nope.')
return view_func(request, *args, **kwargs)
return _wrapped_view
return decorator
53 changes: 28 additions & 25 deletions cookies/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,20 +113,20 @@ def _get_target(v):
return Value.objects.create(_value=value)


def _process_people(field, data, entity_type):
def _process_people(field, data, entity_type, creator):
entities = []
for surname, forename in data:
if surname.startswith('http'):
entity = _find_entity(field, surname)
if not entity:
entity = ConceptEntity.objects.create(name=surname, uri=surname, entity_type=entity_type)
entity = ConceptEntity.objects.create(name=surname, uri=surname, entity_type=entity_type, created_by=creator)
else:
entity = ConceptEntity.objects.create(name=u', '.join([surname, forename]), entity_type=entity_type)
entity = ConceptEntity.objects.create(name=u', '.join([surname, forename]), entity_type=entity_type, created_by=creator)
entities.append(entity)
return entities


def _process_ispartof(field, data):
def _process_ispartof(field, data, creator):
IDENTIFIER = Field.objects.get(uri='http://purl.org/dc/elements/1.1/identifier')
TITLE = Field.objects.get(uri='http://purl.org/dc/elements/1.1/title')
TYPE = Field.objects.get(uri='http://www.w3.org/1999/02/22-rdf-syntax-ns#type')
Expand Down Expand Up @@ -155,14 +155,15 @@ def _process_ispartof(field, data):
entity = ConceptEntity.objects.create(
name=name if name else uu,
uri=uri if uri else uu,
entity_type=entity_type
entity_type=entity_type,
created_by=creator,
)

for field, value in field_data:
target = _find_entity(field, value)
if target is None:
target = Value.objects.create(_value=jsonpickle.encode(value))
Relation.objects.create(source=entity, predicate=field, target=target)
Relation.objects.create(source=entity, predicate=field, target=target, created_by=creator)

return entity

Expand Down Expand Up @@ -207,7 +208,7 @@ def _cast_value(value):
return value


def _process_metadata(metadata, resource):
def _process_metadata(metadata, resource, creator):
"""
Translate key/value data in ``resource`` into JARS model.
"""
Expand All @@ -224,7 +225,7 @@ def _process_metadata(metadata, resource):
entity_type = None


def _process_keypair(key, value):
def _process_keypair(key, value, creator):
value = _cast_value(value)

# If we are on an inner recursion step, ``key`` will already be
Expand All @@ -250,15 +251,15 @@ def _process_keypair(key, value):

# This is kind of hacky, but we need a prefix.
prefix = ''.join([c for c in schema_uri.replace('http://', '').replace('www.', '').split('.')[0] if c not in 'aeiouy'])
schema, _ = Schema.objects.get_or_create(uri=schema_uri, defaults={'prefix': prefix, 'name': schema_uri})
key = Field.objects.create(name=key_name.title(), uri=key, schema=schema, namespace=schema_uri)
schema, _ = Schema.objects.get_or_create(uri=schema_uri, defaults={'prefix': prefix, 'name': schema_uri, 'created_by': creator})
key = Field.objects.create(name=key_name.title(), uri=key, schema=schema, namespace=schema_uri, defaults={'created_by': creator})


if key in [CREATOR, AUTHOR]:
for creator in _process_people(key, value, PERSON):
for creator in _process_people(key, value, PERSON, creator):
metadata.append((CREATOR, creator))
elif key == ISPARTOF:
value = _process_ispartof(key, value)
value = _process_ispartof(key, value, creator)
metadata.append((key, value))
elif key in [TYPE, ZTYPE]:
entity_type = _find_type(value)
Expand All @@ -275,36 +276,38 @@ def _process_keypair(key, value):
value = found
else:
if value.startswith('http'):
value = Resource.objects.create(name=value, uri=value)
value = Resource.objects.create(name=value, uri=value, created_by=creator)
else:
value = Value.objects.create(_value=jsonpickle.encode(value))
metadata.append((key, value))
elif type(value) is list:
for elem in value: # Recurse.
_process_keypair(key, elem)
_process_keypair(key, elem, creator)
else:
value = Value.objects.create(_value=jsonpickle.encode(value))
metadata.append((key, value))

for key, value in resource:
_process_keypair(key, value)
_process_keypair(key, value, creator)

return metadata


def _create_content_resource(localresource, form_data, content_resource_data,
loc, fpath, fname):
creator = form_data.get('created_by')
contentResource = Resource.objects.create(
name=fname,
content_resource=True,
processed=True,
public=form_data.get('public'),
created_by=form_data.get('created_by'),
created_by=creator,
)

cr_data = {
'for_resource': localresource,
'content_resource': contentResource,
'created_by': creator,
}
if loc == 'local':
with open(fpath, 'r') as f:
Expand All @@ -326,9 +329,9 @@ def _create_content_resource(localresource, form_data, content_resource_data,
'predicate': field,
'target': target,
'public': form_data.get('public'),
'created_by': form_data.get('created_by'),
'created_by': creator,
})
add_creation_metadata(contentResource, form_data.get('created_by'))
add_creation_metadata(contentResource, creator)
contentResource.save()
return contentResource

Expand Down Expand Up @@ -362,17 +365,17 @@ def _get_or_create_collection(form_data, public, creator):
return collection


def _get_content_resources(resource):
def _get_content_resources(resource, creator):
file_data = getattr(resource, 'file', [])
content_resources = []
if len(file_data) > 0:
if type(file_data[0]) is list:
for fdata in file_data:
content_metadata = []
content_resources.append(_process_metadata(content_metadata, fdata))
content_resources.append(_process_metadata(content_metadata, fdata, creator))
else:
content_metadata = []
content_resources.append(_process_metadata(content_metadata, file_data))
content_resources.append(_process_metadata(content_metadata, file_data, creator))
return content_resources


Expand Down Expand Up @@ -400,7 +403,7 @@ def handle_bulk(file_path, form_data, file_name):

# We want to be able to recall that this bulk upload was a/the source for
# this collection.
Relation.objects.create(source=collection, predicate=SOURCE, target=upload)
Relation.objects.create(source=collection, predicate=SOURCE, target=upload, created_by=creator)

# Each file will result in a new Resource.
for resource in resources:
Expand All @@ -415,11 +418,11 @@ def handle_bulk(file_path, form_data, file_name):

# These are the metadata that will be used to create Relations later on,
# as opposed to the field values on the Resource model itself.
resource_metadata = _process_metadata([], resource.__dict__.items())
resource_metadata = _process_metadata([], resource.__dict__.items(), creator)

# These may be remote (i.e. just URLs), local (i.e. with a file), or
# both.
content_resources = _get_content_resources(resource)
content_resources = _get_content_resources(resource, creator)

# User can indicate a default Type to assign to each new Resource.
default_type = form_data.get('default_type', None)
Expand All @@ -443,7 +446,7 @@ def handle_bulk(file_path, form_data, file_name):
# Here we create the new Resource instance from the current Zotero
# record.
localresource = Resource.objects.create(**resource_data)
add_creation_metadata(localresource, form_data.get('created_by'))
add_creation_metadata(localresource, creator)

# Handle content.
for content_resource_data in content_resources:
Expand Down
14 changes: 12 additions & 2 deletions cookies/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
from dal_select2_queryset_sequence.widgets import QuerySetSequenceSelect2
from queryset_sequence import QuerySetSequence
import inspect, jsonpickle
from .models import *
from . import models as md
from cookies.models import *
from cookies import authorization


class ContenteditableInput(forms.TextInput):
Expand Down Expand Up @@ -259,3 +259,13 @@ class MetadatumForm(forms.Form):
('Resource', 'Resource'),
('Type', 'Type'),
))


class AuthorizationForm(forms.Form):
for_user = forms.ModelChoiceField(queryset=User.objects.all().order_by('-username'))
authorizations = forms.MultipleChoiceField(choices=authorization.AUTHORIZATIONS)


class CollectionAuthorizationForm(forms.Form):
for_user = forms.ModelChoiceField(queryset=User.objects.all().order_by('-username'))
authorizations = forms.MultipleChoiceField(choices=authorization.COLLECTION_AUTHORIZATIONS)
23 changes: 23 additions & 0 deletions cookies/migrations/0017_auto_20161004_1605.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-10-04 16:05
from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('cookies', '0016_value__type'),
]

operations = [
migrations.AlterModelOptions(
name='collection',
options={'permissions': (('view_resource', 'View resource'), ('change_authorizations', 'Change authorizations'), ('view_authorizations', 'View authorizations'))},
),
migrations.AlterModelOptions(
name='resource',
options={'permissions': (('view_resource', 'View resource'), ('change_authorizations', 'Change authorizations'), ('view_authorizations', 'View authorizations'))},
),
]
48 changes: 48 additions & 0 deletions cookies/migrations/0018_auto_20161004_1745.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-10-04 17:45
from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('cookies', '0017_auto_20161004_1605'),
]

operations = [
migrations.RemoveField(
model_name='authorization',
name='actor',
),
migrations.RemoveField(
model_name='authorization',
name='on_type',
),
migrations.RemoveField(
model_name='authorization',
name='to_do',
),
migrations.RemoveField(
model_name='event',
name='by',
),
migrations.RemoveField(
model_name='event',
name='did',
),
migrations.RemoveField(
model_name='event',
name='on_type',
),
migrations.DeleteModel(
name='Action',
),
migrations.DeleteModel(
name='Authorization',
),
migrations.DeleteModel(
name='Event',
),
]
Loading

0 comments on commit 0d591f2

Please sign in to comment.