Skip to content

Commit

Permalink
Merge pull request #64 from Cadasta/feature/organizations
Browse files Browse the repository at this point in the history
Adds organization models, serializer and views
  • Loading branch information
ian-ross committed Feb 17, 2016
2 parents 592ba20 + eebfd2b commit c7bcea8
Show file tree
Hide file tree
Showing 39 changed files with 1,673 additions and 66 deletions.
21 changes: 21 additions & 0 deletions cadasta/accounts/migrations/0003_auto_20160204_1838.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.2 on 2016-02-04 18:38
from __future__ import unicode_literals

import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('accounts', '0002_user_verify_email_by'),
]

operations = [
migrations.AlterField(
model_name='user',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username'),
),
]
37 changes: 27 additions & 10 deletions cadasta/accounts/tests/test_urls.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,52 @@
from django.test import TestCase
from django.core.urlresolvers import reverse, resolve

from core.tests.url_utils import version_ns, version_url
from .. import views


class UserUrlsTest(TestCase):
def test_account_user(self):
self.assertEqual(reverse('accounts:user'), '/account/')
self.assertEqual(
reverse(version_ns('accounts:user')),
version_url('/account/')
)

resolved = resolve('/account/')
resolved = resolve(version_url('/account/'))
self.assertEqual(resolved.func.__name__, views.AccountUser.__name__)

def test_account_register(self):
self.assertEqual(reverse('accounts:register'), '/account/register/')
self.assertEqual(
reverse(version_ns('accounts:register')),
version_url('/account/register/')
)

resolved = resolve('/account/register/')
resolved = resolve(version_url('/account/register/'))
self.assertEqual(resolved.func.__name__, views.AccountRegister.__name__)

def test_account_login(self):
self.assertEqual(reverse('accounts:login'), '/account/login/')
self.assertEqual(
reverse(version_ns('accounts:login')),
version_url('/account/login/')
)

resolved = resolve('/account/login/')
resolved = resolve(version_url('/account/login/'))
self.assertEqual(resolved.func.__name__, views.AccountLogin.__name__)

def test_account_activate(self):
self.assertEqual(reverse('accounts:activate'), '/account/activate/')
self.assertEqual(
reverse(version_ns('accounts:activate')),
version_url('/account/activate/')
)

resolved = resolve('/account/activate/')
resolved = resolve(version_url('/account/activate/'))
self.assertEqual(resolved.func.__name__, views.AccountVerify.__name__)

def test_password_reset(self):
self.assertEqual(reverse('accounts:password_reset'), '/account/password/reset/')
self.assertEqual(
reverse(version_ns('accounts:password_reset')),
version_url('/account/password/reset/')
)

resolved = resolve('/account/password/reset/')
resolved = resolve(version_url('/account/password/reset/'))
self.assertEqual(resolved.func.__name__, views.PasswordReset.__name__)
24 changes: 10 additions & 14 deletions cadasta/accounts/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_keep_email_address(self):
'username': 'imagine71'
}

request = APIRequestFactory().put('/account/', data)
request = APIRequestFactory().put('/v1/account/', data)
force_authenticate(request, user=user)
response = AccountUser.as_view()(request).render()
self.assertEqual(response.status_code, 200)
Expand All @@ -66,7 +66,7 @@ def test_update_with_existing_email(self):
'username': user.username
}

request = APIRequestFactory().put('/account/', data)
request = APIRequestFactory().put('/v1/account/', data)
force_authenticate(request, user=user)
response = AccountUser.as_view()(request).render()
self.assertEqual(response.status_code, 400)
Expand All @@ -85,7 +85,7 @@ def test_update_username(self):
'username': 'john'
}

request = APIRequestFactory().put('/account/', data)
request = APIRequestFactory().put('/v1/account/', data)
force_authenticate(request, user=user)
response = AccountUser.as_view()(request).render()
self.assertEqual(response.status_code, 200)
Expand All @@ -107,7 +107,7 @@ def test_update_with_existing_username(self):
'username': 'boss'
}

