Skip to content

Commit

Permalink
feat: #825 add toggle button for element.sites endpoints components a…
Browse files Browse the repository at this point in the history
…nd tests
  • Loading branch information
MyPyDavid committed Dec 14, 2023
1 parent 31bcb46 commit 395ce52
Show file tree
Hide file tree
Showing 18 changed files with 208 additions and 24 deletions.
29 changes: 28 additions & 1 deletion rdmo/core/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,15 @@
'bar-user': 404, 'bar-reviewer': 403, 'bar-editor': 204,
'user': 404, 'example-reviewer': 403, 'example-editor': 204,
'anonymous': 401, 'reviewer': 403, 'editor': 204,
}
},
'toggle-site': {
# foo-editor is not permitted to apply own site(foo.com) in test run(example.com)
'foo-user': 403, 'foo-reviewer': 403, 'foo-editor': 403,
# bar-editor is not permitted to apply own site(bar.com) in test run(example.com)
'bar-user': 403, 'bar-reviewer': 403, 'bar-editor': 403,
'user': 403, 'example-reviewer': 403, 'example-editor': 200,
'anonymous': 401, 'reviewer': 403, 'editor': 200,
},
}


Expand Down Expand Up @@ -115,6 +123,25 @@
'example-reviewer': 404, 'example-editor': 404,
}
},
'toggle-site': {
'all-element': {
# foo-editor can not apply own site(foo.com) in test run(example.com)
'foo-reviewer': 403, 'foo-editor': 403,
# bar-editor can not apply own site(bar.com) in test run(example.com)
'bar-reviewer': 403, 'bar-editor': 403,
'example-reviewer': 403, 'example-editor': 200,
},
'foo-element': {
'foo-reviewer': 403, 'foo-editor': 403,
'bar-reviewer': 403, 'bar-editor': 403,
'example-reviewer': 403, 'example-editor': 200,
},
'bar-element': {
'foo-reviewer': 403, 'foo-editor': 403,
'bar-reviewer': 403, 'bar-editor': 403,
'example-reviewer': 403, 'example-editor': 200,
}
}
}


