Membership: Permissions, cleanups, green tests

This commit is contained in:
nikolark committed 2013-10-20 00:18:00 +02:00
1 parent 9961cea151
commit 99cf7cd1bb
7 files changed
+89 -24

No files matched your search

+4
View File
@@ -84,7 +84,11 @@ class MemberResource(ModelResource):
+ len(Member.objects.filter(email=bundle.data['email'])) \
+ len(Member.objects.filter(phone_number=bundle.data['phone_number']))
if member_count > 0:
# TODO Specify what data is missing
raise ImmediateHttpResponse(HttpForbidden("User with given data already exists"))
# Quick hack to allow new users to be created anonymously (unauthenticated)
bundle._anonymous_request_allowed = True
return bundle
def obj_update(self, bundle, **kwargs):
+8 -7
View File
@@ -2,7 +2,8 @@ from django.conf.urls import url
from dusken.api.member import MemberResource
from dusken.models import *
from tastypie import fields
from tastypie.authorization import Authorization
from dusken.authorization import MyDjangoAuthorization
from dusken.authentication import MyApiKeyAuthentication
from tastypie.resources import ModelResource, ALL, ALL_WITH_RELATIONS
@@ -19,7 +20,8 @@ class MembershipResource(ModelResource):
resource_name = 'membership'
list_allowed_methods = [ 'get', 'post' ]
detail_allowed_methods = [ 'get', 'patch' ]
authorization = Authorization() # TODO: for dev (VERY INSECURE)
authorization = MyDjangoAuthorization()
authentication = MyApiKeyAuthentication()
filtering = {
'member' : ALL_WITH_RELATIONS,
'expires' : [ 'exact','range','gt','gte','lt','lte' ], #TODO: Doesn't work.
@@ -41,8 +43,7 @@ class MembershipResource(ModelResource):
def hydrate(self, bundle):
if 'membership_type' in bundle.data:
bundle.data['mtype'] = bundle.data['membership_type']
del bundle.data['membership_type']
bundle.obj.membership_type = MembershipType.objects.get(id=bundle.data['membership_type'])
if 'member' in bundle.data:
bundle.data['member'] = Member.objects.get(id=bundle.data['member'])
@@ -51,13 +52,13 @@ class MembershipResource(ModelResource):
def dehydrate(self, bundle):
bundle.data['member'] = bundle.obj.member.id
bundle.data['membership_type'] = bundle.obj.mtype.id
bundle.data['membership_type'] = bundle.obj.membership_type.id
bundle.data['payment'] = None if not bundle.obj.payment else bundle.obj.payment.id
return bundle
def obj_update(self, bundle, request, **kwargs):
member_id = bundle.data.get('member')
membership_type = bundle.data.get('member')
membership_type = bundle.data.get('membership_type')
start_date = bundle.data.get('start_date')
if None in (member_id, membership_type, start_date):
@@ -75,7 +76,7 @@ class MembershipTypeResource(ModelResource):
resource_name = "membership/type"
list_allowed_methods = [ 'get', 'post' ]
detail_allowed_methods = [ 'get', 'delete' ]
authorization = Authorization() # TODO
authorization = MyDjangoAuthorization()
def delete_detail(self, request, **kwarg):
#TODO set is_active flag to False
+52 -2
View File
@@ -1,8 +1,57 @@
from tastypie.authorization import DjangoAuthorization
from tastypie.exceptions import Unauthorized
# TODO add django-guardian for object level/row level permissions, then
"""
Create
######
All registered users should be allowed to create:
* Membership
Unregistered users should be able to create:
* Member
Update
######
All registered users should be allowed to update:
Delete
######
All registered users should be allowed to delete:
"""
class MyDjangoAuthorization(DjangoAuthorization):
def create_detail(self, object_list, bundle):
# Special case for creating a new user
if hasattr(bundle, '_anonymous_request_allowed'):
return True
klass = self.base_checks(bundle.request, bundle.obj.__class__)
if klass is False:
raise Unauthorized("You are not allowed to access that resource.")
# Skip permission check if the request is made by the object owner
# Note: tastypie.authorization.DjangoAuthorization does not have this check
if bundle.obj.owner() == bundle.request.user:
return True
permission = '%s.add_%s' % (klass._meta.app_label, klass._meta.module_name)
if not bundle.request.user.has_perm(permission):
raise Unauthorized("You are not allowed to access that resource.")
return True
def update_detail(self, object_list, bundle):
klass = self.base_checks(bundle.request, bundle.obj.__class__)
@@ -10,8 +59,8 @@ class MyDjangoAuthorization(DjangoAuthorization):
raise Unauthorized("You are not allowed to access that resource.")
# Skip permission check if the request is made by the object owner
# Note: Standard DjangoAuthorization does not have this check
if bundle.obj == bundle.request.user:
# Note: tastypie.authorization.DjangoAuthorization does not have this check
if bundle.obj.owner() == bundle.request.user:
return True
permission = '%s.change_%s' % (klass._meta.app_label, klass._meta.module_name)
@@ -20,3 +69,4 @@ class MyDjangoAuthorization(DjangoAuthorization):
raise Unauthorized("You are not allowed to access that resource.")
return True
+4 -4
View File
@@ -7,7 +7,7 @@
"updated": "2013-09-11T18:45:13.341Z",
"created": "2013-09-11T18:45:13.341Z",
"payment": null,
"mtype": 1,
"membership_type": 1,
"start_date": "2013-09-11"
}
}, {
@@ -18,7 +18,7 @@
"updated": "2013-09-11T18:45:30.084Z",
"created": "2013-09-11T18:45:30.084Z",
"payment": null,
"mtype": 1,
"membership_type": 1,
"start_date": "2013-09-15"
}
}, {
@@ -29,8 +29,8 @@
"updated": "2013-09-11T18:45:53.522Z",
"created": "2013-09-11T18:45:53.522Z",
"payment": null,
"mtype": 2,
"membership_type": 2,
"start_date": "2013-09-11"
}
}
]
]
+1 -1
View File
@@ -19,4 +19,4 @@
"end_date": "2013-09-11"
}
}
]
]
+16 -7
View File
@@ -21,33 +21,36 @@ class Member(django.contrib.auth.models.AbstractUser):
phone_number = models.CharField(max_length=30, unique=True, null=True, blank=True)
date_of_birth = models.DateField(blank=True, null=True)
legacy_id = models.IntegerField(unique=True, null=True, blank=True)
address = models.ForeignKey('dusken.Address', null=True, blank=True)
address = models.ForeignKey('dusken.Address', null=True, blank=True) # TODO change to ManyToManyField
place_of_study = models.ManyToManyField('dusken.PlaceOfStudy', null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def owner(self):
return self
class Membership(BaseModel):
def __unicode__(self):
return u"{member}: {fromdate}".format(member=self.member, fromdate=self.start_date)
start_date = models.DateField()
mtype = models.ForeignKey('dusken.MembershipType', db_column='type')
membership_type = models.ForeignKey('dusken.MembershipType')
payment = models.ForeignKey('dusken.Payment', unique=True, null=True, blank=True)
member = models.ForeignKey('dusken.Member')
def expires(self):
return self.mtype.end_date
return self.membership_type.end_date
def owner(self):
return self.member
'''
TODO: should validate end_day_of_month and end_month (use datetime exceptions)
'''
class MembershipType(BaseModel):
def __unicode__(self):
return u"{}".format(self.name)
name = models.CharField(max_length=50, unique=True)
end_date = models.DateField()
end_date = models.DateField(null=True, blank=True) # TODO move to Membership (no new Membership types every year)
is_active = models.BooleanField(default=True)
does_not_expire = models.BooleanField(default=False)
@@ -77,6 +80,9 @@ class FacebookAuth(BaseModel):
token_expires = models.DateTimeField(null=True, blank=True)
member = models.OneToOneField('dusken.Member', null=True, blank=True)
def owner(self):
return self.member
class GoogleAuth(BaseModel):
def __unicode__(self):
@@ -86,6 +92,9 @@ class GoogleAuth(BaseModel):
token_expires = models.DateTimeField(null=True, blank=True)
member = models.OneToOneField('dusken.Member', null=True, blank=True)
def owner(self):
return self.member
class Address(BaseModel):
class Meta:
+4 -3
View File
@@ -34,6 +34,7 @@ class MembershipTest(ResourceTestCase):
authentication=self.creds
)
# Help set format and add authentication credentials
def do_post_request(self, url, data):
return self.api_client.post(
url,
@@ -70,7 +71,7 @@ class MembershipTest(ResourceTestCase):
resp = self.do_post_request(self.membership_url, data)
self.assertValidJSONResponse(resp)
self.assertHttpCreated(resp)
def test_patch_membership(self):
data = {
@@ -83,8 +84,8 @@ class MembershipTest(ResourceTestCase):
format='json',
data=data,
authentication=self.creds)
self.assertValidJSONResponse(resp)
self.assertHttpAccepted(resp) # Note: Patch requests should return HTTP 202 Accepted
####################################################################
### JSON Responses