Compare commits
16 Commits
5c828a7524
...
0c14103e4b
Author | SHA1 | Date |
---|---|---|
Agate | 0c14103e4b | |
Frédéric Péters | 65ff0ad9a6 | |
Benjamin Dauvergne | a5acd7e2b2 | |
Frédéric Péters | 8a9ad4ffa5 | |
Frédéric Péters | 3a6640f8f2 | |
Benjamin Dauvergne | 2e18ac3a78 | |
Benjamin Dauvergne | f5ebbc4215 | |
Emmanuel Cazenave | aa7d275028 | |
Emmanuel Cazenave | d022506c33 | |
Benjamin Dauvergne | ce88277e90 | |
Benjamin Dauvergne | a652576b84 | |
Benjamin Dauvergne | 157a69fab6 | |
Benjamin Dauvergne | 760a8b1a7c | |
Benjamin Dauvergne | be635f1001 | |
Benjamin Dauvergne | 56c611d77b | |
Benjamin Dauvergne | 1dc65bde3e |
|
@ -9,23 +9,18 @@ pipeline {
|
|||
stages {
|
||||
stage('Unit Tests') {
|
||||
steps {
|
||||
sh 'tox -rv -p 8'
|
||||
sh """
|
||||
python3 -m venv ./venv/
|
||||
./venv/bin/pip install "tox"
|
||||
./venv/bin/tox run-parallel --workdir ./tox-workdir --recreate --parallel 8 --parallel-no-spinner
|
||||
./venv/bin/tox run -e coverage-report"""
|
||||
}
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
utils = new Utils()
|
||||
utils.publish_coverage('coverage-*.xml')
|
||||
utils.publish_coverage_native(
|
||||
'index.html', 'htmlcov-py3-django22-drf312-coverage-authentic', 'Coverage authentic tests')
|
||||
utils.publish_coverage_native(
|
||||
'index.html', 'htmlcov-py3-django22-drf312-coverage-hobo', 'Coverage hobo tests')
|
||||
utils.publish_coverage_native(
|
||||
'index.html', 'htmlcov-py3-django22-drf312-coverage-multipublik', 'Coverage multipublik tests')
|
||||
utils.publish_coverage_native(
|
||||
'index.html', 'htmlcov-py3-django22-drf312-coverage-multitenant', 'Coverage multitenant tests')
|
||||
utils.publish_coverage_native(
|
||||
'index.html', 'htmlcov-py3-django22-drf312-coverage-passerelle', 'Coverage passerelle tests')
|
||||
utils.publish_coverage('coverage.xml')
|
||||
utils.publish_coverage_native('index.html', 'htmlcov', 'Coverage')
|
||||
utils.publish_pylint('pylint.out')
|
||||
}
|
||||
mergeJunitResults()
|
||||
|
|
|
@ -159,7 +159,7 @@ LOGGING = {
|
|||
'django.security.DisallowedRedirect': {
|
||||
'filters': ['clamp_to_warning'],
|
||||
},
|
||||
'django.core.exceptions.DisallowedHost': {
|
||||
'django.security.DisallowedHost': {
|
||||
'filters': ['clamp_to_warning'],
|
||||
},
|
||||
'django.template': {
|
||||
|
|
|
@ -6,7 +6,7 @@ from kombu.common import Broadcast
|
|||
|
||||
def notify_agents(data):
|
||||
'''Send notifications to all other tenants'''
|
||||
tenant = connection.get_tenant()
|
||||
tenant = connection.tenant
|
||||
if not hasattr(tenant, 'domain_url'):
|
||||
# Fake tenant, certainly during migration, do nothing
|
||||
return
|
||||
|
|
|
@ -297,6 +297,8 @@ class Version(models.Model):
|
|||
continue
|
||||
service_objects = {x.get_base_url_path(): x for x in get_installed_services(types=[service_id])}
|
||||
for service in services.values():
|
||||
if service['url'] not in service_objects:
|
||||
continue
|
||||
if service_objects[service['url']].secondary:
|
||||
continue
|
||||
url = urllib.parse.urljoin(service['url'], 'api/export-import/bundle-import/')
|
||||
|
|
|
@ -83,6 +83,8 @@ class RemoteTemplate:
|
|||
# deployed.
|
||||
return Template('<html><body>{% block content %}{% endblock %}</body></html>')
|
||||
self.theme_skeleton_url = context['portal_url'] + '__skeleton__/'
|
||||
elif not settings.THEME_SKELETON_URL:
|
||||
return Template('<html><body>{% block content %}{% endblock %}</body></html>')
|
||||
else:
|
||||
self.theme_skeleton_url = settings.THEME_SKELETON_URL
|
||||
if item is None:
|
||||
|
|
|
@ -122,7 +122,7 @@ class Command(BaseCommand):
|
|||
wait_operationals(services, timeout, self.verbosity, self.terminal_width, notify_agents)
|
||||
|
||||
def create_hobo(self, url, primary=False, title=None, slug=None, **kwargs):
|
||||
if connection.get_tenant().schema_name == 'public':
|
||||
if connection.tenant.schema_name == 'public':
|
||||
# if we're not currently in a tenant then we force the creation of
|
||||
# a primary hobo
|
||||
primary = True
|
||||
|
@ -245,7 +245,7 @@ class Command(BaseCommand):
|
|||
|
||||
def set_theme(self, theme):
|
||||
set_theme(theme)
|
||||
HoboDeployCommand().configure_theme({'variables': {'theme': theme}}, connection.get_tenant())
|
||||
HoboDeployCommand().configure_theme({'variables': {'theme': theme}}, connection.tenant)
|
||||
|
||||
def set_variable(self, name, value, label=None, auto=None):
|
||||
if auto is None:
|
||||
|
@ -299,7 +299,7 @@ class Command(BaseCommand):
|
|||
attribute.save()
|
||||
|
||||
def cook(self, filename):
|
||||
current_tenant = connection.get_tenant()
|
||||
current_tenant = connection.tenant
|
||||
self.run_cook(filename)
|
||||
connection.set_tenant(current_tenant)
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ def populate_local_hobo(apps, schema_editor):
|
|||
pass
|
||||
|
||||
if hasattr(connection, 'get_tenant'):
|
||||
build_absolute_uri = getattr(connection.get_tenant(), 'build_absolute_uri', None)
|
||||
build_absolute_uri = getattr(connection.tenant, 'build_absolute_uri', None)
|
||||
if build_absolute_uri:
|
||||
Hobo.objects.create(
|
||||
secret_key=get_local_key(build_absolute_uri('/')),
|
||||
|
|
|
@ -13,7 +13,7 @@ def populate_local_hobo(apps, schema_editor):
|
|||
pass
|
||||
|
||||
if hasattr(connection, 'get_tenant'):
|
||||
build_absolute_uri = getattr(connection.get_tenant(), 'build_absolute_uri', None)
|
||||
build_absolute_uri = getattr(connection.tenant, 'build_absolute_uri', None)
|
||||
if build_absolute_uri:
|
||||
Hobo.objects.create(
|
||||
secret_key=get_local_key(build_absolute_uri('/')),
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 2.2.28 on 2022-07-01 07:11
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def clean_internal_ips(apps, schema_editor):
|
||||
Variable = apps.get_model('environment', 'Variable')
|
||||
for var in Variable.objects.filter(name='SETTING_INTERNAL_IPS', service_pk__isnull=True, auto=True):
|
||||
if Variable.objects.filter(
|
||||
name='SETTING_INTERNAL_IPS.extend', service_pk__isnull=True, auto=True
|
||||
).exists():
|
||||
var.delete()
|
||||
else:
|
||||
var.name = 'SETTING_INTERNAL_IPS.extend'
|
||||
var.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('environment', '0027_allow_long_slug'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(clean_internal_ips, migrations.RunPython.noop),
|
||||
]
|
|
@ -60,8 +60,8 @@ def get_or_create_local_hobo():
|
|||
hobo = Hobo.objects.get(local=True)
|
||||
except Hobo.DoesNotExist:
|
||||
build_absolute_uri = None
|
||||
if hasattr(connection, 'get_tenant') and hasattr(connection.get_tenant(), 'build_absolute_uri'):
|
||||
build_absolute_uri = connection.get_tenant().build_absolute_uri
|
||||
if hasattr(connection, 'get_tenant') and hasattr(connection.tenant, 'build_absolute_uri'):
|
||||
build_absolute_uri = connection.tenant.build_absolute_uri
|
||||
else:
|
||||
request = StoreRequestMiddleware.get_request()
|
||||
if request:
|
||||
|
|
|
@ -106,8 +106,9 @@ class RequestContextFilter(logging.Filter):
|
|||
if saml_identifier:
|
||||
record.user_uuid = saml_identifier.name_id
|
||||
record.user = record.user_uuid[:6]
|
||||
if hasattr(user, 'get_full_name') and user.get_full_name():
|
||||
record.user = record.user_display_name = user.get_full_name()
|
||||
if hasattr(user, 'original_get_full_name') and user.original_get_full_name():
|
||||
# record original full name, not templated version from user_name app
|
||||
record.user = record.user_display_name = user.original_get_full_name()
|
||||
if getattr(user, 'email', None):
|
||||
record.user = record.user_email = user.email
|
||||
if getattr(user, 'username', None):
|
||||
|
|
|
@ -28,7 +28,7 @@ from tenant_schemas.postgresql_backend.base import FakeTenant
|
|||
class WhooshSearchBackend(haystack.backends.whoosh_backend.WhooshSearchBackend):
|
||||
@property
|
||||
def use_file_storage(self):
|
||||
tenant = connection.get_tenant()
|
||||
tenant = connection.tenant
|
||||
return not (isinstance(connection.tenant, FakeTenant))
|
||||
|
||||
@use_file_storage.setter
|
||||
|
@ -53,7 +53,7 @@ class WhooshSearchBackend(haystack.backends.whoosh_backend.WhooshSearchBackend):
|
|||
|
||||
@property
|
||||
def path(self):
|
||||
tenant = connection.get_tenant()
|
||||
tenant = connection.tenant
|
||||
return os.path.join(tenant.get_directory(), 'whoosh_index')
|
||||
|
||||
@path.setter
|
||||
|
|
|
@ -24,7 +24,7 @@ class AdminEmailHandler(django.utils.log.AdminEmailHandler):
|
|||
|
||||
subject = super().format_subject(subject)
|
||||
try:
|
||||
subject = '[%s] %s' % (connection.get_tenant().domain_url, subject)
|
||||
subject = '[%s] %s' % (connection.tenant.domain_url, subject)
|
||||
except AttributeError:
|
||||
pass
|
||||
return subject
|
||||
|
|
|
@ -105,8 +105,8 @@ https://django-tenant-schemas.readthedocs.org/en/latest/use.html#creating-a-tena
|
|||
if options.get('domain'):
|
||||
domain = options['domain']
|
||||
else:
|
||||
if connection.get_tenant().schema_name != 'public':
|
||||
return connection.get_tenant()
|
||||
if connection.tenant.schema_name != 'public':
|
||||
return connection.tenant
|
||||
displayed_tenants = all_tenants
|
||||
while True:
|
||||
domain = input("Enter Tenant Domain ('?' to list): ")
|
||||
|
|
|
@ -59,7 +59,7 @@ def run_command_from_argv(command, argv):
|
|||
command.stderr.write(str(e), lambda x: x)
|
||||
else:
|
||||
command.stderr.write(
|
||||
'%s: %s: %s' % (connection.get_tenant(), e.__class__.__name__, exception_to_text(e))
|
||||
'%s: %s: %s' % (connection.tenant, e.__class__.__name__, exception_to_text(e))
|
||||
)
|
||||
return e
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ class TenantSettingsWrapper:
|
|||
return self.default_settings
|
||||
try:
|
||||
self.local.in_get_wrapped = True
|
||||
tenant = connection.get_tenant()
|
||||
tenant = connection.tenant
|
||||
if not hasattr(tenant, 'domain_url'):
|
||||
return self.default_settings
|
||||
return self.get_tenant_settings(tenant)
|
||||
|
|
|
@ -339,8 +339,10 @@ class Authentic(FileBaseSettingsLoader):
|
|||
saml_key = os.path.join(tenant_dir, 'saml.key')
|
||||
if os.path.exists(saml_crt) and os.path.exists(saml_key):
|
||||
tenant_settings.A2_IDP_SAML2_ENABLE = True
|
||||
tenant_settings.A2_IDP_SAML2_SIGNATURE_PUBLIC_KEY = open(saml_crt).read()
|
||||
tenant_settings.A2_IDP_SAML2_SIGNATURE_PRIVATE_KEY = open(saml_key).read()
|
||||
with open(saml_crt) as f:
|
||||
tenant_settings.A2_IDP_SAML2_SIGNATURE_PUBLIC_KEY = f.read()
|
||||
with open(saml_key) as f:
|
||||
tenant_settings.A2_IDP_SAML2_SIGNATURE_PRIVATE_KEY = f.read()
|
||||
if not getattr(tenant_settings, 'A2_IDP_OIDC_JWKSET', None):
|
||||
from jwcrypto import jwk
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ def _new__bootstrap_inner(self):
|
|||
if tenant is not None:
|
||||
from django.db import connection
|
||||
|
||||
old_tenant = connection.get_tenant()
|
||||
old_tenant = connection.tenant
|
||||
connection.set_tenant(self.tenant)
|
||||
try:
|
||||
_Thread__bootstrap_inner(self)
|
||||
|
|
|
@ -1,7 +1,24 @@
|
|||
# hobo - portal to configure and deploy applications
|
||||
# Copyright (C) 2022 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
|
||||
def get_safe_db_name():
|
||||
def get_safe_db_name(max_length=53):
|
||||
"""
|
||||
PostgreSQL database name limit is 63 characters, which can become
|
||||
an issue during testing, because we need to build a unique
|
||||
|
@ -19,4 +36,14 @@ def get_safe_db_name():
|
|||
# when we're in parallel mode, pytest-django will do this
|
||||
# for us at a later point
|
||||
parts.append(os.environ.get('TOX_ENV_NAME'))
|
||||
return '_'.join(parts)
|
||||
full_db_name = '_'.join(parts)
|
||||
if len(full_db_name) < max_length:
|
||||
return full_db_name
|
||||
hashcode_length = 8
|
||||
hashcode = hashlib.md5(full_db_name.encode()).hexdigest()[: hashcode_length - 2]
|
||||
prefix_length = (max_length - hashcode_length) - (max_length - hashcode_length) // 2
|
||||
suffix_length = (max_length - hashcode_length) // 2
|
||||
assert hashcode_length + prefix_length + suffix_length == max_length
|
||||
truncated_db_name = full_db_name[:prefix_length] + '_' + hashcode + '_' + full_db_name[-suffix_length:]
|
||||
assert len(truncated_db_name) == max_length
|
||||
return truncated_db_name
|
||||
|
|
|
@ -16,7 +16,7 @@ def get_full_name(user):
|
|||
from hobo.agent.common.models import UserExtraAttributes
|
||||
|
||||
try:
|
||||
return user.get_full_name_from_template()
|
||||
return utils.get_full_name_from_template(user)
|
||||
except (
|
||||
# There may have been an issue during provisionning
|
||||
UserExtraAttributes.DoesNotExist,
|
||||
|
@ -30,8 +30,35 @@ def get_full_name(user):
|
|||
|
||||
def cached_extra_attributes(user):
|
||||
if 'authentic2' in settings.INSTALLED_APPS:
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
try:
|
||||
from authentic2.custom_user.models import User
|
||||
from authentic2.models import Attribute, AttributeValue
|
||||
except ImportError:
|
||||
return
|
||||
try:
|
||||
user_ct = ContentType.objects.get_for_model(User)
|
||||
except ContentType.DoesNotExist:
|
||||
return
|
||||
|
||||
extra_attributes = {}
|
||||
# we're in authentic, attributes are looked up differently
|
||||
return {k: v.to_python() for k, v in user.attributes.values.items()}
|
||||
for attribute in Attribute.objects.filter(disabled=False):
|
||||
atvs = AttributeValue.objects.filter(
|
||||
content_type=user_ct,
|
||||
object_id=user.id,
|
||||
attribute=attribute,
|
||||
)
|
||||
if not atvs:
|
||||
continue
|
||||
if not attribute.multiple:
|
||||
atv = atvs.first()
|
||||
extra_attributes[attribute.name] = atv.to_python()
|
||||
else:
|
||||
extra_attributes[attribute.name] = [atv.to_python() for atv in atvs]
|
||||
return extra_attributes
|
||||
|
||||
try:
|
||||
return user.extra_attributes.data
|
||||
except django.db.models.ObjectDoesNotExist:
|
||||
|
@ -49,11 +76,9 @@ class UserNameConfig(AppConfig):
|
|||
to ensure consistency in the rendering of user name
|
||||
in the front-office, backoffice, emails, etc.
|
||||
"""
|
||||
logger.info('hobo.user_name: installing User.get_full_name customisation…')
|
||||
logger.info('hobo.user_name: installing User.get_full_name customization…')
|
||||
User = get_user_model()
|
||||
|
||||
# for explicit access
|
||||
User.get_full_name_from_template = utils.get_full_name_from_template
|
||||
# to have a fallback when necessary if the new method crashes during render
|
||||
User.original_get_full_name = User.get_full_name
|
||||
# to replace the rendering everywhere in a consistent manner
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import django.template
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
|
||||
def get_full_name_from_template(user):
|
||||
template_name = 'includes/user-info-user-name.html'
|
||||
template = django.template.loader.get_template(template_name)
|
||||
context = {}
|
||||
context['user'] = user
|
||||
return template.render(context)
|
||||
return render_to_string(template_name, context)
|
||||
|
|
2
setup.py
2
setup.py
|
@ -154,7 +154,7 @@ setup(
|
|||
'django-mellon',
|
||||
'django-tenant-schemas',
|
||||
'prometheus_client',
|
||||
'djangorestframework>=3.4, <3.13',
|
||||
'djangorestframework>=3.12, <3.14',
|
||||
'dnspython',
|
||||
'lxml',
|
||||
'num2words==0.5.9',
|
||||
|
|
|
@ -10,7 +10,7 @@ sys.path.append(TESTS_DIR)
|
|||
LANGUAGE_CODE = 'en-us'
|
||||
BROKER_URL = 'memory://'
|
||||
|
||||
INSTALLED_APPS += ('hobo.agent.common', 'hobo.user_name.apps.UserNameConfig', 'user_name_test_app')
|
||||
INSTALLED_APPS += ('hobo.agent.common', 'hobo.user_name.apps.UserNameConfig')
|
||||
|
||||
ALLOWED_HOSTS.append('localhost')
|
||||
|
||||
|
@ -37,6 +37,7 @@ DATABASES = {
|
|||
}
|
||||
}
|
||||
|
||||
TEMPLATES[0]['DIRS'].append('tests/templates') # pylint: disable=undefined-variable
|
||||
TEMPLATES[0]['OPTIONS'].setdefault('builtins', []).append('hobo.templatetags.hobo')
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
|
|
|
@ -320,7 +320,7 @@ def test_create_hobo_primary(mocked_TenantMiddleware, mocked_call_command, mocke
|
|||
tenant = Mock()
|
||||
tenant.schema_name = 'public'
|
||||
tenant.get_directory = Mock(return_value='/foo')
|
||||
mocked_connection.get_tenant = Mock(return_value=tenant)
|
||||
mocked_connection.tenant = tenant
|
||||
mocked_connection.set_tenant = Mock()
|
||||
mocked_TenantMiddleware.get_tenant_by_hostname = Mock(return_value=tenant)
|
||||
mocked_call_command.side_effect = CommandError
|
||||
|
@ -348,7 +348,7 @@ def test_create_hobo_not_primary(mocked_TenantMiddleware, mocked_call_command, m
|
|||
tenant = Mock()
|
||||
tenant.schema_name = 'other than public'
|
||||
tenant.get_directory = Mock(return_value='/foo')
|
||||
mocked_connection.get_tenant = Mock(return_value=tenant)
|
||||
mocked_connection.tenant = tenant
|
||||
mocked_connection.set_tenant = Mock()
|
||||
mocked_TenantMiddleware.get_tenant_by_hostname = Mock(return_value=tenant)
|
||||
|
||||
|
@ -419,21 +419,19 @@ def test_set_idp(command, db):
|
|||
@patch('hobo.environment.management.commands.cook.connection')
|
||||
@patch('hobo.agent.common.management.commands.hobo_deploy.Command.configure_theme')
|
||||
def test_set_theme(mocked_configure_theme, mocked_connection, mocked_set_theme, command):
|
||||
mocked_connection.get_tenant = Mock(return_value='the tenant')
|
||||
mocked_connection.tenant = 'the tenant'
|
||||
command.set_theme('the theme')
|
||||
|
||||
assert mocked_set_theme.mock_calls == [call('the theme')]
|
||||
assert len(mocked_connection.get_tenant.mock_calls) == 1
|
||||
assert mocked_configure_theme.mock_calls == [call({'variables': {'theme': 'the theme'}}, 'the tenant')]
|
||||
|
||||
|
||||
@patch('hobo.environment.management.commands.cook.connection')
|
||||
def test_cook(mocked_connection, command):
|
||||
mocked_connection.get_tenant = Mock(return_value='the tenant')
|
||||
mocked_connection.tenant = 'the tenant'
|
||||
mocked_connection.set_tenant = Mock()
|
||||
command.run_cook = Mock()
|
||||
command.cook('a-recipe-file.json')
|
||||
|
||||
assert len(mocked_connection.get_tenant.mock_calls) == 1
|
||||
assert command.run_cook.mock_calls == [call('a-recipe-file.json')]
|
||||
assert mocked_connection.set_tenant.mock_calls == [call('the tenant')]
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
# hobo - portal to configure and deploy applications
|
||||
# Copyright (C) 2023 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import pytest
|
||||
from django.core.management import call_command
|
||||
from django.db import connection
|
||||
from django.db.migrations.executor import MigrationExecutor
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def migration(request, transactional_db):
|
||||
# see https://gist.github.com/asfaltboy/b3e6f9b5d95af8ba2cc46f2ba6eae5e2
|
||||
"""
|
||||
This fixture returns a helper object to test Django data migrations.
|
||||
The fixture returns an object with two methods;
|
||||
- `before` to initialize db to the state before the migration under test
|
||||
- `after` to execute the migration and bring db to the state after the migration
|
||||
The methods return `old_apps` and `new_apps` respectively; these can
|
||||
be used to initiate the ORM models as in the migrations themselves.
|
||||
For example:
|
||||
def test_foo_set_to_bar(migration):
|
||||
old_apps = migration.before([('my_app', '0001_inital')])
|
||||
Foo = old_apps.get_model('my_app', 'foo')
|
||||
Foo.objects.create(bar=False)
|
||||
assert Foo.objects.count() == 1
|
||||
assert Foo.objects.filter(bar=False).count() == Foo.objects.count()
|
||||
# executing migration
|
||||
new_apps = migration.apply([('my_app', '0002_set_foo_bar')])
|
||||
Foo = new_apps.get_model('my_app', 'foo')
|
||||
|
||||
assert Foo.objects.filter(bar=False).count() == 0
|
||||
assert Foo.objects.filter(bar=True).count() == Foo.objects.count()
|
||||
Based on: https://gist.github.com/blueyed/4fb0a807104551f103e6
|
||||
"""
|
||||
|
||||
class Migrator:
|
||||
def before(self, targets, at_end=True):
|
||||
"""Specify app and starting migration names as in:
|
||||
before([('app', '0001_before')]) => app/migrations/0001_before.py
|
||||
"""
|
||||
executor = MigrationExecutor(connection)
|
||||
executor.migrate(targets)
|
||||
executor.loader.build_graph()
|
||||
return executor._create_project_state(with_applied_migrations=True).apps
|
||||
|
||||
def apply(self, targets):
|
||||
"""Migrate forwards to the "targets" migration"""
|
||||
executor = MigrationExecutor(connection)
|
||||
executor.migrate(targets)
|
||||
executor.loader.build_graph()
|
||||
return executor._create_project_state(with_applied_migrations=True).apps
|
||||
|
||||
yield Migrator()
|
||||
|
||||
call_command('migrate', verbosity=0)
|
||||
|
||||
|
||||
def test_0028_clean_internal_ips_case_delete(transactional_db, migration):
|
||||
old_apps = migration.before([('environment', '0027_allow_long_slug')])
|
||||
|
||||
Variable = old_apps.get_model('environment', 'Variable')
|
||||
var1 = Variable.objects.create(name='SETTING_INTERNAL_IPS', auto=True, value='["1.1.1.1"]')
|
||||
var2 = Variable.objects.create(name='SETTING_INTERNAL_IPS.extend', auto=True, value='["2.2.2.2"]')
|
||||
|
||||
new_apps = migration.apply([('environment', '0028_clean_internal_ips')])
|
||||
Variable = new_apps.get_model('environment', 'Variable')
|
||||
assert Variable.objects.filter(id=var1.id).count() == 0
|
||||
assert Variable.objects.filter(id=var2.id).count() == 1
|
||||
|
||||
|
||||
def test_0028_clean_internal_ips_case_rename(transactional_db, migration):
|
||||
old_apps = migration.before([('environment', '0027_allow_long_slug')])
|
||||
|
||||
Variable = old_apps.get_model('environment', 'Variable')
|
||||
var1 = Variable.objects.create(name='SETTING_INTERNAL_IPS', auto=True, value='["1.1.1.1"]')
|
||||
|
||||
new_apps = migration.apply([('environment', '0028_clean_internal_ips')])
|
||||
Variable = new_apps.get_model('environment', 'Variable')
|
||||
assert Variable.objects.filter(id=var1.id).count() == 1
|
||||
var1.refresh_from_db()
|
||||
assert var1.name == 'SETTING_INTERNAL_IPS.extend'
|
|
@ -2,6 +2,7 @@ import pytest
|
|||
from django.contrib.auth.models import User
|
||||
|
||||
from hobo.agent.common.models import UserExtraAttributes
|
||||
from hobo.user_name.utils import get_full_name_from_template
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -29,7 +30,7 @@ def test_user_cached_extra_attributes_missing_fallbacks_to_empty_dict(user):
|
|||
|
||||
|
||||
def test_user_get_full_name_from_template(user):
|
||||
assert user.get_full_name_from_template() == 'Jane bar'
|
||||
assert get_full_name_from_template(user) == 'Jane bar'
|
||||
|
||||
|
||||
def test_user_get_full_name(user):
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{{ user.first_name }} {{ user.cached_extra_attributes.nicknames.0 }} {{ user.cached_extra_attributes.nicknames.2 }}
|
|
@ -0,0 +1,92 @@
|
|||
import pytest
|
||||
from authentic2.models import Attribute, AttributeValue
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import override_settings
|
||||
from tenant_schemas.utils import tenant_context
|
||||
|
||||
from hobo.agent.common.models import UserExtraAttributes
|
||||
from hobo.user_name.utils import get_full_name_from_template
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
TEMPLATES_DIR_CONFIG_INDIVIDUAL = ['tests/templates']
|
||||
TEMPLATES_DIR_CONFIG_MULTIPLE = ['tests_authentic/custom_templates']
|
||||
|
||||
|
||||
def test_get_full_name_from_template_utils_from_multiple_attrs(db, tenant, settings):
|
||||
with tenant_context(tenant):
|
||||
user = User.objects.create(
|
||||
first_name='Jane',
|
||||
last_name='Doe',
|
||||
)
|
||||
Attribute.objects.create(
|
||||
name='foo',
|
||||
label='Foo',
|
||||
kind='string',
|
||||
required=False,
|
||||
multiple=False,
|
||||
user_visible=True,
|
||||
user_editable=True,
|
||||
)
|
||||
Attribute.objects.create(
|
||||
name='nicknames',
|
||||
label='Nicknames',
|
||||
kind='string',
|
||||
required=False,
|
||||
multiple=True,
|
||||
user_visible=True,
|
||||
user_editable=True,
|
||||
)
|
||||
user.attributes.nicknames = ['Milly', 'Molly', 'Minnie']
|
||||
user.attributes.foo = 'bar'
|
||||
user.save()
|
||||
|
||||
templates_conf_individual = settings.TEMPLATES.copy()
|
||||
templates_conf_individual[0]['DIRS'] = TEMPLATES_DIR_CONFIG_INDIVIDUAL
|
||||
with override_settings(TEMPLATES=templates_conf_individual):
|
||||
assert get_full_name_from_template(user) == 'Jane bar'
|
||||
|
||||
templates_conf_multiple = settings.TEMPLATES.copy()
|
||||
templates_conf_multiple[0]['DIRS'] = TEMPLATES_DIR_CONFIG_MULTIPLE
|
||||
with override_settings(TEMPLATES=templates_conf_multiple):
|
||||
assert get_full_name_from_template(user) == 'Jane Milly Minnie'
|
||||
|
||||
|
||||
def test_get_full_name_from_template_accessor_from_multiple_attrs(db, tenant, settings):
|
||||
with tenant_context(tenant):
|
||||
user = User.objects.create(
|
||||
first_name='Jane',
|
||||
last_name='Doe',
|
||||
)
|
||||
Attribute.objects.create(
|
||||
name='foo',
|
||||
label='Foo',
|
||||
kind='string',
|
||||
required=False,
|
||||
multiple=False,
|
||||
user_visible=True,
|
||||
user_editable=True,
|
||||
)
|
||||
Attribute.objects.create(
|
||||
name='nicknames',
|
||||
label='Nicknames',
|
||||
kind='string',
|
||||
required=False,
|
||||
multiple=True,
|
||||
user_visible=True,
|
||||
user_editable=True,
|
||||
)
|
||||
user.attributes.nicknames = ['Milly', 'Molly', 'Minnie']
|
||||
user.attributes.foo = 'bar'
|
||||
user.save()
|
||||
|
||||
templates_conf_individual = settings.TEMPLATES.copy()
|
||||
templates_conf_individual[0]['DIRS'] = TEMPLATES_DIR_CONFIG_INDIVIDUAL
|
||||
with override_settings(TEMPLATES=templates_conf_individual):
|
||||
assert user.get_full_name() == 'Jane bar'
|
||||
|
||||
templates_conf_multiple = settings.TEMPLATES.copy()
|
||||
templates_conf_multiple[0]['DIRS'] = TEMPLATES_DIR_CONFIG_MULTIPLE
|
||||
with override_settings(TEMPLATES=templates_conf_multiple):
|
||||
assert user.get_full_name() == 'Jane Milly Minnie'
|
57
tox.ini
57
tox.ini
|
@ -3,21 +3,25 @@
|
|||
# test suite on all supported python versions. To use it, "pip install tox"
|
||||
# and then run "tox" from this directory.
|
||||
[tox]
|
||||
min_version = 4
|
||||
toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/hobo/{env:BRANCH_NAME:}
|
||||
envlist =
|
||||
py3-django22-drf39-{hobo,authentic,multipublik,multitenant,schemas,passerelle}
|
||||
py3-django22-drf312-coverage-{hobo,authentic,multipublik,multitenant,schemas,passerelle}
|
||||
py3-black
|
||||
py3-django22-coverage-{hobo,authentic,multipublik,multitenant,schemas,passerelle}
|
||||
code-style
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
parallel_show_output = True
|
||||
setenv =
|
||||
# necessary to separate coverage binary output between parallel environments
|
||||
COVERAGE_FILE=.coverage.{envname}
|
||||
DISABLE_GLOBAL_HANDLERS=1
|
||||
BRANCH_NAME={env:BRANCH_NAME:}
|
||||
COVERAGE_FILE={envdir}/coverage
|
||||
DB_ENGINE=django.db.backends.postgresql_psycopg2
|
||||
SETUPTOOLS_USE_DISTUTILS=stdlib
|
||||
JUNIT=--junitxml=junit-{envname}.xml
|
||||
JUNIT=--junitxml=junit-{envname}.xml
|
||||
COVERAGE=--cov=hobo/ --cov-config .coveragerc --cov-report= --cov-branch
|
||||
|
||||
hobo: DJANGO_SETTINGS_MODULE=hobo.settings
|
||||
hobo: HOBO_SETTINGS_FILE=tests/settings.py
|
||||
schemas: DJANGO_SETTINGS_MODULE=hobo.settings
|
||||
|
@ -32,11 +36,14 @@ setenv =
|
|||
passerelle: DEBIAN_CONFIG_COMMON=debian/debian_config_common.py
|
||||
passerelle: PASSERELLE_SETTINGS_FILE=tests_passerelle/settings.py
|
||||
passerelle: DJANGO_SETTINGS_MODULE=passerelle.settings
|
||||
coverage: COVERAGE=--cov-report xml:coverage-{envname}.xml --cov-report html:htmlcov-{envname} --cov=hobo/ --cov-config .coveragerc
|
||||
fast: NOMIGRATIONS=--nomigrations
|
||||
# test directories
|
||||
hobo: TEST_DIRECTORY=tests/
|
||||
schemas: TEST_DIRECTORY=tests_schemas/
|
||||
multitenant: TEST_DIRECTORY=tests_multitenant/
|
||||
multipublik: TEST_DIRECTORY=tests_multipublik/
|
||||
authentic: TEST_DIRECTORY=tests_authentic/
|
||||
passerelle: TEST_DIRECTORY=tests_passerelle/
|
||||
deps:
|
||||
drf39: djangorestframework>=3.9.2,<3.10
|
||||
drf312: djangorestframework>=3.12,<3.13
|
||||
django22: django>=2.2,<2.3
|
||||
pytest!=6.0.0
|
||||
pytest-cov
|
||||
|
@ -50,7 +57,6 @@ deps:
|
|||
django-mellon
|
||||
django-webtest
|
||||
Markdown<3
|
||||
django-tables2<2.0
|
||||
authentic: https://git.entrouvert.org/authentic.git/snapshot/authentic-main.tar.gz
|
||||
passerelle: https://git.entrouvert.org/passerelle.git/snapshot/passerelle-main.tar.gz
|
||||
passerelle: python-memcached
|
||||
|
@ -69,21 +75,36 @@ deps:
|
|||
Pillow
|
||||
black: pre-commit
|
||||
git+https://git.entrouvert.org/publik-django-templatetags.git
|
||||
allowlist_externals =
|
||||
./getlasso3.sh
|
||||
commands =
|
||||
./getlasso3.sh
|
||||
hobo: py.test {env:JUNIT:} {env:COVERAGE:} {env:NOMIGRATIONS:} {posargs:tests/}
|
||||
schemas: py.test {env:JUNIT:} {env:COVERAGE:} {env:NOMIGRATIONS:} {posargs:tests_schemas/}
|
||||
multitenant: py.test {env:JUNIT:} {env:COVERAGE:} {env:NOMIGRATIONS:} {posargs:tests_multitenant/}
|
||||
multipublik: py.test {env:JUNIT:} {env:COVERAGE:} {env:NOMIGRATIONS:} {posargs:tests_multipublik/}
|
||||
authentic: py.test {env:JUNIT:} {env:FAST:} {env:COVERAGE:} {env:NOMIGRATIONS:} {posargs:tests_authentic/}
|
||||
passerelle: py.test {env:JUNIT:} {env:COVERAGE:} {env:NOMIGRATIONS:} {posargs:tests_passerelle/}
|
||||
black: pre-commit run black --all-files --show-diff-on-failure
|
||||
py.test {posargs:{tty::{env:JUNIT:}} {tty::{env:COVERAGE:}} {env:TEST_DIRECTORY:}}
|
||||
|
||||
[testenv:code-style]
|
||||
basepython = python3
|
||||
skip_install = true
|
||||
deps =
|
||||
pre-commit
|
||||
commands =
|
||||
pre-commit run --all-files --show-diff-on-failure
|
||||
|
||||
[testenv:update-locales]
|
||||
setenv =
|
||||
DJANGO_SETTINGS_MODULE=hobo.settings
|
||||
deps =
|
||||
allowlist_externals =
|
||||
./getlasso3.sh
|
||||
commands =
|
||||
./getlasso3.sh
|
||||
./manage.py makemessages --add-location=file
|
||||
./manage.py compilemessages
|
||||
|
||||
[testenv:coverage-report]
|
||||
setenv =
|
||||
deps =
|
||||
coverage
|
||||
commands =
|
||||
python3 -m coverage erase --data-file=.coverage
|
||||
python3 -m coverage combine
|
||||
python3 -m coverage html -d htmlcov --show-contexts
|
||||
python3 -m coverage xml
|
||||
|
|
Loading…
Reference in New Issue