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
5e847e32
Unverified
Commit
5e847e32
authored
Sep 04, 2018
by
Peter Stanko
Browse files
Project service
parent
56d4e794
Changes
5
Hide whitespace changes
Inline
Side-by-side
portal/rest/courses.py
View file @
5e847e32
...
...
@@ -7,8 +7,7 @@ from portal.rest import rest_helpers
from
portal.rest.schemas
import
course_import_schema
,
course_schema
,
courses_schema
,
users_schema
from
portal.service
import
permissions
from
portal.service.auth
import
find_client
from
portal.service.courses
import
copy_course
,
create_course
,
delete_course
,
find_all_courses
,
\
get_users_filtered
,
update_course
,
update_notes_token
from
portal.service.courses
import
CourseService
from
portal.service.errors
import
ForbiddenError
,
PortalAPIError
from
portal.service.filters
import
filter_course_dump
from
portal.service.general
import
find_course
...
...
@@ -26,7 +25,7 @@ class CourseList(Resource):
# authorization
permissions
.
PermissionsService
().
require
.
sysadmin
()
courses_list
=
find_all_courses
()
courses_list
=
CourseService
().
find_all_courses
()
return
courses_schema
.
dump
(
courses_list
)
@
jwt_required
...
...
@@ -37,7 +36,7 @@ class CourseList(Resource):
data
=
rest_helpers
.
parse_request_data
(
course_schema
,
resource
=
'course'
,
action
=
'create'
)
new_course
=
create_course
(
**
data
)
new_course
=
CourseService
().
create_course
(
**
data
)
return
course_schema
.
dump
(
new_course
)[
0
],
201
...
...
@@ -70,7 +69,7 @@ class CourseResource(Resource):
def
delete
(
self
,
cid
:
str
):
permissions
.
PermissionsService
().
require
.
sysadmin
()
course
=
find_course
(
cid
)
delete_
course
(
course
)
CourseService
(
course
=
course
).
delete_course
(
)
return
''
,
204
@
jwt_required
...
...
@@ -84,7 +83,7 @@ class CourseResource(Resource):
data
=
rest_helpers
.
parse_request_data
(
schema
=
course_schema
,
action
=
'update'
,
resource
=
'course'
,
partial
=
True
)
update_course
(
course
,
data
)
CourseService
(
course
=
course
).
update_course
(
data
)
return
''
,
204
...
...
@@ -114,7 +113,7 @@ class CourseNotesToken(Resource):
json_data
=
rest_helpers
.
require_data
(
action
=
'update_notes_token'
,
resource
=
'course'
)
update_notes_token
(
course
,
json_data
[
'token'
])
CourseService
(
course
=
course
).
update_notes_token
(
json_data
[
'token'
])
return
''
,
204
...
...
@@ -141,7 +140,7 @@ class CourseImport(Resource):
config
=
data
[
'config'
]
copied_course
=
copy_course
(
source
_course
,
course
,
config
)
copied_course
=
CourseService
(
course
=
course
).
copy
_course
(
course
,
config
)
return
course_schema
.
dump
(
copied_course
)[
0
]
...
...
@@ -158,5 +157,5 @@ class CourseUsers(Resource):
permissions
.
PermissionsService
(
course
=
course
).
require
.
client
([
'view_course_full'
])
group_ids
=
request
.
args
.
getlist
(
'group'
)
role_ids
=
request
.
args
.
getlist
(
'role'
)
users
=
get_users_filtered
(
course
,
group_ids
,
role_ids
)
users
=
CourseService
(
course
=
course
).
get_users_filtered
(
group_ids
,
role_ids
)
return
users_schema
.
dump
(
users
)[
0
]
portal/rest/projects.py
View file @
5e847e32
...
...
@@ -11,9 +11,7 @@ from portal.rest.schemas import config_schema, config_schema_reduced, project_sc
projects_schema
,
submission_create_schema
,
submission_schema
,
submissions_schema
from
portal.service
import
auth
,
general
,
permissions
from
portal.service.errors
import
ForbiddenError
,
SubmissionRefusedError
from
portal.service.projects
import
can_create_submission
,
create_project
,
delete_project
,
\
find_project_submissions
,
list_projects
,
update_project
,
update_project_config
,
\
update_project_test_files_hash
from
portal.service.projects
import
ProjectService
from
portal.service.submissions
import
SubmissionsService
from
portal.tools
import
time
...
...
@@ -31,9 +29,9 @@ class ProjectsList(Resource):
@
projects_namespace
.
response
(
404
,
'Course not found'
)
# @projects_namespace.response(200, 'Projects list', model=projects_schema)
def
get
(
self
,
cid
:
str
):
client
=
auth
.
find_client
()
course
=
general
.
find_course
(
cid
)
return
projects_schema
.
dump
(
list_projects
(
course
,
client
))
projects
=
ProjectService
().
list_projects
(
course
)
return
projects_schema
.
dump
(
projects
)
@
jwt_required
@
projects_namespace
.
response
(
404
,
'Course not found'
)
...
...
@@ -47,7 +45,7 @@ class ProjectsList(Resource):
project_schema
,
action
=
'create'
,
resource
=
'project'
)
new_project
=
create_project
(
course
,
data
)
new_project
=
ProjectService
().
create_project
(
course
,
data
)
return
project_schema
.
dump
(
new_project
)[
0
],
201
...
...
@@ -77,7 +75,7 @@ class ProjectResource(Resource):
permissions
.
PermissionsService
(
course
=
course
).
require
.
update_course
()
project
=
general
.
find_project
(
course
,
pid
)
delete_project
(
project
)
ProjectService
(
project
).
delete_
project
(
)
return
''
,
204
@
jwt_required
...
...
@@ -93,7 +91,7 @@ class ProjectResource(Resource):
)
project
=
general
.
find_project
(
course
,
pid
)
update_project
(
project
,
data
)
ProjectService
(
project
).
update_project
(
project
)
return
''
,
204
...
...
@@ -125,7 +123,7 @@ class ProjectConfigResource(Resource):
)
project
=
general
.
find_project
(
course
,
pid
)
update_project_config
(
project
,
data
)
ProjectService
(
project
).
update_project_config
(
data
)
return
''
,
204
...
...
@@ -142,7 +140,7 @@ class ProjectTestFilesRefresh(Resource):
project
=
general
.
find_project
(
course
,
pid
)
# authorization
permissions
.
PermissionsService
(
course
=
course
).
require
.
write_projects
()
update_project_test_files_hash
(
project
)
ProjectService
(
project
).
update_project_test_files_hash
()
return
''
,
204
...
...
@@ -163,7 +161,7 @@ class ProjectSubmissions(Resource):
user_id
=
request
.
args
.
get
(
'user'
)
project
=
general
.
find_project
(
course
,
pid
)
submissions
=
find_project_submissions
(
project
,
user_id
)
submissions
=
ProjectService
(
project
).
find_project_submissions
(
user_id
)
return
submissions_schema
.
dump
(
submissions
)
@
jwt_required
...
...
@@ -210,7 +208,7 @@ class ProjectTestFiles(Resource):
def
_check_submission_create
(
client
,
project
):
if
project
.
state
(
timestamp
=
time
.
current_time
())
!=
ProjectState
.
ACTIVE
:
raise
SubmissionRefusedError
(
f
"Project
{
project
.
name
}
not active."
)
if
not
can_create_submission
(
client
,
project
):
if
not
ProjectService
(
project
).
can_create_submission
(
client
):
raise
SubmissionRefusedError
(
f
"Submission in project
{
project
.
name
}
already active."
)
...
...
portal/rest/users.py
View file @
5e847e32
...
...
@@ -289,5 +289,5 @@ def get_submissions_based_on_permissions_for_course(client, course_id, project_i
]
perm_service
.
require
.
any_check
(
client
,
*
checks
)
return
users
.
find_submissions_filtered
(
user
,
course_id
=
course_id
,
project_ids
=
project_ids
)
return
UserService
(
user
=
user
)
.
find_submissions_filtered
(
course_id
=
course_id
,
project_ids
=
project_ids
)
portal/service/courses.py
View file @
5e847e32
...
...
@@ -2,7 +2,6 @@
Courses service
"""
import
logging
from
typing
import
List
from
portal
import
logger
...
...
@@ -15,109 +14,104 @@ from portal.service.roles import copy_role
log
=
logger
.
get_logger
(
__name__
)
def
copy_course
(
source
:
Course
,
target
:
Course
,
config
:
dict
)
->
Course
:
"""Copies course and it's other resources (roles, groups, projects)
The other resources that should be copied are specified in the config
Args:
source(Course): From which course the resources will be copied
target(Course): To which course the resources will be copied
config(dict): Configuration to specify which resource should be copied
Returns(Course): Copied course instance (target)
"""
if
config
.
get
(
'roles'
):
for
role
in
source
.
roles
:
copy_role
(
role
,
target
,
with_clients
=
config
[
'roles'
])
if
config
.
get
(
'groups'
):
for
group
in
source
.
groups
:
copy_group
(
group
,
target
,
with_users
=
config
[
'groups'
])
if
config
.
get
(
'projects'
):
for
project
in
source
.
projects
:
copy_project
(
project
,
target
)
general
.
write_entity
(
target
)
log
.
info
(
f
"[IMPORT] From
{
source
.
id
}
to:
{
target
.
id
}
."
)
return
target
def
delete_course
(
course
:
Course
):
"""Deletes course
Args:
course(Course): Course instance
"""
log
.
info
(
f
"[DELETE] Course:
{
course
.
id
}
(
{
course
.
codename
}
)"
)
general
.
delete_entity
(
course
)
def
__set_course_props
(
course
:
Course
,
data
:
dict
):
def
_set_course_props
(
course
:
Course
,
data
:
dict
):
return
general
.
update_entity
(
course
,
data
,
allowed
=
[
'name'
,
'codename'
,
'description'
])
def
update_course
(
course
:
Course
,
data
:
dict
)
->
Course
:
"""Updates course
Args:
course(Course): Course instance
data(dict): Dictionary containing data which should be changed
Returns(Course): Updated course instance
"""
__set_course_props
(
course
=
course
,
data
=
data
)
log
.
info
(
f
"[UPDATE] Course
{
course
.
id
}
(
{
course
.
codename
}
):
{
course
}
."
)
return
course
def
create_course
(
**
data
)
->
Course
:
"""Creates new course
Keyword Args:
name(str): name of the course
codename(str): codename of the course
Returns(Course): Course instance
"""
course
=
Course
()
__set_course_props
(
course
=
course
,
data
=
data
)
log
.
debug
(
f
"[CREATE] Course
{
course
.
id
}
:
{
course
}
"
)
return
course
def
update_notes_token
(
course
:
Course
,
token
:
str
)
->
Course
:
"""Updates notes access token of a course.
Args:
course(Course): Course instance for which the token is set
token(str): The new token
Returns(Course): Course instance
"""
course
.
notes_access_token
=
token
general
.
write_entity
(
course
)
log
.
info
(
f
"[UPDATE] Notes access token (
{
course
.
codename
}
) [
{
course
.
id
}
]:
{
token
}
"
)
return
course
def
find_all_courses
()
->
List
[
Course
]:
"""Find all courses
Returns(list): List of courses
"""
return
Course
.
query
.
all
()
def
get_users_filtered
(
course
:
Course
,
groups
:
List
[
str
],
roles
:
List
[
str
])
->
List
[
User
]:
"""Get all users for course filtered
Args:
course(Course): Course instance
groups(list): Group names list
roles(list): Role names list
Returns(List[User]):
"""
groups_entities
=
Group
.
query
.
filter
(
Group
.
id
.
in_
(
groups
)).
all
()
roles_entities
=
Role
.
query
.
filter
(
Role
.
id
.
in_
(
roles
)).
all
()
return
course
.
get_users_filtered
(
groups_entities
,
roles_entities
)
class
CourseService
:
def
__init__
(
self
,
course
:
Course
=
None
):
self
.
_course
=
course
@
property
def
course
(
self
):
return
self
.
_course
def
create_course
(
self
,
**
data
)
->
Course
:
"""Creates new course
Keyword Args:
name(str): name of the course
codename(str): codename of the course
Returns(Course): Course instance
"""
course
=
Course
()
self
.
_course
=
course
_set_course_props
(
course
=
course
,
data
=
data
)
log
.
debug
(
f
"[CREATE] Course
{
course
.
id
}
:
{
course
}
"
)
return
course
def
copy_course
(
self
,
target
:
Course
,
config
:
dict
)
->
Course
:
"""Copies course and it's other resources (roles, groups, projects)
The other resources that should be copied are specified in the config
Args:
target(Course): To which course the resources will be copied
config(dict): Configuration to specify which resource should be copied
Returns(Course): Copied course instance (target)
"""
if
config
.
get
(
'roles'
):
for
role
in
self
.
course
.
roles
:
copy_role
(
role
,
target
,
with_clients
=
config
[
'roles'
])
if
config
.
get
(
'groups'
):
for
group
in
self
.
course
.
groups
:
copy_group
(
group
,
target
,
with_users
=
config
[
'groups'
])
if
config
.
get
(
'projects'
):
for
project
in
self
.
course
.
projects
:
copy_project
(
project
,
target
)
general
.
write_entity
(
target
)
log
.
info
(
f
"[IMPORT] From
{
self
.
course
.
id
}
to:
{
target
.
id
}
."
)
return
target
def
delete_course
(
self
):
"""Deletes course
"""
log
.
info
(
f
"[DELETE] Course:
{
self
.
course
.
id
}
(
{
self
.
course
.
codename
}
)"
)
general
.
delete_entity
(
self
.
course
)
def
update_course
(
self
,
data
:
dict
)
->
Course
:
"""Updates course
Args:
data(dict): Dictionary containing data which should be changed
Returns(Course): Updated course instance
"""
_set_course_props
(
course
=
self
.
course
,
data
=
data
)
log
.
info
(
f
"[UPDATE] Course
{
self
.
course
.
id
}
(
{
self
.
course
.
codename
}
):
{
self
.
course
}
."
)
return
self
.
course
def
update_notes_token
(
self
,
token
:
str
)
->
Course
:
"""Updates notes access token of a course.
Args:
token(str): The new token
Returns(Course): Course instance
"""
self
.
course
.
notes_access_token
=
token
general
.
write_entity
(
self
.
course
)
log
.
info
(
f
"[UPDATE] Notes access token (
{
self
.
course
.
codename
}
) [
{
self
.
course
.
id
}
]:
{
token
}
"
)
return
self
.
course
def
find_all_courses
(
self
)
->
List
[
Course
]:
"""Find all courses
Returns(list): List of courses
"""
return
Course
.
query
.
all
()
def
get_users_filtered
(
self
,
groups
:
List
[
str
],
roles
:
List
[
str
])
->
List
[
User
]:
"""Get all users for course filtered
Args:
groups(list): Group names list
roles(list): Role names list
Returns(List[User]):
"""
groups_entities
=
Group
.
query
.
filter
(
Group
.
id
.
in_
(
groups
)).
all
()
roles_entities
=
Role
.
query
.
filter
(
Role
.
id
.
in_
(
roles
)).
all
()
return
self
.
course
.
get_users_filtered
(
groups_entities
,
roles_entities
)
portal/service/projects.py
View file @
5e847e32
...
...
@@ -16,161 +16,150 @@ from portal.service.general import get_new_name
log
=
logging
.
getLogger
(
__name__
)
def
copy_project
(
source
:
Project
,
target
:
Course
)
->
Project
:
"""Copies a project to the target course
Args:
source(Project): Project instance
target(Course): Course instance
Returns(Project): Copied project
"""
new_name
=
get_new_name
(
source
,
target
)
new_project
=
Project
(
target
,
codename
=
new_name
)
new_project
.
description
=
source
.
description
new_project
.
name
=
source
.
name
new_project
.
set_config
(
**
vars
(
source
.
config
))
return
new_project
def
can_create_submission
(
user
:
User
,
project
:
Project
)
->
bool
:
"""Checks if a user may create a new submission.
A user is permitted to create a new submission if there are no submissions
created or queued for him in the given project.
Args:
user(User): the user who wants to crete a new submission
project(Project): the project in which the user wants to create a submission
Returns: whether the user can create a new submission in the project
"""
conflicting_states
=
[
SubmissionState
.
CREATED
,
SubmissionState
.
READY
,
SubmissionState
.
QUEUED
]
conflict
=
Submission
.
query
.
filter
(
Submission
.
user
==
user
)
\
.
filter
(
Submission
.
project
==
project
)
\
.
filter
(
Submission
.
state
.
in_
(
conflicting_states
))
\
.
first
()
return
conflict
is
None
def
delete_project
(
project
:
Project
):
"""Deletes a project
Args:
project(Project): project instance
"""
log
.
info
(
f
"[DELETE] Project
{
project
.
id
}
(
{
project
.
codename
}
)."
)
general
.
delete_entity
(
project
)
def
update_project
(
project
:
Project
,
data
:
dict
)
->
Project
:
"""Updates a project
Args:
project(Project): Project instance
data(dict): Data dictionary
Returns(Project): Updated project
"""
log
.
info
(
f
"[UPDATE] Project
{
project
.
id
}
(
{
project
.
codename
}
):
{
data
}
."
)
return
__set_project_data
(
project
,
data
)
def
__set_project_data
(
project
:
Project
,
data
:
dict
)
->
Project
:
def
_set_project_data
(
project
:
Project
,
data
:
dict
)
->
Project
:
allowed
=
[
'name'
,
'description'
,
'codename'
,
'assignment_url'
]
return
general
.
update_entity
(
project
,
data
,
allowed
=
allowed
)
def
create_project
(
course
:
Course
,
data
:
dict
)
->
Project
:
"""Creates a new project
Args:
course(Course): Course instance
data(dict): Project data
Returns(Project): Project instance
"""
new_project
=
Project
(
course
=
course
)
__set_project_data
(
project
=
new_project
,
data
=
data
)
log
.
info
(
f
"[CREATE] Project for course
{
course
.
id
}
(
{
course
.
codename
}
): "
f
"
{
new_project
}
"
)
return
new_project
def
update_project_config
(
project
:
Project
,
data
:
dict
)
->
ProjectConfig
:
"""Updates project config
Args:
project(Project): Project instance
data(dict): Config data
Returns(ProjectConfig): Project config instance
"""
course
=
project
.
course
project
.
set_config
(
**
data
)
general
.
write_entity
(
project
)
log
.
info
(
f
"[UPDATE] Configuration for project
{
project
.
id
}
in '"
f
"course
{
course
.
id
}
to
{
data
}
."
)
return
project
.
config
def
query_submissions
(
project
:
Project
,
user
:
User
)
->
BaseQuery
:
"""Queries submissions for the project and filters them by user.
Args:
project(Project): Project instance
user(User): User instance
"""
course
=
project
.
course
return
Submission
.
query
.
filter
(
Submission
.
user_id
==
user
.
id
)
\
.
filter
(
Submission
.
project_id
==
project
.
id
)
\
.
join
(
Course
,
Course
.
id
==
project
.
course_id
).
filter
(
Course
.
id
==
course
.
id
)
def
find_project_submissions
(
project
:
Project
,
user_id
:
str
=
None
)
->
List
[
Submission
]:
"""Finds all project submissions. If a user id is specified, returns only submissions created
by that user.
Args:
project(Project): Project instance
user_id(str): User id (optional)
Returns(List[Submission]): A list of all submissions in a project if no user_id is specified.
Otherwise a list of submissions created in the project by the specified user.
"""
submissions
=
project
.
submissions
if
user_id
:
user
=
general
.
find_user
(
user_id
)
submissions
=
query_submissions
(
project
,
user
)
return
submissions
def
list_projects
(
course
:
Course
,
client
)
->
list
:
"""List of all projects
Args:
course(Course): Course instance
client: Client instance
Returns(list): list of projects
"""
perm_service
=
permissions
.
PermissionsService
(
course
=
course
)
if
perm_service
.
check
.
client
([
'view_course_full'
]):
return
course
.
projects
elif
perm_service
.
check
.
client
([
'view_course_limited'
]):
return
filters
.
filter_projects_from_course
(
course
=
course
,
user
=
client
)
raise
ForbiddenError
(
uid
=
client
.
id
)
def
update_project_test_files_hash
(
project
:
Project
):
""" Sends a request to Storage to update the project's test_files to the newest version.
:param project: the project to update
:return: nothing
"""
tasks
.
update_project_test_files
.
delay
(
project
.
id
)
class
ProjectService
:
def
__init__
(
self
,
project
:
Project
=
None
):
self
.
_project
=
project
@
property
def
project
(
self
)
->
Project
:
return
self
.
_project
def
copy_project
(
self
,
target
:
Course
)
->
Project
:
"""Copies a project to the target course
Args:
target(Course): Course instance
Returns(Project): Copied project
"""
new_name
=
get_new_name
(
self
.
project
,
target
)
new_project
=
Project
(
target
,
codename
=
new_name
)
new_project
.
description
=
self
.
project
.
description
new_project
.
name
=
self
.
project
.
name
new_project
.
set_config
(
**
vars
(
self
.
project
.
config
))
return
new_project
def
can_create_submission
(
self
,
user
:
User
)
->
bool
:
"""Checks if a user may create a new submission.
A user is permitted to create a new submission if there are no submissions
created or queued for him in the given project.
Args:
user(User): the user who wants to crete a new submission
Returns: whether the user can create a new submission in the project
"""
conflicting_states
=
[
SubmissionState
.
CREATED
,
SubmissionState
.
READY
,
SubmissionState
.
QUEUED
]
conflict
=
Submission
.
query
.
filter
(
Submission
.
user
==
user
)
\
.
filter
(
Submission
.
project
==
self
.
project
)
\
.
filter
(
Submission
.
state
.
in_
(
conflicting_states
))
\
.
first
()
return
conflict
is
None
def
delete_project
(
self
):
"""Deletes a project
"""
log
.
info
(
f
"[DELETE] Project
{
self
.
project
.
id
}
(
{
self
.
project
.
codename
}
)."
)
general
.
delete_entity
(
self
.
project
)
def
update_project
(
self
,
data
:
dict
)
->
Project
:
"""Updates a project
Args:
data(dict): Data dictionary
Returns(Project): Updated project
"""
log
.
info
(
f
"[UPDATE] Project
{
self
.
project
.
id
}
(
{
self
.
project
.
codename
}
):
{
data
}
."
)
return
_set_project_data
(
self
.
project
,
data
)
def
create_project
(
self
,
course
:
Course
,
data
:
dict
)
->
Project
:
"""Creates a new project
Args:
course(Course): Course instance
data(dict): Project data
Returns(Project): Project instance
"""
new_project
=
Project
(
course
=
course
)
self
.
_project
=
new_project
_set_project_data
(
project
=
new_project
,
data
=
data
)
log
.
info
(
f
"[CREATE] Project for course
{
course
.
id
}
(
{
course
.
codename
}
): "