Skip to content

Commit

Permalink
Merge pull request Scifabric#1925 from Scifabric/add-pages
Browse files Browse the repository at this point in the history
Add pages to make PYBOSSA a headless CMS
  • Loading branch information
teleyinex authored Jun 6, 2019
2 parents 33ea0e2 + 066723a commit 3010c8c
Show file tree
Hide file tree
Showing 17 changed files with 1,516 additions and 30 deletions.
40 changes: 40 additions & 0 deletions alembic/versions/66ecf0b2aed5_add_pages_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""add pages table
Revision ID: 66ecf0b2aed5
Revises: c7476118715f
Create Date: 2019-06-01 16:10:06.519049
"""

# revision identifiers, used by Alembic.
revision = '66ecf0b2aed5'
down_revision = 'c7476118715f'

import datetime
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import JSON, TIMESTAMP


def make_timestamp():
now = datetime.datetime.utcnow()
return now.isoformat()


def upgrade():
op.create_table(
'pages',
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('slug', sa.Unicode(length=255), nullable=False),
sa.Column('project_id',
sa.Integer,
sa.ForeignKey('project.id', ondelete='CASCADE'),
nullable=False),
sa.Column('created', TIMESTAMP, default=make_timestamp),
sa.Column('info', JSON, nullable=False),
sa.Column('media_url', sa.Text),
)


def downgrade():
op.drop_table('pages')
5 changes: 5 additions & 0 deletions pybossa/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
* task_runs,
* users,
* global_stats,
* helpingmaterial,
* page
"""

Expand Down Expand Up @@ -55,6 +57,7 @@
from result import ResultAPI
from project_stats import ProjectStatsAPI
from helpingmaterial import HelpingMaterialAPI
from page import PageAPI
from pybossa.core import project_repo, task_repo
from pybossa.contributions_guard import ContributionsGuard
from pybossa.auth import jwt_authorize_project
Expand Down Expand Up @@ -102,6 +105,8 @@ def register_api(view, endpoint, url, pk='id', pk_type='int'):
register_api(BlogpostAPI, 'api_blogpost', '/blogpost', pk='oid', pk_type='int')
register_api(HelpingMaterialAPI, 'api_helpingmaterial',
'/helpingmaterial', pk='oid', pk_type='int')
register_api(PageAPI, 'api_page',
'/page', pk='oid', pk_type='int')
register_api(GlobalStatsAPI, 'api_globalstats', '/globalstats',
pk='oid', pk_type='int')
register_api(FavoritesAPI, 'api_favorites', '/favorites',
Expand Down
17 changes: 12 additions & 5 deletions pybossa/api/api_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
from pybossa.ratelimit import ratelimit
from pybossa.error import ErrorStatus
from pybossa.core import project_repo, user_repo, task_repo, result_repo
from pybossa.core import announcement_repo, blog_repo, helping_repo
from pybossa.core import announcement_repo, blog_repo
from pybossa.core import page_repo, helping_repo
from pybossa.core import project_stats_repo
from pybossa.model import DomainObject, announcement
from pybossa.model.task import Task
Expand All @@ -67,14 +68,19 @@
'delete': 'delete_category'},
'Result': {'repo': result_repo, 'filter': 'filter_by', 'get': 'get',
'update': 'update'},
'Announcement': {'repo': announcement_repo, 'filter': 'filter_by', 'get': 'get',
'Announcement': {'repo': announcement_repo,
'filter': 'filter_by', 'get': 'get',
'get_all_announcements': 'get_all_announcements',
'update': 'update', 'save': 'save', 'delete': 'delete'},
'update': 'update',
'save': 'save', 'delete': 'delete'},
'Blogpost': {'repo': blog_repo, 'filter': 'filter_by', 'get': 'get',
'update': 'update', 'save': 'save', 'delete': 'delete'},
'HelpingMaterial': {'repo': helping_repo, 'filter': 'filter_by',
'get': 'get', 'update': 'update',
'save': 'save', 'delete': 'delete'}}
'save': 'save', 'delete': 'delete'},
'Page': {'repo': page_repo, 'filter': 'filter_by',
'get': 'get', 'update': 'update',
'save': 'save', 'delete': 'delete'}}

