diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 05794c40a5..d42383c1b5 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -5,7 +5,6 @@ name: CI
# push or pull request events but only for the ng branch
on:
push:
- branches: [ ng ]
pull_request:
branches: [ ng ]
diff --git a/requirements/base.txt b/requirements/base.txt
index a1a56cad95..636c08b6a4 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -1,22 +1,21 @@
-r openstack.txt
-r hermes.txt
-Django==1.8.19
+Django==1.10.8
dj.choices==0.11.0
django-extensions==1.7.5
django-filter==0.13.0
-django-import-export==0.4.2
-django-money==0.15.1
+django-import-export==1.2.0
+django-money==0.12
py-moneyed==1.2
django-mptt==0.8.7
-django-reversion==1.8.6
+django-reversion==2.0
django-rq==2.0
django-sitetree==1.7.0
-django-taggit==0.17.1
-django-taggit-serializer==0.1.5
+django-taggit==0.22.2
+django-taggit-serializer==0.1.7
django-threadlocals==0.8
-django-transaction-hooks==0.2 # it's merged to Django 1.9 - remove this when Django version will be bumped to 1.9
django-cryptography==0.3
-djangorestframework==3.2.2
+djangorestframework==3.4.0
djangorestframework_xml==1.2.0
drf-nested-routers==0.11.1
Markdown<3.0 # headerid extension removed in 3.0 - see #3313 for details
@@ -37,4 +36,3 @@ Faker==0.9.0
openpyxl==2.4.0
typing==3.6.6
Pillow==6.2.2
-
diff --git a/requirements/code_style.txt b/requirements/code_style.txt
index 65148d97ee..52e99b140f 100644
--- a/requirements/code_style.txt
+++ b/requirements/code_style.txt
@@ -1,2 +1,3 @@
flake8==3.0.4
-isort==4.2.5
\ No newline at end of file
+isort==4.2.5
+pyflakes==1.5.0
diff --git a/src/ralph/access_cards/admin.py b/src/ralph/access_cards/admin.py
index 7cc76b4abb..3554a175b0 100644
--- a/src/ralph/access_cards/admin.py
+++ b/src/ralph/access_cards/admin.py
@@ -1,7 +1,8 @@
from django.utils.translation import ugettext_lazy as _
from ralph.access_cards.models import AccessCard, AccessZone
-from ralph.admin import RalphAdmin, RalphMPTTAdmin, register
+from ralph.admin.decorators import register
+from ralph.admin.mixins import RalphAdmin, RalphMPTTAdmin
from ralph.lib.transitions.admin import TransitionAdminMixin
diff --git a/src/ralph/accessories/admin.py b/src/ralph/accessories/admin.py
index 6afc1ace73..f7c32eb480 100644
--- a/src/ralph/accessories/admin.py
+++ b/src/ralph/accessories/admin.py
@@ -1,7 +1,8 @@
from django.utils.translation import ugettext_lazy as _
from ralph.accessories.models import Accessory, AccessoryUser
-from ralph.admin import RalphAdmin, RalphTabularInline, register
+from ralph.admin.decorators import register
+from ralph.admin.mixins import RalphAdmin, RalphTabularInline
from ralph.admin.views.extra import RalphDetailViewAdmin
from ralph.lib.transitions.admin import TransitionAdminMixin
diff --git a/src/ralph/accessories/models.py b/src/ralph/accessories/models.py
index a1eb6a0ec9..28d2eadf7d 100644
--- a/src/ralph/accessories/models.py
+++ b/src/ralph/accessories/models.py
@@ -1,4 +1,3 @@
-import reversion
from dj.choices import Choices
from django import forms
from django.conf import settings
@@ -8,6 +7,7 @@
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from mptt.fields import TreeForeignKey
+from reversion import revisions as reversion
from ralph.accounts.models import RalphUser, Regionalizable
from ralph.assets.models import Category, Manufacturer
@@ -18,6 +18,7 @@
from ralph.lib.transitions.fields import TransitionField
from ralph.lib.transitions.models import TransitionWorkflowBaseWithPermissions
+
_SELECT_USED_ACCESSORY_QUERY = """
SELECT COALESCE(SUM({assignment_table}.{quantity_column}), 0)
FROM {assignment_table}
diff --git a/src/ralph/accounts/admin.py b/src/ralph/accounts/admin.py
index 9012a9aee3..becc75c4f1 100644
--- a/src/ralph/accounts/admin.py
+++ b/src/ralph/accounts/admin.py
@@ -12,14 +12,15 @@
from django.forms.models import model_to_dict
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
+from reversion import revisions as reversion
from ralph.accounts.models import RalphUser, Region, Team
-from ralph.admin import RalphAdmin, register
+from ralph.admin.decorators import register
from ralph.admin.helpers import getattr_dunder
-from ralph.admin.mixins import RalphAdminFormMixin
+from ralph.admin.mixins import RalphAdmin, RalphAdminFormMixin
from ralph.admin.views.extra import RalphDetailView
from ralph.back_office.models import BackOfficeAsset, BackOfficeAssetStatus
-from ralph.lib.table import Table
+from ralph.lib.table.table import Table
from ralph.lib.transitions.models import TransitionsHistory
from ralph.licences.models import Licence
from ralph.sim_cards.models import SIMCard
@@ -338,8 +339,8 @@ def user_change_password(self, request, id, form_url=''):
if not self.has_change_permission(request, obj=user):
raise PermissionDenied
-
- return super().user_change_password(request, id, form_url)
+ with reversion.create_revision():
+ return super().user_change_password(request, id, form_url)
@register(Group)
diff --git a/src/ralph/accounts/ldap.py b/src/ralph/accounts/ldap.py
index 005e3fa222..d8cdabe5bd 100644
--- a/src/ralph/accounts/ldap.py
+++ b/src/ralph/accounts/ldap.py
@@ -7,9 +7,6 @@
from django.dispatch import receiver
from django.utils.encoding import force_text
from django_auth_ldap.backend import _LDAPUser, LDAPSettings, populate_user
-from django_auth_ldap.config import ActiveDirectoryGroupType
-
-from ralph.accounts.management.commands.ldap_sync import get_nested_groups
logger = logging.getLogger(__name__)
@@ -102,87 +99,3 @@ def manager_country_attribute_populate(
user.country = Country.id_from_name(country.lower())
except ValueError:
user.country = None
-
-
-class MappedGroupOfNamesType(ActiveDirectoryGroupType):
- """Provide group mappings described in project settings."""
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self._ldap_groups = None
-
- @property
- def ldap_groups(self):
- """
- Composition of flat LDAP groups (taken from `AUTH_LDAP_GROUP_MAPPING`)
- and nested groups (taken from `AUTH_LDAP_NESTED_GROUPS`).
-
- Returns: dict with both flat and nested LDAP groups.
- """
- if not self._ldap_groups:
- logger.debug('Evaluating LDAP groupd from settings')
- self._ldap_flat_groups = getattr(
- settings, 'AUTH_LDAP_GROUP_MAPPING', {}
- )
- self._ldap_nested_groups = getattr(
- settings, 'AUTH_LDAP_NESTED_GROUPS', {}
- )
- self._ldap_groups = self._ldap_flat_groups.copy()
- self._ldap_groups.update(self._ldap_nested_groups)
- return self._ldap_groups
-
- def _get_group(self, group_dn, ldap_user, group_search):
- base_dn = group_search.base_dn
- group_search.base_dn = force_text(group_dn)
- group = group_search.execute(ldap_user.connection)[0]
- group_search.base_dn = base_dn
- return group
-
- def user_groups(self, ldap_user, group_search):
- """Get groups which user belongs to."""
- group_map = []
-
- def handle_groups(groups_dns):
- """
- Compare user groups with groups accepted by Ralph
- (`self.ldap_groups`) and for each in common get LDAP group
-
- Args:
- groups_dns: set of user groups DNs
- """
- for group_dn in groups_dns & set(self.ldap_groups.keys()):
- group = self._get_group(group_dn, ldap_user, group_search)
- group_map.append(group)
-
- username = ldap_user.attrs[settings.AUTH_LDAP_USER_USERNAME_ATTR][0]
-
- # handle flat groups first (to which user belongs directly)
- try:
- flat_groups_dns = set(map(force_text, ldap_user.attrs['memberOf']))
- except KeyError:
- flat_groups_dns = set()
- logger.info('Flat groups DNs for {}: {}'.format(
- username, flat_groups_dns
- ))
- handle_groups(flat_groups_dns)
-
- # handle nested groups
- nested_groups_dns = get_nested_groups()[1].get(username, set())
- logger.info('Nested groups DNs for {}: {}'.format(
- username, nested_groups_dns
- ))
- handle_groups(nested_groups_dns)
- return group_map
-
- def group_name_from_info(self, group_info):
- """Map ldap group names into ralph names if mapping defined."""
- if self.ldap_groups:
- for dn in group_info[1]['distinguishedname']:
- mapped = self.ldap_groups.get(dn)
- if mapped:
- return mapped
- # return original name if mapping not defined
- else:
- return super(
- MappedGroupOfNamesType,
- self
- ).group_name_from_info(group_info)
diff --git a/src/ralph/accounts/ldap_helpers.py b/src/ralph/accounts/ldap_helpers.py
new file mode 100644
index 0000000000..2f7488ca9d
--- /dev/null
+++ b/src/ralph/accounts/ldap_helpers.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+import logging
+
+from django.conf import settings
+from django.utils.encoding import force_text
+from django_auth_ldap.config import ActiveDirectoryGroupType
+
+
+logger = logging.getLogger(__name__)
+
+
+class MappedGroupOfNamesType(ActiveDirectoryGroupType):
+ """Provide group mappings described in project settings."""
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._ldap_groups = None
+
+ @property
+ def ldap_groups(self):
+ """
+ Composition of flat LDAP groups (taken from `AUTH_LDAP_GROUP_MAPPING`)
+ and nested groups (taken from `AUTH_LDAP_NESTED_GROUPS`).
+
+ Returns: dict with both flat and nested LDAP groups.
+ """
+ if not self._ldap_groups:
+ logger.debug('Evaluating LDAP groupd from settings')
+ self._ldap_flat_groups = getattr(
+ settings, 'AUTH_LDAP_GROUP_MAPPING', {}
+ )
+ self._ldap_nested_groups = getattr(
+ settings, 'AUTH_LDAP_NESTED_GROUPS', {}
+ )
+ self._ldap_groups = self._ldap_flat_groups.copy()
+ self._ldap_groups.update(self._ldap_nested_groups)
+ return self._ldap_groups
+
+ def _get_group(self, group_dn, ldap_user, group_search):
+ base_dn = group_search.base_dn
+ group_search.base_dn = force_text(group_dn)
+ group = group_search.execute(ldap_user.connection)[0]
+ group_search.base_dn = base_dn
+ return group
+
+ def user_groups(self, ldap_user, group_search):
+ """Get groups which user belongs to."""
+ group_map = []
+
+ def handle_groups(groups_dns):
+ """
+ Compare user groups with groups accepted by Ralph
+ (`self.ldap_groups`) and for each in common get LDAP group
+
+ Args:
+ groups_dns: set of user groups DNs
+ """
+ for group_dn in groups_dns & set(self.ldap_groups.keys()):
+ group = self._get_group(group_dn, ldap_user, group_search)
+ group_map.append(group)
+
+ username = ldap_user.attrs[settings.AUTH_LDAP_USER_USERNAME_ATTR][0]
+
+ # handle flat groups first (to which user belongs directly)
+ try:
+ flat_groups_dns = set(map(force_text, ldap_user.attrs['memberOf']))
+ except KeyError:
+ flat_groups_dns = set()
+ logger.info('Flat groups DNs for {}: {}'.format(
+ username, flat_groups_dns
+ ))
+ handle_groups(flat_groups_dns)
+ from ralph.accounts.management.commands.ldap_sync import get_nested_groups # noqa
+ # handle nested groups
+ nested_groups_dns = get_nested_groups()[1].get(username, set())
+ logger.info('Nested groups DNs for {}: {}'.format(
+ username, nested_groups_dns
+ ))
+ handle_groups(nested_groups_dns)
+ return group_map
+
+ def group_name_from_info(self, group_info):
+ """Map ldap group names into ralph names if mapping defined."""
+ if self.ldap_groups:
+ for dn in group_info[1]['distinguishedname']:
+ mapped = self.ldap_groups.get(dn)
+ if mapped:
+ return mapped
+ # return original name if mapping not defined
+ else:
+ return super(
+ MappedGroupOfNamesType,
+ self
+ ).group_name_from_info(group_info)
diff --git a/src/ralph/accounts/migrations/0007_auto_20240506_1128.py b/src/ralph/accounts/migrations/0007_auto_20240506_1128.py
new file mode 100644
index 0000000000..80aef8bd63
--- /dev/null
+++ b/src/ralph/accounts/migrations/0007_auto_20240506_1128.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.13 on 2024-05-06 11:28
+from __future__ import unicode_literals
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('accounts', '0006_remove_ralphuser_gender'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='ralphuser',
+ name='username',
+ field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username'),
+ ),
+ ]
diff --git a/src/ralph/accounts/migrations/0008_auto_20240507_1422.py b/src/ralph/accounts/migrations/0008_auto_20240507_1422.py
new file mode 100644
index 0000000000..634fb4f39b
--- /dev/null
+++ b/src/ralph/accounts/migrations/0008_auto_20240507_1422.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.1 on 2024-05-07 14:22
+from __future__ import unicode_literals
+
+import django.contrib.auth.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('accounts', '0007_auto_20240506_1128'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='ralphuser',
+ name='username',
+ field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'),
+ ),
+ ]
diff --git a/src/ralph/accounts/models.py b/src/ralph/accounts/models.py
index 4aca57acc1..8924e5f60c 100644
--- a/src/ralph/accounts/models.py
+++ b/src/ralph/accounts/models.py
@@ -13,7 +13,7 @@
from ralph.admin.autocomplete import AutocompleteTooltipMixin
from ralph.lib.mixins.models import AdminAbsoluteUrlMixin, NamedMixin
-from ralph.lib.permissions import (
+from ralph.lib.permissions.models import (
PermByFieldMixin,
PermissionsForObjectMixin,
user_permission
@@ -164,12 +164,10 @@ def has_any_perms(self, perms, obj=None):
return any([self.has_perm(p, obj=obj) for p in perms])
def save(self, *args, **kwargs):
- # set default values if None provided
- for field in ('country',):
- val = getattr(self, field)
- if val is None:
- val = self._meta.get_field_by_name(field)[0].default
- setattr(self, field, val)
+ if isinstance(self.country, str):
+ self.country = Country.from_name(self.country.lower()).id
+ elif self.country is None:
+ self.country = Country.pl.id
return super().save(*args, **kwargs)
@property
diff --git a/src/ralph/accounts/views.py b/src/ralph/accounts/views.py
index 643673a4fe..acfa754752 100644
--- a/src/ralph/accounts/views.py
+++ b/src/ralph/accounts/views.py
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
from datetime import date
-import reversion
from django.conf import settings
from django.contrib import messages
from django.core.urlresolvers import reverse
@@ -9,6 +8,7 @@
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from django.views.generic import View
+from reversion import revisions as reversion
from ralph.accounts.admin import (
AssetList,
diff --git a/src/ralph/admin/__init__.py b/src/ralph/admin/__init__.py
index a584458339..ac25e79c80 100644
--- a/src/ralph/admin/__init__.py
+++ b/src/ralph/admin/__init__.py
@@ -1,22 +1 @@
-from ralph.admin.sites import ralph_site
-from ralph.admin.mixins import (
- RalphAdmin,
- RalphAdminForm,
- RalphMPTTAdmin,
- RalphStackedInline,
- RalphTabularInline,
-)
-from ralph.admin.decorators import register
-
default_app_config = 'ralph.admin.apps.RalphAdminConfig'
-
-__all__ = [
- 'ralph_site',
- 'default_app_config',
- 'register',
- 'RalphAdmin',
- 'RalphAdminForm',
- 'RalphMPTTAdmin',
- 'RalphStackedInline',
- 'RalphTabularInline',
-]
diff --git a/src/ralph/admin/apps.py b/src/ralph/admin/apps.py
index ff3cf25b7b..e5b701d881 100644
--- a/src/ralph/admin/apps.py
+++ b/src/ralph/admin/apps.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from ralph.admin.filters import register_custom_filters
+
from ralph.apps import RalphAppConfig
@@ -9,5 +9,6 @@ class RalphAdminConfig(RalphAppConfig):
verbose_name = 'Ralph Admin'
def ready(self):
+ from ralph.admin.filters import register_custom_filters
register_custom_filters()
super().ready()
diff --git a/src/ralph/admin/autocomplete.py b/src/ralph/admin/autocomplete.py
index d012dcc46c..bfbcfcebfc 100644
--- a/src/ralph/admin/autocomplete.py
+++ b/src/ralph/admin/autocomplete.py
@@ -4,10 +4,10 @@
from functools import reduce
from dj.choices import Choices
+from django.apps import apps
from django.conf.urls import url
from django.core.exceptions import FieldDoesNotExist
from django.db.models import Manager, Q
-from django.db.models.loading import get_model
from django.http import Http404, HttpResponseBadRequest, JsonResponse
from django.views.generic import View
@@ -164,7 +164,7 @@ class AutocompleteList(SuggestView):
def dispatch(self, request, *args, **kwargs):
try:
- model = get_model(kwargs['app'], kwargs['model'])
+ model = apps.get_model(kwargs['app'], kwargs['model'])
except LookupError:
return HttpResponseBadRequest('Model not found')
diff --git a/src/ralph/admin/m2m.py b/src/ralph/admin/m2m.py
index bf9c77566a..c47615ab15 100644
--- a/src/ralph/admin/m2m.py
+++ b/src/ralph/admin/m2m.py
@@ -55,8 +55,11 @@ class AuthorAdmin(admin.ModelAdmin):
from django.utils.text import get_text_list
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphStackedInline, RalphTabularInline
-from ralph.admin.mixins import RalphAdminForm
+from ralph.admin.mixins import (
+ RalphAdminForm,
+ RalphStackedInline,
+ RalphTabularInline
+)
class BaseInlineM2MFormset(BaseInlineFormSet):
@@ -126,8 +129,9 @@ def get_m2m(parent_model, model):
"""
# need to check on both sides (m2m field will be defined only in one of the
# models)
- for rel in parent_model._meta.get_all_related_many_to_many_objects():
- if issubclass(rel.related_model, model):
+ for rel in parent_model._meta.get_fields(include_hidden=True):
+ if rel.many_to_many and rel.auto_created and \
+ issubclass(rel.related_model, model):
return rel.field
for rel in parent_model._meta.many_to_many:
@@ -143,7 +147,7 @@ def get_foreign_key_for_m2m(parent_model, m2m):
parent_model: Django model for which admin inline is created
m2m: ManyToManyField relation instance
"""
- for field in m2m.related.through._meta.fields:
+ for field in m2m.remote_field.through._meta.fields:
if (
isinstance(field, ForeignKey) and
issubclass(parent_model, field.rel.to)
diff --git a/src/ralph/admin/mixins.py b/src/ralph/admin/mixins.py
index de146bf8cc..23a0b7d5d0 100644
--- a/src/ralph/admin/mixins.py
+++ b/src/ralph/admin/mixins.py
@@ -21,7 +21,8 @@
from import_export.admin import ImportExportModelAdmin
from import_export.widgets import ForeignKeyWidget
from mptt.admin import MPTTAdminForm, MPTTModelAdmin
-from reversion import VersionAdmin
+
+from reversion.admin import VersionAdmin
from ralph.admin import widgets
from ralph.admin.autocomplete import AjaxAutocompleteMixin
@@ -168,29 +169,29 @@ class RalphAdminChecks(admin.checks.ModelAdminChecks):
('contenttypes', 'ContentType'.lower())
)
- def check(self, cls, model, **kwargs):
- errors = super().check(cls, model, **kwargs)
- errors.extend(self._check_absolute_url(cls, model))
+ def check(self, model, **kwargs):
+ errors = super().check(model, **kwargs)
+ errors.extend(self._check_absolute_url(model.model))
return errors
- def _check_form(self, cls, model):
+ def _check_form(self, model):
"""
Check if form subclasses RalphAdminFormMixin
"""
- result = super()._check_form(cls, model)
+ result = super()._check_form(model)
if (
- hasattr(cls, 'form') and
- not issubclass(cls.form, RalphAdminFormMixin)
+ hasattr(model, 'form') and
+ not issubclass(model.form, RalphAdminFormMixin)
):
result += admin.checks.must_inherit_from(
parent='RalphAdminFormMixin',
option='form',
- obj=cls,
+ obj=model,
id='admin.E016'
)
return result
- def _check_absolute_url(self, cls, model):
+ def _check_absolute_url(self, model):
"""
Check if model inherit from AdminAbsoluteUrlMixin
"""
@@ -212,6 +213,10 @@ def _check_absolute_url(self, cls, model):
class DashboardChangelistMixin(object):
+ # TODO(Django-1.11) remove and check if
+ # test_patch_deadline_filters_hosts passes
+ def lookup_allowed(self, *args, **kwargs):
+ return True
def _is_graph_preview_view(self, request):
return request.GET.get('graph-query', '')
@@ -474,7 +479,7 @@ class RalphAdmin(
RalphAdminImportExportMixin,
AjaxAutocompleteMixin,
RalphAdminMixin,
- VersionAdmin
+ VersionAdmin,
):
@property
def media(self):
diff --git a/src/ralph/admin/static/auto-complete-helpers.js b/src/ralph/admin/static/auto-complete-helpers.js
index d68c91a96d..3ea5f5add5 100644
--- a/src/ralph/admin/static/auto-complete-helpers.js
+++ b/src/ralph/admin/static/auto-complete-helpers.js
@@ -24,10 +24,6 @@ function dismissChangeRelatedObjectPopup(win, objId, newRepr, newId) {
}
function dismissAddRelatedObjectPopup(win, newId, newRepr) {
- // newId and newRepr are expected to have previously been escaped by
- // django.utils.html.escape.
- newId = html_unescape(newId);
- newRepr = html_unescape(newRepr);
var name = windowname_to_id(win.name);
var elem = document.getElementById(name);
var o;
diff --git a/src/ralph/admin/static/js/popup_response.js b/src/ralph/admin/static/js/popup_response.js
new file mode 100644
index 0000000000..7d9e5a6546
--- /dev/null
+++ b/src/ralph/admin/static/js/popup_response.js
@@ -0,0 +1,17 @@
+/*global opener */
+(function() {
+ 'use strict';
+ var initData = JSON.parse(document.getElementById('django-admin-popup-response-constants').dataset.popupResponse);
+ switch(initData.action) {
+ case 'change':
+ opener.dismissChangeRelatedObjectPopup(window, initData.value, initData.obj, initData.new_value);
+ opener.updateAfterClose(window.name, initData.new_value);
+ break;
+ case 'delete':
+ opener.dismissDeleteRelatedObjectPopup(window, initData.value);
+ break;
+ default:
+ opener.dismissAddRelatedObjectPopup(window, initData.value, initData.obj);
+ break;
+ }
+})();
diff --git a/src/ralph/admin/templates/admin/edit_inline/tabular.html b/src/ralph/admin/templates/admin/edit_inline/tabular.html
index c9ce5461ee..6a281ef532 100644
--- a/src/ralph/admin/templates/admin/edit_inline/tabular.html
+++ b/src/ralph/admin/templates/admin/edit_inline/tabular.html
@@ -17,7 +17,7 @@
{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}
{% if not field.widget.is_hidden %}
{{ field.label|capfirst }}
{% if field.help_text %}
-
+
{% endif %}
|
{% endif %}
diff --git a/src/ralph/admin/templates/admin/includes/javascripts.html b/src/ralph/admin/templates/admin/includes/javascripts.html
index db63e10e57..4e6cdbdcde 100644
--- a/src/ralph/admin/templates/admin/includes/javascripts.html
+++ b/src/ralph/admin/templates/admin/includes/javascripts.html
@@ -3,7 +3,7 @@
-
+
diff --git a/src/ralph/admin/templates/admin/popup_response.html b/src/ralph/admin/templates/admin/popup_response.html
index c7530f42a6..5be376aeb5 100644
--- a/src/ralph/admin/templates/admin/popup_response.html
+++ b/src/ralph/admin/templates/admin/popup_response.html
@@ -1,16 +1,11 @@
-
+{% load i18n static %}
-
+ {% trans 'Popup closing...' %}
-
diff --git a/src/ralph/admin/tests/tests_functional.py b/src/ralph/admin/tests/tests_functional.py
index 8dcd18e7ed..edad1e2c98 100644
--- a/src/ralph/admin/tests/tests_functional.py
+++ b/src/ralph/admin/tests/tests_functional.py
@@ -6,8 +6,8 @@
from django.test import RequestFactory, TestCase
from django.views.generic import View
-from ralph.admin import RalphAdmin
from ralph.admin.decorators import register_extra_view
+from ralph.admin.mixins import RalphAdmin
from ralph.admin.sites import ralph_site
from ralph.admin.views.extra import RalphDetailView, RalphListView
from ralph.admin.views.main import RalphChangeList
diff --git a/src/ralph/admin/views/main.py b/src/ralph/admin/views/main.py
index 0705295470..8d45df957e 100644
--- a/src/ralph/admin/views/main.py
+++ b/src/ralph/admin/views/main.py
@@ -35,7 +35,7 @@ def get_ordering_from_related_model_admin(self, prefix, field_name):
return fields
try:
model_admin = admin_site._registry[field.field.rel.to]
- except AttributeError:
+ except (AttributeError, KeyError):
pass
else:
if all([model_admin, model_admin.ordering]):
diff --git a/src/ralph/admin/widgets.py b/src/ralph/admin/widgets.py
index ceb554c1b9..659e4f063b 100644
--- a/src/ralph/admin/widgets.py
+++ b/src/ralph/admin/widgets.py
@@ -8,11 +8,11 @@
from urllib import parse
from django import forms
+from django.apps import apps
from django.contrib.admin.templatetags.admin_static import static
from django.contrib.admin.views.main import TO_FIELD_VAR
from django.core.exceptions import FieldDoesNotExist
from django.core.urlresolvers import reverse
-from django.db.models.loading import get_model
from django.forms.utils import flatatt
from django.template import loader
from django.template.context import RenderContext
@@ -232,7 +232,7 @@ def get_search_fields(self):
limit_models = getattr(self.field, 'limit_models', [])
if limit_models:
polymorphic_models = [
- get_model(*i.split('.')) for i in limit_models
+ apps.get_model(*i.split('.')) for i in limit_models
]
search_fields_tooltip = defaultdict(list)
diff --git a/src/ralph/api/routers.py b/src/ralph/api/routers.py
index d48df23c65..92a482533f 100644
--- a/src/ralph/api/routers.py
+++ b/src/ralph/api/routers.py
@@ -18,7 +18,7 @@ class RalphRouter(NestedCustomFieldsRouterMixin, routers.DefaultRouter):
# skip .json style formatting suffixes in urls
include_format_suffixes = False
- def get_api_root_view(self):
+ def get_api_root_view(self, schema_urls=None):
api_root_dict = {}
list_name = self.routes[0].name
for prefix, viewset, basename in self.registry:
diff --git a/src/ralph/api/serializers.py b/src/ralph/api/serializers.py
index 045daa2880..7dbf1fa450 100644
--- a/src/ralph/api/serializers.py
+++ b/src/ralph/api/serializers.py
@@ -3,7 +3,6 @@
import operator
from functools import reduce
-import reversion
from django.core.exceptions import ValidationError as DjangoValidationError
from django.core.exceptions import NON_FIELD_ERRORS, ObjectDoesNotExist
from django.db import transaction
@@ -14,10 +13,12 @@
ValidationError as RestFrameworkValidationError
from rest_framework.settings import api_settings
from rest_framework.utils import model_meta
+from reversion import revisions as reversion
from taggit_serializer.serializers import (
TaggitSerializer,
TagListSerializerField
)
+
from ralph.api.fields import AbsoluteUrlField, ReversedChoiceField
from ralph.api.relations import RalphHyperlinkedRelatedField, RalphRelatedField
from ralph.lib.mixins.models import AdminAbsoluteUrlMixin, TaggableMixin
@@ -175,7 +176,7 @@ class NestedMeta:
# exclude some fields from nested serializer
for field in NESTED_SERIALIZER_FIELDS_BLACKLIST:
try:
- relation_info.related_model._meta.get_field_by_name(field)
+ relation_info.related_model._meta.get_field(field)
except exceptions.FieldDoesNotExist:
pass
else:
diff --git a/src/ralph/api/tests/test_serializers.py b/src/ralph/api/tests/test_serializers.py
index e1fe028588..2fc847af58 100644
--- a/src/ralph/api/tests/test_serializers.py
+++ b/src/ralph/api/tests/test_serializers.py
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
-import reversion
from dj.choices import Choices
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from rest_framework.test import APIClient, APIRequestFactory
+from reversion.models import Version
from ralph.accounts.tests.factories import RegionFactory
from ralph.api.relations import RalphHyperlinkedRelatedField, RalphRelatedField
@@ -99,7 +99,7 @@ def test_reversion_history_save(self):
'/test-ralph-api/foos/', data={'bar': 'bar_name'}
)
foo = Foo.objects.get(pk=response.data['id'])
- history = reversion.get_for_object(foo)
+ history = Version.objects.get_for_object(foo)
self.assertEqual(len(history), 1)
self.assertIn('bar_name', history[0].serialized_data)
@@ -108,7 +108,7 @@ def test_reversion_history_save(self):
data={'bar': 'new_bar'}
)
foo = Foo.objects.get(pk=response.data['id'])
- history = reversion.get_for_object(foo)
+ history = Version.objects.get_for_object(foo)
self.assertEqual(len(history), 2)
self.assertIn('new_bar', history[0].serialized_data)
@@ -124,7 +124,7 @@ def test_reversion_history_for_intermediary_model(self):
base_object_licence = BaseObjectLicence.objects.get(
pk=response.data['id']
)
- history = reversion.get_for_object(base_object_licence)
+ history = Version.objects.get_for_object(base_object_licence)
self.assertEqual(len(history), 1)
self.assertIn(
'"licence": {}'.format(licence.id), history[0].serialized_data
diff --git a/src/ralph/assets/_migration_helpers.py b/src/ralph/assets/_migration_helpers.py
index 306a5d4bd1..100279448b 100644
--- a/src/ralph/assets/_migration_helpers.py
+++ b/src/ralph/assets/_migration_helpers.py
@@ -65,23 +65,26 @@ def baseobject_migration(
obj.save()
# foreign keys
- for relation in Model._meta.get_all_related_objects(local_only=True):
- related_model = relation.related_model
- relation_field = relation.field.attname
- logger.info('Processing relation {}<->{} using field {}'.format(
- model_str, related_model, relation_field
- ))
- relation_mapping = defaultdict(list)
- for related_object in related_model._default_manager.values_list(
- 'pk', relation_field
- ):
- relation_mapping[related_object[1]].append(related_object[0])
- for old_id, new_id in id_mapping.items():
- related_model._default_manager.filter(
- pk__in=relation_mapping.get(old_id, [])
- ).update(
- **{relation_field: new_id}
- )
+ # migrated from deprecated get_all_related_objects(local_only=True)
+ for relation in Model._meta.get_fields(include_parents=False):
+ if (relation.one_to_many or relation.one_to_one) and \
+ relation.auto_created and not relation.concrete:
+ related_model = relation.related_model
+ relation_field = relation.field.attname
+ logger.info('Processing relation {}<->{} using field {}'.format(
+ model_str, related_model, relation_field
+ ))
+ relation_mapping = defaultdict(list)
+ for related_object in related_model._default_manager.values_list(
+ 'pk', relation_field
+ ):
+ relation_mapping[related_object[1]].append(related_object[0])
+ for old_id, new_id in id_mapping.items():
+ related_model._default_manager.filter(
+ pk__in=relation_mapping.get(old_id, [])
+ ).update(
+ **{relation_field: new_id}
+ )
# ImportedObjects
ImportedObjects = apps.get_model('data_importer', 'ImportedObjects')
diff --git a/src/ralph/assets/admin.py b/src/ralph/assets/admin.py
index 6ff1b39fa9..d6a05abffc 100644
--- a/src/ralph/assets/admin.py
+++ b/src/ralph/assets/admin.py
@@ -2,7 +2,8 @@
from django.db.models import Count
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphAdmin, RalphMPTTAdmin, RalphTabularInline, register
+from ralph.admin.decorators import register
+from ralph.admin.mixins import RalphAdmin, RalphMPTTAdmin, RalphTabularInline
from ralph.admin.views.extra import RalphDetailView
from ralph.assets.models.assets import (
Asset,
@@ -34,7 +35,7 @@
)
from ralph.data_importer import resources
from ralph.lib.custom_fields.admin import CustomFieldValueAdminMixin
-from ralph.lib.table import Table, TableWithUrl
+from ralph.lib.table.table import Table, TableWithUrl
from ralph.security.views import ScanStatusInTableMixin
diff --git a/src/ralph/assets/migrations/0035_auto_20240506_1633.py b/src/ralph/assets/migrations/0035_auto_20240506_1633.py
new file mode 100644
index 0000000000..fb3d93e631
--- /dev/null
+++ b/src/ralph/assets/migrations/0035_auto_20240506_1633.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.13 on 2024-05-06 16:33
+from __future__ import unicode_literals
+
+from django.db import migrations
+import django.db.models.manager
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('assets', '0034_auto_20240304_1511'),
+ ]
+
+ operations = [
+ migrations.AlterModelManagers(
+ name='baseobject',
+ managers=[
+ ('polymorphic_objects', django.db.models.manager.Manager()),
+ ],
+ ),
+ ]
diff --git a/src/ralph/assets/models/assets.py b/src/ralph/assets/models/assets.py
index a4ae5b4eb8..3e7abecdf8 100644
--- a/src/ralph/assets/models/assets.py
+++ b/src/ralph/assets/models/assets.py
@@ -31,8 +31,7 @@
PriceMixin,
TimeStampMixin
)
-from ralph.lib.permissions import PermByFieldMixin
-from ralph.lib.permissions.models import PermissionsBase
+from ralph.lib.permissions.models import PermByFieldMixin, PermissionsBase
logger = logging.getLogger(__name__)
diff --git a/src/ralph/assets/models/base.py b/src/ralph/assets/models/base.py
index 681a58adbe..53c985f867 100644
--- a/src/ralph/assets/models/base.py
+++ b/src/ralph/assets/models/base.py
@@ -11,8 +11,7 @@
WithCustomFieldsMixin
)
from ralph.lib.mixins.models import TaggableMixin, TimeStampMixin
-from ralph.lib.permissions import PermByFieldMixin
-from ralph.lib.permissions.models import PermissionsBase
+from ralph.lib.permissions.models import PermByFieldMixin, PermissionsBase
from ralph.lib.polymorphic.models import (
Polymorphic,
PolymorphicBase,
diff --git a/src/ralph/assets/views.py b/src/ralph/assets/views.py
index b869aeefe8..9ebf25c303 100644
--- a/src/ralph/assets/views.py
+++ b/src/ralph/assets/views.py
@@ -1,6 +1,6 @@
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphTabularInline
+from ralph.admin.mixins import RalphTabularInline
from ralph.admin.views.extra import RalphDetailViewAdmin
from ralph.assets.models.components import (
Disk,
diff --git a/src/ralph/attachments/models.py b/src/ralph/attachments/models.py
index b5223f8a8c..d34e9415da 100644
--- a/src/ralph/attachments/models.py
+++ b/src/ralph/attachments/models.py
@@ -3,7 +3,7 @@
import string
from django.conf import settings
-from django.contrib.contenttypes import generic
+from django.contrib.contenttypes import fields
from django.contrib.contenttypes.models import ContentType
from django.core.files.base import ContentFile
from django.db import models, transaction
@@ -169,7 +169,7 @@ class AttachmentItem(models.Model):
attachment = models.ForeignKey(Attachment, related_name='items')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
+ content_object = fields.GenericForeignKey('content_type', 'object_id')
objects = AttachmentItemManager()
diff --git a/src/ralph/back_office/admin.py b/src/ralph/back_office/admin.py
index 0dfd71084b..0664f43841 100644
--- a/src/ralph/back_office/admin.py
+++ b/src/ralph/back_office/admin.py
@@ -1,12 +1,16 @@
# -*- coding: utf-8 -*-
from django import forms
+from django.apps import apps
from django.conf import settings
-from django.db.models.loading import get_model
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphAdmin, RalphTabularInline, register
+from ralph.admin.decorators import register
from ralph.admin.filters import LiquidatedStatusFilter, TagsListFilter
-from ralph.admin.mixins import BulkEditChangeListMixin
+from ralph.admin.mixins import (
+ BulkEditChangeListMixin,
+ RalphAdmin,
+ RalphTabularInline
+)
from ralph.admin.sites import ralph_site
from ralph.admin.views.extra import RalphDetailViewAdmin
from ralph.admin.views.multiadd import MulitiAddAdminMixin
@@ -217,7 +221,7 @@ class BackOfficeAssetBulkForm(Form):
queryset=Licence.objects.all(), label=_('licences'),
required=False,
widget=AutocompleteWidget(
- field=get_model(
+ field=apps.get_model(
'licences.BaseObjectLicence'
)._meta.get_field('licence'),
admin_site=ralph_site,
diff --git a/src/ralph/configuration_management/models.py b/src/ralph/configuration_management/models.py
index ce14bc4a61..4be83267e2 100644
--- a/src/ralph/configuration_management/models.py
+++ b/src/ralph/configuration_management/models.py
@@ -5,7 +5,7 @@
from ralph.assets.models import BaseObject
from ralph.lib.mixins.models import TimeStampMixin
-from ralph.lib.permissions import PermByFieldMixin
+from ralph.lib.permissions.models import PermByFieldMixin
class SCMCheckResult(Choices):
diff --git a/src/ralph/dashboards/admin.py b/src/ralph/dashboards/admin.py
index e4e93d17ae..8f269f6829 100644
--- a/src/ralph/dashboards/admin.py
+++ b/src/ralph/dashboards/admin.py
@@ -7,8 +7,8 @@
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
-from ralph.admin import RalphAdmin, register
-from ralph.admin.mixins import RalphAdminForm
+from ralph.admin.decorators import register
+from ralph.admin.mixins import RalphAdmin, RalphAdminForm
from ralph.dashboards.models import Dashboard, Graph
diff --git a/src/ralph/data_center/admin.py b/src/ralph/data_center/admin.py
index 943a461f5a..327c0b315d 100644
--- a/src/ralph/data_center/admin.py
+++ b/src/ralph/data_center/admin.py
@@ -10,7 +10,8 @@
from django.db.models import Prefetch, Q
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import filters, RalphAdmin, RalphTabularInline, register
+from ralph.admin import filters
+from ralph.admin.decorators import register
from ralph.admin.filters import (
BaseObjectHostnameFilter,
ChoicesListFilter,
@@ -24,7 +25,11 @@
)
from ralph.admin.helpers import generate_html_link
from ralph.admin.m2m import RalphTabularM2MInline
-from ralph.admin.mixins import BulkEditChangeListMixin
+from ralph.admin.mixins import (
+ BulkEditChangeListMixin,
+ RalphAdmin,
+ RalphTabularInline
+)
from ralph.admin.views.extra import RalphDetailViewAdmin
from ralph.admin.views.main import RalphChangeList
from ralph.admin.views.multiadd import MulitiAddAdminMixin
@@ -60,7 +65,7 @@
from ralph.data_importer import resources
from ralph.deployment.mixins import ActiveDeploymentMessageMixin
from ralph.lib.custom_fields.admin import CustomFieldValueAdminMixin
-from ralph.lib.table import Table
+from ralph.lib.table.table import Table
from ralph.lib.transitions.admin import TransitionAdminMixin
from ralph.licences.models import BaseObjectLicence
from ralph.networks.forms import SimpleNetworkWithManagementIPForm
diff --git a/src/ralph/data_center/forms.py b/src/ralph/data_center/forms.py
index 234ac737de..f205e43c8d 100644
--- a/src/ralph/data_center/forms.py
+++ b/src/ralph/data_center/forms.py
@@ -13,7 +13,7 @@
class DataCenterAssetForm(PriceFormMixin, AssetFormMixin, RalphAdminForm):
MODEL_TYPE = ObjectModelType.data_center
- management_ip = forms.IPAddressField(required=False)
+ management_ip = forms.GenericIPAddressField(required=False, protocol='IPv4')
management_hostname = CharFormFieldWithAutoStrip(required=False)
ip_fields = ['management_ip', 'management_hostname']
diff --git a/src/ralph/data_center/tests/test_models.py b/src/ralph/data_center/tests/test_models.py
index ee53299a4f..cbdc93dbe6 100644
--- a/src/ralph/data_center/tests/test_models.py
+++ b/src/ralph/data_center/tests/test_models.py
@@ -2,6 +2,7 @@
from ddt import data, ddt, unpack
from django.conf import settings
from django.core.exceptions import ValidationError
+from django.db.models import Field
from ralph.accounts.tests.factories import RegionFactory
from ralph.back_office.models import BackOfficeAsset, BackOfficeAssetStatus
@@ -169,7 +170,7 @@ def test_convert_to_backoffice_asset_uses_default_from_settings(self):
('16',),
)
def test_should_pass_when_slot_no_is_correct(self, slot_no):
- slot_no_field = self.dc_asset._meta.get_field_by_name('slot_no')[0]
+ slot_no_field: Field = self.dc_asset._meta.get_field('slot_no')
slot_no_field.clean(slot_no, self.dc_asset)
@unpack
@@ -188,7 +189,7 @@ def test_should_pass_when_slot_no_is_correct(self, slot_no):
def test_should_raise_validation_error_when_slot_no_is_incorrect(
self, slot_no
):
- slot_no_field = self.dc_asset._meta.get_field_by_name('slot_no')[0]
+ slot_no_field: Field = self.dc_asset._meta.get_field('slot_no')
with self.assertRaises(ValidationError):
slot_no_field.clean(slot_no, self.dc_asset)
diff --git a/src/ralph/data_center/tests/test_view.py b/src/ralph/data_center/tests/test_view.py
index 62fb20354e..9f6780373b 100644
--- a/src/ralph/data_center/tests/test_view.py
+++ b/src/ralph/data_center/tests/test_view.py
@@ -39,7 +39,7 @@ class DataCenterAssetViewTest(ClientMixin, TestCase):
def test_changelist_view(self):
self.login_as_user()
DataCenterAssetFullFactory.create_batch(10)
- with self.assertNumQueries(18):
+ with self.assertNumQueries(21):
self.client.get(
reverse('admin:data_center_datacenterasset_changelist'),
)
@@ -54,7 +54,7 @@ def test_changelist_view(self):
VirtualServerFullFactory.create_batch(5)
CloudHostFullFactory.create_batch(4)
ClusterFactory.create_batch(4)
- with self.assertNumQueries(19):
+ with self.assertNumQueries(22):
result = self.client.get(
reverse('admin:data_center_dchost_changelist'),
)
@@ -327,7 +327,8 @@ def test_should_add_physical_hosts_to_dictionary(self):
def test_should_add_clusters_to_dictionary(self):
cluster = ContentType.objects.get_for_model(Cluster)
self.view.object.clusters.add(
- BaseObjectCluster(cluster=ClusterFactory())
+ BaseObjectCluster(cluster=ClusterFactory()),
+ bulk=False
)
related_objects = {}
self.view._add_clusters(related_objects)
diff --git a/src/ralph/data_importer/fields.py b/src/ralph/data_importer/fields.py
index 138c95cb7b..9e010aa3f4 100644
--- a/src/ralph/data_importer/fields.py
+++ b/src/ralph/data_importer/fields.py
@@ -44,7 +44,7 @@ def __init__(
self.through_to_field_name = through_to_field_name
super().__init__(attribute, column_name, widget, readonly)
- def save(self, obj, data):
+ def save(self, obj, data, is_m2m=False):
if not self.readonly:
value = data.get(self.column_name)
current = set(self.widget.clean(value))
@@ -89,7 +89,7 @@ def save(self, obj, data):
class PriceField(fields.Field):
- def save(self, obj, data):
+ def save(self, obj, data, is_m2m=False):
price = Money(
data['price'],
data.get('price_currency', DEFAULT_CURRENCY_CODE)
diff --git a/src/ralph/data_importer/mixins.py b/src/ralph/data_importer/mixins.py
index 44cf42dea4..cd0ea70755 100644
--- a/src/ralph/data_importer/mixins.py
+++ b/src/ralph/data_importer/mixins.py
@@ -89,7 +89,14 @@ def get_or_init_instance(self, instance_loader, row):
instance_loader, row
)
- def after_save_instance(self, instance, dry_run):
+ def after_save_instance(
+ self,
+ instance,
+ using_transactions: bool,
+ dry_run: bool,
+ *args,
+ **kwargs
+ ):
if not dry_run and self.old_object_pk:
content_type = ContentType.objects.get_for_model(self._meta.model)
ImportedObjects.objects.update_or_create(
@@ -97,3 +104,13 @@ def after_save_instance(self, instance, dry_run):
old_object_pk=self.old_object_pk,
defaults={'object_pk': instance.pk}
)
+
+ def import_field(self, field, obj, data, is_m2m=False):
+ """
+ Calls :meth:`import_export.fields.Field.save` if ``Field.attribute``
+ and ``Field.column_name`` are found in ``data``.
+ """
+ if field.column_name == 'management_ip':
+ field.save(obj, data, is_m2m=False)
+ elif field.attribute and field.column_name in data:
+ field.save(obj, data, is_m2m)
diff --git a/src/ralph/data_importer/models.py b/src/ralph/data_importer/models.py
index e4c01317d2..9c88794a04 100644
--- a/src/ralph/data_importer/models.py
+++ b/src/ralph/data_importer/models.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from django.contrib.contenttypes import generic
+from django.contrib.contenttypes import fields
from django.contrib.contenttypes.models import ContentType
from django.db import models
@@ -20,7 +20,7 @@ class ImportedObjects(TimeStampMixin, models.Model):
old_ci_uid = models.CharField(
max_length=255, db_index=True, null=True, blank=True
)
- object = generic.GenericForeignKey('content_type', 'object_pk')
+ object = fields.GenericForeignKey('content_type', 'object_pk')
def __str__(self):
return "{} - {}".format(
diff --git a/src/ralph/data_importer/tests/test_export.py b/src/ralph/data_importer/tests/test_export.py
index 6eec5bef33..ff79f2c034 100644
--- a/src/ralph/data_importer/tests/test_export.py
+++ b/src/ralph/data_importer/tests/test_export.py
@@ -7,7 +7,7 @@
from django.test.utils import CaptureQueriesContext
from ralph.accounts.tests.factories import UserFactory
-from ralph.admin import ralph_site
+from ralph.admin.sites import ralph_site
from ralph.data_center.models import DataCenterAsset
from ralph.data_center.tests.factories import DataCenterAssetFullFactory
from ralph.licences.models import Licence
@@ -50,7 +50,7 @@ def _export(self, model, filters=None):
file_format = RawFormat()
queryset = admin_class.get_export_queryset(request)
- export_data = admin_class.get_export_data(file_format, queryset)
+ export_data = admin_class.get_export_data(file_format, queryset, request=request)
return export_data
def _init(self, num=10):
@@ -118,7 +118,7 @@ def test_data_center_asset_export(self):
def test_data_center_asset_export_with_parent_queries_count(self):
self._test_queries_count(func=lambda: self._export(
DataCenterAsset
- ))
+ ), max_queries=12)
def test_data_center_asset_export_with_parent(self):
self._init(10)
diff --git a/src/ralph/data_importer/tests/test_fields.py b/src/ralph/data_importer/tests/test_fields.py
index 0074ab16d2..938d2268ae 100644
--- a/src/ralph/data_importer/tests/test_fields.py
+++ b/src/ralph/data_importer/tests/test_fields.py
@@ -69,7 +69,7 @@ def test_users_through_field(self):
self.assertEqual(self.licence2.users.all().count(), 3)
# Add and remove
- with self.assertNumQueries(4):
+ with self.assertNumQueries(5):
field.save(
self.licence,
{'users': ','.join([i.username for i in self.users])}
@@ -88,7 +88,7 @@ def test_users_through_field(self):
self.assertEqual(self.licence.users.all().count(), 5)
# Remove
- with self.assertNumQueries(3):
+ with self.assertNumQueries(4):
field.save(
self.licence,
{'users': ','.join([i.username for i in users[:4]])}
@@ -134,7 +134,7 @@ def test_through_field_only_add(self):
self.assertEqual(self.licence.base_objects.all().count(), 4)
self.assertCountEqual(
- self.licence.base_objects.values_list('pk', flat=True), ids
+ [bo.pk for bo in self.licence.base_objects.all()], ids
)
# Make sure it doesn't touch other licences
self.assertEqual(self.licence2.base_objects.all().count(), 2)
@@ -143,7 +143,7 @@ def test_through_field_only_remove(self):
field = self._get_base_objects_through_field()
self.assertEqual(self.licence.base_objects.all().count(), 2)
ids = [self.back_office_assets[0].pk]
- with self.assertNumQueries(3):
+ with self.assertNumQueries(4):
field.save(
self.licence,
{'base_objects': ','.join(map(str, ids))}
@@ -151,7 +151,7 @@ def test_through_field_only_remove(self):
self.assertEqual(self.licence.base_objects.all().count(), 1)
self.assertCountEqual(
- self.licence.base_objects.values_list('pk', flat=True), ids
+ [bo.pk for bo in self.licence.base_objects.all()], ids
)
# Make sure it doesn't touch other licences
self.assertEqual(self.licence2.base_objects.all().count(), 2)
@@ -165,14 +165,14 @@ def test_through_field_add_and_remove(self):
self.back_office_assets[3].pk,
]
- with self.assertNumQueries(4):
+ with self.assertNumQueries(5):
field.save(
self.licence,
{'base_objects': ','.join(map(str, ids))}
)
self.assertEqual(self.licence.base_objects.all().count(), 3)
self.assertCountEqual(
- self.licence.base_objects.values_list('pk', flat=True), ids
+ [bo.pk for bo in self.licence.base_objects.all()], ids
)
# Make sure it doesn't touch other licences
self.assertEqual(self.licence2.base_objects.all().count(), 2)
diff --git a/src/ralph/data_importer/widgets.py b/src/ralph/data_importer/widgets.py
index 9230c52110..92d3934b45 100644
--- a/src/ralph/data_importer/widgets.py
+++ b/src/ralph/data_importer/widgets.py
@@ -44,7 +44,7 @@ class UserWidget(widgets.ForeignKeyWidget):
"""Widget for Ralph User Foreign Key field."""
- def clean(self, value):
+ def clean(self, value, *args, **kwargs):
result = None
if value:
result, created = get_user_model().objects.get_or_create(
@@ -56,7 +56,7 @@ def clean(self, value):
)
return result
- def render(self, value):
+ def render(self, value, obj=None):
if value:
return value.username
return ''
@@ -66,13 +66,13 @@ class UserManyToManyWidget(widgets.ManyToManyWidget):
"""Widget for many Ralph Users Foreign Key field."""
- def clean(self, value):
+ def clean(self, value, *args, **kwargs):
if not value:
return get_user_model().objects.none()
usernames = value.split(self.separator)
return get_user_model().objects.filter(username__in=usernames)
- def render(self, value):
+ def render(self, value, obj=None):
return self.separator.join([obj.username for obj in value.all()])
@@ -94,14 +94,14 @@ def __init__(self, through_field, related_model, *args, **kwargs):
self.related_model = related_model
super().__init__(*args, **kwargs)
- def clean(self, value):
+ def clean(self, value, *args, **kwargs):
if not value:
return self.related_model.objects.none()
return self.related_model.objects.filter(
pk__in=value.split(self.separator)
)
- def render(self, value):
+ def render(self, value, obj=None):
return self.separator.join(
[str(getattr(obj, self.through_field).pk) for obj in value.all()]
)
@@ -109,13 +109,13 @@ def render(self, value):
class ExportForeignKeyStrWidget(widgets.Widget):
- def render(self, value):
+ def render(self, value, obj=None):
return str(value)
class ExportManyToManyStrWidget(widgets.ManyToManyWidget):
- def render(self, value):
+ def render(self, value, obj=None):
return self.separator.join([str(obj) for obj in value.all()])
@@ -124,7 +124,7 @@ class ExportManyToManyStrTroughWidget(ManyToManyThroughWidget):
Exporter-equivalent of `ManyToManyThroughWidget` - return str of whole
object instead of pk.
"""
- def render(self, value):
+ def render(self, value, obj=None):
return self.separator.join(
[str(getattr(obj, self.through_field)) for obj in value.all()]
)
@@ -134,7 +134,7 @@ class BaseObjectManyToManyWidget(widgets.ManyToManyWidget):
"""Widget for BO/DC base objects."""
- def clean(self, value):
+ def clean(self, value, *args, **kwargs):
if not value:
return self.model.objects.none()
ids = value.split(self.separator)
@@ -167,7 +167,7 @@ class BaseObjectWidget(widgets.ForeignKeyWidget):
"""Widget for BO/DC base objects."""
- def clean(self, value):
+ def clean(self, value, *args, **kwargs):
if not value:
return None
result = None
@@ -193,7 +193,7 @@ class ImportedForeignKeyWidget(widgets.ForeignKeyWidget):
"""Widget for ForeignKey fields for which can not define unique."""
- def clean(self, value):
+ def clean(self, value, *args, **kwargs):
if settings.MAP_IMPORTED_ID_TO_NEW_ID:
if value:
content_type, imported_obj = get_imported_obj(
@@ -205,7 +205,7 @@ def clean(self, value):
class NullStringWidget(widgets.CharWidget):
- def clean(self, value):
+ def clean(self, value, *args, **kwargs):
return super().clean(value) or None
@@ -216,7 +216,7 @@ class AssetServiceEnvWidget(widgets.ForeignKeyWidget):
CSV field format Service.name|Environment.name
"""
- def clean(self, value):
+ def clean(self, value, *args, **kwargs):
if not value:
return None
try:
@@ -232,7 +232,7 @@ def clean(self, value):
value = None
return value
- def render(self, value):
+ def render(self, value, obj=None):
if value is None:
return ""
return "{}|{}".format(
@@ -243,7 +243,7 @@ def render(self, value):
class AssetServiceUidWidget(widgets.ForeignKeyWidget):
- def clean(self, value):
+ def clean(self, value, *args, **kwargs):
if not value:
return None
try:
@@ -259,7 +259,7 @@ def clean(self, value):
value = None
return value
- def render(self, value):
+ def render(self, value, obj=None):
if value is None:
return ""
return value.service.uid
@@ -277,15 +277,15 @@ class IPManagementWidget(widgets.ManyToManyWidget):
ip. This because management ip is seperate model which can't be created
wihtout DataCenterAsset (and DataCenterAsset is the result of importing).
"""
- def clean(self, value):
+ def clean(self, value, *args, **kwargs):
return value
- def render(self, value):
+ def render(self, value, obj=None):
return value or ''
class BaseObjectServiceNamesM2MWidget(widgets.ManyToManyWidget):
- def render(self, value):
+ def render(self, value, obj=None):
return self.separator.join([
bo.service.name if bo.service else '-'
for bo in value.all()
@@ -293,10 +293,10 @@ def render(self, value):
class PriceAmountWidget(widgets.Widget):
- def render(self, value):
+ def render(self, value, obj=None):
return '{0:.2f}'.format(value.amount)
class PriceCurrencyWidget(widgets.Widget):
- def render(self, value):
+ def render(self, value, obj=None):
return str(value.currency)
diff --git a/src/ralph/deployment/admin.py b/src/ralph/deployment/admin.py
index b0f1ef616a..21f9110bf6 100644
--- a/src/ralph/deployment/admin.py
+++ b/src/ralph/deployment/admin.py
@@ -1,6 +1,7 @@
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphAdmin, register
+from ralph.admin.decorators import register
+from ralph.admin.mixins import RalphAdmin
from ralph.deployment.forms import PrebootConfigurationForm
from ralph.deployment.models import (
Deployment,
diff --git a/src/ralph/deployment/migrations/0007_auto_20240506_1633.py b/src/ralph/deployment/migrations/0007_auto_20240506_1633.py
new file mode 100644
index 0000000000..70a0dbeb64
--- /dev/null
+++ b/src/ralph/deployment/migrations/0007_auto_20240506_1633.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.13 on 2024-05-06 16:33
+from __future__ import unicode_literals
+
+from django.db import migrations
+import django.db.models.manager
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('deployment', '0006_auto_20211019_1456'),
+ ]
+
+ operations = [
+ migrations.AlterModelManagers(
+ name='prebootconfiguration',
+ managers=[
+ ('polymorphic_objects', django.db.models.manager.Manager()),
+ ],
+ ),
+ migrations.AlterModelManagers(
+ name='prebootfile',
+ managers=[
+ ('polymorphic_objects', django.db.models.manager.Manager()),
+ ],
+ ),
+ migrations.AlterModelManagers(
+ name='prebootitem',
+ managers=[
+ ('polymorphic_objects', django.db.models.manager.Manager()),
+ ],
+ ),
+ ]
diff --git a/src/ralph/dhcp/admin.py b/src/ralph/dhcp/admin.py
index abb465765f..63f51073b4 100644
--- a/src/ralph/dhcp/admin.py
+++ b/src/ralph/dhcp/admin.py
@@ -2,14 +2,15 @@
from django.template.defaultfilters import date, timesince_filter
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphAdmin, RalphTabularInline, register
+from ralph.admin.decorators import register
+from ralph.admin.mixins import RalphAdmin, RalphTabularInline
from ralph.dhcp.models import (
DHCPServer,
DNSServer,
DNSServerGroup,
DNSServerGroupOrder
)
-from ralph.lib.table import TableWithUrl
+from ralph.lib.table.table import TableWithUrl
@register(DHCPServer)
diff --git a/src/ralph/dhcp/tests/test_views.py b/src/ralph/dhcp/tests/test_views.py
index a8b329b4a7..cd1f2b3c41 100644
--- a/src/ralph/dhcp/tests/test_views.py
+++ b/src/ralph/dhcp/tests/test_views.py
@@ -1,3 +1,5 @@
+import re
+
from ddt import data, ddt, unpack
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
@@ -61,6 +63,27 @@ class DHCPEntriesViewTest(TestCase):
def setUp(self):
self.view = DHCPEntriesView()
+ def test_dhcp_entries_are_returned_in_correct_format(self):
+ get_user_model().objects.create_superuser(
+ 'test', 'test@test.test', 'test'
+ )
+ self.client.login(username='test', password='test')
+ network = NetworkFactory(address='192.168.1.0/24')
+ IPAddressFactory(address='192.168.1.2', dhcp_expose=True)
+ url = '{}?env={}'.format(
+ reverse('dhcp_config_entries'), network.network_environment
+ )
+ response = self.client.get(url)
+ lines = response.content.decode().strip().split('\n')
+ self.assertTrue(lines[0].startswith('# DHCP config generated by Ralph last modified at'))
+ self.assertTrue(
+ re.match(
+ r'^host\s\S+\s\{fixed-address\s192\.168\.1\.2;\shardware\sethernet\s\S*?;\s}$',
+ lines[2]
+ )
+ )
+ self.assertEqual(len(lines), 4)
+
def test_get_last_modified_should_return_ip_modified(self):
network = NetworkFactory(address='192.168.1.0/24')
ip = IPAddressFactory(address='192.168.1.2')
diff --git a/src/ralph/domains/admin.py b/src/ralph/domains/admin.py
index 7b41d73a8a..d710b91cd2 100644
--- a/src/ralph/domains/admin.py
+++ b/src/ralph/domains/admin.py
@@ -2,8 +2,9 @@
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphAdmin, RalphTabularInline, register
+from ralph.admin.decorators import register
from ralph.admin.filters import DateListFilter
+from ralph.admin.mixins import RalphAdmin, RalphTabularInline
from ralph.attachments.admin import AttachmentsMixin
from ralph.data_importer.resources import DomainContractResource, DomainResource
from ralph.domains.forms import DomainContractForm, DomainForm
diff --git a/src/ralph/domains/models/domains.py b/src/ralph/domains/models/domains.py
index fe8f7edf40..5e490a936d 100644
--- a/src/ralph/domains/models/domains.py
+++ b/src/ralph/domains/models/domains.py
@@ -14,7 +14,7 @@
PriceMixin,
TimeStampMixin
)
-from ralph.lib.permissions import PermByFieldMixin
+from ralph.lib.permissions.models import PermByFieldMixin
class DomainRegistrant(
diff --git a/src/ralph/lib/custom_fields/admin.py b/src/ralph/lib/custom_fields/admin.py
index 1320b75aec..e5ea708cca 100644
--- a/src/ralph/lib/custom_fields/admin.py
+++ b/src/ralph/lib/custom_fields/admin.py
@@ -3,8 +3,8 @@
from django.contrib.admin.utils import unquote
from django.contrib.contenttypes.models import ContentType
-from ralph.admin import RalphAdmin, register
-from ralph.admin.mixins import RalphGenericTabularInline
+from ralph.admin.decorators import register
+from ralph.admin.mixins import RalphAdmin, RalphGenericTabularInline
from ralph.lib.custom_fields.forms import (
CustomFieldValueForm,
CustomFieldValueFormSet,
diff --git a/src/ralph/lib/custom_fields/fields.py b/src/ralph/lib/custom_fields/fields.py
index 926e8b6f11..fa59ffb672 100644
--- a/src/ralph/lib/custom_fields/fields.py
+++ b/src/ralph/lib/custom_fields/fields.py
@@ -1,14 +1,14 @@
import operator
from collections import defaultdict
from functools import reduce
+from typing import Any
from django.contrib.contenttypes.fields import (
create_generic_related_manager,
- GenericRelation,
- ReverseGenericRelatedObjectsDescriptor
+ GenericRelation
)
from django.contrib.contenttypes.models import ContentType
-from django.db import connection, models
+from django.db import models
from django.db.models.fields.related import OneToOneRel
from ralph.admin.helpers import get_field_by_relation_path, getattr_dunder
@@ -112,7 +112,7 @@ def _clone(self, klass=None, setup=False, **kwargs):
'_prioritize': self._prioritize,
'_prioritize_model_or_instance': self._prioritize_model_or_instance,
})
- return super()._clone(klass, setup, **kwargs)
+ return super()._clone(**kwargs)
def prioritize(self, model_or_instance):
self._prioritize = True
@@ -148,9 +148,20 @@ def values_list(self, *fields):
)
-class ReverseGenericRelatedObjectsWithInheritanceDescriptor(
- ReverseGenericRelatedObjectsDescriptor
-):
+class RelModel:
+ def __init__(self, model: Any, field: CustomFieldsWithInheritanceRelation):
+ self.model = model
+ self.field = field
+
+
+class ReverseGenericRelatedObjectsWithInheritanceDescriptor:
+ def __init__(self, field, for_concrete_model=True):
+ """
+ imported from ReverseGenericRelatedObjectsDescriptor
+ """
+ self.field = field
+ self.for_concrete_model = for_concrete_model
+
def __get__(self, instance, instance_type=None):
"""
Overwrite of ReverseGenericRelatedObjectsDescriptor's __get__
@@ -160,35 +171,21 @@ def __get__(self, instance, instance_type=None):
"""
if instance is None:
return self
- rel_model = self.field.rel.to
+ rel_model = RelModel(model=self.field.rel.to, field=self.field)
# difference here comparing to Django!
- superclass = rel_model.inherited_objects.__class__
+ superclass = rel_model.model.inherited_objects.__class__
RelatedManager = create_generic_related_manager_with_inheritance(
- superclass
+ superclass, rel_model
)
- qn = connection.ops.quote_name
- content_type = ContentType.objects.db_manager(
- instance._state.db
- ).get_for_model(
- instance, for_concrete_model=self.for_concrete_model)
-
- join_cols = self.field.get_joining_columns(reverse_join=True)[0]
manager = RelatedManager(
- model=rel_model,
instance=instance,
- source_col_name=qn(join_cols[0]),
- target_col_name=qn(join_cols[1]),
- content_type=content_type,
- content_type_field_name=self.field.content_type_field_name,
- object_id_field_name=self.field.object_id_field_name,
- prefetch_cache_name=self.field.attname,
)
return manager
-def create_generic_related_manager_with_inheritance(superclass): # noqa: C901
+def create_generic_related_manager_with_inheritance(superclass, rel): # noqa: C901
"""
Extension to Django's create_generic_related_manager.
@@ -199,7 +196,7 @@ def create_generic_related_manager_with_inheritance(superclass): # noqa: C901
with inheritance of more than one instance
"""
# get Django's GenericRelatedObject manager first
- manager = create_generic_related_manager(superclass)
+ manager = create_generic_related_manager(superclass, rel)
class GenericRelatedObjectWithInheritanceManager(manager):
def __init__(self, *args, **kwargs):
diff --git a/src/ralph/lib/custom_fields/models.py b/src/ralph/lib/custom_fields/models.py
index cd5970531d..5f2b8e665d 100644
--- a/src/ralph/lib/custom_fields/models.py
+++ b/src/ralph/lib/custom_fields/models.py
@@ -4,7 +4,7 @@
from dj.choices import Choices
from django import forms
from django.contrib.auth.models import Group
-from django.contrib.contenttypes import generic
+from django.contrib.contenttypes import fields
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
@@ -132,7 +132,7 @@ class CustomFieldValue(TimeStampMixin, models.Model):
value = models.CharField(max_length=CUSTOM_FIELD_VALUE_MAX_LENGTH)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField(db_index=True)
- object = generic.GenericForeignKey('content_type', 'object_id')
+ object = fields.GenericForeignKey('content_type', 'object_id')
objects = models.Manager()
# generic relation has to use specific manager (queryset)
diff --git a/src/ralph/lib/error_handling/__init__.py b/src/ralph/lib/error_handling/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/src/ralph/lib/error_handling/exceptions.py b/src/ralph/lib/error_handling/exceptions.py
deleted file mode 100644
index 4543b346c9..0000000000
--- a/src/ralph/lib/error_handling/exceptions.py
+++ /dev/null
@@ -1,5 +0,0 @@
-class WrappedOperationalError(RuntimeError):
- def __init__(self, query, model, error_str):
- self.query = query
- self.model = model
- self.original_error_str = error_str
diff --git a/src/ralph/lib/error_handling/middleware.py b/src/ralph/lib/error_handling/middleware.py
deleted file mode 100644
index d36e51895d..0000000000
--- a/src/ralph/lib/error_handling/middleware.py
+++ /dev/null
@@ -1,46 +0,0 @@
-import logging
-import traceback
-
-from django.conf import settings
-from django.db import OperationalError
-
-from ralph.lib.error_handling.exceptions import WrappedOperationalError
-
-
-logger = logging.getLogger(__name__)
-
-
-class OperationalErrorHandlerMiddleware:
- def process_exception(self, request, exception):
- if exception:
- request_id = request.META.get('HTTP_X_REQUEST_ID', '-')
- if isinstance(exception, OperationalError):
- logger.error("OperationalError occured. URI: %s, "
- "user: %s, exception: %s, "
- "django running since: %s, "
- "request id: %s",
- request.build_absolute_uri(), request.user,
- exception, settings.START_TIMESTAMP,
- request_id,
- exc_info=True, stack_info=True,
- extra={'request_id': request_id}
- )
- raise exception
- elif isinstance(exception, WrappedOperationalError):
- inner_exc = exception.__context__
- logger.error("WrappedOperationalError occured. URI: %s, "
- "user: %s, SQL query: %s, "
- "model object: %s, original_error: %s, "
- "inner exception traceback: %s, "
- "django running since: %s, "
- "request id: %s",
- request.build_absolute_uri(), request.user,
- str(exception.query), exception.model.__dict__,
- exception.original_error_str,
- traceback.format_tb(inner_exc.__traceback__),
- settings.START_TIMESTAMP, request_id,
- exc_info=True, stack_info=True,
- extra={'request_id': request_id}
- )
- raise inner_exc
- return None
diff --git a/src/ralph/lib/metrics/middlewares.py b/src/ralph/lib/metrics/middlewares.py
index bd20592578..ca1a0a377a 100644
--- a/src/ralph/lib/metrics/middlewares.py
+++ b/src/ralph/lib/metrics/middlewares.py
@@ -53,8 +53,8 @@
class QueryLogEntry:
- sql: str
- duration: float
+ sql: str # noqa
+ duration: float # noqa
def __init__(self, sql: str, duration: float):
self.sql = sql,
@@ -164,8 +164,8 @@ def _collect_metrics(self, request, response):
# processing time
end_resources, end_time = getrusage(RUSAGE_SELF), time.monotonic()
real_time = (end_time - request._request_start_time) * 1000
- sys_time = (end_resources.ru_stime - request._start_resources.ru_stime) * 1000
- user_time = (end_resources.ru_utime - request._start_resources.ru_utime) * 1000
+ sys_time = (end_resources.ru_stime - request._start_resources.ru_stime) * 1000 # noqa
+ user_time = (end_resources.ru_utime - request._start_resources.ru_utime) * 1000 # noqa
cpu_time = sys_time + user_time
queries_time = sum(stat.duration for stat in query_stats) * 1000
diff --git a/src/ralph/lib/mixins/fields.py b/src/ralph/lib/mixins/fields.py
index 995cb30513..30978cd9e6 100644
--- a/src/ralph/lib/mixins/fields.py
+++ b/src/ralph/lib/mixins/fields.py
@@ -2,12 +2,12 @@
import netaddr
from django import forms
+from django.apps import apps
from django.conf import settings
from django.contrib.admin.widgets import AdminTextInputWidget
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models
-from django.db.models.loading import get_model
from django.forms.utils import flatatt
from django.utils import six
from django.utils.html import format_html, smart_urlquote
@@ -73,9 +73,12 @@ def clean(self, value, model_instance):
# in UD.
class NUMPFieldMixIn(object):
- def __init__(self, fields_to_ignore, *args, **kwargs):
+ def __init__(self, *args, **kwargs):
+ fields_to_ignore = kwargs.pop('fields_to_ignore', None)
super(NUMPFieldMixIn, self).__init__(*args, **kwargs)
- self.fields_to_ignore = fields_to_ignore
+ self.fields_to_ignore = fields_to_ignore if (
+ fields_to_ignore is not None
+ ) else ('help_text', 'verbose_name')
def deconstruct(self):
name, path, args, kwargs = super(NUMPFieldMixIn, self).deconstruct()
@@ -227,7 +230,7 @@ def limit_choices(self):
"""
if self.limit_models:
content_types = ContentType.objects.get_for_models(
- *[get_model(*i.split('.')) for i in self.limit_models]
+ *[apps.get_model(*i.split('.')) for i in self.limit_models]
)
return {'content_type__in': content_types.values()}
@@ -237,7 +240,7 @@ def get_limit_models(self):
"""
Returns Model class list from limit_models.
"""
- return [get_model(model) for model in self.limit_models]
+ return [apps.get_model(model) for model in self.limit_models]
class TagWidget(forms.TextInput):
diff --git a/src/ralph/lib/mixins/forms.py b/src/ralph/lib/mixins/forms.py
index a5997b1be0..81cf49dc64 100644
--- a/src/ralph/lib/mixins/forms.py
+++ b/src/ralph/lib/mixins/forms.py
@@ -43,9 +43,10 @@ def _get_other_field(self, name, value):
def render(self, name, value, attrs=None, choices=()):
show_other = value and value.get('value') == OTHER
choice_value = (value.get('value') if value else '') or ''
+ self.choices = choices
return '{}{}
'.format(
self.css_class,
- super().render(name, choice_value, attrs=attrs, choices=choices),
+ super().render(name, choice_value, attrs=attrs),
self._get_other_field(name, value) if show_other else ''
)
diff --git a/src/ralph/lib/mixins/models.py b/src/ralph/lib/mixins/models.py
index 333e7acfdb..8253e503db 100644
--- a/src/ralph/lib/mixins/models.py
+++ b/src/ralph/lib/mixins/models.py
@@ -5,6 +5,7 @@
from django.utils.translation import ugettext_lazy as _
from djmoney.models.fields import MoneyField
from taggit.managers import TaggableManager as TaggableManagerOriginal
+from taggit.managers import _TaggableManager # noqa
from ralph.lib.mixins.fields import TaggitTagField
from ralph.settings import DEFAULT_CURRENCY_CODE
@@ -79,10 +80,22 @@ def get_absolute_url(self):
)
+class ManagerOfManager(_TaggableManager):
+ def set(self, *tags, **kwargs):
+ def _flatten(nested_list):
+ for item in nested_list:
+ if isinstance(item, (list, models.QuerySet)):
+ yield from _flatten(item)
+ else:
+ yield item
+ flattened_tags = list(_flatten(tags))
+ super().set(*flattened_tags, **kwargs)
+
+
class TaggableManager(TaggableManagerOriginal):
def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
+ super().__init__(manager=ManagerOfManager, *args, **kwargs)
self.manager.name = 'tags'
def value_from_object(self, instance):
diff --git a/src/ralph/lib/mixins/tests/test_models.py b/src/ralph/lib/mixins/tests/test_models.py
index 1a9f55fd71..e5b3182b74 100644
--- a/src/ralph/lib/mixins/tests/test_models.py
+++ b/src/ralph/lib/mixins/tests/test_models.py
@@ -8,5 +8,5 @@ class AdminUrlTestCase(TestCase):
def test_returned_url(self):
obj = Foo.objects.create(bar='test')
self.assertEqual(
- '/tests/foo/{}/'.format(obj.pk), obj.get_absolute_url()
+ '/tests/foo/{}/change/'.format(obj.pk), obj.get_absolute_url()
)
diff --git a/src/ralph/lib/permissions/__init__.py b/src/ralph/lib/permissions/__init__.py
index 501f22b4b9..5fa2a65554 100644
--- a/src/ralph/lib/permissions/__init__.py
+++ b/src/ralph/lib/permissions/__init__.py
@@ -1,9 +1 @@
-from ralph.lib.permissions.models import (
- PermByFieldMixin,
- PermissionsForObjectMixin,
- user_permission
-)
-
default_app_config = 'ralph.lib.permissions.apps.PermissionAppConfig'
-
-__all__ = ['PermByFieldMixin', 'PermissionsForObjectMixin', 'user_permission']
diff --git a/src/ralph/lib/permissions/admin.py b/src/ralph/lib/permissions/admin.py
index 38377fe1e5..ebbcfbde55 100644
--- a/src/ralph/lib/permissions/admin.py
+++ b/src/ralph/lib/permissions/admin.py
@@ -28,7 +28,7 @@ def _has_access_to_field(self, field_name, request):
this field.
"""
try:
- self.model._meta.get_field_by_name(field_name)
+ self.model._meta.get_field(field_name)
except FieldDoesNotExist:
perm_field = getattr(
(
diff --git a/src/ralph/lib/permissions/api.py b/src/ralph/lib/permissions/api.py
index ae637eddac..5aa40f14a7 100644
--- a/src/ralph/lib/permissions/api.py
+++ b/src/ralph/lib/permissions/api.py
@@ -5,13 +5,15 @@
from collections import OrderedDict
from django.utils.translation import ugettext_lazy as _
-from rest_framework.compat import get_model_name
from rest_framework.exceptions import ValidationError
from rest_framework.fields import empty
from rest_framework.filters import BaseFilterBackend
from rest_framework.permissions import IsAuthenticated as DRFIsAuthenticated
-from ralph.lib.permissions import PermByFieldMixin, PermissionsForObjectMixin
+from ralph.lib.permissions.models import (
+ PermByFieldMixin,
+ PermissionsForObjectMixin
+)
ADD_PERM = ['%(app_label)s.add_%(model_name)s']
CHANGE_PERM = ['%(app_label)s.change_%(model_name)s']
@@ -187,7 +189,7 @@ def get_required_permissions(self, method, model_cls):
"""
kwargs = {
'app_label': model_cls._meta.app_label,
- 'model_name': get_model_name(model_cls)
+ 'model_name': model_cls._meta.model_name
}
return [perm % kwargs for perm in self.perms_map[method]]
diff --git a/src/ralph/lib/permissions/apps.py b/src/ralph/lib/permissions/apps.py
index ddc88f5886..ab8929b3e1 100644
--- a/src/ralph/lib/permissions/apps.py
+++ b/src/ralph/lib/permissions/apps.py
@@ -2,15 +2,14 @@
from django.apps import AppConfig
from django.db.models.signals import post_migrate
-from ralph.lib.permissions.models import create_permissions
-from ralph.lib.permissions.views import update_extra_view_permissions
-
class PermissionAppConfig(AppConfig):
name = 'ralph.lib.permissions'
verbose_name = 'Permissions'
def ready(self):
+ from ralph.lib.permissions.models import create_permissions
+ from ralph.lib.permissions.views import update_extra_view_permissions
post_migrate.disconnect(
dispatch_uid='django.contrib.auth.management.create_permissions'
)
diff --git a/src/ralph/lib/permissions/models.py b/src/ralph/lib/permissions/models.py
index bdcd2e0202..e2f05769bd 100644
--- a/src/ralph/lib/permissions/models.py
+++ b/src/ralph/lib/permissions/models.py
@@ -272,7 +272,7 @@ def has_permission_to_object(self, user):
user_perms = self._permissions.has_access(user)
if not user_perms:
return True
- return self._default_manager.filter(
+ return self.__class__.objects.filter(
user_perms,
pk=self.pk
).exists()
@@ -339,7 +339,7 @@ def create_permissions(
if perms:
perms.update(content_type=ctype)
ctypes.add(ctype)
- for perm in _get_all_permissions(klass._meta, ctype):
+ for perm in _get_all_permissions(klass._meta):
searched_perms.append((ctype, perm))
# Find all the Permissions that have a content_type for a model we're
diff --git a/src/ralph/lib/permissions/views.py b/src/ralph/lib/permissions/views.py
index 753f2af948..98320bad6a 100644
--- a/src/ralph/lib/permissions/views.py
+++ b/src/ralph/lib/permissions/views.py
@@ -70,8 +70,9 @@ def update_extra_view_permissions(sender, **kwargs):
logger.info('Updating extra views permissions...')
admin_classes = {}
for model, admin_class in ralph_site._registry.items():
- for change_view in admin_class.change_views:
- admin_classes[change_view] = model
+ if admin_class.change_views:
+ for change_view in admin_class.change_views:
+ admin_classes[change_view] = model
old_permission = Permission.objects.filter(
codename__startswith='can_view_extra_'
diff --git a/src/ralph/lib/polymorphic/models.py b/src/ralph/lib/polymorphic/models.py
index 5a6aca2f2e..640c052de1 100644
--- a/src/ralph/lib/polymorphic/models.py
+++ b/src/ralph/lib/polymorphic/models.py
@@ -16,9 +16,7 @@
from itertools import groupby
from django.contrib.contenttypes.models import ContentType
-from django.db import models, OperationalError
-
-from ralph.lib.error_handling.exceptions import WrappedOperationalError
+from django.db import models
class PolymorphicQuerySet(models.QuerySet):
@@ -33,7 +31,7 @@ def __init__(self, *args, **kwargs):
self._polymorphic_filter_kwargs = {}
super().__init__(*args, **kwargs)
- def iterator(self):
+ def iterator(self): # noqa
"""
Override iterator:
- Iterate for all objects and collected ID
@@ -42,22 +40,28 @@ def iterator(self):
"""
# if this is final-level model, don't check for descendants - just
# return original queryset result
+
if not getattr(self.model, '_polymorphic_descendants', []):
yield from super().iterator()
return
result = []
- content_types_ids = set()
+ content_types_ids = set() # type: set[int]
select_related = None
+
if self.query.select_related:
select_related = self.query.select_related
self.query.select_related = False
+ objs = [obj for obj in super().iterator()]
+ for obj in objs:
+ try:
+ content_types_ids.add(obj.content_type_id)
+ result.append((
+ obj.content_type_id, obj.pk)
+ )
+ except AttributeError as e: # noqa
+ pass
- for obj in super().iterator():
- content_types_ids.add(obj.content_type_id)
- result.append((
- obj.content_type_id, obj.pk)
- )
# store original order of items by PK
pks_order = [r[1] for r in result]
# WARNING! sorting result (by content type) breaks original order of
@@ -114,15 +118,8 @@ def iterator(self):
*self._polymorphic_filter_args,
**self._polymorphic_filter_kwargs
)
- try:
- for obj in model_query:
- result_mapping[obj.pk].append(obj)
- # NOTE(pszulc): We try to catch OperationalError that randomly
- # occurs (1052, "Column 'created' in field list is ambiguous")
- except OperationalError as e:
- raise WrappedOperationalError(
- query=model_query.query, model=self, error_str=str(e)) \
- from e
+ for obj in model_query:
+ result_mapping[obj.pk].append(obj)
# yield objects in original order
for pk in pks_order:
# yield all objects with particular PK
diff --git a/src/ralph/lib/polymorphic/tests/migrations/0003_auto_20240506_1133.py b/src/ralph/lib/polymorphic/tests/migrations/0003_auto_20240506_1133.py
new file mode 100644
index 0000000000..dc2ce82887
--- /dev/null
+++ b/src/ralph/lib/polymorphic/tests/migrations/0003_auto_20240506_1133.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.13 on 2024-05-06 11:33
+from __future__ import unicode_literals
+
+from django.db import migrations
+import django.db.models.manager
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('polymorphic_tests', '0002_somem2mmodel'),
+ ]
+
+ operations = [
+ migrations.AlterModelManagers(
+ name='polymorphicmodelbasetest',
+ managers=[
+ ('polymorphic_objects', django.db.models.manager.Manager()),
+ ],
+ ),
+ migrations.AlterModelManagers(
+ name='polymorphicmodeltest',
+ managers=[
+ ('polymorphic_objects', django.db.models.manager.Manager()),
+ ],
+ ),
+ migrations.AlterModelManagers(
+ name='polymorphicmodeltest2',
+ managers=[
+ ('polymorphic_objects', django.db.models.manager.Manager()),
+ ],
+ ),
+ ]
diff --git a/src/ralph/lib/table/__init__.py b/src/ralph/lib/table/__init__.py
index 64ccbba1a4..e69de29bb2 100644
--- a/src/ralph/lib/table/__init__.py
+++ b/src/ralph/lib/table/__init__.py
@@ -1,3 +0,0 @@
-from ralph.lib.table.table import Table, TableWithUrl
-
-__all__ = ['Table', 'TableWithUrl']
diff --git a/src/ralph/lib/table/tests.py b/src/ralph/lib/table/tests.py
index 3881ac110a..7b7f280d04 100644
--- a/src/ralph/lib/table/tests.py
+++ b/src/ralph/lib/table/tests.py
@@ -1,6 +1,6 @@
from django.test import TestCase
-from ralph.lib.table import Table
+from ralph.lib.table.table import Table
from ralph.tests.models import Foo
diff --git a/src/ralph/lib/template/loaders.py b/src/ralph/lib/template/loaders.py
index 6d0c71a502..64fe48e662 100644
--- a/src/ralph/lib/template/loaders.py
+++ b/src/ralph/lib/template/loaders.py
@@ -3,7 +3,7 @@
from os.path import abspath, dirname, join
from django.apps import apps
-from django.template import TemplateDoesNotExist
+from django.template.loader import TemplateDoesNotExist
from django.template.loaders.base import Loader as BaseLoader
@@ -50,7 +50,7 @@ def get_template_path(self, template_name, template_dirs=None):
template_parts = template_name.split(":", 1)
if len(template_parts) != 2:
- raise TemplateDoesNotExist()
+ raise TemplateDoesNotExist(template_name)
app_label, template_name = template_parts
app = apps.get_app_config(app_label)
diff --git a/src/ralph/lib/transitions/admin.py b/src/ralph/lib/transitions/admin.py
index 2230cf6897..1efa0b7cea 100644
--- a/src/ralph/lib/transitions/admin.py
+++ b/src/ralph/lib/transitions/admin.py
@@ -9,7 +9,8 @@
from django.utils.functional import curry
from django.utils.http import urlencode
-from ralph.admin import RalphAdmin, register
+from ralph.admin.decorators import register
+from ralph.admin.mixins import RalphAdmin
from ralph.admin.views.extra import RalphDetailView
from ralph.helpers import get_model_view_url_name
from ralph.lib.transitions.forms import TransitionForm
diff --git a/src/ralph/lib/transitions/checks.py b/src/ralph/lib/transitions/checks.py
index e0ed14e64d..6fdf8f4b4d 100644
--- a/src/ralph/lib/transitions/checks.py
+++ b/src/ralph/lib/transitions/checks.py
@@ -2,9 +2,7 @@
from django.core.checks import Error
from django.db.utils import DatabaseError
-from django.template.base import TemplateDoesNotExist
-from django.template.loader import get_template
-
+from django.template.loader import get_template, TemplateDoesNotExist
logger = logging.getLogger(__name__)
diff --git a/src/ralph/lib/transitions/models.py b/src/ralph/lib/transitions/models.py
index 209f1fdd14..61e8daa75c 100644
--- a/src/ralph/lib/transitions/models.py
+++ b/src/ralph/lib/transitions/models.py
@@ -4,7 +4,6 @@
import logging
from collections import defaultdict
-import reversion
from dj.choices import Choices
from django import forms
from django.conf import settings
@@ -25,6 +24,7 @@
from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _
from django_extensions.db.fields.json import JSONField
+from reversion import revisions as reversion
from ralph.admin.helpers import (
get_content_type_for_model,
diff --git a/src/ralph/lib/transitions/tests/test_actions.py b/src/ralph/lib/transitions/tests/test_actions.py
index d1c20f672f..9bf31b3ef4 100644
--- a/src/ralph/lib/transitions/tests/test_actions.py
+++ b/src/ralph/lib/transitions/tests/test_actions.py
@@ -126,7 +126,7 @@ def test_sync_api_validation_error(self):
self.assertEqual(response.status_code, 400)
self.assertEqual(
response.data['user'],
- ['This field is required.']
+ ['This field may not be blank.']
)
def test_sync_gui(self):
diff --git a/src/ralph/lib/transitions/views.py b/src/ralph/lib/transitions/views.py
index 84ebf3c4b9..4b2b91aca8 100644
--- a/src/ralph/lib/transitions/views.py
+++ b/src/ralph/lib/transitions/views.py
@@ -2,9 +2,9 @@
from itertools import repeat
from django import forms
+from django.apps import apps
from django.contrib import messages
from django.core.urlresolvers import reverse
-from django.db.models.loading import get_model
from django.db.transaction import atomic, non_atomic_requests
from django.http import (
Http404,
@@ -76,7 +76,7 @@ def _objects_are_valid(self):
def get_template_names(self):
template_names = super().get_template_names()
- template_names.insert(0, self.transition.template_name)
+ template_names.insert(1, self.transition.template_name)
return template_names
@property
@@ -95,7 +95,7 @@ def form_fields_from_actions(self):
autocomplete_model = options.get('autocomplete_model', False)
model = self.obj
if autocomplete_model:
- model = get_model(autocomplete_model)
+ model = apps.get_model(autocomplete_model)
if autocomplete_field:
field = model._meta.get_field(autocomplete_field)
diff --git a/src/ralph/licences/admin.py b/src/ralph/licences/admin.py
index 70013ae94d..f8c9873d0e 100644
--- a/src/ralph/licences/admin.py
+++ b/src/ralph/licences/admin.py
@@ -1,9 +1,13 @@
# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphAdmin, RalphTabularInline, register
+from ralph.admin.decorators import register
from ralph.admin.filters import TagsListFilter
-from ralph.admin.mixins import BulkEditChangeListMixin
+from ralph.admin.mixins import (
+ BulkEditChangeListMixin,
+ RalphAdmin,
+ RalphTabularInline
+)
from ralph.admin.views.extra import RalphDetailViewAdmin
from ralph.assets.invoice_report import InvoiceReportMixin
from ralph.attachments.admin import AttachmentsMixin
diff --git a/src/ralph/licences/migrations/0007_auto_20240506_1633.py b/src/ralph/licences/migrations/0007_auto_20240506_1633.py
new file mode 100644
index 0000000000..ed4b7e13b8
--- /dev/null
+++ b/src/ralph/licences/migrations/0007_auto_20240506_1633.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.13 on 2024-05-06 16:33
+from __future__ import unicode_literals
+
+from django.db import migrations
+import django.db.models.manager
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('licences', '0006_auto_20200909_1115'),
+ ]
+
+ operations = [
+ migrations.AlterModelManagers(
+ name='licence',
+ managers=[
+ ('polymorphic_objects', django.db.models.manager.Manager()),
+ ],
+ ),
+ ]
diff --git a/src/ralph/licences/models.py b/src/ralph/licences/models.py
index 92f5a9ca32..d0e2383d86 100644
--- a/src/ralph/licences/models.py
+++ b/src/ralph/licences/models.py
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
"""SAM module models."""
-import reversion
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Prefetch, Sum
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
+from reversion import revisions as reversion
from ralph.accounts.models import Regionalizable
from ralph.admin.helpers import getattr_dunder
@@ -19,7 +19,7 @@
NamedMixin,
PriceMixin
)
-from ralph.lib.permissions import PermByFieldMixin
+from ralph.lib.permissions.models import PermByFieldMixin
from ralph.lib.polymorphic.models import PolymorphicQuerySet
diff --git a/src/ralph/networks/admin.py b/src/ralph/networks/admin.py
index 8427cb318d..8eba137044 100644
--- a/src/ralph/networks/admin.py
+++ b/src/ralph/networks/admin.py
@@ -4,15 +4,15 @@
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphAdmin, register
+from ralph.admin.decorators import register
from ralph.admin.filters import RelatedAutocompleteFieldListFilter
from ralph.admin.helpers import CastToInteger
-from ralph.admin.mixins import RalphMPTTAdmin
+from ralph.admin.mixins import RalphAdmin, RalphMPTTAdmin
from ralph.admin.views.main import RalphChangeList
from ralph.assets.models import BaseObject
from ralph.data_importer import resources
from ralph.lib.mixins.admin import ParentChangeMixin
-from ralph.lib.table import TableWithUrl
+from ralph.lib.table.table import TableWithUrl
from ralph.networks.filters import (
ContainsIPAddressFilter,
IPRangeFilter,
@@ -155,6 +155,12 @@ class NetworkAdmin(RalphMPTTAdmin):
})
)
+ def save_model(self, request, obj, form, change):
+ super(NetworkAdmin, self).save_model(request, obj, form, change)
+ terminator = form.cleaned_data['terminators']
+ if terminator:
+ obj.terminators.set(terminator, clear=True)
+
def get_changelist(self, request, **kwargs):
return NetworkRalphChangeList
diff --git a/src/ralph/networks/fields.py b/src/ralph/networks/fields.py
index 8ba7288760..94227fac20 100644
--- a/src/ralph/networks/fields.py
+++ b/src/ralph/networks/fields.py
@@ -1,7 +1,7 @@
import ipaddress
from django.core.exceptions import ValidationError
-from django.db.models.fields import CharField, Field
+from django.db.models.fields import CharField
MAX_NETWORK_ADDRESS_LENGTH = 44
@@ -15,7 +15,7 @@ def network_validator(value):
raise ValidationError(exc.message)
-class IPNetwork(Field):
+class IPNetwork(CharField):
"""Field for network with CIDR notation."""
def __init__(self, *args, **kwargs):
@@ -28,20 +28,6 @@ def db_type(self, connection):
max_length=MAX_NETWORK_ADDRESS_LENGTH
).db_type(connection)
- def to_python(self, value):
- if isinstance(value, ipaddress.IPv4Network):
- return value
- if value is None:
- return value
- try:
- return ipaddress.ip_network(value)
- except ValueError as exc:
- raise ValidationError(
- str(exc),
- code='invalid',
- params={'value': value},
- )
-
def from_db_value(self, value, expression, connection, context):
if value is None:
return value
diff --git a/src/ralph/networks/forms.py b/src/ralph/networks/forms.py
index 67b880b665..7dd62f47f4 100755
--- a/src/ralph/networks/forms.py
+++ b/src/ralph/networks/forms.py
@@ -99,7 +99,11 @@ class SimpleNetworkForm(EthernetLockDeleteForm):
modified/deleted.
"""
hostname = CharFormFieldWithAutoStrip(label='Hostname', required=False)
- address = forms.IPAddressField(label='IP address', required=False)
+ address = forms.GenericIPAddressField(
+ label='IP address',
+ required=False,
+ protocol='IPv4'
+ )
ip_fields = ['hostname', 'address']
diff --git a/src/ralph/networks/tests/test_api.py b/src/ralph/networks/tests/test_api.py
index fac610f8df..5b8fbb6196 100644
--- a/src/ralph/networks/tests/test_api.py
+++ b/src/ralph/networks/tests/test_api.py
@@ -96,7 +96,7 @@ def test_change_ip_address_already_occupied_should_not_pass(self):
url = reverse('ipaddress-detail', args=(self.ip1.id,))
response = self.client.patch(url, format='json', data=data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
- self.assertIn('This field must be unique.', response.data['address'])
+ self.assertIn('IPAddress with this IP address already exists.', response.data['address'])
def test_change_ip_address_with_dhcp_exposition_should_not_pass(self):
data = {'address': '127.0.0.3'}
diff --git a/src/ralph/networks/views.py b/src/ralph/networks/views.py
index a1ad126206..b85deedce4 100644
--- a/src/ralph/networks/views.py
+++ b/src/ralph/networks/views.py
@@ -1,10 +1,10 @@
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphTabularInline
from ralph.admin.m2m import RalphTabularM2MInline
+from ralph.admin.mixins import RalphTabularInline
from ralph.admin.views.extra import RalphDetailViewAdmin
from ralph.assets.models.components import Ethernet
-from ralph.lib.table import TableWithUrl
+from ralph.lib.table.table import TableWithUrl
from ralph.networks.forms import NetworkForm, NetworkInlineFormset
from ralph.networks.models import Network
diff --git a/src/ralph/operations/admin.py b/src/ralph/operations/admin.py
index 127cbf6525..b127ba052a 100644
--- a/src/ralph/operations/admin.py
+++ b/src/ralph/operations/admin.py
@@ -5,8 +5,8 @@
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphAdmin, RalphMPTTAdmin, register
-from ralph.admin.mixins import RalphAdminForm
+from ralph.admin.decorators import register
+from ralph.admin.mixins import RalphAdmin, RalphAdminForm, RalphMPTTAdmin
from ralph.admin.views.main import RalphChangeList
from ralph.admin.widgets import AdminDateTimeWidget
from ralph.assets.models import BaseObject
diff --git a/src/ralph/reports/admin.py b/src/ralph/reports/admin.py
index c63af43a9d..6df109fc11 100644
--- a/src/ralph/reports/admin.py
+++ b/src/ralph/reports/admin.py
@@ -1,4 +1,5 @@
-from ralph.admin import RalphAdmin, RalphTabularInline, register
+from ralph.admin.decorators import register
+from ralph.admin.mixins import RalphAdmin, RalphTabularInline
from ralph.reports.forms import ReportTemplateFormset
from ralph.reports.models import Report, ReportLanguage, ReportTemplate
diff --git a/src/ralph/reports/resources.py b/src/ralph/reports/resources.py
index 71dfc698e3..97167d9c8d 100644
--- a/src/ralph/reports/resources.py
+++ b/src/ralph/reports/resources.py
@@ -17,7 +17,7 @@ class ChoiceWidget(Widget):
def __init__(self, choice: Type[Choices]) -> None:
self.choice = choice
- def render(self, value):
+ def render(self, value, obj=None):
if value:
return self.choice.from_id(value).name
else:
diff --git a/src/ralph/security/models.py b/src/ralph/security/models.py
index 2e58d7f686..2940ff8715 100644
--- a/src/ralph/security/models.py
+++ b/src/ralph/security/models.py
@@ -11,7 +11,7 @@
TaggableMixin,
TimeStampMixin
)
-from ralph.lib.permissions import PermByFieldMixin
+from ralph.lib.permissions.models import PermByFieldMixin
def any_exceeded(vulnerabilties):
diff --git a/src/ralph/security/views.py b/src/ralph/security/views.py
index 03d7123445..9c5d293411 100644
--- a/src/ralph/security/views.py
+++ b/src/ralph/security/views.py
@@ -7,13 +7,13 @@
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphAdmin, RalphTabularInline, register
+from ralph.admin.decorators import register
from ralph.admin.helpers import get_admin_url
+from ralph.admin.mixins import RalphAdmin, RalphTabularInline
from ralph.admin.sites import ralph_site
from ralph.admin.views.extra import RalphDetailView
from ralph.security.models import SecurityScan, Vulnerability
-
logger = logging.getLogger(__name__)
diff --git a/src/ralph/settings/base.py b/src/ralph/settings/base.py
index 62be387907..3a6f445b6f 100644
--- a/src/ralph/settings/base.py
+++ b/src/ralph/settings/base.py
@@ -9,6 +9,9 @@
from ralph.settings.hooks import HOOKS_CONFIGURATION # noqa: F401
+SILENCED_SYSTEM_CHECKS = []
+SILENCED_SYSTEM_CHECKS += ['models.E006', ] # TODO fix
+
def bool_from_env(var, default: bool=False) -> bool:
"""Helper for converting env string into boolean.
@@ -56,10 +59,11 @@ def get_sentinels(sentinels_string):
# Application definition
INSTALLED_APPS = (
+ 'django.contrib.contenttypes',
+ 'taggit',
+ 'django.contrib.auth',
'ralph.admin',
'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
'django.contrib.humanize',
'django.contrib.sessions',
'django.contrib.messages',
@@ -103,7 +107,6 @@ def get_sentinels(sentinels_string):
'ralph.ssl_certificates',
'rest_framework',
'rest_framework.authtoken',
- 'taggit',
'taggit_serializer',
'djmoney',
)
@@ -119,7 +122,6 @@ def get_sentinels(sentinels_string):
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
'threadlocals.middleware.ThreadLocalMiddleware',
- 'ralph.lib.error_handling.middleware.OperationalErrorHandlerMiddleware',
'ralph.lib.metrics.middlewares.RequestMetricsMiddleware'
)
@@ -169,7 +171,7 @@ def get_sentinels(sentinels_string):
DATABASES = {
'default': {
- 'ENGINE': os.environ.get('DATABASE_ENGINE', 'transaction_hooks.backends.mysql'), # noqa
+ 'ENGINE': os.environ.get('DATABASE_ENGINE', 'django.db.backends.mysql'), # noqa
'NAME': os.environ.get('DATABASE_NAME', 'ralph_ng'),
'USER': os.environ.get('DATABASE_USER', 'ralph_ng'),
'PASSWORD': os.environ.get('DATABASE_PASSWORD', 'ralph_ng') or None,
@@ -272,11 +274,6 @@ def get_sentinels(sentinels_string):
'level': os.environ.get('LOGGING_RALPH_LEVEL', 'WARNING'),
'propagate': True,
},
- 'ralph.lib.error_handling.middleware': {
- 'handlers': ['file'],
- 'level': os.environ.get('LOGGING_RALPH_LEVEL', 'WARNING'),
- 'propagate': False,
- },
'rq.worker': {
'level': os.environ.get('LOGGING_RQ_LEVEL', 'WARNING'),
'handlers': ['file'],
diff --git a/src/ralph/settings/local.template b/src/ralph/settings/local.template
index cd4cdf0c18..fe205aaf91 100644
--- a/src/ralph/settings/local.template
+++ b/src/ralph/settings/local.template
@@ -2,7 +2,7 @@ from ralph.settings.dev import * # noqa
DATABASES = {
'default': {
- 'ENGINE': 'transaction_hooks.backends.mysql',
+ 'ENGINE': 'django.db.backends.mysql',
'NAME': os.environ.get('DATABASE_NAME', 'ralph_ng'),
'USER': os.environ.get('DATABASE_USER', 'ralph_ng'),
'PASSWORD': os.environ.get('DATABASE_PASSWORD', 'ralph_ng') or None,
@@ -11,7 +11,7 @@ DATABASES = {
'ATOMIC_REQUESTS': True,
'TEST': {
'NAME': 'test_ralph_ng',
- 'ENGINE': 'transaction_hooks.backends.postgresql_psycopg2',
+ 'ENGINE': 'django.db.backends.postgresql_psycopg2',
'USER': os.environ.get('DATABASE_USER', 'ralph_ng'),
'PASSWORD': os.environ.get('DATABASE_PASSWORD', 'ralph_ng') or None,
'HOST': os.environ.get('DATABASE_HOST', '127.0.0.1'),
diff --git a/src/ralph/settings/test.py b/src/ralph/settings/test.py
index 2d65b0c153..72aa5910ca 100644
--- a/src/ralph/settings/test.py
+++ b/src/ralph/settings/test.py
@@ -10,7 +10,7 @@
TEST_DB_ENGINE = os.environ.get('TEST_DB_ENGINE', 'mysql')
if TEST_DB_ENGINE == 'psql':
DATABASES['default'].update({
- 'ENGINE': 'transaction_hooks.backends.postgresql_psycopg2',
+ 'ENGINE': 'django.db.backends.postgresql_psycopg2',
'PORT': os.environ.get('DATABASE_PORT', 5432),
'OPTIONS': {},
})
diff --git a/src/ralph/signals.py b/src/ralph/signals.py
index 6f7b106f76..2ac27932c4 100644
--- a/src/ralph/signals.py
+++ b/src/ralph/signals.py
@@ -1,4 +1,4 @@
-from django.db import connection
+from django.db import transaction
from django.db.models.signals import post_save
from django.dispatch import receiver
@@ -52,6 +52,4 @@ def wrapper():
func(instance)
setattr(instance, called_already_attr, True)
- # TODO(mkurek): replace connection by transaction after upgrading to
- # Django 1.9
- connection.on_commit(wrapper)
+ transaction.on_commit(wrapper)
diff --git a/src/ralph/sim_cards/admin.py b/src/ralph/sim_cards/admin.py
index b201ffd2b1..7e5e60f849 100644
--- a/src/ralph/sim_cards/admin.py
+++ b/src/ralph/sim_cards/admin.py
@@ -1,7 +1,11 @@
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphAdmin, register
-from ralph.admin.mixins import BulkEditChangeListMixin, RalphAdminMixin
+from ralph.admin.decorators import register
+from ralph.admin.mixins import (
+ BulkEditChangeListMixin,
+ RalphAdmin,
+ RalphAdminMixin
+)
from ralph.admin.views.multiadd import MulitiAddAdminMixin
from ralph.lib.transitions.admin import TransitionAdminMixin
from ralph.sim_cards.forms import SIMCardForm
diff --git a/src/ralph/ssl_certificates/admin.py b/src/ralph/ssl_certificates/admin.py
index 78769d24f8..c2d6a16e31 100644
--- a/src/ralph/ssl_certificates/admin.py
+++ b/src/ralph/ssl_certificates/admin.py
@@ -1,7 +1,8 @@
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphAdmin, register
+from ralph.admin.decorators import register
from ralph.admin.filters import DateListFilter
+from ralph.admin.mixins import RalphAdmin
from ralph.attachments.admin import AttachmentsMixin
from ralph.ssl_certificates.forms import SSLCertificateForm
from ralph.ssl_certificates.models import SSLCertificate
diff --git a/src/ralph/supports/admin.py b/src/ralph/supports/admin.py
index 9b334433fe..db220a4c0a 100644
--- a/src/ralph/supports/admin.py
+++ b/src/ralph/supports/admin.py
@@ -4,10 +4,14 @@
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphAdmin, RalphTabularInline, register
+from ralph.admin.decorators import register
from ralph.admin.filters import TagsListFilter
from ralph.admin.helpers import generate_html_link
-from ralph.admin.mixins import BulkEditChangeListMixin
+from ralph.admin.mixins import (
+ BulkEditChangeListMixin,
+ RalphAdmin,
+ RalphTabularInline
+)
from ralph.admin.views.extra import RalphDetailViewAdmin
from ralph.attachments.admin import AttachmentsMixin
from ralph.data_importer import resources
diff --git a/src/ralph/supports/migrations/0009_auto_20240506_1633.py b/src/ralph/supports/migrations/0009_auto_20240506_1633.py
new file mode 100644
index 0000000000..853f53829b
--- /dev/null
+++ b/src/ralph/supports/migrations/0009_auto_20240506_1633.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.13 on 2024-05-06 16:33
+from __future__ import unicode_literals
+
+from django.db import migrations
+import django.db.models.manager
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('supports', '0008_auto_20200909_1012'),
+ ]
+
+ operations = [
+ migrations.AlterModelManagers(
+ name='support',
+ managers=[
+ ('polymorphic_objects', django.db.models.manager.Manager()),
+ ],
+ ),
+ ]
diff --git a/src/ralph/tests/admin.py b/src/ralph/tests/admin.py
index bcb229fc10..743cef0e4c 100644
--- a/src/ralph/tests/admin.py
+++ b/src/ralph/tests/admin.py
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphAdmin, register
+from ralph.admin.decorators import register
from ralph.admin.m2m import RalphTabularM2MInline
+from ralph.admin.mixins import RalphAdmin
from ralph.attachments.admin import AttachmentsMixin
from ralph.lib.transitions.admin import TransitionAdminMixin
from ralph.networks.views import NetworkInline
diff --git a/src/ralph/tests/mixins.py b/src/ralph/tests/mixins.py
index 2b69e81ad9..aedd084143 100644
--- a/src/ralph/tests/mixins.py
+++ b/src/ralph/tests/mixins.py
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
import sys
from imp import reload
+from importlib import import_module
from django.conf import settings
from django.core.urlresolvers import clear_url_caches
-from django.utils.importlib import import_module
from ralph.tests.factories import UserFactory
diff --git a/src/ralph/trade_marks/admin.py b/src/ralph/trade_marks/admin.py
index 3e301b0c7b..1da56f0073 100644
--- a/src/ralph/trade_marks/admin.py
+++ b/src/ralph/trade_marks/admin.py
@@ -3,13 +3,14 @@
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphAdmin, RalphTabularInline, register
+from ralph.admin.decorators import register
from ralph.admin.filters import (
ChoicesListFilter,
custom_title_filter,
DateListFilter,
RelatedAutocompleteFieldListFilter
)
+from ralph.admin.mixins import RalphAdmin, RalphTabularInline
from ralph.admin.views.extra import RalphDetailViewAdmin
from ralph.attachments.admin import AttachmentsMixin
from ralph.trade_marks.forms import (
diff --git a/src/ralph/urls/base.py b/src/ralph/urls/base.py
index ff910b006e..44d40da9d7 100644
--- a/src/ralph/urls/base.py
+++ b/src/ralph/urls/base.py
@@ -3,7 +3,7 @@
from rest_framework.authtoken import views
from sitetree.sitetreeapp import SiteTree # noqa
-from ralph.admin import ralph_site as admin
+from ralph.admin.sites import ralph_site as admin
from ralph.api import router
from ralph.health_check import status_health, status_ping
diff --git a/src/ralph/virtual/admin.py b/src/ralph/virtual/admin.py
index 811d94394f..7831367604 100644
--- a/src/ralph/virtual/admin.py
+++ b/src/ralph/virtual/admin.py
@@ -6,8 +6,9 @@
from django.db.models import Count, Prefetch
from django.utils.translation import ugettext_lazy as _
-from ralph.admin import RalphAdmin, RalphAdminForm, RalphTabularInline, register
+from ralph.admin.decorators import register
from ralph.admin.filters import BaseObjectHostnameFilter, TagsListFilter
+from ralph.admin.mixins import RalphAdmin, RalphAdminForm, RalphTabularInline
from ralph.assets.models import BaseObject
from ralph.assets.models.components import Ethernet
from ralph.assets.views import ComponentsAdminView, RalphDetailViewAdmin
@@ -33,7 +34,8 @@
CloudProject,
CloudProvider,
VirtualServer,
- VirtualServerType)
+ VirtualServerType
+)
if settings.ENABLE_DNSAAS_INTEGRATION:
from ralph.dns.views import DNSView
@@ -476,7 +478,8 @@ class CloudProjectAdmin(CustomFieldValueAdminMixin, RalphAdmin):
fields = ['name', 'project_id', 'cloudprovider', 'service_env', 'tags',
'remarks', 'instances_count']
list_display = ['name', 'service_env', 'instances_count']
- list_select_related = ['cloudprovider__name', 'service_env__environment',
+ list_select_related = ['cloudprovider',
+ 'service_env__environment',
'service_env__service']
list_filter = ['service_env', 'cloudprovider', TagsListFilter]
readonly_fields = ['name', 'project_id', 'cloudprovider', 'created',
diff --git a/src/ralph/virtual/management/commands/openstack_sync.py b/src/ralph/virtual/management/commands/openstack_sync.py
index 2b4f1de923..20dfd27bf2 100644
--- a/src/ralph/virtual/management/commands/openstack_sync.py
+++ b/src/ralph/virtual/management/commands/openstack_sync.py
@@ -5,11 +5,11 @@
from enum import auto, Enum
from functools import lru_cache
-import reversion as revisions
from django.conf import settings
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from django.core.management.base import BaseCommand
from django.db import IntegrityError, transaction
+from reversion import revisions
from ralph.data_center.models.physical import DataCenterAsset
from ralph.lib.openstack.client import (