Skip to content

Commit 3686165

Browse files
authored
Merge pull request #1646 from ScilifelabDataCentre/dev
New release: 2.13.1
2 parents e7fe7f8 + d6705e5 commit 3686165

20 files changed

+496
-305
lines changed

.github/pull_request_template.md

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,37 @@
1-
## Read this before submitting the PR
1+
## Pull Request Template
22

3-
1. Always create a Draft PR first
4-
2. Go through sections 1-5 below, fill them in and check all the boxes
5-
3. Make sure that the branch is updated; if there's an "Update branch" button at the bottom of the PR, rebase or update branch.
6-
4. When all boxes are checked, information is filled in, and the branch is updated: mark as Ready For Review and tag reviewers (top right)
7-
5. Once there is a submitted review, implement the suggestions (if reasonable, otherwise discuss) and request an new review.
3+
### Before Marking as Ready for Review
84

9-
If there is a field which you are unsure about, enter the edit mode of this description or go to the [PR template](../.github/pull_request_template.md); There are invisible comments providing descriptions which may be of help.
5+
- [ ] Add relevant information to the sections below ([Summary](#summary) etc)
6+
- [ ] Rebase or merge the latest `dev` (or other targeted branch)
7+
- [ ] Update documentation if needed
8+
- [ ] Add an entry to the [`SPRINTLOG.md`](https://github.com/ScilifelabDataCentre/dds_web/blob/dev/SPRINTLOG.md) if needed
9+
- [ ] Choose an appropriate label. See [here](https://github.com/ScilifelabDataCentre/dds_web/blob/dev/docs/procedures/labelling_a_pull_request.md) for information on the labelling options
10+
- [ ] The code follows the [style guidelines](https://github.com/ScilifelabDataCentre/dds_web/blob/dev/docs/procedures/style_guidelines.md)
11+
- [ ] Perform a self-review: read the diff as if reviewing someone else's code
12+
- [ ] I have commented the code, particularly in hard-to-understand areas
13+
- [ ] Verify that all checks and tests have passed
1014

11-
## 1. Description / Summary
15+
**If the target branch is `master`**:
1216

13-
**Add a summary here**: What does this PR add/change and why?
17+
- [ ] Read and follow [the release instructions](https://github.com/ScilifelabDataCentre/dds_web/blob/dev/docs/procedures/new_release.md)
1418

15-
## 2. Jira task / GitHub issue
19+
### Summary
1620

17-
**Is this a GitHub issue?** --> Add the link to the github issue
21+
_Describe what the PR changes and why._
1822

19-
**Is this from a Jira task?** --> If your branch does not contain info regarding the Jira task ID, put it here.
23+
### Related Issue/Ticket
2024

21-
## 3. Type of change - Add label
25+
_Link GitHub issue or provide Jira ID._
2226

23-
- [ ] Add a label to the PR. See [here](../docs/procedures/labelling_a_pull_request.md) for information on the labelling options.
27+
### Testing
2428

25-
## 4. Additional information
29+
_If applicable: How did you verify the change? Include commands, data, or screenshots._
2630

27-
- [ ] I have added an entry to the [Sprintlog](../SPRINTLOG.md)
28-
- [ ] This is a PR to the `master` branch: _If checked, read [the release instructions](../docs/procedures/new_release.md)_ <!-- Check this if the PR is made to the `master` branch. Only the `dev` branch should be doing this. -->
29-
- [ ] I have followed steps 1-9. <!-- Should be checked if the "PR to `master` branch" box is checked AND the specified steps in the release instructions have been followed. -->
31+
### Reviewer Notes
32+
33+
_Anything that helps reviewers (e.g. areas needing close attention)._
34+
35+
---
36+
37+
Once all boxes are checked, mark the PR as **Ready for Review** and tag at least one team member as the initial reviewer.

CHANGELOG.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,25 @@
11
Changelog
22
==========
33

4+
.. _2.13.1:
5+
6+
2.13.1 - 2025-09-22
7+
~~~~~~~~~~~~~~~~~~~~
8+
9+
- 🐛 Bug Fixes
10+
- Disable autoflush on project creation and assure no duplicate public ID
11+
- Fix bug: Users should be able to release once and extend twice
12+
- 📄 Documentation
13+
- Correct key access swagger references
14+
- 🛡️ Dependencies
15+
- Bump node packages using `npm audit fix`
16+
- Bump python packages to solve vulnerabilities:
17+
- `cryptography` from 42.0.4 to 44.0.1
18+
- `dnspython` from 2.2.0 to 2.6.1
19+
- `idna` from 3.3 to 3.7
20+
- `Pillow` from 10.2.0 to 10.3.0
21+
- `requests` from 2.32.0 to 2.32.4
22+
423
.. _2.13.0:
524

625
2.13.0 - 2025-08-25

SPRINTLOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,3 +520,18 @@ _Nothing merged during this sprint_
520520
- Move instructions on how to solve failing actions / workflows to the workflow files ([#1620](https://github.com/ScilifelabDataCentre/dds_web/pull/1620/))
521521
- Added ADR for Queue solution ([#1625](https://github.com/ScilifelabDataCentre/dds_web/pull/1625))
522522
- New version: 2.13.0 ([#1627](https://github.com/ScilifelabDataCentre/dds_web/pull/1627))
523+
- Bug: Users should be able to release once and extend twice ([#1630](https://github.com/ScilifelabDataCentre/dds_web/pull/1630))
524+
525+
# 2025-09-01 - 2025-09-12
526+
527+
- Disable autoflush on project creation and assure no duplicate public ID ([#1626](https://github.com/ScilifelabDataCentre/dds_web/pull/1626))
528+
- Bump node libraries and python dependencies to solve vulnerabilities ([#1640](https://github.com/ScilifelabDataCentre/dds_web/pull/1640)):
529+
- `dnspython` from 2.2.0 to 2.6.1
530+
- `idna` from 3.3 to 3.7
531+
- `Pillow` from 10.2.0 to 10.3.0
532+
- `requests` from 2.32.0 to 2.32.4
533+
534+
## 2025-09-15 - 2025-09-26
535+
536+
- Bump cryptography library from 42.0.4 to 44.0.1 to solve vulnerabities ([#1640](https://github.com/ScilifelabDataCentre/dds_web/pull/1640))
537+
- New version: 2.13.1 ([#1647](https://github.com/ScilifelabDataCentre/dds_web/pull/1647))

dds_web/api/project.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@
5050
from dds_web.security.auth import get_user_roles_common
5151
from dds_web.api.files import check_eligibility_for_deletion
5252

53+
####################################################################################################
54+
# CONSTANTS ############################################################################ CONSTANTS #
55+
####################################################################################################
56+
57+
# Maximum number of times a project deadline can be extended
58+
MAX_DEADLINE_EXTENSIONS = 2
59+
60+
# Maximum number of times a project can be released after having expired
61+
MAX_RELEASES_FROM_EXPIRED = 3
62+
5363

5464
####################################################################################################
5565
# ENDPOINTS ############################################################################ ENDPOINTS #
@@ -214,7 +224,15 @@ def post(self):
214224
@handle_validation_errors
215225
@handle_db_error
216226
def patch(self):
217-
"""Partially update a the project status"""
227+
"""
228+
Use to extend the project deadline of a project.
229+
230+
It works by faking an expiration and re-releasing the project.
231+
Therefore it consumes one of the times that a project can be released after having expired.
232+
233+
Available --> Expired --> Available
234+
235+
"""
218236
# Get project ID, project and verify access
219237
project_id = dds_web.utils.get_required_item(obj=flask.request.args, req="project")
220238
project = dds_web.utils.collect_project(project_id=project_id)
@@ -223,11 +241,9 @@ def patch(self):
223241
# Get json input from request
224242
json_input = flask.request.get_json(silent=True) # Already checked by json_required
225243

226-
# A project can have the expired status 3 times
227-
# It can be released / extended 3 times
228-
# If more attempts --> cannot be extended again
229-
# will be automatically archived by the system
230-
if project.times_expired > 3:
244+
# A project can have the deadline extended a maximum of MAX_DEADLINE_EXTENSIONS
245+
# If more attempts occur, the deadline cannot be extended again.
246+
if project.times_expired >= MAX_DEADLINE_EXTENSIONS:
231247
raise DDSArgumentError(
232248
"Project availability limit: The maximum number of changes in data availability has been reached."
233249
)
@@ -401,10 +417,10 @@ def release_project(
401417
days=deadline_in
402418
)
403419

404-
# Project can only MOVE FROM Expired 2 times
405-
# but can BE IN Exired 3 times
420+
# Project can only MOVE FROM Expired to Available
421+
# MAX_RELEASES_FROM_EXPIRED times
406422
if project.current_status == "Expired":
407-
if project.times_expired > 3:
423+
if project.times_expired >= MAX_RELEASES_FROM_EXPIRED:
408424
raise DDSArgumentError(
409425
"Project availability limit: Project cannot be made Available any more times"
410426
)
@@ -569,7 +585,7 @@ def queue_helper_function_update_proj_status(
569585
new_status: str,
570586
aborted: bool = False,
571587
):
572-
"""When the delete contents operation has being sucesfully executed. Perform the update in the DB.
588+
"""When the delete contents operation has been successfully executed, perform the update in the DB.
573589
Function ONLY to be called by the queue.
574590
"""
575591

@@ -611,7 +627,7 @@ def delete_project_info(self, proj):
611627

612628

613629
class GetPublic(flask_restful.Resource):
614-
"""Gets the public key beloning to the current project."""
630+
"""Gets the public key belonging to the current project."""
615631

616632
@auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"])
617633
@logging_bind_request

dds_web/api/schemas/project_schemas.py

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
# Own modules
1818
from dds_web import errors as ddserr
19-
from dds_web import auth
19+
from dds_web import auth, db
2020
from dds_web.database import models
2121
from dds_web.api import api_s3_connector
2222
from dds_web.api.schemas import sqlalchemyautoschemas
@@ -137,37 +137,51 @@ def generate_bucketname(self, public_id, created_time):
137137
def create_project(self, data, **kwargs):
138138
"""Create project row in db."""
139139

140-
# Lock db, get unit row and update counter
141-
unit_row = (
142-
models.Unit.query.filter_by(id=auth.current_user().unit_id)
143-
.with_for_update()
144-
.one_or_none()
145-
)
146-
if not unit_row:
147-
raise ddserr.AccessDeniedError(message="Error: Your user is not associated to a unit.")
148-
149-
unit_row.counter = unit_row.counter + 1 if unit_row.counter else 1
150-
data["public_id"] = "{}{:05d}".format(unit_row.internal_ref, unit_row.counter)
151-
152-
# Generate bucket name
153-
data["bucket"] = self.generate_bucketname(
154-
public_id=data["public_id"], created_time=data["date_created"]
155-
)
140+
with db.session.no_autoflush:
141+
# Lock db, get unit row and update counter
142+
unit_row = (
143+
models.Unit.query.filter_by(id=auth.current_user().unit_id)
144+
.with_for_update()
145+
.one_or_none()
146+
)
147+
if not unit_row:
148+
raise ddserr.DatabaseError(
149+
message="Error: Your account is not associated to a unit. Contact the Data Centre.",
150+
pass_message=True,
151+
)
156152

157-
# Create project
158-
current_user = auth.current_user()
159-
new_project = models.Project(
160-
**{**data, "unit_id": current_user.unit.id, "created_by": current_user.username}
161-
)
162-
new_project.project_statuses.append(
163-
models.ProjectStatuses(
164-
**{
165-
"status": "In Progress",
166-
"date_created": data["date_created"],
167-
}
153+
if unit_row.counter is None:
154+
unit_row.counter = 0
155+
156+
# Check if public_id of the project already exists
157+
duplicate_public_id = True
158+
while duplicate_public_id:
159+
unit_row.counter += 1
160+
elected_public_id = "{}{:05d}".format(unit_row.internal_ref, unit_row.counter)
161+
if not models.Project.query.filter_by(public_id=elected_public_id).first():
162+
data["public_id"] = elected_public_id
163+
# Generate bucket name
164+
data["bucket"] = self.generate_bucketname(
165+
public_id=data["public_id"], created_time=data["date_created"]
166+
)
167+
duplicate_public_id = False
168+
169+
# Create project
170+
current_user = auth.current_user()
171+
new_project = models.Project(
172+
**{**data, "unit_id": current_user.unit.id, "created_by": current_user.username}
168173
)
169-
)
170-
generate_project_key_pair(current_user, new_project)
174+
new_project.project_statuses.append(
175+
models.ProjectStatuses(
176+
**{
177+
"status": "In Progress",
178+
"date_created": data["date_created"],
179+
}
180+
)
181+
)
182+
183+
generate_project_key_pair(current_user, new_project)
184+
db.session.flush()
171185

172186
return new_project
173187

dds_web/api/user.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def post(self):
6464
if not dds_web.utils.valid_user_role(specified_role=role):
6565
raise ddserr.DDSArgumentError(message="Invalid user role.")
6666

67-
# Unit only changable for Super Admin invites
67+
# Unit only changeable for Super Admin invites
6868
unit = json_info.get("unit") if auth.current_user().role == "Super Admin" else None
6969

7070
# A project may or may not be specified
@@ -88,7 +88,7 @@ def post(self):
8888
db.session.rollback()
8989
raise ddserr.DatabaseError(
9090
message=str(err),
91-
alt_message="Something happened while checking for existig account / active invite.",
91+
alt_message="Something happened while checking for existing account / active invite.",
9292
pass_message=False,
9393
)
9494

@@ -211,7 +211,7 @@ def invite_user(email, new_user_role, project=None, unit=None):
211211
if unit:
212212
unit_row = models.Unit.query.filter_by(public_id=unit).one_or_none()
213213
if not unit_row:
214-
raise ddserr.DDSArgumentError(message="Invalid unit publid id.")
214+
raise ddserr.DDSArgumentError(message="Invalid unit public id.")
215215

216216
unit_row.invites.append(new_invite)
217217
goahead = True

dds_web/errors.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ class DatabaseError(LoggedHTTPException):
151151
"""Baseclass for database related issues."""
152152

153153
code = http.HTTPStatus.INTERNAL_SERVER_ERROR
154+
description = "The system encountered an error in the database."
154155

155156
def __init__(
156157
self,
@@ -164,11 +165,7 @@ def __init__(
164165
if project:
165166
structlog.threadlocal.bind_threadlocal(project=project)
166167

167-
super().__init__(
168-
(alt_message or "The system encountered an error in the database.")
169-
if not pass_message
170-
else message
171-
)
168+
super().__init__((alt_message or self.description) if not pass_message else message)
172169

173170

174171
class EmptyProjectException(LoggedHTTPException):

0 commit comments

Comments
 (0)