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 %} -  ({{ field.help_text|striptags }}) +  ({{ field.help_text|striptags }}) {% 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 (