Projet

Général

Profil

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

Paul Marillonnet, 05 décembre 2022 17:24

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/0044_auto_20220530_1426.py     | 29 +++++++
 src/authentic2/models.py                      | 82 ++++++++++++++++++-
 tests/test_all.py                             |  2 +
 tests/test_user_manager.py                    | 68 +++++++++++++++
 5 files changed, 191 insertions(+), 6 deletions(-)
 create mode 100644 src/authentic2/migrations/0044_auto_20220530_1426.py
src/authentic2/custom_user/models.py
95 95
        self.__dict__['values'] = owner._a2_attributes_cache
96 96

  
97 97
    def __setattr__(self, name, value):
98
        self._set_sourced_attr(name, value, None)
99

  
100
    def _set_sourced_attr(self, name, value, verification_source=None):
98 101
        attribute = get_attributes_map().get(name)
99 102
        if not attribute:
100 103
            raise AttributeError(name)
101 104

  
102 105
        with transaction.atomic():
103 106
            if attribute.multiple:
104
                attribute.set_value(self.owner, value, verified=bool(self.verified))
107
                attribute.set_value(
108
                    self.owner,
109
                    value,
110
                    verified=bool(self.verified),
111
                    verification_source=verification_source,
112
                )
105 113
            else:
106 114
                atv = self.values.get(name)
107 115
                self.values[name] = attribute.set_value(
108
                    self.owner, value, verified=bool(self.verified), attribute_value=atv
116
                    self.owner,
117
                    value,
118
                    verified=bool(self.verified),
119
                    attribute_value=atv,
120
                    verification_source=verification_source,
109 121
                )
110 122

  
111 123
            update_fields = ['modified']
src/authentic2/migrations/0044_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', '0043_api_client_description'),
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
27 27
from django.contrib import auth
28 28
from django.contrib.contenttypes.fields import GenericForeignKey
29 29
from django.contrib.contenttypes.models import ContentType
30
from django.contrib.postgres.fields import ArrayField
30 31
from django.contrib.postgres.indexes import GinIndex
31 32
from django.contrib.postgres.search import SearchVectorField
32 33
from django.core.exceptions import PermissionDenied, ValidationError
......
251 252
            except AttributeValue.DoesNotExist:
252 253
                return kind['default']
253 254

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

  
270
        avs = AttributeValue.objects.with_owner(owner).select_for_update()
271
        with transaction.atomic():
272
            try:
273
                av = avs.get(attribute=attribute, content=value)
274
            except AttributeValue.DoesNotExist:
275
                # caller should have checked that the attribute value is defined, silently failing
276
                # to add source.
277
                pass
278
            else:
279
                sources = av.verification_sources or []
280
                if source not in sources:
281
                    sources.append(source)
282
                    av.verification_sources = sources
283
                    av.last_verified_on = timezone.now()
284
                    av.save()
285

  
286
    def set_value(self, owner, value, verified=False, attribute_value=None, verification_source=None):
255 287
        serialize = self.get_kind()['serialize']
256 288
        # setting to None is to delete
257 289
        if value is None:
......
275 307
                        attribute=self,
276 308
                        multiple=True,
277 309
                        content=content,
278
                        defaults={'verified': verified},
310
                        defaults={
311
                            'verified': verified,
312
                            'last_verified_on': timezone.now() if verified else None,
313
                            'verification_sources': [verification_source]
314
                            if verification_source and verified
315
                            else [],
316
                        },
279 317
                    )
280 318
                    if not created:
281 319
                        av.verified = verified
320
                        sources = av.verification_sources or []
321
                        if verified:
322
                            av.last_verified_on = timezone.now()
323
                            if not sources and verification_source:
324
                                sources.append(verification_source)
325
                                av.verification_sources = sources
326
                        elif verification_source in sources:
327
                            av.verification_sources.remove(verification_source)
328
                            if not av.verification_sources:
329
                                av.verified = False
282 330
                        av.save()
283 331
                    avs.append(av)
284 332
                    content_list.append(content)
......
300 348
                        object_id=owner.pk,
301 349
                        attribute=self,
302 350
                        multiple=False,
303
                        defaults={'content': content, 'verified': verified},
351
                        defaults={
352
                            'content': content,
353
                            'verified': verified,
354
                            'last_verified_on': timezone.now() if verified else None,
355
                            'verification_sources': [verification_source]
356
                            if verification_source and verified
357
                            else [],
358
                        },
304 359
                    )
305
                if not created and (av.content != content or av.verified != verified):
360
                if not created:
306 361
                    av.content = content
307 362
                    av.verified = verified
363
                    sources = av.verification_sources or []
364
                    if verified:
365
                        av.last_verified_on = timezone.now()
366
                        if not sources and verification_source:
367
                            sources.append(verification_source)
