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

DRAFT: Feature/django ninja #363

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions consultation_analyser/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"crispy_forms_gds",
"django.contrib.humanize",
"django_rq",
"ninja_jwt",
]


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends "base.html" %}

{% set page_title = "My account" %}

{% block content %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-l">My account</h1>
<h2 class="govuk-heading-m">JSON Web Token for API</h1>
<p class="govuk-body long-token--word-wrap">{{ jwt_token }}</p>
</div>
</div>
{% endblock %}
3 changes: 3 additions & 0 deletions consultation_analyser/support_console/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
from django.urls import path

from .views import consultations, consultations_users, pages, users
from .views.api import api

urlpatterns = [
path("", lambda request: redirect("/support/consultations/")),
path("sign-out/", pages.sign_out),
path("users/", users.index),
path("users/new/", users.new),
path("users/<int:user_id>/", users.show),
path("users/me/", users.my_account),
path("consultations/", consultations.index),
path("consultations/<uuid:consultation_id>/", consultations.show, name="support_consultation"),
path(
Expand All @@ -31,4 +33,5 @@
consultations.download,
name="download_consultation",
),
path("api/", api.urls),
]
36 changes: 36 additions & 0 deletions consultation_analyser/support_console/views/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import datetime
import logging

from django.core.files.base import ContentFile
from django.core.files.storage import default_storage as storage
from ninja import NinjaAPI
from ninja_jwt.authentication import JWTAuth

from consultation_analyser.consultations.jobs.upload_consultation import async_upload_consultation
from consultation_analyser.consultations.public_schema import ConsultationWithResponses
from consultation_analyser.support_console.decorators import support_login_required

logger = logging.getLogger("api")

api = NinjaAPI()


# For now, just upload a consultation as we don't know format of themes
@api.post("/upload-consultation/", auth=JWTAuth())
@support_login_required
def upload_consultation(request, data: ConsultationWithResponses):
logger.info("Saving uploaded consultation data")
timestamp = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
filename = f"{timestamp}-consultation-uploaded.json"
file = ContentFile(data.json())
file_path = storage.save(filename, file)
async_upload_consultation.delay(file_path, request.user.id)
return "Your data is being uploaded"


# For testing
@api.get("/hello/", auth=JWTAuth())
@support_login_required
def hello(request):
print(request.user)
return "Hello world"
11 changes: 11 additions & 0 deletions consultation_analyser/support_console/views/users.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.contrib import messages
from django.http import HttpRequest
from django.shortcuts import get_object_or_404, redirect, render
from ninja_jwt.tokens import RefreshToken

from consultation_analyser.authentication.models import User
from consultation_analyser.support_console.decorators import support_login_required
Expand Down Expand Up @@ -51,3 +52,13 @@ def show(request: HttpRequest, user_id: int):
"support_console/users/show.html",
{"user": user, "consultations": consultations, "form": form},
)


@support_login_required
def my_account(request: HttpRequest):
user = request.user
user = request.user
refresh = RefreshToken.for_user(user)
access_token = str(refresh.access_token)
context = {"jwt_token": access_token}
return render(request, "support_console/users/my_account.html", context=context)
6 changes: 6 additions & 0 deletions frontend/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,9 @@ pre code {
--fonts-prefix: "iai-assets";
}
@import "../node_modules/i.ai-design-system/dist/styles.scss";


/** SUPPORT PAGES **/
.long-token--word-wrap {
word-wrap: break-word;
}
239 changes: 234 additions & 5 deletions poetry.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ django-rq = "^2.10.2"
django-redis = "^5.4.0"
honcho = "^1.1.0"
django-storages = "^1.14.4"
django-ninja = "^1.2.2"
django-ninja-jwt = "^5.3.2"

[tool.poetry.group.development.dependencies]
ruff = "^0.5.6"
Expand All @@ -71,6 +73,8 @@ mypy = "^1.11.0"
files = '**/*.py'
exclude = ['^consultation_analyser/consultations/migrations/']



[tool.poetry.group.test.dependencies]
pytest-django = "^4.8.0"
django-webtest = "^1.9.11"
Expand Down
49 changes: 49 additions & 0 deletions tests/unit/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import json

import pytest
from django.conf import settings
from ninja_jwt.tokens import RefreshToken

from consultation_analyser import factories

UPLOAD_CONSULTATION_URL = "/support/api/upload-consultation/"
VALID_CONSULTATION_UPLOAD = settings.BASE_DIR / "tests" / "examples" / "chocolate.json"


@pytest.mark.django_db
def test_upload_consultation_no_auth(client):
response = client.post(UPLOAD_CONSULTATION_URL)
assert response.status_code == 401


@pytest.mark.django_db
def test_upload_consultation_valid(client):
staff_user = factories.UserFactory(email="[email protected]", is_staff=True)
client.force_login(staff_user)
refresh = RefreshToken.for_user(staff_user)
access_token = str(refresh.access_token)
headers = {"Authorization": f"Bearer {access_token}"}
with open(VALID_CONSULTATION_UPLOAD, "r") as file:
data = json.load(file)

# Staff user should be able to upload consultation
response = client.post(UPLOAD_CONSULTATION_URL, headers=headers, data=json.dumps(data), content_type="application/json")
assert response.status_code == 200

# TODO - test for invalid data


@pytest.mark.django_db
def test_upload_consultation_non_staff_user(client):
regular_user = factories.UserFactory(email="[email protected]", is_staff=False)
client.force_login(regular_user)
refresh = RefreshToken.for_user(regular_user)
access_token = str(refresh.access_token)
headers = {"Authorization": f"Bearer {access_token}"}
with open(VALID_CONSULTATION_UPLOAD, "r") as file:
data = json.load(file)

response = client.post(UPLOAD_CONSULTATION_URL, headers=headers, data=json.dumps(data), content_type="application/json")
print(response.content)
assert response.status_code == 401
# TODO - what should response be? At the moment 302 redirect.
Loading