Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Kontr 2.0
Portal API Backend
Commits
a431272d
Commit
a431272d
authored
Apr 27, 2018
by
Barbora Kompišová
Browse files
documentation, refactor
parent
55464f6a
Changes
26
Hide whitespace changes
Inline
Side-by-side
portal/database/models.py
View file @
a431272d
import
uuid
import
enum
import
datetime
import
pytz
from
flask_sqlalchemy
import
BaseQuery
from
sqlalchemy
import
event
from
sqlalchemy.ext.hybrid
import
hybrid_property
from
sqlalchemy.orm
import
aliased
from
typing
import
List
from
werkzeug.security
import
generate_password_hash
,
check_password_hash
from
portal.database
import
SubmissionState
...
...
@@ -15,17 +14,17 @@ from portal.database.mixins import EntityBase
from
portal.database.exceptions
import
PortalDbError
from
portal.tools
import
time
# uuid as primary key source:
# https://stackoverflow.com/questions/36806403/cant-render-element-of-type-class-sqlalchemy-dialects-postgresql-base-uuid
from
portal.tools.time
import
normalize_time
def
_repr
(
instance
):
no_include
=
{
'password_hash'
,
'review'
,
'course'
,
'user'
,
'project'
,
'group'
,
'role'
,
'review_items'
}
result
=
f
"
{
instance
.
__class__
.
__name__
}
: "
for
key
,
value
in
vars
(
instance
).
items
():
if
not
key
.
startswith
(
"_"
)
and
key
not
in
(
'password_hash'
,
'review'
,
'course'
,
'user'
,
'project'
,
'group'
,
'role'
,
'review_items'
):
if
not
key
.
startswith
(
"_"
)
and
key
not
in
no_include
:
result
+=
f
"
{
key
}
=
{
value
}
"
return
result
...
...
@@ -46,14 +45,13 @@ class User(db.Model, EntityBase):
def
set_password
(
self
,
password
:
str
):
"""Sets password for the user
Args:
password(str): Unhashed password
"""
self
.
password_hash
=
generate_password_hash
(
password
)
def
verify_password
(
self
,
password
:
str
)
->
bool
:
"""
"""
Verifies user's password
Args:
password(str): Unhashed password
...
...
@@ -64,7 +62,7 @@ class User(db.Model, EntityBase):
@
property
def
courses
(
self
)
->
list
:
"""Gets courses the users is
signed in
"""Gets courses the users is
a part of
Returns(list): List of courses
"""
...
...
@@ -99,11 +97,11 @@ class User(db.Model, EntityBase):
.
join
(
Role
.
users
).
filter
(
User
.
id
==
self
.
id
)
def
query_groups_in_course
(
self
,
course
:
'Course'
,
project
:
'Project'
=
None
)
->
BaseQuery
:
"""Queries user's groups for course and project
(optional)
"""Queries user's groups for course and project
Args:
course(Course): Course instance
project(Project): Project Instance
project(Project): Project Instance
(optional)
Returns(BaseQuery): BaseQuery that can be executed
"""
...
...
@@ -114,11 +112,11 @@ class User(db.Model, EntityBase):
return
base_query
.
join
(
Group
.
users
).
filter
(
User
.
id
==
self
.
id
)
def
get_groups_in_course
(
self
,
course
:
'Course'
,
project
:
'Project'
=
None
)
->
list
:
"""Ge
r
s user's groups for course and project(optional)
"""Ge
t
s user's groups for course and project
(optional)
Args:
course(Course): Course instance
project(Project): Project Instance
project(Project): Project Instance
(optional)
Returns(list): List of groups
"""
...
...
@@ -155,15 +153,33 @@ class User(db.Model, EntityBase):
role
=
role
).
all
()
def
query_permissions_for_course
(
self
,
course
:
'Course'
):
permissions
=
RolePermissions
.
query
.
join
(
RolePermissions
.
role
)
\
"""Returns the query for users permissions in the course
Args:
course(Course): Course instance
Returns: Query for the permissions
"""
permissions
=
RolePermissions
.
query
.
join
(
RolePermissions
.
role
)
\
.
filter_by
(
course
=
course
)
\
.
join
(
Role
.
users
).
filter
(
User
.
id
==
self
.
id
)
return
permissions
def
get_permissions_for_course
(
self
,
course
:
'Course'
):
def
get_permissions_for_course
(
self
,
course
:
'Course'
)
->
List
[
'RolePermissions'
]:
"""Gets list of permissions for the course
Args:
course(Course): Course instance
Returns(list[Permissions]): List of the Permissions
"""
return
self
.
query_permissions_for_course
(
course
=
course
).
all
()
def
__init__
(
self
,
uco
,
email
,
username
,
is_admin
=
False
)
->
None
:
def
__init__
(
self
,
uco
:
int
=
None
,
email
:
str
=
None
,
username
:
str
=
None
,
is_admin
:
bool
=
False
)
->
None
:
"""Creates user instance
Args:
uco(int): User's UCO (school identifier)
email(str): User's Email
username(str): Username
is_admin(bool): Whether user is admin
"""
self
.
uco
=
uco
self
.
email
=
email
self
.
username
=
username
...
...
@@ -187,7 +203,8 @@ class Course(db.Model, EntityBase):
notes_access_token
=
db
.
Column
(
db
.
String
(
100
))
# TODO: check length
# Info on cascades: http://docs.sqlalchemy.org/en/latest/orm/cascades.html
# Cascades here simulate a composition - roles, groups and projects exist only when associated to a course
# Cascades here simulate a composition - roles, groups and projects
# exist only when associated with a course
roles
=
db
.
relationship
(
"Role"
,
back_populates
=
"course"
,
cascade
=
"all, delete-orphan"
,
passive_deletes
=
True
)
groups
=
db
.
relationship
(
"Group"
,
back_populates
=
"course"
,
cascade
=
"all, delete-orphan"
,
...
...
@@ -195,7 +212,12 @@ class Course(db.Model, EntityBase):
projects
=
db
.
relationship
(
"Project"
,
back_populates
=
"course"
,
cascade
=
"all, delete-orphan"
,
passive_deletes
=
True
)
def
__init__
(
self
,
name
,
codename
)
->
None
:
def
__init__
(
self
,
name
:
str
=
None
,
codename
:
str
=
None
)
->
None
:
"""Creates course instance
Args:
name(str): Course name
codename(str): Course codename
"""
self
.
name
=
name
self
.
codename
=
codename
...
...
@@ -205,13 +227,23 @@ class Course(db.Model, EntityBase):
def
__eq__
(
self
,
other
):
return
self
.
id
==
other
.
id
def
get_users_by_role
(
self
,
role
:
'Role'
):
def
get_users_by_role
(
self
,
role
:
'Role'
)
->
List
[
'User'
]:
"""Gets all users in the course based on their role
Args:
role(Role): The role to filter by
Returns(list[User]): List of users that have the role
"""
return
User
.
query
.
join
(
User
.
roles
)
\
.
filter
((
Role
.
id
==
role
.
id
)
&
(
Role
.
course
==
self
))
.
filter
((
Role
.
id
==
role
.
id
)
&
(
Role
.
course
==
self
))
.
all
()
def
get_users_by_group
(
self
,
group
:
'Group'
):
def
get_users_by_group
(
self
,
group
:
'Group'
)
->
List
[
'User'
]:
"""Gets all users in the group
Args:
group(Group): The group to find users in
Returns(list[User]): List of users that are in the group
"""
return
User
.
query
.
join
(
User
.
groups
)
\
.
filter
((
Group
.
id
==
group
.
id
)
&
(
Role
.
course
==
self
))
.
filter
((
Group
.
id
==
group
.
id
)
&
(
Role
.
course
==
self
))
.
all
()
class
ProjectState
(
enum
.
Enum
):
...
...
@@ -239,7 +271,14 @@ class Project(db.Model, EntityBase):
)
def
state
(
self
,
timestamp
=
time
.
NOW
())
->
ProjectState
:
if
not
(
self
.
config
.
submissions_allowed_from
or
self
.
config
.
submissions_allowed_to
or
self
.
config
.
archive_from
):
"""Gets project state based on the timestamp
Args:
timestamp: Time for which the state should be calculated
Returns:
"""
if
not
(
self
.
config
.
submissions_allowed_from
or
self
.
config
.
submissions_allowed_to
or
self
.
config
.
archive_from
):
return
ProjectState
.
INACTIVE
if
self
.
config
.
submissions_allowed_from
<=
timestamp
<
self
.
config
.
submissions_allowed_to
:
return
ProjectState
.
ACTIVE
...
...
@@ -249,14 +288,39 @@ class Project(db.Model, EntityBase):
return
ProjectState
.
INACTIVE
def
set_config
(
self
,
**
kwargs
):
"""Sets project configuration
Args:
test_files_source(str): Repository URL
file_whitelist(str): Filter string for whitelist
pre_submit_script(str): Pre submit script used in the submissions processing
post_submit_script(str): Post submit script used in the submissions processing
submission_parameters(str):
Schema that defines all the parameters that should be passed to submission
submission_scheduler_config(str):
Configuration for the submissions scheduler
submissions_allowed_from(time):
Time from all the submissions are allowed from
submissions_allowed_to(time):
Time to which all submissions are allowed to
archive_from(time):
Archive all submissions from
"""
for
k
,
w
in
kwargs
.
items
():
if
k
in
(
'test_files_source'
,
'file_whitelist'
,
'pre_submit_script'
,
'post_submit_script'
,
'submission_parameters'
,
'submission_scheduler_config'
,
'submissions_allowed_from'
,
'test_files_source'
,
'file_whitelist'
,
'pre_submit_script'
,
'post_submit_script'
,
'submission_parameters'
,
'submission_scheduler_config'
,
'submissions_allowed_from'
,
'submissions_allowed_to'
,
'archive_from'
):
setattr
(
self
.
config
,
k
,
w
)
def
__init__
(
self
,
course
,
name
,
test_files_source
)
->
None
:
def
__init__
(
self
,
course
:
Course
,
name
:
str
=
None
,
test_files_source
:
str
=
None
):
"""Creates instance of the project
Args:
course(Course): Course instance
name(str): Project name
test_files_source(str): Url to test files
"""
self
.
course
=
course
self
.
name
=
name
self
.
config
=
ProjectConfig
(
project
=
self
,
test_files_source
=
test_files_source
)
...
...
@@ -333,7 +397,12 @@ class ProjectConfig(db.Model, EntityBase):
raise
PortalDbError
()
self
.
_archive_from
=
time
.
strip_seconds
(
archive_from
)
def
__init__
(
self
,
project
,
test_files_source
)
->
None
:
def
__init__
(
self
,
project
:
Project
,
test_files_source
:
str
=
None
):
"""Creates instance of the project's config
Args:
project(Project): Project instance
test_files_source: Source files
"""
self
.
project
=
project
self
.
test_files_source
=
test_files_source
...
...
@@ -358,6 +427,10 @@ class Role(db.Model, EntityBase):
passive_deletes
=
True
,
uselist
=
False
)
def
set_permissions
(
self
,
**
kwargs
):
"""Sets permissions for the role
Args:
**kwargs(dict): Permissions
"""
for
k
,
w
in
kwargs
.
items
():
if
hasattr
(
self
.
permissions
,
k
)
and
k
not
in
(
'id'
,
'role_id'
,
'role'
):
setattr
(
self
.
permissions
,
k
,
w
)
...
...
@@ -366,7 +439,12 @@ class Role(db.Model, EntityBase):
db
.
UniqueConstraint
(
'course_id'
,
'name'
,
name
=
'course_unique_name'
),
)
def
__init__
(
self
,
course
,
name
):
def
__init__
(
self
,
course
:
Course
,
name
:
str
=
None
):
"""Creates instance of the role in a course
Args:
course(Course): Course instance
name(str): Name of the role
"""
self
.
course
=
course
self
.
name
=
name
self
.
permissions
=
RolePermissions
(
self
)
...
...
@@ -418,7 +496,11 @@ class RolePermissions(db.Model, EntityBase):
write_reviews_group
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
write_reviews_own
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
def
__init__
(
self
,
role
):
def
__init__
(
self
,
role
:
Role
):
"""Creates instance of the permissions for the role
Args:
role:
"""
self
.
role
=
role
def
__repr__
(
self
):
...
...
@@ -441,7 +523,12 @@ class Group(db.Model, EntityBase):
db
.
UniqueConstraint
(
'course_id'
,
'name'
,
name
=
'course_unique_name'
),
)
def
__init__
(
self
,
course
,
name
)
->
None
:
def
__init__
(
self
,
course
:
Course
,
name
:
str
=
None
):
"""Creates instance of the group
Args:
course(Course): Course instance
name(str): Name of the role
"""
self
.
course
=
course
self
.
name
=
name
...
...
@@ -471,7 +558,15 @@ class Submission(db.Model, EntityBase):
# open to extension (state transition validation, ...)
self
.
state
=
new_state
def
__init__
(
self
,
user
,
project
,
parameters
,
review
=
None
):
def
__init__
(
self
,
user
:
User
,
project
:
Project
,
parameters
:
str
=
None
,
review
:
'Review'
=
None
):
"""Creates a new submission instance
Args:
user(User): User instance
project(Project): Project instance
parameters(str): Parameters for the submission
review(Review): Review instance
"""
self
.
user
=
user
self
.
project
=
project
self
.
review
=
review
...
...
@@ -493,7 +588,11 @@ class Review(db.Model, EntityBase):
review_items
=
db
.
relationship
(
"ReviewItem"
,
back_populates
=
"review"
,
cascade
=
"all, delete-orphan"
,
passive_deletes
=
True
)
def
__init__
(
self
,
submission
):
def
__init__
(
self
,
submission
:
Submission
):
"""Creates a review for the submission
Args:
submission(Submission):
"""
self
.
submission
=
submission
def
__repr__
(
self
):
...
...
@@ -512,10 +611,18 @@ class ReviewItem(db.Model, EntityBase):
user
=
db
.
relationship
(
"User"
,
uselist
=
False
,
back_populates
=
"review_items"
)
# the review item's author
content
=
db
.
Column
(
db
.
Text
)
file
=
db
.
Column
(
db
.
String
(
100
),
nullable
=
False
)
file
=
db
.
Column
(
db
.
String
(
256
),
nullable
=
False
)
line
=
db
.
Column
(
db
.
Integer
,
nullable
=
False
)
def
__init__
(
self
,
user
,
review
,
file
,
line
,
content
):
def
__init__
(
self
,
user
:
User
,
review
:
Review
,
file
:
str
,
line
:
int
,
content
:
str
):
"""Creates a review item in the review
Args:
user(User): The author of the review item
review(Review): Review the item belongs to
file(str): File the review item binds to
line(int): Line of the file
content: Review item content (text)
"""
self
.
review
=
review
self
.
user
=
user
self
.
file
=
file
...
...
@@ -535,6 +642,24 @@ class Component(db.Model, EntityBase):
name
=
db
.
Column
(
db
.
String
(
30
),
nullable
=
False
,
unique
=
True
)
secret
=
db
.
Column
(
db
.
String
(
250
))
ip_address
=
db
.
Column
(
db
.
String
(
50
))
type
=
db
.
Column
(
db
.
String
(
25
))
notes
=
db
.
Column
(
db
.
Text
)
def
__init__
(
self
,
name
:
str
=
None
,
secret
:
str
=
None
,
ip_address
:
str
=
None
,
type
:
str
=
None
,
notes
:
str
=
None
):
"""Creates component
Args:
name(str): Name of the component
secret(str): Component's secret key
ip_address(str): Component's IP address
type(str): Type of the component
notes(str): Additional notes for the component
"""
self
.
name
=
name
self
.
secret
=
secret
self
.
ip_address
=
ip_address
self
.
notes
=
notes
self
.
type
=
type
# source: http://docs.sqlalchemy.org/en/latest/orm/events.html
...
...
portal/rest/__init__.py
View file @
a431272d
...
...
@@ -173,6 +173,8 @@ class ComponentSchema(Schema):
name
=
fields
.
Str
()
access_token
=
fields
.
Str
()
ip_address
=
fields
.
Str
()
type
=
fields
.
Str
()
notes
=
fields
.
Str
()
class
CourseImportConfigSchema
(
Schema
):
...
...
portal/rest/components/components.py
View file @
a431272d
import
logging
from
flask
import
Blueprint
from
flask_jwt_extended
import
jwt_required
from
flask_restful
import
Api
,
Resource
from
portal.rest
import
component_schema
,
rest_helpers
from
portal.service
import
permissions
,
auth
from
portal.service.auth
import
find_client
from
portal.service.components
import
create_component
,
delete_component
,
update_component
,
\
find_all_components
from
portal.service.errors
import
ForbiddenError
from
portal.service.general
import
find_component
from
portal.tools.decorators
import
error_handler
components
=
Blueprint
(
'components'
,
__name__
)
components_api
=
Api
(
components
)
log
=
logging
.
getLogger
(
__name__
)
class
ComponentList
(
Resource
):
@
error_handler
@
jwt_required
def
get
(
self
):
client
=
auth
.
find_client
()
# authorization
if
not
permissions
.
check_sysadmin
(
client
):
raise
ForbiddenError
(
uid
=
client
.
id
)
components_list
=
find_all_components
()
return
component_schema
.
dump
(
components_list
),
200
@
error_handler
@
jwt_required
def
post
(
self
):
client
=
auth
.
find_client
()
# authorization
if
not
permissions
.
check_sysadmin
(
client
):
raise
ForbiddenError
(
uid
=
client
.
id
)
data
=
rest_helpers
.
parse_request_data
(
component_schema
,
action
=
'create'
,
resource
=
'component'
)
new_component
=
create_component
(
data
)
return
component_schema
.
dump
(
new_component
)[
0
],
201
class
ComponentResource
(
Resource
):
@
error_handler
@
jwt_required
def
get
(
self
,
cid
):
client
=
find_client
()
if
not
permissions
.
check_sysadmin
(
client
):
raise
ForbiddenError
(
uid
=
client
.
id
)
component
=
find_component
(
cid
)
return
component_schema
.
dump
(
component
)[
0
],
200
@
error_handler
@
jwt_required
def
delete
(
self
,
cid
):
client
=
find_client
()
# authorization
if
not
permissions
.
check_sysadmin
(
client
):
raise
ForbiddenError
(
uid
=
client
.
id
)
component
=
find_component
(
cid
)
delete_component
(
component
)
return
''
,
204
@
error_handler
@
jwt_required
def
put
(
self
,
cid
):
client
=
find_client
()
# authorization
if
not
permissions
.
check_sysadmin
(
client
):
raise
ForbiddenError
(
uid
=
client
.
id
)
data
=
rest_helpers
.
parse_request_data
(
component_schema
,
action
=
'update'
,
resource
=
'component'
)
component
=
find_component
(
cid
)
update_component
(
component
,
data
)
return
''
,
204
components_api
.
add_resource
(
ComponentList
,
''
)
components_api
.
add_resource
(
ComponentResource
,
'/<string:cid>'
)
portal/rest/courses/courses.py
View file @
a431272d
import
logging
from
flask
import
Blueprint
,
request
from
flask
import
Blueprint
from
flask_jwt_extended
import
jwt_required
from
flask_restful
import
Api
,
Resource
from
portal.database.models
import
Course
from
portal.rest
import
course_schema
,
courses_schema
,
course_import_schema
from
portal.rest
import
course_schema
,
courses_schema
,
course_import_schema
,
rest_helpers
from
portal.service.courses
import
delete_course
,
update_course
,
create_course
,
\
update_notes_token
,
copy_course
,
filter_course_dump
update_notes_token
,
copy_course
,
filter_course_dump
,
find_all_courses
from
portal.service.general
import
find_course
from
portal.service.errors
import
PortalAPIError
,
ForbiddenError
from
portal.service.errors
import
ForbiddenError
from
portal.service.permissions
import
check_client
from
portal.service.auth
import
find_client
from
portal.tools.decorators
import
error_handler
...
...
@@ -46,11 +45,11 @@ class CourseResource(Resource):
@
jwt_required
def
delete
(
self
,
cid
):
client
=
find_client
()
course
=
find_course
(
cid
)
# authorization
if
not
permissions
.
check_sysadmin
(
client
):
raise
ForbiddenError
(
uid
=
client
.
id
)
course
=
find_course
(
cid
)
delete_course
(
course
)
return
''
,
204
...
...
@@ -63,11 +62,9 @@ class CourseResource(Resource):
if
not
(
check_client
(
client
=
client
,
course
=
course
,
permissions
=
[
'update_course'
])):
raise
ForbiddenError
(
uid
=
client
.
id
)
json_data
=
request
.
get_json
()
if
not
json_data
:
raise
PortalAPIError
(
400
,
message
=
'No data provided for course update.'
)
data
=
course_schema
.
load
(
json_data
)[
0
]
data
=
rest_helpers
.
parse_request_data
(
schema
=
course_schema
,
action
=
'update'
,
resource
=
'course'
)
update_course
(
course
,
data
)
return
''
,
204
...
...
@@ -81,7 +78,7 @@ class CourseList(Resource):
if
not
permissions
.
check_sysadmin
(
client
):
raise
ForbiddenError
(
uid
=
client
.
id
)
courses_list
=
Course
.
query
.
all
()
courses_list
=
find_all_courses
()
return
courses_schema
.
dump
(
courses_list
)
@
error_handler
...
...
@@ -92,11 +89,7 @@ class CourseList(Resource):
if
not
permissions
.
check_sysadmin
(
client
):
raise
ForbiddenError
(
uid
=
client
.
id
)
json_data
=
request
.
get_json
()
if
not
json_data
:
raise
PortalAPIError
(
400
,
message
=
'No data provided for course creation.'
)
data
=
course_schema
.
load
(
json_data
)[
0
]
data
=
rest_helpers
.
parse_request_data
(
course_schema
,
resource
=
'course'
,
action
=
'create'
)
new_course
=
create_course
(
data
)
return
course_schema
.
dump
(
new_course
)[
0
],
201
...
...
@@ -124,9 +117,7 @@ class CourseNotesToken(Resource):
permissions
=
[
'handle_notes_access_token'
])):
raise
ForbiddenError
(
uid
=
client
.
id
)
json_data
=
request
.
get_json
()
if
not
json_data
:
raise
PortalAPIError
(
400
,
message
=
'No data provided for notes token update.'
)
json_data
=
rest_helpers
.
require_data
(
action
=
'update_notes_token'
,
resource
=
'course'
)
update_notes_token
(
course
,
json_data
)
return
''
,
204
...
...
@@ -142,10 +133,9 @@ class CourseImport(Resource):
if
not
check_client
(
client
=
client
,
course
=
course
,
permissions
=
[
'update_course'
]):
raise
ForbiddenError
(
uid
=
client
.
id
)
json_data
=
request
.
get_json
()
if
not
json_data
:
raise
PortalAPIError
(
400
,
message
=
'No data provided for notes token update.'
)
data
=
course_import_schema
.
load
(
json_data
)[
0
]
data
=
rest_helpers
.
parse_request_data
(
course_import_schema
,
action
=
'import'
,
resource
=
'course'
)
source_course
=
find_course
(
data
[
'source_course'
])
config
=
data
[
'config'
]
...
...
portal/rest/groups/groups.py
View file @
a431272d
...
...
@@ -4,7 +4,6 @@ from flask import Blueprint, request
from
flask_jwt_extended
import
jwt_required
from
flask_restful
import
Api
,
Resource
from
portal.service
import
general
,
auth
from
portal.service.groups
import
delete_group
,
update_group
,
create_group
,
\
update_user_group_membership
,
find_users_in_group_by_role
,
add_single_user_to_group
,
\
...
...
@@ -12,7 +11,7 @@ from portal.service.groups import delete_group, update_group, create_group, \
from
portal.service.permissions
import
check_client
from
portal.tools.decorators
import
error_handler
from
portal.rest
import
group_schema
,
groups_schema
,
users_schema
,
user_list_update_schema
,
\
group_import_schema
group_import_schema
,
rest_helpers
from
portal.service.errors
import
PortalAPIError
,
ForbiddenError
groups
=
Blueprint
(
'groups'
,
__name__
)
...
...
@@ -27,11 +26,13 @@ class GroupResource(Resource):
def
get
(
self
,
cid
,
gid
):
client
=
auth
.
find_client
()
course
=
general
.
find_course
(
cid
)
group
=
general
.
find_group
(
course
,
gid
)
# authorization
if
not
check_client
(
client
=
client
,
course
=
course
,
permissions
=
[
'view_course_full'
])
\
or
not
check_client
(
client
=
client
,
course
=
course
,
permissions
=
[
'view_course_limited'
]):
or
not
check_client
(
client
=
client
,
course
=
course
,
permissions
=
[
'view_course_limited'
]):
raise
ForbiddenError
(
uid
=
client
.
id
)
group
=
general
.
find_group
(
course
,
gid
)
return
group_schema
.
dump
(
group
)
@
error_handler
...
...
@@ -39,11 +40,11 @@ class GroupResource(Resource):
def
delete
(
self
,
cid
,
gid
):
client
=
auth
.
find_client
()
course
=
general
.
find_course
(
cid
)
group
=
general
.
find_group
(
course
,
gid
)
# authorization
if
not
check_client
(
client
=
client
,
course
=
course
,
permissions
=
[
'write_groups'
]):
raise
ForbiddenError
(
uid
=
client
.
id
)
group
=
general
.
find_group
(
course
,
gid
)
delete_group
(
group
)
return
''
,
204
...
...
@@ -52,17 +53,15 @@ class GroupResource(Resource):
def
put
(
self
,
cid
,
gid
):
client
=
auth
.
find_client
()
course
=
general
.
find_course
(
cid
)
group
=
general
.
find_group
(
course
,
gid
)
# authorization
if