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
302dfef3
Unverified
Commit
302dfef3
authored
Sep 21, 2018
by
Peter Stanko
Browse files
Submissions list implementation
parent
f0df11cc
Changes
8
Hide whitespace changes
Inline
Side-by-side
portal/database/models.py
View file @
302dfef3
...
...
@@ -898,6 +898,10 @@ class Submission(db.Model, EntityBase):
def
course
(
self
):
return
self
.
project
.
course
@
course
.
expression
def
course
(
cls
):
return
Project
.
course
def
change_state
(
self
,
new_state
):
# open to extension (state transition validation, ...)
self
.
state
=
new_state
...
...
portal/rest/rest_helpers.py
View file @
302dfef3
"""
Helpers to work and process the rest requests and responses
"""
from
typing
import
Optional
from
flask
import
request
import
flask
from
werkzeug.datastructures
import
ImmutableMultiDict
import
portal.service.general
from
portal.database
import
Course
,
Project
,
User
from
portal.rest.schemas
import
SCHEMAS
from
portal.service.errors
import
DataMissingError
...
...
@@ -37,7 +41,55 @@ def require_data(action: str, resource: str) -> dict:
Returns(dict): Parsed json data
"""
json_data
=
request
.
get_json
()
json_data
=
flask
.
request
.
get_json
()
if
not
json_data
:
raise
DataMissingError
(
action
=
action
,
resource
=
resource
)
return
json_data
class
FlaskRequestArgsHelper
:
def
__init__
(
self
,
parser
:
'FlaskRequestHelper'
):
self
.
_parser
=
parser
@
property
def
_args
(
self
):
return
self
.
_parser
.
request_args
@
property
def
_general
(
self
)
->
portal
.
service
.
general
:
return
portal
.
service
.
general
def
course
(
self
)
->
Optional
[
Course
]:
course
=
self
.
_args
.
get
(
'course'
)
if
course
is
None
:
return
None
return
self
.
_general
.
find_course
(
course
)
def
user
(
self
)
->
Optional
[
User
]:
user
=
self
.
_args
.
get
(
'user'
)
if
user
is
None
:
return
None
return
self
.
_general
.
find_user
(
user
)
def
project
(
self
)
->
Optional
[
Project
]:
project
=
self
.
_args
.
get
(
'project'
)
if
project
is
None
:
return
None
course
=
self
.
course
()
if
course
is
None
:
return
None
return
self
.
_general
.
find_project
(
course
,
project
)
class
FlaskRequestHelper
:
@
property
def
args
(
self
)
->
FlaskRequestArgsHelper
:
return
FlaskRequestArgsHelper
(
parser
=
self
)
@
property
def
request
(
self
)
->
flask
.
Request
:
return
flask
.
request
@
property
def
request_args
(
self
)
->
ImmutableMultiDict
:
return
self
.
request
.
args
portal/rest/submissions.py
View file @
302dfef3
import
time
import
flask
from
flask_jwt_extended
import
jwt_required
from
flask_restplus
import
Namespace
,
Resource
from
portal
import
logger
,
storage
from
portal.database
import
Project
from
portal.rest
import
rest_helpers
from
portal.rest.schemas
import
SCHEMAS
from
portal.service
import
auth
,
general
,
permissions
from
portal.service.projects
import
ProjectService
from
portal.service.reviews
import
ReviewService
from
portal.service.submissions
import
SubmissionsService
...
...
@@ -18,9 +14,18 @@ submissions_namespace = Namespace('submissions')
log
=
logger
.
get_logger
(
__name__
)
@
submissions_namespace
.
route
(
''
)
class
SubmissionsResource
(
Resource
):
@
jwt_required
def
get
(
self
):
service
=
SubmissionsService
()
submissions
=
service
.
find_all_submissions
()
return
SCHEMAS
.
dump
(
'submissions'
,
submissions
)
@
submissions_namespace
.
route
(
'/<string:sid>'
)
@
submissions_namespace
.
param
(
'sid'
,
'Submission id'
)
@
submissions_namespace
.
response
(
404
,
'Submission
s
not found'
)
@
submissions_namespace
.
response
(
404
,
'Submission not found'
)
class
SubmissionResource
(
Resource
):
@
jwt_required
# @submissions_namespace.response(200, 'Submission found', model=submission_schema)
...
...
@@ -247,4 +252,3 @@ class SubmissionReview(Resource):
review_service
.
create_review_items
(
items
=
data
[
'review_items'
],
author
=
client
)
return
SCHEMAS
.
dump
(
'review'
,
submission
.
review
),
201
portal/service/general.py
View file @
302dfef3
...
...
@@ -105,7 +105,7 @@ def find_resource(identifier: str, resource: str, query, throws: bool = True):
return
instance
def
find_group
(
course
:
Course
,
identifier
:
str
,
throws
=
True
)
->
Group
:
def
find_group
(
course
:
Union
[
Course
,
str
]
,
identifier
:
str
,
throws
=
True
)
->
Group
:
"""Gets a Group instance
Args:
...
...
@@ -115,7 +115,8 @@ def find_group(course: Course, identifier: str, throws=True) -> Group:
Returns(Group): Group entity instance
"""
query
=
Group
.
query
.
filter_by
(
course
=
course
).
filter
(
query
=
_get_course
(
Group
.
query
,
course
=
course
)
query
=
query
.
filter
(
(
Group
.
id
==
identifier
)
|
(
Group
.
codename
==
sanitize_code_name
(
identifier
))
)
...
...
@@ -168,6 +169,14 @@ def find_course(identifier: str, throws=True) -> Course:
)
def
_get_course
(
query
,
course
:
Union
[
Course
,
str
]):
if
isinstance
(
course
,
str
):
query
=
query
.
filter
((
Course
.
id
==
course
)
|
(
Course
.
codename
==
course
))
else
:
query
=
query
.
filter_by
(
course
=
course
)
return
query
def
find_project
(
course
,
identifier
:
str
,
throws
=
True
)
->
Project
:
"""Gets a Project instance
...
...
@@ -178,7 +187,8 @@ def find_project(course, identifier: str, throws=True) -> Project:
Returns(Project): Project entity instance
"""
query
=
Project
.
query
.
filter_by
(
course
=
course
).
filter
(
query
=
_get_course
(
Project
.
query
,
course
=
course
)
query
=
query
.
filter
(
(
Project
.
id
==
identifier
)
|
(
Project
.
codename
==
sanitize_code_name
(
identifier
))
)
...
...
@@ -203,7 +213,7 @@ def find_secret(client, identifier, throws=True) -> Secret:
)
def
find_role
(
course
:
Course
,
identifier
:
str
,
throws
=
True
)
->
Role
:
def
find_role
(
course
:
Union
[
Course
,
str
]
,
identifier
:
str
,
throws
=
True
)
->
Role
:
"""Gets a Role instance
Args:
...
...
@@ -213,7 +223,8 @@ def find_role(course: Course, identifier: str, throws=True) -> Role:
Returns(Role): Role entity instance
"""
query
=
Role
.
query
.
filter_by
(
course
=
course
).
filter
(
query
=
_get_course
(
Role
.
query
,
course
=
course
)
query
=
query
.
filter
(
(
Role
.
id
==
identifier
)
|
(
Role
.
codename
==
sanitize_code_name
(
identifier
))
)
...
...
@@ -252,8 +263,7 @@ def find_user(identifier: str, throws=True) -> User:
Returns(User): User entity instance
"""
query
=
User
.
query
.
filter
(
((
User
.
id
==
identifier
)
|
(
User
.
username
==
identifier
)
|
(
User
.
email
==
identifier
))
((
User
.
id
==
identifier
)
|
(
User
.
username
==
identifier
)
|
(
User
.
email
==
identifier
))
)
return
find_resource
(
...
...
portal/service/permissions.py
View file @
302dfef3
...
...
@@ -66,15 +66,25 @@ class PermissionServiceCheck:
def
read_submissions_all
(
self
)
->
bool
:
return
self
.
permissions
([
'read_submissions_all'
])
def
read_submissions_group
(
self
,
submission
:
Submission
)
->
bool
:
def
read_submissions_group
(
self
,
submission
:
Submission
=
None
)
->
bool
:
submission
=
submission
or
self
.
service
.
submission
return
self
.
read_submissions_all
()
or
\
self
.
service
.
submission_access_group
(
submission
,
[
'read_submissions_groups'
])
def
read_submissions_own
(
self
,
submission
:
Submission
)
->
bool
:
def
read_submissions_own
(
self
,
submission
:
Submission
=
None
)
->
bool
:
submission
=
submission
or
self
.
service
.
submission
return
self
.
read_submissions_all
()
or
\
self
.
permissions
([
'read_submissions_own'
])
and
\
submission
.
user
==
self
.
service
.
client_owner
def
read_submission
(
self
,
submission
=
None
):
submission
=
submission
or
self
.
service
.
submission
checks
=
[
self
.
read_submissions_group
(
submission
),
self
.
read_submissions_own
(
submission
=
submission
)
]
return
self
.
any_check
(
*
checks
)
class
PermissionServiceRequire
:
def
__init__
(
self
,
service
:
'PermissionsService'
):
...
...
@@ -128,14 +138,16 @@ class PermissionServiceRequire:
]
self
.
any_check
(
*
checks
)
def
read_submission
(
self
,
submission
):
def
read_submission
(
self
,
submission
=
None
):
submission
=
submission
or
self
.
service
.
submission
checks
=
[
self
.
_check
.
read_submissions_group
(
submission
),
self
.
_check
.
read_submissions_own
(
submission
=
submission
)
]
self
.
any_check
(
*
checks
)
def
read_submission_group
(
self
,
submission
):
def
read_submission_group
(
self
,
submission
=
None
):
submission
=
submission
or
self
.
service
.
submission
checks
=
[
self
.
_check
.
read_submissions_group
(
submission
=
submission
)
]
...
...
@@ -162,9 +174,12 @@ class PermissionServiceRequire:
class
PermissionsService
:
def
__init__
(
self
,
client
=
None
,
course
=
None
):
def
__init__
(
self
,
client
=
None
,
course
=
None
,
submission
=
None
):
self
.
_client
=
client
or
auth
.
find_client
()
self
.
_course
=
course
if
course
is
None
and
submission
is
not
None
:
self
.
_course
=
submission
.
course
self
.
_submission
=
submission
self
.
_requires
=
PermissionServiceRequire
(
self
)
self
.
_checks
=
PermissionServiceCheck
(
self
)
...
...
@@ -188,6 +203,10 @@ class PermissionsService:
def
course
(
self
)
->
Course
:
return
self
.
_course
@
property
def
submission
(
self
)
->
Submission
:
return
self
.
_submission
def
get_effective_permissions
(
self
,
course_id
:
str
=
None
)
->
dict
:
"""Gets effective permissions for a course in course.
If no course is specified, returns the client's
...
...
portal/service/submissions.py
View file @
302dfef3
...
...
@@ -15,8 +15,10 @@ from werkzeug.utils import secure_filename
from
portal
import
storage
from
portal.async_celery
import
submission_processor
,
tasks
from
portal.database.models
import
Project
,
Submission
,
SubmissionState
,
User
,
Worker
from
portal.rest.rest_helpers
import
FlaskRequestHelper
from
portal.service
import
errors
,
general
from
portal.service.general
import
delete_entity
,
write_entity
from
portal.service.permissions
import
PermissionsService
from
portal.service.projects
import
ProjectService
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -71,6 +73,15 @@ class SubmissionsService(object):
def
__init__
(
self
,
submission
:
Submission
=
None
):
self
.
_submission
=
submission
self
.
_perm_service
=
PermissionsService
()
@
property
def
perm_service
(
self
):
return
self
.
_perm_service
@
property
def
client
(
self
):
return
self
.
perm_service
.
client
@
property
def
submission
(
self
)
->
Submission
:
...
...
@@ -218,3 +229,27 @@ class SubmissionsService(object):
if
project
.
config
.
test_files_commit_hash
is
not
None
:
break
time
.
sleep
(
1
)
def
filter_user_avail_submissions
(
self
,
query
):
submissions
=
query
.
all
()
return
[
submission
for
submission
in
submissions
if
PermissionsService
(
submission
=
submission
).
check
.
read_submission
()]
def
find_all_submissions
(
self
):
request_helper
=
FlaskRequestHelper
()
query
=
Submission
.
query
user
=
request_helper
.
args
.
user
()
project
=
request_helper
.
args
.
project
()
course
=
request_helper
.
args
.
course
()
if
user
:
query
=
query
.
filter
(
Submission
.
user
==
user
)
if
course
:
query
=
query
.
filter
(
Submission
.
course
==
course
)
if
project
:
query
=
query
.
filter
(
Submission
.
project
==
project
)
return
self
.
filter_user_avail_submissions
(
query
=
query
)
tests/rest/test_submission.py
View file @
302dfef3
import
json
from
portal.database.models
import
Course
,
Review
,
ReviewItem
,
Submission
,
SubmissionState
from
portal.database.models
import
Course
,
Review
,
ReviewItem
,
Submission
,
SubmissionState
,
Project
from
portal.service
import
general
from
.
import
utils
...
...
@@ -29,6 +29,48 @@ def create_submission(client) -> Submission:
return
general
.
find_submission
(
new_submission
[
'id'
])
def
test_list_all_avail
(
client
):
db_subm
=
Submission
.
query
.
all
()
response
=
utils
.
make_request
(
client
,
f
'/submissions'
,
method
=
'get'
)
assert
response
.
status_code
==
200
assert
response
.
mimetype
==
'application/json'
resp_submissions
=
utils
.
extract_data
(
response
)
assert
len
(
resp_submissions
)
==
len
(
db_subm
)
def
test_list_all_avail_for_user
(
client
):
student
=
general
.
find_user
(
'student1'
)
db_subm
=
Submission
.
query
.
filter
(
Submission
.
user
==
student
).
all
()
response
=
utils
.
make_request
(
client
,
f
'/submissions?user=student1'
,
method
=
'get'
)
assert
response
.
status_code
==
200
assert
response
.
mimetype
==
'application/json'
resp_submissions
=
utils
.
extract_data
(
response
)
assert
len
(
resp_submissions
)
==
len
(
db_subm
)
def
test_list_all_avail_for_course
(
client
):
course
=
general
.
find_course
(
'testcourse1'
)
db_subm
=
Submission
.
query
.
filter
(
Project
.
course
==
course
).
all
()
response
=
utils
.
make_request
(
client
,
f
'/submissions?course=testcourse1'
,
method
=
'get'
)
assert
response
.
status_code
==
200
assert
response
.
mimetype
==
'application/json'
resp_submissions
=
utils
.
extract_data
(
response
)
assert
len
(
resp_submissions
)
==
len
(
db_subm
)
def
test_list_all_avail_for_project
(
client
):
project
=
general
.
find_project
(
'testcourse1'
,
'hw01'
)
db_subm
=
Submission
.
query
.
filter
(
Submission
.
project
==
project
).
all
()
response
=
utils
.
make_request
(
client
,
f
'/submissions?course=testcourse1&project=hw01'
)
assert
response
.
status_code
==
200
assert
response
.
mimetype
==
'application/json'
resp_submissions
=
utils
.
extract_data
(
response
)
assert
len
(
resp_submissions
)
==
len
(
db_subm
)
# missing tests for working with zip
def
test_read
(
client
):
submissions
=
Submission
.
query
.
all
()
...
...
tests/rest/utils.py
View file @
302dfef3
...
...
@@ -19,13 +19,12 @@ API_PREFIX = "/api/v1.0"
def
extract_data
(
response
:
Response
)
->
dict
:
data
=
response
.
get_data
(
as_text
=
True
)
data
=
json
.
loads
(
data
)
data
=
response
.
get_json
()
log
.
debug
(
f
"[EXTRACT] Data:
{
data
}
"
)
return
data
def
make_request
(
client
:
FlaskClient
,
url
:
str
,
method
:
str
,
def
make_request
(
client
:
FlaskClient
,
url
:
str
,
method
:
str
=
'get'
,
credentials
:
str
=
None
,
headers
:
dict
=
None
,
data
:
str
=
None
)
->
Response
:
""" Creates an authenticated request to an endpoint.
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment