Projet

Général

Profil

0002-add-new-agent-task-to-provision-objects-to-tenants-f.patch

Benjamin Dauvergne, 15 septembre 2015 11:18

Télécharger (24,8 ko)

Voir les différences:

Subject: [PATCH 2/2] add new agent task to provision objects to tenants (fixes
 #8217)

First use is to connect it to post_save, post_delete signal on Role
model of authentic, to propagate roles to tenants.
 MANIFEST.in                                        |   1 +
 debian/agent/sudo-hobo-agent                       |   5 +
 debian/debian_config_common.py                     |   4 +
 hobo/agent/authentic2/apps.py                      |  73 ++++++++++
 hobo/agent/authentic2/role_forms.py                |  96 ++++++++++++++
 hobo/agent/common/__init__.py                      |  25 ++++
 .../common/management/commands/hobo_notify.py      |   6 +
 hobo/agent/worker/celery.py                        |   8 ++
 hobo/agent/worker/services.py                      |  33 ++++-
 hobo/environment/locale/fr/LC_MESSAGES/django.po   | 147 ++++++++++++++-------
 hobo/locale/fr/LC_MESSAGES/django.po               | 109 ++++++++++++++-
 11 files changed, 448 insertions(+), 59 deletions(-)
 create mode 100644 hobo/agent/authentic2/role_forms.py
 create mode 100644 hobo/agent/common/management/commands/hobo_notify.py
MANIFEST.in
3 3
recursive-include hobo/profile/templates *.html *.txt
4 4
recursive-include hobo/environment/templates *.html *.txt
5 5
recursive-include hobo/locale *.po *.mo
6
recursive-include hobo/agent/authentic2/locale *.po *.mo
6 7
recursive-include hobo/environment/locale *.po *.mo
7 8
recursive-include tests *.py
8 9
include hobo/multitenant/README
debian/agent/sudo-hobo-agent
3 3
hobo-agent ALL=(combo)NOPASSWD:/usr/bin/combo-manage hobo_deploy * -
4 4
hobo-agent ALL=(passerelle)NOPASSWD:/usr/bin/passerelle-manage hobo_deploy * -
5 5
hobo-agent ALL=(fargo)NOPASSWD:/usr/bin/fargo-manage hobo_deploy * -
6
hobo-agent ALL=(wcs-au-quotidien)NOPASSWD:/usr/sbin/wcsctl -f /etc/wcs/wcs-au-quotidien.cfg hobo_notify -
7
hobo-agent ALL=(authentic-multitenant)NOPASSWD:/usr/bin/authentic2-multitenant-manage hobo_notify -
8
hobo-agent ALL=(combo)NOPASSWD:/usr/bin/combo-manage hobo_notify -
9
hobo-agent ALL=(passerelle)NOPASSWD:/usr/bin/passerelle-manage hobo_notify -
10
hobo-agent ALL=(fargo)NOPASSWD:/usr/bin/fargo-manage hobo_notify -
debian/debian_config_common.py
176 176
LANGUAGES = (('fr', u'Fran\xe7ais'),)
177 177
USE_L10N = True
178 178
USE_TZ = True
179

  
180
# Celery configuration
181
BROKER_URL = 'amqp://'
182
BROKER_TASK_EXPIRES = 120
hobo/agent/authentic2/apps.py
1
import json
2

  
1 3
from django.apps import AppConfig
4
from django.db.models.signals import post_save, post_delete
5
from django.db.models import Q
6
from django.conf import settings
7

  
8
from django_rbac.utils import get_role_model
9

  
10
from hobo.agent.common import notify_agents
11
from authentic2.utils import to_list
12
from authentic2.saml.models import LibertyProvider
13

  
14

  
15
def get_ou(role_or_through):
16
    if hasattr(role_or_through, 'ou'):
17
        return role_or_through.ou
18
    else:
19
        return role_or_through.role.ou
20

  
21

  
22
def get_audience(role_or_through):
23
    ou = get_ou(role_or_through)
24
    if ou:
25
        qs = LibertyProvider.objects.filter(ou=ou)
26
    else:
27
        qs = LibertyProvider.objects.filter(ou__isnull=True)
28
    return list(qs.values_list('entity_id', flat=True))
29

  
30

  
31
def get_related_roles(role_or_through):
32
    ou = get_ou(role_or_through)
33
    Role = get_role_model()
34
    qs = Role.objects.filter(admin_scope_id__isnull=True) \
35
        .prefetch_related('attributes')
36
    if ou:
37
        qs = qs.filter(ou=ou)
38
    else:
39
        qs = qs.filter(ou__isnull=True)
40
    for role in qs:
41
        role.emails = []
42
        role.emails_to_members = False
43
        for attribute in role.attributes.all():
44
            if attribute.name in ('emails', 'emails_to_members') and attribute.kind == 'json':
45
                setattr(role, attribute.name, json.loads(attribute.value))
46
    return qs
47

  
48

  
49
def notify_roles(sender, instance, **kwargs):
50
    notify_agents({
51
        '@type': 'provision',
52
        'audience': get_audience(instance),
53
        'full': True,
54
        'objects': [
55
            {
56
                '@type': 'role',
57
                'uuid': role.uuid,
58
                'name': role.name,
59
                'slug': role.slug,
60
                'description': role.description,
61
                'emails': role.emails,
62
                'emails_to_members': role.emails_to_members,
63
            } for role in get_related_roles(instance)
64
        ]
65
    })
66

  
2 67

  
3 68
class Authentic2AgentConfig(AppConfig):
4 69
    name = 'hobo.agent.authentic2'
5 70
    label = 'authentic2_agent'
6 71
    verbose_name = 'Authentic2 Agent'
72

  
73
    def ready(self):
74
        Role = get_role_model()
75
        post_save.connect(notify_roles, Role)
76
        post_delete.connect(notify_roles, Role)
77
        post_save.connect(notify_roles, Role.members.through)
78
        post_delete.connect(notify_roles, Role.members.through)
79
        settings.A2_MANAGER_ROLE_FORM_CLASS = 'hobo.agent.authentic2.role_forms.RoleForm'
hobo/agent/authentic2/role_forms.py
1
import json
2

  
3
from django import forms
4
from django.core import validators
5
from django.core.exceptions import ValidationError
6
from django.utils.translation import ugettext_lazy as _
7

  
8
from authentic2.a2_rbac.models import RoleAttribute, Role
9
from authentic2.validators import EmailValidator
10
from authentic2.manager.forms import RoleEditForm
11

  
12

  
13
class ListValidator(object):
14
    def __init__(self, item_validator):
15
        self.item_validator = item_validator
16

  
17
    def __call__(self, value):
18
        for i, item in enumerate(value):
19
            try:
20
                self.item_validator(item)
21
            except ValidationError, e:
22
                raise ValidationError(
23
                    _('Item {0} is invalid: {1}') % (i, e.args[0]))
24

  
25

  
26
class CommaSeparatedInput(forms.TextInput):
27
    def _format_value(self, value):
28
        return u', '.join(value)
29

  
30

  
31
class CommaSeparatedCharField(forms.Field):
32
    widget = CommaSeparatedInput
33

  
34
    def __init__(self, dedup=True, max_length=None, min_length=None, *args,
35
                 **kwargs):
36
        self.dedup = dedup
37
        self.max_length = max_length
38
        self.min_length = min_length
39
        item_validators = kwargs.pop('item_validators', [])
40
        super(CommaSeparatedCharField, self).__init__(*args, **kwargs)
41
        for item_validator in item_validators:
42
            self.validators.append(ListValidator(item_validator))
43

  
44
    def to_python(self, value):
45
        if value in validators.EMPTY_VALUES:
46
            return []
47

  
48
        value = [item.strip() for item in value.split(',') if item.strip()]
49
        if self.dedup:
50
            value = list(set(value))
51

  
52
        return value
53

  
54
    def clean(self, value):
55
        value = self.to_python(value)
56
        self.validate(value)
57
        self.run_validators(value)
58
        return value
59

  
60

  
61
class RoleForm(RoleEditForm):
62
    emails = CommaSeparatedCharField(label=_('Emails'),
63
                                     item_validators=[EmailValidator()],
64
                                     required=False)
65
    emails_to_members = forms.BooleanField(required=False,
66
                                           label=_('Emails to members'))
67

  
68
    def __init__(self, *args, **kwargs):
69
        instance = kwargs.get('instance')
70
        if instance:
71
            fields = Role._meta.get_all_field_names()
72
            initial = kwargs.setdefault('initial', {})
73
            role_attributes = RoleAttribute.objects.filter(role=instance,
74
                                                           kind='json')
75
            for role_attribute in role_attributes:
76
                if role_attribute.name in fields:
77
                    continue
78
                initial[role_attribute.name] = json.loads(role_attribute.value)
79
        super(RoleForm, self).__init__(*args, **kwargs)
80

  
81
    def save(self, commit=True):
82
        fields = Role._meta.get_all_field_names()
83
        assert commit
84
        instance = super(RoleForm, self).save(commit=commit)
85
        for field in self.cleaned_data:
86
            if field in fields:
87
                continue
88
            value = json.dumps(self.cleaned_data[field])
89
            ra, created = RoleAttribute.objects.get_or_create(
90
                role=instance, name=field, kind='json',
91
                defaults={'value': value})
92
            if not created and ra.value != value:
93
                ra.value = value
94
                ra.save()
95
        instance.save()
96
        return instance
hobo/agent/common/__init__.py
1
from celery import Celery
2
from kombu.common import Broadcast
3

  
4
from django.conf import settings
5
from django.db import connection
6

  
7

  
8
def notify_agents(data):
9
    '''Send notifications to all other tenants'''
10
    notification = {
11
        'tenant': connection.get_tenant().domain_url,
12
        'data': data,
13
    }
14
    with Celery('hobo', broker=settings.BROKER_URL) as app:
15
        app.conf.update(
16
            CELERY_TASK_SERIALIZER='json',
17
            CELERY_ACCEPT_CONTENT=['json'],
18
            CELERY_RESULT_SERIALIZER='json',
19
            CELERY_QUEUES=(Broadcast('broadcast_tasks'), )
20
        )
21
        # see called method in hobo.agent.worker.celery
22
        app.send_task('hobo-notify',
23
                      (notification,),
24
                      expires=settings.BROKER_TASK_EXPIRES,
25
                      queue='broadcast_tasks')
hobo/agent/common/management/commands/hobo_notify.py
1
from django.core.management.base import BaseCommand
2

  
3

  
4
class Command(BaseCommand):
5
    def handle(self, *args, **kwargs):
6
        pass
hobo/agent/worker/celery.py
13 13
    CELERY_QUEUES=(Broadcast('broadcast_tasks'), )
14 14
)
15 15

  
16

  
16 17
@app.task(name='hobo-deploy', bind=True)
17 18
def deploy(self, environment):
18 19
    services.deploy(environment)
