0001-manage-custom-attribute-s-verification-sources-65612.patch
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 |
- |