request = APIRequestFactory().put('/account/', data)
request = APIRequestFactory().put('/v1/account/', data)
force_authenticate(request, user=user)
response = AccountUser.as_view()(request).render()
self.assertEqual(response.status_code, 400)
Expand All @@ -126,14 +126,10 @@ def test_user_signs_up(self):
'last_name': 'Lennon',
}

request = APIRequestFactory().post('/account/register/', data)
request = APIRequestFactory().post('/v1/account/register/', data)
response = AccountRegister.as_view()(request).render()
self.assertEqual(response.status_code, 201)
# self.assertIn('auth_token', response.content.decode("utf-8"))

self.assertEqual(User.objects.count(), 1)
# user = User.objects.first()
# self.assertTrue(user.is_authenticated())

def test_user_signs_up_with_invalid(self):
"""The server should respond with an 404 error code when a user tries
Expand All @@ -145,7 +141,7 @@ def test_user_signs_up_with_invalid(self):
'last_name': 'Lennon',
}

request = APIRequestFactory().post('/account/register/', data)
request = APIRequestFactory().post('/v1/account/register/', data)
response = AccountRegister.as_view()(request).render()
self.assertEqual(response.status_code, 400)
self.assertEqual(User.objects.count(), 0)
Expand All @@ -161,7 +157,7 @@ def setUp(self):

def test_successful_login(self):
"""The view should return a token to authenticate API calls"""
request = APIRequestFactory().post('/account/login/', {
request = APIRequestFactory().post('/v1/account/login/', {
'username': 'imagine71',
'password': 'iloveyoko79'
})
Expand All @@ -171,7 +167,7 @@ def test_successful_login(self):

def test_unsuccessful_login(self):
"""The view should return a token to authenticate API calls"""
request = APIRequestFactory().post('/account/login/', {
request = APIRequestFactory().post('/v1/account/login/', {
'username': 'imagine71',
'password': 'iloveyoko78'
})
Expand All @@ -185,7 +181,7 @@ def test_login_with_unverified_email(self):
self.user.verify_email_by = datetime.now()
self.user.save()

request = APIRequestFactory().post('/account/login/', {
request = APIRequestFactory().post('/v1/account/login/', {
'username': 'imagine71',
'password': 'iloveyoko79'
})
Expand All @@ -204,7 +200,7 @@ def test_activate_account(self):
user.last_login = datetime.now()
user.save()

request = APIRequestFactory().post('/account/activate/', {
request = APIRequestFactory().post('/v1/account/activate/', {
'uid': encode_uid(user.pk),
'token': token
})
Expand Down
7 changes: 3 additions & 4 deletions cadasta/accounts/urls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from django.conf.urls import patterns, url
from django.conf.urls import url

from . import views

urlpatterns = patterns(
'',
urlpatterns = [
url(r'^$', views.AccountUser.as_view(), name='user'),
url(r'^register/$', views.AccountRegister.as_view(), name='register'),
url(r'^login/$', views.AccountLogin.as_view(), name='login'),
url(r'^activate/$', views.AccountVerify.as_view(), name='activate'),
url(r'^password/reset/$', views.PasswordReset.as_view(), name='password_reset'),
)
]
3 changes: 0 additions & 3 deletions cadasta/accounts/views.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
from django.contrib.auth import user_logged_in
from django.utils.translation import ugettext as _

from rest_framework.serializers import ValidationError
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from rest_framework.permissions import AllowAny
from rest_framework import status
from rest_framework.authtoken.models import Token

from djoser import views as djoser_views
from djoser import utils as djoser_utils
Expand All @@ -24,7 +22,6 @@ class AccountUser(djoser_utils.SendEmailViewMixin, djoser_views.UserView):
plain_body_template_name = 'change_email.txt'
serializer_class = serializers.UserSerializer


def get_email_context(self, user):
context = super(AccountUser, self).get_email_context(user)
context['url'] = settings.get('ACTIVATION_URL').format(**context)
Expand Down
13 changes: 13 additions & 0 deletions cadasta/config/permissions/default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"clause": [
{
"effect": "allow",
"object": ["*"],
"action": ["org.list"]
}, {
"effect": "allow",
"object": ["organization/*"],
"action": ["org.view"]
}
]
}
13 changes: 13 additions & 0 deletions cadasta/config/permissions/org-admin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"clause": [
{
"effect": "allow",
"object": ["*"],
"action": ["org.*"]
}, {
"effect": "allow",
"object": ["organization/*"],
"action": ["org.*"]
}
]
}
9 changes: 9 additions & 0 deletions cadasta/config/permissions/superuser.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"clause": [
{
"effect": "allow",
"object": ["organization/*"],
"action": ["org.*"]
}
]
}
10 changes: 10 additions & 0 deletions cadasta/config/settings/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,15 @@
'django.contrib.staticfiles',
'corsheaders',

'crispy_forms',
'rest_framework',
'rest_framework.authtoken',
'djoser',
'tutelary',

'core',
'accounts',
'organization'
)

MIDDLEWARE_CLASSES = (
Expand All @@ -53,12 +57,16 @@
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
'audit_log.middleware.UserLoggingMiddleware',
)

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
'DEFAULT_VERSION': 'v1',
'EXCEPTION_HANDLER': 'core.views.exception_handler'
}

ROOT_URLCONF = 'config.urls'
Expand All @@ -79,6 +87,8 @@
},
]

AUTHENTICATION_BACKENDS = ['core.backends.Auth']

DJOSER = {
'SITE_NAME': 'Cadasta',
'SET_PASSWORD_RETYPE': True,
Expand Down
8 changes: 7 additions & 1 deletion cadasta/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@
"""
from django.conf.urls import include, url