20

  
21

  
22
@app.task(name='hobo-notify', bind=True, acks_late=True)
23
def hobo_notify(self, notification):
24
    assert 'tenant' in notification
25
    assert 'data' in notification
26
    services.notify(notification['data'])
hobo/agent/worker/services.py
1
import sys
1 2
import ConfigParser
2 3
import fnmatch
3 4
import json
......
19 20
        self.title = title
20 21
        self.secret_key = secret_key
21 22

  
22
    def is_for_us(self):
23
    @classmethod
24
    def is_for_us(cls, url):
23 25
        # This function checks if the requested service is to be hosted
24 26
        # on this server, and return True if appropriate.
25 27
        #
......
30 32
        # (ex: "! *.dev.au-quotidien.com").
31 33
        if not settings.AGENT_HOST_PATTERNS:
32 34
            return True
33
        patterns = settings.AGENT_HOST_PATTERNS.get(self.service_id)
35
        patterns = settings.AGENT_HOST_PATTERNS.get(cls.service_id)
34 36
        if patterns is None:
35 37
            return True
36
        parsed_url = urllib2.urlparse.urlsplit(self.base_url)
38
        parsed_url = urllib2.urlparse.urlsplit(url)
37 39
        netloc = parsed_url.netloc
38 40
        match = False
