Skip to content
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
006d2e6
Upgrage to Django 4.2.6, Python 3.11
VadimKovalenkoSNF Dec 24, 2025
cf97fc8
Upgrage to Django 5.1.3
VadimKovalenkoSNF Dec 24, 2025
06e8e96
Fix default timezone issue
VadimKovalenkoSNF Dec 24, 2025
b5cea92
Migrate to django-ckeditor-5
VadimKovalenkoSNF Dec 24, 2025
99368bc
Consolidate duplicate index_together compatibility workarounds
VadimKovalenkoSNF Dec 24, 2025
f2370ba
Fix api-block route name collision
VadimKovalenkoSNF Dec 24, 2025
2059c64
Use proper utc timezone
VadimKovalenkoSNF Dec 24, 2025
19e7eb1
Fix flake8 issues
VadimKovalenkoSNF Dec 24, 2025
8212503
Use timezone.make_aware() instead of replace(tzinfo=...)
VadimKovalenkoSNF Dec 24, 2025
ab75ffd
Remove the last timezone.utc usage
VadimKovalenkoSNF Dec 24, 2025
b040ec5
Update release notes
VadimKovalenkoSNF Dec 24, 2025
6bcb0fe
Upd Release notes
VadimKovalenkoSNF Dec 24, 2025
255dc05
Refine phrase in release notes
VadimKovalenkoSNF Dec 24, 2025
59a16b5
Update migrations graph
VadimKovalenkoSNF Dec 29, 2025
2f010ce
Switch the throttling cache backend to django.core.cache.backends.mem…
VadimKovalenkoSNF Dec 29, 2025
7d076f9
Add explicit logging
VadimKovalenkoSNF Dec 29, 2025
905ffce
Fix migration graph
VadimKovalenkoSNF Dec 30, 2025
6c7d60d
Tighten the storage config to always use S3
VadimKovalenkoSNF Dec 30, 2025
996c25e
Use explicit image tag
VadimKovalenkoSNF Dec 30, 2025
6c1b1cb
Use dynamic image tag
VadimKovalenkoSNF Dec 31, 2025
6ca768d
Fix indentation error
VadimKovalenkoSNF Dec 31, 2025
2735b9d
Force S3 bucket storage
VadimKovalenkoSNF Dec 31, 2025
f7f3218
Remove cursor artifacts
VadimKovalenkoSNF Dec 31, 2025
fc6ccba
Add DJANGO_SETTINGS_MODULE to job configs
VadimKovalenkoSNF Dec 31, 2025
831a6a1
Add logs to the list parsing
VadimKovalenkoSNF Jan 1, 2026
2c32b24
Add DJANGO_SETTINGS_MODULE: oar.settings to all job definitions
VadimKovalenkoSNF Jan 1, 2026
b784da8
Introduce FILE_UPLOAD_STORAGE
VadimKovalenkoSNF Jan 1, 2026
9cc1425
Explicit add of DEFAULT_FILE_STORAGE
VadimKovalenkoSNF Jan 1, 2026
40a9b42
Refactor storage init settings
VadimKovalenkoSNF Jan 2, 2026
f0b818d
Updated storage settings
VadimKovalenkoSNF Jan 2, 2026
2ae400a
Revome redundant logs and settings
VadimKovalenkoSNF Jan 2, 2026
0c74d70
Remove legacy CKEDITOR_CONFIGS
VadimKovalenkoSNF Jan 2, 2026
9d1f73c
Use REST_AUTH instead of REST_AUTH_SERIALIZER, use tuple unpacking fo…
VadimKovalenkoSNF Jan 2, 2026
b94c06b
Resolve merge conflicts
VadimKovalenkoSNF Jan 8, 2026
c85c20c
Resolve merge conflicts
VadimKovalenkoSNF Jan 8, 2026
c324f4c
Refactor migration graph, update release notes
VadimKovalenkoSNF Jan 9, 2026
29140b6
Remove duplicated migration
VadimKovalenkoSNF Jan 9, 2026
a8f7cac
Merge branch 'main' into OSDEV-814-upgrade-django-python
VadimKovalenkoSNF Jan 19, 2026
5f580b3
Merge branch 'main' into OSDEV-814-upgrade-django-python
VadimKovalenkoSNF Jan 21, 2026
78b9330
Drop the legacy index_together index in migration 0195
VadimKovalenkoSNF Jan 21, 2026
c46daf1
Remove legacy index and and use AddIndex to create the new api_event_…
VadimKovalenkoSNF Jan 21, 2026
621e455
Update 0197_add_event_index to drop legacy index_together hashes defe…
VadimKovalenkoSNF Jan 21, 2026
352bd36
Upgrade Django to v.5.2
VadimKovalenkoSNF Jan 23, 2026
4d1ea25
Proper configs and import of ckeditor, remove DEFAULT_FILE_STORAGE
VadimKovalenkoSNF Jan 23, 2026
0f4ce23
Merge branch 'main' into OSDEV-814-upgrade-django-python
VadimKovalenkoSNF Jan 23, 2026
fabcfa1
Upgrade dj-rest-auth, drf-yasg, django-ckeditor-5
VadimKovalenkoSNF Jan 23, 2026
f87facb
Update static files backend settings
VadimKovalenkoSNF Jan 23, 2026
35bb08f
Update release notes
VadimKovalenkoSNF Jan 23, 2026
beb1d4b
Set DEFAULT_FILE_STORAGE outside debug mode
VadimKovalenkoSNF Jan 27, 2026
7ba0861
Minor refactoring
VadimKovalenkoSNF Jan 27, 2026
4eef757
Refactor storage settings
VadimKovalenkoSNF Jan 27, 2026
9a0772e
Fix storage condition for static files
VadimKovalenkoSNF Jan 27, 2026
166a51f
Fix admin static manifest by running collectstatic with production st…
VadimKovalenkoSNF Jan 27, 2026
8258471
Bring back TESTING condition
VadimKovalenkoSNF Jan 27, 2026
4cf03ed
Fix storage lookup for Debug mode while CI
VadimKovalenkoSNF Jan 27, 2026
5d396d4
Keep only the valid identifiers and removing CKBox, EasyImage, and I…
VadimKovalenkoSNF Jan 27, 2026
feec362
Merge branch 'main' into OSDEV-814-upgrade-django-python
VadimKovalenkoSNF Jan 30, 2026
333c93b
Bring back SPA storage for static files
VadimKovalenkoSNF Jan 30, 2026
53d3bf5
[Test]: remove production flag in Django Dockerfile
VadimKovalenkoSNF Jan 30, 2026
f64adaa
Remove redundant django-elasticache
VadimKovalenkoSNF Jan 30, 2026
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
18 changes: 18 additions & 0 deletions doc/release/RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@ All notable changes to this project will be documented in this file.