368
                            av.verification_sources = sources
369
                    elif verification_source in sources:
370
                        av.verification_sources.remove(verification_source)
371
                        if not av.verification_sources:
372
                            av.verified = False
308 373
                    av.save()
309 374
                return av
310 375

  
......
340 405
    content = models.TextField(verbose_name=_('content'), db_index=True)
341 406
    search_vector = SearchVectorField(null=True, editable=False)
342 407
    verified = models.BooleanField(default=False)
408
    verification_sources = ArrayField(
409
        verbose_name=_('verification sources'),
410
        base_field=models.CharField(max_length=63),
411
        null=True,
412
    )
413
    last_verified_on = models.DateTimeField(
414
        verbose_name=_('last verification timestamp'),
415
        null=True,
416
    )
343 417

  
344 418
    all_objects = managers.AttributeValueManager()
345 419
    objects = managers.AttributeValueManager(attribute__disabled=False)
tests/test_all.py
120 120
                    'content': '0101010101',
121 121
                    'multiple': False,
122 122
                    'verified': False,
123
                    'verification_sources': None,
124
                    'last_verified_on': None,
123 125
                    'search_vector': None,
124 126
                },
125 127
            },
tests/test_user_manager.py
1291 1291
        .count()
1292 1292
        == 1
1293 1293
    )
1294

  
1295

  
1296
def test_user_attribute_verification_sources(db, app, superuser, simple_user):
1297
    user = User.objects.create(username='user-foo')
1298
    attr = Attribute.objects.create(name='attr', label='Attr', kind='string')
1299
    attr_d = Attribute.objects.create(name='attrd', label='Attrd', kind='string')
1300
    av = AttributeValue.objects.create(owner=user, attribute=attr, content='attr-value')
1301
    av_d = AttributeValue.objects.create(owner=user, attribute=attr_d, content='attrd-value')
1302

  
1303
    assert not av.verified
1304
    assert not av_d.verified
1305
    assert av.verification_sources is None
1306
    assert av_d.verification_sources is None
1307
    assert av.last_verified_on is None
1308
    assert av_d.last_verified_on is None
1309

  
1310
    attr.set_value(user, 'foo', verified=True, verification_source='Foo')
1311
    attr_d.set_value(user, 'bar', verified=True, verification_source='Bar')
1312

  
1313
    user_ct = ContentType.objects.get_for_model(User)
1314
    assert AttributeValue.objects.get(content_type=user_ct, object_id=user.id, attribute=attr).verified
1315
    assert AttributeValue.objects.get(content_type=user_ct, object_id=user.id, attribute=attr_d).verified
1316
    assert AttributeValue.objects.get(
1317
        content_type=user_ct, object_id=user.id, attribute=attr
1318
    ).last_verified_on
1319
    assert AttributeValue.objects.get(
1320
        content_type=user_ct, object_id=user.id, attribute=attr_d
1321
    ).last_verified_on
1322
    assert (
1323
        AttributeValue.objects.get(content_type=user_ct, object_id=user.id, attribute=attr).content == 'foo'
1324
    )
1325
    assert (
1326
        AttributeValue.objects.get(content_type=user_ct, object_id=user.id, attribute=attr_d).content == 'bar'
1327
    )
1328
    assert (
1329
        'Foo'
1330
        in AttributeValue.objects.get(
1331
            content_type=user_ct, object_id=user.id, attribute=attr
1332
        ).verification_sources
1333
    )
1334
    assert (
1335
        'Bar'
1336
        in AttributeValue.objects.get(
1337
            content_type=user_ct, object_id=user.id, attribute=attr_d
1338
        ).verification_sources
1339
    )
1340

  
1341
    attr.multiple = True
1342
    attr.save()
1343
    attr_d.multiple = True
1344
    attr_d.save()
1345

  
1346
    attr.set_value(user, ['fooz'], verified=True, verification_source='Fooz')
1347
    attr_d.set_value(user, ['baz'], verified=True, verification_source='Baz')
1348
    assert AttributeValue.objects.filter(content_type=user_ct, object_id=user.id, attribute=attr).count() == 2
1349
    assert (
1350
        AttributeValue.objects.filter(content_type=user_ct, object_id=user.id, attribute=attr_d).count() == 2
1351
    )
1352

  
1353
    sources = [['Foo'], ['Fooz']]
1354
    for av in AttributeValue.objects.filter(content_type=user_ct, object_id=user.id, attribute=attr):
1355
        assert av.verification_sources in sources
1356
        sources.remove(av.verification_sources)
1357

  
1358
    sources = [['Bar'], ['Baz']]
1359
    for av in AttributeValue.objects.filter(content_type=user_ct, object_id=user.id, attribute=attr_d):
1360
        assert av.verification_sources in sources
1361
        sources.remove(av.verification_sources)
1294
-