39 41
        for pattern in patterns:
......
55 57
                shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
56 58
        stdout = cmd_process.communicate(input=json.dumps(environment))
57 59

  
60
    @classmethod
61
    def notify(cls, data):
62
        for audience in data.get('audience', []):
63
            if cls.is_for_us(audience):
64
                break
65
        else:
66
            return
67
        cmd = cls.service_manage_cmd + ' hobo_notify -'
68
        try:
69
            cmd_process = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
70
                                           stdout=subprocess.PIPE, stderr=subprocess.PIPE)
71
        except OSError:
72
            return
73
        stdout, stderr = cmd_process.communicate(input=json.dumps(data))
74
        if cmd_process.returncode != 0:
75
            raise RuntimeError('command "%s" failed: %r %r' % (cmd, stdout, stderr))
76

  
58 77

  
59 78
class Passerelle(BaseService):
60 79
    service_id = 'passerelle'
......
106 125
        if not service_id in service_classes:
107 126
            continue
108 127
        service_obj = service_classes.get(service_id)(**service)
109
        if not service_obj.is_for_us():
128
        if not service_obj.is_for_us(service_obj.base_url):
110 129
            logger.debug('skipping as not for us: %r', service_obj)
111 130
            continue
112 131
        if service_obj.check_timestamp(hobo_timestamp):
