0004_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 |
all_forms = self.formdef.data_class().select() |
|
717 |
for formdata in all_forms: |
|
718 |
if not formdata.anonymised: |
|
719 |
formdata.anonymise() |
|
720 |
else: |
|
721 |
import ipdb; ipdb.set_trace() |
|
722 |
if get_request().form.get('job'): |
|
723 |
return self.anonymise_processing() |
|
724 | ||
725 |
form = Form(enctype='multipart/form-data') |
|
726 | ||
727 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
728 |
"You are about to irrevocably anonymise forms which are closed."))) |
|
729 |
form.add_submit('submit', _('Submit')) |
|
730 |
form.add_submit('cancel', _('Cancel')) |
|
731 | ||
732 |
if form.get_widget('cancel').parse(): |
|
733 |
return redirect('.') |
|
734 | ||
735 |
if not form.is_submitted() or form.has_errors(): |
|
736 |
get_response().breadcrumb.append(('anonymise', _('Anonymise'))) |
|
737 |
html_top('forms', title = _('Anonymise Forms')) |
|
738 |
r = TemplateIO(html=True) |
|
739 |
r += htmltext('<h2>%s</h2>') % _('Anonymise Forms') |
|
740 |
r += form.render() |
|
741 |
return r.getvalue() |
|
742 |
else: |
|
743 |
return self.anonymise_submit(form) |
|
744 | ||
745 |
def anonymise_submit(self, form): |
|
746 | ||
747 |
class Anonymiser: |
|
748 | ||
749 |
def __init__(self, formdef): |
|
750 |
self.formdef = formdef |
|
751 | ||
752 |
def anonymise(self, job=None): |
|
753 |
all_forms = self.formdef.data_class().select() |
|
754 |
not_endpoint_status = self.formdef.workflow.get_not_endpoint_status() |
|
755 |
not_endpoint_status_ids = ['wf-%s' % x.id for x in not_endpoint_status] |
|
756 |
all_forms = [x for x in all_forms if x.status not in not_endpoint_status_ids] |
|
757 |
for formdata in all_forms: |
|
758 |
if not formdata.anonymised: |
|
759 |
formdata.anonymise() |
|
760 | ||
761 |
count = self.formdef.data_class().count() |
|
762 |
anonymiser = Anonymiser(self.formdef) |
|
763 |
if count > 100: # Arbitrary threshold |
|
764 |
job = get_response().add_after_job( |
|
765 |
str(N_('Anonymising forms')), |
|
766 |
anonymiser.anonymise) |
|
767 |
return redirect('anonymise?job=%s' % job.id) |
|
768 |
else: |
|
769 |
anonymiser.anonymise() |
|
770 | ||
771 |
return redirect('.') |
|
772 | ||
773 |
def anonymise_processing(self): |
|
774 |
try: |
|
775 |
job = AfterJob.get(get_request().form.get('job')) |
|
776 |
except KeyError: |
|
777 |
return redirect('.') |
|
778 | ||
779 |
html_top('forms', title=_('Anonymising')) |
|
780 |
r = TemplateIO(html=True) |
|
781 |
r += get_session().display_message() |
|
782 |
get_response().add_javascript(['jquery.js', 'afterjob.js']) |
|
783 |
r += htmltext('<dl class="job-status">') |
|
784 |
r += htmltext('<dt>') |
|
785 |
r += _(job.label) |
|
786 |
r += htmltext('</dt>') |
|
787 |
r += htmltext('<dd>') |
|
788 |
r += htmltext('<span class="afterjob" id="%s">') % job.id |
|
789 |
r += _(job.status) |
|
790 |
r += htmltext('</span>') |
|
791 |
r += htmltext('</dd>') |
|
792 |
r += htmltext('</dl>') |
|
793 | ||
794 |
r += htmltext('<div class="done">') |
|
795 |
r += htmltext('<a href="./">%s</a>') % _('Back') |
|
796 |
r += htmltext('</div>') |
|
797 |
return r.getvalue() |
|
798 | ||
714 | 799 |
def invite(self): |
715 | 800 |
if not self.formdef.roles: |
716 | 801 |
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): |
... | ... | |
549 | 550 | |
550 | 551 |
widget_class = CheckboxWidget |
551 | 552 |
required = False |
553 |
anonymise = False |
|
552 | 554 | |
553 | 555 |
def perform_more_widget_changes(self, form, kwargs, edit = True): |
554 | 556 |
if not edit: |
... | ... | |
628 | 630 |
key = 'file' |
629 | 631 |
description = N_('File Upload') |
630 | 632 | |
633 |
anonymise = False |
|
631 | 634 |
widget_class = FileWithPreviewWidget |
632 | 635 | |
633 | 636 |
def get_view_value(self, value): |
... | ... | |
759 | 762 | |
760 | 763 |
items = [] |
761 | 764 |
show_as_radio = False |
765 |
anonymise = False |
|
762 | 766 |
widget_class = SingleSelectHintWidget |
763 | 767 |
data_source = {} |
764 | 768 | |
... | ... | |
893 | 897 | |
894 | 898 |
items = [] |
895 | 899 |
max_choices = 0 |
900 |
anonymise = False |
|
896 | 901 | |
897 | 902 |
widget_class = CheckboxesWidget |
898 | 903 | |
... | ... | |
1311 | 1316 |
items = [] |
1312 | 1317 |
randomize_items = False |
1313 | 1318 |
widget_class = RankedItemsWidget |
1319 |
anonymise = False |
|
1314 | 1320 | |
1315 | 1321 |
def perform_more_widget_changes(self, form, kwargs, edit = True): |
1316 | 1322 |
kwargs['elements'] = self.items or [] |
wcs/formdata.py | ||
---|---|---|
15 | 15 |
# along with this program; if not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
import copy |
18 |
import datetime |
|
18 | 19 |
import json |
19 | 20 |
import time |
20 | 21 | |
... | ... | |
130 | 131 |
user_hash = None |
131 | 132 |
receipt_time = None |
132 | 133 |
status = None |
134 |
anonymised = None |
|
133 | 135 |
page_no = None # page to use when restoring from draft |
134 | 136 |
evolution = None |
135 | 137 |
data = None |
... | ... | |
438 | 440 | |
439 | 441 |
return status_action_roles |
440 | 442 | |
443 |
def anonymise(self): |
|
444 |
for field in self.formdef.fields: |
|
445 |
if field.anonymise: |
|
446 |
self.data[field.id] = None |
|
447 | ||
448 |
self.anonymised = datetime.datetime.now() |
|
449 |
self.user_id = None |
|
450 |
self.user_hash = None |
|
451 |
self.editable_by = None |
|
452 |
self._signature = None |
|
453 |
self.workflow_data = None |
|
454 |
self.workflow_roles = None |
|
455 | ||
456 |
for evo in self.evolution: |
|
457 |
evo.who = None |
|
458 |
evo.parts = None |
|
459 |
evo.comment = None |
|
460 |
evo.parts = None |
|
461 |
self.store() |
|
462 | ||
441 | 463 |
def export_to_json(self): |
442 | 464 |
data = {} |
443 | 465 |
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 timestamptz, |
|
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 timestamptz''' % 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', 'timestamptz'), |
|
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)), |