Projet

Général

Profil

0001-manage-custom-attribute-s-verification-sources-65612.patch

Paul Marillonnet, 08 juin 2022 11:59

Télécharger (12,8 ko)

Voir les différences:

Subject: [PATCH 1/3] manage custom attribute's verification sources (#65612)

 src/authentic2/custom_user/models.py          | 16 +++-
 .../migrations/0042_auto_20220530_1426.py     | 29 +++++++
 src/authentic2/models.py                      | 82 +++++++++++++++++--
 tests/test_all.py                             |  2 +
 tests/test_user_manager.py                    | 68 +++++++++++++++
 5 files changed, 190 insertions(+), 7 deletions(-)
 create mode 100644 src/authentic2/migrations/0042_auto_20220530_1426.py
src/authentic2/custom_user/models.py
80 80
        self.__dict__['values'] = owner._a2_attributes_cache
81 81

  
82 82
    def __setattr__(self, name, value):
83
        self._set_sourced_attr(name, value, None)
84

  
85
    def _set_sourced_attr(self, name, value, verification_source=None):
83 86
        attribute = get_attributes_map().get(name)
84 87
        if not attribute:
85 88
            raise AttributeError(name)
86 89

  
87 90
        with transaction.atomic():
88 91
            if attribute.multiple:
89
                attribute.set_value(self.owner, value, verified=bool(self.verified))
92
                attribute.set_value(
93
                    self.owner,
94
                    value,
95
                    verified=bool(self.verified),
96
                    verification_source=verification_source,
97
                )
90 98
            else:
91 99
                atv = self.values.get(name)
92 100
                self.values[name] = attribute.set_value(
93
                    self.owner, value, verified=bool(self.verified), attribute_value=atv
101
                    self.owner,
102
                    value,
103
                    verified=bool(self.verified),
104
                    attribute_value=atv,
105
                    verification_source=verification_source,
94 106
                )
95 107

  
96 108
            update_fields = ['modified']
src/authentic2/migrations/0042_auto_20220530_1426.py
1
# Generated by Django 2.2.26 on 2022-05-30 12:26
2

  
3
import django.contrib.postgres.fields
4
from django.db import migrations, models
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('authentic2', '0041_lock'),
11
    ]
12

  
13
    operations = [
14
        migrations.AddField(
15
            model_name='attributevalue',
16
            name='last_verified_on',
17
            field=models.DateTimeField(null=True, verbose_name='last verification timestamp'),
18
        ),
19
        migrations.AddField(
20
            model_name='attributevalue',
21
            name='verification_sources',
22
            field=django.contrib.postgres.fields.ArrayField(
23
                base_field=models.CharField(max_length=63),
24
                null=True,
25
                size=None,
26
                verbose_name='verification sources',
27
            ),
28
        ),
29
    ]
src/authentic2/models.py
24 24
from django.conf import settings
25 25
from django.contrib.contenttypes.fields import GenericForeignKey
26 26
from django.contrib.contenttypes.models import ContentType
27
from django.contrib.postgres.fields import jsonb
27
from django.contrib.postgres.fields import ArrayField, jsonb
28 28
from django.contrib.postgres.indexes import GinIndex
29 29
from django.contrib.postgres.search import SearchVectorField
30 30
from django.core.exceptions import ValidationError
......
243 243
            except AttributeValue.DoesNotExist:
244 244
                return kind['default']
245 245

  
246
    def set_value(self, owner, value, verified=False, attribute_value=None):
246
    @classmethod
247
    def add_verification_source(cls, owner, attribute_name, value, source):
248
        '''
249
        Add verification source for an existing unitary attribute value,
250
        typically the user's first_name and last_name extended attributes.
251
        '''
252
        attribute = None
253
        try:
254
            attribute = Attribute.objects.get(name=attribute_name)
255
        except Attribute.DoesNotExist:
256
            pass
257
        if not attribute:
258
            # caller should have checked that the attribute object has been created, silently failing.
259
            return
260

  
261
        with transaction.atomic():
262
            try:
263
                av = AttributeValue.objects.with_owner(owner).get(attribute=attribute, content=value)
264
            except AttributeValue.DoesNotExist:
265
                # caller should have checked that the attribute value is defined, silently failing
266
                # to add source.
267
                pass
268
            else:
269
                sources = av.verification_sources or []
270
                if source not in sources:
271
                    sources.append(source)
272
                    av.verification_sources = sources
273
                    av.last_verified_on = timezone.now()
274
                    av.save()
275

  
276
    def set_value(self, owner, value, verified=False, attribute_value=None, verification_source=None):
247 277
        serialize = self.get_kind()['serialize']
248 278
        # setting to None is to delete
249 279
        if value is None:
......
267 297
                        attribute=self,
268 298
                        multiple=True,
269 299
                        content=content,
270
                        defaults={'verified': verified},
300
                        defaults={
301
                            'verified': verified,
302
                            'last_verified_on': timezone.now() if verified else None,
303
                            'verification_sources': [verification_source]
304
                            if verification_source and verified
305
                            else [],
306
                        },
271 307
                    )
272 308
                    if not created:
273 309
                        av.verified = verified
310
                        sources = av.verification_sources or []
311
                        if verified:
312
                            av.last_verified_on = timezone.now()
313
                            if not sources and verification_source:
314
                                sources.append(verification_source)
315
                                av.verification_sources = sources
316
                        elif verification_source in sources:
317
                            av.verification_sources.remove(verification_source)
318
                            if not av.verification_sources:
319
                                av.verified = False
274 320
                        av.save()
275 321
                    avs.append(av)
