Simple app to add configuration options to a Django project.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

updated rest framework resources

+148 -18
+7
HISTORY.rst
··· 3 3 History 4 4 ------- 5 5 6 + 2.1.0 (2019-08-29) 7 + ++++++++++++++++++ 8 + 9 + * Added public option to Option. 10 + * Changed permissions classes to OptionViewSet. 11 + 12 + 6 13 2.0.2 (2019-08-27) 7 14 ++++++++++++++++++ 8 15
+1 -1
options/__init__.py
··· 14 14 "TYPE_CHOICES", 15 15 "CONVERTER", 16 16 ] 17 - __version__ = "2.0.2" 17 + __version__ = "2.1.0"
+4 -2
options/admin.py
··· 7 7 class OptionAdmin(admin.ModelAdmin): 8 8 """Manage configuration options.""" 9 9 10 - list_display = ["public_name", "value"] 10 + list_display = ["public_name", "value", "is_public"] 11 + list_filter = ["is_public"] 11 12 search_fields = ["public_name", "name"] 12 13 13 14 ··· 15 16 class UserOptionAdmin(admin.ModelAdmin): 16 17 """Manage configuration user options.""" 17 18 18 - list_display = ["user", "public_name", "value"] 19 + list_display = ["user", "public_name", "value", "is_public"] 20 + list_filter = ["is_public"] 19 21 search_fields = ["public_name", "name"]
+1 -1
options/helpers.py
··· 42 42 except LookupError: 43 43 raise ImproperlyConfigured( 44 44 "SIMPLE_OPTIONS_USER_OPTION_MODEL refers to model '%s' that has not " 45 - "been installed" % DEFAULT_OPTION_MODEL 45 + "been installed" % DEFAULT_USER_OPTION_MODEL 46 46 ) 47 47 48 48
+26
options/managers.py
··· 4 4 from options.settings import DEFAULT_EXCLUDE_USER 5 5 6 6 7 + class OptionQuerySet(models.QuerySet): 8 + def public(self): 9 + """Gets public options.""" 10 + return self.filter(is_public=True) 11 + 12 + 7 13 class OptionManager(models.Manager): 8 14 """Manager for options.""" 15 + 16 + def get_queryset(self): 17 + return OptionQuerySet(self.model, using=self._db) 18 + 19 + def public(self): 20 + """Gets public options.""" 21 + return self.get_queryset().public() 9 22 10 23 def get_value(self, name, default=None): 11 24 """Gets the value with the proper type.""" ··· 16 29 return default 17 30 18 31 32 + class UserOptionQuerySet(models.QuerySet): 33 + def public(self): 34 + """Gets public options.""" 35 + return self.filter(is_public=True) 36 + 37 + 19 38 class UserOptionManager(models.Manager): 20 39 """Manager to handle user's custom options.""" 40 + 41 + def get_queryset(self): 42 + return OptionQuerySet(self.model, using=self._db) 43 + 44 + def public(self): 45 + """Gets public options.""" 46 + return self.get_queryset().public() 21 47 22 48 def filter_user_customizable(self): 23 49 """Returns option that the user can customize himself."""
+6 -1
options/models.py
··· 18 18 verbose_name=_("public name of the parameter"), 19 19 max_length=255, 20 20 unique=False, 21 + blank=True, 21 22 db_index=True, 22 23 ) 23 24 type = models.PositiveIntegerField(choices=TYPE_CHOICES, default=STRING) ··· 28 29 upload_to=UploadToDir("options", random_name=True), null=True, blank=True 29 30 ) 30 31 is_list = models.BooleanField(default=False) 32 + is_public = models.BooleanField(default=False) 31 33 32 34 class Meta: 33 35 abstract = True ··· 58 60 [CONVERTER.get(self.type, str)(value) for value in values] 59 61 except ValueError: 60 62 raise ValidationError(_("Invalid value for this type.")) 63 + # Default public name 64 + if not self.public_name: 65 + self.public_name = self.name 61 66 62 67 def save(self, *args, **kwargs): 63 68 self.clean() ··· 80 85 user = models.ForeignKey( 81 86 settings.AUTH_USER_MODEL, related_name="options", on_delete=models.CASCADE 82 87 ) 83 - name = models.CharField(verbose_name=_("Parameter"), max_length=255) 88 + name = models.CharField(_("parameter name"), max_length=255) 84 89 85 90 objects = UserOptionManager() 86 91
+9
options/rest_framework/permissions.py
··· 1 + from rest_framework import permissions 2 + from rest_framework.permissions import SAFE_METHODS 3 + 4 + 5 + class IsAdminForNoSafeMethods(permissions.BasePermission): 6 + def has_permission(self, request, view): 7 + if request.method not in SAFE_METHODS: 8 + return request.user.is_authenticated and request.user.is_staff 9 + return True
+14 -1
options/rest_framework/serializers.py
··· 11 11 class OptionSerializer(serializers.ModelSerializer): 12 12 class Meta: 13 13 model = Option 14 - fields = ["id", "name", "public_name", "type", "value", "file", "is_list"] 14 + fields = [ 15 + "id", 16 + "name", 17 + "public_name", 18 + "type", 19 + "value", 20 + "file", 21 + "is_list", 22 + "is_public", 23 + ] 15 24 16 25 17 26 class UserOptionSerializer(OptionSerializer): 27 + 28 + is_public = serializers.BooleanField(default=True, write_only=True) 29 + 18 30 class Meta(OptionSerializer.Meta): 19 31 model = UserOption 20 32 ··· 24 36 raise serializers.ValidationError( 25 37 _("The name in the option can't be handle by the user.") 26 38 ) 39 + return value
+12 -3
options/rest_framework/viewsets.py
··· 1 1 from rest_framework import viewsets 2 - from rest_framework.permissions import IsAdminUser, IsAuthenticated 2 + from rest_framework.permissions import IsAuthenticated 3 3 4 4 from options import get_option_model, get_user_option_model 5 + from options.rest_framework.permissions import IsAdminForNoSafeMethods 5 6 from options.rest_framework.serializers import OptionSerializer, UserOptionSerializer 6 7 7 8 Option = get_option_model() ··· 11 12 class OptionViewSet(viewsets.ModelViewSet): 12 13 queryset = Option.objects.all() 13 14 serializer_class = OptionSerializer 14 - permission_classes = (IsAdminUser,) 15 + permission_classes = (IsAdminForNoSafeMethods,) 16 + 17 + def get_queryset(self): 18 + queryset = super().get_queryset() 19 + if self.request.user.is_authenticated and self.request.user.is_staff: 20 + return queryset 21 + return queryset.public() 15 22 16 23 17 24 class UserOptionViewSet(viewsets.ModelViewSet): ··· 21 28 22 29 def get_queryset(self): 23 30 queryset = super().get_queryset() 24 - return queryset.filter(user=self.request.user) 31 + if self.request.user.is_authenticated and self.request.user.is_staff: 32 + return queryset 33 + return queryset.public().filter(user=self.request.user) 25 34 26 35 def perform_create(self, serializer): 27 36 serializer.save(user=self.request.user)
+2
tests/factories.py
··· 28 28 29 29 30 30 class OptionFactory(DjangoModelFactory): 31 + name = FuzzyText() 31 32 public_name = FuzzyText() 32 33 type = STRING 33 34 ··· 36 37 37 38 38 39 class UserOptionFactory(DjangoModelFactory): 40 + name = FuzzyText() 39 41 public_name = FuzzyText() 40 42 type = STRING 41 43
+66 -9
tests/test_options.py
··· 2 2 from rest_framework import status 3 3 from rest_framework.test import APITestCase 4 4 5 - from options import INT, FLOAT, STRING, get_option_model, get_user_option_model 5 + from options import INT, FLOAT, STRING 6 + from options.models import Option, UserOption 6 7 from tests.factories import UserFactory, UserOptionFactory, OptionFactory 7 - 8 - Option = get_option_model() 9 - UserOption = get_user_option_model() 10 8 11 9 12 10 class OptionTests(TestCase): ··· 35 33 self.assertIsInstance(value, float) 36 34 self.assertAlmostEqual(42.5, value) 37 35 36 + def test_public(self): 37 + options = OptionFactory.create_batch(size=5, is_public=True) 38 + OptionFactory.create_batch(size=3, is_public=False) 39 + self.assertEqual(len(options), Option.objects.public().count()) 40 + 38 41 39 42 class UserOptionTests(TestCase): 40 43 def test_custom_user_options(self): ··· 44 47 UserOption.objects.create( 45 48 name=name, public_name=name, value=expected_value, type=STRING, user=user 46 49 ) 47 - value = UserOption.objects.get_value(name, user=user, default="ohter") 50 + value = UserOption.objects.get_value(name, user=user, default="other") 48 51 self.assertEqual(expected_value, value) 49 52 50 53 51 54 class OptionAPITests(APITestCase): 52 55 def test_list_options(self): 56 + options = OptionFactory.create_batch(size=10) 53 57 admin = UserFactory(is_staff=True) 54 58 self.client.force_authenticate(admin) 55 59 response = self.client.get("/api/options/", format="json") 56 60 self.assertEqual(response.status_code, status.HTTP_200_OK) 57 61 data = response.json() 58 - self.assertEqual(1, len(data)) 62 + self.assertEqual(1 + len(options), len(data)) 63 + 64 + def test_list_options_no_staff(self): 65 + OptionFactory.create_batch(size=10) 66 + options = OptionFactory.create_batch(size=3, is_public=True) 67 + admin = UserFactory() 68 + self.client.force_authenticate(admin) 69 + response = self.client.get("/api/options/", format="json") 70 + self.assertEqual(response.status_code, status.HTTP_200_OK) 71 + data = response.json() 72 + self.assertEqual(len(options), len(data)) 73 + 74 + def test_cant_update_options_no_staff(self): 75 + OptionFactory.create_batch(size=10) 76 + options = OptionFactory.create_batch(size=3, is_public=True) 77 + admin = UserFactory() 78 + self.client.force_authenticate(admin) 79 + data = {"value": "dummy"} 80 + response = self.client.patch( 81 + f"/api/options/{options[0].pk}/", data=data, format="json" 82 + ) 83 + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) 84 + 85 + def test_can_update_options_staff(self): 86 + OptionFactory.create_batch(size=10) 87 + options = OptionFactory.create_batch(size=3, is_public=True) 88 + admin = UserFactory(is_staff=True) 89 + self.client.force_authenticate(admin) 90 + data = {"value": "dummy"} 91 + response = self.client.patch( 92 + f"/api/options/{options[0].pk}/", data=data, format="json" 93 + ) 94 + self.assertEqual(response.status_code, status.HTTP_200_OK) 59 95 60 96 def test_list_user_options(self): 61 97 user = UserFactory() 62 98 name = "default_option" 63 99 expected_value = "user default" 64 - UserOptionFactory(name=name, value=expected_value, user=user) 100 + UserOptionFactory(name=name, value=expected_value, user=user, is_public=True) 65 101 self.client.force_authenticate(user) 66 102 response = self.client.get("/api/user-options/", format="json") 67 103 self.assertEqual(response.status_code, status.HTTP_200_OK) ··· 72 108 user = UserFactory() 73 109 name = "default_option" 74 110 expected_value = "user default" 75 - UserOptionFactory(name="secret_option", value="secret", user=user) 76 - UserOptionFactory(name=name, value=expected_value, user=user) 111 + UserOptionFactory( 112 + name="secret_option", value="secret", user=user, is_public=True 113 + ) 114 + UserOptionFactory(name=name, value=expected_value, user=user, is_public=True) 77 115 self.client.force_authenticate(user) 78 116 response = self.client.get("/api/user-options/", format="json") 79 117 self.assertEqual(response.status_code, status.HTTP_200_OK) 80 118 data = response.json() 81 119 self.assertEqual(1, len(data)) 120 + 121 + def test_create_user_options(self): 122 + user = UserFactory() 123 + name = "default_option" 124 + expected_value = "user default" 125 + data = {"name": name, "value": expected_value} 126 + self.client.force_authenticate(user) 127 + response = self.client.post("/api/user-options/", data=data, format="json") 128 + self.assertEqual(response.status_code, status.HTTP_201_CREATED) 129 + self.assertEqual(1, UserOption.objects.filter(user=user).count()) 130 + 131 + def test_create_user_options_excluded(self): 132 + user = UserFactory() 133 + name = "secret_option" 134 + expected_value = "secret" 135 + data = {"name": name, "value": expected_value} 136 + self.client.force_authenticate(user) 137 + response = self.client.post("/api/user-options/", data=data, format="json") 138 + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)