Skip to content

Commit

Permalink
Upgrade to Python 3.12 and Django 5 (#477)
Browse files Browse the repository at this point in the history
* Upgrade to python 3.12 and django 5, update dependencies

* Update docker and Heroku runtime info to python 3.12

* Update poetry version, cypress python version

* Fix logout to use POST requests

* Add cypress logout command

* Update pre-commit package versions
  • Loading branch information
smartspot2 authored Feb 1, 2025
1 parent 21ae230 commit 57cc852
Show file tree
Hide file tree
Showing 15 changed files with 528 additions and 609 deletions.
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

0 comments on commit 57cc852

Please sign in to comment.