· 5 years ago · May 27, 2020, 02:30 PM
1import decimal
2import datetime
3
4from collections import Iterable
5from flask.ext import wtf
6
7from wtforms import validators
8from wtforms import StringField, SelectField, TextAreaField, IntegerField, BooleanField, RadioField, DecimalField, DateTimeField, HiddenField, DateField
9from wtforms.ext.sqlalchemy.fields import QuerySelectMultipleField, QuerySelectField
10from flask.ext.admin.form.widgets import Select2Widget
11from flask.ext.admin.model.form import InlineBaseFormAdmin
12from flask.ext.admin.model.fields import InlineModelFormField
13from flask.ext.admin.contrib.sqla.fields import InlineModelFormList
14from flask.ext.login import current_user
15
16from ticketer.consts import global_const as const
17from ticketer.database import db
18from ticketer.packages.certificates.api import get_coupon_error_text
19from ticketer.packages.mail.api import get_email_templates
20from ticketer.util.forms import CkeditorTextArea, MyDateTimePickerWidget, CurrencyField, NumberInput, ImageUploadField, MyDatePickerWidget
21from ticketer.packages.tour.models import TourRecurrence, Addon, AddonList
22from ticketer.packages.user.api import get_admin_users
23from ticketer.packages.common.api import get_all_franchises, get_admin_franchise
24
25from . import api, models, consts
26
27from ticketer.util.forms import strip_filter, country_choices, country_codes, check_phone
28
29
30def query_tours():
31 return db.session.query(TourRecurrence).filter_by(hidden=False).all()
32
33
34def get_addons(franchise=None, hidden=False, list_id=None):
35 franchise = franchise or get_admin_franchise()
36 query = db.session.query(Addon).join(AddonList).filter(AddonList.franchise_id == franchise.id)
37
38 if hidden is False:
39 query = query.filter(Addon.hidden == hidden)
40
41 if list_id:
42 query = query.filter(Addon.addon_list_id == list_id)
43
44 return query.order_by(Addon.priority, Addon.name).all()
45
46
47def get_addons_ignore_franchise(hidden=False, list_id=None):
48 query = db.session.query(Addon)
49
50 if hidden is False:
51 query = query.filter(Addon.hidden == hidden)
52
53 if list_id:
54 query = query.join(AddonList).filter(Addon.addon_list_id == list_id)
55
56 return query.order_by(Addon.priority, Addon.name).all()
57
58
59class MyCouponAddonQuerySelectMultipleField(QuerySelectMultipleField):
60 def iter_choices(self):
61 for pk, obj in self._get_object_list():
62 if obj.addon_list and obj.addon_list.franchise:
63 label = '%s %s' % (obj.__unicode__(), obj.addon_list.franchise.prefix_code)
64 else:
65 label = obj.__unicode__()
66 yield (pk, label, obj in self.data)
67
68
69class MyCertificateCustomPriceSelectField(SelectField):
70 def pre_validate(self, form):
71 if form.certificate.data:
72 cert = form.certificate.data
73
74 if cert and form.custom_amounts.data:
75 amounts = api.parse_certificate_custom_amount(cert)
76 if form.custom_amounts.data not in [str(a) for a in amounts]:
77 raise ValueError(self.gettext('Not a valid choice'))
78 else:
79 raise ValueError(self.gettext('Not a valid choice'))
80 else:
81 raise ValueError(self.gettext('Non-valid certificate'))
82
83
84class MyInlineModelFormField(InlineModelFormField):
85 def get_pk(self):
86 if isinstance(self._pk, Iterable):
87 return tuple(getattr(self.form, pk).data for pk in self._pk)
88 else:
89 return getattr(self.form, self._pk).data
90
91 def populate_obj(self, obj, name):
92 for name, field in self.form._fields.iteritems():
93 if name not in self._pk:
94 field.populate_obj(obj, name)
95
96
97class MyInlineModelFormList(InlineModelFormList):
98 form_field_type = MyInlineModelFormField
99
100 def populate_obj(self, obj, name):
101 values = getattr(obj, name, None)
102
103 if values is None:
104 return
105
106 # Create primary key map
107 pk_map = {}
108
109 for v in values:
110 if isinstance(self._pk, Iterable):
111 pk = tuple((str(getattr(v, key)) for key in self._pk))
112 else:
113 pk = str(getattr(v, self._pk))
114
115 pk_map[pk] = v
116
117 # Handle request data
118 for field in self.entries:
119 field_id = field.get_pk()
120
121 if field_id in pk_map:
122 model = pk_map[field_id]
123
124 if self.should_delete(field):
125 self.session.delete(model)
126 continue
127 else:
128 model = self.model()
129 self.session.add(model)
130 values.append(model)
131
132 field.populate_obj(model, None)
133
134 self.inline_view.on_model_change(field, model)
135
136
137class CouponFormBlack(wtf.Form):
138 id = HiddenField()
139 date = DateTimeField('Date',
140 format='%m/%d/%Y',
141 widget=MyDatePickerWidget(),
142 validators=[validators.Optional()])
143
144
145class CouponForm(wtf.Form):
146 name = StringField('Promotion Name', validators=[validators.Required(), validators.Length(max=256)])
147 description = TextAreaField('Front-end Description', validators=[validators.Required()], widget=CkeditorTextArea(), description='This info will show to guests on the front end - ensure to include all promo code and tour valid dates as it will also serve as the error message')
148 admin_description = TextAreaField('Admin Description', validators=[validators.Optional()], widget=CkeditorTextArea())
149 discount_type = SelectField('Promotion Type',
150 choices=const.TOUR_COUPON_TYPES,
151 coerce=int,
152 validators=[validators.Required()])
153 amount = DecimalField('Promotion Value', validators=[validators.DataRequired()])
154
155 start_date = DateTimeField('Active From',
156 format='%a, %b %d, %Y %I:%M %p',
157 widget=MyDateTimePickerWidget(),
158 validators=[validators.Optional()])
159
160 end_date = DateTimeField('Active To',
161 format='%a, %b %d, %Y %I:%M %p',
162 widget=MyDateTimePickerWidget(),
163 validators=[validators.Optional()])
164
165 tour_start_date = DateTimeField('Valid From',
166 format='%a, %b %d, %Y %I:%M %p',
167 widget=MyDateTimePickerWidget(),
168 validators=[validators.Optional()])
169
170 tour_end_date = DateTimeField('Valid To',
171 format='%a, %b %d, %Y %I:%M %p',
172 widget=MyDateTimePickerWidget(),
173 validators=[validators.Optional()])
174 blackout_date = InlineModelFormList(CouponFormBlack,
175 db.session,
176 models.CouponBlackout,
177 'blackout_date',
178 InlineBaseFormAdmin(),
179 label='Blackout Date')
180
181 allow_monday = BooleanField('Allow Monday', default=True)
182 allow_tuesday = BooleanField('Allow Tuesday', default=True)
183 allow_wednesday = BooleanField('Allow Wednesday', default=True)
184 allow_thursday = BooleanField('Allow Thursday', default=True)
185 allow_friday = BooleanField('Allow Friday', default=True)
186 allow_saturday = BooleanField('Allow Saturday', default=True)
187 allow_sunday = BooleanField('Allow Sunday', default=True)
188
189 code = StringField('Promotion Code', validators=[validators.Optional(), validators.Length(max=32)])
190 activation_max = IntegerField('Max Activations', validators=[validators.Optional()])
191 tours = QuerySelectMultipleField('Tours',
192 query_factory=query_tours,
193 allow_blank=True,
194 blank_text='-- All Tours --',
195 widget=Select2Widget(multiple=True))
196 certificates = QuerySelectMultipleField('Certificates',
197 query_factory=api.get_certificates,
198 allow_blank=True,
199 blank_text='-- All Certificates --',
200 widget=Select2Widget(multiple=True))
201 addons = MyCouponAddonQuerySelectMultipleField('Addons',
202 query_factory=get_addons_ignore_franchise,
203 allow_blank=True,
204 blank_text='-- All Addons --',
205 widget=Select2Widget(multiple=True))
206 per_person = BooleanField('Per Person', default=False)
207 # link_franchise = BooleanField('Link to Current Franchise', default=True)
208 hidden = BooleanField('Hidden')
209
210 def validate_amount(self, field):
211 if self.discount_type.data == 1 and self.amount.data < 0:
212 raise validators.ValidationError('Amount should be > 0.')
213 elif self.discount_type.data == 2 and (self.amount.data <= 0 or self.amount.data > 100):
214 raise validators.ValidationError('Amount should be > 0 and <= 100.')
215
216 def validate_end_date(self, field):
217 if self.start_date.data and self.end_date.data:
218 if self.end_date.data < self.start_date.data:
219 raise validators.ValidationError('Promo Code End Date can`t be less then Promo Code Start Date')
220
221 def validate_tour_end_date(self, field):
222 if self.tour_start_date.data and self.tour_end_date.data:
223 if self.tour_end_date.data < self.tour_start_date.data:
224 raise validators.ValidationError('Tour End Date can`t be less then Tour Start Date')
225
226
227class CertificateFranchiseForm(wtf.Form):
228 certificate_id = HiddenField()
229 franchise_id = HiddenField()
230 max_redemption = CurrencyField('Max Redemption Amount',
231 validators=[validators.InputRequired()],
232 default=decimal.Decimal('0.00'))
233 franchise = QuerySelectField('Branch',
234 query_factory=get_all_franchises,
235 allow_blank=False,
236 widget=Select2Widget())
237
238
239class CertificateForm(wtf.Form):
240 name = StringField('Gift Certificate Name', validators=[validators.Required(), validators.Length(max=256)])
241 certificate_type = SelectField('Certificate Type',
242 choices=const.TOUR_CERTIFICATE_TYPES,
243 coerce=int,
244 validators=[validators.Required()],
245 widget=Select2Widget())
246 price = CurrencyField('Price',
247 validators=[validators.Optional()],
248 default=decimal.Decimal('0.00'),
249 description='If set for an amount certificate it will always be charged instead of the actual value.')
250 custom_amounts = StringField('Custom Amounts',
251 validators=[validators.Optional()],
252 description='Enter one or more comma-separated dollar amounts.')
253 tours = QuerySelectMultipleField('Gift Certificate Tours',
254 query_factory=query_tours,
255 allow_blank=True,
256 blank_text='-- All Tours --',
257 widget=Select2Widget(multiple=True))
258 valid_from = DateField('Valid For Tour Dates From',
259 widget=MyDatePickerWidget(),
260 format='%m/%d/%Y',
261 validators=[validators.Optional()])
262 valid_to = DateField('Valid For Tour Dates To',
263 widget=MyDatePickerWidget(),
264 format='%m/%d/%Y',
265 validators=[validators.Optional()])
266 max_redemption = CurrencyField('Max Redemption Amount', validators=[validators.Optional()], default=decimal.Decimal('0.00'))
267 certificate_franchises = MyInlineModelFormList(CertificateFranchiseForm,
268 db.session,
269 models.CertificateFranchise,
270 'certificate_franchises',
271 InlineBaseFormAdmin(),
272 label='Certificate Location')
273
274 allow_monday = BooleanField('Allow Monday')
275 allow_tuesday = BooleanField('Allow Tuesday')
276 allow_wednesday = BooleanField('Allow Wednesday')
277 allow_thursday = BooleanField('Allow Thursday')
278 allow_friday = BooleanField('Allow Friday')
279 allow_saturday = BooleanField('Allow Saturday')
280 allow_sunday = BooleanField('Allow Sunday')
281
282 template = QuerySelectField('Email Template',
283 query_factory=get_email_templates,
284 allow_blank=True,
285 widget=Select2Widget())
286 email_content = TextAreaField('Email Content', widget=CkeditorTextArea())
287 pdf_content = TextAreaField('PDF Content', widget=CkeditorTextArea())
288 description = TextAreaField('Gift Certificate Description',
289 validators=[validators.Optional()],
290 widget=CkeditorTextArea())
291 image = ImageUploadField('Image')
292 active_online = BooleanField('Active')
293 display_online = BooleanField('Online')
294 priority = IntegerField('Priority', validators=[validators.Optional()])
295 hidden = BooleanField('Hidden')
296 show_in_tour_credit = BooleanField('Show in Tour Credit')
297
298 def validate_amount(self, field):
299 if self.certificate_type.data == 1 and self.amount.data < 0:
300 raise validators.ValidationError('Invalid amount.')
301
302 def validate_tours(self, field):
303 if self.certificate_type.data == const.TOUR_CERTIFICATE_TOUR and not field.data:
304 raise validators.ValidationError('Please select at least one tour.')
305
306
307class PurchaseBaseForm(wtf.Form):
308 certificate = QuerySelectField('Gift Certificate Name',
309 query_factory=api.get_certificates,
310 validators=[validators.Required()],
311 widget=Select2Widget())
312 amount_value_type = RadioField('Amount Value Type',
313 choices=const.CERTIFICATE_AMOUNT_VALUE_TYPES,
314 coerce=int,
315 default=1,
316 widget=Select2Widget(),
317 validators=[validators.Optional()])
318 new_seats_value = IntegerField('Gift Certificate Balance',
319 default=1,
320 widget=NumberInput(min_=0))
321 new_amount_value = CurrencyField('Gift Certificate Balance', validators=[validators.Optional()])
322 custom_amounts = MyCertificateCustomPriceSelectField('Choose amount', choices=(), validators=[validators.Optional()], widget=Select2Widget())
323 adjusted_amount = CurrencyField('Custom Amount', validators=[validators.Optional()])
324 status = SelectField('Gift Certificate Status', choices=const.CERTIFICATE_STATUS_TYPE, coerce=int, default=6, widget=Select2Widget())
325 first_name = StringField('Purchaser\'s First Name', validators=[validators.Required()])
326 last_name = StringField('Purchaser\'s Last Name', validators=[validators.Required()])
327 company_name = StringField('Company Name', validators=[validators.Optional()])
328 email = StringField('Purchaser\'s Email', validators=[validators.Required(), validators.Email()], filters=[strip_filter])
329 country = SelectField('Country', choices=country_choices, default='US', widget=Select2Widget())
330 phone = StringField('Purchaser\'s Cell Phone', validators=[validators.Required(), validators.Length(max=32)])
331 seats = IntegerField('Seats',
332 default=1,
333 widget=NumberInput(min_=0))
334 code = StringField('Gift Certificate Code', validators=[validators.Optional(), validators.Length(max=32)])
335 valid_to = DateField('Expiration Date',
336 format='%m/%d/%Y',
337 widget=MyDatePickerWidget(),
338 validators=[validators.Optional()])
339 valid_from = DateField('Valid From',
340 format='%m/%d/%Y',
341 widget=MyDatePickerWidget(),
342 validators=[validators.Optional()])
343
344 service_fee = DecimalField('Fees (%)', validators=[validators.Optional()])
345
346 notes = TextAreaField('Notes To Gift Recepient', widget=CkeditorTextArea())
347 internal_notes = TextAreaField('Internal Notes', widget=CkeditorTextArea())
348 billing_type = SelectField('Billing Type',
349 coerce=int,
350 choices=const.CERTIFICATE_PAYMENT_TYPE,
351 widget=Select2Widget())
352 package = QuerySelectField('Hotel Package',
353 query_factory=api.get_packages,
354 widget=Select2Widget(),
355 allow_blank=True)
356 coupon_code = StringField('Promotion Code', validators=[validators.Optional()], filters=[strip_filter])
357 charge_corkbucks = BooleanField('Charge Cork Bucks', default=False)
358 price_adjustment = CurrencyField('Price Adjustment', validators=[validators.Optional()])
359 price_adjustment_reason = TextAreaField('Price Adjustment Reason', validators=[validators.Optional()])
360
361 reserved_by = QuerySelectField('Reserved By',
362 query_factory=get_admin_users,
363 widget=Select2Widget(),
364 default=current_user)
365
366 send_email = BooleanField('Send Email', default=False)
367
368 hidden = BooleanField('Hidden')
369 old_coupon = HiddenField()
370
371 def validate_price_adjustment(form, field):
372 if field.data and not form.price_adjustment_reason.data:
373 raise validators.ValidationError('Price adjustment reason must be specified in the \'Price Adjustment Reason\' field.')
374
375 def validate_package(form, field):
376 if form.billing_type.data == const.CERTIFICATE_PAYMENT_PACKAGE and field.data is None:
377 raise validators.ValidationError('Please choose a package.')
378
379
380 def validate_seats(form, field):
381 if form.certificate.data.certificate_type == const.TOUR_CERTIFICATE_TOUR:
382 if not field.data:
383 raise validators.ValidationError('Please choose seats quantity.')
384
385 def validate_phone(form, field):
386 phone = check_phone(field.data, form.country.data)
387
388 if phone:
389 field.data = phone
390 else:
391 raise validators.ValidationError('Invalid phone number.')
392
393 def validate_coupon_code(form, field):
394 if form.old_coupon.data == field.data:
395 return
396
397 result = api.validate_coupon(field.data, cert=form.certificate.data)
398
399 if result != consts.COUPON.VALIDATED:
400 raise validators.ValidationError(get_coupon_error_text(result, field.data, show_for_frontend=False))
401
402
403class PurchaseForm(PurchaseBaseForm):
404 def validate_valid_to(form, field):
405 if field.data and field.data <= datetime.date.today():
406 raise validators.ValidationError('Can\'t make expiration date in the past.')
407
408
409class DecimalSelectField(SelectField):
410 def process_formdata(self, valuelist):
411 if valuelist:
412 try:
413 self.data = decimal.Decimal(valuelist[0])
414 except (ValueError, decimal.InvalidOperation):
415 pass
416
417
418class PurchaseFrontendForm(wtf.Form):
419 first_name = StringField('First Name', validators=[validators.Required()])
420 last_name = StringField('Last Name', validators=[validators.Required()])
421 email = StringField('Email', validators=[validators.Required(), validators.Email()], filters=[strip_filter])
422 country_code = SelectField('Country', choices=country_codes, default='US', widget=Select2Widget())
423 phone = StringField('phone', validators=[validators.Required()])
424 notes = TextAreaField('Notes')
425 policy_checkbox = BooleanField(validators=[validators.InputRequired()])
426 use_corkbucks = BooleanField('Use my Cork Bucks', default=False)
427
428 coupon_code = StringField('promo code',
429 description='promo code',
430 validators=[validators.Optional()],
431 filters=[strip_filter])
432
433 def validate_phone(self, field):
434 if field.data:
435 phone = check_phone(field.data, self.country_code.data)
436
437 if phone:
438 field.data = phone
439 else:
440 raise validators.ValidationError('Invalid phone number.')
441
442 def validate_coupon_code(form, field):
443 result = api.validate_coupon(field.data, cert=form.certificate)
444
445 if result != consts.COUPON.VALIDATED:
446 raise validators.ValidationError(get_coupon_error_text(result, field.data, show_for_frontend=False))
447
448
449class PurchaseFrontendTourCertForm(PurchaseFrontendForm):
450 seats = SelectField('Guests',
451 description='* guests',
452 choices=([(0, '* guests')] + [(x, str(x)) for x in range(1, 19)]),
453 coerce=int,
454 default=0,
455 widget=Select2Widget(),
456 validators=[validators.InputRequired()])
457
458 def validate_seats(form, field):
459 if form.seats.data == 0:
460 raise validators.ValidationError('Please select guest quantity.')
461
462
463class PurchaseFrontendAmountCertForm(PurchaseFrontendForm):
464 quantity = SelectField('Quantity',
465 description='* quantity',
466 choices=([(0, '* quantity')] + [(x, str(x)) for x in range(1, 19)]),
467 coerce=int,
468 default=0,
469 widget=Select2Widget(),
470 validators=[validators.InputRequired()])
471 custom_amounts = DecimalSelectField('Choose amount', choices=(), validators=[validators.Optional()], widget=Select2Widget())
472
473 def validate_quantity(form, field):
474 if form.quantity.data == 0:
475 raise validators.ValidationError('Please select quantity.')