caching = {'Project': {'refresh': clean_project},
'User': {'refresh': delete_user_summary_id},
Expand All @@ -92,7 +98,8 @@ class APIBase(MethodView):
allowed_classes_upload = ['blogpost',
'helpingmaterial',
'announcement',
'taskrun']
'taskrun',
'page']

def refresh_cache(self, cls_name, oid):
"""Refresh the cache."""
Expand Down
46 changes: 46 additions & 0 deletions pybossa/api/page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -*- coding: utf8 -*-
# This file is part of PYBOSSA.
#
# Copyright (C) 2019 Scifabric LTD.
#
# PYBOSSA is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PYBOSSA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with PYBOSSA. If not, see <http://www.gnu.org/licenses/>.
"""
PYBOSSA api module for domain object Page via an API.
This package adds GET, POST, PUT and DELETE methods for:
* page
"""
from api_base import APIBase
from pybossa.model.page import Page
from flask_login import current_user
from werkzeug.exceptions import BadRequest


class PageAPI(APIBase):

"""Class API for domain object Page."""

reserved_keys = set(['id', 'created'])

__class__ = Page

def _forbidden_attributes(self, data):
for key in data.keys():
if key in self.reserved_keys:
raise BadRequest("Reserved keys in payload")

def _update_object(self, obj):
if not current_user.is_anonymous():
obj.user_id = current_user.id
8 changes: 6 additions & 2 deletions pybossa/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import webhook
import result
import helpingmaterial
import page

assert project
assert projectstats
Expand All @@ -53,6 +54,7 @@
assert auditlog
assert webhook
assert result
assert page


_actions = ['create', 'read', 'update', 'delete']
Expand All @@ -68,7 +70,9 @@
'user': user.UserAuth,
'webhook': webhook.WebhookAuth,
'result': result.ResultAuth,
'helpingmaterial': helpingmaterial.HelpingMaterialAuth}
'helpingmaterial': helpingmaterial.HelpingMaterialAuth,
'page': page.PageAuth
}


def is_authorized(user, action, resource, **kwargs):
Expand Down Expand Up @@ -99,7 +103,7 @@ def _authorizer_for(resource_name):
kwargs.update({'task_repo': task_repo})
if resource_name in ('auditlog', 'blogpost', 'task',
'taskrun', 'webhook', 'result',
'helpingmaterial'):
'helpingmaterial', 'page'):
kwargs.update({'project_repo': project_repo})
if resource_name in ('project', 'task', 'taskrun'):
kwargs.update({'result_repo': result_repo})
Expand Down
73 changes: 73 additions & 0 deletions pybossa/auth/page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# -*- coding: utf8 -*-
# This file is part of PYBOSSA.
#
# Copyright (C) 2019 Scifabric LTD.
#
# PYBOSSA is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PYBOSSA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with PYBOSSA. If not, see <http://www.gnu.org/licenses/>.


class PageAuth(object):
_specific_actions = []

def __init__(self, project_repo):
self.project_repo = project_repo

@property
def specific_actions(self):
return self._specific_actions

def can(self, user, action, page=None, project_id=None):
action = ''.join(['_', action])
return getattr(self, action)(user, page, project_id)

def _create(self, user, page=None, project_id=None):
if user.is_anonymous() or (page is None and project_id is None):
return False
project = self._get_project(page, project_id)
if page is None:
return self._is_admin_or_owner(user, project)
return self._is_admin_or_owner(user, project)

