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 start_index=0): 109 110 """ 111 Return period values from fields found in 'args' prefixed with the given 112 'start_name' (for start dates), 'end_name' (for end dates), 113 'end_enabled_name' (to enable end dates for periods), 'times_enabled_name' 114 (to enable times for periods). 115 116 If 'origin' is specified, a single period with the given origin is 117 returned. If 'origin_name' is specified, fields containing the name will 118 provide origin information. 119 120 If specified, fields containing 'replacement_name' will indicate periods 121 provided by separate recurrences, fields containing 'cancelled_name' 122 will indicate periods that are replacements and cancelled, and fields 123 containing 'recurrenceid_name' will indicate periods that have existing 124 recurrence details from an event. 125 126 If 'tzid' is specified, it will provide the time zone where no explicit 127 time zone information is indicated in the field data. 128 """ 129 130 # Get the end datetime and time presence settings. 131 132 all_end_enabled = args.get(end_enabled_name, []) 133 all_times_enabled = args.get(times_enabled_name, []) 134 135 # Get the origins of period data and whether the periods are replacements. 136 137 if origin: 138 all_origins = [origin] 139 else: 140 all_origins = origin_name and args.get(origin_name, []) or [] 141 142 all_replacements = replacement_name and args.get(replacement_name, []) or [] 143 all_cancelled = cancelled_name and args.get(cancelled_name, []) or [] 144 all_recurrenceids = recurrenceid_name and args.get(recurrenceid_name, []) or [] 145 146 # Get the start and end datetimes. 147 148 all_starts = get_date_control_values(args, start_name, True, tzid=tzid) 149 all_ends = get_date_control_values(args, end_name, True, start_name, tzid=tzid) 150 151 # Construct period objects for each start, end, origin combination. 152 153 periods = [] 154 155 for index, (start, end, found_origin, recurrenceid) in \ 156 enumerate(map(None, all_starts, all_ends, all_origins, all_recurrenceids), 157 start_index): 158 159 # Obtain period settings from separate controls. 160 161 end_enabled = str(index) in all_end_enabled 162 times_enabled = str(index) in all_times_enabled 163 replacement = str(index) in all_replacements 164 cancelled = str(index) in all_cancelled 165 166 period = FormPeriod(start, end, end_enabled, times_enabled, tzid, 167 found_origin or origin, replacement, cancelled, 168 recurrenceid) 169 periods.append(period) 170 171 # Return a single period if a single origin was specified. 172 173 if origin: 174 return periods[0] 175 else: 176 return periods 177 178 def set_period_control_values(periods, args, start_name, end_name, 179 end_enabled_name, times_enabled_name, 180 origin_name=None, replacement_name=None, 181 cancelled_name=None, recurrenceid_name=None): 182 183 """ 184 Using the given 'periods', replace form fields in 'args' prefixed with the 185 given 'start_name' (for start dates), 'end_name' (for end dates), 186 'end_enabled_name' (to enable end dates for periods), 'times_enabled_name' 187 (to enable times for periods). 188 189 If 'origin_name' is specified, fields containing the name will provide 190 origin information, fields containing 'replacement_name' will indicate 191 periods provided by separate recurrences, fields containing 'cancelled_name' 192 will indicate periods that are replacements and cancelled, and fields 193 containing 'recurrenceid_name' will indicate periods that have existing 194 recurrence details from an event. 195 """ 196 197 # Record period settings separately. 198 199 args[end_enabled_name] = [] 200 args[times_enabled_name] = [] 201 202 # Record origin and replacement information if naming is defined. 203 204 if origin_name: 205 args[origin_name] = [] 206 207 if replacement_name: 208 args[replacement_name] = [] 209 210 if cancelled_name: 211 args[cancelled_name] = [] 212 213 if recurrenceid_name: 214 args[recurrenceid_name] = [] 215 216 all_starts = [] 217 all_ends = [] 218 219 for index, period in enumerate(periods): 220 221 # Encode period settings in controls. 222 223 if period.end_enabled: 224 args[end_enabled_name].append(str(index)) 225 if period.times_enabled: 226 args[times_enabled_name].append(str(index)) 227 228 # Add origin information where controls are present to record it. 229 230 if origin_name: 231 args[origin_name].append(period.origin or "") 232 233 # Add replacement information where controls are present to record it. 234 235 if replacement_name and period.replacement: 236 args[replacement_name].append(str(index)) 237 238 # Add cancelled recurrence information where controls are present to 239 # record it. 240 241 if cancelled_name and period.cancelled: 242 args[cancelled_name].append(str(index)) 243 244 # Add recurrence identifiers where controls are present to record it. 245 246 if recurrenceid_name: 247 args[recurrenceid_name].append(period.recurrenceid or "") 248 249 # Collect form date information for addition below. 250 251 all_starts.append(period.get_form_start()) 252 all_ends.append(period.get_form_end()) 253 254 # Set the controls for the dates. 255 256 set_date_control_values(all_starts, args, start_name) 257 set_date_control_values(all_ends, args, end_name, tzid_name=start_name) 258 259 260 261 # Utilities. 262 263 def filter_duplicates(l): 264 265 """ 266 Return collection 'l' filtered for duplicate values, retaining the given 267 element ordering. 268 """ 269 270 s = set() 271 f = [] 272 273 for value in l: 274 if value not in s: 275 s.add(value) 276 f.append(value) 277 278 return f 279 280 def remove_from_collection(l, indexes, fn): 281 282 """ 283 Remove from collection 'l' all values present at the given 'indexes' where 284 'fn' applied to each referenced value returns a true value. Values where 285 'fn' returns a false value are added to a list of deferred removals which is 286 returned. 287 """ 288 289 still_to_remove = [] 290 correction = 0 291 292 for i in indexes: 293 try: 294 i = int(i) - correction 295 value = l[i] 296 except (IndexError, ValueError): 297 continue 298 299 if fn(value): 300 del l[i] 301 correction += 1 302 else: 303 still_to_remove.append(value) 304 305 return still_to_remove 306 307 # vim: tabstop=4 expandtab shiftwidth=4