urlpatterns = [
urls = [
url(r'^account/', include('accounts.urls', namespace='accounts')),
url(r'^account/', include('djoser.urls.authtoken')),

url(r'^organizations/', include('organization.urls', namespace='organization')),
]

urlpatterns = [
url(r'^v1/', include(urls, namespace='v1'))
]
6 changes: 6 additions & 0 deletions cadasta/core/backends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from tutelary.backends import Backend as TutelaryBackend
from django.contrib.auth.backends import ModelBackend


class Auth(TutelaryBackend, ModelBackend):
pass
9 changes: 9 additions & 0 deletions cadasta/core/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import json


class JsonValidationError(BaseException):
def __init__(self, errors):
self.errors = errors

def __str__(self):
return json.dumps(self.errors)
12 changes: 12 additions & 0 deletions cadasta/core/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.db.models.query import QuerySet


class DetailSerializer:
def __init__(self, *args, **kwargs):
detail = kwargs.pop('detail', False)
super(DetailSerializer, self).__init__(*args, **kwargs)

is_list = type(self.instance) in [list, QuerySet]
if is_list and not detail:
for field_name in self.Meta.detail_only_fields:
self.fields.pop(field_name)
12 changes: 12 additions & 0 deletions cadasta/core/tests/test_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.test import TestCase

from ..exceptions import JsonValidationError


class JsonValidationErrorTest(TestCase):
def test_to_string(self):
exc = JsonValidationError({
'field': "Some error"
})

assert str(exc) == '{"field": "Some error"}'
11 changes: 8 additions & 3 deletions cadasta/core/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
from .testcases import AbstractModelTestCase
from django.test import TestCase
from ..models import RandomIDModel


class RandomIDModelTest(AbstractModelTestCase):
class MyTestModel(RandomIDModel):
class Meta:
app_label = 'core'


class RandomIDModelTest(TestCase):
abstract_model = RandomIDModel

def test_save(self):
instance = self.model()
instance = MyTestModel()
instance.save()
self.assertIsNotNone(instance.id)
Loading

0 comments on commit c7bcea8

Please sign in to comment.