0002_anonymise_forms.patch
wcs/admin/forms.py | ||
---|---|---|
206 | 206 | |
207 | 207 |
class FormDefPage(Directory): |
208 | 208 |
_q_exports = ['', 'fields', 'delete', 'duplicate', 'export', |
209 |
'invite', 'enable', 'workflow', 'category', |
|
209 |
'anonymise', 'invite', 'enable', 'workflow', 'category',
|
|
210 | 210 |
'role', ('workflow-options', 'workflow_options'), |
211 | 211 |
('workflow-status-remapping', 'workflow_status_remapping'), |
212 | 212 |
'roles', 'title', 'options', ('acl-read', 'acl_read')] |
... | ... | |
381 | 381 |
r += htmltext('<li><a href="duplicate">%s</a></li>') % _('Duplicate') |
382 | 382 |
if ET: |
383 | 383 |
r += htmltext('<li><a href="export">%s</a></li>') % _('Export') |
384 |
r += htmltext('<li><a href="anonymise">%s</a></li>') % _('Anonymise handled forms') |
|
384 | 385 |
if self.formdef.roles: |
385 | 386 |
r += htmltext('<li><a href="invite">%s</a></li>') % _('Invites') |
386 | 387 |
r += htmltext('</ul>') |
... | ... | |
711 | 712 |
'attachment; filename=%s.wcs' % self.formdef.url_name) |
712 | 713 |
return '<?xml version="1.0" encoding="iso-8859-15"?>\n' + ET.tostring(x) |
713 | 714 | |
715 |
def anonymise(self): |
|
716 |
if get_request().form.get('job'): |
|
717 |
return self.anonymise_processing() |
|
718 | ||
719 |
form = Form(enctype='multipart/form-data') |
|
720 | ||
721 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
722 |
"You are about to irrevocably anonymise forms which are closed."))) |
|
723 |
form.add_submit('submit', _('Submit')) |
|
724 |
form.add_submit('cancel', _('Cancel')) |
|
725 | ||
726 |
if form.get_widget('cancel').parse(): |
|
727 |
return redirect('.') |
|
728 | ||
729 |
if not form.is_submitted() or form.has_errors(): |
|
730 |
get_response().breadcrumb.append(('anonymise', _('Anonymise'))) |
|
731 |
html_top('forms', title = _('Anonymise Forms')) |
|
732 |
r = TemplateIO(html=True) |
|
733 |
r += htmltext('<h2>%s</h2>') % _('Anonymise Forms') |
|
734 |
r += form.render() |
|
735 |
return r.getvalue() |
|
736 |
else: |
|
737 |
return self.anonymise_submit(form) |
|
738 | ||
739 |
def anonymise_submit(self, form): |
|
740 | ||
741 |
class Anonymiser: |
|
742 | ||
743 |
def __init__(self, formdef): |
|
744 |
self.formdef = formdef |
|
745 | ||
746 |
def anonymise(self, job=None): |
|
747 |
all_forms = self.formdef.data_class().select() |
|
748 |
not_endpoint_status = self.formdef.workflow.get_not_endpoint_status() |
|
749 |
not_endpoint_status_ids = ['wf-%s' % x.id for x in not_endpoint_status] |
|
750 |
all_forms = [x for x in all_forms if x.status not in not_endpoint_status_ids] |
|
751 |
for formdata in all_forms: |
|
752 |
if not formdata.anonymised: |
|
753 |
formdata.anonymise() |
|
754 | ||
755 |
count = self.formdef.data_class().count() |
|
756 |
anonymiser = Anonymiser(self.formdef) |
|
757 |
if count > 100: # Arbitrary threshold |
|
758 |
job = get_response().add_after_job( |
|
759 |
str(N_('Anonymising forms')), |
|
760 |
anonymiser.anonymise) |
|
761 |
return redirect('anonymise?job=%s' % job.id) |
|
762 |
else: |
|
763 |
anonymiser.anonymise() |
|
764 | ||
765 |
return redirect('.') |
|
766 | ||
767 |
def anonymise_processing(self): |
|
768 |
try: |
|
769 |
job = AfterJob.get(get_request().form.get('job')) |
|
770 |
except KeyError: |
|
771 |
return redirect('.') |
|
772 | ||
773 |
html_top('forms', title=_('Anonymising')) |
|
774 |
r = TemplateIO(html=True) |
|
775 |
r += get_session().display_message() |
|
776 |
get_response().add_javascript(['jquery.js', 'afterjob.js']) |
|
777 |
r += htmltext('<dl class="job-status">') |
|
778 |
r += htmltext('<dt>') |
|
779 |
r += _(job.label) |
|
780 |
r += htmltext('</dt>') |
|
781 |
r += htmltext('<dd>') |
|
782 |
r += htmltext('<span class="afterjob" id="%s">') % job.id |
|
783 |
r += _(job.status) |
|
784 |
r += htmltext('</span>') |
|
785 |
r += htmltext('</dd>') |
|
786 |
r += htmltext('</dl>') |
|
787 | ||
788 |
r += htmltext('<div class="done">') |
|
789 |
r += htmltext('<a href="./">%s</a>') % _('Back') |
|
790 |
r += htmltext('</div>') |
|
791 |
return r.getvalue() |
|
792 | ||
714 | 793 |
def invite(self): |
715 | 794 |
if not self.formdef.roles: |
716 | 795 |
return template.error_page( |
wcs/fields.py | ||
---|---|---|
115 | 115 |
prefill = None |
116 | 116 |
store_display_value = None |
117 | 117 | |
118 |
anonymise = True |
|
118 | 119 |
stats = None |
119 | 120 | |
120 | 121 |
def __init__(self, **kwargs): |
... | ... | |
547 | 548 | |
548 | 549 |
widget_class = CheckboxWidget |
549 | 550 |
required = False |
551 |
anonymise = False |
|
550 | 552 | |
551 | 553 |
def perform_more_widget_changes(self, form, kwargs, edit = True): |
552 | 554 |
if not edit: |
... | ... | |
626 | 628 |
key = 'file' |
627 | 629 |
description = N_('File Upload') |
628 | 630 | |
631 |
anonymise = False |
|
629 | 632 |
widget_class = FileWithPreviewWidget |
630 | 633 | |
631 | 634 |
def get_view_value(self, value): |
... | ... | |
757 | 760 | |
758 | 761 |
items = [] |
759 | 762 |
show_as_radio = False |
763 |
anonymise = False |
|
760 | 764 |
widget_class = SingleSelectHintWidget |
761 | 765 |
data_source = {} |
762 | 766 | |
... | ... | |
891 | 895 | |
892 | 896 |
items = [] |
893 | 897 |
max_choices = 0 |
898 |
anonymise = False |
|
894 | 899 | |
895 | 900 |
widget_class = CheckboxesWidget |
896 | 901 | |
... | ... | |
1309 | 1314 |
items = [] |
1310 | 1315 |
randomize_items = False |
1311 | 1316 |
widget_class = RankedItemsWidget |
1317 |
anonymise = False |
|
1312 | 1318 | |
1313 | 1319 |
def perform_more_widget_changes(self, form, kwargs, edit = True): |
1314 | 1320 |
kwargs['elements'] = self.items or [] |
wcs/formdata.py | ||
---|---|---|
130 | 130 |
user_hash = None |
131 | 131 |
receipt_time = None |
132 | 132 |
status = None |
133 |
anonymised = None |
|
133 | 134 |
page_no = None # page to use when restoring from draft |
134 | 135 |
evolution = None |
135 | 136 |
data = None |
... | ... | |
438 | 439 | |
439 | 440 |
return status_action_roles |
440 | 441 | |
442 |
def anonymise(self): |
|
443 |
for field in self.formdef.fields: |
|
444 |
if field.anonymise: |
|
445 |
self.data[field.id] = None |
|
446 | ||
447 |
self.anonymised = time.localtime() |
|
448 |
self.user_id = None |
|
449 |
self.user_hash = None |
|
450 |
self.editable_by = None |
|
451 |
self._signature = None |
|
452 |
self.workflow_data = None |
|
453 |
self.workflow_roles = None |
|
454 | ||
455 |
for evo in self.evolution: |
|
456 |
evo.who = None |
|
457 |
evo.parts = None |
|
458 |
evo.comment = None |
|
459 |
evo.parts = None |
|
460 |
self.store() |
|
461 | ||
441 | 462 |
def export_to_json(self): |
442 | 463 |
data = {} |
443 | 464 |
data['id'] = '%s/%s' % (self.formdef.url_name, self.id) |
wcs/sql.py | ||
---|---|---|
112 | 112 |
user_id varchar, |
113 | 113 |
user_hash varchar, |
114 | 114 |
receipt_time timestamp, |
115 |
anonymised timestamp, |
|
115 | 116 |
status varchar, |
116 | 117 |
page_no varchar, |
117 | 118 |
workflow_data bytea, |
... | ... | |
131 | 132 | |
132 | 133 |
needed_fields = set(['id', 'user_id', 'user_hash', 'receipt_time', |
133 | 134 |
'status', 'workflow_data', 'id_display', 'fts', 'page_no', |
134 |
'workflow_roles', 'workflow_roles_array']) |
|
135 |
'anonymised', 'workflow_roles', 'workflow_roles_array'])
|
|
135 | 136 | |
136 | 137 |
# migrations |
137 | 138 |
if not 'fts' in existing_fields: |
... | ... | |
147 | 148 |
if not 'page_no' in existing_fields: |
148 | 149 |
cur.execute('''ALTER TABLE %s ADD COLUMN page_no varchar''' % table_name) |
149 | 150 | |
151 |
if not 'anonymised' in existing_fields: |
|
152 |
cur.execute('''ALTER TABLE %s ADD COLUMN anonymised timestamp''' % table_name) |
|
153 | ||
150 | 154 |
# add new fields |
151 | 155 |
for field in formdef.fields: |
152 | 156 |
assert field.id is not None |
... | ... | |
449 | 453 |
('receipt_time', 'timestamp'), |
450 | 454 |
('status', 'varchar'), |
451 | 455 |
('page_no', 'varchar'), |
456 |
('anonymised', 'timestamp'), |
|
452 | 457 |
('workflow_data', 'bytea'), |
453 | 458 |
('id_display', 'varchar'), |
454 | 459 |
('workflow_roles', 'bytea'), |
... | ... | |
506 | 511 |
'page_no': self.page_no, |
507 | 512 |
'workflow_data': bytearray(cPickle.dumps(self.workflow_data)), |
508 | 513 |
'id_display': self.id_display, |
514 |
'anonymised': self.anonymised, |
|
509 | 515 |
} |
510 | 516 |
if self.receipt_time: |
511 | 517 |
sql_dict['receipt_time'] = datetime.datetime.fromtimestamp(time.mktime(self.receipt_time)), |
512 | 518 |
else: |
513 | 519 |
sql_dict['receipt_time'] = None |
520 | ||
521 |
if self.anonymised: |
|
522 |
sql_dict['anonymised'] = datetime.datetime.fromtimestamp(time.mktime(self.anonymised)), |
|
523 |
else: |
|
524 |
sql_dict['anonymised'] = None |
|
525 | ||
514 | 526 |
if self.workflow_roles: |
515 | 527 |
sql_dict['workflow_roles'] = bytearray(cPickle.dumps(self.workflow_roles)) |
516 | 528 |
sql_dict['workflow_roles_array'] = [int(x) for x in self.workflow_roles.values()] |