forms: make anonymise attribute a string (#73203) #372
|
@ -224,7 +224,7 @@ def test_restrict_to_anonymised_data(app, pub, role):
|
|||
formdef.workflow_roles = {'_receiver': role.id}
|
||||
formdef.fields = [
|
||||
fields.StringField(id='1', label='foobar', varname='foobar'),
|
||||
fields.StringField(id='2', label='foobar2', varname='foobar2', anonymise=False),
|
||||
fields.StringField(id='2', label='foobar2', varname='foobar2', anonymise='no'),
|
||||
]
|
||||
formdef.workflow_id = workflow.id
|
||||
formdef.store()
|
||||
|
|
|
@ -2483,7 +2483,7 @@ def test_api_access_restrict_to_anonymised_data(pub, local_user, auth):
|
|||
formdef.workflow_roles = {'_receiver': role.id}
|
||||
formdef.fields = [
|
||||
fields.StringField(id='1', label='foobar', varname='foobar'),
|
||||
fields.StringField(id='2', label='foobar2', varname='foobar2', anonymise=False),
|
||||
fields.StringField(id='2', label='foobar2', varname='foobar2', anonymise='no'),
|
||||
]
|
||||
formdef.workflow_id = workflow.id
|
||||
formdef.store()
|
||||
|
|
|
@ -78,7 +78,7 @@ def formdef(pub):
|
|||
varname='block-items',
|
||||
label='Block items',
|
||||
items=['foo', 'bar', 'baz'],
|
||||
anonymise=False,
|
||||
anonymise='no',
|
||||
display_locations=['statistics'],
|
||||
),
|
||||
]
|
||||
|
@ -94,11 +94,11 @@ def formdef(pub):
|
|||
varname='test-items',
|
||||
label='Test items',
|
||||
items=['foo', 'bar', 'baz'],
|
||||
anonymise=False,
|
||||
anonymise='no',
|
||||
)
|
||||
items_field.display_locations = ['statistics']
|
||||
block_field = fields.BlockField(
|
||||
id='4', label='Block Data', varname='blockdata', block_slug='foobar', anonymise=False
|
||||
id='4', label='Block Data', varname='blockdata', block_slug='foobar', anonymise='no'
|
||||
)
|
||||
formdef.fields = [item_field, items_field, block_field]
|
||||
formdef.store()
|
||||
|
|
|
@ -17,6 +17,7 @@ from wcs.api_access import ApiAccess
|
|||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.categories import Category
|
||||
from wcs.fields import StringField
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.qommon.ident.password_accounts import PasswordAccount
|
||||
|
@ -5697,3 +5698,130 @@ def test_webservice_call_error_handling_with_marker(http_requests, pub):
|
|||
assert evolutions[2].status == 'wf-one'
|
||||
# error handling of wscall
|
||||
assert evolutions[3].status == 'wf-two'
|
||||
|
||||
|
||||
def test_anonymise_action_intermediate(pub):
|
||||
FormDef.wipe()
|
||||
Workflow.wipe()
|
||||
|
||||
role = pub.role_class(name='xxx')
|
||||
role.store()
|
||||
user = create_user(pub, is_admin=True)
|
||||
user.name = 'Foo Bar'
|
||||
user.email = 'foo@example.com'
|
||||
user.roles.append(role.id)
|
||||
user.store()
|
||||
|
||||
wf = Workflow(name='anonymise-action-intermediate')
|
||||
wf.id = '1'
|
||||
wf.roles = {'_receiver': role.id}
|
||||
anonymise_intermediate_status = wf.add_status('anonymise_intermediate', id='anonymise_intermediate')
|
||||
anonymise_intermediate_status.visibility = ['_receiver']
|
||||
anonymise_final_status = wf.add_status('anonymise_final', id='anonymise_final')
|
||||
anonymise_final_status.visibility = ['_receiver']
|
||||
|
||||
anonymise_intermediate_action = anonymise_intermediate_status.add_action(
|
||||
'anonymise', id='_anonymise', prepend=True
|
||||
)
|
||||
anonymise_intermediate_action.label = 'Intermediate anonymisation'
|
||||
anonymise_intermediate_action.mode = 'intermediate'
|
||||
jump_to_anonymise_final = anonymise_intermediate_status.add_action('choice', id='_to_anonymise_final')
|
||||
jump_to_anonymise_final.by = ['_receiver']
|
||||
jump_to_anonymise_final.status = anonymise_final_status.id
|
||||
jump_to_anonymise_final.label = 'choice'
|
||||
jump_to_anonymise_final.identifier = 'id1'
|
||||
|
||||
anonymise_final_action = anonymise_final_status.add_action('anonymise', id='_anonymise', prepend=True)
|
||||
anonymise_final_action.label = 'Final anonymisation'
|
||||
anonymise_final_action.mode = 'final'
|
||||
wf.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.id = '1'
|
||||
formdef.workflow_roles = {'_receiver': role.id}
|
||||
formdef.name = 'Foo'
|
||||
formdef.workflow_id = wf.id
|
||||
formdef.fields = [
|
||||
StringField(id='0', label='string', anonymise='intermediate', varname='inter'),
|
||||
StringField(id='1', label='string', anonymise='final', varname='final'),
|
||||
]
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'0': 'Foo', '1': 'Bar'}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'wf-anonymise_intermediate'
|
||||
assert formdata.data == {'0': None, '1': 'Bar'}
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/management/foo/%s/' % formdata.id)
|
||||
resp = resp.form.submit('button_to_anonymise_final')
|
||||
resp = resp.follow()
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'wf-anonymise_final'
|
||||
assert formdata.data == {'0': None, '1': None}
|
||||
|
||||
|
||||
def test_anonymise_action_final_also_deletes_fields_with_intermediate(pub):
|
||||
FormDef.wipe()
|
||||
Workflow.wipe()
|
||||
|
||||
role = pub.role_class(name='xxx')
|
||||
role.store()
|
||||
user = create_user(pub, is_admin=True)
|
||||
user.name = 'Foo Bar'
|
||||
user.email = 'foo@example.com'
|
||||
user.roles.append(role.id)
|
||||
user.store()
|
||||
|
||||
wf = Workflow(name='anonymise-action-final')
|
||||
wf.id = '1'
|
||||
wf.roles = {'_receiver': role.id}
|
||||
received_status = wf.add_status('received', id='anonymise_received')
|
||||
anonymise_final_status = wf.add_status('anonymise_final', id='anonymise_final')
|
||||
anonymise_final_status.visibility = ['_receiver']
|
||||
|
||||
anonymise_final_action = anonymise_final_status.add_action('anonymise', id='_anonymise', prepend=True)
|
||||
anonymise_final_action.label = 'Final anonymisation'
|
||||
anonymise_final_action.mode = 'final'
|
||||
jump_to_anonymise_final = received_status.add_action('choice', id='_to_anonymise_final')
|
||||
jump_to_anonymise_final.by = ['_receiver']
|
||||
jump_to_anonymise_final.status = anonymise_final_status.id
|
||||
jump_to_anonymise_final.label = 'choice'
|
||||
jump_to_anonymise_final.identifier = 'id1'
|
||||
wf.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.id = '1'
|
||||
formdef.workflow_roles = {'_receiver': role.id}
|
||||
formdef.name = 'Foo'
|
||||
formdef.workflow_id = wf.id
|
||||
formdef.fields = [
|
||||
StringField(id='0', label='string', anonymise='intermediate', varname='inter'),
|
||||
StringField(id='1', label='string', anonymise='final', varname='final'),
|
||||
]
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'0': 'Foo', '1': 'Bar'}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'wf-anonymise_received'
|
||||
assert formdata.data == {'0': 'Foo', '1': 'Bar'}
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/management/foo/%s/' % formdata.id)
|
||||
resp = resp.form.submit('button_to_anonymise_final')
|
||||
resp = resp.follow()
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'wf-anonymise_final'
|
||||
assert formdata.data == {'0': None, '1': None}
|
||||
|
|
|
@ -156,7 +156,7 @@ def test_text_anonymise(pub):
|
|||
formdata.anonymise()
|
||||
assert not formdata.data.get('0')
|
||||
|
||||
formdef.fields[0].anonymise = False
|
||||
formdef.fields[0].anonymise = 'no'
|
||||
formdef.store()
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
@ -849,7 +849,7 @@ def test_date_anonymise(pub):
|
|||
formdata.anonymise()
|
||||
assert not formdata.data.get('0')
|
||||
|
||||
formdef.fields[0].anonymise = False
|
||||
formdef.fields[0].anonymise = 'no'
|
||||
formdef.store()
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
|
|
@ -1020,7 +1020,7 @@ def test_anonymise(pub):
|
|||
assert formdata.evolution[1].parts is None
|
||||
|
||||
assert item.render_as_line() == 'Anonymisation'
|
||||
item.unlink_user = True
|
||||
item.mode = 'unlink_user'
|
||||
assert item.render_as_line() == 'Anonymisation (only user unlinking)'
|
||||
|
||||
|
||||
|
@ -1051,7 +1051,7 @@ def test_anonymise_custom_view_user_filtered(pub):
|
|||
formdef = FormDef()
|
||||
formdef.name = 'baz'
|
||||
formdef.fields = [
|
||||
ItemsField(id='1', label='list', data_source={'type': 'carddef:foo:card-view'}, anonymise=True),
|
||||
ItemsField(id='1', label='list', data_source={'type': 'carddef:foo:card-view'}, anonymise='final'),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
|
|
|
@ -1722,7 +1722,7 @@ def test_anonymise_action_unlink_user(pub, submitter_is_triggerer):
|
|||
anonymise = wf.possible_status[1].add_action('anonymise', id='_anonymise', prepend=True)
|
||||
anonymise.label = 'Unlink User'
|
||||
anonymise.varname = 'mycard'
|
||||
anonymise.unlink_user = True
|
||||
anonymise.mode = 'unlink_user'
|
||||
wf.store()
|
||||
|
||||
carddef = CardDef()
|
||||
|
@ -1765,7 +1765,7 @@ def test_anonymise_action_unlink_user_no_request(pub):
|
|||
anonymise = wf.possible_status[1].add_action('anonymise', id='_anonymise', prepend=True)
|
||||
anonymise.label = 'Unlink User'
|
||||
anonymise.varname = 'mycard'
|
||||
anonymise.unlink_user = True
|
||||
anonymise.mode = 'unlink_user'
|
||||
wf.store()
|
||||
|
||||
carddef = CardDef()
|
||||
|
|
|
@ -447,7 +447,7 @@ def test_anonymise_action_unlink_user(pub, submitter_is_triggerer):
|
|||
anonymise = wf.possible_status[1].add_action('anonymise', id='_anonymise', prepend=True)
|
||||
anonymise.label = 'Unlink User'
|
||||
anonymise.varname = 'mycard'
|
||||
anonymise.unlink_user = True
|
||||
anonymise.mode = 'unlink_user'
|
||||
wf.store()
|
||||
|
||||
formdef = FormDef()
|
||||
|
|
|
@ -281,8 +281,9 @@ class Field:
|
|||
condition = None
|
||||
|
||||
# flag a field for removal by AnonymiseWorkflowStatusItem
|
||||
# possible values are final, intermediate, no.
|
||||
|
||||
# can be overriden in field' settings
|
||||
anonymise = True
|
||||
anonymise = 'final'
|
||||
stats = None
|
||||
|
||||
# declarations for serialization, they are mostly for legacy files,
|
||||
|
@ -623,6 +624,9 @@ class Field:
|
|||
self.display_locations.append('listings')
|
||||
changed = True
|
||||
self.in_listing = None
|
||||
if isinstance(self.anonymise, bool): # 2023-06-13
|
||||
self.anonymise = 'final' if self.anonymise else 'no'
|
||||
changed = True
|
||||
fpeters
commented
Ce code va toujours mettre "final" comme valeur, ça devrait être soit if/else, soit en une seule ligne,
Ce code va toujours mettre "final" comme valeur, ça devrait être soit if/else, soit en une seule ligne,
```
self.anonymise = 'final' if self.anonymise else 'no'
```
|
||||
return changed
|
||||
|
||||
@staticmethod
|
||||
|
@ -949,6 +953,9 @@ class WidgetField(Field):
|
|||
|
||||
return options
|
||||
|
||||
def get_anonymise_options(self):
|
||||
return [('final', _('Final')), ('intermediate', _('Intermediate')), ('no', _('No'))]
|
||||
|
||||
def fill_admin_form(self, form):
|
||||
form.add(StringWidget, 'label', title=_('Label'), value=self.label, required=True, size=50)
|
||||
form.add(
|
||||
|
@ -1006,9 +1013,10 @@ class WidgetField(Field):
|
|||
if 'anonymise' in self.get_admin_attributes():
|
||||
# override anonymise flag default value
|
||||
form.add(
|
||||
CheckboxWidget,
|
||||
RadiobuttonsWidget,
|
||||
'anonymise',
|
||||
title=_('Anonymise'),
|
||||
title=_('Anonymisation'),
|
||||
options=self.get_anonymise_options(),
|
||||
value=self.anonymise,
|
||||
advanced=True,
|
||||
hint=_('Marks the field data for removal in the anonymisation processes.'),
|
||||
|
@ -1559,7 +1567,7 @@ class BoolField(WidgetField):
|
|||
|
||||
widget_class = CheckboxWidget
|
||||
required = False
|
||||
anonymise = False
|
||||
anonymise = 'no'
|
||||
|
||||
def perform_more_widget_changes(self, form, kwargs, edit=True):
|
||||
if not edit:
|
||||
|
@ -2276,7 +2284,7 @@ class ItemField(WidgetField, MapOptionsMixin, ItemFieldMixin):
|
|||
|
||||
items = []
|
||||
show_as_radio = None
|
||||
anonymise = False
|
||||
anonymise = 'no'
|
||||
widget_class = SingleSelectHintWidget
|
||||
data_source = {}
|
||||
in_filters = False
|
||||
|
@ -3761,7 +3769,7 @@ class RankedItemsField(WidgetField):
|
|||
items = []
|
||||
randomize_items = False
|
||||
widget_class = RankedItemsWidget
|
||||
anonymise = False
|
||||
anonymise = 'no'
|
||||
|
||||
def perform_more_widget_changes(self, form, kwargs, edit=True):
|
||||
kwargs['elements'] = self.items or []
|
||||
|
|
|
@ -1255,11 +1255,17 @@ class FormData(StorableObject):
|
|||
|
||||
last_update_time = property(get_last_update_time, set_last_update_time)
|
||||
|
||||
def anonymise(self):
|
||||
def anonymise(self, mode='final'):
|
||||
for field in self.formdef.get_all_fields():
|
||||
if field.anonymise:
|
||||
if field.anonymise == 'no':
|
||||
continue
|
||||
fpeters
commented
Je pense que si kind == 'final' ça devrait anonymiser tout, y compris ce qui est marqué pour 'intermediate'. Je pense que si kind == 'final' ça devrait anonymiser tout, y compris ce qui est marqué pour 'intermediate'.
|
||||
if mode in ('final', field.anonymise):
|
||||
field.set_value(self.data, None)
|
||||
|
||||
fpeters
commented
Je trouverais plus lisible (à la fois le diff et le code résultant) de moins déplacer, plutôt avoir un return supplémentaire, plus haut
Je trouverais plus lisible (à la fois le diff et le code résultant) de moins déplacer, plutôt avoir un return supplémentaire, plus haut
```
if kind != 'final':
self.store()
return
self.anonymised = localtime()
self.user_id = None
...
```
|
||||
if mode != 'final':
|
||||
self.store()
|
||||
return
|
||||
|
||||
self.anonymised = localtime()
|
||||
self.user_id = None
|
||||
self.user_label = None
|
||||
|
@ -1325,7 +1331,7 @@ class FormData(StorableObject):
|
|||
new_data = {}
|
||||
seen = set()
|
||||
for field in fields:
|
||||
if anonymise and field.anonymise:
|
||||
if anonymise and field.anonymise == 'final':
|
||||
continue
|
||||
if not field.varname and not include_unnamed_fields:
|
||||
continue
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
from quixote import get_request, get_session
|
||||
|
||||
from wcs.qommon import _
|
||||
from wcs.qommon.form import CheckboxWidget
|
||||
from wcs.qommon.form import RadiobuttonsWidget
|
||||
from wcs.workflows import WorkflowStatusItem, register_item_class
|
||||
|
||||
|
||||
|
@ -25,36 +25,53 @@ class AnonymiseWorkflowStatusItem(WorkflowStatusItem):
|
|||
description = _('Anonymisation')
|
||||
key = 'anonymise'
|
||||
category = 'formdata-action'
|
||||
unlink_user = False
|
||||
mode = 'final'
|
||||
fpeters
commented
Détail mais plutôt "mode" que "kind" (pour faire comme user_association_mode, operation_mode, target_mode, qui existent dans les autres actions). Détail mais plutôt "mode" que "kind" (pour faire comme user_association_mode, operation_mode, target_mode, qui existent dans les autres actions).
|
||||
|
||||
def migrate(self):
|
||||
changed = super().migrate()
|
||||
if getattr(self, 'unlink_user', False): # 2023-06-13
|
||||
self.mode = 'unlink_user'
|
||||
self.unlink_user = None
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
def get_line_details(self):
|
||||
if self.unlink_user:
|
||||
if self.mode == 'unlink_user':
|
||||
return _('only user unlinking')
|
||||
|
||||
def perform(self, formdata):
|
||||
if self.unlink_user is True:
|
||||
if self.mode == 'unlink_user':
|
||||
if get_request() and formdata.is_submitter(get_request().user):
|
||||
get_session().mark_anonymous_formdata(formdata)
|
||||
formdata.unlink_user()
|
||||
formdata.remove_tracking_code()
|
||||
else:
|
||||
formdata.anonymise()
|
||||
# self.mode is 'intermediate' or 'final'
|
||||
formdata.anonymise(self.mode)
|
||||
|
||||
def get_parameters(self):
|
||||
return ('unlink_user',)
|
||||
return ('mode',)
|
||||
|
||||
def get_mode_options(self):
|
||||
return [
|
||||
('final', _('Final')),
|
||||
('intermediate', _('Intermediate')),
|
||||
(
|
||||
'unlink_user',
|
||||
_('Only unlink user from the form/card. If existing the tracking code will be deleted.'),
|
||||
),
|
||||
]
|
||||
|
||||
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None, **kwargs):
|
||||
super().add_parameters_widgets(form, parameters, prefix, formdef, **kwargs)
|
||||
if 'unlink_user' in parameters:
|
||||
if 'mode' in parameters:
|
||||
form.add(
|
||||
CheckboxWidget,
|
||||
'%sunlink_user' % prefix,
|
||||
title=_('Only perform form/card user unlinking'),
|
||||
hint=_(
|
||||
'If checked, this action will only unlink user from the form/card. '
|
||||
'If existing, the tracking code will be deleted.'
|
||||
),
|
||||
value=self.unlink_user,
|
||||
RadiobuttonsWidget,
|
||||
'%smode' % prefix,
|
||||
title=_('Anonymisation type'),
|
||||
options=self.get_mode_options(),
|
||||
value=self.mode,
|
||||
default_value=self.__class__.mode,
|
||||
)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Peut-être actualiser le commentaire qui précède, pour noter les valeurs possibles .
Fait.