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

Upgrade to Python 3.12 and Django 5 #477

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/cypress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
workflow_dispatch:

env:
PYTHON_VERSION: 3.9
PYTHON_VERSION: 3.12

jobs:
cypress-run-chrome:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/django.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
workflow_dispatch:

env:
PYTHON_VERSION: 3.9
PYTHON_VERSION: 3.12

jobs:
pytest:
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ repos:
- id: trailing-whitespace
# python
- repo: https://github.com/psf/black
rev: 23.9.1
rev: 24.4.2
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
rev: 5.13.2
hooks:
- id: isort
- repo: local
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile.django
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# set up environment variables
FROM python:3.9.16
FROM python:3.12.4

ENV POETRY_VIRTUALENVS_CREATE false
ENV POETRY_VERSION 1.3.1
ENV POETRY_VERSION 1.8.3
ENV POETRY_HOME /opt/poetry
ENV POETRY_NO_INTERACTION 1
ENV VIRTUAL_ENV /venv
Expand Down
12 changes: 10 additions & 2 deletions csm_web/csm_web/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,13 @@
AWS_DEFAULT_ACL = None
AWS_S3_VERIFY = True
AWS_QUERYSTRING_AUTH = False # public bucket
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"

STORAGES = {
"default": {"BACKEND": "storages.backends.s3boto3.S3Boto3Storage"},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
}

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/
Expand All @@ -182,7 +188,9 @@

if DJANGO_ENV in (PRODUCTION, STAGING):
# Enables compression and caching
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
STORAGES["staticfiles"] = {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"
}
WHITENOISE_MAX_AGE = 31536000 # one year

AUTHENTICATION_BACKENDS = (
Expand Down
3 changes: 2 additions & 1 deletion csm_web/frontend/src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { Component, useEffect, useState } from "react";
import { Link, NavLink, NavLinkProps, Outlet, Route, Routes, useLocation } from "react-router-dom";

import { logout } from "../utils/api";
import { useProfiles } from "../utils/queries/base";
import { useMatcherActiveCourses } from "../utils/queries/matcher";
import { Role } from "../utils/types";
Expand Down Expand Up @@ -140,7 +141,7 @@ function Header(): React.ReactElement {
<NavLink to="/policies" className={navlinkClassSubtitle}>
<h3 className="site-subtitle">Policies</h3>
</NavLink>
<a id="logout-btn" href="/logout" title="Log out">
<a id="logout-btn" href="#" onClick={logout} title="Log out">
<LogOutIcon className="icon" />
</a>
</div>
Expand Down
36 changes: 35 additions & 1 deletion csm_web/frontend/src/utils/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,44 @@ export function normalizeEndpoint(endpoint: string) {
return `/api/${endpoint}`;
}

/**
* Endpoint for logging out the user.
* Must end in a slash, otherwise Django will complain, since this will redirect to the login endpoint.
*/
const LOGOUT_ENDPOINT = "/logout/";

/**
* Log out the current user by sending a POST request to ``/logout/`.
*
* Since we must also redirect after sending the POST request (following the server response),
* this sends the request using a newly created form with the CSRF token added.
*/
export function logout() {
const csrfToken = Cookies.get("csrftoken") ?? "";

// must use a form to allow redirect after the POST request
const form = document.createElement("form");
form.method = "POST";
form.action = LOGOUT_ENDPOINT;

// add the csrf token
const csrfInput = document.createElement("input");
csrfInput.type = "hidden";
csrfInput.name = "csrfmiddlewaretoken";
csrfInput.value = csrfToken;
form.appendChild(csrfInput);

// form must also be present in the document body
document.body.appendChild(form);

// submitting the form logs the user out and redirects to the login screen
form.submit();
}

export function fetchWithMethod(
endpoint: string,
method: string,
data: any = {},
data: any = {}, // eslint-disable-line @typescript-eslint/no-explicit-any
isFormData = false,
queryParams: URLSearchParams | null = null
) {
Expand Down
39 changes: 39 additions & 0 deletions cypress/e2e/login.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,43 @@ describe("login", () => {
cy.visit("/");
cy.get("h3.page-title").should("contain", "My courses");
});

it("should be able to log out after login", () => {
cy.setupDB("login", "setup");

cy.login();

// log out the current user
cy.logout();

// when visiting the home page now, it should be redirected to a login form
cy.visit("/");
cy.get("#login-btn").should("be.visible");
});

it("should be able to log out and redirect after login", () => {
cy.setupDB("login", "setup");

cy.login();

// log out the current user
cy.logout_redirect();

// should be redirected to a login form
cy.get("#login-btn").should("be.visible");
});

it("should be able to click the log out button to log out", () => {
cy.setupDB("login", "setup");

cy.login();

cy.visit("/");

// log out by clicking the log out button
cy.get("#logout-btn").should("be.visible").click();

// should redirect to the login screen
cy.get("#login-btn").should("be.visible");
});
});
4 changes: 2 additions & 2 deletions cypress/e2e/section/mentor-student-interaction.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe("word of the day", () => {
cy.contains(".primary-btn", /update/i).should("be.disabled"); // disabled because unchanged

// logout
cy.visit("/logout/");
cy.logout();

// log in as student next
cy.login({ username: "demo_student", password: "pass" });
Expand Down Expand Up @@ -89,7 +89,7 @@ describe("word of the day", () => {
});

// logout
cy.visit("/logout/");
cy.logout();

// log in as mentor again
cy.login({ username: "demo_mentor", password: "pass" });
Expand Down
26 changes: 26 additions & 0 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,32 @@ Cypress.Commands.add("login", (loginInfo: LoginInfo = { username: "demo_user", p
});
});

/**
* Headless logout
*/
Cypress.Commands.add("logout", () => {
cy.getCookie("csrftoken").then(csrfCookie => {
return cy.request({
method: "POST",
url: "/logout/",
form: true,
body: { csrfmiddlewaretoken: csrfCookie.value }
});
});
});

/**
* Logout with redirect
*/
Cypress.Commands.add("logout_redirect", () => {
cy.getCookie("csrftoken").then(csrfCookie => {
cy.visit("/logout/", {
method: "POST",
body: { csrfmiddlewaretoken: csrfCookie.value }
});
});
});

interface SetupDBOptions {
force?: boolean;
mutate?: boolean;
Expand Down
2 changes: 2 additions & 0 deletions cypress/support/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface SetupDBOptions {
declare namespace Cypress {
interface Chainable {
login(loginInfo?: LoginInfo): Chainable<void>;
logout(): Chainable<void>;
logout_redirect(): Chainable<void>;
setupDB(script_name: string, func_name: string, options?: SetupDBOptions): Chainable<void>;
initDB(): Chainable<void>;
_exec(command: string): Chainable<void>;
Expand Down
Loading
Loading