1 #!/usr/bin/env python 2 3 """ 4 Web user interface operations. 5 6 Copyright (C) 2014, 2015, 2017 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from imiptools.editing import FormDate, FormPeriod 23 24 # Form field extraction and serialisation. 25 26 def get_date_control_inputs(args, name, tzid_name=None): 27 28 """ 29 Return a tuple of date control inputs taken from 'args' for field names 30 starting with 'name'. 31 32 If 'tzid_name' is specified, the time zone information will be acquired 33 from fields starting with 'tzid_name' instead of 'name'. 34 """ 35 36 return args.get("%s-date" % name, []), \ 37 args.get("%s-hour" % name, []), \ 38 args.get("%s-minute" % name, []), \ 39 args.get("%s-second" % name, []), \ 40 args.get("%s-tzid" % (tzid_name or name), []) 41 42 def get_date_control_values(args, name, multiple=False, tzid_name=None, tzid=None): 43 44 """ 45 Return a form date object representing fields taken from 'args' starting 46 with 'name'. 47 48 If 'multiple' is set to a true value, many date objects will be returned 49 corresponding to a collection of datetimes. 50 51 If 'tzid_name' is specified, the time zone information will be acquired 52 from fields starting with 'tzid_name' instead of 'name'. 53 54 If 'tzid' is specified, it will provide the time zone where no explicit 55 time zone information is indicated in the field data. 56 """ 57 58 dates, hours, minutes, seconds, tzids = get_date_control_inputs(args, name, tzid_name) 59 60 # Handle absent values by employing None values. 61 62 field_values = map(None, dates, hours, minutes, seconds, tzids) 63 64 if not field_values and not multiple: 65 all_values = FormDate() 66 else: 67 all_values = [] 68 for date, hour, minute, second, tzid_field in field_values: 69 value = FormDate(date, hour, minute, second, tzid_field or tzid) 70 71 # Return a single value or append to a collection of all values. 72 73 if not multiple: 74 return value 75 else: 76 all_values.append(value) 77 78 return all_values 79 80 def set_date_control_values(formdates, args, name, tzid_name=None): 81 82 """ 83 Using the values of the given 'formdates', replace form fields in 'args' 84 starting with 'name'. 85 86 If 'tzid_name' is specified, the time zone information will be stored in 87 fields starting with 'tzid_name' instead of 'name'. 88 """ 89 90 args["%s-date" % name] = [] 91 args["%s-hour" % name] = [] 92 args["%s-minute" % name] = [] 93 args["%s-second" % name] = [] 94 args["%s-tzid" % (tzid_name or name)] = [] 95 96 for d in formdates: 97 args["%s-date" % name].append(d and d.date or "") 98 args["%s-hour" % name].append(d and d.hour or "") 99 args["%s-minute" % name].append(d and d.minute or "") 100 args["%s-second" % name].append(d and d.second or "") 101 args["%s-tzid" % (tzid_name or name)].append(d and d.tzid or "") 102 103 def get_period_control_values(args, start_name, end_name, 104 end_enabled_name, times_enabled_name, 105 origin=None, origin_name=None, 106 replacement_name=None, cancelled_name=None, 107 recurrenceid_name=None, tzid=None): 108 109 """ 110 Return period values from fields found in 'args' prefixed with the given 111 'start_name' (for start dates), 'end_name' (for end dates), 112 'end_enabled_name' (to enable end dates for periods), 'times_enabled_name' 113 (to enable times for periods). 114 115 If 'origin' is specified, a single period with the given origin is 116 returned. If 'origin_name' is specified, fields containing the name will 117 provide origin information. 118 119 If specified, fields containing 'replacement_name' will indicate periods 120 provided by separate recurrences, fields containing 'cancelled_name' 121 will indicate periods that are replacements and cancelled, and fields 122 containing 'recurrenceid_name' will indicate periods that have existing 123 recurrence details from an event. 124 125 If 'tzid' is specified, it will provide the time zone where no explicit 126 time zone information is indicated in the field data. 127 """ 128 129 # Get the end datetime and time presence settings. 130 131 all_end_enabled = args.get(end_enabled_name, []) 132 all_times_enabled = args.get(times_enabled_name, []) 133 134 # Get the origins of period data and whether the periods are replacements. 135 136 if origin: 137 all_origins = [origin] 138 else: 139 all_origins = origin_name and args.get(origin_name, []) or [] 140 141 all_replacements = replacement_name and args.get(replacement_name, []) or [] 142 all_cancelled = cancelled_name and args.get(cancelled_name, []) or [] 143 all_recurrenceids = recurrenceid_name and args.get(recurrenceid_name, []) or [] 144 145 # Get the start and end datetimes. 146 147 all_starts = get_date_control_values(args, start_name, True, tzid=tzid) 148 all_ends = get_date_control_values(args, end_name, True, start_name, tzid=tzid) 149 150 # Construct period objects for each start, end, origin combination. 151 152 periods = [] 153 154 for index, (start, end, found_origin, recurrenceid) in \ 155 enumerate(map(None, all_starts, all_ends, all_origins, all_recurrenceids)): 156 157 # Obtain period settings from separate controls. 158 159 end_enabled = str(index) in all_end_enabled 160 times_enabled = str(index) in all_times_enabled 161 replacement = str(index) in all_replacements 162 cancelled = str(index) in all_cancelled 163 164 period = FormPeriod(start, end, end_enabled, times_enabled, tzid, 165 found_origin or origin, replacement, cancelled, 166 recurrenceid) 167 periods.append(period) 168 169 # Return a single period if a single origin was specified. 170 171 if origin: 172 return periods[0] 173 else: 174 return periods 175 176 def set_period_control_values(periods, args, start_name, end_name, 177 end_enabled_name, times_enabled_name, 178 origin_name=None, replacement_name=None, 179 cancelled_name=None, recurrenceid_name=None): 180 181 """ 182 Using the given 'periods', replace form fields in 'args' prefixed with the 183 given 'start_name' (for start dates), 'end_name' (for end dates), 184 'end_enabled_name' (to enable end dates for periods), 'times_enabled_name' 185 (to enable times for periods). 186 187 If 'origin_name' is specified, fields containing the name will provide 188 origin information, fields containing 'replacement_name' will indicate 189 periods provided by separate recurrences, fields containing 'cancelled_name' 190 will indicate periods that are replacements and cancelled, and fields 191 containing 'recurrenceid_name' will indicate periods that have existing 192 recurrence details from an event. 193 """ 194 195 # Record period settings separately. 196 197 args[end_enabled_name] = [] 198 args[times_enabled_name] = [] 199 200 # Record origin and replacement information if naming is defined. 201 202 if origin_name: 203 args[origin_name] = [] 204 205 if replacement_name: 206 args[replacement_name] = [] 207 208 if cancelled_name: 209 args[cancelled_name] = [] 210 211 if recurrenceid_name: 212 args[recurrenceid_name] = [] 213 214 all_starts = [] 215 all_ends = [] 216 217 for index, period in enumerate(periods): 218 219 # Encode period settings in controls. 220 221 if period.end_enabled: 222 args[end_enabled_name].append(str(index)) 223 if period.times_enabled: 224 args[times_enabled_name].append(str(index)) 225 226 # Add origin information where controls are present to record it. 227 228 if origin_name: 229 args[origin_name].append(period.origin or "") 230 231 # Add replacement information where controls are present to record it. 232 233 if replacement_name and period.replacement: 234 args[replacement_name].append(str(index)) 235 236 # Add cancelled recurrence information where controls are present to 237 # record it. 238 239 if cancelled_name and period.cancelled: 240 args[cancelled_name].append(str(index)) 241 242 # Add recurrence identifiers where controls are present to record it. 243 244 if recurrenceid_name: 245 args[recurrenceid_name].append(period.recurrenceid or "") 246 247 # Collect form date information for addition below. 248 249 all_starts.append(period.get_form_start()) 250 all_ends.append(period.get_form_end()) 251 252 # Set the controls for the dates. 253 254 set_date_control_values(all_starts, args, start_name) 255 set_date_control_values(all_ends, args, end_name, tzid_name=start_name) 256 257 258 259 # Utilities. 260 261 def filter_duplicates(l): 262 263 """ 264 Return collection 'l' filtered for duplicate values, retaining the given 265 element ordering. 266 """ 267 268 s = set() 269 f = [] 270 271 for value in l: 272 if value not in s: 273 s.add(value) 274 f.append(value) 275 276 return f 277 278 def remove_from_collection(l, indexes, fn): 279 280 """ 281 Remove from collection 'l' all values present at the given 'indexes' where 282 'fn' applied to each referenced value returns a true value. Values where 283 'fn' returns a false value are added to a list of deferred removals which is 284 returned. 285 """ 286 287 still_to_remove = [] 288 correction = 0 289 290 for i in indexes: 291 try: 292 i = int(i) - correction 293 value = l[i] 294 except (IndexError, ValueError): 295 continue 296 297 if fn(value): 298 del l[i] 299 correction += 1 300 else: 301 still_to_remove.append(value) 302 303 return still_to_remove 304 305 # vim: tabstop=4 expandtab shiftwidth=4