Expand Down
16 changes: 8 additions & 8 deletions rdmo/management/assets/js/actions/elementActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,14 +312,15 @@ export function fetchElementError(error) {

// store element

export function storeElement(elementType, element, back) {
export function storeElement(elementType, element, back, elementAction=null) {
return function(dispatch, getState) {
dispatch(storeElementInit(element))

dispatch(storeElementInit(element, elementAction))

let action
switch (elementType) {
case 'catalogs':
action = () => QuestionsApi.storeCatalog(element)
action = () => QuestionsApi.storeCatalog(element, elementAction)
break

case 'sections':
Expand Down Expand Up @@ -355,11 +356,11 @@ export function storeElement(elementType, element, back) {
break

case 'tasks':
action = () => TasksApi.storeTask(element)
action = () => TasksApi.storeTask(element, elementAction)
break

case 'views':
action = () => ViewsApi.storeView(element)
action = () => ViewsApi.storeView(element, elementAction)
break
}

Expand All @@ -376,8 +377,8 @@ export function storeElement(elementType, element, back) {
}
}

export function storeElementInit(element) {
return {type: 'elements/storeElementInit', element}
export function storeElementInit(element, elementAction) {
return {type: 'elements/storeElementInit', element, elementAction}
}

export function storeElementSuccess(element) {
Expand Down Expand Up @@ -544,7 +545,6 @@ export function deleteElement(elementType, element) {
case 'catalogs':
action = () => QuestionsApi.deleteCatalog(element)
break

case 'sections':
action = () => QuestionsApi.deleteSection(element)
break
Expand Down
8 changes: 6 additions & 2 deletions rdmo/management/assets/js/api/QuestionsApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ class QuestionsApi extends BaseApi {
return this.get(url)
}

static storeCatalog(catalog) {
static storeCatalog(catalog, action) {
if (isNil(catalog.id)) {
return this.post('/api/v1/questions/catalogs/', catalog)
} else {
return this.put(`/api/v1/questions/catalogs/${catalog.id}/`, catalog)
let url = `/api/v1/questions/catalogs/${catalog.id}/`
if (['add-site', 'remove-site'].includes(action)) {
url = `/api/v1/questions/catalog-toggle-site/${catalog.id}/${action}/`
}
return this.put(url, catalog)
}
}

Expand Down
8 changes: 6 additions & 2 deletions rdmo/management/assets/js/api/TasksApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ class TasksApi extends BaseApi {
return this.get(`/api/v1/tasks/tasks/${id}/`)
}

static storeTask(task) {
static storeTask(task, action) {
if (isNil(task.id)) {
return this.post('/api/v1/tasks/tasks/', task)
} else {
return this.put(`/api/v1/tasks/tasks/${task.id}/`, task)
let url = `/api/v1/tasks/tasks/${task.id}/`
if (['add-site', 'remove-site'].includes(action)) {
url = `/api/v1/tasks/task-toggle-site/${task.id}/${action}/`
}
return this.put(url, task)
}
}

Expand Down
8 changes: 6 additions & 2 deletions rdmo/management/assets/js/api/ViewsApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ class ViewsApi extends BaseApi {
return this.get(`/api/v1/views/views/${id}/`)
}

static storeView(view) {
static storeView(view, action) {
if (isNil(view.id)) {
return this.post('/api/v1/views/views/', view)
} else {
return this.put(`/api/v1/views/views/${view.id}/`, view)
let url= `/api/v1/views/views/${view.id}/`
if (['add-site', 'remove-site'].includes(action)) {
url = `/api/v1/views/view-toggle-site/${view.id}/${action}/`
}
return this.put(url, view)
}
}

Expand Down
22 changes: 21 additions & 1 deletion rdmo/management/assets/js/components/common/Links.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,26 @@ LockedLink.propTypes = {
disabled: PropTypes.bool
}

const ToggleCurrentSiteLink = ({ has_current_site, locked, onClick, disabled }) => {
const className = classNames({
'element-btn-link fa': true,
'fa-plus-square-o': !has_current_site,
'fa-minus-square-o': has_current_site,
})
const title = has_current_site ? gettext('Remove your site'): gettext('Add your site')

return <LinkButton className={className} title={locked ? gettext('Locked') : title}
disabled={locked || disabled} onClick={onClick} />
}

ToggleCurrentSiteLink.propTypes = {
has_current_site: PropTypes.bool.isRequired,
locked: PropTypes.bool.isRequired,
onClick: PropTypes.func.isRequired,
disabled: PropTypes.bool
}


const ShowElementsLink = ({ showElements, show, onClick }) => {
const className = classNames({
'element-btn-link fa': true,
Expand Down Expand Up @@ -242,5 +262,5 @@ ShowLink.propTypes = {
onClick: PropTypes.func.isRequired
}

export { EditLink, CopyLink, AddLink, AvailableLink, LockedLink, ShowElementsLink,
export { EditLink, CopyLink, AddLink, AvailableLink, ToggleCurrentSiteLink, LockedLink, ShowElementsLink,
NestedLink, ExportLink, ExtendLink, CodeLink, ErrorLink, WarningLink, ShowLink }
9 changes: 7 additions & 2 deletions rdmo/management/assets/js/components/element/Catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { filterElement } from '../../utils/filter'
import { buildPath } from '../../utils/location'

import { ElementErrors } from '../common/Errors'
import { EditLink, CopyLink, AddLink, AvailableLink, LockedLink, NestedLink,
import { EditLink, CopyLink, AddLink, AvailableLink, ToggleCurrentSiteLink, LockedLink, NestedLink,
ExportLink, CodeLink } from '../common/Links'
import { ReadOnlyIcon } from '../common/Icons'

Expand All @@ -26,12 +26,17 @@ const Catalog = ({ config, catalog, elementActions, display='list',

const toggleAvailable = () => elementActions.storeElement('catalogs', {...catalog, available: !catalog.available })
const toggleLocked = () => elementActions.storeElement('catalogs', {...catalog, locked: !catalog.locked })

const addCurrentSite = () => elementActions.storeElement('catalogs', catalog, null, 'add-site')
const removeCurrentSite = () => elementActions.storeElement('catalogs', catalog, null, 'remove-site')
let has_current_site = config.settings.multisite ? catalog.sites.includes(config.currentSite.id) : true
const createSection = () => elementActions.createElement('sections', { catalog })

const elementNode = (
<div className="element">
<div className="pull-right">
<ToggleCurrentSiteLink has_current_site={has_current_site}
locked={catalog.locked} onClick={has_current_site ? removeCurrentSite : addCurrentSite}
show={config.settings.multisite}/>
<ReadOnlyIcon title={gettext('This catalog is read only')} show={catalog.read_only} />
<NestedLink title={gettext('View catalog nested')} href={nestedUrl} onClick={fetchNested} />
<EditLink title={gettext('Edit catalog')} href={editUrl} onClick={fetchEdit} />
Expand Down
8 changes: 7 additions & 1 deletion rdmo/management/assets/js/components/element/Task.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { filterElement } from '../../utils/filter'
import { buildPath } from '../../utils/location'

import { ElementErrors } from '../common/Errors'
import { EditLink, CopyLink, AvailableLink, LockedLink, ExportLink, CodeLink } from '../common/Links'
import { EditLink, CopyLink, AvailableLink, LockedLink, ExportLink, CodeLink, ToggleCurrentSiteLink } from '../common/Links'
import { ReadOnlyIcon } from '../common/Icons'

const Task = ({ config, task, elementActions, filter=false, filterSites=false, filterEditors=false }) => {
Expand All @@ -21,13 +21,19 @@ const Task = ({ config, task, elementActions, filter=false, filterSites=false, f
const fetchCopy = () => elementActions.fetchElement('tasks', task.id, 'copy')
const toggleAvailable = () => elementActions.storeElement('tasks', {...task, available: !task.available })
const toggleLocked = () => elementActions.storeElement('tasks', {...task, locked: !task.locked })
const addCurrentSite = () => elementActions.storeElement('tasks', task, null, 'add-site')
const removeCurrentSite = () => elementActions.storeElement('tasks', task, null, 'remove-site')
let has_current_site = config.settings.multisite ? task.sites.includes(config.currentSite.id) : true

const fetchCondition = (index) => elementActions.fetchElement('conditions', task.conditions[index])

return showElement && (
<li className="list-group-item">
<div className="element">
<div className="pull-right">
<ToggleCurrentSiteLink has_current_site={has_current_site} locked={task.locked}
onClick={has_current_site ? removeCurrentSite : addCurrentSite}
show={config.settings.multisite}/>
<ReadOnlyIcon title={gettext('This task is read only')} show={task.read_only} />
<EditLink title={gettext('Edit task')} href={editUrl} onClick={fetchEdit} />
<CopyLink title={gettext('Copy task')} href={copyUrl} onClick={fetchCopy} />
Expand Down
8 changes: 7 additions & 1 deletion rdmo/management/assets/js/components/element/View.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { filterElement } from '../../utils/filter'
import { buildPath } from '../../utils/location'

import { ElementErrors } from '../common/Errors'
import { EditLink, CopyLink, AvailableLink, LockedLink, ExportLink, CodeLink } from '../common/Links'
import { EditLink, CopyLink, AvailableLink, LockedLink, ExportLink, CodeLink, ToggleCurrentSiteLink } from '../common/Links'
import { ReadOnlyIcon } from '../common/Icons'

const View = ({ config, view, elementActions, filter=false, filterSites=false, filterEditors=false }) => {
Expand All @@ -20,11 +20,17 @@ const View = ({ config, view, elementActions, filter=false, filterSites=false, f
const fetchCopy = () => elementActions.fetchElement('views', view.id, 'copy')
const toggleAvailable = () => elementActions.storeElement('views', {...view, available: !view.available })
const toggleLocked = () => elementActions.storeElement('views', {...view, locked: !view.locked })
const addCurrentSite = () => elementActions.storeElement('views', view, null, 'add-site')
const removeCurrentSite = () => elementActions.storeElement('views', view, null, 'remove-site')
let has_current_site = config.settings.multisite ? view.sites.includes(config.currentSite.id) : true

return showElement && (
<li className="list-group-item">
<div className="element">
<div className="pull-right">
<ToggleCurrentSiteLink has_current_site={has_current_site} locked={view.locked}
onClick={has_current_site ? removeCurrentSite : addCurrentSite}
show={config.settings.multisite}/>
<ReadOnlyIcon title={gettext('This view is read only')} show={view.read_only} />
<EditLink title={gettext('Edit view')} href={editUrl} onClick={fetchEdit} />
<CopyLink title={gettext('Copy view')} href={copyUrl} onClick={fetchCopy} />
Expand Down
28 changes: 28 additions & 0 deletions rdmo/questions/tests/test_viewset_catalog_multisite.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import pytest

from django.contrib.sites.models import Site
from django.urls import reverse

from ...core.tests import get_obj_perms_status_code
Expand All @@ -10,6 +11,9 @@
from ..models import Catalog
from .test_viewset_catalog import export_formats, urlnames

urlnames['catalog-toggle-site-add-site'] = 'v1-questions:catalog-toggle-site-add-site'
urlnames['catalog-toggle-site-remove-site'] = 'v1-questions:catalog-toggle-site-remove-site'


@pytest.mark.parametrize('username,password', users)
def test_list(db, client, username, password):
Expand Down Expand Up @@ -206,3 +210,27 @@ def test_detail_export(db, client, username, password, export_format):
assert root.tag == 'rdmo'
for child in root:
assert child.tag in ['catalog', 'section', 'page', 'questionset', 'question']


@pytest.mark.parametrize('username,password', users)
@pytest.mark.parametrize('add_or_remove, has_current_site_check', [('add', True), ('remove', False)])
def test_update_catalog_toggle_site(db, client, username, password, add_or_remove, has_current_site_check):
client.login(username=username, password=password)
instances = Catalog.objects.all()
current_site = Site.objects.get_current()

for instance in instances:
before_has_current_site = instance.sites.filter(id=current_site.id).exists()

url = reverse(urlnames[f'catalog-toggle-site-{add_or_remove}-site'], kwargs={'pk': instance.pk})

response = client.put(url, {}, content_type='application/json')
assert response.status_code == get_obj_perms_status_code(instance, username, 'toggle-site'), response.json()
instance.refresh_from_db()
after_has_current_site = instance.sites.filter(id=current_site.id).exists()
if response.status_code == 200:
# check if instance now has the current site or not
assert after_has_current_site is has_current_site_check
else:
# check that the instance was not updated
assert after_has_current_site is before_has_current_site
3 changes: 2 additions & 1 deletion rdmo/questions/urls/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from rest_framework import routers

from ..viewsets import (
CatalogToggleCurrentSiteViewSet,
CatalogViewSet,
PageViewSet,
QuestionSetViewSet,
Expand All @@ -16,13 +17,13 @@

router = routers.DefaultRouter()
router.register(r'catalogs', CatalogViewSet, basename='catalog')
router.register(r'catalog-toggle-site', CatalogToggleCurrentSiteViewSet, basename='catalog-toggle-site')
router.register(r'sections', SectionViewSet, basename='section')
router.register(r'pages', PageViewSet, basename='page')
router.register(r'questionsets', QuestionSetViewSet, basename='questionset')
router.register(r'questions', QuestionViewSet, basename='question')
router.register(r'widgettypes', WidgetTypeViewSet, basename='widgettype')
router.register(r'valuetypes', ValueTypeViewSet, basename='valuetype')

urlpatterns = [
path('', include(router.urls)),
]
11 changes: 10 additions & 1 deletion rdmo/questions/viewsets.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@

from django.db import models

from rest_framework.decorators import action
from rest_framework.filters import SearchFilter
from rest_framework.permissions import IsAuthenticated
from rest_framework.permissions import (
IsAuthenticated,
)
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet

Expand All @@ -13,6 +16,7 @@
from rdmo.core.permissions import HasModelPermission, HasObjectPermission
from rdmo.core.utils import is_truthy, render_to_format
from rdmo.core.views import ChoicesViewSet
from rdmo.management.viewsets import ElementToggleCurrentSiteViewSetMixin

from .models import Catalog, Page, Question, QuestionSet, Section
from .renderers import CatalogRenderer, PageRenderer, QuestionRenderer, QuestionSetRenderer, SectionRenderer
Expand Down Expand Up @@ -117,6 +121,11 @@ def get_export_renderer_context(self, request):
}


class CatalogToggleCurrentSiteViewSet(ElementToggleCurrentSiteViewSetMixin):
serializer_class = CatalogSerializer
viewset_class = CatalogViewSet


class SectionViewSet(ModelViewSet):
permission_classes = (HasModelPermission | HasObjectPermission, )
serializer_class = SectionSerializer
Expand Down

0 comments on commit 395ce52

Please sign in to comment.