diff --git a/common/djangoapps/config_models/models.py b/common/djangoapps/config_models/models.py index 415775e535..34b28f6c9c 100644 --- a/common/djangoapps/config_models/models.py +++ b/common/djangoapps/config_models/models.py @@ -93,6 +93,8 @@ class ConfigurationModel(models.Model): """ Clear the cached value when saving a new configuration entry """ + # Always create a new entry, instead of updating an existing model + self.pk = None super(ConfigurationModel, self).save(*args, **kwargs) cache.delete(self.cache_key_name(*[getattr(self, key) for key in self.KEY_FIELDS])) if self.KEY_FIELDS: diff --git a/common/djangoapps/config_models/tests.py b/common/djangoapps/config_models/tests.py index caa92ab657..8e76c9af01 100644 --- a/common/djangoapps/config_models/tests.py +++ b/common/djangoapps/config_models/tests.py @@ -7,10 +7,13 @@ import ddt from django.contrib.auth.models import User from django.db import models from django.test import TestCase +from rest_framework.test import APIRequestFactory + from freezegun import freeze_time -from mock import patch +from mock import patch, Mock from config_models.models import ConfigurationModel +from config_models.views import ConfigurationModelCurrentAPIView class ExampleConfig(ConfigurationModel): @@ -92,6 +95,14 @@ class ConfigurationModelTests(TestCase): self.assertEqual(rows[1].string_field, 'first') self.assertEqual(rows[1].is_active, False) + def test_always_insert(self, mock_cache): + config = ExampleConfig(changed_by=self.user, string_field='first') + config.save() + config.string_field = 'second' + config.save() + + self.assertEquals(2, ExampleConfig.objects.all().count()) + class ExampleKeyedConfig(ConfigurationModel): """ @@ -282,3 +293,81 @@ class KeyedConfigurationModelTests(TestCase): fake_result = [('a', 'b'), ('c', 'd')] mock_cache.get.return_value = fake_result self.assertEquals(ExampleKeyedConfig.key_values(), fake_result) + + +@ddt.ddt +class ConfigurationModelAPITests(TestCase): + def setUp(self): + self.factory = APIRequestFactory() + self.user = User.objects.create_user( + username='test_user', + email='test_user@example.com', + password='test_pass', + ) + self.user.is_superuser = True + self.user.save() + + self.current_view = ConfigurationModelCurrentAPIView.as_view(model=ExampleConfig) + + # Disable caching while testing the API + patcher = patch('config_models.models.cache', Mock(get=Mock(return_value=None))) + patcher.start() + self.addCleanup(patcher.stop) + + def test_insert(self): + self.assertEquals("", ExampleConfig.current().string_field) + + request = self.factory.post('/config/ExampleConfig', {"string_field": "string_value"}) + request.user = self.user + response = self.current_view(request) + + self.assertEquals("string_value", ExampleConfig.current().string_field) + self.assertEquals(self.user, ExampleConfig.current().changed_by) + + def test_multiple_inserts(self): + for i in xrange(3): + self.assertEquals(i, ExampleConfig.objects.all().count()) + + request = self.factory.post('/config/ExampleConfig', {"string_field": str(i)}) + request.user = self.user + response = self.current_view(request) + self.assertEquals(201, response.status_code) + + self.assertEquals(i+1, ExampleConfig.objects.all().count()) + self.assertEquals(str(i), ExampleConfig.current().string_field) + + def test_get_current(self): + request = self.factory.get('/config/ExampleConfig') + request.user = self.user + response = self.current_view(request) + self.assertEquals('', response.data['string_field']) + self.assertEquals(10, response.data['int_field']) + self.assertEquals(None, response.data['changed_by']) + self.assertEquals(False, response.data['enabled']) + self.assertEquals(None, response.data['change_date']) + + ExampleConfig(string_field='string_value', int_field=20).save() + + response = self.current_view(request) + self.assertEquals('string_value', response.data['string_field']) + self.assertEquals(20, response.data['int_field']) + + @ddt.data( + ('get', [], 200), + ('post', [{'string_field': 'string_value', 'int_field': 10}], 201), + ) + @ddt.unpack + def test_permissions(self, method, args, status_code): + request = getattr(self.factory, method)('/config/ExampleConfig', *args) + + request.user = User.objects.create_user( + username='no-perms', + email='no-perms@example.com', + password='no-perms', + ) + response = self.current_view(request) + self.assertEquals(403, response.status_code) + + request.user = self.user + response = self.current_view(request) + self.assertEquals(status_code, response.status_code) diff --git a/common/djangoapps/config_models/views.py b/common/djangoapps/config_models/views.py new file mode 100644 index 0000000000..e5bc1b899c --- /dev/null +++ b/common/djangoapps/config_models/views.py @@ -0,0 +1,44 @@ +from rest_framework.generics import CreateAPIView, RetrieveAPIView +from rest_framework.permissions import DjangoModelPermissions +from rest_framework.authentication import SessionAuthentication +from rest_framework.serializers import ModelSerializer + + +class ReadableOnlyByAuthors(DjangoModelPermissions): + perms_map = DjangoModelPermissions.perms_map.copy() + perms_map['GET'] = perms_map['OPTIONS'] = perms_map['HEAD'] = perms_map['POST'] + + +class ConfigurationModelCurrentAPIView(CreateAPIView, RetrieveAPIView): + """ + This view allows an authenticated user with the appropriate model permissions + to read and write the current configuration for the specified `model`. + + Like other APIViews, you can use this by using a url pattern similar to the following:: + + url(r'config/example_config$', ConfigurationModelCurrentAPIView.as_view(model=ExampleConfig)) + """ + authentication_classes = (SessionAuthentication,) + permission_classes = (ReadableOnlyByAuthors,) + model = None + + def get_queryset(self): + return self.model.objects.all() + + def get_object(self): + # Return the currently active configuration + return self.model.current() + + def get_serializer_class(self): + if self.serializer_class is None: + class AutoConfigModelSerializer(ModelSerializer): + class Meta: + model = self.model + + self.serializer_class = AutoConfigModelSerializer + + return self.serializer_class + + def perform_create(self, serializer): + # Set the requesting user as the one who is updating the configuration + serializer.save(changed_by = self.request.user)