| | 426 | class DatePartsSelect(object): |
|---|
| | 427 | """ |
|---|
| | 428 | A date entry widget that uses three <input> elements for the day, month and |
|---|
| | 429 | year parts. |
|---|
| | 430 | |
|---|
| | 431 | The default entry format is the US (month, day, year) but can be switched to |
|---|
| | 432 | the more common (day, month, year) by setting the dayFirst attribute to |
|---|
| | 433 | True. |
|---|
| | 434 | |
|---|
| | 435 | The start and end year can be passed through but default to 1970 and 2070. |
|---|
| | 436 | |
|---|
| | 437 | The months default to non-zero prefixed numerics but can be passed as a list |
|---|
| | 438 | of label, value pairs |
|---|
| | 439 | """ |
|---|
| | 440 | implements( iformal.IWidget ) |
|---|
| | 441 | |
|---|
| | 442 | dayFirst = False |
|---|
| | 443 | days = [ (d,d) for d in xrange(1,32) ] |
|---|
| | 444 | months = [ (m,m) for m in xrange(1,13) ] |
|---|
| | 445 | yearFrom = 1970 |
|---|
| | 446 | yearTo = 2070 |
|---|
| | 447 | |
|---|
| | 448 | def __init__(self, original, dayFirst=None, yearFrom=None, yearTo=None, months=None): |
|---|
| | 449 | self.original = original |
|---|
| | 450 | if dayFirst is not None: |
|---|
| | 451 | self.dayFirst = dayFirst |
|---|
| | 452 | if yearFrom is not None: |
|---|
| | 453 | self.yearFrom = yearFrom |
|---|
| | 454 | if yearTo is not None: |
|---|
| | 455 | self.yearTo = yearTo |
|---|
| | 456 | if months is not None: |
|---|
| | 457 | self.months = months |
|---|
| | 458 | |
|---|
| | 459 | def _namer(self, prefix): |
|---|
| | 460 | def _(part): |
|---|
| | 461 | return '%s__%s' % (prefix,part) |
|---|
| | 462 | return _ |
|---|
| | 463 | |
|---|
| | 464 | def _renderTag(self, ctx, year, month, day, namer, readonly): |
|---|
| | 465 | years = [(v,v) for v in xrange(self.yearFrom,self.yearTo)] |
|---|
| | 466 | months = self.months |
|---|
| | 467 | days = self.days |
|---|
| | 468 | |
|---|
| | 469 | options = [] |
|---|
| | 470 | for value in years: |
|---|
| | 471 | if str(value[0]) == year: |
|---|
| | 472 | options.append( T.option(value=value[0],selected='selected')[value[1]] ) |
|---|
| | 473 | else: |
|---|
| | 474 | options.append( T.option(value=value[0])[value[1]] ) |
|---|
| | 475 | yearTag = T.select(name=namer('year'))[ options ] |
|---|
| | 476 | |
|---|
| | 477 | options = [] |
|---|
| | 478 | for value in months: |
|---|
| | 479 | if str(value[0]) == month: |
|---|
| | 480 | options.append( T.option(value=value[0],selected='selected')[value[1]] ) |
|---|
| | 481 | else: |
|---|
| | 482 | options.append( T.option(value=value[0])[value[1]] ) |
|---|
| | 483 | monthTag = T.select(name=namer('month'))[ options ] |
|---|
| | 484 | |
|---|
| | 485 | |
|---|
| | 486 | options = [] |
|---|
| | 487 | for value in days: |
|---|
| | 488 | if str(value[0]) == day: |
|---|
| | 489 | options.append( T.option(value=value[0],selected='selected')[value[1]] ) |
|---|
| | 490 | else: |
|---|
| | 491 | options.append( T.option(value=value[0])[value[1]] ) |
|---|
| | 492 | dayTag = T.select(name=namer('day'))[ options ] |
|---|
| | 493 | |
|---|
| | 494 | if readonly: |
|---|
| | 495 | tags = (yearTag, monthTag, dayTag) |
|---|
| | 496 | for tag in tags: |
|---|
| | 497 | tag(class_='readonly', readonly='readonly') |
|---|
| | 498 | |
|---|
| | 499 | if self.dayFirst: |
|---|
| | 500 | return dayTag, ' / ', monthTag, ' / ', yearTag, ' ', _('(day/month/year)') |
|---|
| | 501 | else: |
|---|
| | 502 | return monthTag, ' / ', dayTag, ' / ', yearTag, ' ', _('(month/day/year)') |
|---|
| | 503 | |
|---|
| | 504 | def render(self, ctx, key, args, errors): |
|---|
| | 505 | converter = iformal.IDateTupleConvertible(self.original) |
|---|
| | 506 | namer = self._namer(key) |
|---|
| | 507 | if errors: |
|---|
| | 508 | year = args.get(namer('year'), [''])[0] |
|---|
| | 509 | month = args.get(namer('month'), [''])[0] |
|---|
| | 510 | day = args.get(namer('day'), [''])[0] |
|---|
| | 511 | else: |
|---|
| | 512 | year, month, day = converter.fromType(args.get(key)) |
|---|
| | 513 | |
|---|
| | 514 | return self._renderTag(ctx, year, month, day, namer, False) |
|---|
| | 515 | |
|---|
| | 516 | def renderImmutable(self, ctx, key, args, errors): |
|---|
| | 517 | converter = iformal.IDateTupleConvertible(self.original) |
|---|
| | 518 | namer = self._namer(key) |
|---|
| | 519 | year, month, day = converter.fromType(args.get(key)) |
|---|
| | 520 | return self._renderTag(ctx, year, month, day, namer, True) |
|---|
| | 521 | |
|---|
| | 522 | def processInput(self, ctx, key, args): |
|---|
| | 523 | namer = self._namer(key) |
|---|
| | 524 | # Get the form field values as a (y,m,d) tuple |
|---|
| | 525 | ymd = [args.get(namer(part), [''])[0].strip() for part in ('year', 'month', 'day')] |
|---|
| | 526 | # Remove parts that were not entered. |
|---|
| | 527 | ymd = [p for p in ymd if p] |
|---|
| | 528 | # Nothing entered means None otherwise we need all three. |
|---|
| | 529 | if not ymd: |
|---|
| | 530 | ymd = None |
|---|
| | 531 | elif len(ymd) != 3: |
|---|
| | 532 | raise validation.FieldValidationError("Invalid date") |
|---|
| | 533 | # So, we have what looks like a good attempt to enter a date. |
|---|
| | 534 | if ymd is not None: |
|---|
| | 535 | # Map to integers |
|---|
| | 536 | try: |
|---|
| | 537 | ymd = [int(p) for p in ymd] |
|---|
| | 538 | except ValueError, e: |
|---|
| | 539 | raise validation.FieldValidationError("Invalid date") |
|---|
| | 540 | ymd = iformal.IDateTupleConvertible(self.original).toType(ymd) |
|---|
| | 541 | return self.original.validate(ymd) |
|---|
| | 542 | |
|---|
| | 543 | |
|---|