~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""Views for SprintAttendance."""

__metaclass__ = type
__all__ = [
    'SprintAttendanceAttendView',
    'SprintAttendanceRegisterView',
    ]

from datetime import timedelta

import pytz

from lp import _
from lp.app.browser.launchpadform import (
    action,
    custom_widget,
    LaunchpadFormView,
    )
from lp.app.widgets.date import DateTimeWidget
from lp.app.widgets.itemswidgets import LaunchpadBooleanRadioWidget
from lp.blueprints.interfaces.sprintattendance import ISprintAttendance
from lp.services.webapp import canonical_url


class BaseSprintAttendanceAddView(LaunchpadFormView):

    schema = ISprintAttendance
    field_names = ['time_starts', 'time_ends', 'is_physical']
    custom_widget('time_starts', DateTimeWidget)
    custom_widget('time_ends', DateTimeWidget)
    custom_widget(
        'is_physical', LaunchpadBooleanRadioWidget, orientation='vertical',
        true_label="Physically", false_label="Remotely", hint=None)

    def setUpWidgets(self):
        LaunchpadFormView.setUpWidgets(self)
        tz = pytz.timezone(self.context.time_zone)
        self.starts_widget = self.widgets['time_starts']
        self.ends_widget = self.widgets['time_ends']
        self.starts_widget.required_time_zone = tz
        self.ends_widget.required_time_zone = tz
        # We don't need to display seconds
        timeformat = '%Y-%m-%d %H:%M'
        self.starts_widget.timeformat = timeformat
        self.ends_widget.timeformat = timeformat
        # Constrain the widget to dates from the day before to the day
        # after the sprint. We will accept a time just before or just after
        # and map those to the beginning and end times, respectively, in
        # self.getDates().
        from_date = self.context.time_starts.astimezone(tz)
        to_date = self.context.time_ends.astimezone(tz)
        self.starts_widget.from_date = from_date - timedelta(days=1)
        self.starts_widget.to_date = to_date
        self.ends_widget.from_date = from_date
        self.ends_widget.to_date = to_date + timedelta(days=1)

    def validate(self, data):
        """Verify that the entered times are valid.

        We check that:
         * they depart after they arrive
         * they don't arrive after the end of the sprint
         * they don't depart before the start of the sprint
        """
        time_starts = data.get('time_starts')
        time_ends = data.get('time_ends')

        if time_starts and time_starts > self.context.time_ends:
            self.setFieldError(
                'time_starts',
                _('Choose an arrival time before the end of the meeting.'))
        if time_ends:
            if time_starts and time_ends < time_starts:
                self.setFieldError(
                    'time_ends',
                    _('The end time must be after the start time.'))
            elif time_ends < self.context.time_starts:
                self.setFieldError(
                    'time_ends', _('Choose a departure time after the '
                                   'start of the meeting.'))
            elif (time_ends.hour == 0 and time_ends.minute == 0 and
                  time_ends.second == 0):
                # We assume the user entered just a date, which gives them
                # midnight in the morning of that day, when they probably want
                # the end of the day.
                data['time_ends'] = min(
                    self.context.time_ends,
                    time_ends + timedelta(days=1, seconds=-1))

    def getDates(self, data):
        time_starts = data['time_starts']
        time_ends = data['time_ends']
        if (time_ends.hour == 0 and time_ends.minute == 0 and
            time_ends.second == 0):
            # We assume the user entered just a date, which gives them
            # midnight in the morning of that day, when they probably want
            # the end of the day.
            time_ends = time_ends + timedelta(days=1, seconds=-1)
        if time_starts < self.context.time_starts:
            # Can't arrive before the conference starts, we assume that you
            # meant to say you will get there at the beginning
            time_starts = self.context.time_starts
        if time_ends > self.context.time_ends:
            # Can't stay after the conference ends, we assume that you meant
            # to say you will leave at the end.
            time_ends = self.context.time_ends
        return time_starts, time_ends

    @property
    def next_url(self):
        return canonical_url(self.context)

    cancel_url = next_url

    _local_timeformat = '%H:%M on %A, %Y-%m-%d'

    @property
    def local_start(self):
        """The sprint start time, in the local time zone, as text."""
        tz = pytz.timezone(self.context.time_zone)
        return self.context.time_starts.astimezone(tz).strftime(
                    self._local_timeformat)

    @property
    def local_end(self):
        """The sprint end time, in the local time zone, as text."""
        tz = pytz.timezone(self.context.time_zone)
        return self.context.time_ends.astimezone(tz).strftime(
                    self._local_timeformat)


class SprintAttendanceAttendView(BaseSprintAttendanceAddView):
    """A view used to register your attendance at a sprint."""

    label = "Register your attendance"

    @property
    def initial_values(self):
        """Show committed attendance, or default to the sprint times."""
        for attendance in self.context.attendances:
            if attendance.attendee == self.user:
                return dict(time_starts=attendance.time_starts,
                            time_ends=attendance.time_ends,
                            is_physical=attendance.is_physical)
        # If this person is not yet registered, then default to showing the
        # full sprint dates.
        return {'time_starts': self.context.time_starts,
                'time_ends': self.context.time_ends}

    @action(_('Register'), name='register')
    def register_action(self, action, data):
        time_starts, time_ends = self.getDates(data)
        is_physical = data['is_physical']
        self.context.attend(self.user, time_starts, time_ends, is_physical)


class SprintAttendanceRegisterView(BaseSprintAttendanceAddView):
    """A view used to register someone else's attendance at a sprint."""

    label = 'Register someone else'
    field_names = ['attendee'] + list(BaseSprintAttendanceAddView.field_names)

    @property
    def initial_values(self):
        """Default to displaying the full span of the sprint."""
        return {'time_starts': self.context.time_starts,
                'time_ends': self.context.time_ends}

    @action(_('Register'), name='register')
    def register_action(self, action, data):
        time_starts, time_ends = self.getDates(data)
        is_physical = data['is_physical']
        self.context.attend(
            data['attendee'], time_starts, time_ends, is_physical)