0001_anonymisation_keep_archiving.patch
wcs/admin/forms.py | ||
---|---|---|
27 | 27 |
except ImportError: |
28 | 28 |
M2Crypto = None |
29 | 29 | |
30 |
import datetime |
|
30 | 31 |
import tarfile |
31 | 32 |
import time |
32 | 33 |
from cStringIO import StringIO |
... | ... | |
206 | 207 | |
207 | 208 |
class FormDefPage(Directory): |
208 | 209 |
_q_exports = ['', 'fields', 'delete', 'duplicate', 'export', |
209 |
'archive', 'invite', 'enable', 'workflow', 'category',
|
|
210 |
'role', ('workflow-options', 'workflow_options'), |
|
210 |
'anonymise', 'archive', 'invite', 'enable', 'workflow',
|
|
211 |
'category', 'role', ('workflow-options', 'workflow_options'),
|
|
211 | 212 |
('workflow-status-remapping', 'workflow_status_remapping'), |
212 | 213 |
'roles', 'title', 'options', ('acl-read', 'acl_read')] |
213 | 214 | |
... | ... | |
386 | 387 |
r += htmltext('<li><a href="duplicate">%s</a></li>') % _('Duplicate') |
387 | 388 |
if ET: |
388 | 389 |
r += htmltext('<li><a href="export">%s</a></li>') % _('Export') |
390 |
r += htmltext('<li><a href="anonymise">%s</a></li>') % _('Anonymise handled forms') |
|
389 | 391 |
if not (get_publisher().has_site_option('postgresql') and get_cfg('postgresql', {})): |
390 | 392 |
r += htmltext('<li><a href="archive">%s</a></li>') % _('Archive') |
391 | 393 |
if self.formdef.roles: |
... | ... | |
847 | 849 |
'attachment; filename=%s-archive.wcs' % self.formdef.url_name) |
848 | 850 |
return job.file_content |
849 | 851 | |
852 |
def anonymise(self): |
|
853 |
all_forms = self.formdef.data_class().select() |
|
854 |
for formdata in all_forms: |
|
855 |
if not formdata.anonymised: |
|
856 |
formdata.anonymise() |
|
857 |
if get_request().form.get('job'): |
|
858 |
return self.anonymise_processing() |
|
859 | ||
860 |
endpoints = [] |
|
861 |
for status in self.formdef.workflow.get_endpoint_status(): |
|
862 |
endpoints.append((status.id, status.name)) |
|
863 | ||
864 |
form = Form(enctype='multipart/form-data') |
|
865 | ||
866 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
867 |
"You are about to irrevocably anonymise forms which are closed."))) |
|
868 | ||
869 | ||
870 |
form.add(DateWidget, 'before_request_date', |
|
871 |
title=_('Requests ended before'), |
|
872 |
value=datetime.date.today() - datetime.timedelta(30), |
|
873 |
required=True) |
|
874 |
form.add(CheckboxesWidget, 'endpoints', title=_('Status of the requests to anonymise'), |
|
875 |
value=[endpoint[0] for endpoint in endpoints], |
|
876 |
elements=endpoints, |
|
877 |
inline=False, |
|
878 |
required=True) |
|
879 | ||
880 |
form.add_submit('submit', _('Submit')) |
|
881 |
form.add_submit('cancel', _('Cancel')) |
|
882 | ||
883 |
if form.get_widget('cancel').parse(): |
|
884 |
return redirect('.') |
|
885 | ||
886 |
if not form.is_submitted() or form.has_errors(): |
|
887 |
get_response().breadcrumb.append(('anonymise', _('Anonymise'))) |
|
888 |
html_top('forms', title=_('Anonymise Forms')) |
|
889 |
r = TemplateIO(html=True) |
|
890 |
r += htmltext('<h2>%s</h2>') % _('Anonymise Forms') |
|
891 |
r += form.render() |
|
892 |
return r.getvalue() |
|
893 |
else: |
|
894 |
return self.anonymise_submit(form) |
|
895 | ||
896 |
def anonymise_submit(self, form): |
|
897 | ||
898 |
class Anonymiser: |
|
899 | ||
900 |
def __init__(self, formdef, status_ids, before_date): |
|
901 |
self.formdef = formdef |
|
902 |
self.status_ids = ["wf-%s" % id for id in status_ids] |
|
903 |
self.before_date = before_date |
|
904 | ||
905 |
def anonymise(self, job=None): |
|
906 |
all_forms = self.formdef.data_class().select() |
|
907 |
all_forms = [x for x in all_forms if x.status in self.status_ids] |
|
908 |
all_forms = [x for x in all_forms if ( |
|
909 |
x.evolution and x.evolution[-1].time < self.before_date) or ( |
|
910 |
x.receipt_time < self.before_date)] |
|
911 |
for formdata in all_forms: |
|
912 |
if not formdata.anonymised: |
|
913 |
formdata.anonymise() |
|
914 | ||
915 |
before_date = form.get_widget('before_request_date').parse() |
|
916 |
before_date = time.strptime(before_date, misc.date_format()) |
|
917 |
status_ids = form.get_widget('endpoints').parse() |
|
918 |
count = self.formdef.data_class().count() |
|
919 |
anonymiser = Anonymiser(self.formdef, status_ids, before_date) |
|
920 |
if count > 100: # Arbitrary threshold |
|
921 |
job = get_response().add_after_job( |
|
922 |
str(N_('Anonymising forms')), |
|
923 |
anonymiser.anonymise) |
|
924 |
return redirect('anonymise?job=%s' % job.id) |
|
925 |
else: |
|
926 |
anonymiser.anonymise() |
|
927 | ||
928 |
return redirect('.') |
|
929 | ||
930 |
def anonymise_processing(self): |
|
931 |
try: |
|
932 |
job = AfterJob.get(get_request().form.get('job')) |
|
933 |
except KeyError: |
|
934 |
return redirect('.') |
|
935 | ||
936 |
html_top('forms', title=_('Anonymising')) |
|
937 |
r = TemplateIO(html=True) |
|
938 |
r += get_session().display_message() |
|
939 |
get_response().add_javascript(['jquery.js', 'afterjob.js']) |
|
940 |
r += htmltext('<dl class="job-status">') |
|
941 |
r += htmltext('<dt>') |
|
942 |
r += _(job.label) |
|
943 |
r += htmltext('</dt>') |
|
944 |
r += htmltext('<dd>') |
|
945 |
r += htmltext('<span class="afterjob" id="%s">') % job.id |
|
946 |
r += _(job.status) |
|
947 |
r += htmltext('</span>') |
|
948 |
r += htmltext('</dd>') |
|
949 |
r += htmltext('</dl>') |
|
950 | ||
951 |
r += htmltext('<div class="done">') |
|
952 |
r += htmltext('<a href="./">%s</a>') % _('Back') |
|
953 |
r += htmltext('</div>') |
|
954 |
return r.getvalue() |
|
955 | ||
850 | 956 |
def invite(self): |
851 | 957 |
if not self.formdef.roles: |
852 | 958 |
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): |
... | ... | |
551 | 552 | |
552 | 553 |
widget_class = CheckboxWidget |
553 | 554 |
required = False |
555 |
anonymise = False |
|
554 | 556 | |
555 | 557 |
def perform_more_widget_changes(self, form, kwargs, edit = True): |
556 | 558 |
if not edit: |
... | ... | |
630 | 632 |
key = 'file' |
631 | 633 |
description = N_('File Upload') |
632 | 634 | |
635 |
anonymise = False |
|
633 | 636 |
widget_class = FileWithPreviewWidget |
634 | 637 | |
635 | 638 |
def get_view_value(self, value): |
... | ... | |
762 | 765 | |
763 | 766 |
items = [] |
764 | 767 |
show_as_radio = False |
768 |
anonymise = False |
|
765 | 769 |
widget_class = SingleSelectHintWidget |
766 | 770 |
data_source = {} |
767 | 771 | |
... | ... | |
1365 | 1369 |
items = [] |
1366 | 1370 |
randomize_items = False |
1367 | 1371 |
widget_class = RankedItemsWidget |
1372 |
anonymise = False |
|
1368 | 1373 | |
1369 | 1374 |
def perform_more_widget_changes(self, form, kwargs, edit = True): |
1370 | 1375 |
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 |
... | ... | |
441 | 443 | |
442 | 444 |
return status_action_roles |
443 | 445 | |
446 |
def anonymise(self): |
|
447 |
for field in self.formdef.fields: |
|
448 |
if field.anonymise: |
|
449 |
self.data[field.id] = None |
|
450 | ||
451 |
self.anonymised = datetime.datetime.now() |
|
452 |
self.user_id = None |
|
453 |
self.user_hash = None |
|
454 |
self.editable_by = None |
|
455 |
self._signature = None |
|
456 |
self.workflow_data = None |
|
457 |
self.workflow_roles = None |
|
458 | ||
459 |
for evo in self.evolution: |
|
460 |
evo.who = None |
|
461 |
evo.parts = None |
|
462 |
evo.comment = None |
|
463 |
evo.parts = None |
|
464 |
self.store() |
|
465 | ||
444 | 466 |
def export_to_json(self): |
445 | 467 |
data = {} |
446 | 468 |
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)), |