Skip to content

Commit

Permalink
refacturing of the permissions, set up new tests, disabled template u…
Browse files Browse the repository at this point in the history
…rls for now
  • Loading branch information
hanneshapke committed Jan 30, 2015
1 parent a98cd21 commit e20159b
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 25 deletions.
2 changes: 1 addition & 1 deletion requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ coverage==3.7.1
flake8==2.2.5
python-coveralls
pytest-cov
pytest-flakes
pytest-flakes
9 changes: 5 additions & 4 deletions volunteerapp/config/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ class Common(Configuration):
)

# See: http://django-crispy-forms.readthedocs.org/en/latest/install.html#template-packs
CRISPY_TEMPLATE_PACK = 'bootstrap3'
# CRISPY_TEMPLATE_PACK = 'bootstrap3'
# END TEMPLATE CONFIGURATION

# STATIC FILE CONFIGURATION
Expand Down Expand Up @@ -267,11 +267,12 @@ def post_setup(cls):

# Django REST Framework
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
# 'rest_framework.permissions.AllowAny',
),
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}

Expand Down
10 changes: 5 additions & 5 deletions volunteerapp/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@
<div class="container">
<a class="navbar-brand" href="/">VolunteerApp</a>
<ul class="nav navbar-nav">
<li class="active"><a href="{% url 'home' %}">Home</a></li>
<li><a href="{% url 'about' %}">About</a></li>
<li class="active"><a href="{# url 'home' #}">Home</a></li>
<li><a href="{# url 'about' #}">About</a></li>

{% if request.user.is_authenticated %}
<li><a href="{% url 'users:detail' request.user.username %}">{% trans "My Profile" %}</a></li>
<li><a href="{% url 'account_logout' %}">{% trans "Logout" %}</a></li>
<li><a href="{# url 'account_logout' #}">{% trans "Logout" %}</a></li>
{% else %}
<li><a href="{% url 'account_signup' %}">{% trans "Sign Up" %}</a></li>
<li><a href="{% url 'account_login' %}">{% trans "Log In" %}</a></li>
<li><a href="{# url 'account_signup' #}">{% trans "Sign Up" %}</a></li>
<li><a href="{# url 'account_login' #}">{% trans "Log In" %}</a></li>
{% endif %}
</ul>
</div>
Expand Down
2 changes: 1 addition & 1 deletion volunteerapp/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from django.conf import settings
from django.conf.urls import patterns, include, url
from django.conf.urls.static import static
# from django.views.generic import TemplateView
from django.views.generic import TemplateView

from rest_framework.routers import DefaultRouter

Expand Down
2 changes: 0 additions & 2 deletions volunteerapp/users/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,4 @@ def has_object_permission(self, request, view, obj):
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True

# Instance must have an attribute named `owner`.
return obj == request.user
5 changes: 2 additions & 3 deletions volunteerapp/users/tests.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from django.test import TestCase
from .models import Users
from users.models import Users


class UserTestCase(TestCase):
def setUp(self):
def setUp(self): # noqa
Users.objects.create(username="john")
Users.objects.create(username="martha")

Expand All @@ -13,4 +13,3 @@ def test_user(self):
martha = Users.objects.get(username="martha")
self.assertEqual(john.username, 'john')
self.assertEqual(martha.username, 'martha')
# self.assertEqual(cat.speak(), 'The cat says "meow"')
32 changes: 32 additions & 0 deletions volunteerapp/volunteer/migrations/0009_auto_20150129_1840.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations


class Migration(migrations.Migration):

dependencies = [
('volunteer', '0008_auto_20150125_1630'),
]

operations = [
migrations.AlterField(
model_name='event',
name='name',
field=models.CharField(help_text='Please add a descriptive name.', max_length=100, unique=True, null=True),
preserve_default=True,
),
migrations.AlterField(
model_name='organization',
name='name',
field=models.CharField(help_text='Please add a descriptive name.', max_length=100, unique=True, null=True),
preserve_default=True,
),
migrations.AlterField(
model_name='shift',
name='name',
field=models.CharField(help_text='Please add a descriptive name.', max_length=100, unique=True, null=True),
preserve_default=True,
),
]
21 changes: 20 additions & 1 deletion volunteerapp/volunteer/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
# Python methods and packages
from decimal import Decimal
# from datetime import datetime, timedelta

# Django packages
from django.db import models
Expand All @@ -16,6 +15,10 @@


class CommonModel(TimeStampedModel):
"""
The models Organization, Event and Shift with
inherit common attributes from CommonModel
"""

