Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Delta Between Two Patch Sets: sitescripts/formmail/web/formmail2.py

Issue 29374647: Issue 4814 - Adds csv log to formmail2 (Closed) Base URL: https://hg.adblockplus.org/sitescripts
Left Patch Set: address comments, now encodes user input to utf8 Created Feb. 28, 2017, 4 p.m.
Right Patch Set: Created March 23, 2017, 6:50 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « sitescripts/formmail/test/test_formmail2.py ('k') | tox.ini » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 # This file is part of the Adblock Plus web scripts, 1 # This file is part of the Adblock Plus web scripts,
2 # Copyright (C) 2006-2016 Eyeo GmbH 2 # Copyright (C) 2006-2017 eyeo GmbH
3 # 3 #
4 # Adblock Plus is free software: you can redistribute it and/or modify 4 # Adblock Plus is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License version 3 as 5 # it under the terms of the GNU General Public License version 3 as
6 # published by the Free Software Foundation. 6 # published by the Free Software Foundation.
7 # 7 #
8 # Adblock Plus is distributed in the hope that it will be useful, 8 # Adblock Plus is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details. 11 # GNU General Public License for more details.
12 # 12 #
13 # You should have received a copy of the GNU General Public License 13 # You should have received a copy of the GNU General Public License
14 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 14 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
15
16 from __future__ import print_function
17
15 import os 18 import os
19 import sys
16 import datetime 20 import datetime
21 import traceback
17 import collections 22 import collections
18 from csv import DictWriter, DictReader 23 from csv import DictWriter, DictReader
19 24
20 from sitescripts.utils import get_config, sendMail, encode_email_address 25 import jinja2
26
27 from sitescripts.utils import (get_config, sendMail, encode_email_address,
28 get_template)
21 from sitescripts.web import registerUrlHandler, form_handler 29 from sitescripts.web import registerUrlHandler, form_handler
22 30
23 31
24 def get_config_items(): 32 def get_config_items():
25 config = get_config() 33 config = get_config()
26 default_keys = set(config.defaults()) 34 default_keys = set(config.defaults())
27 for name, value in config.items('formmail2'): 35 for name, value in config.items('formmail2'):
28 if name not in default_keys: 36 if name not in default_keys:
29 yield name, value 37 yield name, value
30 38
(...skipping 15 matching lines...) Expand all
46 store_value(conf_dict, path, value) 54 store_value(conf_dict, path, value)
47 return conf_dict 55 return conf_dict
48 56
49 57
50 def make_error(spec, check_type, default_message): 58 def make_error(spec, check_type, default_message):
51 if check_type in spec: 59 if check_type in spec:
52 return spec[check_type].value 60 return spec[check_type].value
53 return default_message 61 return default_message
54 62
55 63
56 def formfield_error(parameters, log_path): 64 def log_formfield_error(parameters, log_path):
57 err_file = os.path.basename(log_path) + '_error' 65 err_file = os.path.basename(log_path) + '_error'
58 err_path = os.path.join(os.path.dirname(log_path), err_file) 66 err_path = os.path.join(os.path.dirname(log_path), err_file)
59 if os.path.isfile(err_path): 67 if os.path.isfile(err_path):
60 with open(err_path, 'a') as error_log: 68 with open(err_path, 'a') as error_log:
61 writer = DictWriter(error_log, fieldnames=parameters.keys()) 69 writer = DictWriter(error_log, fieldnames=parameters.keys())
62 writer.writerow(parameters) 70 writer.writerow(parameters)
63 raise Exception('Field names have changed, error log ' 71 raise Exception('Field names have changed, error log '
64 'appended to ' + err_path) 72 'appended to ' + err_path)
65 with open(err_path, 'w') as error_log: 73 with open(err_path, 'w') as error_log:
66 writer = DictWriter(error_log, fieldnames=parameters.keys()) 74 writer = DictWriter(error_log, fieldnames=parameters.keys())
67 writer.writeheader() 75 writer.writeheader()
68 writer.writerow(parameters) 76 writer.writerow(parameters)
69 raise Exception('Field names have changed, error log ' 77 raise Exception('Field names have changed, error log '
70 'written to ' + err_path) 78 'written to ' + err_path)
71 79
72 80
73 def collect_formdata(params, path): 81 def log_formdata(params, path):
Vasily Kuznetsov 2017/02/28 18:38:49 This function is basically adding the form data to
Jon Sonesen 2017/03/07 12:11:24 I agree
74 if os.path.isfile(path): 82 if os.path.isfile(path):
75 with open(path, 'ab+') as formlog: 83 with open(path, 'ab+') as formlog:
76 formlog.seek(0) 84 formlog.seek(0)
77 reader = DictReader(formlog) 85 reader = DictReader(formlog)
78 if reader.fieldnames != params.keys(): 86 if reader.fieldnames != params.keys():
79 formfield_error(params, path) 87 log_formfield_error(params, path)
80 formlog.seek(os.SEEK_END) 88 formlog.seek(os.SEEK_END)
81 writer = DictWriter(formlog, fieldnames=params.keys()) 89 writer = DictWriter(formlog, fieldnames=params.keys())
82 writer.writerow(params) 90 writer.writerow(params)
83 return 91 return
84 with open(path, 'w') as new_formlog: 92 with open(path, 'w') as new_formlog:
85 writer = DictWriter(new_formlog, fieldnames=params.keys()) 93 writer = DictWriter(new_formlog, fieldnames=params.keys())
86 writer.writeheader() 94 writer.writeheader()
87 writer.writerow(params) 95 writer.writerow(params)
88 return 96 return
89 97
90 98
91 def validate_fields(fields, params): 99 def validate_fields(fields, params):
92 errors = [] 100 errors = []
93 for field, spec in fields.items(): 101 for field, spec in fields.items():
94 if 'mandatory' in spec.value: 102 if 'mandatory' in spec.value and field not in params:
95 if field not in params.keys():
96 errors.append(make_error(spec, 'mandatory', 103 errors.append(make_error(spec, 'mandatory',
97 'No {} entered'.format(field))) 104 'No {} entered'.format(field)))
98 if 'email' in spec.value and field in params.keys(): 105 if 'email' in spec.value and field in params:
99 try: 106 try:
100 params[field] = encode_email_address(params[field]) 107 params[field] = encode_email_address(params[field])
101 except ValueError: 108 except ValueError:
102 errors.append(make_error(spec, 'email', 'Invalid email')) 109 errors.append(make_error(spec, 'email', 'Invalid email'))
110
111 unexpected_fields = ' '.join(set(params.keys()) - set(fields.keys()))
112 if unexpected_fields:
113 errors.append('Unexpected field/fields: ' + str(unexpected_fields))
103 return errors 114 return errors
104 115
105 116
106 def make_handler(name, config): 117 def make_handler(name, config):
107 try: 118 try:
108 log_path = config['csv_log'].value
109 except KeyError:
110 raise Exception('No log configured for form handler: ' + name)
Vasily Kuznetsov 2017/02/28 18:38:49 It seems that the log is still mandatory here. Als
Jon Sonesen 2017/03/07 12:11:25 Ok, yeah I think I meant to change that to an attr
111 try:
112 url = config['url'].value 119 url = config['url'].value
113 except (KeyError, AttributeError): 120 except (KeyError, AttributeError):
114 raise Exception('No URL configured for form handler:' + name) 121 raise Exception('No URL configured for form handler: ' + name)
115 try: 122 try:
116 template = config['template'].value 123 template = config['template'].value
117 except KeyError: 124 get_template(template, autoescape=False)
118 template = None 125 except (KeyError, AttributeError):
126 raise Exception('No template configured for form handler: ' + name)
127 except (jinja2.TemplateNotFound):
128 raise Exception('Template not found at: ' + template)
119 try: 129 try:
120 fields = config['fields'] 130 fields = config['fields']
121 for field, spec in fields.items(): 131 for field, spec in fields.items():
122 spec.value = {s.strip() for s in spec.value.split(',')} 132 spec.value = {s.strip() for s in spec.value.split(',')}
123 except KeyError: 133 except KeyError:
124 raise Exception('No fields configured for form handler:' + name) 134 raise Exception('No fields configured for form handler: ' + name)
125 if len(fields) == 0: 135 if len(fields) == 0:
126 raise Exception('No fields configured for form handler:' + name) 136 raise Exception('No fields configured for form handler: ' + name)
127 137
128 @form_handler 138 @form_handler
129 def handler(environ, start_response, params): 139 def handler(environ, start_response, params):
130 response_headers = [('Content-Type', 'text/plain; charset=utf-8')] 140 response_headers = [('Content-Type', 'text/plain; charset=utf-8')]
131 errors = validate_fields(fields, params) 141 errors = validate_fields(fields, params)
132 if errors: 142 if errors:
133 start_response('400 Bad Request', response_headers) 143 start_response('400 Bad Request', response_headers)
134 return '\n'.join(errors) 144 return '\n'.join(errors)
135 params = {field: params.get(field, '').encode('utf8')
136 for field in fields}
137 time = datetime.datetime.now() 145 time = datetime.datetime.now()
138 if template is not None: 146 template_args = {
139 template_args = { 147 'time': time,
140 'time': time, 148 'fields': {field: params.get(field, '') for field in fields}
141 'fields': {field: params.get(field, '') 149 }
Vasily Kuznetsov 2017/02/28 18:38:49 This and the following lines could fit in one line
Jon Sonesen 2017/03/07 12:11:25 I usually feel it is easier on the eyes if the key
142 for field in fields} 150 try:
143
144 }
145 sendMail(template, template_args) 151 sendMail(template, template_args)
146 params['time'] = time 152 except:
147 ollect_formdata(params, log_path) 153 print(traceback.print_exc(), file=sys.stderr)
Vasily Kuznetsov 2017/02/28 18:38:49 The first 'c' seems to have disappeared somehow ;)
Jon Sonesen 2017/03/07 12:11:25 geeez XD sorry
154 start_response('500 Server Error', response_headers)
155 return ''
156 finally:
157 if 'csv_log' in config:
158 params = {field: params.get(field, '').encode('utf8')
159 for field in fields}
160 params['time'] = time
161 log_formdata(params, config['csv_log'].value)
148 start_response('200 OK', response_headers) 162 start_response('200 OK', response_headers)
149 return '' 163 return ''
150 164
151 return url, handler 165 return url, handler
152 166
153 167
154 conf_dict = conf_parse(get_config_items()) 168 conf_dict = conf_parse(get_config_items())
155 for name, config in conf_dict.items(): 169 for name, config in conf_dict.items():
156 url, handler = make_handler(name, config) 170 url, handler = make_handler(name, config)
157 registerUrlHandler(url, handler) 171 registerUrlHandler(url, handler)
LEFTRIGHT

Powered by Google App Engine
This is Rietveld