Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
C
compendium-v2
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
geant-swd
compendium-v2
Commits
60a4155b
Commit
60a4155b
authored
1 year ago
by
Remco Tukker
Browse files
Options
Downloads
Patches
Plain Diff
wrap up locking functionality
parent
14bc8982
No related branches found
No related tags found
1 merge request
!57
Feature/survey locking system
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
compendium_v2/routes/response.py
+86
-60
86 additions, 60 deletions
compendium_v2/routes/response.py
survey-frontend/src/SurveyContainerComponent.tsx
+20
-10
20 additions, 10 deletions
survey-frontend/src/SurveyContainerComponent.tsx
with
106 additions
and
70 deletions
compendium_v2/routes/response.py
+
86
−
60
View file @
60a4155b
...
...
@@ -6,6 +6,7 @@ from uuid import uuid4
from
flask
import
Blueprint
,
request
from
flask_login
import
login_required
,
current_user
# type: ignore
from
sqlalchemy
import
select
from
sqlalchemy.orm
import
lazyload
from
compendium_v2.db
import
db
from
compendium_v2.db.model
import
NREN
...
...
@@ -72,7 +73,8 @@ def try_survey(year) -> Any:
"
model
"
:
survey
.
survey
,
"
data
"
:
{},
"
page
"
:
0
,
"
verification_status
"
:
{}
"
verification_status
"
:
{},
"
mode
"
:
"
display
"
}
...
...
@@ -110,10 +112,37 @@ def inspect_survey(year) -> Any:
"
model
"
:
survey
.
survey
,
"
data
"
:
{},
"
page
"
:
0
,
"
verification_status
"
:
{}
"
verification_status
"
:
{},
"
mode
"
:
"
display
"
}
def
get_response_data
(
response
,
year
,
nren_id
):
data
=
{}
page
=
0
verification_status
=
{}
locked_by
=
None
if
response
and
response
.
locked_by_user
:
locked_by
=
response
.
locked_by_user
.
fullname
if
response
and
response
.
answers
:
data
=
response
.
answers
[
"
data
"
]
page
=
response
.
answers
[
"
page
"
]
verification_status
=
response
.
answers
[
"
verification_status
"
]
else
:
previous_response
=
db
.
session
.
scalar
(
select
(
SurveyResponse
).
where
(
SurveyResponse
.
survey_year
==
year
-
1
).
where
(
SurveyResponse
.
nren_id
==
nren_id
)
)
if
previous_response
:
# TODO add a 'migration' hook here for updating data per year
previous_response_data
:
dict
=
previous_response
.
answers
[
"
data
"
]
data
=
previous_response_data
verification_status
=
{
question_name
:
VerificationStatus
.
Unverified
for
question_name
in
data
.
keys
()}
return
data
,
page
,
verification_status
,
locked_by
@routes.route
(
'
/load/<int:year>/<string:nren_name>
'
,
methods
=
[
'
GET
'
])
@common.require_accepts_json
@login_required
...
...
@@ -141,35 +170,69 @@ def load_survey(year, nren_name) -> Any:
if
not
check_access_nren
(
current_user
,
nren
):
return
{
'
success
'
:
False
,
'
message
'
:
'
You do not have permissions to access this survey.
'
},
403
data
=
{}
page
=
0
verification_status
=
{}
locked_by
=
None
response
=
db
.
session
.
scalar
(
select
(
SurveyResponse
).
where
(
SurveyResponse
.
survey_year
==
year
).
where
(
SurveyResponse
.
nren_id
==
nren
.
id
)
)
previous_response
=
db
.
session
.
scalar
(
select
(
SurveyResponse
).
where
(
SurveyResponse
.
survey_year
==
year
-
1
).
where
(
SurveyResponse
.
nren_id
==
nren
.
id
)
)
if
response
:
data
=
response
.
answers
[
"
data
"
]
page
=
response
.
answers
[
"
page
"
]
verification_status
=
response
.
answers
[
"
verification_status
"
]
locked_by
=
response
.
locked_by_user
.
fullname
if
response
.
locked_by_user
else
None
elif
previous_response
:
# TODO add a 'migration' hook here for updating data per year
previous_response_data
:
dict
=
previous_response
.
answers
[
"
data
"
]
data
=
previous_response_data
verification_status
=
{
question_name
:
VerificationStatus
.
Unverified
for
question_name
in
data
.
keys
()}
data
,
page
,
verification_status
,
locked_by
=
get_response_data
(
response
,
year
,
nren
.
id
)
return
{
"
model
"
:
survey
.
survey
,
"
locked_by
"
:
locked_by
,
"
data
"
:
data
,
"
page
"
:
page
,
"
verification_status
"
:
verification_status
"
verification_status
"
:
verification_status
,
"
mode
"
:
"
display
"
}
@routes.route
(
'
/lock/<int:year>/<string:nren_name>
'
,
methods
=
[
'
POST
'
])
@common.require_accepts_json
@login_required
def
lock_survey
(
year
,
nren_name
)
->
Any
:
nren
=
db
.
session
.
scalar
(
select
(
NREN
).
filter
(
NREN
.
name
==
nren_name
))
if
not
nren
:
return
{
'
success
'
:
False
,
'
message
'
:
'
NREN not found
'
},
404
survey
=
db
.
session
.
scalar
(
select
(
Survey
).
where
(
Survey
.
year
==
year
))
if
not
survey
:
return
{
'
success
'
:
False
,
'
message
'
:
'
Survey not found
'
},
404
if
not
check_access_nren
(
current_user
,
nren
):
return
{
'
success
'
:
False
,
'
message
'
:
'
You do not have permissions to access this survey.
'
},
403
response
=
db
.
session
.
scalar
(
select
(
SurveyResponse
).
where
(
SurveyResponse
.
survey_year
==
year
)
.
where
(
SurveyResponse
.
nren_id
==
nren
.
id
)
.
options
(
lazyload
(
"
*
"
))
.
with_for_update
()
)
data
,
page
,
verification_status
,
locked_by
=
get_response_data
(
response
,
year
,
nren
.
id
)
# it's possible this is the first time somebody opens the survey for edit, in
# that case we have to create the response:
if
response
is
None
:
response
=
SurveyResponse
(
survey_year
=
year
,
nren_id
=
nren
.
id
,
status
=
ResponseStatus
.
started
)
db
.
session
.
add
(
response
)
if
response
.
locked_by
is
not
None
:
return
{
'
success
'
:
False
,
'
message
'
:
'
This survey is already locked.
'
},
403
lock_uuid
=
uuid4
()
response
.
lock_uuid
=
lock_uuid
response
.
locked_by
=
current_user
.
id
db
.
session
.
commit
()
locked_by
=
response
.
locked_by_user
.
fullname
return
{
'
success
'
:
True
,
'
lock_uuid
'
:
lock_uuid
,
'
locked_by
'
:
locked_by
,
'
data
'
:
data
,
'
verification_status
'
:
verification_status
,
"
mode
"
:
"
edit
"
}
...
...
@@ -201,8 +264,7 @@ def save_survey(year, nren_name) -> Any:
select
(
SurveyResponse
).
where
(
SurveyResponse
.
survey_year
==
year
).
where
(
SurveyResponse
.
nren_id
==
nren
.
id
)
)
if
response
is
None
:
response
=
SurveyResponse
(
survey_year
=
year
,
nren_id
=
nren
.
id
,
status
=
ResponseStatus
.
started
)
db
.
session
.
add
(
response
)
return
{
'
success
'
:
False
,
'
message
'
:
'
Survey response not found
'
},
400
save_survey
=
request
.
json
if
not
save_survey
:
...
...
@@ -230,7 +292,7 @@ def save_survey(year, nren_name) -> Any:
}
db
.
session
.
commit
()
mode
=
"
di
splay
"
if
new_state
==
"
readonly
"
else
"
e
di
t
"
mode
=
"
e
di
t
"
if
new_state
==
"
editing
"
else
"
di
splay
"
locked_by
=
response
.
locked_by_user
.
fullname
if
response
.
locked_by_user
else
None
return
{
'
success
'
:
True
,
'
locked_by
'
:
locked_by
,
'
mode
'
:
mode
}
...
...
@@ -268,39 +330,3 @@ def unlock_survey(year, nren_name) -> Any:
db
.
session
.
commit
()
return
{
'
success
'
:
True
,
'
locked_by
'
:
None
,
'
mode
'
:
'
display
'
}
@routes.route
(
'
/lock/<int:year>/<string:nren_name>
'
,
methods
=
[
'
POST
'
])
@common.require_accepts_json
@login_required
def
lock_survey
(
year
,
nren_name
)
->
Any
:
# TODO THIS SHOULD ALWAYS BE COMBINED WITH GET SURVEY DATA TO PREVENT RACE CONDITIONS!
nren
=
db
.
session
.
scalar
(
select
(
NREN
).
filter
(
NREN
.
name
==
nren_name
))
if
nren
is
None
:
return
{
'
success
'
:
False
,
'
message
'
:
'
NREN not found
'
},
404
survey
=
db
.
session
.
scalar
(
select
(
Survey
).
where
(
Survey
.
year
==
year
))
if
survey
is
None
:
return
{
'
success
'
:
False
,
'
message
'
:
'
Survey not found
'
},
404
if
not
check_access_nren
(
current_user
,
nren
):
return
{
'
success
'
:
False
,
'
message
'
:
'
You do not have permission to edit this survey.
'
},
403
response
=
db
.
session
.
scalar
(
select
(
SurveyResponse
).
where
(
SurveyResponse
.
survey_year
==
year
).
where
(
SurveyResponse
.
nren_id
==
nren
.
id
)
)
if
response
is
None
:
response
=
SurveyResponse
(
survey_year
=
year
,
nren_id
=
nren
.
id
,
status
=
ResponseStatus
.
started
)
# TODO this is always going to be the place where new responses are going to be saved for the first time
# so also fill the answer field, so that it always has data
db
.
session
.
add
(
response
)
if
response
.
locked_by
is
not
None
:
return
{
'
success
'
:
False
,
'
message
'
:
'
This survey is already locked.
'
},
403
lock_uuid
=
uuid4
()
response
.
lock_uuid
=
lock_uuid
response
.
locked_by
=
current_user
.
id
db
.
session
.
commit
()
return
{
'
success
'
:
True
,
'
lock_uuid
'
:
lock_uuid
}
This diff is collapsed.
Click to expand it.
survey-frontend/src/SurveyContainerComponent.tsx
+
20
−
10
View file @
60a4155b
...
...
@@ -63,7 +63,7 @@ function SurveyContainerComponent({ loadFrom }) {
survey
.
currentPageNo
=
json
[
'
page
'
];
survey
.
showNavigationButtons
=
false
;
survey
.
showTOC
=
false
;
survey
.
mode
=
'
display
'
;
survey
.
mode
=
json
[
'
mode
'
]
;
survey
.
lockedBy
=
json
[
'
locked_by
'
]
setSurveyModel
(
survey
);
...
...
@@ -81,7 +81,7 @@ function SurveyContainerComponent({ loadFrom }) {
if
(
nren
==
''
)
{
return
;
}
const
saveData
=
{
lock_uuid
:
lockUUID
.
current
,
new_state
:
newState
,
...
...
@@ -138,7 +138,7 @@ function SurveyContainerComponent({ loadFrom }) {
}
},
'
complete
'
:
async
()
=>
{
const
validSurvey
=
validateWithAnswerVerification
(
surveyModel
.
validate
);
const
validSurvey
=
validateWithAnswerVerification
(
surveyModel
.
validate
.
bind
(
surveyModel
,
true
,
true
)
);
if
(
validSurvey
)
{
const
errorMessage
=
await
saveSurveyData
(
surveyModel
,
"
completed
"
);
if
(
errorMessage
)
{
...
...
@@ -163,20 +163,30 @@ function SurveyContainerComponent({ loadFrom }) {
'
startEdit
'
:
async
()
=>
{
const
response
=
await
fetch
(
'
/api/response/lock/
'
+
year
+
'
/
'
+
nren
,
{
method
:
"
POST
"
});
const
json
=
await
response
.
json
();
console
.
log
(
json
);
lockUUID
.
current
=
json
.
lock_uuid
;
// TODO handle failed lock!
if
(
!
response
.
ok
)
{
toast
(
"
Failed starting edit:
"
+
json
[
'
message
'
]);
return
;
}
addEventListener
(
"
pagehide
"
,
pageHideListener
);
addEventListener
(
"
beforeunload
"
,
beforeUnloadListener
,
{
capture
:
true
});
surveyModel
.
mode
=
'
edit
'
;
surveyModel
.
lockedBy
=
'
something we get back from the server?
'
;
surveyModel
.
data
=
json
[
'
data
'
];
surveyModel
.
clearIncorrectValues
(
true
);
surveyModel
.
mode
=
json
[
'
mode
'
];
surveyModel
.
lockedBy
=
json
[
'
locked_by
'
]
lockUUID
.
current
=
json
[
'
lock_uuid
'
];
},
'
releaseLock
'
:
async
()
=>
{
const
response
=
await
fetch
(
'
/api/response/unlock/
'
+
year
+
'
/
'
+
nren
,
{
method
:
'
POST
'
});
// TODO handle failed release
const
json
=
await
response
.
json
();
if
(
!
response
.
ok
)
{
toast
(
"
Failed releasing lock:
"
+
json
[
'
message
'
]);
return
;
}
surveyModel
.
mode
=
json
[
'
mode
'
];
surveyModel
.
lockedBy
=
json
[
'
locked_by
'
];
},
'
validatePage
'
:
()
=>
{
const
validSurvey
=
validateWithAnswerVerification
(
surveyModel
.
validatePage
);
const
validSurvey
=
validateWithAnswerVerification
(
surveyModel
.
validatePage
.
bind
(
surveyModel
)
);
if
(
validSurvey
)
{
toast
(
"
Page validation successful!
"
);
}
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
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!
Save comment
Cancel
Please
register
or
sign in
to comment