Skip to content

Commit

Permalink
remove Django 4.1 from supported matrix, add Python 3.12
Browse files Browse the repository at this point in the history
[#185819424]
  • Loading branch information
uraniumanchor committed Dec 5, 2023
1 parent eb49b37 commit 03b59ed
Show file tree
Hide file tree
Showing 9 changed files with 55 additions and 49 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

## Requirements

- Python 3.7 to 3.11
- Django 3.2, 4.1, or 4.2
- Python 3.8 to 3.12
- Django 3.2 or 4.2 (5.0 is presently unsupported)

Additionally, if you are planning on developing, and/or building the JS bundles yourself:

Expand All @@ -27,7 +27,7 @@ Or after downloading or checking out locally:

For further reading on what else your server needs to look like:

- [Deploying Django](https://docs.djangoproject.com/en/4.2/howto/deployment/)
- [Deploying Django](https://docs.djangoproject.com/en/dev/howto/deployment/)
- [Deploying Django Channels](https://channels.readthedocs.io/en/latest/deploying.html)
- [Configuring Post Office](https://github.com/ui/django-post_office#management-commands) (needed to send emails)
- [Using Celery with Django](https://docs.celeryproject.org/en/stable/django/first-steps-with-django.html) (optional)
Expand Down Expand Up @@ -101,9 +101,9 @@ Allows you to place a logo asset in the navbar for public facing pages.

## Development Quick Start

Start up a new Django Project like the [Django Tutorial](https://docs.djangoproject.com/en/2.2/intro/tutorial01/).
Start up a new Django Project like the [Django Tutorial](https://docs.djangoproject.com/en/dev/intro/tutorial01/).

- `pip install django~=3.2`
- `pip install django~=4.2`
- `django-admin startproject tracker_development`
- `cd tracker_development`

Expand Down Expand Up @@ -212,4 +212,5 @@ This project uses [`pre-commit`](https://pre-commit.com/) to run linters and oth
If you followed the instructions above, `pre-commit` should run the appropriate hooks every time you commit or push.

_Note:_ You _can_ bypass these checks by adding `--no-verify` when you commit or push, though this is highly
discouraged in most cases. In the future, CI tests may fail if any of these checks are not satisfied.
discouraged in most cases. CI runs the same checks as the hooks do, and will cause pipeline to fail if you bypass
a genuine failure.
10 changes: 5 additions & 5 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@ jobs:
strategy:
matrix:
Latest:
PYTHON_VERSION: '3.11'
PYTHON_VERSION: '3.12'
DJANGO_VERSION: '4.2'
Oldest:
PYTHON_VERSION: '3.8'
DJANGO_VERSION: '3.2'
Django32:
PYTHON_VERSION: '3.11'
PYTHON_VERSION: '3.10'
DJANGO_VERSION: '3.2'
Django41:
PYTHON_VERSION: '3.11'
DJANGO_VERSION: '4.1'
Python38:
PYTHON_VERSION: '3.8'
DJANGO_VERSION: '4.2'
Expand All @@ -32,6 +29,9 @@ jobs:
Python3A:
PYTHON_VERSION: '3.10'
DJANGO_VERSION: '4.2'
Python3B:
PYTHON_VERSION: '3.11'
DJANGO_VERSION: '4.2'

steps:
- task: UsePythonVersion@0
Expand Down
9 changes: 4 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def get_package_name(name):

setup(
name=get_package_name('django-donation-tracker'),
version='3.1',
version='3.2',
author='Games Done Quick',
author_email='tracker@gamesdonequick.com',
packages=find_packages(include=['tracker', 'tracker.*']),
Expand All @@ -48,10 +48,9 @@ def get_package_name(name):
'package': PackageCommand,
},
install_requires=[
'backports.cached-property~=1.0.2;python_version<"3.8"',
'celery~=5.0',
'channels>=2.0',
'Django>=3.2,!=4.0.*,<4.3',
'Django>=3.2,!=4.0.*,!=4.1.*,<4.3',
'django-ajax-selects~=2.2',
'django-ical~=1.7',
'django-mptt~=0.10',
Expand All @@ -63,7 +62,7 @@ def get_package_name(name):
'pytz>=2019.3',
'requests>=2.27.1,<2.32.0',
],
python_requires='>=3.7, <3.12',
python_requires='>=3.8, <3.13',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
Expand All @@ -73,11 +72,11 @@ def get_package_name(name):
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Software Development :: Libraries :: Python Modules',
],
Expand Down
48 changes: 24 additions & 24 deletions tests/apiv2/test_donations.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def test_unprocessed_returns_oldest_unprocessed_donations_first(self):

self.assertEqual(len(response.data), len(donations))
for index, donation in enumerate(donations):
self.assertEquals(response.data[index]['id'], donation.pk)
self.assertEqual(response.data[index]['id'], donation.pk)

def test_unprocessed_returns_only_after_timestamp(self):
date = datetime.utcnow()
Expand Down Expand Up @@ -162,7 +162,7 @@ def test_flagged_returns_oldest_unprocessed_donations_first(self):

self.assertEqual(len(response.data), len(donations))
for index, donation in enumerate(donations):
self.assertEquals(response.data[index]['id'], donation.pk)
self.assertEqual(response.data[index]['id'], donation.pk)

def test_flagged_returns_only_after_timestamp(self):
date = datetime.utcnow()
Expand Down Expand Up @@ -214,14 +214,14 @@ def test_flagged_does_not_return_processed_donations(self):
def test_unprocess_fails_without_login(self):
self.client.force_authenticate(user=None)
response = self.client.post(f'/tracker/api/v2/donations/1234/unprocess/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_unprocess_fails_without_change_donation_permission(self):
user = User.objects.create()
self.client.force_authenticate(user=user)

response = self.client.post(f'/tracker/api/v2/donations/1234/unprocess/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_unprocess_resets_donation_state(self):
donation = self.generate_donations(self.event, count=1, state='approved')[0]
Expand Down Expand Up @@ -253,14 +253,14 @@ def test_unprocess_logs_changes(self):
def test_approve_comment_fails_without_login(self):
self.client.force_authenticate(user=None)
response = self.client.post(f'/tracker/api/v2/donations/1234/approve_comment/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_approve_comment_fails_without_change_donation_permission(self):
user = User.objects.create()
self.client.force_authenticate(user=user)

response = self.client.post(f'/tracker/api/v2/donations/1234/approve_comment/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_approve_comment_sets_donation_state(self):
donation = self.generate_donations(self.event, count=1, state='pending')[0]
Expand Down Expand Up @@ -295,14 +295,14 @@ def test_approve_comment_logs_changes(self):
def test_deny_comment_fails_without_login(self):
self.client.force_authenticate(user=None)
response = self.client.post(f'/tracker/api/v2/donations/1234/deny_comment/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_deny_comment_fails_without_change_donation_permission(self):
user = User.objects.create()
self.client.force_authenticate(user=user)

response = self.client.post(f'/tracker/api/v2/donations/1234/deny_comment/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_deny_comment_sets_donation_state(self):
donation = self.generate_donations(self.event, count=1, state='pending')[0]
Expand Down Expand Up @@ -337,14 +337,14 @@ def test_deny_comment_logs_changes(self):
def test_flag_fails_without_login(self):
self.client.force_authenticate(user=None)
response = self.client.post(f'/tracker/api/v2/donations/1234/flag/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_flag_fails_without_change_donation_permission(self):
user = User.objects.create()
self.client.force_authenticate(user=user)

response = self.client.post(f'/tracker/api/v2/donations/1234/flag/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_flag_sets_donation_state(self):
donation = self.generate_donations(self.event, count=1, state='')[0]
Expand Down Expand Up @@ -377,7 +377,7 @@ def test_flag_logs_changes(self):
def test_send_to_reader_fails_without_login(self):
self.client.force_authenticate(user=None)
response = self.client.post(f'/tracker/api/v2/donations/1234/send_to_reader/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_send_to_reader_requires_permissions(self):
user = User.objects.create()
Expand All @@ -388,23 +388,23 @@ def test_send_to_reader_requires_permissions(self):
self.client.force_authenticate(user=user)

response = self.client.patch(f'/tracker/api/v2/donations/1234/send_to_reader/')
self.assertEquals(response.status_code, 404)
self.assertEqual(response.status_code, 404)

def test_send_to_reader_fails_with_only_send_to_reader_permission(self):
user = User.objects.create()
user.user_permissions.add(Permission.objects.get(codename='send_to_reader'))
self.client.force_authenticate(user=user)

response = self.client.patch(f'/tracker/api/v2/donations/1234/send_to_reader/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_send_to_reader_fails_with_only_change_donation_permission(self):
user = User.objects.create()
user.user_permissions.add(Permission.objects.get(codename='change_donation'))
self.client.force_authenticate(user=user)

response = self.client.post(f'/tracker/api/v2/donations/1234/send_to_reader/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_send_to_reader_sets_donation_state(self):
donation = self.generate_donations(self.event, count=1, state='pending')[0]
Expand Down Expand Up @@ -439,14 +439,14 @@ def test_send_to_reader_logs_changes(self):
def test_pin_fails_without_login(self):
self.client.force_authenticate(user=None)
response = self.client.post(f'/tracker/api/v2/donations/1234/pin/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_pin_fails_without_change_donation_permission(self):
user = User.objects.create()
self.client.force_authenticate(user=user)

response = self.client.post(f'/tracker/api/v2/donations/1234/pin/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_pin_sets_donation_state(self):
donation = self.generate_donations(self.event, count=1, state='ready')[0]
Expand Down Expand Up @@ -477,14 +477,14 @@ def test_pin_logs_changes(self):
def test_unpin_fails_without_login(self):
self.client.force_authenticate(user=None)
response = self.client.post(f'/tracker/api/v2/donations/1234/unpin/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_unpin_fails_without_change_donation_permission(self):
user = User.objects.create()
self.client.force_authenticate(user=user)

response = self.client.post(f'/tracker/api/v2/donations/1234/unpin/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_unpin_sets_donation_state(self):
donation = self.generate_donations(self.event, count=1, state='ready')[0]
Expand Down Expand Up @@ -517,14 +517,14 @@ def test_unpin_logs_changes(self):
def test_read_fails_without_login(self):
self.client.force_authenticate(user=None)
response = self.client.post(f'/tracker/api/v2/donations/1234/read/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_read_fails_without_change_donation_permission(self):
user = User.objects.create()
self.client.force_authenticate(user=user)

response = self.client.post(f'/tracker/api/v2/donations/1234/read/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_read_sets_donation_state(self):
donation = self.generate_donations(self.event, count=1, state='ready')[0]
Expand Down Expand Up @@ -554,14 +554,14 @@ def test_read_logs_changes(self):
def test_ignore_fails_without_login(self):
self.client.force_authenticate(user=None)
response = self.client.post(f'/tracker/api/v2/donations/1234/ignore/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_ignore_fails_without_change_donation_permission(self):
user = User.objects.create()
self.client.force_authenticate(user=user)

response = self.client.post(f'/tracker/api/v2/donations/1234/ignore/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_ignore_sets_donation_state(self):
donation = self.generate_donations(self.event, count=1, state='ready')[0]
Expand Down Expand Up @@ -591,14 +591,14 @@ def test_ignore_logs_changes(self):
def test_comment_fails_without_login(self):
self.client.force_authenticate(user=None)
response = self.client.post(f'/tracker/api/v2/donations/1234/comment/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_comment_fails_without_change_donation_permission(self):
user = User.objects.create()
self.client.force_authenticate(user=user)

response = self.client.post(f'/tracker/api/v2/donations/1234/comment/')
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)

def test_comment_sets_donation_state(self):
donation = self.generate_donations(self.event, count=1, state='ready')[0]
Expand Down
6 changes: 1 addition & 5 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,11 @@ django-mptt==0.14.0
django-post-office==3.6.0
django-timezone-field==6.1.0
djangorestframework==3.14.0
# can be removed when either 3.7 is not supported or once we upgrade Celery
importlib_metadata==4.13.0 ; python_version<"3.8"
pre-commit==3.5.0 ; python_version>="3.8"
pre-commit==2.21.0 ; python_version<"3.8"
pre-commit==3.5.0
python-dateutil==2.8.2
# lock these down until backports.zoneinfo is in use
pytz==2022.2.1
tzdata==2022.2
webpack-manifest==2.1.1
# only for testing
responses~=0.24.1
selenium==4.2.0
Expand Down
2 changes: 1 addition & 1 deletion tests/test_donor.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ def test_alias_truly_degenerate(self):
models.Donor.objects.create(alias='Raelcun', alias_num=i)
with self.assertLogs(level=logging.WARNING) as logs:
donor = models.Donor.objects.create(alias='Raelcun')
self.assertRegexpMatches(logs.output[0], 'namespace was full')
self.assertRegex(logs.output[0], 'namespace was full')
self.assertEqual(donor.alias, None, msg='Alias was not cleared')
self.assertEqual(donor.alias_num, None, msg='Alias was not cleared')

Expand Down
4 changes: 2 additions & 2 deletions tests/test_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,13 @@ def setUp(self):
def test_donation_count_annotation(self):
manager = models.Event.objects.with_annotations()
event = manager.get(pk=self.event.pk)
self.assertEquals(event.donation_count, len(self.completed_donations))
self.assertEqual(event.donation_count, len(self.completed_donations))

def test_amount_annotation(self):
manager = models.Event.objects.with_annotations()
event = manager.get(pk=self.event.pk)
total_amount = sum(donation.amount for donation in self.completed_donations)
self.assertAlmostEquals(event.amount, total_amount)
self.assertAlmostEqual(event.amount, total_amount)


class TestEventViews(TransactionTestCase):
Expand Down
7 changes: 7 additions & 0 deletions tests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import json
import logging
import random
import sys
import time
import unittest

Expand Down Expand Up @@ -394,6 +395,12 @@ def _compare_lists(self, expected, found, partial, prefix=''):
results.append(f'index #{n} was unequal: {pair[0]:r} != {pair[1]:r}')
return results

def assertDictContainsSubset(self, subset, dictionary, msg=None):
if sys.version_info < (3, 12):
super().assertDictContainsSubset(subset, dictionary, msg)
else:
self.assertEqual(dictionary, {**dictionary, **subset}, msg)

def assertModelPresent(self, expected_model, data, partial=False, msg=None):
try:
found_model = next(
Expand Down
5 changes: 4 additions & 1 deletion tracker/models/bid.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,10 @@ def clean(self):
same_name = Bid.objects.filter(
speedrun=self.speedrun,
event=self.event,
parent=self.parent,
# FIXME: Django 5.0 started complaining about self.parent during tests, if a child tried to clean itself
# before the parent was saved, but this might not ever happen in the real world, so spend some time
# looking into this when possible
parent=self.parent_id,
name__iexact=self.name,
).exclude(pk=self.pk)
if same_name.exists():
Expand Down

0 comments on commit 03b59ed

Please sign in to comment.