Skip to content
Snippets Groups Projects
Commit 7fe8e475 authored by Vedran Karacic's avatar Vedran Karacic Committed by Marko Jevtic
Browse files

[SOL-2133] Add user deactivation endpoint.

parent c2d7e446
No related merge requests found
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('student', '0008_auto_20161117_1209'),
]
operations = [
migrations.AlterModelOptions(
name='userprofile',
options={'permissions': (('can_deactivate_users', 'Can deactivate, but NOT delete users'),)},
),
]
......@@ -234,6 +234,7 @@ class UserProfile(models.Model):
class Meta(object):
db_table = "auth_userprofile"
permissions = (("can_deactivate_users", "Can deactivate, but NOT delete users"),)
# CRITICAL TODO/SECURITY
# Sanitize all fields.
......
......@@ -6,7 +6,8 @@ from student.models import (User, UserProfile, Registration,
PendingEmailChange, UserStanding,
CourseAccessRole)
from course_modes.models import CourseMode
from django.contrib.auth.models import Group, AnonymousUser
from django.contrib.auth.models import AnonymousUser, Group, Permission
from django.contrib.contenttypes.models import ContentType
from datetime import datetime
import factory
from factory import lazy_attribute
......@@ -18,6 +19,8 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
# Factories are self documenting
# pylint: disable=missing-docstring
TEST_PASSWORD = 'test'
class GroupFactory(DjangoModelFactory):
class Meta(object):
......@@ -123,6 +126,10 @@ class AdminFactory(UserFactory):
is_staff = True
class SuperuserFactory(UserFactory):
is_superuser = True
class CourseEnrollmentFactory(DjangoModelFactory):
class Meta(object):
model = CourseEnrollment
......@@ -161,3 +168,18 @@ class PendingEmailChangeFactory(DjangoModelFactory):
user = factory.SubFactory(UserFactory)
new_email = factory.Sequence(u'new+email+{0}@edx.org'.format)
activation_key = factory.Sequence(u'{:0<30d}'.format)
class ContentTypeFactory(DjangoModelFactory):
class Meta(object):
model = ContentType
app_label = factory.Faker('app_name')
class PermissionFactory(DjangoModelFactory):
class Meta(object):
model = Permission
codename = factory.Faker('codename')
content_type = factory.SubFactory(ContentTypeFactory)
"""
Permissions classes for User accounts API views.
"""
from __future__ import unicode_literals
from rest_framework import permissions
class CanDeactivateUser(permissions.BasePermission):
"""
Grants access to AccountDeactivationView if the requesting user is a superuser
or has the explicit permission to deactivate a User account.
"""
def has_permission(self, request, view):
return request.user.has_perm('student.can_deactivate_users')
"""
Tests for User deactivation API permissions
"""
from django.test import TestCase, RequestFactory
from openedx.core.djangoapps.user_api.accounts.permissions import CanDeactivateUser
from student.tests.factories import ContentTypeFactory, PermissionFactory, SuperuserFactory, UserFactory
class CanDeactivateUserTest(TestCase):
""" Tests for user deactivation API permissions """
def setUp(self):
super(CanDeactivateUserTest, self).setUp()
self.request = RequestFactory().get('/test/url')
def test_api_permission_superuser(self):
self.request.user = SuperuserFactory()
result = CanDeactivateUser().has_permission(self.request, None)
self.assertTrue(result)
def test_api_permission_user_granted_permission(self):
user = UserFactory()
permission = PermissionFactory(
codename='can_deactivate_users',
content_type=ContentTypeFactory(
app_label='student'
)
)
user.user_permissions.add(permission) # pylint: disable=no-member
self.request.user = user
result = CanDeactivateUser().has_permission(self.request, None)
self.assertTrue(result)
def test_api_permission_user_without_permission(self):
self.request.user = UserFactory()
result = CanDeactivateUser().has_permission(self.request, None)
self.assertFalse(result)
......@@ -2,30 +2,35 @@
"""
Test cases to cover Accounts-related behaviors of the User API application
"""
from collections import OrderedDict
from copy import deepcopy
import datetime
import ddt
import hashlib
import json
import unittest
from collections import OrderedDict
from copy import deepcopy
from mock import patch
from nose.plugins.attrib import attr
from pytz import UTC
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.test.testcases import TransactionTestCase
from django.test.utils import override_settings
from rest_framework import status
from rest_framework.test import APITestCase, APIClient
from openedx.core.djangoapps.user_api.models import UserPreference
from student.tests.factories import UserFactory
from student.models import UserProfile, LanguageProficiency, PendingEmailChange
from .. import PRIVATE_VISIBILITY, ALL_USERS_VISIBILITY
from openedx.core.djangoapps.user_api.accounts import ACCOUNT_VISIBILITY_PREF_KEY
from openedx.core.djangoapps.user_api.models import UserPreference
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
from .. import PRIVATE_VISIBILITY, ALL_USERS_VISIBILITY
from student.models import UserProfile, LanguageProficiency, PendingEmailChange
from student.tests.factories import (
AdminFactory, ContentTypeFactory, TEST_PASSWORD, PermissionFactory, SuperuserFactory, UserFactory
)
TEST_PROFILE_IMAGE_UPLOADED_AT = datetime.datetime(2002, 1, 9, 15, 43, 01, tzinfo=UTC)
......@@ -40,23 +45,22 @@ class UserAPITestCase(APITestCase):
"""
The base class for all tests of the User API
"""
test_password = "test"
def setUp(self):
super(UserAPITestCase, self).setUp()
self.anonymous_client = APIClient()
self.different_user = UserFactory.create(password=self.test_password)
self.different_user = UserFactory.create(password=TEST_PASSWORD)
self.different_client = APIClient()
self.staff_user = UserFactory(is_staff=True, password=self.test_password)
self.staff_user = UserFactory(is_staff=True, password=TEST_PASSWORD)
self.staff_client = APIClient()
self.user = UserFactory.create(password=self.test_password) # will be assigned to self.client by default
self.user = UserFactory.create(password=TEST_PASSWORD) # will be assigned to self.client by default
def login_client(self, api_client, user):
"""Helper method for getting the client and user and logging in. Returns client. """
client = getattr(self, api_client)
user = getattr(self, user)
client.login(username=user.username, password=self.test_password)
client.login(username=user.username, password=TEST_PASSWORD)
return client
def send_patch(self, client, json_data, content_type="application/merge-patch+json", expected_status=200):
......@@ -168,7 +172,7 @@ class TestOwnUsernameAPI(CacheIsolationTestCase, UserAPITestCase):
"""
Test that a client (logged in) can get her own username.
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
self._verify_get_own_username(15)
def test_get_username_inactive(self):
......@@ -176,7 +180,7 @@ class TestOwnUsernameAPI(CacheIsolationTestCase, UserAPITestCase):
Test that a logged-in client can get their
username, even if inactive.
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
self.user.is_active = False
self.user.save()
self._verify_get_own_username(15)
......@@ -271,7 +275,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
"""
Test that DELETE, POST, and PUT are not supported.
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
self.assertEqual(405, self.client.put(self.url).status_code)
self.assertEqual(405, self.client.post(self.url).status_code)
self.assertEqual(405, self.client.delete(self.url).status_code)
......@@ -298,7 +302,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
Test that a client (logged in) can only get the shareable fields for a different user.
This is the case when default_visibility is set to "all_users".
"""
self.different_client.login(username=self.different_user.username, password=self.test_password)
self.different_client.login(username=self.different_user.username, password=TEST_PASSWORD)
self.create_mock_profile(self.user)
with self.assertNumQueries(19):
response = self.send_get(self.different_client)
......@@ -313,7 +317,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
Test that a client (logged in) can only get the shareable fields for a different user.
This is the case when default_visibility is set to "private".
"""
self.different_client.login(username=self.different_user.username, password=self.test_password)
self.different_client.login(username=self.different_user.username, password=TEST_PASSWORD)
self.create_mock_profile(self.user)
with self.assertNumQueries(19):
response = self.send_get(self.different_client)
......@@ -389,7 +393,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
# Badges aren't on by default, so should not be present.
self.assertEqual(False, data["accomplishments_shared"])
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
verify_get_own_information(17)
# Now make sure that the user can get the same information, even if not active
......@@ -408,7 +412,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
legacy_profile.bio = ""
legacy_profile.save()
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
with self.assertNumQueries(17):
response = self.send_get(self.client)
for empty_field in ("level_of_education", "gender", "country", "bio"):
......@@ -499,7 +503,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
def test_patch_inactive_user(self):
""" Verify that a user can patch her own account, even if inactive. """
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
self.user.is_active = False
self.user.save()
response = self.send_patch(self.client, {"goals": "to not activate account"})
......@@ -541,7 +545,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
"""
Test the behavior of patch when an incorrect content_type is specified.
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
self.send_patch(self.client, {}, content_type="application/json", expected_status=415)
self.send_patch(self.client, {}, content_type="application/xml", expected_status=415)
......@@ -550,7 +554,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
Tests the behavior of patch when attempting to set fields with a select list of options to the empty string.
Also verifies the behaviour when setting to None.
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
for field_name in ["gender", "level_of_education", "country"]:
response = self.send_patch(self.client, {field_name: ""})
# Although throwing a 400 might be reasonable, the default DRF behavior with ModelSerializer
......@@ -586,7 +590,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
get_response = self.send_get(self.client)
self.assertEqual(new_name, get_response.data["name"])
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
legacy_profile = UserProfile.objects.get(id=self.user.id)
self.assertEqual({}, legacy_profile.get_meta())
old_name = legacy_profile.name
......@@ -706,7 +710,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
Test that AccountUpdateErrors are passed through to the response.
"""
serializer_save.side_effect = [Exception("bummer"), None]
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
error_response = self.send_patch(self.client, {"goals": "save an account field"}, expected_status=400)
self.assertEqual(
"Error thrown when saving account updates: 'bummer'",
......@@ -721,7 +725,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
with a '/', the API generates the full URL to profile images based on
the URL of the request.
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
response = self.send_get(self.client)
self.assertEqual(
response.data["profile_image"],
......@@ -787,12 +791,11 @@ class TestAccountAPITransactions(TransactionTestCase):
"""
Tests the transactional behavior of the account API
"""
test_password = "test"
def setUp(self):
super(TestAccountAPITransactions, self).setUp()
self.client = APIClient()
self.user = UserFactory.create(password=self.test_password)
self.user = UserFactory.create(password=TEST_PASSWORD)
self.url = reverse("accounts_api", kwargs={'username': self.user.username})
@patch('student.views.do_email_change_request')
......@@ -804,7 +807,7 @@ class TestAccountAPITransactions(TransactionTestCase):
# Throw an error from the method that is used to process the email change request
# (this is the last thing done in the api method). Verify that the profile did not change.
mock_email_change.side_effect = [ValueError, "mock value error thrown"]
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
old_email = self.user.email
json_data = {"email": "foo@bar.com", "gender": "o"}
......@@ -816,3 +819,65 @@ class TestAccountAPITransactions(TransactionTestCase):
data = response.data
self.assertEqual(old_email, data["email"])
self.assertEqual(u"m", data["gender"])
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Account APIs are only supported in LMS')
class TestAccountDeactivation(TestCase):
"""
Tests the account deactivation endpoint.
"""
def setUp(self):
super(TestAccountDeactivation, self).setUp()
self.superuser = SuperuserFactory()
self.staff_user = AdminFactory()
self.test_user = UserFactory()
self.url = reverse('accounts_deactivation', kwargs={'username': self.test_user.username})
def assert_activation_status(self, expected_status=status.HTTP_200_OK, expected_activation_status=False):
"""
Helper function for making a request to the deactivation endpoint, and asserting the status.
Args:
expected_status(int): Expected request's response status.
expected_activation_status(bool): Expected user has_usable_password attribute value.
"""
response = self.client.post(self.url)
self.assertEqual(response.status_code, expected_status)
self.test_user.refresh_from_db() # pylint: disable=no-member
self.assertEqual(self.test_user.has_usable_password(), expected_activation_status) # pylint: disable=no-member
def test_superuser_deactivates_user(self):
"""
Verify a user is deactivated when a superuser posts to the deactivation endpoint.
"""
self.client.login(username=self.superuser.username, password=TEST_PASSWORD)
self.assertTrue(self.test_user.has_usable_password()) # pylint: disable=no-member
self.assert_activation_status()
def test_user_with_permission_deactivates_user(self):
"""
Verify a user is deactivated when a user with permission posts to the deactivation endpoint.
"""
user = UserFactory()
permission = PermissionFactory(
codename='can_deactivate_users',
content_type=ContentTypeFactory(
app_label='student'
)
)
user.user_permissions.add(permission) # pylint: disable=no-member
self.client.login(username=user.username, password=TEST_PASSWORD)
self.assertTrue(self.test_user.has_usable_password()) # pylint: disable=no-member
self.assert_activation_status()
def test_unauthorized_rejection(self):
"""
Verify unauthorized users cannot deactivate accounts.
"""
self.client.login(username=self.test_user.username, password=TEST_PASSWORD)
self.assertTrue(self.test_user.has_usable_password()) # pylint: disable=no-member
self.assert_activation_status(
expected_status=status.HTTP_403_FORBIDDEN,
expected_activation_status=True
)
......@@ -10,15 +10,18 @@ from edx_rest_framework_extensions.authentication import JwtAuthentication
from rest_framework import permissions
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSet
from .api import get_account_settings, update_account_settings
from .permissions import CanDeactivateUser
from ..errors import UserNotFound, UserNotAuthorized, AccountUpdateError, AccountValidationError
from openedx.core.lib.api.authentication import (
SessionAuthenticationAllowInactiveUser,
OAuth2AuthenticationAllowInactiveUser,
)
from openedx.core.lib.api.parsers import MergePatchParser
from .api import get_account_settings, update_account_settings
from ..errors import UserNotFound, UserNotAuthorized, AccountUpdateError, AccountValidationError
from student.models import User
class AccountViewSet(ViewSet):
......@@ -219,3 +222,23 @@ class AccountViewSet(ViewSet):
)
return Response(account_settings)
class AccountDeactivationView(APIView):
"""
Account deactivation viewset. Currently only supports POST requests.
Only admins can deactivate accounts.
"""
permission_classes = (permissions.IsAuthenticated, CanDeactivateUser)
def post(self, request, username):
"""
POST /api/user/v1/accounts/{username}/deactivate/
Marks the user as having no password set for deactivation purposes.
"""
user = User.objects.get(username=username)
user.set_unusable_password()
user.save()
account_settings = get_account_settings(request, [username])[0]
return Response(account_settings)
......@@ -10,7 +10,7 @@ from mock import patch
from django.core.urlresolvers import reverse
from django.test.testcases import TransactionTestCase
from rest_framework.test import APIClient
from student.tests.factories import UserFactory
from student.tests.factories import UserFactory, TEST_PASSWORD
from openedx.core.djangolib.testing.utils import skip_unless_lms
from ...accounts.tests.test_views import UserAPITestCase
......@@ -42,7 +42,7 @@ class TestPreferencesAPI(UserAPITestCase):
"""
Test that DELETE, POST, and PUT are not supported.
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
self.assertEqual(405, self.client.put(self.url).status_code)
self.assertEqual(405, self.client.post(self.url).status_code)
self.assertEqual(405, self.client.delete(self.url).status_code)
......@@ -51,7 +51,7 @@ class TestPreferencesAPI(UserAPITestCase):
"""
Test that a client (logged in) cannot get the preferences information for a different client.
"""
self.different_client.login(username=self.different_user.username, password=self.test_password)
self.different_client.login(username=self.different_user.username, password=TEST_PASSWORD)
self.send_get(self.different_client, expected_status=404)
@ddt.data(
......@@ -72,7 +72,7 @@ class TestPreferencesAPI(UserAPITestCase):
Test that a client (logged in) can get her own preferences information (verifying the default
state before any preferences are stored).
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
response = self.send_get(self.client)
self.assertEqual({}, response.data)
......@@ -117,7 +117,7 @@ class TestPreferencesAPI(UserAPITestCase):
"""
Test the behavior of patch when an incorrect content_type is specified.
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
self.send_patch(self.client, {}, content_type="application/json", expected_status=415)
self.send_patch(self.client, {}, content_type="application/xml", expected_status=415)
......@@ -137,7 +137,7 @@ class TestPreferencesAPI(UserAPITestCase):
"""
Internal helper to generalize the creation of a set of preferences
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
if not is_active:
self.user.is_active = False
self.user.save()
......@@ -182,7 +182,7 @@ class TestPreferencesAPI(UserAPITestCase):
set_user_preference(self.user, "time_zone", "Asia/Macau")
# Send the patch request
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
self.send_patch(
self.client,
{
......@@ -215,7 +215,7 @@ class TestPreferencesAPI(UserAPITestCase):
set_user_preference(self.user, "time_zone", "Pacific/Midway")
# Send the patch request
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
response = self.send_patch(
self.client,
{
......@@ -266,7 +266,7 @@ class TestPreferencesAPI(UserAPITestCase):
"""
Test that a client (logged in) receives appropriate errors for a bad request.
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
# Verify a non-dict request
response = self.send_patch(self.client, "non_dict_request", expected_status=400)
......@@ -325,7 +325,7 @@ class TestPreferencesAPITransactions(TransactionTestCase):
def setUp(self):
super(TestPreferencesAPITransactions, self).setUp()
self.client = APIClient()
self.user = UserFactory.create(password=self.test_password)
self.user = UserFactory.create(password=TEST_PASSWORD)
self.url = reverse("preferences_api", kwargs={'username': self.user.username})
@patch('openedx.core.djangoapps.user_api.models.UserPreference.delete')
......@@ -342,7 +342,7 @@ class TestPreferencesAPITransactions(TransactionTestCase):
# after one of the updates has happened, in which case the whole operation
# should be rolled back.
delete_user_preference.side_effect = [Exception, None]
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
json_data = {
"a": "2",
"b": None,
......@@ -396,7 +396,7 @@ class TestPreferencesDetailAPI(UserAPITestCase):
"""
Test that POST and PATCH are not supported.
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
self.assertEqual(405, self.client.post(self.url).status_code)
self.assertEqual(405, self.client.patch(self.url).status_code)
......@@ -404,7 +404,7 @@ class TestPreferencesDetailAPI(UserAPITestCase):
"""
Test that a client (logged in) cannot manipulate a preference for a different client.
"""
self.different_client.login(username=self.different_user.username, password=self.test_password)
self.different_client.login(username=self.different_user.username, password=TEST_PASSWORD)
self.send_get(self.different_client, expected_status=404)
self.send_put(self.different_client, "new_value", expected_status=404)
self.send_delete(self.different_client, expected_status=404)
......@@ -429,7 +429,7 @@ class TestPreferencesDetailAPI(UserAPITestCase):
Test that a 404 is returned if the user does not have a preference with the given preference_key.
"""
self._set_url("does_not_exist")
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
response = self.send_get(self.client, expected_status=404)
self.assertIsNone(response.data)
......@@ -469,7 +469,7 @@ class TestPreferencesDetailAPI(UserAPITestCase):
"""
Generalization of the actual test workflow
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
if not is_active:
self.user.is_active = False
self.user.save()
......@@ -490,7 +490,7 @@ class TestPreferencesDetailAPI(UserAPITestCase):
Test that a client (logged in) cannot create an empty preference.
"""
self._set_url("new_key")
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
response = self.send_put(self.client, preference_value, expected_status=400)
self.assertEqual(
response.data,
......@@ -505,7 +505,7 @@ class TestPreferencesDetailAPI(UserAPITestCase):
"""
Test that a client cannot create preferences with bad keys
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
too_long_preference_key = "x" * 256
new_value = "new value"
......@@ -544,7 +544,7 @@ class TestPreferencesDetailAPI(UserAPITestCase):
"""
Test that a client (logged in) can update a preference.
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
self.send_put(self.client, preference_value)
response = self.send_get(self.client)
self.assertEqual(unicode(preference_value), response.data)
......@@ -572,7 +572,7 @@ class TestPreferencesDetailAPI(UserAPITestCase):
"""
Test that a client (logged in) cannot update a preference to null.
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
response = self.send_put(self.client, preference_value, expected_status=400)
self.assertEqual(
response.data,
......@@ -588,7 +588,7 @@ class TestPreferencesDetailAPI(UserAPITestCase):
"""
Test that a client (logged in) can delete her own preference.
"""
self.client.login(username=self.user.username, password=self.test_password)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
# Verify that a preference can be deleted
self.send_delete(self.client)
......
......@@ -6,7 +6,7 @@ from django.conf import settings
from django.conf.urls import patterns, url
from ..profile_images.views import ProfileImageView
from .accounts.views import AccountViewSet
from .accounts.views import AccountDeactivationView, AccountViewSet
from .preferences.views import PreferencesView, PreferencesDetailView
from .verification_api.views import PhotoVerificationStatusView
......@@ -33,6 +33,11 @@ urlpatterns = patterns(
ProfileImageView.as_view(),
name='accounts_profile_image_api'
),
url(
r'^v1/accounts/{}/deactivate/$'.format(settings.USERNAME_PATTERN),
AccountDeactivationView.as_view(),
name='accounts_deactivation'
),
url(
r'^v1/accounts/{}/verification_status/$'.format(settings.USERNAME_PATTERN),
PhotoVerificationStatusView.as_view(),
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment