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

Test on python 3.11, Django 4.2 #2850

Open
wants to merge 5 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/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: ["3.9", "3.10"]
python-version: ["3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v4
Expand Down
56 changes: 31 additions & 25 deletions python/nav/models/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from django import forms
from django.db import models
from django.db.models import signals
from django.db.models.fields.mixins import FieldCacheMixin
from django.core import exceptions
from django.db.models import Q
from django.apps import apps
Expand Down Expand Up @@ -146,7 +147,7 @@ def formfield(self, **kwargs):
# this interfaces with Django model protocols, which generates unnecessary
# pylint violations:
# pylint: disable=W0201,W0212
class LegacyGenericForeignKey(object):
class LegacyGenericForeignKey(FieldCacheMixin):
"""Generic foreign key for legacy NAV database.

Some legacy tables in NAV have generic foreign keys that look very much
Expand All @@ -155,16 +156,25 @@ class LegacyGenericForeignKey(object):

"""

# Field flags
auto_created = False
concrete = False
editable = False
hidden = False

is_relation = True
many_to_many = False
many_to_one = True
one_to_one = False
related_model = None
remote_field = None

def __init__(self, model_name_field, model_fk_field, for_concrete_model=True):
self.mn_field = model_name_field
self.fk_field = model_fk_field
self.is_relation = True
self.many_to_many = False
self.one_to_many = True
self.related_model = None
self.auto_created = False
self.for_concrete_model = for_concrete_model
self.editable = False
self.for_concrete_model = for_concrete_model

def __str__(self):
modelname = getattr(self, 'mn_field')
Expand All @@ -175,14 +185,16 @@ def contribute_to_class(self, cls, name):
"""Add things to the model class using this descriptor"""
self.name = name
self.model = cls
self.cache_attr = "_%s_cache" % name
cls._meta.private_fields.append(self)

if not cls._meta.abstract:
signals.pre_init.connect(self.instance_pre_init, sender=cls)

setattr(cls, name, self)

def get_cache_name(self):
return self.name

def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs):
"""
Handles initializing an object with the generic FK instead of
Expand All @@ -197,28 +209,22 @@ def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs):
kwargs[self.mn_field] = None
kwargs[self.fk_field] = None

def is_cached(self, instance):
return hasattr(instance, self.cache_attr)

def __get__(self, instance, instance_type=None):
if instance is None:
return self

try:
return getattr(instance, self.cache_attr)
except AttributeError:
rel_obj = None
rel_obj = self.get_cached_value(instance, default=None)

field = self.model._meta.get_field(self.mn_field)
table_name = getattr(instance, field.get_attname(), None)
rel_model = self.get_model_class(table_name)
if rel_model:
try:
rel_obj = rel_model.objects.get(id=getattr(instance, self.fk_field))
except exceptions.ObjectDoesNotExist:
pass
setattr(instance, self.cache_attr, rel_obj)
return rel_obj
field = self.model._meta.get_field(self.mn_field)
table_name = getattr(instance, field.get_attname(), None)
rel_model = self.get_model_class(table_name)
if rel_model:
try:
rel_obj = rel_model.objects.get(id=getattr(instance, self.fk_field))
except exceptions.ObjectDoesNotExist:
pass
self.set_cached_value(instance, rel_obj)
return rel_obj

def __set__(self, instance, value):
if instance is None:
Expand All @@ -232,7 +238,7 @@ def __set__(self, instance, value):

setattr(instance, self.mn_field, table_name)
setattr(instance, self.fk_field, fkey)
setattr(instance, self.cache_attr, value)
self.set_cached_value(instance, value)

@staticmethod
def get_model_name(obj) -> str:
Expand Down
3 changes: 2 additions & 1 deletion python/nav/web/auth/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
authorization_not_required,
)
from nav.web.auth.sudo import get_sudoer
from nav.web.utils import is_ajax


_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -107,7 +108,7 @@ def redirect_to_login(self, request):
response.

"""
if request.is_ajax():
if is_ajax(request):
return HttpResponse(status=401)

new_url = get_login_url(request)
Expand Down
8 changes: 4 additions & 4 deletions python/nav/web/syslogger/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from nav.models.logger import LogMessage
from nav.models.logger import ErrorError
from nav.web.syslogger.forms import LoggerGroupSearchForm
from nav.web.utils import create_title
from nav.web.utils import create_title, is_ajax


DATEFORMAT = "%Y-%m-%d %H:%M:%S"
Expand Down Expand Up @@ -204,7 +204,7 @@ def index(request):


def group_search(request):
if not request.is_ajax():
if not is_ajax(request):
return HttpResponseRedirect(reverse(index) + '?' + request.GET.urlencode())
return handle_search(request, LoggerGroupSearchForm, reverse(group_search))

Expand All @@ -213,7 +213,7 @@ def exceptions_response(request):
"""
Handler for exception-mode.
"""
if not request.is_ajax():
if not is_ajax(request):
return HttpResponseRedirect(reverse(index) + '?' + request.GET.urlencode())

account = get_account(request)
Expand All @@ -236,7 +236,7 @@ def errors_response(request):
"""
Handler for error-mode.
"""
if not request.is_ajax():
if not is_ajax(request):
return HttpResponseRedirect(reverse(index) + '?' + request.GET.urlencode())

account = get_account(request)
Expand Down
4 changes: 4 additions & 0 deletions python/nav/web/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
import qrcode.image.pil


def is_ajax(request):
return request.headers.get("x-requested-with") == "XMLHttpRequest"


def get_navpath_root():
"""Returns the default navpath root

Expand Down
2 changes: 1 addition & 1 deletion requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ feedparser==6.0.8
dnspython<3.0.0,>=2.1.0

django-filter>=2
djangorestframework>=3.12,<3.13
djangorestframework>=3.12

# REST framework
iso8601
Expand Down
1 change: 1 addition & 0 deletions requirements/django32.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
Django>=3.2,<3.3
djangorestframework>=3.12,<3.13
2 changes: 2 additions & 0 deletions requirements/django42.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Django>=4.2,<4.3
pytz
1 change: 1 addition & 0 deletions tests/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ RUN add-apt-repository ppa:deadsnakes/ppa && \
curl git build-essential \
python3.9-dbg python3.9-dev \
python3.10-dbg python3.10-dev \
python3.11-dbg python3.11-dev \
python3-pip

RUN echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list
Expand Down
4 changes: 3 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
[tox]
envlist =
{unit,integration,functional}-py{39,310}-django{32}
{unit,integration,functional}-py{39,310,311}-django{42}
javascript
docs
basepython = python3.9
Expand All @@ -21,6 +22,7 @@ markers =
python =
3.9: py39
3.10: py310
3.11: py311

[testenv]
# Baseline test environment
Expand All @@ -43,7 +45,7 @@ setenv =
COVERAGE_FILE = {toxinidir}/reports/coverage/.coverage
PYTHONFAULTHANDLER=1
django32: DJANGO_VER=32
django40: DJANGO_VER=40
django42: DJANGO_VER=42
passenv =
C_INCLUDE_PATH
GITHUB_ACTIONS
Expand Down
Loading