Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jkmarx/add group member api #3309

Merged
merged 33 commits into from
Apr 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
6553d89
Extend group api to update group perms and retrieve all group info.
jkmarx Mar 26, 2019
e8d769e
Add unit tests.
jkmarx Mar 26, 2019
397603e
Refactor and add utils method.
jkmarx Mar 26, 2019
621bd48
Fix method name and comments for clarity.
jkmarx Mar 26, 2019
3fb5ac8
Refactor other view sets to use helper. (#3286)
jkmarx Mar 28, 2019
65722cf
Resolve merge conflict.
jkmarx Mar 28, 2019
964bd37
Refactor email address in tests.
jkmarx Mar 28, 2019
8e52d31
Update return statuses for clarity
jkmarx Mar 28, 2019
4c5aabd
Limit group returns by user membership.
jkmarx Mar 28, 2019
b987154
Remove unused groupManagementResource.
jkmarx Mar 28, 2019
694872c
Extend api to create groups.
jkmarx Mar 29, 2019
faa52d1
Resolve merge conflict.
jkmarx Mar 29, 2019
8dc2e47
Add unit tests.
jkmarx Mar 29, 2019
30e4eca
Merge branch 'develop' of https://github.com/refinery-platform/refine…
jkmarx Mar 29, 2019
88446c8
Extend api to return all member groups.
jkmarx Mar 29, 2019
e423606
Add unit tests.
jkmarx Mar 29, 2019
9deb3ed
Merge branch 'develop' of https://github.com/refinery-platform/refine…
jkmarx Mar 29, 2019
c40d450
Add unit tests.
jkmarx Mar 30, 2019
b5774a5
Extend group API to destroy.
jkmarx Apr 1, 2019
cc3ed54
Add unit tests.
jkmarx Apr 1, 2019
41d9a0a
Fix typo.
jkmarx Apr 1, 2019
0ec7315
Update client.
jkmarx Apr 1, 2019
9dde810
Extend api to return can_edit field.
jkmarx Apr 1, 2019
27f9405
Add group unit tests.
jkmarx Apr 1, 2019
b260db1
Avoid unneccessary api call.
jkmarx Apr 1, 2019
8c1d0e5
Adjust fields to accomodate current client.
jkmarx Apr 2, 2019
b9d12f2
Add unit tests.
jkmarx Apr 3, 2019
34c4f56
Resolve merge conflicts.
jkmarx Apr 3, 2019
1688a71
Move helper to model method.
jkmarx Apr 3, 2019
c07b901
Add unit tests.
jkmarx Apr 3, 2019
55f0969
Fix flake issue with line break.
jkmarx Apr 3, 2019
b03f733
Update client to group memebers api v2. (#3310)
jkmarx Apr 3, 2019
be9d803
Fix method name.
jkmarx Apr 3, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions refinery/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from tastypie.api import Api

from config.utils import RouterCombiner
from core.api import ExtendedGroupResource, InvitationResource
from core.api import InvitationResource
from core.forms import RegistrationFormWithCustomFields
from core.models import AuthenticationFormUsernameOrEmail
from core.urls import core_router
Expand All @@ -27,7 +27,6 @@
v1_api = Api(api_name='v1')

v1_api.register(InvitationResource())
v1_api.register(ExtendedGroupResource())


# patterns for all of the different applications
Expand Down
275 changes: 2 additions & 273 deletions refinery/core/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,20 @@
import uuid

from django.conf import settings
from django.conf.urls import url
from django.contrib.auth.models import Group
from django.contrib.sites.models import get_current_site
from django.core.mail import EmailMessage
from django.forms import ValidationError
from django.template import loader
from django.utils import timezone

from constants import UUID_RE
from tastypie import fields
from tastypie.authorization import Authorization
from tastypie.constants import ALL
from tastypie.exceptions import ImmediateHttpResponse
from tastypie.http import (HttpAccepted, HttpBadRequest, HttpCreated,
HttpForbidden, HttpMethodNotAllowed,
HttpNoContent, HttpNotFound, HttpUnauthorized)
from tastypie.http import (HttpCreated, HttpNotFound, HttpUnauthorized)
from tastypie.resources import ModelResource

from .models import ExtendedGroup, Invitation
from .models import Invitation


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -154,269 +149,3 @@ def obj_update(self, bundle, **kwargs):
inv.save()
self.send_email(bundle.request, inv)
return bundle


class ExtendedGroupResource(ModelResource):
member_list = fields.ListField(attribute='member_list', null=True)
perm_list = fields.ListField(attribute='perm_list', null=True)
can_edit = fields.BooleanField(attribute='can_edit', default=False)
manager_group_uuid = fields.CharField(attribute='manager_group_uuid',
null=True)

class Meta:
queryset = ExtendedGroup.objects.all()
resource_name = 'extended_groups'
object_class = ExtendedGroup
detail_uri_name = 'uuid'
# authentication = SessionAuthentication()
authorization = Authorization()

# More low-level group access
def _get_ext_group(self, uuid):
eg_list = ExtendedGroup.objects.filter(uuid=uuid)
return None if len(eg_list) == 0 else eg_list[0]

# Wrap get_ext_group and raise error if cannot be found.
def get_ext_group_or_fail(self, uuid):
ext_group = self._get_ext_group(uuid)

if ext_group:
return ext_group
else:
raise ImmediateHttpResponse(HttpNotFound())

def ext_groups_with_user(self, user, allow_manager_groups=False):
if allow_manager_groups:
return filter(
lambda g: user in g.user_set.all(),
ExtendedGroup.objects.all())
else:
return filter(
lambda g:
not g.is_manager_group() and user in g.user_set.all(),
ExtendedGroup.objects.all())

def user_authorized(self, user, ext_group):
if ext_group.is_manager_group():
return user in ext_group.user_set.all()
else:
return (ext_group.manager_group and
user in ext_group.manager_group.user_set.all())

def get_member_list(self, ext_group):
return map(
lambda u: {
'user_id': u.id,
'uuid': u.profile.uuid,
'username': u.username,
'first_name': u.first_name,
'last_name': u.last_name,
'is_manager': self.user_authorized(u, ext_group)
},
ext_group.user_set.all().filter(is_active=True).exclude(
username=settings.ANONYMOUS_USER_NAME
)
)

# Override ORM methods for customization.
def obj_get(self, bundle, **kwargs):
user = bundle.request.user
ext_group = super(ExtendedGroupResource, self).obj_get(
bundle, **kwargs)
ext_group.can_edit = self.user_authorized(user, ext_group)
ext_group.manager_group_uuid = (
ext_group.uuid
if ext_group.is_manager_group()
else ext_group.manager_group.uuid
)
return ext_group

def obj_get_list(self, bundle, **kwargs):
user = bundle.request.user
ext_group_list = super(ExtendedGroupResource, self).obj_get_list(
bundle, **kwargs)

for i in ext_group_list:
i.can_edit = self.user_authorized(user, i)
i.manager_group_uuid = (
i.uuid if i.is_manager_group() else i.manager_group.uuid
)
return ext_group_list

def get_object_list(self, bundle, **kwargs):
ext_group_list = super(ExtendedGroupResource, self).get_object_list(
bundle, **kwargs)
# Currently set so that manager groups are filtered out.
# This does not make sense semantically but somehow works.
return ext_group_list.filter(managed_group=None)

def obj_create(self, bundle, **kwargs):
user = bundle.request.user
data = json.loads(bundle.request.body)
new_ext_group = ExtendedGroup(name=data['name'])

new_ext_group.name = new_ext_group.name.strip()
try:
new_ext_group.full_clean()
except ValidationError as e:
raise ImmediateHttpResponse(HttpBadRequest(
'Invalid group creation request: %s.' % e
))
new_ext_group.save()
new_ext_group.user_set.add(user)
new_ext_group.manager_group.user_set.add(user)
return bundle

def obj_delete(self, bundle, **kwargs):
user = bundle.request.user
ext_group = super(ExtendedGroupResource, self).obj_get(
bundle, **kwargs)
if not self.user_authorized(user, ext_group):
raise ImmediateHttpResponse(HttpForbidden(
'Only managers may delete groups.'
))
ext_group.delete()
return HttpAccepted('Group deleted.')

# Extra things
def prepend_urls(self):
return [
url(r'^extended_groups/members/$',
self.wrap_view('ext_groups_members_list'),
name='api_ext_group_members_list'),
url(r'^extended_groups/(?P<uuid>' + UUID_RE + r')/members/$',
self.wrap_view('ext_groups_members_basic'),
name='api_ext_group_members_basic'),
url(r'^extended_groups/(?P<uuid>' + UUID_RE +
r')/members/(?P<user_id>[0-9]+)/$',
self.wrap_view('ext_groups_members_detail'),
name='api_ext_group_members_detail'),
]

def ext_groups_members_basic(self, request, **kwargs):
ext_group = self.get_ext_group_or_fail(kwargs['uuid'])
user = request.user

if request.method == 'GET':
ext_group.member_list = self.get_member_list(ext_group)
ext_group.can_edit = self.user_authorized(user, ext_group)
ext_group.manager_group_uuid = (
ext_group.uuid
if ext_group.is_manager_group()
else ext_group.manager_group.uuid
)
bundle = self.build_bundle(obj=ext_group, request=request)
return self.create_response(request, self.full_dehydrate(bundle))
elif request.method == 'PATCH':
if not self.user_authorized(user, ext_group):
return HttpUnauthorized()

data = json.loads(request.body)
new_member_list = data['member_list']

# Remove old members before updating.
ext_group.user_set.clear()

for m in new_member_list:
ext_group.user_set.add(int(m['user_id']))

# Managers should be in the groups they manage.
if ext_group.is_manager_group():
for g in ext_group.managed_group.all():
for m in new_member_list:
g.user_set.add(int(m['user_id']))

return HttpAccepted()
elif request.method == 'POST':
if not self.user_authorized(user, ext_group):
return HttpUnauthorized()

data = json.loads(request.body)
new_member = data['user_id']
ext_group.user_set.add(new_member)

if ext_group.is_manager_group():
for g in ext_group.managed_group.all():
g.user_set.add(new_member)

return HttpAccepted()
else:
return HttpMethodNotAllowed()

def ext_groups_members_detail(self, request, **kwargs):
ext_group = self.get_ext_group_or_fail(kwargs['uuid'])
user = request.user

if request.method == 'GET':
return self.ext_groups_members_basic(self, request, **kwargs)
elif request.method == 'DELETE':
# Removing yourself - must delete to leave if last member.
if user.id == int(kwargs['user_id']):

# Prevent users from leaving default public group
if ext_group.name == 'Public':
return HttpForbidden('Members may not leave Public group')

if ext_group.user_set.count() == 1:
return HttpForbidden('Last member - must delete group')

# When demoting yourself while targetting manager group.
if (ext_group.is_manager_group() and
ext_group.user_set.count() == 1):
return HttpForbidden(
'Last manager must delete group to leave')

if (not ext_group.is_manager_group() and
user in ext_group.manager_group.user_set.all() and
ext_group.manager_group.user_set.count() == 1):
return HttpForbidden(
'Last manager must delete group to leave')

ext_group.user_set.remove(user)

if not ext_group.is_manager_group():
ext_group.manager_group.user_set.remove(user)

return HttpNoContent()

# Removing other people from the group
else:
if not self.user_authorized(user, ext_group):
return HttpUnauthorized()

if ext_group.is_manager_group():
ext_group.user_set.remove(int(kwargs['user_id']))
else:
ext_group.user_set.remove(int(kwargs['user_id']))
ext_group.manager_group.user_set.remove(
int(kwargs['user_id']))
return HttpNoContent()
else:
return HttpMethodNotAllowed()

def ext_groups_members_list(self, request, **kwargs):
user = request.user

if request.method == 'GET':
ext_group_list = self.ext_groups_with_user(user)

for i in ext_group_list:
i.member_list = self.get_member_list(i)
i.can_edit = self.user_authorized(user, i)
i.manager_group_uuid = (
i.uuid if i.is_manager_group() else i.manager_group.uuid
)
return self.create_response(
request,
{
'meta': {
'total_count': len(ext_group_list)
},
'objects': map(
lambda g: self.full_dehydrate(
self.build_bundle(obj=g, request=request)),
ext_group_list)
}
)
else:
return HttpMethodNotAllowed()
6 changes: 6 additions & 0 deletions refinery/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1863,6 +1863,12 @@ def get_managed_group(self):
except:
return None

def is_user_a_group_manager(self, user):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like it should be a method on the User model.

if self.is_manager_group():
return user in self.user_set.all()
else:
return user in self.manager_group.user_set.all()


# automatic creation of a managed group when an extended group is created:
def create_manager_group(sender, instance, created, **kwargs):
Expand Down
24 changes: 21 additions & 3 deletions refinery/core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ def partial_update(self, instance, validated_data):


class ExtendedGroupSerializer(serializers.ModelSerializer):
manager_group_uuid = serializers.SerializerMethodField()
member_list = serializers.SerializerMethodField()
perm_list = serializers.SerializerMethodField()
can_edit = serializers.SerializerMethodField()
Expand All @@ -128,6 +129,11 @@ class ExtendedGroupSerializer(serializers.ModelSerializer):
validators=[UniqueValidator(queryset=ExtendedGroup.objects.all())]
)

def get_manager_group_uuid(self, group):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems redundant. Doesn't appear to be used anywhere.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a field the client expects.

if group.is_manager_group():
return ''
return group.manager_group.uuid

def get_can_edit(self, group):
user = self.context.get('user')
if user is None:
Expand All @@ -145,7 +151,18 @@ def get_member_list(self, group):
users = group.user_set.all().filter(is_active=True).exclude(
username=settings.ANONYMOUS_USER_NAME
)
return UserSerializer(users, many=True).data
user_data = UserSerializer(users, many=True).data
for user in user_data:
if group.is_manager_group():
user['is_manager'] = user.get('id') in \
group.user_set.all().values_list(
'id', flat=True
)
else:
user['is_manager'] = user.get('id') in \
group.manager_group.user_set.all()\
.values_list('id', flat=True)
return user_data
return []

def get_perm_list(self, group):
Expand All @@ -162,7 +179,8 @@ def get_uuid(self, group):

class Meta:
model = ExtendedGroup
fields = ('can_edit', 'name', 'id', 'uuid', 'member_list', 'perm_list')
fields = ('can_edit', 'name', 'id', 'uuid', 'manager_group_uuid',
'member_list', 'perm_list')


class SiteVideoSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -248,7 +266,7 @@ class UserSerializer(serializers.ModelSerializer):

class Meta:
model = User
fields = ('first_name', 'last_name', 'profile', 'username')
fields = ('first_name', 'id', 'last_name', 'profile', 'username')


class WorkflowSerializer(serializers.HyperlinkedModelSerializer):
Expand Down