276 322
                    content_list.append(content)
......
292 338
                        object_id=owner.pk,
293 339
                        attribute=self,
294 340
                        multiple=False,
295
                        defaults={'content': content, 'verified': verified},
341
                        defaults={
342
                            'content': content,
343
                            'verified': verified,
344
                            'last_verified_on': timezone.now() if verified else None,
345
                            'verification_sources': [verification_source]
346
                            if verification_source and verified
347
                            else [],
348
                        },
296 349
                    )
297
                if not created and (av.content != content or av.verified != verified):
350
                if not created:
298 351
                    av.content = content
299 352
                    av.verified = verified
353
                    sources = av.verification_sources or []
354
                    if verified:
355
                        av.last_verified_on = timezone.now()
356
                        if not sources and verification_source:
357
                            sources.append(verification_source)
358
                            av.verification_sources = sources
359
                    elif verification_source in sources:
360
                        av.verification_sources.remove(verification_source)
361
                        if not av.verification_sources:
362
                            av.verified = False
300 363
                    av.save()
301 364
                return av
302 365

  
......
332 395
    content = models.TextField(verbose_name=_('content'), db_index=True)
333 396
    search_vector = SearchVectorField(null=True, editable=False)
334 397
    verified = models.BooleanField(default=False)
398
    verification_sources = ArrayField(
399
        verbose_name=_('verification sources'),
400
        base_field=models.CharField(max_length=63),
401
        null=True,
402
    )
403
    last_verified_on = models.DateTimeField(
404
        verbose_name=_('last verification timestamp'),
405
        null=True,
406
    )
335 407

  
336 408
    all_objects = managers.AttributeValueManager()
337 409
    objects = managers.AttributeValueManager(attribute__disabled=False)
tests/test_all.py
117 117
                    'content': '0101010101',
118 118
                    'multiple': False,
119 119
                    'verified': False,
120
                    'verification_sources': None,
121
                    'last_verified_on': None,
120 122
                    'search_vector': None,
121 123
                },
122 124
            },
tests/test_user_manager.py
1276 1276
        .count()
1277 1277
        == 1
1278 1278
    )
1279

  
1280

  
1281
def test_user_attribute_verification_sources(db, app, superuser, simple_user):
1282
    user = User.objects.create(username='user-foo')
1283
    attr = Attribute.objects.create(name='attr', label='Attr', kind='string')
1284
    attr_d = Attribute.objects.create(name='attrd', label='Attrd', kind='string')
1285
    av = AttributeValue.objects.create(owner=user, attribute=attr, content='attr-value')
1286
    av_d = AttributeValue.objects.create(owner=user, attribute=attr_d, content='attrd-value')
1287

  
1288
    assert not av.verified
1289
    assert not av_d.verified
1290
    assert av.verification_sources is None
1291
    assert av_d.verification_sources is None
1292
    assert av.last_verified_on is None
1293
    assert av_d.last_verified_on is None
1294

  
1295
    attr.set_value(user, 'foo', verified=True, verification_source='Foo')
1296
    attr_d.set_value(user, 'bar', verified=True, verification_source='Bar')
1297

  
1298
    user_ct = ContentType.objects.get_for_model(User)
1299
    assert AttributeValue.objects.get(content_type=user_ct, object_id=user.id, attribute=attr).verified
1300
    assert AttributeValue.objects.get(content_type=user_ct, object_id=user.id, attribute=attr_d).verified
1301
    assert AttributeValue.objects.get(
1302
        content_type=user_ct, object_id=user.id, attribute=attr
1303
    ).last_verified_on
1304
    assert AttributeValue.objects.get(
1305
        content_type=user_ct, object_id=user.id, attribute=attr_d
1306
    ).last_verified_on
1307
    assert (
1308
        AttributeValue.objects.get(content_type=user_ct, object_id=user.id, attribute=attr).content == 'foo'
1309
    )
1310
    assert (
1311
        AttributeValue.objects.get(content_type=user_ct, object_id=user.id, attribute=attr_d).content == 'bar'
1312
    )
1313
    assert (
1314
        'Foo'
1315
        in AttributeValue.objects.get(
1316
            content_type=user_ct, object_id=user.id, attribute=attr
1317
        ).verification_sources
1318
    )
1319
    assert (
1320
        'Bar'
1321
        in AttributeValue.objects.get(
1322
            content_type=user_ct, object_id=user.id, attribute=attr_d
1323
        ).verification_sources
1324
    )
1325

  
1326
    attr.multiple = True
1327
    attr.save()
1328
    attr_d.multiple = True
1329
    attr_d.save()
1330

  
1331
    attr.set_value(user, ['fooz'], verified=True, verification_source='Fooz')
1332
    attr_d.set_value(user, ['baz'], verified=True, verification_source='Baz')
1333
    assert AttributeValue.objects.filter(content_type=user_ct, object_id=user.id, attribute=attr).count() == 2
1334
    assert (
1335
        AttributeValue.objects.filter(content_type=user_ct, object_id=user.id, attribute=attr_d).count() == 2
1336
    )
1337

  
1338
    sources = [['Foo'], ['Fooz']]
1339
    for av in AttributeValue.objects.filter(content_type=user_ct, object_id=user.id, attribute=attr):
1340
        assert av.verification_sources in sources
1341
        sources.remove(av.verification_sources)
1342

  
1343
    sources = [['Bar'], ['Baz']]
1344
    for av in AttributeValue.objects.filter(content_type=user_ct, object_id=user.id, attribute=attr_d):
1345
        assert av.verification_sources in sources
1346
        sources.remove(av.verification_sources)
1279
-