113 132
            logger.debug('skipping uptodate site: %r', service_obj)
114 133
            continue
115 134
        service_obj.execute(environment)
135

  
136
def notify(data):
137
    for klassname, service in globals().items():
138
        if not hasattr(service, 'service_id'):
139
            continue
140
        service.notify(data)
hobo/environment/locale/fr/LC_MESSAGES/django.po
8 8
msgstr ""
9 9
"Project-Id-Version: hobo 0\n"
10 10
"Report-Msgid-Bugs-To: \n"
11
"POT-Creation-Date: 2014-12-03 08:06+0100\n"
11
"POT-Creation-Date: 2015-09-15 09:17+0000\n"
12 12
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 13
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14 14
"Language: French\n"
......
17 17
"Content-Transfer-Encoding: 8bit\n"
18 18
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
19 19

  
20
#: forms.py:27 models.py:51
20
#: hobo/environment/forms.py:27 hobo/environment/models.py:56
21 21
msgid "Template"
22 22
msgstr "Modèle"
23 23

  
24
#: models.py:20
24
#: hobo/environment/models.py:19
25 25
msgid "name"
26 26
msgstr "nom"
27 27

  
28
#: models.py:21
28
#: hobo/environment/models.py:20
29
msgid "label"
30
msgstr ""
31

  
32
#: hobo/environment/models.py:21
29 33
msgid "value"
30 34
msgstr "valeur"
31 35

  
32
#: models.py:23
36
#: hobo/environment/models.py:23
33 37
msgid "start with [ or { for a JSON document"
34 38
msgstr "démarrer avec [ ou { pour un document JSON"
35 39

  
36
#: models.py:47
40
#: hobo/environment/models.py:52
37 41
msgid "Title"
38 42
msgstr "Titre"
39 43

  
40
#: models.py:49
44
#: hobo/environment/models.py:53
45
msgid "Slug"
46
msgstr ""
47

  
48
#: hobo/environment/models.py:54
41 49
msgid "Base URL"
42 50
msgstr "URL de base"
43 51

  
44
#: models.py:50
52
#: hobo/environment/models.py:55
45 53
msgid "Secret Key"
46 54
msgstr "Clé secrète"
47 55

  
48
#: models.py:113
56
#: hobo/environment/models.py:146
57
msgid "Use as IdP"
58
msgstr ""
59

  
60
#: hobo/environment/models.py:150
49 61
msgid "Authentic Identity Provider"
50 62
msgstr "Fournisseur d'identités Authentic"
51 63

  
52
#: models.py:114
64
#: hobo/environment/models.py:151
53 65
msgid "Authentic Identity Providers"
54 66
msgstr "Fournisseurs d'identité Authentic"
55 67

  
56
#: models.py:121
68
#: hobo/environment/models.py:155
69
msgid "Authentic"
70
msgstr ""
71

  
72
#: hobo/environment/models.py:159
57 73
msgid "User Management"
58 74
msgstr "Gestion des utilisateurs"
59 75

  
60
#: models.py:122
76
#: hobo/environment/models.py:160
61 77
msgid "Role Management"
62 78
msgstr "Gestion des rôles"
63 79

  
64
#: models.py:128
80
#: hobo/environment/models.py:172 hobo/environment/models.py:173
65 81
msgid "w.c.s. Web Forms"
66 82
msgstr "Téléformulaires w.c.s."
67 83

  
68
#: models.py:129
69
msgid ".w.c.s. Web Forms"
70
msgstr "Téléformulaires w.c.s."
84
#: hobo/environment/models.py:177
85
msgid "w.c.s."
86
msgstr ""
71 87

  
72
#: models.py:142 models.py:143
88
#: hobo/environment/models.py:193 hobo/environment/models.py:194
89
#: hobo/environment/models.py:198
73 90
msgid "Passerelle"
74 91
msgstr "Passerelle"
75 92

  
76
#: templates/environment/generic_confirm_delete.html:5
93
#: hobo/environment/models.py:214
94
msgid "Combo Portal"
95
msgstr ""
96

  
97
#: hobo/environment/models.py:215
98
msgid "Combo Portals"
99
msgstr ""
100

  
101
#: hobo/environment/models.py:219
102
msgid "Combo"
103
msgstr ""
104

  
105
#: hobo/environment/models.py:235 hobo/environment/models.py:236
106
msgid "Fargo document box"
107
msgstr ""
108

  
109
#: hobo/environment/models.py:240
110
msgid "Fargo"
111
msgstr ""
112

  
113
#: hobo/environment/models.py:253
114
msgid "Welco Multichannel Home"
115
msgstr ""
116

  
117
#: hobo/environment/templates/environment/generic_confirm_delete.html:5
77 118
#, python-format
78 119
msgid "Removal of \"%(title)s\""
79 120
msgstr "Suppression de \"%(title)s\""
80 121

  
81
#: templates/environment/generic_confirm_delete.html:12
122
#: hobo/environment/templates/environment/generic_confirm_delete.html:12
82 123
msgid "Are you sure you want to delete this element ?"
83 124
msgstr "Êtes-vous sûr de vouloir supprimer cet élément ?"
84 125

  
85
#: templates/environment/generic_confirm_delete.html:15
86
#: templates/environment/tenant_confirm_delete.html:14
126
#: hobo/environment/templates/environment/generic_confirm_delete.html:15
127
#: hobo/environment/templates/environment/tenant_confirm_delete.html:14
87 128
msgid "Delete"
88 129
msgstr "Supprimer"
89 130

  
90
#: templates/environment/generic_confirm_delete.html:16
91
#: templates/environment/service_form.html:19
92
#: templates/environment/tenant_confirm_delete.html:13
93
#: templates/environment/variable_form.html:18
131
#: hobo/environment/templates/environment/generic_confirm_delete.html:16
132
#: hobo/environment/templates/environment/service_form.html:19
133
#: hobo/environment/templates/environment/tenant_confirm_delete.html:13
134
#: hobo/environment/templates/environment/variable_form.html:18
94 135
msgid "Cancel"
95 136
msgstr "Annuler"
96 137

  
97
#: templates/environment/home.html:5
138
#: hobo/environment/templates/environment/home.html:5
98 139
msgid "Environment Settings"
99 140
msgstr "Paramétrage de l'environnement"
100 141

  
101
#: templates/environment/home.html:12
102
msgid "URL Template:"
103
msgstr "Modèle d'URL :"
104

  
105
#: templates/environment/home.html:15 templates/environment/home.html.py:64
106
#: templates/environment/service_form.html:18
107
#: templates/environment/variable_form.html:17
108
msgid "Save"
109
msgstr "Enregistrer"
110

  
111
#: templates/environment/home.html:18
142
#: hobo/environment/templates/environment/home.html:10
112 143
msgid "Variables"
113 144
msgstr "Variables"
114 145

  
115
#: templates/environment/home.html:25 templates/environment/home.html.py:71
146
#: hobo/environment/templates/environment/home.html:17
147
#: hobo/environment/templates/environment/home.html:64
116 148
msgid "Update variable"
117 149
msgstr "Modifier la variable"
118 150

  
119
#: templates/environment/home.html:25 templates/environment/home.html.py:71
151
#: hobo/environment/templates/environment/home.html:17
152
#: hobo/environment/templates/environment/home.html:64
120 153
msgid "edit"
121 154
msgstr "modifier"
122 155

  
123
#: templates/environment/home.html:26 templates/environment/home.html.py:72
156
#: hobo/environment/templates/environment/home.html:18
157
#: hobo/environment/templates/environment/home.html:65
124 158
msgid "Delete variable"
125 159
msgstr "Supprimer la variable"
126 160

  
127
#: templates/environment/home.html:29 templates/environment/home.html.py:75
161
#: hobo/environment/templates/environment/home.html:21
162
#: hobo/environment/templates/environment/home.html:68
128 163
msgid "Add new variable"
129 164
msgstr "Ajouter une nouvelle variable"
130 165

  
131
#: templates/environment/home.html:32
166
#: hobo/environment/templates/environment/home.html:24
132 167
msgid "Services"
133 168
msgstr "Services"
134 169

  
135
#: templates/environment/home.html:35
170
#: hobo/environment/templates/environment/home.html:27
136 171
msgid "Add new service:"
137 172
msgstr "Ajouter un nouveau service :"
138 173

  
139
#: templates/environment/home.html:52
174
#: hobo/environment/templates/environment/home.html:45
140 175
msgid "This service is still being deployed."
141 176
msgstr "Ce service est encore en cours de déploiement."
142 177

  
143
#: templates/environment/home.html:56
178
#: hobo/environment/templates/environment/home.html:49
144 179
msgid "This service is not operational."
145 180
msgstr "Ce service n'est pas opérationnel."
146 181

  
147
#: templates/environment/home.html:57
182
#: hobo/environment/templates/environment/home.html:50
148 183
msgid "Delete service"
149 184
msgstr "Supprimer le service"
150 185

  
151
#: templates/environment/home.html:66
186
#: hobo/environment/templates/environment/home.html:57
187
#: hobo/environment/templates/environment/service_form.html:18
188
#: hobo/environment/templates/environment/variable_form.html:17
189
msgid "Save"
190
msgstr "Enregistrer"
191

  
192
#: hobo/environment/templates/environment/home.html:59
152 193
msgid "Custom variables"
153 194
msgstr "Variables personnalisées"
154 195

  
155
#: templates/environment/service_form.html:6
196
#: hobo/environment/templates/environment/service_form.html:6
156 197
msgid "Back to settings"
157 198
msgstr "Retour au paramétrage"
158 199

  
159
#: templates/environment/tenant_confirm_delete.html:8
200
#: hobo/environment/templates/environment/tenant_confirm_delete.html:8
160 201
#, python-format
161 202
msgid ""
162 203
"\n"
......
167 208
"    Êtes-vous sûr de voulour supprimer \"%(name)s\" ?\n"
168 209
"    "
169 210

  
170
#: templates/environment/variable_form.html:5
211
#: hobo/environment/templates/environment/variable_form.html:5
171 212
msgid "Variable"
172 213
msgstr "Variable"
214

  
215
#~ msgid ".w.c.s. Web Forms"
216
#~ msgstr "Téléformulaires w.c.s."
217

  
218
#~ msgid "URL Template:"
219
#~ msgstr "Modèle d'URL :"
hobo/locale/fr/LC_MESSAGES/django.po
7 7
msgstr ""
8 8
"Project-Id-Version: hobo 0\n"
9 9
"Report-Msgid-Bugs-To: \n"
10
"POT-Creation-Date: 2015-06-11 15:06+0200\n"
10
"POT-Creation-Date: 2015-09-15 09:17+0000\n"
11 11
"PO-Revision-Date: 2014-03-24 19:31+0100\n"
12 12
"Last-Translator: Frederic Peters <fpeters@entrouvert.com>\n"
13 13
"Language: French\n"
......
16 16
"Content-Transfer-Encoding: 8bit\n"
17 17
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
18 18

  
19
#: hobo/agent/authentic2/management/commands/hobo_deploy.py:157
19
#: hobo/agent/authentic2/management/commands/hobo_deploy.py:148
20 20
#: hobo/agent/authentic2/management/commands/import-wcs-roles.py:49
21 21
msgid "Superuser"
22 22
msgstr "Administrateur"
23 23

  
24
#: views.py:21
25
msgid "Environment Settings"
24
#: hobo/agent/authentic2/role_forms.py:23
25
#, python-brace-format
26
msgid "Item {0} is invalid: {1}"
27
msgstr "L'élément {0} est invalide: {1}"
28

  
29
#: hobo/agent/authentic2/role_forms.py:62
30
msgid "Emails"
31
msgstr "Courriels"
32

  
33
#: hobo/agent/authentic2/role_forms.py:66
34
msgid "Emails to members"
35
msgstr "Propager les courriels à tous les utilisateurs ayant ce rôle"
36

  
37
#: hobo/profile/models.py:22
38
msgid "label"
39
msgstr ""
40

  
41
#: hobo/profile/models.py:24
42
msgid "description"
43
msgstr ""
44

  
45
#: hobo/profile/models.py:26
46
msgid "name"
47
msgstr ""
48

  
49
#: hobo/profile/models.py:28
50
msgid "required"
51
msgstr ""
52

  
53
#: hobo/profile/models.py:30
54
msgid "asked on registration"
55
msgstr ""
56

  
57
#: hobo/profile/models.py:32
58
msgid "user editable"
59
msgstr ""
60

  
61
#: hobo/profile/models.py:34
62
msgid "user visible"
63
msgstr ""
64

  
65
#: hobo/profile/models.py:36
66
msgid "kind"
67
msgstr ""
68

  
69
#: hobo/profile/models.py:38
70
msgid "disabled"
71
msgstr ""
72

  
73
#: hobo/profile/templates/profile/attributedefinition_list.html:5
74
#, fuzzy
75
#| msgid "Environment Settings"
76
msgid "Profile Settings"
26 77
msgstr "Paramétrage global"
27 78

  
28
#: templates/hobo/home.html:6
79
#: hobo/profile/templates/profile/attributedefinition_list.html:15
80
msgid "enable"
81
msgstr ""
82

  
83
#: hobo/profile/templates/profile/attributedefinition_list.html:17
84
msgid "disable"
85
msgstr ""
86

  
87
#: hobo/templates/403.html:6
88
msgid "You have no permission to access this page"
89
msgstr ""
90

  
91
#: hobo/templates/hobo/home.html:6
29 92
msgid "Welcome"
30 93
msgstr "Bienvenue"
94

  
95
#: hobo/templates/hobo/manager_home.html:6
96
msgid "Add instance"
97
msgstr ""
98

  
99
#: hobo/templates/hobo/manager_home.html:13
100
msgid "Add"
101
msgstr ""
102

  
103
#: hobo/templates/hobo/manager_home.html:16
104
msgid "Tenants"
105
msgstr ""
106

  
107
#: hobo/templates/hobo/manager_home.html:19
108
msgid "Delete tenant"
109
msgstr ""
110

  
111
#: hobo/templates/hobo/manager_home.html:24
112
msgid "Save"
113
msgstr ""
114

  
115
#: hobo/templates/hobo/manager_home.html:37
116
msgid "Tenant deletion"
117
msgstr ""
118

  
119
#: hobo/templates/registration/login.html:5
120
msgid "Authentication"
121
msgstr ""
122

  
123
#: hobo/templates/registration/login.html:13
124
msgid "Log in"
125
msgstr ""
126

  
127
#: hobo/views.py:51
128
msgid "Environment Settings"
129
msgstr "Paramétrage global"
31
-