STATUS_CHOICES = (
('--', _('Choose the Status')),
Expand All @@ -26,6 +29,7 @@ class CommonModel(TimeStampedModel):

name = models.CharField(
max_length=100, null=True,
unique=True,
help_text=_("Please add a descriptive name."))
slug = models.SlugField(
max_length=50, blank=True,
Expand Down Expand Up @@ -61,6 +65,9 @@ def save(self, *args, **kwargs):


class Shift(CommonModel):
"""
The Shift model hold the information around event shifts.
"""

max_volunteers = models.PositiveIntegerField(
blank=True, null=True,
Expand All @@ -81,6 +88,10 @@ class Meta:
def __str__(self):
return self.name

def __init__(self, *args, **kwargs):
self._meta.get_field('name').unique = False
super(Shift, self).__init__(*args, **kwargs)

def is_full(self):
if self.max_volunteers is None:
return False
Expand Down Expand Up @@ -128,6 +139,10 @@ def get_duration(self):


class Event(CommonModel):
"""
The Event model holds the information around Events and
connects Organizations with Shifts
"""

admin = models.ManyToManyField(
Users, null=True, blank=True, related_name='event')
Expand All @@ -148,6 +163,10 @@ def get_num_shifts(self):


class Organization(CommonModel):
"""
Simple Organization model to hold the information about
organizations and who can modify these details
"""

admin = models.ManyToManyField(
Users, null=True, blank=True, related_name='organization')
Expand Down
8 changes: 5 additions & 3 deletions volunteerapp/volunteer/permissions.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from rest_framework import permissions


class IsAdminOrReadOnly(permissions.BasePermission):
class IsObjectAdminOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
If the user is authenticated, (s)he can create new objects.
"""

def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True

# For all other submissions
# Write permissions are only allowed if user is admin of the object.
return obj.admin.filter(id=request.user.id).exists()
return (request.user.is_authenticated()
and obj.admin.filter(id=request.user.id).exists())
88 changes: 86 additions & 2 deletions volunteerapp/volunteer/tests.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,87 @@
from django.test import TestCase
import json
from django.core.urlresolvers import reverse

# Create your tests here.
from rest_framework import status
from rest_framework.test import APITestCase

from volunteer.models import Organization # , Event, Shift, Task
from users.models import Users


class OrganizationTestCase(APITestCase):

def setUp(self): # noqa
"""
Set up test user and a sample organization
for testing the duplicate error message
"""
Users.objects.create_user(
username='john',
last_name='John',
first_name='Crane',
email='john@gmail.com',
password='123'
)
Organization.objects.create(
name='Duplicate Test Organization',
description='Duplicate Description',
)

def test_create_organization_witout_auth(self):
"""
Test the status code if a unauthenticated POST
request is submitted
"""
url = reverse('organization-list')
data = {
'name': 'Test Organization',
'description': 'Test Description',
}
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

def test_create_organization_logged_in(self):
"""
Test the status code and returned dataif an
authenticated POST request is submitted
"""
self.client.login(username='john', password='123')
url = reverse('organization-list')
data = {
'name': 'Test Organization',
'description': 'Test Description',
}
response_data = {
'description': 'Test Description',
'get_num_events': 0,
'get_status_display': 'Draft Mode',
'id': 3,
'location': None,
'name': 'Test Organization',
'slug': 'test-organization',
'status': 'DR'
}

response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertJSONEqual(json.dumps(response.data), json.dumps(response_data))
self.client.logout()

def test_create_duplicate_organization(self):
"""
Test the status code if an
authenticated POST, but duplicated object
is submitted
"""
self.client.login(username='john', password='123')
url = reverse('organization-list')
data = {
'name': 'Duplicate Test Organization',
'description': 'Duplicate Description',
}
response_data = {u'name': [u'This field must be unique.']}

response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertJSONEqual(json.dumps(response.data), json.dumps(response_data))
self.client.logout()
20 changes: 17 additions & 3 deletions volunteerapp/volunteer/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from .models import Organization, Event, Shift
from .serializers import OrganizationSerializer, EventSerializer, ShiftSerializer
from .permissions import IsAdminOrReadOnly
from .permissions import IsObjectAdminOrReadOnly


class OrganizationViewSet(viewsets.ModelViewSet):
Expand All @@ -11,19 +11,33 @@ class OrganizationViewSet(viewsets.ModelViewSet):
"""
queryset = Organization.objects.all()
serializer_class = OrganizationSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsAdminOrReadOnly)
permission_classes = (
permissions.IsAuthenticatedOrReadOnly,
IsObjectAdminOrReadOnly,
)
lookup_field = 'slug'

def perform_create(self, serializer):
instance = serializer.save()
instance.admin.add(self.request.user)


class EventViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list` and `detail` actions.
"""
queryset = Event.objects.all()
serializer_class = EventSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsAdminOrReadOnly)
permission_classes = (
permissions.IsAuthenticatedOrReadOnly,
IsObjectAdminOrReadOnly,
)
lookup_field = 'slug'

def perform_create(self, serializer):
instance = serializer.save()
instance.admin.add(self.request.user)


class ShiftViewSet(viewsets.ModelViewSet):
"""
Expand Down

0 comments on commit e20159b

Please sign in to comment.