Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ATV-205 | Fix document status change causing error with json payload #117

Merged
merged 9 commits into from
Dec 23, 2024
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,7 @@ repos:
- id: commitlint
stages: [commit-msg, manual]
additional_dependencies: ["@commitlint/config-conventional"]
- repo: https://github.com/koalaman/shellcheck-precommit
rev: v0.10.0
hooks:
- id: shellcheck
12 changes: 0 additions & 12 deletions .prod/on_deploy.sh

This file was deleted.

10 changes: 5 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# ==============================
FROM registry.access.redhat.com/ubi8/python-311 as appbase
FROM registry.access.redhat.com/ubi8/python-311 AS appbase
# ==============================

EXPOSE 8000/tcp

USER root

RUN yum --disableplugin subscription-manager -y --allowerasing update \
&& yum --disableplugin subscription-manager -y install pcre-devel \
&& yum --disableplugin subscription-manager -y install pcre-devel nmap-ncat \
&& yum --disableplugin subscription-manager -y clean all

COPY scripts /scripts
Expand Down Expand Up @@ -38,7 +38,7 @@ COPY --chown=1000:0 docker-entrypoint.sh /entrypoint/docker-entrypoint.sh
ENTRYPOINT ["/entrypoint/docker-entrypoint.sh"]

# ==============================
FROM appbase as development
FROM appbase AS development
# ==============================

COPY --chown=1000:0 requirements-dev.txt ./requirements-dev.txt
Expand All @@ -49,15 +49,15 @@ ENV DEV_SERVER=1
COPY --chown=1000:0 . /app/

# ==============================
FROM appbase as staticbuilder
FROM appbase AS staticbuilder
# ==============================

ENV STATIC_ROOT /var/static
COPY --chown=1000:0 . /app/
RUN SECRET_KEY="only-used-for-collectstatic" python manage.py collectstatic --noinput

# ==============================
FROM appbase as production
FROM appbase AS production
# ==============================

# fatal: detected dubious ownership in repository at '/app'
Expand Down
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
version: "3.7"
services:
postgres:
image: postgres:14
Expand Down
13 changes: 9 additions & 4 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
set -e

# Wait for the database
if [ -z "$SKIP_DATABASE_CHECK" -o "$SKIP_DATABASE_CHECK" = "0" ]; then
wait-for-it.sh "${DATABASE_HOST}:${DATABASE_PORT-5432}" --timeout=30
if [ -z "$SKIP_DATABASE_CHECK" ] || [ "$SKIP_DATABASE_CHECK" = "0" ]; then
until nc --verbose --wait 30 -z "${DATABASE_HOST}" "${DATABASE_PORT-5432}"
do
echo "Waiting for postgres database connection..."
sleep 1
done
echo "Database is up!"
fi

# Apply database migrations
Expand All @@ -17,14 +22,14 @@ fi
# Create admin user. Generate password if there isn't one in the environment variables
if [[ "$CREATE_ADMIN_USER" = "1" ]]; then
if [[ "$ADMIN_USER_PASSWORD" ]]; then
./manage.py add_admin_user -u admin -p $ADMIN_USER_PASSWORD -e [email protected]
./manage.py add_admin_user -u admin -p "$ADMIN_USER_PASSWORD" -e [email protected]
else
./manage.py add_admin_user -u admin -e [email protected]
fi
fi

# Start server
if [[ ! -z "$@" ]]; then
if [[ -n "$*" ]]; then
"$@"
elif [[ "$DEV_SERVER" = "1" ]]; then
python -Wd ./manage.py runserver 0.0.0.0:8000
Expand Down
19 changes: 15 additions & 4 deletions documents/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,10 @@ class DocumentViewSet(AuditLoggingModelViewSet):
filterset_class = DocumentFilterSet
queryset = Document.objects.none()

def __init__(self, **kwargs):
super().__init__(**kwargs)
self.request_data_extra_fields = {}

def get_queryset(self):
user = self.request.user
service = get_service_from_request(self.request)
Expand Down Expand Up @@ -580,13 +584,20 @@ def partial_update(self, request, pk, *args, **kwargs):
status_history_serializer.is_valid(raise_exception=True)
status_history_serializer.save()

# Make sure the request data query dict is mutable, assign status_timestamp field to be updated.
request.data._mutable = True
request.data["status_timestamp"] = timezone.now()
request.data._mutable = False
# Update status_timestamp field also if the status has changed
self.request_data_extra_fields["status_timestamp"] = timezone.now()

return super().partial_update(request, pk, *args, **kwargs)

def get_serializer(self, *args, **kwargs):
if self.request_data_extra_fields:
# Request data itself is immutable so we modify the copy of the
# data before it's given to the serializer.
data = kwargs["data"].copy()
data.update(self.request_data_extra_fields)
kwargs["data"] = data
return super().get_serializer(*args, **kwargs)

def update(self, request, *args, **kwargs):
# Only allow for PATCH updates as described by the documentation
# PUT requests will fail
Expand Down
6 changes: 3 additions & 3 deletions documents/tests/snapshots/snap_test_api_retrieve_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"id": "485af718-d9d1-46b9-ad7b-33ea054126e3",
"locked_after": None,
"metadata": {},
"service": "service 212",
"service": "service 215",
"status": {
"timestamp": "2020-06-01T03:00:00+03:00",
"value": "testing",
Expand Down Expand Up @@ -47,7 +47,7 @@
"id": "485af718-d9d1-46b9-ad7b-33ea054126e3",
"locked_after": None,
"metadata": {},
"service": "service 215",
"service": "service 218",
"status": {
"timestamp": "2020-06-01T03:00:00+03:00",
"value": "testing",
Expand Down Expand Up @@ -75,7 +75,7 @@
"id": "485af718-d9d1-46b9-ad7b-33ea054126e3",
"locked_after": None,
"metadata": {},
"service": "service 214",
"service": "service 217",
"status": {
"timestamp": "2020-06-01T03:00:00+03:00",
"value": "testing",
Expand Down
44 changes: 44 additions & 0 deletions documents/tests/test_api_patch_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from dateutil.relativedelta import relativedelta
from dateutil.utils import today
from django.core.files.uploadedfile import SimpleUploadedFile
from django.utils import timezone
from freezegun import freeze_time
from guardian.shortcuts import assign_perm
from rest_framework import status
Expand Down Expand Up @@ -663,3 +664,46 @@ def test_audit_log_is_created_when_patching(user, attachments, ip_address):
).count()
== 1
)


@pytest.mark.parametrize("format", ["multipart", "json"])
def test_patch_status_update_in_multiple_formats(service_api_client, format):
"""Updating status (and status_timestamp) should work with multipart and json
formatting.
"""
document = DocumentFactory(
service=service_api_client.service,
status="testing",
)

response = service_api_client.patch(
reverse("documents-detail", args=[document.id]),
{"status": "changed status"},
format=format,
)

assert response.status_code == status.HTTP_200_OK
document.refresh_from_db()
assert document.status == "changed status"


@freeze_time("2021-06-30T12:00:00")
def test_patch_status_timestamp_is_updated(service_api_client):
"""When updating the status of a document, the status_timestamp should also get
updated automatically.
"""
document = DocumentFactory(
service=service_api_client.service,
status="testing",
)

with freeze_time("2024-12-20T12:00:00"):
dt = timezone.now()
response = service_api_client.patch(
reverse("documents-detail", args=[document.id]),
{"status": "changed status"},
)

assert response.status_code == status.HTTP_200_OK
document.refresh_from_db()
assert document.status_timestamp == dt
Loading
Loading