Compare commits

..

16 Commits

Author SHA1 Message Date
Agate 0c14103e4b user: allow customization of User.get_full_name() through templates (#72945)
gitea-wip/hobo/pipeline/pr-main This commit looks good Details
2023-01-30 16:46:52 +01:00
Frédéric Péters 65ff0ad9a6 context processors: return mini-template if there's no skeleton url (#73796) 2023-01-27 08:25:40 +01:00
Benjamin Dauvergne a5acd7e2b2 environment: clean old auto variable for internal ips (#65235) 2023-01-24 16:24:31 +01:00
Frédéric Péters 8a9ad4ffa5 applications: skip unknown services (#73583) 2023-01-19 19:37:25 +01:00
Frédéric Péters 3a6640f8f2 misc: close opened key files in authentic settings loader (#73550) 2023-01-18 13:49:35 +01:00
Benjamin Dauvergne 2e18ac3a78 tox.ini: fix coverage warning
gitea-wip/hobo/pipeline/head There was a failure building this commit Details
py3-django22-coverage-multipublik/lib/python3.9/site-packages/coverage/control.py:687: CoverageWarning: Conflicting dynamic contexts (dynamic-conflict)
	    self._warn("Conflicting dynamic contexts", slug="dynamic-conflict", once=True)
2023-01-16 17:06:30 +01:00
Benjamin Dauvergne f5ebbc4215 debian: fix typo (#73437) 2023-01-16 17:05:00 +01:00
Emmanuel Cazenave aa7d275028 tox: drop django-tables2 (#73271) 2023-01-16 15:34:40 +01:00
Emmanuel Cazenave d022506c33 misc: drop djangorestframework 3.9 compatibility (#73260)
gitea-wip/hobo/pipeline/pr-main Build started... Details
2023-01-16 15:33:50 +01:00
Benjamin Dauvergne ce88277e90 misc: improve get_safe_db_name (#72643) 2023-01-16 15:07:56 +01:00
Benjamin Dauvergne a652576b84 misc: improve jenkins log with parallel tox (#72643) 2023-01-16 15:07:56 +01:00
Benjamin Dauvergne 157a69fab6 misc: fix warning about deprecated connection.get_tenant() (#73294)
gitea-wip/hobo/pipeline/pr-main This commit looks good Details
2023-01-12 12:04:49 +01:00
Benjamin Dauvergne 760a8b1a7c Revert "misc: improve get_safe_db_name (#72643)"
This reverts commit 56c611d77b.
2023-01-12 10:27:18 +01:00
Benjamin Dauvergne be635f1001 Revert "misc: improve jenkins log with parallel tox (#72643)"
This reverts commit 1dc65bde3e.
2023-01-12 10:27:11 +01:00
Benjamin Dauvergne 56c611d77b misc: improve get_safe_db_name (#72643)
gitea-wip/hobo/pipeline/pr-main Build started... Details
2023-01-12 10:17:59 +01:00
Benjamin Dauvergne 1dc65bde3e misc: improve jenkins log with parallel tox (#72643) 2023-01-12 10:17:59 +01:00
31 changed files with 357 additions and 70 deletions

19
Jenkinsfile vendored
View File

@ -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()

View File

@ -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': {

View File

@ -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

View File

@ -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/')

View File

@ -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:

View File

@ -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)

View File

@ -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('/')),

View File

@ -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('/')),

View File

@ -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),
]

View File

@ -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:

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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): ")

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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',

View File

@ -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 = {

View File

@ -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')]

94
tests/test_migrations.py Normal file
View File

@ -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'

View File

@ -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):

View File

@ -0,0 +1 @@
{{ user.first_name }} {{ user.cached_extra_attributes.nicknames.0 }} {{ user.cached_extra_attributes.nicknames.2 }}

View File

@ -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
View File

@ -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