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