Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4a1aca9
Enable admin editing of channels in the UI.
rtibbles Mar 29, 2021
8d8d2c2
Check event has a target before checking the target tagName.
rtibbles Mar 29, 2021
5c54628
Don't pass unsanitized inputs into regex.
rtibbles Mar 29, 2021
6d35dfe
Use 'usage' to ensure polyfills.
rtibbles Mar 29, 2021
3e15315
Merge pull request #3052 from learningequality/dependabot/pip/pygment…
rtibbles Mar 29, 2021
436b202
Remove corejs from tests. Remove proposal only use of lastItem.
rtibbles Mar 30, 2021
e8e5afc
Bump y18n from 3.2.1 to 3.2.2
dependabot[bot] Mar 30, 2021
f43a78c
Merge pull request #3055 from rtibbles/polyfill_usage
jonboiser Mar 30, 2021
3c74632
Merge pull request #3053 from rtibbles/tagname
jonboiser Mar 30, 2021
11c854c
Merge pull request #3057 from learningequality/dependabot/npm_and_yar…
rtibbles Mar 31, 2021
e6aaf0d
Merge pull request #3068 from learningequality/sprint-release-2021-03-29
rtibbles Apr 5, 2021
1751f45
Merge pull request #3072 from learningequality/sprint-release-2021-03-29
rtibbles Apr 6, 2021
016aa65
Merge pull request #3073 from bjester/merge-down-sprint-release-2021-…
rtibbles Apr 10, 2021
48af864
Initial update to tests and bookmark saving
sairina Mar 16, 2021
b709012
Merge pull request #3051 from rtibbles/admin_edit
bjester Apr 12, 2021
cf0a1fa
Add ChannelListPagination class for ChannelViewSet
sairina Apr 7, 2021
b4abe6d
Add test for viewer editing bookmarked public channel
sairina Apr 8, 2021
eb01677
Fix tests and add fix bookmark mods for serializer
sairina Apr 10, 2021
dd077ae
Add docstring to update_from_changes method
sairina Apr 11, 2021
46e83fd
Merge pull request #3054 from rtibbles/admin_channels
bjester Apr 12, 2021
7ddca73
Merge pull request #3070 from sairina/starred
rtibbles Apr 12, 2021
7ca1fb4
Quick change to disable synchronous calculation of resource size
bjester Apr 12, 2021
367e008
Add patch for constant to viewset test
bjester Apr 12, 2021
516d79d
Merge pull request #3090 from bjester/disable-sync-resource-size-calc
bjester Apr 12, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module.exports = {
[
'@babel/preset-env',
{
useBuiltIns: 'entry',
useBuiltIns: 'usage',
corejs: '3',
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@
...mapActions('task', ['deleteTask']),
handleTileClick(e) {
// Ensures that clicking an icon button is not treated the same as clicking the card
if (e.target.tagName !== 'svg' && !this.copying) {
if (e.target && e.target.tagName !== 'svg' && !this.copying) {
this.isTopic ? this.$emit('topicChevronClick') : this.$emit('infoClick');
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ export function getCurrentChannelStagingDiff(state) {
return state.currentChannelStagingDiff;
}

export function canEdit(state, getters) {
export function canEdit(state, getters, rootState, rootGetters) {
return (
getters.currentChannel &&
getters.currentChannel.edit &&
(getters.currentChannel.edit || rootGetters.isAdmin) &&
!getters.currentChannel.ricecooker_version
);
}

// Allow some extra actions for ricecooker channels
export function canManage(state, getters) {
return getters.currentChannel && getters.currentChannel.edit;
export function canManage(state, getters, rootState, rootGetters) {
return getters.currentChannel && (getters.currentChannel.edit || rootGetters.isAdmin);
}

// For the most part, we use !canEdit, but this is a way of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export function searchCatalog(context, params) {
.sort()
.map(key => `${key}=${search[key]}`)
.join('&');

const trackingData = {
total: pageData.count,
matched: pageData.results.map(c => `${c.id} ${c.name}`),
Expand Down
1 change: 0 additions & 1 deletion contentcuration/contentcuration/frontend/shared/app.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'core-js';
import 'regenerator-runtime/runtime';
import Vue from 'vue';
import VueRouter from 'vue-router';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const imageMdToParams = imageMd => {
};

export const paramsToImageMd = ({ src, alt, width, height }) => {
src = src.split('/').lastItem;
src = src.split('/').slice(-1)[0];
if (width && width !== 'auto' && height && height !== 'auto') {
return `![${alt}](${IMAGE_PLACEHOLDER}/${src} =${width}x${height})`;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ export default {
clipboardRootId(state) {
return state.currentUser.clipboard_tree_id;
},
isAdmin(state) {
return state.currentUser.is_admin;
},
},
actions: {
async saveSession(context, currentUser) {
Expand Down
3 changes: 3 additions & 0 deletions contentcuration/contentcuration/tests/utils/test_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,14 @@ def test_missing__too_big__forced(self, cache, helper):
self.node.get_descendant_count.return_value = STALE_MAX_CALCULATION_SIZE + 1
self.assertCalculation(cache, helper, force=True)

@mock.patch("contentcuration.utils.nodes.STALE_MAX_CALCULATION_SIZE", 5000)
def test_missing__small(self, cache, helper):
self.node.get_descendant_count.return_value = 1
cache().get_size.return_value = None
cache().get_modified.return_value = None
self.assertCalculation(cache, helper)

@mock.patch("contentcuration.utils.nodes.STALE_MAX_CALCULATION_SIZE", 5000)
def test_unforced__took_too_long(self, cache, helper):
self.node.get_descendant_count.return_value = 1
cache().get_size.return_value = None
Expand All @@ -115,6 +117,7 @@ def db_get_size():
self.assertIsInstance(report_exception.mock_calls[0][1][0], SlowCalculationError)


@mock.patch("contentcuration.utils.nodes.STALE_MAX_CALCULATION_SIZE", 5000)
class CalculateResourceSizeIntegrationTestCase(BaseTestCase):
"""
Integration test case
Expand Down
94 changes: 94 additions & 0 deletions contentcuration/contentcuration/tests/viewsets/test_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,100 @@ def test_viewer_cannot_update_some_channels(self):
self.assertEqual(models.Channel.objects.get(id=channel1.id).name, new_name)
self.assertNotEqual(models.Channel.objects.get(id=channel2.id).name, new_name)

def test_editor_can_bookmark_channel(self):
user = testdata.user()
channel = models.Channel.objects.create(**self.channel_metadata)
channel.editors.add(user)

self.client.force_authenticate(user=user)
with self.settings(TEST_ENV=False):
# Override test env here to check what will happen in production
response = self.client.post(
self.sync_url,
[generate_update_event(channel.id, CHANNEL, {"bookmark": True})],
format="json",
)
self.assertEqual(response.status_code, 200, response.content)
self.assertTrue(user.bookmarked_channels.filter(id=channel.id).exists())

def test_viewer_can_bookmark_channel(self):
user = testdata.user()
channel = models.Channel.objects.create(**self.channel_metadata)
channel.viewers.add(user)

self.client.force_authenticate(user=user)
with self.settings(TEST_ENV=False):
# Override test env here to check what will happen in production
response = self.client.post(
self.sync_url,
[generate_update_event(channel.id, CHANNEL, {"bookmark": True})],
format="json",
)
self.assertEqual(response.status_code, 200, response.content)
self.assertTrue(user.bookmarked_channels.filter(id=channel.id).exists())

def test_viewer_can_bookmark_public_channel(self):
user = testdata.user()
channel = models.Channel.objects.create(**self.channel_metadata)
channel.viewers.add(user)
channel.public = True
channel.save()

self.client.force_authenticate(user=user)
with self.settings(TEST_ENV=False):
# Override test env here to check what will happen in production
response = self.client.post(
self.sync_url,
[generate_update_event(channel.id, CHANNEL, {
"bookmark": True,
})],
format="json",
)
self.assertEqual(response.status_code, 200, response.content)
self.assertTrue(user.bookmarked_channels.filter(id=channel.id).exists())

def test_anyone_can_bookmark_public_channel(self):
user = testdata.user()
channel = models.Channel.objects.create(**self.channel_metadata)
channel.public = True
channel.save()

self.client.force_authenticate(user=user)
with self.settings(TEST_ENV=False):
# Override test env here to check what will happen in production
response = self.client.post(
self.sync_url,
[generate_update_event(channel.id, CHANNEL, {
"bookmark": True,
})],
format="json",
)
self.assertEqual(response.status_code, 200, response.content)
self.assertTrue(user.bookmarked_channels.filter(id=channel.id).exists())

def test_viewer_cannot_edit_public_bookmarked_channel(self):
user = testdata.user()
new_name = "This is not the old name"
channel = models.Channel.objects.create(**self.channel_metadata)
channel.viewers.add(user)
channel.public = True
channel.save()

self.client.force_authenticate(user=user)
with self.settings(TEST_ENV=False):
# Override test env here to check what will happen in production
response = self.client.post(
self.sync_url,
[generate_update_event(channel.id, CHANNEL, {
"bookmark": True,
"name": new_name
})],
format="json",
)
self.assertEqual(response.status_code, 400, response.content)
self.assertTrue(user.bookmarked_channels.filter(id=channel.id).exists())
self.assertNotEqual(models.Channel.objects.get(id=channel.id).name, new_name)

def test_delete_channel(self):
user = testdata.user()
channel = models.Channel.objects.create(**self.channel_metadata)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import uuid

import mock
import pytest
from django.conf import settings
from django.core.management import call_command
Expand Down Expand Up @@ -1531,6 +1532,7 @@ def test_delete_orphanage_root(self):
except models.ContentNode.DoesNotExist:
self.fail("Orphanage root was deleted")

@mock.patch("contentcuration.utils.nodes.STALE_MAX_CALCULATION_SIZE", 5000)
def test_resource_size(self):
user = testdata.user()
channel = testdata.channel()
Expand Down
3 changes: 2 additions & 1 deletion contentcuration/contentcuration/utils/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,8 @@ def modified_since(self, compare_datetime):
# return result['modified_since']


STALE_MAX_CALCULATION_SIZE = 5000
# TODO: clean up sync vs async calculation switching
STALE_MAX_CALCULATION_SIZE = 0
SLOW_UNFORCED_CALC_THRESHOLD = 5


Expand Down
50 changes: 42 additions & 8 deletions contentcuration/contentcuration/viewsets/channel.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import logging
from functools import reduce
from operator import or_

from django.conf import settings
from django.db.models import Exists
Expand All @@ -18,6 +20,7 @@
from rest_framework import serializers
from rest_framework.decorators import detail_route
from rest_framework.exceptions import ValidationError
from rest_framework.pagination import PageNumberPagination
from rest_framework.permissions import AllowAny
from rest_framework.permissions import IsAdminUser
from rest_framework.permissions import IsAuthenticated
Expand All @@ -41,7 +44,6 @@
from contentcuration.viewsets.base import ReadOnlyValuesViewset
from contentcuration.viewsets.base import RequiredFilterSet
from contentcuration.viewsets.base import ValuesViewset
from contentcuration.viewsets.common import CatalogPaginator
from contentcuration.viewsets.common import ChangeEventMixin
from contentcuration.viewsets.common import ContentDefaultsSerializer
from contentcuration.viewsets.common import JSONFieldDictSerializer
Expand All @@ -52,11 +54,16 @@
from contentcuration.viewsets.sync.utils import generate_update_event


class ChannelListPagination(PageNumberPagination):
page_size = None
page_size_query_param = "page_size"
max_page_size = 1000


class CatalogListPagination(CachedListPagination):
page_size = None
page_size_query_param = "page_size"
max_page_size = 1000
django_paginator_class = CatalogPaginator


primary_token_subquery = Subquery(
Expand Down Expand Up @@ -315,7 +322,6 @@ def update(self, instance, validated_data):
instance.bookmarked_by.add(user_id)
elif bookmark is not None:
instance.bookmarked_by.remove(user_id)

return super(ChannelSerializer, self).update(instance, validated_data)


Expand Down Expand Up @@ -384,7 +390,7 @@ class ChannelViewSet(ChangeEventMixin, ValuesViewset):
permission_classes = [IsAuthenticated]
serializer_class = ChannelSerializer
filter_backends = (DjangoFilterBackend,)
pagination_class = CatalogListPagination
pagination_class = ChannelListPagination
filter_class = ChannelFilter

field_map = channel_field_map
Expand Down Expand Up @@ -424,6 +430,31 @@ def annotate_queryset(self, queryset):
)
return queryset

def update_from_changes(self, changes):
"""
If a channel can be bookmarked, changes from bookmarking are addressed in this
method before the `update_from_changes` method in `UpdateModelMixin`.
"""
for change in changes:
if 'bookmark' in change["mods"].keys():
keys = [change["key"] for change in changes]
queryset = self.filter_queryset_from_keys(
self.get_queryset(), keys
).order_by()
instance = queryset.get(**dict(self.values_from_key(change["key"])))
bookmark = {k: v for k, v in change['mods'].items() if k == 'bookmark'}
other_mods = {k: v for k, v in change['mods'].items() if k != 'bookmark'}
change["mods"] = bookmark
serializer = self.get_serializer(
instance, data=self._map_update_change(change), partial=True
)
if serializer.is_valid():
self.perform_update(serializer)
change["mods"] = other_mods

changes = [change for change in changes if change['mods']]
return super(ChannelViewSet, self).update_from_changes(changes)

@detail_route(methods=["post"])
def publish(self, request, pk=None):
if not pk:
Expand Down Expand Up @@ -566,16 +597,19 @@ def complete_annotations(self, queryset):

class AdminChannelFilter(BaseChannelFilter):
def filter_keywords(self, queryset, name, value):
regex = r"^(" + "|".join(value.split(" ")) + ")$"
keywords = value.split(" ")
editors_first_name = reduce(or_, (Q(editors__first_name__icontains=k) for k in keywords))
editors_last_name = reduce(or_, (Q(editors__last_name__icontains=k) for k in keywords))
editors_email = reduce(or_, (Q(editors__email__icontains=k) for k in keywords))
return queryset.annotate(primary_token=primary_token_subquery,).filter(
Q(name__icontains=value)
| Q(pk__istartswith=value)
| Q(primary_token=value.replace("-", ""))
| (
Q(editors__first_name__iregex=regex)
& Q(editors__last_name__iregex=regex)
editors_first_name
& editors_last_name
)
| Q(editors__email__iregex=regex)
| editors_email
)


Expand Down
1 change: 0 additions & 1 deletion jest_config/setup.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'core-js';
import 'regenerator-runtime/runtime';
import * as Aphrodite from 'aphrodite';
import * as AphroditeNoImportant from 'aphrodite/no-important';
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -20738,9 +20738,9 @@ xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==

y18n@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
version "3.2.2"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"
integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==

y18n@^4.0.0:
version "4.0.0"
Expand Down