This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). The format is based on the `RELEASE-NOTES-TEMPLATE.md` file.

## Release 2.19.0

## Introduction
* Product name: Open Supply Hub
* Release date: *Provide release date*

### Database changes

#### Migrations
* 0196_switch_partner_field_source_by_editor.py - Migrates `PartnerField.source_by` to CKEditor5 so the rich-text content works after replacing `django-ckeditor` with `django-ckeditor-5` (required for Django 5); keeps existing source descriptions editable with formatting and links.
* 0197_add_event_index.py - Adds an explicit index on `Event(content_type, object_id)` to replace the legacy `index_together` removed in Django 5, keeping the existing schema intact without editing old migrations.

### What's new
* [OSDEV-814](https://opensupplyhub.atlassian.net/browse/OSDEV-814) - Major upgrade of Django application backend services:
* Upgraded Python from `3.8` to `3.11`.
* Upgraded Django from `3.2.17` to `5.1.3`.
* Upgraded Python and Django packages to maintain compatibility.


## Release 2.18.1

Expand Down
7 changes: 1 addition & 6 deletions src/django/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
FROM --platform=linux/amd64 python:3.8-slim-bookworm
FROM --platform=linux/amd64 python:3.11-slim-bookworm

RUN mkdir -p /usr/local/src/static/static
WORKDIR /usr/local/src

COPY requirements.txt /usr/local/src/

# [OSDEV-1785][https://github.com/aio-libs/aiokafka/issues/1093] - Remove aiokafka in requirements.txt to avoid conflicts.
RUN sed -i '/aiokafka/d' requirements.txt

RUN set -ex \
&& buildDeps=" \
build-essential \
Expand All @@ -24,8 +21,6 @@ RUN set -ex \
" \
&& apt-get update && apt-get install -y $buildDeps $deps --no-install-recommends \
&& pip install --no-cache-dir -r requirements.txt \
# [OSDEV-1785][https://github.com/aio-libs/aiokafka/issues/1093] - Install aiokafka from the forked repo.
&& pip install --no-cache-dir git+https://github.com/opensupplyhub/aiokafka.git@kafka-python-2.0.3 \
&& apt-get purge -y --auto-remove $buildDeps \
&& rm -rf /usr/local/src/requirements.txt /var/lib/apt/lists/*

Expand Down
4 changes: 2 additions & 2 deletions src/django/api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from simple_history.admin import SimpleHistoryAdmin
from waffle.models import Flag, Sample, Switch
from waffle.admin import FlagAdmin, SampleAdmin, SwitchAdmin
from ckeditor.widgets import CKEditorWidget
from django_ckeditor_5.widgets import CKEditor5Widget
from jsoneditor.forms import JSONEditor

from api import models
Expand Down Expand Up @@ -256,7 +256,7 @@ def get_ordering(self, request):
class PartnerFieldAdminForm(forms.ModelForm):
source_by = forms.CharField(
required=False,
widget=CKEditorWidget()
widget=CKEditor5Widget(config_name='default')
)
json_schema = forms.JSONField(
required=False,
Expand Down
2 changes: 1 addition & 1 deletion src/django/api/close_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
def close_list(list_id, user_id):
user = User.objects.get(id=user_id)
contributor = Contributor.objects.get(admin=user)
now = datetime.now(tz=timezone.utc)
now = datetime.now(tz=timezone.get_default_timezone())
reason = "Closed via bulk list closure"
facilities = Facility.objects.filter(
facilitylistitem__source__facility_list_id=list_id)
Expand Down
21 changes: 14 additions & 7 deletions src/django/api/limitation/date/monthly_date_limitation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime
from datetime import datetime, timezone as dt_timezone
from dateutil.relativedelta import relativedelta
from django.utils import timezone

Expand All @@ -10,12 +10,19 @@
class MonthlyDateLimitation(DateLimitation):

def execute(self, period_start_date: datetime):
utc = timezone.utc
self.start_date = period_start_date

one_month_in_past = datetime.now(tz=utc) - relativedelta(months=1)
while (self.start_date < one_month_in_past):
self.start_date = self.start_date + relativedelta(months=1)
default_tz = timezone.get_default_timezone()
start_date = period_start_date
if timezone.is_naive(start_date):
start_date = timezone.make_aware(start_date, default_tz)
start_date_utc = start_date.astimezone(dt_timezone.utc)

one_month_ago_utc = (
datetime.now(tz=dt_timezone.utc) - relativedelta(months=1)
)
while start_date_utc < one_month_ago_utc:
start_date_utc = start_date_utc + relativedelta(months=1)

self.start_date = start_date_utc

return self

Expand Down
4 changes: 2 additions & 2 deletions src/django/api/limitation/date/yearly_date_limitation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
class YearlyDateLimitation(DateLimitation):

def execute(self, period_start_date: datetime):
utc = timezone.utc
default_tz = timezone.get_default_timezone()
self.start_date = period_start_date

one_year_in_past = datetime.now(tz=utc) - relativedelta(years=1)
one_year_in_past = datetime.now(tz=default_tz) - relativedelta(years=1)
while (self.start_date < one_year_in_past):
self.start_date = self.start_date + relativedelta(years=1)

Expand Down
2 changes: 1 addition & 1 deletion src/django/api/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def has_active_block(request):

contributor = token.user.contributor
apiBlock = get_api_block(contributor)
at_datetime = datetime.datetime.now(tz=timezone.utc)
at_datetime = datetime.datetime.now(tz=timezone.get_default_timezone())
return (apiBlock is not None and
apiBlock.until > at_datetime and apiBlock.active)
except ObjectDoesNotExist:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from django.db import migrations
import django_ckeditor_5.fields


class Migration(migrations.Migration):

dependencies = [
('api', '0195_add_mit_livingwage_partner_field'),
]

operations = [
migrations.AlterField(
model_name='partnerfield',
name='source_by',
field=django_ckeditor_5.fields.CKEditor5Field(
blank=True,
help_text='Rich text field describing the source of this partner field.',
null=True,
),
),
]

24 changes: 24 additions & 0 deletions src/django/api/migrations/0197_add_event_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("api", "0196_switch_partner_field_source_by_editor"),
]

operations = [
migrations.RunSQL(
sql=[
"DROP INDEX IF EXISTS api_event_content_type_id_object_id_idx",
"DROP INDEX IF EXISTS api_event_content_type_id_object_id_b046420a_idx",
],
reverse_sql=migrations.RunSQL.noop,
),
migrations.AddIndex(
model_name="event",
index=models.Index(
fields=["content_type", "object_id"],
name="api_event_content_object_idx",
),
),
]
7 changes: 6 additions & 1 deletion src/django/api/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@

class Event(models.Model):
class Meta:
index_together = (('content_type', 'object_id'),)
indexes = [
models.Index(
fields=['content_type', 'object_id'],
name='api_event_content_object_idx',
),
]

EVENT_TYPE_CHOICES = (
(FacilityHistoryActions.CREATE, FacilityHistoryActions.CREATE),
Expand Down
8 changes: 3 additions & 5 deletions src/django/api/models/partner_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from django.db import models
from django.core.cache import cache
from ckeditor.fields import RichTextField
from django_ckeditor_5.fields import CKEditor5Field

from api.constants import PARTNER_FIELD_LIST_KEY, PARTNER_FIELD_NAMES_KEY
from api.models.partner_field_manager import PartnerFieldManager
Expand Down Expand Up @@ -68,13 +68,11 @@ class Meta:
max_length=500,
blank=True,
)
source_by = RichTextField(
source_by = CKEditor5Field(
blank=True,
null=True,
config_name="default",
help_text=(
"Rich text field describing the "
"source of this partner field."
"Rich text field describing the source of this partner field."
),
)
json_schema = models.JSONField(
Expand Down
2 changes: 1 addition & 1 deletion src/django/api/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def parse_production_location_list(location_list: FacilityList):

def geocode_facility_list_item(item):
started = str(timezone.now())
if type(item) != FacilityListItem:
if not isinstance(item, FacilityListItem):
raise ValueError('Argument must be a FacilityListItem')
if item.status == FacilityListItem.ITEM_REMOVED:
raise ItemRemovedException()
Expand Down
10 changes: 8 additions & 2 deletions src/django/api/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ def monthly_promoted_name_and_address():
def geocoding_time_without_queue(date_format):
temp_data = dict()

start_date = datetime.now(tz=timezone.utc) - relativedelta(months=4)
start_date = (
datetime.now(tz=timezone.get_default_timezone())
- relativedelta(months=4)
)
processing_results = FacilityListItem.objects.filter(
created_at__gte=start_date,
processing_results__contains=[{"action": "geocode"}]
Expand Down Expand Up @@ -89,7 +92,10 @@ def weekly_geocoding_time_without_queue():

def geocoding_time_with_queue(date_format):
temp_data = dict()
start_date = datetime.now(tz=timezone.utc) - relativedelta(months=4)
start_date = (
datetime.now(tz=timezone.get_default_timezone())
- relativedelta(months=4)
)
listitems = FacilityListItem.objects.filter(
created_at__gte=start_date,
processing_results__contains=[{"action": "geocode"}]
Expand Down
5 changes: 2 additions & 3 deletions src/django/api/serializers/facility/utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
from typing import (Union)
from itertools import groupby
from datetime import timezone as dt_timezone

from api.constants import (
FacilityClaimStatuses,
FacilitiesQueryParams
)
from dateutil import parser
from django.utils import timezone

from ...helpers.helpers import (
cleanup_data,
replace_invalid_data,
Expand Down Expand Up @@ -265,7 +264,7 @@ def format_date(date: str) -> str:
original_datetime = parser.isoparse(date)

# Convert the datetime object to UTC.
utc_datetime = original_datetime.astimezone(timezone.utc)
utc_datetime = original_datetime.astimezone(dt_timezone.utc)

# Format the UTC datetime with the "Z" notation.
formatted_datetime_str = utc_datetime.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from rest_auth.serializers import PasswordResetConfirmSerializer
from dj_rest_auth.serializers import PasswordResetConfirmSerializer
from django.db import transaction


Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from rest_auth.serializers import PasswordResetSerializer
from dj_rest_auth.serializers import PasswordResetSerializer
from rest_framework.exceptions import ValidationError
from rest_framework.serializers import EmailField
from django.conf import settings
Expand Down
2 changes: 1 addition & 1 deletion src/django/api/throttles.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


class UserCustomRateThrottle(UserRateThrottle):
"""Allow custom per-user throttle rates defined on custom Django user model.
"""Allow per-user throttle rates defined on the custom user model.

`model_rate_field`: Specify the throttle rate field. Required.
"""
Expand Down
2 changes: 1 addition & 1 deletion src/django/api/views/auth/login_to_oar_client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from allauth.account.models import EmailAddress
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.response import Response
from rest_auth.views import LoginView
from dj_rest_auth.views import LoginView
from django.contrib.auth import authenticate, login

from ...serializers.user.user_serializer import UserSerializer
Expand Down
2 changes: 1 addition & 1 deletion src/django/api/views/auth/logout_to_oar_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from rest_framework import status
from rest_framework.response import Response
from rest_auth.views import LogoutView
from dj_rest_auth.views import LogoutView
from django.contrib.auth import logout

from ...serializers.user.user_serializer import UserSerializer
Expand Down
1 change: 1 addition & 0 deletions src/django/ckeditor/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Compatibility shim for legacy migrations that import `ckeditor`."""
10 changes: 10 additions & 0 deletions src/django/ckeditor/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.db import models


class RichTextField(models.TextField):
"""Minimal stub to satisfy legacy migrations."""

def __init__(self, *args, **kwargs):
# Legacy migrations pass config_name; ignore it for compatibility.
kwargs.pop("config_name", None)
super().__init__(*args, **kwargs)
10 changes: 10 additions & 0 deletions src/django/ckeditor/widgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.forms import Textarea


class CKEditorWidget(Textarea):
"""Minimal stub to satisfy any legacy imports."""

def __init__(self, *args, **kwargs):
# Ignore legacy config_name argument if provided.
kwargs.pop("config_name", None)
super().__init__(*args, **kwargs)
Loading