def _read(self, user, page=None, project_id=None):
if page or project_id:
project = self._get_project(page, project_id)
if project:
return (project.published or
self._is_admin_or_owner(user, project))
if user.is_anonymous() or (page is None and project_id is None):
return False
return self._is_admin_or_owner(user, project)
else:
return True

def _update(self, user, page, project_id=None):
project = self._get_project(page, project_id)
if user.is_anonymous():
return False
return self._is_admin_or_owner(user, project)

def _delete(self, user, page, project_id=None):
project = self._get_project(page, project_id)
if user.is_anonymous():
return False
return self._is_admin_or_owner(user, project)

def _get_project(self, page, project_id):
if page is not None:
return self.project_repo.get(page.project_id)
return self.project_repo.get(project_id)

def _is_admin_or_owner(self, user, project):
return (not user.is_anonymous() and
(project.owner_id == user.id or user.admin))
3 changes: 3 additions & 0 deletions pybossa/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ def setup_repositories(app):
from pybossa.repositories import WebhookRepository
from pybossa.repositories import ResultRepository
from pybossa.repositories import HelpingMaterialRepository
from pybossa.repositories import PageRepository
global user_repo
global project_repo
global project_stats_repo
Expand All @@ -208,6 +209,7 @@ def setup_repositories(app):
global webhook_repo
global result_repo
global helping_repo
global page_repo
language = app.config.get('FULLTEXTSEARCH_LANGUAGE')
user_repo = UserRepository(db)
project_repo = ProjectRepository(db)
Expand All @@ -219,6 +221,7 @@ def setup_repositories(app):
webhook_repo = WebhookRepository(db)
result_repo = ResultRepository(db)
helping_repo = HelpingMaterialRepository(db)
page_repo = PageRepository(db)


def setup_error_email(app):
Expand Down
6 changes: 6 additions & 0 deletions pybossa/hateoas.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ def create_links(self, item):
return None, self.create_link(item.id, title='announcement')
elif cls == 'helpingmaterial':
link = self.create_link(item.id, title='helpingmaterial')
if item.project_id is not None:
links = [self.create_link(item.project_id,
title='project', rel='parent')]
return links, link
elif cls == 'page':
link = self.create_link(item.id, title='page')
if item.project_id is not None:
links = [self.create_link(item.project_id,
title='project', rel='parent')]
Expand Down
55 changes: 55 additions & 0 deletions pybossa/model/page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# -*- coding: utf8 -*-
# This file is part of PYBOSSA.
#
# Copyright (C) 2019 Scifabric LTD.
#
# PYBOSSA is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PYBOSSA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with PYBOSSA. If not, see <http://www.gnu.org/licenses/>.

from sqlalchemy import Integer, Text, Float, Unicode
from sqlalchemy.schema import Column, ForeignKey
from sqlalchemy.dialects.postgresql import TIMESTAMP, JSONB
from pybossa.core import db
from pybossa.model import DomainObject, make_timestamp
from sqlalchemy.ext.mutable import MutableDict


class Page(db.Model, DomainObject):
'''A Page objects to provide a headles CMS.'''

__tablename__ = 'pages'

#: Counter.ID
id = Column(Integer, primary_key=True)
#: UTC timestamp when the counter was created.
created = Column(TIMESTAMP, default=make_timestamp)
#: Slug the url for the page object
slug = Column(Unicode(length=255), nullable=False)
#: Project.ID that this counter is associated with.
project_id = Column(Integer, ForeignKey('project.id',
ondelete='CASCADE'),
nullable=False)
#: Info field where it can be stored anything related to it
info = Column(MutableDict.as_mutable(JSONB), default=dict())
#: media URL to store a link to an image or file
media_url = Column(Text)

@classmethod
def public_attributes(self):
"""Return a list of public attributes."""
return ['created', 'id', 'info', 'media_url', 'slug']

@classmethod
def public_info_keys(self):
"""Return a list of public info keys."""
pass
Loading

0 comments on commit 3010c8c

Please sign in to comment.