From 56d847aace3f0ace40e96adecd64091acd545ce4 Mon Sep 17 00:00:00 2001 From: winkidney Date: Tue, 11 Feb 2020 13:31:55 +0800 Subject: [PATCH 01/11] Feature: Add Private property for Pin and read proection Now the private pin should only be viewed by owner. --- core/models.py | 1 + core/permissions.py | 13 +++++++++++++ core/serializers.py | 19 ++++++++++++++++++- core/views.py | 14 ++++++++++---- 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/core/models.py b/core/models.py index a61c822..0568e15 100644 --- a/core/models.py +++ b/core/models.py @@ -83,6 +83,7 @@ class Board(models.Model): class Pin(models.Model): submitter = models.ForeignKey(User) + private = models.BooleanField(default=False, blank=False) url = models.CharField(null=True, blank=True, max_length=256) referer = models.CharField(null=True, blank=True, max_length=256) description = models.TextField(blank=True, null=True) diff --git a/core/permissions.py b/core/permissions.py index 0df1293..b46750c 100644 --- a/core/permissions.py +++ b/core/permissions.py @@ -21,6 +21,19 @@ class IsOwnerOrReadOnly(permissions.IsAuthenticatedOrReadOnly): return getattr(obj, self.__owner_field_name) == request.user +class OwnerOnlyIfPrivate(permissions.BasePermission): + def __init__(self, owner_field_name="owner"): + self.__owner_field_name = owner_field_name + + def __call__(self): + return self + + def has_object_permission(self, request, view, obj): + if getattr(obj, "private"): + return request.user == getattr(obj, self.__owner_field_name) + return True + + class OwnerOnly(permissions.IsAuthenticatedOrReadOnly): def has_permission(self, request, view): diff --git a/core/serializers.py b/core/serializers.py index 6cef857..acfd1dd 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -1,4 +1,5 @@ from django.conf import settings +from django.db.models import Q from rest_framework import serializers from rest_framework.exceptions import ValidationError from taggit.models import Tag @@ -9,6 +10,14 @@ from django_images.models import Thumbnail from users.serializers import UserSerializer +def filter_private_pin(request, query): + if request.user.is_authenticated: + query = query.exclude(~Q(submitter=request.user), private=True) + else: + query = query.exclude(private=True) + return query.select_related('image', 'submitter') + + class ThumbnailSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Thumbnail @@ -164,7 +173,9 @@ class BoardSerializer(serializers.HyperlinkedModelSerializer): } submitter = UserSerializer(read_only=True) - pins_detail = PinSerializer(source="pins", many=True, read_only=True) + pins_detail = serializers.SerializerMethodField( + read_only=True, + ) pins = serializers.HyperlinkedRelatedField( write_only=True, queryset=Pin.objects.all(), @@ -187,6 +198,12 @@ class BoardSerializer(serializers.HyperlinkedModelSerializer): help_text="only patch method works for this field" ) + def get_pins_detail(self, instance): + query = instance.pins.all() + request = self.context['request'] + query = filter_private_pin(request, query) + return [PinSerializer(pin, context=self.context).data for pin in query] + @staticmethod def _get_list(pins_id): return tuple(Pin.objects.filter(id__in=pins_id)) diff --git a/core/views.py b/core/views.py index 543486b..e97a0a2 100644 --- a/core/views.py +++ b/core/views.py @@ -1,3 +1,4 @@ +from django.db.models import Q from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page from django_filters.rest_framework import DjangoFilterBackend @@ -8,7 +9,8 @@ from taggit.models import Tag from core import serializers as api from core.models import Image, Pin, Board -from core.permissions import IsOwnerOrReadOnly +from core.permissions import IsOwnerOrReadOnly, OwnerOnlyIfPrivate +from core.serializers import filter_private_pin class ImageViewSet(mixins.CreateModelMixin, GenericViewSet): @@ -20,13 +22,17 @@ class ImageViewSet(mixins.CreateModelMixin, GenericViewSet): class PinViewSet(viewsets.ModelViewSet): - queryset = Pin.objects.all().select_related('image', 'submitter') serializer_class = api.PinSerializer filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter) filter_fields = ("submitter__username", 'tags__name', ) ordering_fields = ('-id', ) ordering = ('-id', ) - permission_classes = [IsOwnerOrReadOnly("submitter"), ] + permission_classes = [IsOwnerOrReadOnly("submitter"), OwnerOnlyIfPrivate("submitter")] + + def get_queryset(self): + query = Pin.objects.all() + request = self.request + return filter_private_pin(request, query) class BoardViewSet(viewsets.ModelViewSet): @@ -67,7 +73,7 @@ class TagAutoCompleteViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): drf_router = routers.DefaultRouter() -drf_router.register(r'pins', PinViewSet) +drf_router.register(r'pins', PinViewSet, basename="pin") drf_router.register(r'images', ImageViewSet) drf_router.register(r'boards', BoardViewSet) drf_router.register(r'tags-auto-complete', TagAutoCompleteViewSet) From ef72df34a42eb32409a4928346382f6b5e670000 Mon Sep 17 00:00:00 2001 From: winkidney Date: Tue, 11 Feb 2020 14:33:42 +0800 Subject: [PATCH 02/11] Feature: Add migrations for private property of pin --- core/migrations/0007_pin_private.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 core/migrations/0007_pin_private.py diff --git a/core/migrations/0007_pin_private.py b/core/migrations/0007_pin_private.py new file mode 100644 index 0000000..23ff90a --- /dev/null +++ b/core/migrations/0007_pin_private.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.26 on 2020-02-11 05:32 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0006_remove_pin_origin'), + ] + + operations = [ + migrations.AddField( + model_name='pin', + name='private', + field=models.BooleanField(default=False), + ), + ] From c6253d9712efd3b60e619bae2a07de5dcd0a239d Mon Sep 17 00:00:00 2001 From: winkidney Date: Tue, 11 Feb 2020 14:51:14 +0800 Subject: [PATCH 03/11] Feature: Add private field for pin serializer --- core/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/core/serializers.py b/core/serializers.py index acfd1dd..f53f813 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -81,6 +81,7 @@ class PinSerializer(serializers.HyperlinkedModelSerializer): model = Pin fields = ( settings.DRF_URL_FIELD_NAME, + "private", "id", "submitter", "url", From 65cd51daa89115ad4c0732234111ac7dc1d4c62e Mon Sep 17 00:00:00 2001 From: winkidney Date: Tue, 11 Feb 2020 15:36:35 +0800 Subject: [PATCH 04/11] Feature: Add UI to handle visible-option for single Pin --- pinry-spa/src/components/Pins.vue | 1 + .../src/components/pin_edit/PinCreateModal.vue | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/pinry-spa/src/components/Pins.vue b/pinry-spa/src/components/Pins.vue index de0c7ea..990b880 100644 --- a/pinry-spa/src/components/Pins.vue +++ b/pinry-spa/src/components/Pins.vue @@ -90,6 +90,7 @@ function createImageItem(pin) { image.url = pinHandler.escapeUrl(pin.image.thumbnail.image); image.id = pin.id; image.owner_id = pin.submitter.id; + image.private = pin.private; image.description = pin.description; image.tags = pin.tags; image.author = pin.submitter.username; diff --git a/pinry-spa/src/components/pin_edit/PinCreateModal.vue b/pinry-spa/src/components/pin_edit/PinCreateModal.vue index 4b27bf7..0abfaa9 100644 --- a/pinry-spa/src/components/pin_edit/PinCreateModal.vue +++ b/pinry-spa/src/components/pin_edit/PinCreateModal.vue @@ -27,6 +27,13 @@ > + + + {{ pinModel.form.private.value?"only visible to yourself":"visible to everyone" }} + + @@ -107,7 +114,7 @@ function isURLBlank(url) { return url !== null && url === ''; } -const fields = ['url', 'referer', 'description', 'tags']; +const fields = ['url', 'referer', 'description', 'tags', 'private']; export default { name: 'PinCreateModal', @@ -160,11 +167,13 @@ export default { this.pinModel.form.referer.value = this.existedPin.referer; this.pinModel.form.description.value = this.existedPin.description; this.pinModel.form.tags.value = this.existedPin.tags; + this.pinModel.form.private.value = this.existedPin.private; } if (this.fromUrl) { this.pinModel.form.url.value = this.fromUrl.url; this.pinModel.form.referer.value = this.fromUrl.referer; this.pinModel.form.description.value = this.fromUrl.description; + this.pinModel.form.private.value = false; } }, methods: { @@ -216,7 +225,7 @@ export default { savePin() { const self = this; const data = this.pinModel.asDataByFields( - ['referer', 'description', 'tags'], + ['referer', 'description', 'tags', 'private'], ); const promise = API.Pin.updateById(this.existedPin.id, data); promise.then( @@ -239,7 +248,7 @@ export default { promise = API.Pin.createFromURL(data); } else { const data = this.pinModel.asDataByFields( - ['referer', 'description', 'tags'], + ['referer', 'description', 'tags', 'private'], ); data.image_by_id = this.formUpload.imageId; promise = API.Pin.createFromUploaded(data); From 335cc3a7547e2b35f9b277745ae9cab2add06884 Mon Sep 17 00:00:00 2001 From: winkidney Date: Tue, 11 Feb 2020 15:40:02 +0800 Subject: [PATCH 05/11] Fix: flake8 --- core/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/core/views.py b/core/views.py index e97a0a2..d75e4de 100644 --- a/core/views.py +++ b/core/views.py @@ -1,4 +1,3 @@ -from django.db.models import Q from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page from django_filters.rest_framework import DjangoFilterBackend From d303e7aa26aad99f8a4ebd2daf3f142bd8ca8f91 Mon Sep 17 00:00:00 2001 From: winkidney Date: Tue, 11 Feb 2020 16:24:24 +0800 Subject: [PATCH 06/11] Feature: Add permission tests for pin-pravicy option --- core/tests/api.py | 82 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 5 deletions(-) diff --git a/core/tests/api.py b/core/tests/api.py index fa93080..ce10819 100644 --- a/core/tests/api.py +++ b/core/tests/api.py @@ -5,11 +5,17 @@ import mock from rest_framework import status from rest_framework.test import APITestCase -from django_images.models import Thumbnail from taggit.models import Tag from .helpers import create_image, create_user, create_pin -from core.models import Pin, Image +from core.models import Pin, Image, Board + + +def _teardown_models(): + Pin.objects.all().delete() + Image.objects.all().delete() + Tag.objects.all().delete() + Board.objects.all().delete() def mock_requests_get(url, **kwargs): @@ -29,6 +35,73 @@ class ImageTests(APITestCase): self.assertEqual(response.status_code, 403, response.data) +class PrivacyTests(APITestCase): + _JSON_TYPE = "application/json" + + def setUp(self): + super(PrivacyTests, self).setUp() + self.owner = create_user("default") + self.non_owner = create_user("non_owner") + + with mock.patch('requests.get', mock_requests_get): + image = Image.objects.create_for_url('http://a.com/b.png') + self.private_pin = Pin.objects.create( + submitter=self.owner, + image=image, + private=True, + ) + self.private_pin_url = reverse("pin-detail", kwargs={"pk": self.private_pin.pk}) + + self.board = Board.objects.create(name="test_board", submitter=self.owner) + self.board.pins.add(self.private_pin) + self.board.save() + self.board_url = reverse("board-detail", kwargs={"pk": self.board.pk}) + + def tearDown(self): + _teardown_models() + + def test_should_non_owner_and_anonymous_user_has_no_permission_to_list_private_pin(self): + resp = self.client.get(reverse("pin-list")) + self.assertEqual(len(resp.data['results']), 0, resp.data) + + self.client.login(username=self.non_owner.username, password='password') + resp = self.client.get(reverse("pin-list")) + self.assertEqual(len(resp.data['results']), 0, resp.data) + + def test_should_non_owner_and_anonymous_user_has_no_permission_to_list_private_pin_in_board(self): + resp = self.client.get(self.board_url) + self.assertEqual(len(resp.data['pins_detail']), 0, resp.data) + self.client.login(username=self.non_owner.username, password='password') + + resp = self.client.get(self.board_url) + self.assertEqual(len(resp.data['pins_detail']), 0, resp.data) + + def test_should_owner_user_has_permission_to_list_private_pin_in_board(self): + self.client.login(username=self.owner.username, password='password') + resp = self.client.get(self.board_url) + self.assertEqual(len(resp.data['pins_detail']), 1, resp.data) + + def test_should_owner_user_has_permission_to_list_private_pin(self): + self.client.login(username=self.owner.username, password='password') + resp = self.client.get(reverse("pin-list")) + self.assertEqual(len(resp.data['results']), 1, resp.data) + + def test_should_owner_has_permission_to_view_private_pin(self): + self.client.login(username=self.owner.username, password='password') + resp = self.client.get(self.private_pin_url) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.data['id'], self.private_pin.id) + + def test_should_anonymous_user_has_no_permission_to_view_private_pin(self): + resp = self.client.get(self.private_pin_url) + self.assertEqual(resp.status_code, 404) + + def test_should_non_owner_has_no_permission_to_view_private_pin(self): + self.client.login(username=self.non_owner.username, password='password') + resp = self.client.get(self.private_pin_url) + self.assertEqual(resp.status_code, 404) + + class PinTests(APITestCase): _JSON_TYPE = "application/json" @@ -38,9 +111,7 @@ class PinTests(APITestCase): self.client.login(username=self.user.username, password='password') def tearDown(self): - Pin.objects.all().delete() - Image.objects.all().delete() - Tag.objects.all().delete() + _teardown_models() @mock.patch('requests.get', mock_requests_get) def test_should_create_pin(self): @@ -49,6 +120,7 @@ class PinTests(APITestCase): referer = 'http://testserver.com/' post_data = { 'url': url, + 'private': False, 'referer': referer, 'description': 'That\'s an Apple!' } From 23aacbc7c02968db7c06a8e7e507d91b9373356e Mon Sep 17 00:00:00 2001 From: winkidney Date: Tue, 11 Feb 2020 16:37:38 +0800 Subject: [PATCH 07/11] Feature: Add private property for board --- core/models.py | 1 + core/serializers.py | 8 ++++++++ core/tests/api.py | 1 - core/views.py | 16 ++++++++++------ 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/core/models.py b/core/models.py index 0568e15..5d8a60b 100644 --- a/core/models.py +++ b/core/models.py @@ -76,6 +76,7 @@ class Board(models.Model): submitter = models.ForeignKey(User) name = models.CharField(max_length=128, blank=False, null=False) + private = models.BooleanField(default=False, blank=False) pins = models.ManyToManyField("Pin", related_name="pins", blank=True) published = models.DateTimeField(auto_now_add=True) diff --git a/core/serializers.py b/core/serializers.py index f53f813..4f160b8 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -18,6 +18,14 @@ def filter_private_pin(request, query): return query.select_related('image', 'submitter') +def filter_private_board(request, query): + if request.user.is_authenticated: + query = query.exclude(~Q(submitter=request.user), private=True) + else: + query = query.exclude(private=True) + return query + + class ThumbnailSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Thumbnail diff --git a/core/tests/api.py b/core/tests/api.py index ce10819..4d0d424 100644 --- a/core/tests/api.py +++ b/core/tests/api.py @@ -36,7 +36,6 @@ class ImageTests(APITestCase): class PrivacyTests(APITestCase): - _JSON_TYPE = "application/json" def setUp(self): super(PrivacyTests, self).setUp() diff --git a/core/views.py b/core/views.py index d75e4de..a23f86a 100644 --- a/core/views.py +++ b/core/views.py @@ -9,7 +9,7 @@ from taggit.models import Tag from core import serializers as api from core.models import Image, Pin, Board from core.permissions import IsOwnerOrReadOnly, OwnerOnlyIfPrivate -from core.serializers import filter_private_pin +from core.serializers import filter_private_pin, filter_private_board class ImageViewSet(mixins.CreateModelMixin, GenericViewSet): @@ -35,20 +35,21 @@ class PinViewSet(viewsets.ModelViewSet): class BoardViewSet(viewsets.ModelViewSet): - queryset = Board.objects.all() serializer_class = api.BoardSerializer filter_backends = (DjangoFilterBackend, OrderingFilter) filter_fields = ("submitter__username", ) ordering_fields = ('-id', ) ordering = ('-id', ) - permission_classes = [IsOwnerOrReadOnly("submitter"), ] + permission_classes = [IsOwnerOrReadOnly("submitter"), OwnerOnlyIfPrivate("submitter")] + + def get_queryset(self): + return filter_private_board(self.request, Board.objects.all()) class BoardAutoCompleteViewSet( mixins.ListModelMixin, viewsets.GenericViewSet, ): - queryset = Board.objects.all() serializer_class = api.BoardAutoCompleteSerializer filter_backends = (DjangoFilterBackend, OrderingFilter) filter_fields = ("submitter__username", ) @@ -56,6 +57,9 @@ class BoardAutoCompleteViewSet( ordering = ('-id', ) pagination_class = None + def get_queryset(self): + return filter_private_board(self.request, Board.objects.all()) + class TagAutoCompleteViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): queryset = Tag.objects.all() @@ -74,6 +78,6 @@ class TagAutoCompleteViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): drf_router = routers.DefaultRouter() drf_router.register(r'pins', PinViewSet, basename="pin") drf_router.register(r'images', ImageViewSet) -drf_router.register(r'boards', BoardViewSet) +drf_router.register(r'boards', BoardViewSet, basename="board") drf_router.register(r'tags-auto-complete', TagAutoCompleteViewSet) -drf_router.register(r'boards-auto-complete', BoardAutoCompleteViewSet) +drf_router.register(r'boards-auto-complete', BoardAutoCompleteViewSet, base_name="board") From c31d93f7979c0ca87e1ef9f94752736bd2efd681 Mon Sep 17 00:00:00 2001 From: winkidney Date: Tue, 11 Feb 2020 17:11:39 +0800 Subject: [PATCH 08/11] Feature: Add tests and migrations for board in backend --- core/migrations/0008_board_private.py | 20 +++++++++++ core/tests/api.py | 49 +++++++++++++++++++++++++-- core/views.py | 1 + 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 core/migrations/0008_board_private.py diff --git a/core/migrations/0008_board_private.py b/core/migrations/0008_board_private.py new file mode 100644 index 0000000..ace315c --- /dev/null +++ b/core/migrations/0008_board_private.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.26 on 2020-02-11 08:38 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0007_pin_private'), + ] + + operations = [ + migrations.AddField( + model_name='board', + name='private', + field=models.BooleanField(default=False), + ), + ] diff --git a/core/tests/api.py b/core/tests/api.py index 4d0d424..28703ab 100644 --- a/core/tests/api.py +++ b/core/tests/api.py @@ -35,10 +35,55 @@ class ImageTests(APITestCase): self.assertEqual(response.status_code, 403, response.data) -class PrivacyTests(APITestCase): +class BoardPrivacyTests(APITestCase): def setUp(self): - super(PrivacyTests, self).setUp() + super(BoardPrivacyTests, self).setUp() + self.owner = create_user("default") + self.non_owner = create_user("non_owner") + + self.private_board = Board.objects.create( + name="test_board", + submitter=self.owner, + private=True, + ) + self.board_url = reverse("board-detail", kwargs={"pk": self.private_board.pk}) + self.boards_url = reverse("board-list") + + def tearDown(self): + _teardown_models() + + def test_should_non_owner_and_anonymous_user_has_no_permission_to_list_private_board(self): + resp = self.client.get(self.boards_url) + self.assertEqual(len(resp.data), 0, resp.data) + + self.client.login(username=self.non_owner.username, password='password') + resp = self.client.get(self.boards_url) + self.assertEqual(len(resp.data), 0, resp.data) + + def test_should_owner_has_permission_to_list_private_board(self): + self.client.login(username=self.non_owner.username, password='password') + resp = self.client.get(self.boards_url) + self.assertEqual(len(resp.data), 0, resp.data) + + def test_should_non_owner_and_anonymous_user_has_no_permission_to_view_private_board(self): + resp = self.client.get(self.board_url) + self.assertEqual(resp.status_code, 404) + + self.client.login(username=self.non_owner.username, password='password') + resp = self.client.get(self.board_url) + self.assertEqual(resp.status_code, 404) + + def test_should_owner_has_permission_to_view_private_board(self): + self.client.login(username=self.owner.username, password='password') + resp = self.client.get(self.board_url) + self.assertEqual(resp.status_code, 200) + + +class PinPrivacyTests(APITestCase): + + def setUp(self): + super(PinPrivacyTests, self).setUp() self.owner = create_user("default") self.non_owner = create_user("non_owner") diff --git a/core/views.py b/core/views.py index a23f86a..277db09 100644 --- a/core/views.py +++ b/core/views.py @@ -56,6 +56,7 @@ class BoardAutoCompleteViewSet( ordering_fields = ('-id', ) ordering = ('-id', ) pagination_class = None + permission_classes = [OwnerOnlyIfPrivate("submitter"), ] def get_queryset(self): return filter_private_board(self.request, Board.objects.all()) From dd763753990053baf6f7dc7c9bf8558639265843 Mon Sep 17 00:00:00 2001 From: winkidney Date: Tue, 11 Feb 2020 17:31:37 +0800 Subject: [PATCH 09/11] Feature: Add private field to board --- core/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/core/serializers.py b/core/serializers.py index 4f160b8..b4a1552 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -169,6 +169,7 @@ class BoardSerializer(serializers.HyperlinkedModelSerializer): settings.DRF_URL_FIELD_NAME, "id", "name", + "private", "pins", "pins_detail", "published", From e2de094ba0c5a60e4b08f5aa5fcb0c6c51a28218 Mon Sep 17 00:00:00 2001 From: winkidney Date: Tue, 11 Feb 2020 17:49:18 +0800 Subject: [PATCH 10/11] Fix: no-changes on board name should be allowed --- core/serializers.py | 5 +++-- pinry-spa/src/components/BoardEdit.vue | 21 +++++++++++++++++++-- pinry-spa/src/components/Boards.vue | 1 + pinry-spa/src/components/api.js | 4 ++-- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/core/serializers.py b/core/serializers.py index b4a1552..76f58e6 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -221,10 +221,11 @@ class BoardSerializer(serializers.HyperlinkedModelSerializer): def update(self, instance: Board, validated_data): pins_to_add = validated_data.pop("pins_to_add", []) pins_to_remove = validated_data.pop("pins_to_remove", []) - if Board.objects.filter( + board = Board.objects.filter( submitter=instance.submitter, name=validated_data.get('name', None) - ).exists(): + ).first() + if board.id != instance.id: raise ValidationError( detail={'name': "Board with this name already exists"} ) diff --git a/pinry-spa/src/components/BoardEdit.vue b/pinry-spa/src/components/BoardEdit.vue index 373368a..5d4e7aa 100644 --- a/pinry-spa/src/components/BoardEdit.vue +++ b/pinry-spa/src/components/BoardEdit.vue @@ -17,6 +17,13 @@ maxlength="128" > + + + + {{ pinModel.form.private.value?"only visible to yourself":"visible to everyone" }} +
@@ -30,6 +37,13 @@ maxlength="128" > + + + + {{ editModel.form.private.value?"only visible to yourself":"visible to everyone" }} +
@@ -56,7 +70,7 @@ import API from './api'; import ModelForm from './utils/ModelForm'; import bus from './utils/bus'; -const fields = ['name']; +const fields = ['name', 'private']; export default { name: 'BoardEditModal', @@ -108,7 +122,10 @@ export default { }, createBoard() { const self = this; - const promise = API.Board.create(this.createModel.form.name.value); + const promise = API.Board.create( + this.createModel.form.name.value, + this.createModel.form.private.value, + ); promise.then( (resp) => { bus.bus.$emit(bus.events.refreshBoards); diff --git a/pinry-spa/src/components/Boards.vue b/pinry-spa/src/components/Boards.vue index c7fda3b..e0341cd 100644 --- a/pinry-spa/src/components/Boards.vue +++ b/pinry-spa/src/components/Boards.vue @@ -76,6 +76,7 @@ function createBoardItem(board) { } boardItem.id = board.id; boardItem.name = board.name; + boardItem.private = board.private; boardItem.total_pins = pins4Board.length; if (previewImage.image.thumbnail.image !== null) { boardItem.preview_image_url = pinHandler.escapeUrl( diff --git a/pinry-spa/src/components/api.js b/pinry-spa/src/components/api.js index 3e07936..d64e46b 100644 --- a/pinry-spa/src/components/api.js +++ b/pinry-spa/src/components/api.js @@ -4,9 +4,9 @@ import storage from './utils/storage'; const API_PREFIX = '/api/v2/'; const Board = { - create(name) { + create(name, private_ = false) { const url = `${API_PREFIX}boards/`; - const data = { name }; + const data = { name, private: private_ }; return new Promise( (resolve, reject) => { axios.post(url, data).then( From 31e4d19b7df5f9cc2802098fc519d7b9d8301f12 Mon Sep 17 00:00:00 2001 From: winkidney Date: Tue, 11 Feb 2020 17:55:30 +0800 Subject: [PATCH 11/11] Feature: Add privacy option to board edit UI --- pinry-spa/src/components/BoardEdit.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pinry-spa/src/components/BoardEdit.vue b/pinry-spa/src/components/BoardEdit.vue index 5d4e7aa..ecb34b3 100644 --- a/pinry-spa/src/components/BoardEdit.vue +++ b/pinry-spa/src/components/BoardEdit.vue @@ -22,7 +22,7 @@ :type="createModel.form.private.type" :message="createModel.form.private.error"> - {{ pinModel.form.private.value?"only visible to yourself":"visible to everyone" }} + {{ createModel.form.private.value?"only visible to yourself":"visible to everyone" }}